diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/net/ethernet/sfc/mae.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/net/ethernet/sfc/mae.c')
-rw-r--r-- | drivers/net/ethernet/sfc/mae.c | 1023 |
1 files changed, 1023 insertions, 0 deletions
diff --git a/drivers/net/ethernet/sfc/mae.c b/drivers/net/ethernet/sfc/mae.c new file mode 100644 index 000000000..2d32abe5f --- /dev/null +++ b/drivers/net/ethernet/sfc/mae.c @@ -0,0 +1,1023 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + * Driver for Solarflare network controllers and boards + * Copyright 2019 Solarflare Communications Inc. + * Copyright 2020-2022 Xilinx Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#include <linux/rhashtable.h> +#include "ef100_nic.h" +#include "mae.h" +#include "mcdi.h" +#include "mcdi_pcol.h" +#include "mcdi_pcol_mae.h" + +int efx_mae_allocate_mport(struct efx_nic *efx, u32 *id, u32 *label) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_MPORT_ALLOC_ALIAS_OUT_LEN); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_MPORT_ALLOC_ALIAS_IN_LEN); + size_t outlen; + int rc; + + if (WARN_ON_ONCE(!id)) + return -EINVAL; + if (WARN_ON_ONCE(!label)) + return -EINVAL; + + MCDI_SET_DWORD(inbuf, MAE_MPORT_ALLOC_ALIAS_IN_TYPE, + MC_CMD_MAE_MPORT_ALLOC_ALIAS_IN_MPORT_TYPE_ALIAS); + MCDI_SET_DWORD(inbuf, MAE_MPORT_ALLOC_ALIAS_IN_DELIVER_MPORT, + MAE_MPORT_SELECTOR_ASSIGNED); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_MPORT_ALLOC, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + *id = MCDI_DWORD(outbuf, MAE_MPORT_ALLOC_ALIAS_OUT_MPORT_ID); + *label = MCDI_DWORD(outbuf, MAE_MPORT_ALLOC_ALIAS_OUT_LABEL); + return 0; +} + +int efx_mae_free_mport(struct efx_nic *efx, u32 id) +{ + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_MPORT_FREE_IN_LEN); + + BUILD_BUG_ON(MC_CMD_MAE_MPORT_FREE_OUT_LEN); + MCDI_SET_DWORD(inbuf, MAE_MPORT_FREE_IN_MPORT_ID, id); + return efx_mcdi_rpc(efx, MC_CMD_MAE_MPORT_FREE, inbuf, sizeof(inbuf), + NULL, 0, NULL); +} + +void efx_mae_mport_wire(struct efx_nic *efx, u32 *out) +{ + efx_dword_t mport; + + EFX_POPULATE_DWORD_2(mport, + MAE_MPORT_SELECTOR_TYPE, MAE_MPORT_SELECTOR_TYPE_PPORT, + MAE_MPORT_SELECTOR_PPORT_ID, efx->port_num); + *out = EFX_DWORD_VAL(mport); +} + +void efx_mae_mport_uplink(struct efx_nic *efx __always_unused, u32 *out) +{ + efx_dword_t mport; + + EFX_POPULATE_DWORD_3(mport, + MAE_MPORT_SELECTOR_TYPE, MAE_MPORT_SELECTOR_TYPE_FUNC, + MAE_MPORT_SELECTOR_FUNC_PF_ID, MAE_MPORT_SELECTOR_FUNC_PF_ID_CALLER, + MAE_MPORT_SELECTOR_FUNC_VF_ID, MAE_MPORT_SELECTOR_FUNC_VF_ID_NULL); + *out = EFX_DWORD_VAL(mport); +} + +void efx_mae_mport_vf(struct efx_nic *efx __always_unused, u32 vf_id, u32 *out) +{ + efx_dword_t mport; + + EFX_POPULATE_DWORD_3(mport, + MAE_MPORT_SELECTOR_TYPE, MAE_MPORT_SELECTOR_TYPE_FUNC, + MAE_MPORT_SELECTOR_FUNC_PF_ID, MAE_MPORT_SELECTOR_FUNC_PF_ID_CALLER, + MAE_MPORT_SELECTOR_FUNC_VF_ID, vf_id); + *out = EFX_DWORD_VAL(mport); +} + +/* Constructs an mport selector from an mport ID, because they're not the same */ +void efx_mae_mport_mport(struct efx_nic *efx __always_unused, u32 mport_id, u32 *out) +{ + efx_dword_t mport; + + EFX_POPULATE_DWORD_2(mport, + MAE_MPORT_SELECTOR_TYPE, MAE_MPORT_SELECTOR_TYPE_MPORT_ID, + MAE_MPORT_SELECTOR_MPORT_ID, mport_id); + *out = EFX_DWORD_VAL(mport); +} + +/* id is really only 24 bits wide */ +int efx_mae_fw_lookup_mport(struct efx_nic *efx, u32 selector, u32 *id) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_MPORT_LOOKUP_OUT_LEN); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_MPORT_LOOKUP_IN_LEN); + size_t outlen; + int rc; + + MCDI_SET_DWORD(inbuf, MAE_MPORT_LOOKUP_IN_MPORT_SELECTOR, selector); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_MPORT_LOOKUP, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + *id = MCDI_DWORD(outbuf, MAE_MPORT_LOOKUP_OUT_MPORT_ID); + return 0; +} + +int efx_mae_start_counters(struct efx_nic *efx, struct efx_rx_queue *rx_queue) +{ + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_COUNTERS_STREAM_START_V2_IN_LEN); + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_COUNTERS_STREAM_START_OUT_LEN); + u32 out_flags; + size_t outlen; + int rc; + + MCDI_SET_WORD(inbuf, MAE_COUNTERS_STREAM_START_V2_IN_QID, + efx_rx_queue_index(rx_queue)); + MCDI_SET_WORD(inbuf, MAE_COUNTERS_STREAM_START_V2_IN_PACKET_SIZE, + efx->net_dev->mtu); + MCDI_SET_DWORD(inbuf, MAE_COUNTERS_STREAM_START_V2_IN_COUNTER_TYPES_MASK, + BIT(MAE_COUNTER_TYPE_AR) | BIT(MAE_COUNTER_TYPE_CT) | + BIT(MAE_COUNTER_TYPE_OR)); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_COUNTERS_STREAM_START, + inbuf, sizeof(inbuf), outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + out_flags = MCDI_DWORD(outbuf, MAE_COUNTERS_STREAM_START_OUT_FLAGS); + if (out_flags & BIT(MC_CMD_MAE_COUNTERS_STREAM_START_OUT_USES_CREDITS_OFST)) { + netif_dbg(efx, drv, efx->net_dev, + "MAE counter stream uses credits\n"); + rx_queue->grant_credits = true; + out_flags &= ~BIT(MC_CMD_MAE_COUNTERS_STREAM_START_OUT_USES_CREDITS_OFST); + } + if (out_flags) { + netif_err(efx, drv, efx->net_dev, + "MAE counter stream start: unrecognised flags %x\n", + out_flags); + goto out_stop; + } + return 0; +out_stop: + efx_mae_stop_counters(efx, rx_queue); + return -EOPNOTSUPP; +} + +static bool efx_mae_counters_flushed(u32 *flush_gen, u32 *seen_gen) +{ + int i; + + for (i = 0; i < EFX_TC_COUNTER_TYPE_MAX; i++) + if ((s32)(flush_gen[i] - seen_gen[i]) > 0) + return false; + return true; +} + +int efx_mae_stop_counters(struct efx_nic *efx, struct efx_rx_queue *rx_queue) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_COUNTERS_STREAM_STOP_V2_OUT_LENMAX); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_COUNTERS_STREAM_STOP_IN_LEN); + size_t outlen; + int rc, i; + + MCDI_SET_WORD(inbuf, MAE_COUNTERS_STREAM_STOP_IN_QID, + efx_rx_queue_index(rx_queue)); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_COUNTERS_STREAM_STOP, + inbuf, sizeof(inbuf), outbuf, sizeof(outbuf), &outlen); + + if (rc) + return rc; + + netif_dbg(efx, drv, efx->net_dev, "Draining counters:\n"); + /* Only process received generation counts */ + for (i = 0; (i < (outlen / 4)) && (i < EFX_TC_COUNTER_TYPE_MAX); i++) { + efx->tc->flush_gen[i] = MCDI_ARRAY_DWORD(outbuf, + MAE_COUNTERS_STREAM_STOP_V2_OUT_GENERATION_COUNT, + i); + netif_dbg(efx, drv, efx->net_dev, + "\ttype %u, awaiting gen %u\n", i, + efx->tc->flush_gen[i]); + } + + efx->tc->flush_counters = true; + + /* Drain can take up to 2 seconds owing to FWRIVERHD-2884; whatever + * timeout we use, that delay is added to unload on nonresponsive + * hardware, so 2500ms seems like a reasonable compromise. + */ + if (!wait_event_timeout(efx->tc->flush_wq, + efx_mae_counters_flushed(efx->tc->flush_gen, + efx->tc->seen_gen), + msecs_to_jiffies(2500))) + netif_warn(efx, drv, efx->net_dev, + "Failed to drain counters RXQ, FW may be unhappy\n"); + + efx->tc->flush_counters = false; + + return rc; +} + +void efx_mae_counters_grant_credits(struct work_struct *work) +{ + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_COUNTERS_STREAM_GIVE_CREDITS_IN_LEN); + struct efx_rx_queue *rx_queue = container_of(work, struct efx_rx_queue, + grant_work); + struct efx_nic *efx = rx_queue->efx; + unsigned int credits; + + BUILD_BUG_ON(MC_CMD_MAE_COUNTERS_STREAM_GIVE_CREDITS_OUT_LEN); + credits = READ_ONCE(rx_queue->notified_count) - rx_queue->granted_count; + MCDI_SET_DWORD(inbuf, MAE_COUNTERS_STREAM_GIVE_CREDITS_IN_NUM_CREDITS, + credits); + if (!efx_mcdi_rpc(efx, MC_CMD_MAE_COUNTERS_STREAM_GIVE_CREDITS, + inbuf, sizeof(inbuf), NULL, 0, NULL)) + rx_queue->granted_count += credits; +} + +static int efx_mae_get_basic_caps(struct efx_nic *efx, struct mae_caps *caps) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_GET_CAPS_OUT_LEN); + size_t outlen; + int rc; + + BUILD_BUG_ON(MC_CMD_MAE_GET_CAPS_IN_LEN); + + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_GET_CAPS, NULL, 0, outbuf, + sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + caps->match_field_count = MCDI_DWORD(outbuf, MAE_GET_CAPS_OUT_MATCH_FIELD_COUNT); + caps->action_prios = MCDI_DWORD(outbuf, MAE_GET_CAPS_OUT_ACTION_PRIOS); + return 0; +} + +static int efx_mae_get_rule_fields(struct efx_nic *efx, u32 cmd, + u8 *field_support) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_GET_AR_CAPS_OUT_LEN(MAE_NUM_FIELDS)); + MCDI_DECLARE_STRUCT_PTR(caps); + unsigned int count; + size_t outlen; + int rc, i; + + BUILD_BUG_ON(MC_CMD_MAE_GET_AR_CAPS_IN_LEN); + + rc = efx_mcdi_rpc(efx, cmd, NULL, 0, outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + count = MCDI_DWORD(outbuf, MAE_GET_AR_CAPS_OUT_COUNT); + memset(field_support, MAE_FIELD_UNSUPPORTED, MAE_NUM_FIELDS); + caps = _MCDI_DWORD(outbuf, MAE_GET_AR_CAPS_OUT_FIELD_FLAGS); + /* We're only interested in the support status enum, not any other + * flags, so just extract that from each entry. + */ + for (i = 0; i < count; i++) + if (i * sizeof(*outbuf) + MC_CMD_MAE_GET_AR_CAPS_OUT_FIELD_FLAGS_OFST < outlen) + field_support[i] = EFX_DWORD_FIELD(caps[i], MAE_FIELD_FLAGS_SUPPORT_STATUS); + return 0; +} + +int efx_mae_get_caps(struct efx_nic *efx, struct mae_caps *caps) +{ + int rc; + + rc = efx_mae_get_basic_caps(efx, caps); + if (rc) + return rc; + return efx_mae_get_rule_fields(efx, MC_CMD_MAE_GET_AR_CAPS, + caps->action_rule_fields); +} + +/* Bit twiddling: + * Prefix: 1...110...0 + * ~: 0...001...1 + * + 1: 0...010...0 is power of two + * so (~x) & ((~x) + 1) == 0. Converse holds also. + */ +#define is_prefix_byte(_x) !(((_x) ^ 0xff) & (((_x) ^ 0xff) + 1)) + +enum mask_type { MASK_ONES, MASK_ZEROES, MASK_PREFIX, MASK_OTHER }; + +static const char *mask_type_name(enum mask_type typ) +{ + switch (typ) { + case MASK_ONES: + return "all-1s"; + case MASK_ZEROES: + return "all-0s"; + case MASK_PREFIX: + return "prefix"; + case MASK_OTHER: + return "arbitrary"; + default: /* can't happen */ + return "unknown"; + } +} + +/* Checks a (big-endian) bytestring is a bit prefix */ +static enum mask_type classify_mask(const u8 *mask, size_t len) +{ + bool zeroes = true; /* All bits seen so far are zeroes */ + bool ones = true; /* All bits seen so far are ones */ + bool prefix = true; /* Valid prefix so far */ + size_t i; + + for (i = 0; i < len; i++) { + if (ones) { + if (!is_prefix_byte(mask[i])) + prefix = false; + } else if (mask[i]) { + prefix = false; + } + if (mask[i] != 0xff) + ones = false; + if (mask[i]) + zeroes = false; + } + if (ones) + return MASK_ONES; + if (zeroes) + return MASK_ZEROES; + if (prefix) + return MASK_PREFIX; + return MASK_OTHER; +} + +static int efx_mae_match_check_cap_typ(u8 support, enum mask_type typ) +{ + switch (support) { + case MAE_FIELD_UNSUPPORTED: + case MAE_FIELD_SUPPORTED_MATCH_NEVER: + if (typ == MASK_ZEROES) + return 0; + return -EOPNOTSUPP; + case MAE_FIELD_SUPPORTED_MATCH_OPTIONAL: + if (typ == MASK_ZEROES) + return 0; + fallthrough; + case MAE_FIELD_SUPPORTED_MATCH_ALWAYS: + if (typ == MASK_ONES) + return 0; + return -EINVAL; + case MAE_FIELD_SUPPORTED_MATCH_PREFIX: + if (typ == MASK_OTHER) + return -EOPNOTSUPP; + return 0; + case MAE_FIELD_SUPPORTED_MATCH_MASK: + return 0; + default: + return -EIO; + } +} + +/* Validate field mask against hardware capabilities. Captures caller's 'rc' */ +#define CHECK(_mcdi, _field) ({ \ + enum mask_type typ = classify_mask((const u8 *)&mask->_field, \ + sizeof(mask->_field)); \ + \ + rc = efx_mae_match_check_cap_typ(supported_fields[MAE_FIELD_ ## _mcdi],\ + typ); \ + if (rc) \ + NL_SET_ERR_MSG_FMT_MOD(extack, \ + "No support for %s mask in field %s", \ + mask_type_name(typ), #_field); \ + rc; \ +}) +/* Booleans need special handling */ +#define CHECK_BIT(_mcdi, _field) ({ \ + enum mask_type typ = mask->_field ? MASK_ONES : MASK_ZEROES; \ + \ + rc = efx_mae_match_check_cap_typ(supported_fields[MAE_FIELD_ ## _mcdi],\ + typ); \ + if (rc) \ + NL_SET_ERR_MSG_FMT_MOD(extack, \ + "No support for %s mask in field %s", \ + mask_type_name(typ), #_field); \ + rc; \ +}) + +int efx_mae_match_check_caps(struct efx_nic *efx, + const struct efx_tc_match_fields *mask, + struct netlink_ext_ack *extack) +{ + const u8 *supported_fields = efx->tc->caps->action_rule_fields; + __be32 ingress_port = cpu_to_be32(mask->ingress_port); + enum mask_type ingress_port_mask_type; + int rc; + + /* Check for _PREFIX assumes big-endian, so we need to convert */ + ingress_port_mask_type = classify_mask((const u8 *)&ingress_port, + sizeof(ingress_port)); + rc = efx_mae_match_check_cap_typ(supported_fields[MAE_FIELD_INGRESS_PORT], + ingress_port_mask_type); + if (rc) { + NL_SET_ERR_MSG_FMT_MOD(extack, "No support for %s mask in field ingress_port", + mask_type_name(ingress_port_mask_type)); + return rc; + } + if (CHECK(ETHER_TYPE, eth_proto) || + CHECK(VLAN0_TCI, vlan_tci[0]) || + CHECK(VLAN0_PROTO, vlan_proto[0]) || + CHECK(VLAN1_TCI, vlan_tci[1]) || + CHECK(VLAN1_PROTO, vlan_proto[1]) || + CHECK(ETH_SADDR, eth_saddr) || + CHECK(ETH_DADDR, eth_daddr) || + CHECK(IP_PROTO, ip_proto) || + CHECK(IP_TOS, ip_tos) || + CHECK(IP_TTL, ip_ttl) || + CHECK(SRC_IP4, src_ip) || + CHECK(DST_IP4, dst_ip) || +#ifdef CONFIG_IPV6 + CHECK(SRC_IP6, src_ip6) || + CHECK(DST_IP6, dst_ip6) || +#endif + CHECK(L4_SPORT, l4_sport) || + CHECK(L4_DPORT, l4_dport) || + CHECK(TCP_FLAGS, tcp_flags) || + CHECK_BIT(IS_IP_FRAG, ip_frag) || + CHECK_BIT(IP_FIRST_FRAG, ip_firstfrag) || + CHECK(RECIRC_ID, recirc_id)) + return rc; + return 0; +} +#undef CHECK_BIT +#undef CHECK + +int efx_mae_allocate_counter(struct efx_nic *efx, struct efx_tc_counter *cnt) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_COUNTER_ALLOC_OUT_LEN(1)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_COUNTER_ALLOC_V2_IN_LEN); + size_t outlen; + int rc; + + if (!cnt) + return -EINVAL; + + MCDI_SET_DWORD(inbuf, MAE_COUNTER_ALLOC_V2_IN_REQUESTED_COUNT, 1); + MCDI_SET_DWORD(inbuf, MAE_COUNTER_ALLOC_V2_IN_COUNTER_TYPE, cnt->type); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_COUNTER_ALLOC, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + /* pcol says this can't happen, since count is 1 */ + if (outlen < sizeof(outbuf)) + return -EIO; + cnt->fw_id = MCDI_DWORD(outbuf, MAE_COUNTER_ALLOC_OUT_COUNTER_ID); + cnt->gen = MCDI_DWORD(outbuf, MAE_COUNTER_ALLOC_OUT_GENERATION_COUNT); + return 0; +} + +int efx_mae_free_counter(struct efx_nic *efx, struct efx_tc_counter *cnt) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_COUNTER_FREE_OUT_LEN(1)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_COUNTER_FREE_V2_IN_LEN); + size_t outlen; + int rc; + + MCDI_SET_DWORD(inbuf, MAE_COUNTER_FREE_V2_IN_COUNTER_ID_COUNT, 1); + MCDI_SET_DWORD(inbuf, MAE_COUNTER_FREE_V2_IN_FREE_COUNTER_ID, cnt->fw_id); + MCDI_SET_DWORD(inbuf, MAE_COUNTER_FREE_V2_IN_COUNTER_TYPE, cnt->type); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_COUNTER_FREE, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + /* pcol says this can't happen, since count is 1 */ + if (outlen < sizeof(outbuf)) + return -EIO; + /* FW freed a different ID than we asked for, should also never happen. + * Warn because it means we've now got a different idea to the FW of + * what counters exist, which could cause mayhem later. + */ + if (WARN_ON(MCDI_DWORD(outbuf, MAE_COUNTER_FREE_OUT_FREED_COUNTER_ID) != + cnt->fw_id)) + return -EIO; + return 0; +} + +int efx_mae_lookup_mport(struct efx_nic *efx, u32 vf_idx, u32 *id) +{ + struct ef100_nic_data *nic_data = efx->nic_data; + struct efx_mae *mae = efx->mae; + struct rhashtable_iter walk; + struct mae_mport_desc *m; + int rc = -ENOENT; + + rhashtable_walk_enter(&mae->mports_ht, &walk); + rhashtable_walk_start(&walk); + while ((m = rhashtable_walk_next(&walk)) != NULL) { + if (m->mport_type == MAE_MPORT_DESC_MPORT_TYPE_VNIC && + m->interface_idx == nic_data->local_mae_intf && + m->pf_idx == 0 && + m->vf_idx == vf_idx) { + *id = m->mport_id; + rc = 0; + break; + } + } + rhashtable_walk_stop(&walk); + rhashtable_walk_exit(&walk); + return rc; +} + +static bool efx_mae_asl_id(u32 id) +{ + return !!(id & BIT(31)); +} + +/* mport handling */ +static const struct rhashtable_params efx_mae_mports_ht_params = { + .key_len = sizeof(u32), + .key_offset = offsetof(struct mae_mport_desc, mport_id), + .head_offset = offsetof(struct mae_mport_desc, linkage), +}; + +struct mae_mport_desc *efx_mae_get_mport(struct efx_nic *efx, u32 mport_id) +{ + return rhashtable_lookup_fast(&efx->mae->mports_ht, &mport_id, + efx_mae_mports_ht_params); +} + +static int efx_mae_add_mport(struct efx_nic *efx, struct mae_mport_desc *desc) +{ + struct efx_mae *mae = efx->mae; + int rc; + + rc = rhashtable_insert_fast(&mae->mports_ht, &desc->linkage, + efx_mae_mports_ht_params); + + if (rc) { + pci_err(efx->pci_dev, "Failed to insert MPORT %08x, rc %d\n", + desc->mport_id, rc); + kfree(desc); + return rc; + } + + return rc; +} + +void efx_mae_remove_mport(void *desc, void *arg) +{ + struct mae_mport_desc *mport = desc; + + synchronize_rcu(); + kfree(mport); +} + +static int efx_mae_process_mport(struct efx_nic *efx, + struct mae_mport_desc *desc) +{ + struct ef100_nic_data *nic_data = efx->nic_data; + struct mae_mport_desc *mport; + + mport = efx_mae_get_mport(efx, desc->mport_id); + if (!IS_ERR_OR_NULL(mport)) { + netif_err(efx, drv, efx->net_dev, + "mport with id %u does exist!!!\n", desc->mport_id); + return -EEXIST; + } + + if (nic_data->have_own_mport && + desc->mport_id == nic_data->own_mport) { + WARN_ON(desc->mport_type != MAE_MPORT_DESC_MPORT_TYPE_VNIC); + WARN_ON(desc->vnic_client_type != + MAE_MPORT_DESC_VNIC_CLIENT_TYPE_FUNCTION); + nic_data->local_mae_intf = desc->interface_idx; + nic_data->have_local_intf = true; + pci_dbg(efx->pci_dev, "MAE interface_idx is %u\n", + nic_data->local_mae_intf); + } + + return efx_mae_add_mport(efx, desc); +} + +#define MCDI_MPORT_JOURNAL_LEN \ + ALIGN(MC_CMD_MAE_MPORT_READ_JOURNAL_OUT_LENMAX_MCDI2, 4) + +int efx_mae_enumerate_mports(struct efx_nic *efx) +{ + efx_dword_t *outbuf = kzalloc(MCDI_MPORT_JOURNAL_LEN, GFP_KERNEL); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_MPORT_READ_JOURNAL_IN_LEN); + MCDI_DECLARE_STRUCT_PTR(desc); + size_t outlen, stride, count; + int rc = 0, i; + + if (!outbuf) + return -ENOMEM; + do { + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_MPORT_READ_JOURNAL, inbuf, + sizeof(inbuf), outbuf, + MCDI_MPORT_JOURNAL_LEN, &outlen); + if (rc) + goto fail; + if (outlen < MC_CMD_MAE_MPORT_READ_JOURNAL_OUT_MPORT_DESC_DATA_OFST) { + rc = -EIO; + goto fail; + } + count = MCDI_DWORD(outbuf, MAE_MPORT_READ_JOURNAL_OUT_MPORT_DESC_COUNT); + if (!count) + continue; /* not break; we want to look at MORE flag */ + stride = MCDI_DWORD(outbuf, MAE_MPORT_READ_JOURNAL_OUT_SIZEOF_MPORT_DESC); + if (stride < MAE_MPORT_DESC_LEN) { + rc = -EIO; + goto fail; + } + if (outlen < MC_CMD_MAE_MPORT_READ_JOURNAL_OUT_LEN(count * stride)) { + rc = -EIO; + goto fail; + } + + for (i = 0; i < count; i++) { + struct mae_mport_desc *d; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) { + rc = -ENOMEM; + goto fail; + } + + desc = (efx_dword_t *) + _MCDI_PTR(outbuf, MC_CMD_MAE_MPORT_READ_JOURNAL_OUT_MPORT_DESC_DATA_OFST + + i * stride); + d->mport_id = MCDI_STRUCT_DWORD(desc, MAE_MPORT_DESC_MPORT_ID); + d->flags = MCDI_STRUCT_DWORD(desc, MAE_MPORT_DESC_FLAGS); + d->caller_flags = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_CALLER_FLAGS); + d->mport_type = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_MPORT_TYPE); + switch (d->mport_type) { + case MAE_MPORT_DESC_MPORT_TYPE_NET_PORT: + d->port_idx = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_NET_PORT_IDX); + break; + case MAE_MPORT_DESC_MPORT_TYPE_ALIAS: + d->alias_mport_id = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_ALIAS_DELIVER_MPORT_ID); + break; + case MAE_MPORT_DESC_MPORT_TYPE_VNIC: + d->vnic_client_type = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_VNIC_CLIENT_TYPE); + d->interface_idx = MCDI_STRUCT_DWORD(desc, + MAE_MPORT_DESC_VNIC_FUNCTION_INTERFACE); + d->pf_idx = MCDI_STRUCT_WORD(desc, + MAE_MPORT_DESC_VNIC_FUNCTION_PF_IDX); + d->vf_idx = MCDI_STRUCT_WORD(desc, + MAE_MPORT_DESC_VNIC_FUNCTION_VF_IDX); + break; + default: + /* Unknown mport_type, just accept it */ + break; + } + rc = efx_mae_process_mport(efx, d); + /* Any failure will be due to memory allocation faiure, + * so there is no point to try subsequent entries. + */ + if (rc) + goto fail; + } + } while (MCDI_FIELD(outbuf, MAE_MPORT_READ_JOURNAL_OUT, MORE) && + !WARN_ON(!count)); +fail: + kfree(outbuf); + return rc; +} + +int efx_mae_alloc_action_set(struct efx_nic *efx, struct efx_tc_action_set *act) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_SET_ALLOC_OUT_LEN); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_SET_ALLOC_IN_LEN); + size_t outlen; + int rc; + + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_SRC_MAC_ID, + MC_CMD_MAE_MAC_ADDR_ALLOC_OUT_MAC_ID_NULL); + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_DST_MAC_ID, + MC_CMD_MAE_MAC_ADDR_ALLOC_OUT_MAC_ID_NULL); + if (act->count && !WARN_ON(!act->count->cnt)) + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_COUNTER_ID, + act->count->cnt->fw_id); + else + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_COUNTER_ID, + MC_CMD_MAE_COUNTER_ALLOC_OUT_COUNTER_ID_NULL); + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_COUNTER_LIST_ID, + MC_CMD_MAE_COUNTER_LIST_ALLOC_OUT_COUNTER_LIST_ID_NULL); + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_ENCAP_HEADER_ID, + MC_CMD_MAE_ENCAP_HEADER_ALLOC_OUT_ENCAP_HEADER_ID_NULL); + if (act->deliver) + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_ALLOC_IN_DELIVER, + act->dest_mport); + BUILD_BUG_ON(MAE_MPORT_SELECTOR_NULL); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_SET_ALLOC, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + act->fw_id = MCDI_DWORD(outbuf, MAE_ACTION_SET_ALLOC_OUT_AS_ID); + /* We rely on the high bit of AS IDs always being clear. + * The firmware API guarantees this, but let's check it ourselves. + */ + if (WARN_ON_ONCE(efx_mae_asl_id(act->fw_id))) { + efx_mae_free_action_set(efx, act->fw_id); + return -EIO; + } + return 0; +} + +int efx_mae_free_action_set(struct efx_nic *efx, u32 fw_id) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_SET_FREE_OUT_LEN(1)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_SET_FREE_IN_LEN(1)); + size_t outlen; + int rc; + + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_FREE_IN_AS_ID, fw_id); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_SET_FREE, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + /* FW freed a different ID than we asked for, should never happen. + * Warn because it means we've now got a different idea to the FW of + * what action-sets exist, which could cause mayhem later. + */ + if (WARN_ON(MCDI_DWORD(outbuf, MAE_ACTION_SET_FREE_OUT_FREED_AS_ID) != fw_id)) + return -EIO; + return 0; +} + +int efx_mae_alloc_action_set_list(struct efx_nic *efx, + struct efx_tc_action_set_list *acts) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_SET_LIST_ALLOC_OUT_LEN); + struct efx_tc_action_set *act; + size_t inlen, outlen, i = 0; + efx_dword_t *inbuf; + int rc; + + list_for_each_entry(act, &acts->list, list) + i++; + if (i == 0) + return -EINVAL; + if (i == 1) { + /* Don't wrap an ASL around a single AS, just use the AS_ID + * directly. ASLs are a more limited resource. + */ + act = list_first_entry(&acts->list, struct efx_tc_action_set, list); + acts->fw_id = act->fw_id; + return 0; + } + if (i > MC_CMD_MAE_ACTION_SET_LIST_ALLOC_IN_AS_IDS_MAXNUM_MCDI2) + return -EOPNOTSUPP; /* Too many actions */ + inlen = MC_CMD_MAE_ACTION_SET_LIST_ALLOC_IN_LEN(i); + inbuf = kzalloc(inlen, GFP_KERNEL); + if (!inbuf) + return -ENOMEM; + i = 0; + list_for_each_entry(act, &acts->list, list) { + MCDI_SET_ARRAY_DWORD(inbuf, MAE_ACTION_SET_LIST_ALLOC_IN_AS_IDS, + i, act->fw_id); + i++; + } + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_LIST_ALLOC_IN_COUNT, i); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_SET_LIST_ALLOC, inbuf, inlen, + outbuf, sizeof(outbuf), &outlen); + if (rc) + goto out_free; + if (outlen < sizeof(outbuf)) { + rc = -EIO; + goto out_free; + } + acts->fw_id = MCDI_DWORD(outbuf, MAE_ACTION_SET_LIST_ALLOC_OUT_ASL_ID); + /* We rely on the high bit of ASL IDs always being set. + * The firmware API guarantees this, but let's check it ourselves. + */ + if (WARN_ON_ONCE(!efx_mae_asl_id(acts->fw_id))) { + efx_mae_free_action_set_list(efx, acts); + rc = -EIO; + } +out_free: + kfree(inbuf); + return rc; +} + +int efx_mae_free_action_set_list(struct efx_nic *efx, + struct efx_tc_action_set_list *acts) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_SET_LIST_FREE_OUT_LEN(1)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_SET_LIST_FREE_IN_LEN(1)); + size_t outlen; + int rc; + + /* If this is just an AS_ID with no ASL wrapper, then there is + * nothing for us to free. (The AS will be freed later.) + */ + if (efx_mae_asl_id(acts->fw_id)) { + MCDI_SET_DWORD(inbuf, MAE_ACTION_SET_LIST_FREE_IN_ASL_ID, + acts->fw_id); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_SET_LIST_FREE, inbuf, + sizeof(inbuf), outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + /* FW freed a different ID than we asked for, should never happen. + * Warn because it means we've now got a different idea to the FW of + * what action-set-lists exist, which could cause mayhem later. + */ + if (WARN_ON(MCDI_DWORD(outbuf, MAE_ACTION_SET_LIST_FREE_OUT_FREED_ASL_ID) != acts->fw_id)) + return -EIO; + } + /* We're probably about to free @acts, but let's just make sure its + * fw_id is blatted so that it won't look valid if it leaks out. + */ + acts->fw_id = MC_CMD_MAE_ACTION_SET_LIST_ALLOC_OUT_ACTION_SET_LIST_ID_NULL; + return 0; +} + +static int efx_mae_populate_match_criteria(MCDI_DECLARE_STRUCT_PTR(match_crit), + const struct efx_tc_match *match) +{ + if (match->mask.ingress_port) { + if (~match->mask.ingress_port) + return -EOPNOTSUPP; + MCDI_STRUCT_SET_DWORD(match_crit, + MAE_FIELD_MASK_VALUE_PAIRS_V2_INGRESS_MPORT_SELECTOR, + match->value.ingress_port); + } + MCDI_STRUCT_SET_DWORD(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_INGRESS_MPORT_SELECTOR_MASK, + match->mask.ingress_port); + EFX_POPULATE_DWORD_2(*_MCDI_STRUCT_DWORD(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_FLAGS), + MAE_FIELD_MASK_VALUE_PAIRS_V2_IS_IP_FRAG, + match->value.ip_frag, + MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_FIRST_FRAG, + match->value.ip_firstfrag); + EFX_POPULATE_DWORD_2(*_MCDI_STRUCT_DWORD(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_FLAGS_MASK), + MAE_FIELD_MASK_VALUE_PAIRS_V2_IS_IP_FRAG, + match->mask.ip_frag, + MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_FIRST_FRAG, + match->mask.ip_firstfrag); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_RECIRC_ID, + match->value.recirc_id); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_RECIRC_ID_MASK, + match->mask.recirc_id); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETHER_TYPE_BE, + match->value.eth_proto); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETHER_TYPE_BE_MASK, + match->mask.eth_proto); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN0_TCI_BE, + match->value.vlan_tci[0]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN0_TCI_BE_MASK, + match->mask.vlan_tci[0]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN0_PROTO_BE, + match->value.vlan_proto[0]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN0_PROTO_BE_MASK, + match->mask.vlan_proto[0]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN1_TCI_BE, + match->value.vlan_tci[1]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN1_TCI_BE_MASK, + match->mask.vlan_tci[1]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN1_PROTO_BE, + match->value.vlan_proto[1]); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_VLAN1_PROTO_BE_MASK, + match->mask.vlan_proto[1]); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETH_SADDR_BE), + match->value.eth_saddr, ETH_ALEN); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETH_SADDR_BE_MASK), + match->mask.eth_saddr, ETH_ALEN); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETH_DADDR_BE), + match->value.eth_daddr, ETH_ALEN); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_ETH_DADDR_BE_MASK), + match->mask.eth_daddr, ETH_ALEN); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_PROTO, + match->value.ip_proto); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_PROTO_MASK, + match->mask.ip_proto); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_TOS, + match->value.ip_tos); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_TOS_MASK, + match->mask.ip_tos); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_TTL, + match->value.ip_ttl); + MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_IP_TTL_MASK, + match->mask.ip_ttl); + MCDI_STRUCT_SET_DWORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_SRC_IP4_BE, + match->value.src_ip); + MCDI_STRUCT_SET_DWORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_SRC_IP4_BE_MASK, + match->mask.src_ip); + MCDI_STRUCT_SET_DWORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_DST_IP4_BE, + match->value.dst_ip); + MCDI_STRUCT_SET_DWORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_DST_IP4_BE_MASK, + match->mask.dst_ip); +#ifdef CONFIG_IPV6 + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_SRC_IP6_BE), + &match->value.src_ip6, sizeof(struct in6_addr)); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_SRC_IP6_BE_MASK), + &match->mask.src_ip6, sizeof(struct in6_addr)); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_DST_IP6_BE), + &match->value.dst_ip6, sizeof(struct in6_addr)); + memcpy(MCDI_STRUCT_PTR(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_DST_IP6_BE_MASK), + &match->mask.dst_ip6, sizeof(struct in6_addr)); +#endif + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_L4_SPORT_BE, + match->value.l4_sport); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_L4_SPORT_BE_MASK, + match->mask.l4_sport); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_L4_DPORT_BE, + match->value.l4_dport); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_L4_DPORT_BE_MASK, + match->mask.l4_dport); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_TCP_FLAGS_BE, + match->value.tcp_flags); + MCDI_STRUCT_SET_WORD_BE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_TCP_FLAGS_BE_MASK, + match->mask.tcp_flags); + return 0; +} + +int efx_mae_insert_rule(struct efx_nic *efx, const struct efx_tc_match *match, + u32 prio, u32 acts_id, u32 *id) +{ + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_RULE_INSERT_IN_LEN(MAE_FIELD_MASK_VALUE_PAIRS_V2_LEN)); + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_RULE_INSERT_OUT_LEN); + MCDI_DECLARE_STRUCT_PTR(match_crit); + MCDI_DECLARE_STRUCT_PTR(response); + size_t outlen; + int rc; + + if (!id) + return -EINVAL; + + match_crit = _MCDI_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_MATCH_CRITERIA); + response = _MCDI_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_RESPONSE); + if (efx_mae_asl_id(acts_id)) { + MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_ASL_ID, acts_id); + MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_AS_ID, + MC_CMD_MAE_ACTION_SET_ALLOC_OUT_ACTION_SET_ID_NULL); + } else { + /* We only had one AS, so we didn't wrap it in an ASL */ + MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_ASL_ID, + MC_CMD_MAE_ACTION_SET_LIST_ALLOC_OUT_ACTION_SET_LIST_ID_NULL); + MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_AS_ID, acts_id); + } + MCDI_SET_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_PRIO, prio); + rc = efx_mae_populate_match_criteria(match_crit, match); + if (rc) + return rc; + + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_RULE_INSERT, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + *id = MCDI_DWORD(outbuf, MAE_ACTION_RULE_INSERT_OUT_AR_ID); + return 0; +} + +int efx_mae_delete_rule(struct efx_nic *efx, u32 id) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_RULE_DELETE_OUT_LEN(1)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_RULE_DELETE_IN_LEN(1)); + size_t outlen; + int rc; + + MCDI_SET_DWORD(inbuf, MAE_ACTION_RULE_DELETE_IN_AR_ID, id); + rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_RULE_DELETE, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + return rc; + if (outlen < sizeof(outbuf)) + return -EIO; + /* FW freed a different ID than we asked for, should also never happen. + * Warn because it means we've now got a different idea to the FW of + * what rules exist, which could cause mayhem later. + */ + if (WARN_ON(MCDI_DWORD(outbuf, MAE_ACTION_RULE_DELETE_OUT_DELETED_AR_ID) != id)) + return -EIO; + return 0; +} + +int efx_init_mae(struct efx_nic *efx) +{ + struct ef100_nic_data *nic_data = efx->nic_data; + struct efx_mae *mae; + int rc; + + if (!nic_data->have_mport) + return -EINVAL; + + mae = kmalloc(sizeof(*mae), GFP_KERNEL); + if (!mae) + return -ENOMEM; + + rc = rhashtable_init(&mae->mports_ht, &efx_mae_mports_ht_params); + if (rc < 0) { + kfree(mae); + return rc; + } + efx->mae = mae; + mae->efx = efx; + return 0; +} + +void efx_fini_mae(struct efx_nic *efx) +{ + struct efx_mae *mae = efx->mae; + + kfree(mae); + efx->mae = NULL; +} |