* [PATCH net-next v3 00/13] net: lan966x: add support for PCIe FDMA
From: Daniel Machon @ 2026-05-04 14:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Horatiu Vultur, Steen Hegelund, UNGLinuxDriver,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Herve Codina, Arnd Bergmann,
Greg Kroah-Hartman, Mohsin Bashir
Cc: netdev, linux-kernel, bpf, linux-arm-kernel
When lan966x operates as a PCIe endpoint, the driver currently uses
register-based I/O for frame injection and extraction. This approach is
functional but slow, topping out at around 33 Mbps on an Intel x86 host
with a lan966x PCIe card.
This series adds FDMA (Frame DMA) support for the PCIe path. When
operating as a PCIe endpoint, the internal FDMA engine on lan966x cannot
directly access host memory, so DMA buffers are allocated as contiguous
coherent memory and mapped through the PCIe Address Translation Unit
(ATU). The ATU provides outbound windows that translate internal FDMA
addresses to PCIe bus addresses, allowing the FDMA engine to read and
write host memory. Because the ATU requires contiguous address regions,
page_pool and normal per-page DMA mappings cannot be used. Instead,
frames are transferred using memcpy between the ATU-mapped buffers and
the network stack. With this, throughput increases from ~33 Mbps to
~620 Mbps for default MTU.
Patch 1 adds the shared drivers/net/ethernet/microchip/fdma/ directory
to the Sparx5 SoC MAINTAINERS entry.
Patches 2-3 prepare the shared FDMA library: patch 2 renames the
contiguous dataptr helpers for clarity, and patch 3 adds PCIe ATU
region management and coherent DMA allocation with ATU mapping.
Patches 4-6 refactor the lan966x FDMA code to support both platform
and PCIe paths: extracting the LLP register write into a helper,
exporting shared functions, and introducing an ops dispatch table
selected at probe time.
Patches 7-8 harden the existing FDMA path for the PCIe endpoint
lifecycle: patch 7 clears latched FDMA error/interrupt stickies after
the switch reset so they don't assert as soon as interrupts are
enabled, and patch 8 adds a shutdown() callback that quiesces the
FDMA engine on host warm reboot (on the PCIe card the FDMA survives
host reset and would otherwise keep the shared INTx asserted into
the next probe).
Patch 9 adds the core PCIe FDMA implementation with RX/TX using
contiguous ATU-mapped buffers. Patches 10 and 11 extend it with MTU
change and XDP support respectively. XDP_PASS, XDP_TX, XDP_DROP and
XDP_ABORTED are supported; XDP_REDIRECT is deliberately not, because
the PCIe data path does not use page_pool.
Patches 12-13 update the lan966x PCI device tree overlay to extend the
cpu register mapping to cover the ATU register space and add the FDMA
interrupt.
To: Andrew Lunn <andrew+netdev@lunn.ch>
To: David S. Miller <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Horatiu Vultur <horatiu.vultur@microchip.com>
To: Steen Hegelund <steen.hegelund@microchip.com>
To: UNGLinuxDriver@microchip.com
To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: Jesper Dangaard Brouer <hawk@kernel.org>
To: John Fastabend <john.fastabend@gmail.com>
To: Stanislav Fomichev <sdf@fomichev.me>
To: Herve Codina <herve.codina@bootlin.com>
To: Arnd Bergmann <arnd@arndb.de>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: Mohsin Bashir <mohsin.bashr@gmail.com>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: bpf@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
Changes in v3:
Version 3 fixes a number of issues reported by sashiko - mostly
hardening.
- Fix double use of XDP_PACKET_HEADROOM.
- Fix ERR_PTR persistence in fdma->atu_region and add missing
NULL/ERR_PTR guard in fdma_pci_atu_region_unmap().
- Reject size <= 0 in fdma_pci_atu_region_map() and return
-ENOSPC (was -ENOMEM) when no region is free.
- Introduce lan966x_fdma_pci_tx_size_fits() that accounts for
XDP_PACKET_HEADROOM; use it from both xmit paths to keep
bpf_xdp_adjust_tail from writing past the TX slot.
- Validate BLOCKL in rx_check_frame() (reject < IFH+FCS or
> db_size) before it feeds memcpy/XDP sizes.
- READ_ONCE(port->xdp_prog) inside lan966x_xdp_pci_run() to close
a TOCTOU on XDP detach that could deref NULL in
bpf_prog_run_xdp().
- Strip IFH and FCS pre-XDP in rx_check_frame(). After BPF runs
the driver cannot tell whether the tail was modified; drop the
unconditional skb_pull/skb_trim in rx_get_frame().
- Account tx_bytes/tx_packets on XDP_TX success and tx_dropped on
XDP_TX size reject.
- Add dma_wmb()/dma_rmb() around DCB status writes and reads in
xmit, xmit_xdpf, and napi_poll.
- Collected Tested-by: Hervé Codina.
- Link to v2: https://lore.kernel.org/r/20260428-lan966x-pci-fdma-v2-0-d3ec66e06202@microchip.com
Changes in v2:
Version 2 primarily addresses issues with module unload/load, where
traffic would stop working (Hervé), and XDP head/tail adjust that would be
discarded (Mohsin).
Apart from that, I ran through issues reported by Sashiko, and fixed a
number of other issues.
- New patch 1: add drivers/net/ethernet/microchip/fdma/ to the Sparx5
SoC MAINTAINERS entry.
- New patch 7: clear latched FDMA error/interrupt stickies after the
switch reset so they don't fire as soon as interrupts are enabled.
- New patch 8: shutdown() callback, quiescing FDMA on host warm reboot.
- Replaced the depth-2 dev_is_pci(parent->parent) backend selector
with a parent-chain walk.
- XDP: use xdp.data/xdp.data_end for the post-XDP frame length so that
bpf_xdp_adjust_head/tail are respected (Mohsin Bashir)
- MTU change: drain in-flight xmits with netif_tx_disable() on every
port before reallocating rings, waking them again on completion.
- MTU change: cap the PCIe DCB ring at 256 entries so a full-ring
coherent DMA allocation fits in a single MAX_PAGE_ORDER block at
jumbo MTU.
- PCIe ATU: disable the region before clearing its translation on
unmap.
- PCIe FDMA: hold tx_lock in napi_poll around the free-DCB check used
to wake stopped netdev queues.
- PCIe FDMA: return -ENOSPC (not -1) when the DCB ring is exhausted.
- Link to v1: https://lore.kernel.org/r/20260320-lan966x-pci-fdma-v1-0-ef54cb9b0c4b@microchip.com
---
Daniel Machon (13):
MAINTAINERS: add FDMA library to Sparx5 SoC entry
net: microchip: fdma: rename contiguous dataptr helpers
net: microchip: fdma: add PCIe ATU support
net: lan966x: add FDMA LLP register write helper
net: lan966x: export FDMA helpers for reuse
net: lan966x: add FDMA ops dispatch for PCIe support
net: lan966x: clear FDMA interrupt stickies after switch reset
net: lan966x: add shutdown callback to stop FDMA on reboot
net: lan966x: add PCIe FDMA support
net: lan966x: add PCIe FDMA MTU change support
net: lan966x: add PCIe FDMA XDP support
misc: lan966x-pci: dts: extend cpu reg to cover PCIE DBI space
misc: lan966x-pci: dts: add fdma interrupt to overlay
MAINTAINERS | 1 +
drivers/misc/lan966x_pci.dtso | 5 +-
drivers/net/ethernet/microchip/fdma/Makefile | 4 +
drivers/net/ethernet/microchip/fdma/fdma_api.c | 33 ++
drivers/net/ethernet/microchip/fdma/fdma_api.h | 25 +-
drivers/net/ethernet/microchip/fdma/fdma_pci.c | 182 ++++++
drivers/net/ethernet/microchip/fdma/fdma_pci.h | 42 ++
drivers/net/ethernet/microchip/lan966x/Makefile | 4 +
.../net/ethernet/microchip/lan966x/lan966x_fdma.c | 51 +-
.../ethernet/microchip/lan966x/lan966x_fdma_pci.c | 656 +++++++++++++++++++++
.../net/ethernet/microchip/lan966x/lan966x_main.c | 74 ++-
.../net/ethernet/microchip/lan966x/lan966x_main.h | 45 ++
.../net/ethernet/microchip/lan966x/lan966x_regs.h | 25 +
.../net/ethernet/microchip/lan966x/lan966x_xdp.c | 10 +
14 files changed, 1117 insertions(+), 40 deletions(-)
---
base-commit: 790ead9394860e7d70c5e0e50a35b243e909a618
change-id: 20260313-lan966x-pci-fdma-94ed485d23fa
Best regards,
--
Daniel Machon <daniel.machon@microchip.com>
^ permalink raw reply
* [PATCH net-next v3 01/13] MAINTAINERS: add FDMA library to Sparx5 SoC entry
From: Daniel Machon @ 2026-05-04 14:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Horatiu Vultur, Steen Hegelund, UNGLinuxDriver,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Herve Codina, Arnd Bergmann,
Greg Kroah-Hartman, Mohsin Bashir
Cc: netdev, linux-kernel, bpf, linux-arm-kernel
In-Reply-To: <20260504-lan966x-pci-fdma-v3-0-a56f5740d870@microchip.com>
The FDMA library under drivers/net/ethernet/microchip/fdma/ is shared by
the lan966x, sparx5 and lan969x drivers, but is not covered by an entry
in the MAINTAINERS file. A subsequent patch will add new files to the
FDMA library, so let's make sure it's covered.
Add drivers/net/ethernet/microchip/fdma/ to the Sparx5 SoC entry, since
I am already listed there.
Tested-by: Herve Codina <herve.codina@bootlin.com>
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..c962cded2a2e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3101,6 +3101,7 @@ M: UNGLinuxDriver@microchip.com
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Supported
F: arch/arm64/boot/dts/microchip/sparx*
+F: drivers/net/ethernet/microchip/fdma/
F: drivers/net/ethernet/microchip/vcap/
F: drivers/pinctrl/pinctrl-microchip-sgpio.c
N: sparx5
--
2.34.1
^ permalink raw reply related
* [PATCH net-next v3 02/13] net: microchip: fdma: rename contiguous dataptr helpers
From: Daniel Machon @ 2026-05-04 14:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Horatiu Vultur, Steen Hegelund, UNGLinuxDriver,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Herve Codina, Arnd Bergmann,
Greg Kroah-Hartman, Mohsin Bashir
Cc: netdev, linux-kernel, bpf, linux-arm-kernel
In-Reply-To: <20260504-lan966x-pci-fdma-v3-0-a56f5740d870@microchip.com>
When the FDMA library was introduced [1], two helpers to get the DMA and
virtual address of a DCB, in contiguous memory, were added. These
helpers have had no callers until this series. I found the naming I
initially used confusing and inconsistent.
Rename fdma_dataptr_get_contiguous() and
fdma_dataptr_virt_get_contiguous() to fdma_dataptr_dma_addr_contiguous()
and fdma_dataptr_virt_addr_contiguous(). This makes the pair symmetric
and clarifies what type of address each returns.
[1]: commit 30e48a75df9c ("net: microchip: add FDMA library")
Tested-by: Herve Codina <herve.codina@bootlin.com>
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/fdma/fdma_api.h | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.h b/drivers/net/ethernet/microchip/fdma/fdma_api.h
index d91affe8bd98..94f1a6596097 100644
--- a/drivers/net/ethernet/microchip/fdma/fdma_api.h
+++ b/drivers/net/ethernet/microchip/fdma/fdma_api.h
@@ -197,8 +197,9 @@ static inline int fdma_nextptr_cb(struct fdma *fdma, int dcb_idx, u64 *nextptr)
* if the dataptr addresses and DCB's are in contiguous memory and the driver
* supports XDP.
*/
-static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx,
- int db_idx)
+static inline u64 fdma_dataptr_dma_addr_contiguous(struct fdma *fdma,
+ int dcb_idx,
+ int db_idx)
{
return fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
(dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size +
@@ -209,8 +210,8 @@ static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx,
* applicable if the dataptr addresses and DCB's are in contiguous memory and
* the driver supports XDP.
*/
-static inline void *fdma_dataptr_virt_get_contiguous(struct fdma *fdma,
- int dcb_idx, int db_idx)
+static inline void *fdma_dataptr_virt_addr_contiguous(struct fdma *fdma,
+ int dcb_idx, int db_idx)
{
return (u8 *)fdma->dcbs + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
(dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size +
--
2.34.1
^ permalink raw reply related
* [PATCH iwl-next v2 0/5] ice: five small fixes and cleanups
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
Three correctness fixes and two cleanups for the ice driver.
Patch 1 corrects a kernel-doc comment in ice_ptp_hw.h that described the
ETH56G MAC Rx offset field as unsigned when it is signed (trivial doc fix,
no functional change).
Patch 2 removes the PF_SB_REM_DEV_CTL sideband register write from
ice_ptp_init_phc_e82x(). PHY access is enabled by default on E82X and
the register write was a leftover from an earlier SWITCH_MODE workaround
that is no longer needed.
Patch 3 renames ICE_SMA2_UFL2_RX_DIS to ICE_SMA2_UFL2_RX_EN to match
the actual active-high hardware semantics and inverts the three use sites
in ice_dpll.c so that the logic remains correct.
Patch 4 replaces the static per-type frequency tables for CGU pins with a
single DPLL_PIN_FREQUENCY_RANGE(1, 25 MHz) entry. The firmware defines
an any_freq capability for configurable CGU inputs, but the old tables
restricted users to 1 PPS or 10 MHz. GNSS pins retain a 1 PPS-only
entry since they are physically constrained.
Patch 5 exports ice_dcb_need_recfg() and calls it in the four SW LLDP
netlink setters instead of memcmp() on a non-packed struct, which is
undefined behaviour due to uninitialised padding bytes. The redundant
memcmp in ice_pf_dcb_cfg() is removed since callers now guard it.
Aleksandr Loktionov (2):
ice: add correct handling of SMA/u.FL states
ice: use element-by-element comparison for DCB config changes
Arkadiusz Kubalewski (1):
ice: fix DPLL pin frequency range in CGU pin descriptors
Karol Kolacinski (2):
ice: fix ETH56G Rx offset type description in kernel-doc comment
ice: remove unnecessary PF_SB_REM_DEV_CTL write for E82X
v1 -> v2 updated mail subject with PATCH iwl-next
drivers/net/ethernet/intel/ice/ice_dcb_lib.c | 13 +-
drivers/net/ethernet/intel/ice/ice_dcb_lib.h | 2 +
drivers/net/ethernet/intel/ice/ice_dcb_nl.c | 30 +++-
drivers/net/ethernet/intel/ice/ice_dpll.c | 6 +-
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 141 ++++++++++---------
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 8 +-
6 files changed, 113 insertions(+), 87 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH iwl-next v2 1/5] ice: fix ETH56G Rx offset type description in kernel-doc comment
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260504142451.4161845-1-aleksandr.loktionov@intel.com>
From: Karol Kolacinski <karol.kolacinski@intel.com>
The ETH56G MAC register configuration Rx offset field stores a signed
integer, not an unsigned one. Correct the struct comment that incorrectly
described it as '11 bit unsigned int'. Also update 'unsigned ints' to
'unsigned integers' for consistency.
Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 9bfd3e7..c1aa408 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -144,9 +144,9 @@ struct ice_vernier_info_e82x {
* @tx_offset: total Tx offset, fixed point
* @rx_offset: total Rx offset, contains value for bitslip/deskew, fixed point
*
- * All fixed point registers except Rx offset are 23 bit unsigned ints with
+ * All fixed point registers except Rx offset are 23 bit unsigned integers with
* a 9 bit fractional.
- * Rx offset is 11 bit unsigned int with a 9 bit fractional.
+ * Rx offset is 11 bit signed integer with a 9 bit fractional.
*/
struct ice_eth56g_mac_reg_cfg {
struct {
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 2/5] ice: remove unnecessary PF_SB_REM_DEV_CTL write for E82X
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260504142451.4161845-1-aleksandr.loktionov@intel.com>
From: Karol Kolacinski <karol.kolacinski@intel.com>
Remove the PF_SB_REM_DEV_CTL register write from ice_ptp_init_phc_e82x().
PHY access is enabled by default on E82X devices and the driver does not
need to configure switch device access. The register write was a
remnant of an earlier SWITCH_MODE workaround for a FIFO issue and is
no longer needed.
Also update the kernel-doc comment to refer to the E82X family rather than
E822.
Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 61c0a0d..7b1b402 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2767,22 +2767,13 @@ static int ice_ptp_set_vernier_wl(struct ice_hw *hw)
}
/**
- * ice_ptp_init_phc_e82x - Perform E822 specific PHC initialization
+ * ice_ptp_init_phc_e82x - Perform E82X specific PHC initialization
* @hw: pointer to HW struct
*
- * Perform PHC initialization steps specific to E822 devices.
+ * Perform PHC initialization steps specific to E82X devices.
*/
static int ice_ptp_init_phc_e82x(struct ice_hw *hw)
{
- u32 val;
-
- /* Enable reading switch and PHY registers over the sideband queue */
-#define PF_SB_REM_DEV_CTL_SWITCH_READ BIT(1)
-#define PF_SB_REM_DEV_CTL_PHY0 BIT(2)
- val = rd32(hw, PF_SB_REM_DEV_CTL);
- val |= (PF_SB_REM_DEV_CTL_SWITCH_READ | PF_SB_REM_DEV_CTL_PHY0);
- wr32(hw, PF_SB_REM_DEV_CTL, val);
-
/* Set window length for all the ports */
return ice_ptp_set_vernier_wl(hw);
}
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 3/5] ice: add correct handling of SMA/u.FL states
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260504142451.4161845-1-aleksandr.loktionov@intel.com>
The ICE_SMA2_UFL2_RX_DIS bit name is wrong: the bit is active high
(setting it *enables* RX for u.FL2 / SMA2), not active low. Rename
it to ICE_SMA2_UFL2_RX_EN and invert the use sites in ice_dpll.c so
that enabling the u.FL2 pin clears the bit (as it used to do) and
disabling sets it.
Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 6 +++---
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 62f75701d..7e8bb63 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -672,7 +672,7 @@ ice_dpll_sw_pins_update(struct ice_pf *pf)
p->active = false;
p = &d->ufl[ICE_DPLL_PIN_SW_2_IDX];
- p->active = (data & ICE_SMA2_DIR_EN) && !(data & ICE_SMA2_UFL2_RX_DIS);
+ p->active = (data & ICE_SMA2_DIR_EN) && !(data & ICE_SMA2_UFL2_RX_EN);
d->sma_data = data;
return 0;
@@ -1264,10 +1264,10 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
case ICE_DPLL_PIN_SW_2_IDX:
if (state == DPLL_PIN_STATE_SELECTABLE) {
data |= ICE_SMA2_DIR_EN;
- data &= ~ICE_SMA2_UFL2_RX_DIS;
+ data &= ~ICE_SMA2_UFL2_RX_EN;
enable = true;
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
- data |= ICE_SMA2_UFL2_RX_DIS;
+ data |= ICE_SMA2_UFL2_RX_EN;
enable = false;
} else {
goto unlock;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index c1aa408..278d757 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -655,12 +655,12 @@ static inline u64 ice_get_base_incval(struct ice_hw *hw)
/* SMA controller pin control */
#define ICE_SMA1_DIR_EN BIT(4)
#define ICE_SMA1_TX_EN BIT(5)
-#define ICE_SMA2_UFL2_RX_DIS BIT(3)
+#define ICE_SMA2_UFL2_RX_EN BIT(3)
#define ICE_SMA2_DIR_EN BIT(6)
#define ICE_SMA2_TX_EN BIT(7)
#define ICE_SMA1_MASK (ICE_SMA1_DIR_EN | ICE_SMA1_TX_EN)
-#define ICE_SMA2_MASK (ICE_SMA2_UFL2_RX_DIS | ICE_SMA2_DIR_EN | \
+#define ICE_SMA2_MASK (ICE_SMA2_UFL2_RX_EN | ICE_SMA2_DIR_EN | \
ICE_SMA2_TX_EN)
#define ICE_SMA2_INACTIVE_MASK (ICE_SMA2_DIR_EN | ICE_SMA2_TX_EN)
#define ICE_ALL_SMA_MASK (ICE_SMA1_MASK | ICE_SMA2_MASK)
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 4/5] ice: fix DPLL pin frequency range in CGU pin descriptors
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov
Cc: netdev, Arkadiusz Kubalewski
In-Reply-To: <20260504142451.4161845-1-aleksandr.loktionov@intel.com>
From: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Replace the per-type frequency tables (ice_cgu_pin_freq_1_hz and
ice_cgu_pin_freq_10_mhz) and the two-entry ice_cgu_pin_freq_common array
with a named range constant ICE_CGU_MAX_FREQ_HZ (25 MHz), a new
ice_cgu_pin_freq_range array containing DPLL_PIN_FREQUENCY_RANGE(1,
ICE_CGU_MAX_FREQ_HZ), and a separate ice_cgu_pin_freq_gnss array that
retains DPLL_PIN_FREQUENCY_1PPS for GNSS input pins.
The hardware firmware spec defines an any_freq capability for CGU inputs
(ICE_AQC_GET_CGU_IN_CFG_FLG1_ANYFREQ), but the static pin descriptor
tables constrained configurable pins to 1PPS or 10MHz, preventing users
from setting valid intermediate frequencies. Use a range entry so the
DPLL netlink interface correctly reflects what the firmware will accept.
The firmware validates the actual value and rejects out-of-range requests.
MUX, SyncE ETH port, and configurable EXT pins now advertise the full
frequency range, matching the hardware capability. GNSS input pins retain
the 1PPS-only advertisement since a GNSS receiver is physically
constrained to 1 Hz.
Fixes: 6db5f2cd9ebb ("ice: dpll: fix output pin capabilities")
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 128 +++++++++++---------
1 file changed, 72 insertions(+), 56 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 7b1b402..3949138 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -7,127 +7,143 @@
#include "ice_ptp_hw.h"
#include "ice_ptp_consts.h"
-static struct dpll_pin_frequency ice_cgu_pin_freq_common[] = {
- DPLL_PIN_FREQUENCY_1PPS,
- DPLL_PIN_FREQUENCY_10MHZ,
-};
+/* Maximum frequency supported by CGU pins, in Hz */
+#define ICE_CGU_MAX_FREQ_HZ 25000000
-static struct dpll_pin_frequency ice_cgu_pin_freq_1_hz[] = {
- DPLL_PIN_FREQUENCY_1PPS,
+static struct dpll_pin_frequency ice_cgu_pin_freq_range[] = {
+ DPLL_PIN_FREQUENCY_RANGE(1, ICE_CGU_MAX_FREQ_HZ),
};
-static struct dpll_pin_frequency ice_cgu_pin_freq_10_mhz[] = {
- DPLL_PIN_FREQUENCY_10MHZ,
+static struct dpll_pin_frequency ice_cgu_pin_freq_gnss[] = {
+ DPLL_PIN_FREQUENCY_1PPS,
};
static const struct ice_cgu_pin_desc ice_e810t_sfp_cgu_inputs[] = {
{ "CVL-SDP22", ZL_REF0P, DPLL_PIN_TYPE_INT_OSCILLATOR,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP20", ZL_REF0N, DPLL_PIN_TYPE_INT_OSCILLATOR,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
- { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX, 0, },
- { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX, 0, },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "SMA1", ZL_REF3P, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "SMA2/U.FL2", ZL_REF3N, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "GNSS-1PPS", ZL_REF4P, DPLL_PIN_TYPE_GNSS,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_gnss), ice_cgu_pin_freq_gnss },
};
static const struct ice_cgu_pin_desc ice_e810t_qsfp_cgu_inputs[] = {
{ "CVL-SDP22", ZL_REF0P, DPLL_PIN_TYPE_INT_OSCILLATOR,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP20", ZL_REF0N, DPLL_PIN_TYPE_INT_OSCILLATOR,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
- { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX, },
- { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX, },
- { "C827_1-RCLKA", ZL_REF2P, DPLL_PIN_TYPE_MUX, },
- { "C827_1-RCLKB", ZL_REF2N, DPLL_PIN_TYPE_MUX, },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_1-RCLKA", ZL_REF2P, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "C827_1-RCLKB", ZL_REF2N, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "SMA1", ZL_REF3P, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "SMA2/U.FL2", ZL_REF3N, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "GNSS-1PPS", ZL_REF4P, DPLL_PIN_TYPE_GNSS,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_gnss), ice_cgu_pin_freq_gnss },
};
static const struct ice_cgu_pin_desc ice_e810t_sfp_cgu_outputs[] = {
{ "REF-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "REF-SMA2/U.FL2", ZL_OUT1, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
- { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, },
- { "MAC-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "MAC-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP21", ZL_OUT4, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP23", ZL_OUT5, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
};
static const struct ice_cgu_pin_desc ice_e810t_qsfp_cgu_outputs[] = {
{ "REF-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "REF-SMA2/U.FL2", ZL_OUT1, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
- { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
- { "PHY2-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
- { "MAC-CLK", ZL_OUT4, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "PHY2-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "MAC-CLK", ZL_OUT4, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP21", ZL_OUT5, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CVL-SDP23", ZL_OUT6, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
};
static const struct ice_cgu_pin_desc ice_e823_si_cgu_inputs[] = {
{ "NONE", SI_REF0P, 0, 0 },
{ "NONE", SI_REF0N, 0, 0 },
- { "SYNCE0_DP", SI_REF1P, DPLL_PIN_TYPE_MUX, 0 },
- { "SYNCE0_DN", SI_REF1N, DPLL_PIN_TYPE_MUX, 0 },
+ { "SYNCE0_DP", SI_REF1P, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "SYNCE0_DN", SI_REF1N, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "EXT_CLK_SYNC", SI_REF2P, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "NONE", SI_REF2N, 0, 0 },
{ "EXT_PPS_OUT", SI_REF3, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "INT_PPS_OUT", SI_REF4, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
};
static const struct ice_cgu_pin_desc ice_e823_si_cgu_outputs[] = {
{ "1588-TIME_SYNC", SI_OUT0, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
- { "PHY-CLK", SI_OUT1, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "PHY-CLK", SI_OUT1, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "10MHZ-SMA2", SI_OUT2, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_10_mhz), ice_cgu_pin_freq_10_mhz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "PPS-SMA1", SI_OUT3, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
};
static const struct ice_cgu_pin_desc ice_e823_zl_cgu_inputs[] = {
{ "NONE", ZL_REF0P, 0, 0 },
{ "INT_PPS_OUT", ZL_REF0N, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
- { "SYNCE0_DP", ZL_REF1P, DPLL_PIN_TYPE_MUX, 0 },
- { "SYNCE0_DN", ZL_REF1N, DPLL_PIN_TYPE_MUX, 0 },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "SYNCE0_DP", ZL_REF1P, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "SYNCE0_DN", ZL_REF1N, DPLL_PIN_TYPE_MUX,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "NONE", ZL_REF2P, 0, 0 },
{ "NONE", ZL_REF2N, 0, 0 },
{ "EXT_CLK_SYNC", ZL_REF3P, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "NONE", ZL_REF3N, 0, 0 },
{ "EXT_PPS_OUT", ZL_REF4P, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "OCXO", ZL_REF4N, DPLL_PIN_TYPE_INT_OSCILLATOR, 0 },
};
static const struct ice_cgu_pin_desc ice_e823_zl_cgu_outputs[] = {
{ "PPS-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_1_hz), ice_cgu_pin_freq_1_hz },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "10MHZ-SMA2", ZL_OUT1, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_10_mhz), ice_cgu_pin_freq_10_mhz },
- { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
- { "1588-TIME_REF", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, 0 },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
+ { "1588-TIME_REF", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT,
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "CPK-TIME_SYNC", ZL_OUT4, DPLL_PIN_TYPE_EXT,
- ARRAY_SIZE(ice_cgu_pin_freq_common), ice_cgu_pin_freq_common },
+ ARRAY_SIZE(ice_cgu_pin_freq_range), ice_cgu_pin_freq_range },
{ "NONE", ZL_OUT5, 0, 0 },
};
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 5/5] ice: use element-by-element comparison for DCB config changes
From: Aleksandr Loktionov @ 2026-05-04 14:24 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260504142451.4161845-1-aleksandr.loktionov@intel.com>
Comparing two ice_dcbx_cfg structs with memcmp() is unreliable on
non-packed structs due to uninitialised padding bytes. The HW DCB
path already has ice_dcb_need_recfg() that compares fields
individually; export it and use it in the SW DCB netlink setters
(setets, setpfc, setapp, cee_set_all) instead of the unsafe memcmp.
Remove the now-redundant memcmp check from ice_pf_dcb_cfg() so that
function always attempts the HW reconfiguration when called.
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dcb_lib.c | 13 ++-------
drivers/net/ethernet/intel/ice/ice_dcb_lib.h | 2 ++
drivers/net/ethernet/intel/ice/ice_dcb_nl.c | 30 ++++++++++++++++++--
3 files changed, 32 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
index bd77f1c..2e85fae 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_lib.c
@@ -353,15 +353,11 @@ int ice_pf_dcb_cfg(struct ice_pf *pf, struct ice_dcbx_cfg *new_cfg, bool locked)
struct ice_dcbx_cfg *old_cfg, *curr_cfg;
struct device *dev = ice_pf_to_dev(pf);
struct iidc_rdma_event *event;
- int ret = ICE_DCB_NO_HW_CHG;
struct ice_vsi *pf_vsi;
+ int ret;
curr_cfg = &pf->hw.port_info->qos_cfg.local_dcbx_cfg;
- /* FW does not care if change happened */
- if (!pf->hw.port_info->qos_cfg.is_sw_lldp)
- ret = ICE_DCB_HW_CHG_RST;
-
/* Enable DCB tagging only when more than one TC */
if (ice_dcb_get_num_tc(new_cfg) > 1) {
dev_dbg(dev, "DCB tagging enabled (num TC > 1)\n");
@@ -377,11 +373,6 @@ int ice_pf_dcb_cfg(struct ice_pf *pf, struct ice_dcbx_cfg *new_cfg, bool locked)
clear_bit(ICE_FLAG_DCB_ENA, pf->flags);
}
- if (!memcmp(new_cfg, curr_cfg, sizeof(*new_cfg))) {
- dev_dbg(dev, "No change in DCB config required\n");
- return ret;
- }
-
if (ice_dcb_bwchk(pf, new_cfg))
return -EINVAL;
@@ -481,7 +472,7 @@ static void ice_cfg_etsrec_defaults(struct ice_port_info *pi)
* @old_cfg: current DCB config
* @new_cfg: new DCB config
*/
-static bool
+bool
ice_dcb_need_recfg(struct ice_pf *pf, struct ice_dcbx_cfg *old_cfg,
struct ice_dcbx_cfg *new_cfg)
{
diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_lib.h b/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
index da9ba81..a7eaa2f9 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_lib.h
@@ -20,6 +20,8 @@ u8 ice_dcb_get_num_tc(struct ice_dcbx_cfg *dcbcfg);
void ice_vsi_set_dcb_tc_cfg(struct ice_vsi *vsi);
bool ice_is_pfc_causing_hung_q(struct ice_pf *pf, unsigned int txqueue);
u8 ice_dcb_get_tc(struct ice_vsi *vsi, int queue_index);
+bool ice_dcb_need_recfg(struct ice_pf *pf, struct ice_dcbx_cfg *old_cfg,
+ struct ice_dcbx_cfg *new_cfg);
int
ice_pf_dcb_cfg(struct ice_pf *pf, struct ice_dcbx_cfg *new_cfg, bool locked);
int ice_dcb_bwchk(struct ice_pf *pf, struct ice_dcbx_cfg *dcbcfg);
diff --git a/drivers/net/ethernet/intel/ice/ice_dcb_nl.c b/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
index a10c1c8d..13a52c1 100644
--- a/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
+++ b/drivers/net/ethernet/intel/ice/ice_dcb_nl.c
@@ -108,11 +108,17 @@ static int ice_dcbnl_setets(struct net_device *netdev, struct ieee_ets *ets)
if (!bwrec)
new_cfg->etsrec.tcbwtable[0] = 100;
+ if (!ice_dcb_need_recfg(pf, &pf->hw.port_info->qos_cfg.local_dcbx_cfg,
+ new_cfg)) {
+ err = ICE_DCB_NO_HW_CHG;
+ goto ets_out;
+ }
+
err = ice_pf_dcb_cfg(pf, new_cfg, true);
/* return of zero indicates new cfg applied */
- if (err == ICE_DCB_HW_CHG_RST)
+ if (!err)
ice_dcbnl_devreset(netdev);
- if (err == ICE_DCB_NO_HW_CHG)
+ else if (err == ICE_DCB_NO_HW_CHG)
err = ICE_DCB_HW_CHG_RST;
ets_out:
@@ -287,11 +293,18 @@ static int ice_dcbnl_setpfc(struct net_device *netdev, struct ieee_pfc *pfc)
new_cfg->pfc.pfcena = pfc->pfc_en;
+ if (!ice_dcb_need_recfg(pf, &pf->hw.port_info->qos_cfg.local_dcbx_cfg,
+ new_cfg)) {
+ err = ICE_DCB_NO_HW_CHG;
+ goto pfc_out;
+ }
+
err = ice_pf_dcb_cfg(pf, new_cfg, true);
if (err == ICE_DCB_HW_CHG_RST)
ice_dcbnl_devreset(netdev);
if (err == ICE_DCB_NO_HW_CHG)
err = ICE_DCB_HW_CHG_RST;
+pfc_out:
mutex_unlock(&pf->tc_mutex);
return err;
}
@@ -845,6 +858,12 @@ static int ice_dcbnl_setapp(struct net_device *netdev, struct dcb_app *app)
new_cfg->dscp_map[app->protocol] = app->priority;
new_cfg->app[new_cfg->numapps++] = new_app;
+ if (!ice_dcb_need_recfg(pf, &pf->hw.port_info->qos_cfg.local_dcbx_cfg,
+ new_cfg)) {
+ ret = ICE_DCB_NO_HW_CHG;
+ goto setapp_out;
+ }
+
ret = ice_pf_dcb_cfg(pf, new_cfg, true);
/* return of zero indicates new cfg applied */
if (ret == ICE_DCB_HW_CHG_RST)
@@ -991,8 +1010,15 @@ static u8 ice_dcbnl_cee_set_all(struct net_device *netdev)
mutex_lock(&pf->tc_mutex);
+ if (!ice_dcb_need_recfg(pf, &pf->hw.port_info->qos_cfg.local_dcbx_cfg,
+ new_cfg)) {
+ err = ICE_DCB_NO_HW_CHG;
+ goto out;
+ }
+
err = ice_pf_dcb_cfg(pf, new_cfg, true);
+out:
mutex_unlock(&pf->tc_mutex);
return (err != ICE_DCB_HW_CHG_RST) ? ICE_DCB_NO_HW_CHG : err;
}
--
2.52.0
^ permalink raw reply related
* [PATCH net v3] net/mana: Fix auxiliary device double-delete race
From: Konstantin Taranov @ 2026-05-04 14:27 UTC (permalink / raw)
To: shirazsaleem, kotaranov, pabeni, haiyangz, kys, edumazet, kuba,
davem, decui, wei.liu, longli, jgg, leon
Cc: linux-rdma, linux-kernel, netdev
From: Shiraz Saleem <shirazsaleem@microsoft.com>
Make remove_adev() safe to call concurrently from the service reset
and PCI eject paths by using xchg() to atomically claim the adev
pointer. This prevents double auxiliary_device_delete/uninit when
hv_eject_device_work races with the service reset workqueue.
Fixes: 505cc26bcae0 ("net: mana: Add support for auxiliary device servicing events")
Signed-off-by: Shiraz Saleem <shirazsaleem@microsoft.com>
Signed-off-by: Konstantin Taranov <kotaranov@microsoft.com>
---
v3: Separate the xchg() call from the variable declaration in remove_adev()
to avoid calling functions with side effects as variable initializers
v2: rebased on the latest net
drivers/net/ethernet/microsoft/mana/mana_en.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index a654b3699..dd4f4215a 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -3465,14 +3465,19 @@ static void adev_release(struct device *dev)
static void remove_adev(struct gdma_dev *gd)
{
- struct auxiliary_device *adev = gd->adev;
- int id = adev->id;
+ struct auxiliary_device *adev;
+ int id;
+
+ adev = xchg(&gd->adev, NULL);
+ if (!adev)
+ return;
+
+ id = adev->id;
auxiliary_device_delete(adev);
auxiliary_device_uninit(adev);
mana_adev_idx_free(id);
- gd->adev = NULL;
}
static int add_adev(struct gdma_dev *gd, const char *name)
@@ -3538,7 +3543,7 @@ static void mana_rdma_service_handle(struct work_struct *work)
switch (serv_work->event) {
case GDMA_SERVICE_TYPE_RDMA_SUSPEND:
- if (!gd->adev || gd->is_suspended)
+ if (gd->is_suspended)
break;
remove_adev(gd);
@@ -3753,8 +3758,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
cancel_delayed_work_sync(&ac->gf_stats_work);
/* adev currently doesn't support suspending, always remove it */
- if (gd->adev)
- remove_adev(gd);
+ remove_adev(gd);
for (i = 0; i < ac->num_ports; i++) {
ndev = ac->ports[i];
@@ -3843,8 +3847,7 @@ void mana_rdma_remove(struct gdma_dev *gd)
if (gc->service_wq)
flush_workqueue(gc->service_wq);
- if (gd->adev)
- remove_adev(gd);
+ remove_adev(gd);
mana_gd_deregister_device(gd);
}
--
2.43.0
^ permalink raw reply related
* [PATCH net] xfrm: route MIGRATE notifications to caller's netns
From: Maoyi Xie @ 2026-05-04 14:27 UTC (permalink / raw)
To: steffen.klassert, herbert
Cc: davem, kuba, pabeni, edumazet, horms, antony.antony, netdev,
linux-kernel, stable
xfrm_send_migrate() in net/xfrm/xfrm_user.c and pfkey_send_migrate()
in net/key/af_key.c both hardcode &init_net for the multicast that
announces a successful XFRM_MSG_MIGRATE / SADB_X_MIGRATE.
XFRM_MSG_MIGRATE arrives on a per-netns NETLINK_XFRM socket, and the
rest of the xfrm/af_key netlink path was made netns-aware in 2008.
The other 14 multicast paths in xfrm_user.c route their event using
xs_net(x), xp_net(xp) or sock_net(skb->sk); only the migrate path
was missed.
Two consequences of the init_net hardcoding:
1. The notification (selector, old/new endpoint addresses, and the
km_address) is delivered to listeners on init_net's
XFRMNLGRP_MIGRATE / pfkey BROADCAST_ALL groups rather than on
the issuing netns. An IKE daemon running in init_net therefore
receives migration notifications originating from any other
netns on the host.
2. An IKE daemon running inside a non-init netns and subscribed
to its own XFRMNLGRP_MIGRATE / pfkey groups never receives the
notification of its own migration. IKEv2 MOBIKE / address-update
handling inside a netns is silently broken.
Thread struct net through km_migrate() and the xfrm_mgr.migrate
function pointer, drop the &init_net override in xfrm_send_migrate()
and pfkey_send_migrate(), and pass the caller's net (already in
scope in xfrm_migrate() via sock_net(skb->sk)) all the way down.
struct xfrm_mgr is in-tree only and not exported as a stable API,
so the function-pointer signature change is internal.
pfkey_broadcast() is already netns-aware via net_generic(net,
pfkey_net_id) since the pernet conversion. The five other
pfkey_broadcast() callers in af_key.c already pass xs_net(x),
sock_net(sk) or a per-netns net, so this only removes the
&init_net outlier.
Fixes: 5c79de6e79cd ("[XFRM]: User interface for handling XFRM_MSG_MIGRATE")
Cc: stable@vger.kernel.org # v5.15+
Signed-off-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>
---
include/net/xfrm.h | 3 ++-
net/key/af_key.c | 6 +++---
net/xfrm/xfrm_policy.c | 2 +-
net/xfrm/xfrm_state.c | 4 ++--
net/xfrm/xfrm_user.c | 5 ++---
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 10d3edde6..874409127 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -715,6 +715,7 @@ struct xfrm_mgr {
const struct xfrm_migrate *m,
int num_bundles,
const struct xfrm_kmaddress *k,
+ struct net *net,
const struct xfrm_encap_tmpl *encap);
bool (*is_alive)(const struct km_event *c);
};
@@ -1891,7 +1892,7 @@ int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol);
#ifdef CONFIG_XFRM_MIGRATE
int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_bundles,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap);
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
u32 if_id);
diff --git a/net/key/af_key.c b/net/key/af_key.c
index a166a88d8..9cffeef18 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -3564,7 +3564,7 @@ static int set_ipsecrequest(struct sk_buff *skb,
#ifdef CONFIG_NET_KEY_MIGRATE
static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_bundles,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap)
{
int i;
@@ -3669,7 +3669,7 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
}
/* broadcast migrate message to sockets */
- pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL, &init_net);
+ pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL, net);
return 0;
@@ -3680,7 +3680,7 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
#else
static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_bundles,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap)
{
return -ENOPROTOOPT;
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index c944327ce..59968dcba 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4703,7 +4703,7 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
}
/* Stage 5 - announce */
- km_migrate(sel, dir, type, m, num_migrate, k, encap);
+ km_migrate(sel, dir, type, m, num_migrate, k, net, encap);
xfrm_pol_put(pol);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 1748d374a..8f1379681 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2837,7 +2837,7 @@ EXPORT_SYMBOL(km_policy_expired);
#ifdef CONFIG_XFRM_MIGRATE
int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_migrate,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap)
{
int err = -EINVAL;
@@ -2848,7 +2848,7 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
list_for_each_entry_rcu(km, &xfrm_km_list, list) {
if (km->migrate) {
ret = km->migrate(sel, dir, type, m, num_migrate, k,
- encap);
+ net, encap);
if (!ret)
err = ret;
}
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index d56450f61..b39c33276 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3271,10 +3271,9 @@ static int build_migrate(struct sk_buff *skb, const struct xfrm_migrate *m,
static int xfrm_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_migrate,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap)
{
- struct net *net = &init_net;
struct sk_buff *skb;
int err;
@@ -3292,7 +3291,7 @@ static int xfrm_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
#else
static int xfrm_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_migrate *m, int num_migrate,
- const struct xfrm_kmaddress *k,
+ const struct xfrm_kmaddress *k, struct net *net,
const struct xfrm_encap_tmpl *encap)
{
return -ENOPROTOOPT;
base-commit: bd3a4795d5744f59a1f485379f1303e5e606f377
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] ipv6: default IPV6_SIT to m
From: Alyssa Ross @ 2026-05-04 14:30 UTC (permalink / raw)
To: Fernando Fernandez Mancera, David S. Miller, David Ahern,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netdev, linux-kernel, Ricardo B . Marlière,
Krzysztof Kozlowski
In-Reply-To: <5f676587-fcdd-4704-9523-07a7fd98455a@suse.de>
[-- Attachment #1: Type: text/plain, Size: 1498 bytes --]
Fernando Fernandez Mancera <fmancera@suse.de> writes:
> On 5/3/26 9:25 PM, Alyssa Ross wrote:
>> This basically defaulted to m until recently, since IPV6 defaulted to
>> m. Since IPV6 was changed to a boolean with a default of y, IPV6_SIT
>> started defaulting to built-in as well. This results in a surprise
>> sit0 device by default for defconfig (and defconfig-derived config)
>> users at boot. For me, this broke an (admittedly non-robust) script.
>> Preserve the behaviour of most configs by avoiding building this
>> module, that's probably overall seldom used compared to IPv6 as a
>> whole, into the kernel.
>>
>> Fixes: 309b905deee59 ("ipv6: convert CONFIG_IPV6 to built-in only and clean up Kconfigs")
>> Signed-off-by: Alyssa Ross <hi@alyssa.is>
>
> Hi, I am not sure about this. IPV6_SIT was already defaulting to 'y' for
> most distributions (except for ARM). Sure, if you had CONFIG_IPV6=m then
> CONFIG_IPV6_SIT was defaulting to 'm'.
>
> Hm. Maybe we can aim this for net-next tree instead of net? I just don't
> think this is a fix but a change in behavior. FWIW, it makes sense to me
> to have this as a module by default.
>
> Let's see what do others think.
Yes, it appears you're right. I had only tested on ARM. In that case,
perhaps it make sense to instead set this to m in the arm64 defconfig, to
preserve the previous situation, but I can also see the value in
reducing platform discongruity by letting it change to y on arm64 too.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]
^ permalink raw reply
* Re: [PATCH net-next v1 1/5] net: mdiobus: Provide fwnode_mdiobus_register()
From: Andy Shevchenko @ 2026-05-04 14:41 UTC (permalink / raw)
To: Victor Nogueira
Cc: linux-kernel, Linux Kernel Network Developers, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Heiner Kallweit, Russell King
In-Reply-To: <CA+NMeC-VCxWfzwHQTiY-eGA=FEmLY7_7AHgBrZ+dK+LC-d7pUw@mail.gmail.com>
On Mon, May 04, 2026 at 11:16:00AM -0300, Victor Nogueira wrote:
> On 04/05/2026 04:29, Andy Shevchenko wrote:
> > Provide an agnostic helper to register MDIO bus independently on
> > the firmware node provider.
[...]
> > + if (is_of_node(fwnode))
> > + return __of_mdiobus_register(mdio, to_of_node(fwnode), owner);
>
> Build seems to break when CONFIG_OF_MDIO is not defined:
>
> drivers/net/mdio/fwnode_mdio.c: In function ‘__fwnode_mdiobus_register’:
> drivers/net/mdio/fwnode_mdio.c:198:24: error: implicit declaration of
> function ‘__of_mdiobus_register’; did you mean ‘of_mdiobus_register’?
> [-Wimplicit-function-declaration]
> 198 | return __of_mdiobus_register(mdio,
> to_of_node(fwnode), owner);
Ah, thanks for a good catch. I haven't checked OF case to be off.
Yeah, with __acpi_mdiobus_register the compiler sees the prototype
independently on the configuration option. I will think about OF
case (like I said it can also be hidden, the problem is that there
is a huge amount of users, that's why I haven't touched it).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH net 0/4] net: sparx5: misc fixes for sparx5 and lan969x
From: Daniel Machon @ 2026-05-04 14:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Bjarni Jonasson, Lars Povlsen, Philipp Zabel
Cc: linux-kernel, netdev, linux-arm-kernel, Steen Hegelund,
linux-rt-devel, Andrew Lunn
This series fixes various issues in the sparx5 driver, which also
serves lan969x.
Details are in the individual commit descriptions.
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
Daniel Machon (4):
net: sparx5: defer VCAP debugfs creation until after netdev registration
net: sparx5: fix sleep in atomic context in MAC table access
net: sparx5: fix wrong chip ids for TSN SKUs
net: sparx5: configure serdes for 1000BASE-X in sparx5_port_init()
drivers/net/ethernet/microchip/sparx5/Makefile | 3 ++-
.../net/ethernet/microchip/sparx5/sparx5_debugfs.c | 26 ++++++++++++++++++++++
.../ethernet/microchip/sparx5/sparx5_mactable.c | 24 ++++++++++----------
.../net/ethernet/microchip/sparx5/sparx5_main.c | 4 ++--
.../net/ethernet/microchip/sparx5/sparx5_main.h | 19 +++++++++++-----
.../net/ethernet/microchip/sparx5/sparx5_port.c | 3 ++-
.../ethernet/microchip/sparx5/sparx5_vcap_impl.c | 6 -----
7 files changed, 57 insertions(+), 28 deletions(-)
---
base-commit: 98878ed91b68a3150126fccef125ee7b1bb86ab2
change-id: 20260428-misc-fixes-sparx5-lan969x-bc2961a570fb
Best regards,
--
Daniel Machon <daniel.machon@microchip.com>
^ permalink raw reply
* [PATCH net 1/4] net: sparx5: defer VCAP debugfs creation until after netdev registration
From: Daniel Machon @ 2026-05-04 14:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Bjarni Jonasson, Lars Povlsen, Philipp Zabel
Cc: linux-kernel, netdev, linux-arm-kernel, Steen Hegelund,
linux-rt-devel
In-Reply-To: <20260504-misc-fixes-sparx5-lan969x-v1-0-6604306b5743@microchip.com>
Commit 3a95973e7c79 ("net: sparx5: move VCAP initialization to probe")
moved sparx5_vcap_init() ahead of sparx5_register_netdevs() in probe.
The VCAP init path ends by calling vcap_port_debugfs() for every port,
which uses netdev_name(ndev) as the debugfs file name. At that point
the netdevs have only been allocated, not registered, so dev->name
still holds the "eth%d" template and netdev_name() returns
"(unnamed net_device)". Every port tries to create the same file under
vcaps/, producing a flood of warnings at boot:
debugfs: '(unnamed net_device)' already exists in 'vcaps'
debugfs: '(unnamed net_device)' already exists in 'vcaps'
...
Move the debugfs setup into a new sparx5_debugfs() helper in
sparx5_debugfs.c, invoked after sparx5_register_notifier_blocks()
succeeds so the netdev names are finalized. sparx5_vcap_init() now
only deals with VCAP state. The sparx5/ debugfs root is created in
the new helper as well.
Fixes: 3a95973e7c79 ("net: sparx5: move VCAP initialization to probe")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/sparx5/Makefile | 3 ++-
.../net/ethernet/microchip/sparx5/sparx5_debugfs.c | 26 ++++++++++++++++++++++
.../net/ethernet/microchip/sparx5/sparx5_main.c | 4 ++--
.../net/ethernet/microchip/sparx5/sparx5_main.h | 7 ++++++
.../ethernet/microchip/sparx5/sparx5_vcap_impl.c | 6 -----
5 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile
index d447f9e84d92..eb5c81527f41 100644
--- a/drivers/net/ethernet/microchip/sparx5/Makefile
+++ b/drivers/net/ethernet/microchip/sparx5/Makefile
@@ -14,7 +14,8 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \
sparx5_psfp.o sparx5_mirror.o sparx5_regs.o
sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
-sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
+sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o \
+ sparx5_debugfs.o
sparx5-switch-$(CONFIG_LAN969X_SWITCH) += lan969x/lan969x_regs.o \
lan969x/lan969x.o \
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_debugfs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_debugfs.c
new file mode 100644
index 000000000000..f6cb1eeaab80
--- /dev/null
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_debugfs.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver debug filesystem support
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/debugfs.h>
+
+#include "sparx5_main.h"
+#include "vcap_api_debugfs.h"
+
+void sparx5_debugfs(struct sparx5 *sparx5)
+{
+ const struct sparx5_consts *consts = sparx5->data->consts;
+ struct vcap_control *ctrl = sparx5->vcap_ctrl;
+ struct dentry *dir;
+ int idx;
+
+ sparx5->debugfs_root = debugfs_create_dir("sparx5", NULL);
+
+ dir = vcap_debugfs(sparx5->dev, sparx5->debugfs_root, ctrl);
+ for (idx = 0; idx < consts->n_ports; ++idx)
+ if (sparx5->ports[idx])
+ vcap_port_debugfs(sparx5->dev, dir, ctrl,
+ sparx5->ports[idx]->ndev);
+}
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
index dad713e9ddd5..bec07560e6fe 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
@@ -820,8 +820,6 @@ static int mchp_sparx5_probe(struct platform_device *pdev)
/* Default values, some from DT */
sparx5->coreclock = SPX5_CORE_CLOCK_DEFAULT;
- sparx5->debugfs_root = debugfs_create_dir("sparx5", NULL);
-
ports = of_get_child_by_name(np, "ethernet-ports");
if (!ports) {
dev_err(sparx5->dev, "no ethernet-ports child node found\n");
@@ -1000,6 +998,8 @@ static int mchp_sparx5_probe(struct platform_device *pdev)
goto cleanup_netdevs;
}
+ sparx5_debugfs(sparx5);
+
goto cleanup_config;
cleanup_netdevs:
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index 6a745bb71b5c..d5e6644ff124 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -565,6 +565,13 @@ void sparx5_get_hwtimestamp(struct sparx5 *sparx5,
int sparx5_vcap_init(struct sparx5 *sparx5);
void sparx5_vcap_deinit(struct sparx5 *sparx5);
+/* sparx5_debugfs.c */
+#if defined(CONFIG_DEBUG_FS)
+void sparx5_debugfs(struct sparx5 *sparx5);
+#else
+static inline void sparx5_debugfs(struct sparx5 *sparx5) {}
+#endif
+
/* sparx5_pgid.c */
enum sparx5_pgid_type {
SPX5_PGID_FREE,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
index 95b93e46a41d..dd446b3a9f20 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c
@@ -2035,7 +2035,6 @@ int sparx5_vcap_init(struct sparx5 *sparx5)
const struct sparx5_vcap_inst *cfg;
struct vcap_control *ctrl;
struct vcap_admin *admin;
- struct dentry *dir;
int err = 0, idx;
/* Create a VCAP control instance that owns the platform specific VCAP
@@ -2074,11 +2073,6 @@ int sparx5_vcap_init(struct sparx5 *sparx5)
sparx5_vcap_port_key_selection(sparx5, admin);
list_add_tail(&admin->list, &ctrl->list);
}
- dir = vcap_debugfs(sparx5->dev, sparx5->debugfs_root, ctrl);
- for (idx = 0; idx < consts->n_ports; ++idx)
- if (sparx5->ports[idx])
- vcap_port_debugfs(sparx5->dev, dir, ctrl,
- sparx5->ports[idx]->ndev);
return err;
}
--
2.34.1
^ permalink raw reply related
* [PATCH iwl-net v2] idpf: fix RSS LUT memcpy size
From: Larysa Zaremba @ 2026-05-04 14:43 UTC (permalink / raw)
To: intel-wired-lan, Jacob Keller
Cc: Larysa Zaremba, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Joshua Hay,
Willem de Bruijn, netdev, linux-kernel, Aleksandr Loktionov,
Tony Nguyen, Simon Horman
Based on the following feedback from Sashiko (received for iXD phase 1
patchset, but valid for the net tree):
"Is the bounds check xn_params.recv_mem.iov_len < lut_buf_size sufficient?
Since lut_buf_size only represents the size of the array elements, should
this check instead verify that the payload is at least
sizeof(struct virtchnl2_rss_lut) + lut_buf_size?
[...]
Does memcpy copy the correct amount of data here? rss_lut_size stores the
number of 32-bit entries, not the size in bytes. Should it use
lut_buf_size or rss_data->rss_lut_size * sizeof(u32) instead?"
After inspecting the code, it was concluded that RSS memcpy size is in fact
4 times smaller than it has to be, since a single array entry in a u32, and
rss_data->rss_lut_size is clearly used as an array size. Required Rx buffer
size is also too small, but this is a common issue in the idpf code.
Use a full buffer size (lut_buf_size) instead of the array length
(rss_data->rss_lut_size) when doing memcpy of RSS lookup table.
While at it, increase required Rx buffer size to a whole flex-array
containing structure instead of just the array.
Link: https://sashiko.dev/#/patchset/20260323174052.5355-1-larysa.zaremba%40intel.com?part=8
Fixes: 95af467d9a4e ("idpf: configure resources for RX queues")
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Larysa Zaremba <larysa.zaremba@intel.com>
---
v1 -> v2: replace manual array size calculation with flex_array_size()
v1: https://lore.kernel.org/netdev/20260429074232.180528-1-larysa.zaremba@intel.com/
drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
index be66f9b2e101..0fc7c68447f8 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
@@ -2915,8 +2915,9 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter,
if (reply_sz < sizeof(struct virtchnl2_rss_lut))
return -EIO;
- lut_buf_size = le16_to_cpu(recv_rl->lut_entries) * sizeof(u32);
- if (reply_sz < lut_buf_size)
+ lut_buf_size = flex_array_size(recv_rl, lut,
+ le16_to_cpu(recv_rl->lut_entries));
+ if (reply_sz < lut_buf_size + sizeof(struct virtchnl2_rss_lut))
return -EIO;
/* size didn't change, we can reuse existing lut buf */
@@ -2933,7 +2934,7 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_adapter *adapter,
}
do_memcpy:
- memcpy(rss_data->rss_lut, recv_rl->lut, rss_data->rss_lut_size);
+ memcpy(rss_data->rss_lut, recv_rl->lut, lut_buf_size);
return 0;
}
--
2.47.0
^ permalink raw reply related
* [PATCH net 2/4] net: sparx5: fix sleep in atomic context in MAC table access
From: Daniel Machon @ 2026-05-04 14:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Bjarni Jonasson, Lars Povlsen, Philipp Zabel
Cc: linux-kernel, netdev, linux-arm-kernel, Steen Hegelund,
linux-rt-devel
In-Reply-To: <20260504-misc-fixes-sparx5-lan969x-v1-0-6604306b5743@microchip.com>
sparx5_mact_learn() is called from .ndo_set_rx_mode with
netif_addr_lock_bh held, but takes sparx5->lock which is a mutex.
A mutex may block, which is not allowed from atomic context.
Convert sparx5->lock to a spinlock, switch the hardware completion poll
to readx_poll_timeout_atomic(), and use spin_lock_bh() since no caller
runs in hardirq context.
Observed with CONFIG_PROVE_LOCKING, CONFIG_DEBUG_SPINLOCK,
CONFIG_DEBUG_MUTEXES and CONFIG_DEBUG_ATOMIC_SLEEP enabled:
BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 217, name: ip
preempt_count: 201, expected: 0
Call trace:
__might_resched+0x144/0x248
__might_sleep+0x48/0x7c
__mutex_lock+0x74/0x850
mutex_lock_nested+0x24/0x30
sparx5_mact_learn+0x78/0x100
sparx5_mc_sync+0x40/0x54
__hw_addr_sync_dev+0xc4/0x170
sparx5_set_rx_mode+0x4c/0x58
__dev_set_rx_mode+0x64/0xa4
__dev_open+0x1ec/0x26c
Fixes: b37a1bae742f ("net: sparx5: add mactable support")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
.../ethernet/microchip/sparx5/sparx5_mactable.c | 24 +++++++++++-----------
.../net/ethernet/microchip/sparx5/sparx5_main.h | 2 +-
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c b/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
index 2bf9c5f64151..0797cfa32916 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
@@ -50,7 +50,7 @@ static int sparx5_mact_wait_for_completion(struct sparx5 *sparx5)
{
u32 val;
- return readx_poll_timeout(sparx5_mact_get_status,
+ return readx_poll_timeout_atomic(sparx5_mact_get_status,
sparx5, val,
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_GET(val) == 0,
TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
@@ -92,7 +92,7 @@ int sparx5_mact_learn(struct sparx5 *sparx5, int pgid,
addr = pgid - consts->n_ports;
}
- mutex_lock(&sparx5->lock);
+ spin_lock_bh(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
@@ -111,7 +111,7 @@ int sparx5_mact_learn(struct sparx5 *sparx5, int pgid,
ret = sparx5_mact_wait_for_completion(sparx5);
- mutex_unlock(&sparx5->lock);
+ spin_unlock_bh(&sparx5->lock);
return ret;
}
@@ -164,7 +164,7 @@ bool sparx5_mact_getnext(struct sparx5 *sparx5,
u32 cfg2;
int ret;
- mutex_lock(&sparx5->lock);
+ spin_lock_bh(&sparx5->lock);
sparx5_mact_select(sparx5, mac, *vid);
@@ -183,7 +183,7 @@ bool sparx5_mact_getnext(struct sparx5 *sparx5,
*pcfg2 = cfg2;
}
- mutex_unlock(&sparx5->lock);
+ spin_unlock_bh(&sparx5->lock);
return ret == 0;
}
@@ -194,7 +194,7 @@ int sparx5_mact_find(struct sparx5 *sparx5,
int ret;
u32 cfg2;
- mutex_lock(&sparx5->lock);
+ spin_lock_bh(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
@@ -212,7 +212,7 @@ int sparx5_mact_find(struct sparx5 *sparx5,
ret = -ENOENT;
}
- mutex_unlock(&sparx5->lock);
+ spin_unlock_bh(&sparx5->lock);
return ret;
}
@@ -222,7 +222,7 @@ int sparx5_mact_forget(struct sparx5 *sparx5,
{
int ret;
- mutex_lock(&sparx5->lock);
+ spin_lock_bh(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
@@ -233,7 +233,7 @@ int sparx5_mact_forget(struct sparx5 *sparx5,
ret = sparx5_mact_wait_for_completion(sparx5);
- mutex_unlock(&sparx5->lock);
+ spin_unlock_bh(&sparx5->lock);
return ret;
}
@@ -440,7 +440,7 @@ static void sparx5_mact_pull_work(struct work_struct *work)
vid = 0;
memset(mac, 0, sizeof(mac));
do {
- mutex_lock(&sparx5->lock);
+ spin_lock_bh(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
sparx5, LRN_SCAN_NEXT_CFG);
@@ -451,7 +451,7 @@ static void sparx5_mact_pull_work(struct work_struct *work)
ret = sparx5_mact_wait_for_completion(sparx5);
if (ret == 0)
ret = sparx5_mact_get(sparx5, mac, &vid, &cfg2);
- mutex_unlock(&sparx5->lock);
+ spin_unlock_bh(&sparx5->lock);
if (ret == 0)
sparx5_mact_handle_entry(sparx5, mac, vid, cfg2);
} while (ret == 0);
@@ -493,7 +493,7 @@ int sparx5_mact_init(struct sparx5 *sparx5)
{
char queue_name[32];
- mutex_init(&sparx5->lock);
+ spin_lock_init(&sparx5->lock);
/* Flush MAC table */
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_CLEAR_ALL) |
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index d5e6644ff124..8ef4050ceecc 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -373,7 +373,7 @@ struct sparx5 {
u32 features;
void __iomem *regs[NUM_TARGETS];
int port_count;
- struct mutex lock; /* MAC reg lock */
+ spinlock_t lock; /* MAC reg lock */
/* port structures are in net device */
struct sparx5_port *ports[SPX5_PORTS];
enum sparx5_core_clockfreq coreclock;
--
2.34.1
^ permalink raw reply related
* [PATCH net 3/4] net: sparx5: fix wrong chip ids for TSN SKUs
From: Daniel Machon @ 2026-05-04 14:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Bjarni Jonasson, Lars Povlsen, Philipp Zabel
Cc: linux-kernel, netdev, linux-arm-kernel, Steen Hegelund,
linux-rt-devel, Andrew Lunn
In-Reply-To: <20260504-misc-fixes-sparx5-lan969x-v1-0-6604306b5743@microchip.com>
The TSN SKUs in enum spx5_target_chiptype have incorrect IDs:
SPX5_TARGET_CT_7546TSN = 0x47546,
SPX5_TARGET_CT_7549TSN = 0x47549,
SPX5_TARGET_CT_7552TSN = 0x47552,
SPX5_TARGET_CT_7556TSN = 0x47556,
SPX5_TARGET_CT_7558TSN = 0x47558,
The value read back from the chip is GCB_CHIP_ID_PART_ID, which is a
GENMASK(27, 12) field, i.e. at most 16 bits wide. It can never match
these IDs, so probing a TSN part fails with a "Target not supported"
error.
Fix the enum to use the actual 16-bit part IDs returned by the
hardware: 0x0546, 0x0549, 0x0552, 0x0556 and 0x0558.
Reported-by: Andrew Lunn <andrew@lunn.ch>
Fixes: 3cfa11bac9bb ("net: sparx5: add the basic sparx5 driver")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/sparx5/sparx5_main.h | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index 8ef4050ceecc..70ca707b5ce4 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -31,11 +31,11 @@ enum spx5_target_chiptype {
SPX5_TARGET_CT_7552 = 0x7552, /* SparX-5-128 Enterprise */
SPX5_TARGET_CT_7556 = 0x7556, /* SparX-5-160 Enterprise */
SPX5_TARGET_CT_7558 = 0x7558, /* SparX-5-200 Enterprise */
- SPX5_TARGET_CT_7546TSN = 0x47546, /* SparX-5-64i Industrial */
- SPX5_TARGET_CT_7549TSN = 0x47549, /* SparX-5-90i Industrial */
- SPX5_TARGET_CT_7552TSN = 0x47552, /* SparX-5-128i Industrial */
- SPX5_TARGET_CT_7556TSN = 0x47556, /* SparX-5-160i Industrial */
- SPX5_TARGET_CT_7558TSN = 0x47558, /* SparX-5-200i Industrial */
+ SPX5_TARGET_CT_7546TSN = 0x0546, /* SparX-5-64i Industrial */
+ SPX5_TARGET_CT_7549TSN = 0x0549, /* SparX-5-90i Industrial */
+ SPX5_TARGET_CT_7552TSN = 0x0552, /* SparX-5-128i Industrial */
+ SPX5_TARGET_CT_7556TSN = 0x0556, /* SparX-5-160i Industrial */
+ SPX5_TARGET_CT_7558TSN = 0x0558, /* SparX-5-200i Industrial */
SPX5_TARGET_CT_LAN9694 = 0x9694, /* lan969x-40 */
SPX5_TARGET_CT_LAN9691VAO = 0x9691, /* lan969x-40-VAO */
SPX5_TARGET_CT_LAN9694TSN = 0x9695, /* lan969x-40-TSN */
--
2.34.1
^ permalink raw reply related
* [PATCH net 4/4] net: sparx5: configure serdes for 1000BASE-X in sparx5_port_init()
From: Daniel Machon @ 2026-05-04 14:43 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Bjarni Jonasson, Lars Povlsen, Philipp Zabel
Cc: linux-kernel, netdev, linux-arm-kernel, Steen Hegelund,
linux-rt-devel, Andrew Lunn
In-Reply-To: <20260504-misc-fixes-sparx5-lan969x-v1-0-6604306b5743@microchip.com>
sparx5_port_init() only invokes sparx5_serdes_set() and the associated
shadow-device enable and low-speed device switch for SGMII and QSGMII.
On any port with a high-speed primary device (DEV5G/DEV10G/DEV25G)
configured for 1000BASE-X the serdes is therefore left uninitialized,
the DEV2G5 shadow is never enabled, and the port stays pointed at its
high-speed device rather than the DEV2G5. The PCS1G block looks
healthy in isolation, but no frames reach the link partner.
Add 1000BASE-X to the check so the same three steps run.
Reported-by: Andrew Lunn <andrew@lunn.ch>
Fixes: 946e7fd5053a ("net: sparx5: add port module support")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/sparx5/sparx5_port.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c
index 04bc8fffaf96..62c49893de3c 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c
@@ -1128,7 +1128,8 @@ int sparx5_port_init(struct sparx5 *sparx5,
DEV2G5_PCS1G_SD_CFG(port->portno));
if (conf->portmode == PHY_INTERFACE_MODE_QSGMII ||
- conf->portmode == PHY_INTERFACE_MODE_SGMII) {
+ conf->portmode == PHY_INTERFACE_MODE_SGMII ||
+ conf->portmode == PHY_INTERFACE_MODE_1000BASEX) {
err = sparx5_serdes_set(sparx5, port, conf);
if (err)
return err;
--
2.34.1
^ permalink raw reply related
* [PATCH net-next v42 5/8] eea: implement packet receive logic
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
In-Reply-To: <20260504144439.29570-1-xuanzhuo@linux.alibaba.com>
Implement the core logic for receiving packets in the EEA RX path,
including packet buffering and basic validation.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/eea_net.h | 5 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 532 ++++++++++++++++++++-
2 files changed, 535 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 0d5fc8d1375a..1ff9ef1a8fbe 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -41,6 +41,7 @@ struct eea_rx_meta {
struct page *page;
dma_addr_t dma;
u32 offset;
+ u32 sync_for_cpu;
u32 frags;
struct page *hdr_page;
@@ -54,6 +55,8 @@ struct eea_rx_meta {
u32 tailroom;
u32 len;
+
+ bool in_use;
};
struct eea_net_rx_pkt_ctx {
@@ -62,6 +65,7 @@ struct eea_net_rx_pkt_ctx {
bool data_valid;
bool do_drop;
+ u32 recv_len;
struct sk_buff *head_skb;
};
@@ -167,6 +171,7 @@ void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx);
int eea_queues_check_and_reset(struct eea_device *edev);
/* rx apis */
+
void enet_rx_stop(struct eea_net_rx *rx);
void enet_rx_start(struct eea_net_rx *rx);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
index b1265048fbc3..9c71a7cf950e 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_rx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -17,6 +17,39 @@
#define EEA_PAGE_FRAGS_NUM 1024
+#define EEA_RX_BUF_ALIGN 128
+
+#define EEA_RX_BUF_MAX_LEN (10 * 1024)
+
+struct eea_rx_ctx {
+ u32 len;
+ u32 hdr_len;
+
+ u16 flags;
+ bool more;
+
+ struct eea_rx_meta *meta;
+};
+
+static struct eea_rx_meta *eea_rx_meta_get(struct eea_net_rx *rx)
+{
+ struct eea_rx_meta *meta;
+
+ if (!rx->free)
+ return NULL;
+
+ meta = rx->free;
+ rx->free = meta->next;
+
+ return meta;
+}
+
+static void eea_rx_meta_put(struct eea_net_rx *rx, struct eea_rx_meta *meta)
+{
+ meta->next = rx->free;
+ rx->free = meta;
+}
+
static void eea_free_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta,
bool allow_direct)
{
@@ -31,6 +64,89 @@ static void eea_free_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta,
meta->page = NULL;
}
+static void eea_rx_meta_dma_sync_for_device(struct eea_net_rx *rx,
+ struct eea_rx_meta *meta)
+{
+ u32 len;
+
+ if (meta->sync_for_cpu <= meta->offset + rx->headroom)
+ return;
+
+ len = meta->sync_for_cpu - meta->offset - rx->headroom;
+
+ dma_sync_single_for_device(rx->enet->edev->dma_dev,
+ meta->dma + meta->offset + rx->headroom,
+ len, DMA_FROM_DEVICE);
+ meta->sync_for_cpu = 0;
+}
+
+static void meta_align_offset(struct eea_net_rx *rx, struct eea_rx_meta *meta)
+{
+ int h, b;
+
+ h = rx->headroom;
+ b = meta->offset + h;
+
+ /* For better performance, we align the buffer address to
+ * EEA_RX_BUF_ALIGN, as required by the device design.
+ */
+ b = ALIGN(b, EEA_RX_BUF_ALIGN);
+
+ meta->offset = b - h;
+}
+
+static int eea_alloc_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta)
+{
+ struct page *page;
+
+ if (meta->page) {
+ eea_rx_meta_dma_sync_for_device(rx, meta);
+ return 0;
+ }
+
+ page = page_pool_dev_alloc_pages(rx->pp);
+ if (!page)
+ return -ENOMEM;
+
+ page_pool_fragment_page(page, EEA_PAGE_FRAGS_NUM);
+
+ meta->page = page;
+ meta->dma = page_pool_get_dma_addr(page);
+ meta->offset = 0;
+ meta->frags = 0;
+ meta->sync_for_cpu = 0;
+
+ meta_align_offset(rx, meta);
+
+ return 0;
+}
+
+static u32 eea_consume_rx_buffer(struct eea_net_rx *rx,
+ struct eea_rx_meta *meta,
+ u32 consumed)
+{
+ u32 offset;
+ int min;
+
+ offset = meta->offset;
+
+ meta->offset += consumed;
+ ++meta->frags;
+
+ min = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ min += rx->headroom;
+ min += SKB_DATA_ALIGN(ETH_DATA_LEN);
+
+ meta_align_offset(rx, meta);
+
+ if (min + meta->offset > PAGE_SIZE) {
+ eea_free_rx_buffer(rx, meta, true);
+ return PAGE_SIZE - offset;
+ }
+
+ return meta->offset - offset;
+}
+
static void eea_free_rx_hdr(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
{
struct eea_rx_meta *meta;
@@ -96,17 +212,429 @@ static int eea_alloc_rx_hdr(struct eea_net_init_ctx *ctx, struct eea_net_rx *rx)
return -ENOMEM;
}
-static int eea_poll(struct napi_struct *napi, int budget)
+static void eea_rx_meta_dma_sync_for_cpu(struct eea_net_rx *rx,
+ struct eea_rx_meta *meta, u32 len)
+{
+ dma_sync_single_for_cpu(rx->enet->edev->dma_dev,
+ meta->dma + meta->offset + meta->headroom,
+ len, DMA_FROM_DEVICE);
+ meta->sync_for_cpu = meta->offset + meta->headroom + len;
+}
+
+static int eea_harden_check_overflow(struct eea_rx_ctx *ctx,
+ struct eea_net *enet)
+{
+ u32 max_len;
+
+ max_len = ctx->meta->truesize - ctx->meta->headroom -
+ ctx->meta->tailroom;
+
+ if (unlikely(ctx->len > max_len)) {
+ pr_debug("%s: rx error: len %u exceeds truesize %u\n",
+ enet->netdev->name, ctx->len, max_len);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
{
- /* Empty function; will be implemented in a subsequent commit. */
+ int err;
+
+ err = eea_harden_check_overflow(ctx, enet);
+ if (err)
+ return err;
+
+ if (ctx->hdr_len) {
+ if (unlikely(ctx->hdr_len < ETH_HLEN)) {
+ pr_debug("%s: short hdr %u\n", enet->netdev->name,
+ ctx->hdr_len);
+ return -EINVAL;
+ }
+
+ if (unlikely(ctx->hdr_len > enet->cfg.split_hdr)) {
+ pr_debug("%s: rx error: hdr len %u exceeds hdr buffer size %u\n",
+ enet->netdev->name, ctx->hdr_len,
+ enet->cfg.split_hdr);
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ if (unlikely(ctx->len < ETH_HLEN)) {
+ pr_debug("%s: short packet %u\n", enet->netdev->name, ctx->len);
+ return -EINVAL;
+ }
+
return 0;
}
+static struct sk_buff *eea_build_skb(void *buf, u32 buflen, u32 headroom,
+ u32 len)
+{
+ struct sk_buff *skb;
+
+ skb = build_skb(buf, buflen);
+ if (unlikely(!skb))
+ return NULL;
+
+ skb_reserve(skb, headroom);
+ skb_put(skb, len);
+
+ return skb;
+}
+
+static struct sk_buff *eea_rx_build_split_hdr_skb(struct eea_net_rx *rx,
+ struct eea_rx_ctx *ctx)
+{
+ struct eea_rx_meta *meta = ctx->meta;
+ u32 truesize, offset;
+ struct sk_buff *skb;
+ struct page *page;
+
+ dma_sync_single_for_cpu(rx->enet->edev->dma_dev, meta->hdr_dma,
+ ctx->hdr_len, DMA_FROM_DEVICE);
+
+ skb = napi_alloc_skb(rx->napi, ctx->hdr_len);
+ if (unlikely(!skb))
+ return NULL;
+
+ skb_put_data(skb, ctx->meta->hdr_addr, ctx->hdr_len);
+
+ if (ctx->len) {
+ page = meta->page;
+ offset = meta->offset + meta->headroom;
+
+ truesize = eea_consume_rx_buffer(rx, meta,
+ meta->headroom + ctx->len);
+
+ skb_add_rx_frag(skb, 0, page, offset, ctx->len, truesize);
+ }
+
+ skb_mark_for_recycle(skb);
+
+ return skb;
+}
+
+static struct sk_buff *eea_rx_build_skb(struct eea_net_rx *rx,
+ struct eea_rx_ctx *ctx)
+{
+ struct eea_rx_meta *meta = ctx->meta;
+ u32 shinfo_size, bufsize, truesize;
+ struct sk_buff *skb;
+ struct page *page;
+ void *buf;
+
+ page = meta->page;
+
+ shinfo_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+
+ buf = page_address(page) + meta->offset;
+ bufsize = meta->headroom + SKB_DATA_ALIGN(ctx->len) + shinfo_size;
+
+ skb = eea_build_skb(buf, bufsize, meta->headroom, ctx->len);
+ if (unlikely(!skb))
+ return NULL;
+
+ truesize = eea_consume_rx_buffer(rx, meta, bufsize);
+ skb_mark_for_recycle(skb);
+
+ skb->truesize += truesize - bufsize;
+
+ return skb;
+}
+
+static void process_remain_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
+{
+ struct eea_net *enet = rx->enet;
+ struct sk_buff *head_skb;
+ u32 offset, truesize, nr_frags;
+ struct page *page;
+
+ if (eea_harden_check_overflow(ctx, enet))
+ goto err;
+
+ head_skb = rx->pkt.head_skb;
+
+ nr_frags = skb_shinfo(head_skb)->nr_frags;
+ if (unlikely(nr_frags >= MAX_SKB_FRAGS))
+ goto err;
+
+ offset = ctx->meta->offset + ctx->meta->headroom;
+ page = ctx->meta->page;
+ truesize = eea_consume_rx_buffer(rx, ctx->meta,
+ ctx->meta->headroom + ctx->len);
+
+ skb_add_rx_frag(head_skb, nr_frags, page, offset, ctx->len, truesize);
+
+ return;
+
+err:
+ dev_kfree_skb(rx->pkt.head_skb);
+ rx->pkt.do_drop = true;
+ rx->pkt.head_skb = NULL;
+}
+
+static void process_first_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
+{
+ struct eea_net *enet = rx->enet;
+ struct sk_buff *skb = NULL;
+
+ if (eea_harden_check_size(ctx, enet))
+ goto err;
+
+ rx->pkt.data_valid = ctx->flags & EEA_DESC_F_DATA_VALID;
+
+ if (ctx->hdr_len)
+ skb = eea_rx_build_split_hdr_skb(rx, ctx);
+ else
+ skb = eea_rx_build_skb(rx, ctx);
+
+ if (unlikely(!skb))
+ goto err;
+
+ rx->pkt.head_skb = skb;
+
+ return;
+
+err:
+ rx->pkt.do_drop = true;
+}
+
+static void eea_submit_skb(struct eea_net_rx *rx, struct sk_buff *skb,
+ struct eea_rx_cdesc *desc)
+{
+ struct eea_net *enet = rx->enet;
+
+ if (rx->pkt.data_valid)
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ if (enet->cfg.ts_cfg.rx_filter == HWTSTAMP_FILTER_ALL)
+ skb_hwtstamps(skb)->hwtstamp = EEA_DESC_TS(desc) +
+ enet->hw_ts_offset;
+
+ skb_record_rx_queue(skb, rx->index);
+ skb->protocol = eth_type_trans(skb, enet->netdev);
+
+ napi_gro_receive(rx->napi, skb);
+}
+
+static int eea_rx_desc_to_ctx(struct eea_net_rx *rx,
+ struct eea_rx_ctx *ctx,
+ struct eea_rx_cdesc *desc)
+{
+ u16 id;
+
+ ctx->meta = NULL;
+
+ id = le16_to_cpu(desc->id);
+ if (unlikely(id >= rx->ering->num)) {
+ if (net_ratelimit())
+ netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
+ return -EINVAL;
+ }
+
+ ctx->meta = &rx->meta[id];
+ if (!ctx->meta->in_use) {
+ if (net_ratelimit())
+ netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
+ ctx->meta = NULL;
+ return -EINVAL;
+ }
+
+ ctx->meta->in_use = false;
+
+ ctx->len = le16_to_cpu(desc->len);
+ if (unlikely(ctx->len > ctx->meta->len)) {
+ if (net_ratelimit())
+ netdev_err(rx->enet->netdev, "rx invalid len(%d) id:%d\n",
+ ctx->len, id);
+ return -EINVAL;
+ }
+
+ ctx->flags = le16_to_cpu(desc->flags);
+
+ ctx->hdr_len = 0;
+ if (ctx->flags & EEA_DESC_F_SPLIT_HDR) {
+ ctx->hdr_len = le16_to_cpu(desc->len_ex) &
+ EEA_RX_CDESC_HDR_LEN_MASK;
+ }
+
+ ctx->more = ctx->flags & EEA_RING_DESC_F_MORE;
+
+ return 0;
+}
+
+static int eea_cleanrx(struct eea_net_rx *rx, int budget,
+ struct eea_rx_ctx *ctx)
+{
+ struct eea_rx_cdesc *desc;
+ struct eea_rx_meta *meta;
+ int recv, err;
+
+ for (recv = 0; recv < budget; ) {
+ desc = eea_ering_cq_get_desc(rx->ering);
+ if (!desc)
+ break;
+
+ err = eea_rx_desc_to_ctx(rx, ctx, desc);
+ if (unlikely(err)) {
+ if (ctx->meta)
+ eea_rx_meta_put(rx, ctx->meta);
+
+ if (rx->pkt.head_skb)
+ dev_kfree_skb(rx->pkt.head_skb);
+
+ /* A hardware error occurred; we are attempting to
+ * mitigate the impact. Subsequent packets may be
+ * corrupted.
+ */
+ ctx->more = false;
+ goto ack;
+ }
+
+ meta = ctx->meta;
+
+ if (unlikely(rx->pkt.do_drop))
+ goto skip;
+
+ eea_rx_meta_dma_sync_for_cpu(rx, meta, ctx->len);
+
+ rx->pkt.recv_len += ctx->len;
+ rx->pkt.recv_len += ctx->hdr_len;
+
+ if (!rx->pkt.idx)
+ process_first_buf(rx, ctx);
+ else
+ process_remain_buf(rx, ctx);
+
+ ++rx->pkt.idx;
+
+ if (!ctx->more && rx->pkt.head_skb)
+ eea_submit_skb(rx, rx->pkt.head_skb, desc);
+
+skip:
+ eea_rx_meta_put(rx, meta);
+ack:
+ eea_ering_cq_ack_desc(rx->ering, 1);
+
+ if (!ctx->more) {
+ memset(&rx->pkt, 0, sizeof(rx->pkt));
+ ++recv;
+ }
+ }
+
+ return recv;
+}
+
+static void eea_rx_dma_sync_hdr(struct eea_net_rx *rx, dma_addr_t addr)
+{
+ dma_sync_single_for_device(rx->dma_dev, addr,
+ rx->enet->cfg.split_hdr,
+ DMA_FROM_DEVICE);
+}
+
+/* Only be called from napi. */
+static void eea_rx_post(struct eea_net_rx *rx)
+{
+ u32 tailroom, headroom, room, len;
+ struct eea_rx_meta *meta;
+ struct eea_rx_desc *desc;
+ int err = 0, num = 0;
+ dma_addr_t addr;
+
+ tailroom = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ headroom = rx->headroom;
+ room = headroom + tailroom;
+
+ while (true) {
+ meta = eea_rx_meta_get(rx);
+ if (!meta)
+ break;
+
+ err = eea_alloc_rx_buffer(rx, meta);
+ if (err) {
+ eea_rx_meta_put(rx, meta);
+ break;
+ }
+
+ len = min_t(u32, PAGE_SIZE - meta->offset - room,
+ EEA_RX_BUF_MAX_LEN);
+
+ len = ALIGN_DOWN(len, SMP_CACHE_BYTES);
+
+ addr = meta->dma + meta->offset + headroom;
+
+ desc = eea_ering_sq_alloc_desc(rx->ering, meta->id, true, 0);
+ desc->addr = cpu_to_le64(addr);
+ desc->len = cpu_to_le16(len);
+
+ if (meta->hdr_addr) {
+ eea_rx_dma_sync_hdr(rx, meta->hdr_dma);
+ desc->hdr_addr = cpu_to_le64(meta->hdr_dma);
+ }
+
+ eea_ering_sq_commit_desc(rx->ering);
+
+ meta->truesize = len + room;
+ meta->headroom = headroom;
+ meta->tailroom = tailroom;
+ meta->len = len;
+ meta->in_use = true;
+ ++num;
+ }
+
+ if (num)
+ eea_ering_kick(rx->ering);
+}
+
+static int eea_poll(struct napi_struct *napi, int budget)
+{
+ struct eea_irq_blk *blk = container_of(napi, struct eea_irq_blk, napi);
+ struct eea_net_rx *rx = blk->rx;
+ struct eea_net_tx *tx = &rx->enet->tx[rx->index];
+ struct eea_rx_ctx ctx = {};
+ bool busy = false;
+ u32 received;
+
+ busy |= eea_poll_tx(tx, budget) >= budget;
+
+ received = eea_cleanrx(rx, budget, &ctx);
+
+ if (rx->ering->num_free > budget) {
+ /* Due to the hardware design, there is no notification when
+ * buffers are exhausted. Therefore, we should proactively
+ * pre-fill the buffers to avoid starvation.
+ */
+ eea_rx_post(rx);
+
+ if (rx->ering->num - rx->ering->num_free < budget)
+ busy = true;
+ }
+
+ busy |= received >= budget;
+
+ if (busy)
+ return budget;
+
+ if (napi_complete_done(napi, received))
+ eea_ering_irq_active(rx->ering, tx->ering);
+
+ return received;
+}
+
static void eea_free_rx_buffers(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
{
struct eea_rx_meta *meta;
u32 i;
+ if (rx->pkt.head_skb) {
+ dev_kfree_skb(rx->pkt.head_skb);
+ rx->pkt.head_skb = NULL;
+ }
+
for (i = 0; i < cfg->rx_ring_depth; ++i) {
meta = &rx->meta[i];
if (!meta->page)
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v42 6/8] eea: implement packet transmit logic
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
In-Reply-To: <20260504144439.29570-1-xuanzhuo@linux.alibaba.com>
Implement the core logic for transmitting packets in the EEA TX path,
including packet preparation and submission to the underlying transport.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/eea_tx.c | 381 +++++++++++++++++++++-
1 file changed, 377 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
index 5a8babfce5de..1c5a55184ad9 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_tx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -11,6 +11,11 @@
#include "eea_pci.h"
#include "eea_ring.h"
+struct eea_sq_free_stats {
+ u64 packets;
+ u64 bytes;
+};
+
struct eea_tx_meta {
struct eea_tx_meta *next;
@@ -26,23 +31,391 @@ struct eea_tx_meta {
dma_addr_t dma_addr;
struct eea_tx_desc *desc;
u32 dma_len;
+ bool unmap;
+ bool unmap_single;
};
+static struct eea_tx_meta *eea_tx_meta_get(struct eea_net_tx *tx)
+{
+ struct eea_tx_meta *meta;
+
+ if (!tx->free)
+ return NULL;
+
+ meta = tx->free;
+ tx->free = meta->next;
+
+ return meta;
+}
+
+static void eea_tx_meta_put_and_unmap(struct eea_net_tx *tx,
+ struct eea_tx_meta *meta)
+{
+ struct eea_tx_meta *head;
+
+ head = meta;
+
+ while (true) {
+ if (meta->unmap) {
+ if (meta->unmap_single)
+ dma_unmap_single(tx->dma_dev, meta->dma_addr,
+ meta->dma_len, DMA_TO_DEVICE);
+ else
+ dma_unmap_page(tx->dma_dev, meta->dma_addr,
+ meta->dma_len, DMA_TO_DEVICE);
+ }
+
+ if (meta->next) {
+ meta = meta->next;
+ continue;
+ }
+
+ break;
+ }
+
+ meta->next = tx->free;
+ tx->free = head;
+}
+
+static void eea_meta_free_xmit(struct eea_net_tx *tx,
+ struct eea_tx_meta *meta,
+ int budget,
+ struct eea_tx_cdesc *desc,
+ struct eea_sq_free_stats *stats)
+{
+ struct sk_buff *skb = meta->skb;
+
+ if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && desc)) {
+ struct skb_shared_hwtstamps ts = {};
+
+ ts.hwtstamp = EEA_DESC_TS(desc) + tx->enet->hw_ts_offset;
+ skb_tstamp_tx(skb, &ts);
+ }
+
+ ++stats->packets;
+ napi_consume_skb(meta->skb, budget);
+
+ meta->data = NULL;
+}
+
+static int eea_clean_tx(struct eea_net_tx *tx, int budget)
+{
+ struct eea_sq_free_stats stats = {0};
+ struct eea_tx_cdesc *desc;
+ struct eea_tx_meta *meta;
+ int desc_n;
+ u16 id;
+
+ while (stats.packets < budget) {
+ desc = eea_ering_cq_get_desc(tx->ering);
+ if (!desc)
+ break;
+
+ id = le16_to_cpu(desc->id);
+ if (unlikely(id >= tx->ering->num)) {
+ if (net_ratelimit())
+ netdev_err(tx->enet->netdev, "tx invalid id %d\n",
+ id);
+ eea_ering_cq_ack_desc(tx->ering, 1);
+ continue;
+ }
+
+ meta = &tx->meta[id];
+
+ if (meta->data) {
+ eea_tx_meta_put_and_unmap(tx, meta);
+ eea_meta_free_xmit(tx, meta, budget, desc, &stats);
+ desc_n = meta->num;
+ } else {
+ if (net_ratelimit())
+ netdev_err(tx->enet->netdev,
+ "tx meta->data is null. id %d num: %d\n",
+ meta->id, meta->num);
+ desc_n = 1;
+ }
+
+ eea_ering_cq_ack_desc(tx->ering, desc_n);
+ }
+
+ return stats.packets;
+}
+
int eea_poll_tx(struct eea_net_tx *tx, int budget)
{
- /* Empty function; will be implemented in a subsequent commit. */
- return budget;
+ struct eea_net *enet = tx->enet;
+ u32 index = tx - enet->tx;
+ struct netdev_queue *txq;
+ int num;
+
+ txq = netdev_get_tx_queue(enet->netdev, index);
+
+ __netif_tx_lock(txq, smp_processor_id());
+
+ num = eea_clean_tx(tx, budget);
+
+ if (netif_tx_queue_stopped(txq) &&
+ tx->ering->num_free >= MAX_SKB_FRAGS + 2)
+ netif_tx_wake_queue(txq);
+
+ __netif_tx_unlock(txq);
+
+ return num;
+}
+
+static int eea_fill_desc_from_skb(const struct sk_buff *skb,
+ struct eea_tx_desc *desc)
+{
+ if (skb_is_gso(skb)) {
+ struct skb_shared_info *sinfo = skb_shinfo(skb);
+
+ desc->gso_size = cpu_to_le16(sinfo->gso_size);
+ if (sinfo->gso_type & SKB_GSO_TCPV4)
+ desc->gso_type = EEA_TX_GSO_TCPV4;
+
+ else if (sinfo->gso_type & SKB_GSO_TCPV6)
+ desc->gso_type = EEA_TX_GSO_TCPV6;
+
+ else if (sinfo->gso_type & SKB_GSO_UDP_L4)
+ desc->gso_type = EEA_TX_GSO_UDP_L4;
+
+ else
+ return -EINVAL;
+
+ if (sinfo->gso_type & SKB_GSO_TCP_ECN)
+ desc->gso_type |= EEA_TX_GSO_ECN;
+ } else {
+ desc->gso_type = EEA_TX_GSO_NONE;
+ }
+
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ desc->csum_start = cpu_to_le16(skb_checksum_start_offset(skb));
+ desc->csum_offset = cpu_to_le16(skb->csum_offset);
+ }
+
+ return 0;
+}
+
+static struct eea_tx_meta *__eea_tx_desc_fill(struct eea_net_tx *tx,
+ struct eea_tx_meta *head_meta,
+ dma_addr_t addr, u32 data_len,
+ u32 dma_len, bool last,
+ void *data, u16 flags,
+ bool unmap)
+{
+ struct eea_tx_meta *meta;
+ struct eea_tx_desc *desc;
+
+ meta = eea_tx_meta_get(tx);
+
+ desc = eea_ering_sq_alloc_desc(tx->ering, meta->id, last, flags);
+ desc->addr = cpu_to_le64(addr);
+ desc->len = cpu_to_le16(data_len);
+
+ meta->next = NULL;
+ meta->dma_len = dma_len;
+ meta->dma_addr = addr;
+ meta->data = data;
+ meta->num = 1;
+ meta->desc = desc;
+ meta->unmap = unmap;
+ meta->unmap_single = false;
+
+ if (head_meta) {
+ meta->next = head_meta->next;
+ head_meta->next = meta;
+ ++head_meta->num;
+ }
+
+ return meta;
+}
+
+static struct eea_tx_meta *eea_tx_desc_fill(struct eea_net_tx *tx,
+ struct eea_tx_meta *head_meta,
+ dma_addr_t addr, u32 length,
+ bool is_last, void *data, u16 flags)
+{
+ struct eea_tx_meta *meta;
+ u16 len, last;
+
+ WARN_ON_ONCE(length >= 2 * USHRT_MAX);
+
+ /* Since eea does not support BIG TCP, the maximum GSO size is capped at
+ * 64KB. Consequently, a single skb buffer (head or fragment) will not
+ * require more than two descriptors
+ */
+ if (length > USHRT_MAX) {
+ len = USHRT_MAX;
+ last = false;
+ } else {
+ len = length;
+ last = is_last;
+ }
+
+ meta = __eea_tx_desc_fill(tx, head_meta, addr, len, length,
+ last, data, flags, true);
+
+ if (length > USHRT_MAX) {
+ if (!head_meta)
+ head_meta = meta;
+
+ addr += USHRT_MAX;
+ len = length - USHRT_MAX;
+
+ __eea_tx_desc_fill(tx, head_meta, addr, len, 0, is_last,
+ NULL, 0, false);
+ }
+
+ return meta;
+}
+
+static int eea_tx_add_skb_frag(struct eea_net_tx *tx,
+ struct eea_tx_meta *head_meta,
+ const skb_frag_t *frag, bool is_last)
+{
+ u32 len = skb_frag_size(frag);
+ dma_addr_t addr;
+
+ addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
+ return -ENOMEM;
+
+ eea_tx_desc_fill(tx, head_meta, addr, len, is_last, NULL, 0);
+
+ return 0;
+}
+
+static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
+{
+ const struct skb_shared_info *shinfo = skb_shinfo(skb);
+ u32 hlen = skb_headlen(skb);
+ struct eea_tx_meta *meta;
+ const skb_frag_t *frag;
+ dma_addr_t addr;
+ u32 len = hlen;
+ int i, err;
+ u16 flags;
+ bool last;
+
+ if (len) {
+ addr = dma_map_single(tx->dma_dev, skb->data, len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
+ return -ENOMEM;
+
+ last = !shinfo->nr_frags;
+ i = 0;
+ } else {
+ /* The net stack will never submit an skb with an skb->len of
+ * 0. If the head len is 0, the number of frags must be greater
+ * than 0.
+ */
+ frag = &shinfo->frags[0];
+ len = skb_frag_size(frag);
+
+ addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
+ return -ENOMEM;
+
+ last = shinfo->nr_frags == 1;
+ i = 1;
+ }
+
+ flags = skb->ip_summed == CHECKSUM_PARTIAL ? EEA_DESC_F_DO_CSUM : 0;
+
+ meta = eea_tx_desc_fill(tx, NULL, addr, len, last, skb, flags);
+ meta->unmap_single = !!hlen;
+
+ err = eea_fill_desc_from_skb(skb, meta->desc);
+ if (err)
+ goto err_cancel;
+
+ for (; i < shinfo->nr_frags; i++) {
+ frag = &shinfo->frags[i];
+ bool is_last = i == (shinfo->nr_frags - 1);
+
+ err = eea_tx_add_skb_frag(tx, meta, frag, is_last);
+ if (err)
+ goto err_cancel;
+ }
+
+ eea_ering_sq_commit_desc(tx->ering);
+
+ return 0;
+
+err_cancel:
+ eea_ering_sq_cancel(tx->ering);
+ eea_tx_meta_put_and_unmap(tx, meta);
+ meta->data = NULL;
+ return err;
+}
+
+static void eea_tx_kick(struct eea_net_tx *tx)
+{
+ eea_ering_kick(tx->ering);
+}
+
+static int eea_tx_check_free_num(struct eea_net_tx *tx,
+ struct netdev_queue *txq)
+{
+ int n;
+
+ /* MAX_SKB_FRAGS + 1: Covers the skb linear head and all paged fragments
+ * 1: Extra slot for a head or fragment that exceeds 64KB.
+ */
+ n = MAX_SKB_FRAGS + 2;
+ return netif_txq_maybe_stop(txq, tx->ering->num_free, n, n);
}
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
{
- /* Empty function; will be implemented in a subsequent commit. */
- dev_kfree_skb_any(skb);
+ struct eea_net *enet = netdev_priv(netdev);
+ int qnum = skb_get_queue_mapping(skb);
+ struct eea_net_tx *tx = &enet->tx[qnum];
+ struct netdev_queue *txq;
+ int err, enable;
+
+ txq = netdev_get_tx_queue(netdev, qnum);
+
+ enable = eea_tx_check_free_num(tx, txq);
+ if (!enable)
+ return NETDEV_TX_BUSY;
+
+ err = eea_tx_post_skb(tx, skb);
+ if (unlikely(err))
+ dev_kfree_skb_any(skb);
+ else
+ skb_tx_timestamp(skb);
+
+ /* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue. */
+ eea_tx_check_free_num(tx, txq);
+
+ if (!netdev_xmit_more() || netif_xmit_stopped(txq))
+ eea_tx_kick(tx);
+
return NETDEV_TX_OK;
}
static void eea_free_meta(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
{
+ struct eea_sq_free_stats stats = {0};
+ struct eea_tx_meta *meta;
+ int i;
+
+ while ((meta = eea_tx_meta_get(tx)))
+ meta->skb = NULL;
+
+ for (i = 0; i < cfg->tx_ring_depth; i++) {
+ meta = &tx->meta[i];
+
+ if (!meta->skb)
+ continue;
+
+ eea_tx_meta_put_and_unmap(tx, meta);
+
+ eea_meta_free_xmit(tx, meta, 0, NULL, &stats);
+ }
+
kvfree(tx->meta);
tx->meta = NULL;
}
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v42 0/8] eea: Add basic driver framework for Alibaba Elastic Ethernet Adaptor
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
Add a driver framework for EEA that will be available in the future.
This driver is currently quite minimal, implementing only fundamental
core functionalities. Key features include: I/O queue management via
adminq, basic PCI-layer operations, and essential RX/TX data
communication capabilities. It also supports the creation,
initialization, and management of network devices (netdev). Furthermore,
the ring structures for both I/O queues and adminq have been abstracted
into a simple, unified, and reusable library implementation,
facilitating future extension and maintenance.
v42:
1. fix the comments from Jakub
2. fix the comments from sashiko
v41:
1. make https://sashiko.dev/ happy
v40:
1. fix comments from https://sashiko.dev/
v39:
1. fix comments from https://sashiko.dev/#/patchset/20260407122001.22265-1-xuanzhuo%40linux.alibaba.com
v38:
1. fix comments from https://sashiko.dev/#/patchset/20260404135144.33166-1-xuanzhuo%40linux.alibaba.com
v36:
1. fix comments from https://sashiko.dev/#/patchset/20260323074441.91691-1-xuanzhuo%40linux.alibaba.com
v35:
1. fix comments from https://netdev-ai.bots.linux.dev/ai-review.html?id=24371ffc-a1ea-46e1-a6b3-b8cbcbb52efb
v34:
1. fix the comments from https://sashiko.dev/#/patchset/20260317015257.79494-1-xuanzhuo%40linux.alibaba.com
v33:
1. fix the comments. old version refers enet_bind_new_q_and_cfg, but that is
changed to eea_bind_q_and_cfg.
v32:
1. remove unused parameters from eea_net_ha_reset_remove and eea_create_pp
2. call skb_mark_for_recycle for new skb for frag_list skbs
v31:
1. remove unused parameter "enet" from eex_rx_post()
v30:
1. fix some small problems
v29:
Address AI-generated review suggestions from the previous version.
v28:
Address AI-generated review suggestions from the previous version.
v27:
Address AI-generated review suggestions from the previous version.
v26:
Adopting suggestions from the previous AI review, another significant
change is the introduction of an IRQ block to implement IRQ proxying. With
this design, when an IRQ occurs, we no longer invoke the RX data structure
directly -- instead, the IRQ block serves as an intermediary proxy. This
approach offers several advantages: IRQ resources no longer need to be
reallocated during reset operations, and IRQs are decoupled from RX
structures. Consequently, when certain errors occur, we can fall back and
safely reuse the original memory resources.
v25:
I have adopted most of the suggestions from the AI's feedback. However, I
believe some of the feedback is incorrect. I have already replied in the
previous thread. http://lore.kernel.org/all/1770002612.3297296-2-xuanzhuo@linux.alibaba.com
v24:
1. Add null checks for enet->rx and enet->tx in eea_get_ethtool_stat to
prevent errors when reading rx = enet->rx[i] in case enet->rx is null.
tx is similar. With rtnl protection in place, this check is sufficient.
2. Use 'received' as the return value in eea_poll.
v23:
I have moved netif_set_real_num_queues() out of eea_start_rxtx(), so
eea_start_rxtx() is now a void function. I believe enet_bind_new_q_and_cfg()
is a more suitable place to include netif_set_real_num_queues(). In
eea_active_ring_and_irq(), I first execute request_irq() before interacting
with the hardware to create queues. Therefore, during the NIC setup process,
all driver-internal operations (memory allocation, IRQ initialization, sysfs
configuration, etc.) will be completed before the final notification to the
hardware.
v22:
1. Use the budget from the NAPI poll function as the parameter for
napi_consume_skb.
2. Stop the TX queue when the remaining ring slots cannot hold an SKB.
v21:
Fix two issues from the previous version:
1, a DMA unmap operation was missing.
2, RCU APIs were not used in eea_stats. Although the standard practice when
using RCU would require adding the __rcu annotation to both the rx and
tx fields, in many cases these fields are read without needing RCU
protection. Therefore, I do not want to add the __rcu annotation.
Instead, I use a spin lock to protect modifications to rx and tx.
v20:
Fix the partially initialized structure passed to db. @Jakub
http://lore.kernel.org/all/20260113172353.2ae6ef81@kernel.org
v19:
fix the comments from @Simon Horman
v18:
v17 with [PATCH] prefix.
v17:
1. In `eea_adminq_dev_status`, uniformly use `enet->cfg.rx_ring_num`.
2. Add a `struct eea_net_cfg *cfg` parameter to `eea_free_rx` and
`eea_free_tx`. When called in the normal path, pass `enet->cfg` as
the argument; when called during initialization, pass the temporary
`cfg` instead.
3. Move the `.ndo_get_stats64` callback into `eea_net.c`.
4. In the `.ndo_get_stats64` callback, add a comment explaining how the TX
and RX statistics are protected by RCU.
/* This function is protected by RCU. Here uses enet->tx and enet->rx
* to check whether the TX and RX structures are safe to access. In
* eea_free_rxtx_q_mem, before freeing the TX and RX resources, enet->rx
* and enet->tx are set to NULL, and synchronize_net is called.
*/
v16:
1. follow the advices from @ALOK TIWARI
http://lore.kernel.org/all/5ff95a71-69e5-4cb6-9b2a-5224c983bdc2@oracle.com
v15:
1. remove 'default m' from eea kconfig
2. free the resources when open failed.
v14:
1. some tiny fixes
v13:
1. fix some tiny fixes @Simon
v12:
I encountered some issues with sending the v11 patches, as they were quite
messy. Therefore, I'm resending them as v12.
v11:
1. remove auto clean __free(kfree)
2. some tiny fixes
v10:
1. name the jump labels after the target @Jakub
2. rm __GFP_ZERO from dma_alloc_coherent @Jakub
v9:
1. some fixes for ethtool from http://lore.kernel.org/all/20251027183754.52fe2a2c@kernel.org
v8: 1. rename eea_net_tmp to eea_net_init_ctx
2. rm code that allocs memory to destroy queues
3. some other minor changes
v7: 1. remove the irrelative code from ethtool commit
2. build every commits with W12
v6: Split the big one commit to five commits
v5: Thanks for the comments from Kalesh Anakkur Purayil, ALOK TIWARI
v4: Thanks for the comments from Troy Mitchell, Przemek Kitszel, Andrew Lunn, Kalesh Anakkur Purayil
v3: Thanks for the comments from Paolo Abenchi
v2: Thanks for the comments from Simon Horman and Andrew Lunn
v1: Thanks for the comments from Simon Horman and Andrew Lunn
Xuan Zhuo (8):
eea: introduce PCI framework
eea: introduce ring and descriptor structures
eea: probe the netdevice and create adminq
eea: create/destroy rx,tx queues for netdevice open and stop
eea: implement packet receive logic
eea: implement packet transmit logic
eea: introduce ethtool support
eea: introduce callback for ndo_get_stats64 and register netdev
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/alibaba/Kconfig | 28 +
drivers/net/ethernet/alibaba/Makefile | 5 +
drivers/net/ethernet/alibaba/eea/Makefile | 9 +
drivers/net/ethernet/alibaba/eea/eea_adminq.c | 518 ++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 83 ++
drivers/net/ethernet/alibaba/eea/eea_desc.h | 138 +++
.../net/ethernet/alibaba/eea/eea_ethtool.c | 273 ++++++
.../net/ethernet/alibaba/eea/eea_ethtool.h | 48 +
drivers/net/ethernet/alibaba/eea/eea_net.c | 885 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 198 ++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 730 +++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_pci.h | 73 ++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 249 +++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 99 ++
drivers/net/ethernet/alibaba/eea/eea_rx.c | 814 ++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_tx.c | 498 ++++++++++
19 files changed, 4658 insertions(+)
create mode 100644 drivers/net/ethernet/alibaba/Kconfig
create mode 100644 drivers/net/ethernet/alibaba/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_desc.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_rx.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_tx.c
--
2.32.0.3.g01195cf9f
^ permalink raw reply
* [PATCH net-next v42 1/8] eea: introduce PCI framework
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
In-Reply-To: <20260504144439.29570-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit implements the EEA PCI probe functionality.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/alibaba/Kconfig | 28 ++
drivers/net/ethernet/alibaba/Makefile | 5 +
drivers/net/ethernet/alibaba/eea/Makefile | 3 +
drivers/net/ethernet/alibaba/eea/eea_pci.c | 489 +++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_pci.h | 50 +++
8 files changed, 585 insertions(+)
create mode 100644 drivers/net/ethernet/alibaba/Kconfig
create mode 100644 drivers/net/ethernet/alibaba/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 27a073f53cea..2a8a4a90734e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -802,6 +802,14 @@ S: Maintained
F: Documentation/i2c/busses/i2c-ali1563.rst
F: drivers/i2c/busses/i2c-ali1563.c
+ALIBABA ELASTIC ETHERNET ADAPTER DRIVER
+M: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
+M: Wen Gu <guwen@linux.alibaba.com>
+R: Philo Lu <lulie@linux.alibaba.com>
+L: netdev@vger.kernel.org
+S: Maintained
+F: drivers/net/ethernet/alibaba/eea
+
ALIBABA ELASTIC RDMA DRIVER
M: Cheng Xu <chengyou@linux.alibaba.com>
M: Kai Shen <kaishen@linux.alibaba.com>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index b8f70e2a1763..78c79ad7bba5 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -22,6 +22,7 @@ source "drivers/net/ethernet/aeroflex/Kconfig"
source "drivers/net/ethernet/agere/Kconfig"
source "drivers/net/ethernet/airoha/Kconfig"
source "drivers/net/ethernet/alacritech/Kconfig"
+source "drivers/net/ethernet/alibaba/Kconfig"
source "drivers/net/ethernet/allwinner/Kconfig"
source "drivers/net/ethernet/altera/Kconfig"
source "drivers/net/ethernet/amazon/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 57344fec6ce0..bba55d9af387 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_NET_VENDOR_ADI) += adi/
obj-$(CONFIG_NET_VENDOR_AGERE) += agere/
obj-$(CONFIG_NET_VENDOR_AIROHA) += airoha/
obj-$(CONFIG_NET_VENDOR_ALACRITECH) += alacritech/
+obj-$(CONFIG_NET_VENDOR_ALIBABA) += alibaba/
obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/
obj-$(CONFIG_ALTERA_TSE) += altera/
obj-$(CONFIG_NET_VENDOR_AMAZON) += amazon/
diff --git a/drivers/net/ethernet/alibaba/Kconfig b/drivers/net/ethernet/alibaba/Kconfig
new file mode 100644
index 000000000000..b8fd3c92fa4c
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/Kconfig
@@ -0,0 +1,28 @@
+#
+# Alibaba network device configuration
+#
+
+config NET_VENDOR_ALIBABA
+ bool "Alibaba Devices"
+ default y
+ help
+ If you have a network (Ethernet) device belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Alibaba devices. If you say Y, you will be asked
+ for your specific device in the following questions.
+
+if NET_VENDOR_ALIBABA
+
+config ALIBABA_EEA
+ tristate "Alibaba Elastic Ethernet Adapter support"
+ depends on PCI_MSI
+ depends on 64BIT
+ select PAGE_POOL
+ help
+ This driver supports Alibaba Elastic Ethernet Adapter.
+
+ To compile this driver as a module, choose M here.
+
+endif #NET_VENDOR_ALIBABA
diff --git a/drivers/net/ethernet/alibaba/Makefile b/drivers/net/ethernet/alibaba/Makefile
new file mode 100644
index 000000000000..63a527e79ba7
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the Alibaba network device drivers.
+#
+
+obj-$(CONFIG_ALIBABA_EEA) += eea/
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
new file mode 100644
index 000000000000..a07f3019ec1f
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -0,0 +1,3 @@
+
+obj-$(CONFIG_ALIBABA_EEA) += eea.o
+eea-y := eea_pci.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
new file mode 100644
index 000000000000..185774dee38e
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
+
+#include "eea_pci.h"
+
+#define EEA_PCI_DB_OFFSET 4096
+#define EEA_PCI_DB_MIN_SIZE 8
+#define EEA_PCI_DB_MAX_SIZE 512
+#define EEA_PCI_Q_MAX_NUM 1000
+
+struct eea_pci_cfg {
+ __le32 reserve0;
+ __le32 reserve1;
+ __le32 drv_f_idx;
+ __le32 drv_f;
+
+#define EEA_S_INIT (BIT(0) | BIT(1))
+#define EEA_S_OK BIT(2)
+#define EEA_S_FEATURE_DONE BIT(3)
+#define EEA_S_FAILED BIT(7)
+ u8 device_status;
+ u8 reserved[7];
+
+ __le32 rx_num_max;
+ __le32 tx_num_max;
+ __le32 db_blk_size;
+
+ /* admin queue cfg */
+ __le16 aq_size;
+ __le16 aq_msix_vector;
+ __le32 aq_db_off;
+
+ __le32 aq_sq_addr;
+ __le32 aq_sq_addr_hi;
+ __le32 aq_cq_addr;
+ __le32 aq_cq_addr_hi;
+
+ __le32 reserved1;
+ __le64 hw_ts;
+};
+
+struct eea_pci_device {
+ struct eea_device edev;
+ struct pci_dev *pci_dev;
+
+ u32 msix_vec_n;
+ u32 db_len;
+
+ void __iomem *reg;
+ void __iomem *db_base;
+ void __iomem *db_end;
+
+ char ha_irq_name[32];
+ u8 reset_pos;
+ bool shutdown;
+};
+
+#define cfg_pointer(reg, item) \
+ ((void __iomem *)((reg) + offsetof(struct eea_pci_cfg, item)))
+
+#define cfg_write8(reg, item, val) iowrite8(val, cfg_pointer(reg, item))
+#define cfg_write32(reg, item, val) iowrite32(val, cfg_pointer(reg, item))
+
+#define cfg_read8(reg, item) ioread8(cfg_pointer(reg, item))
+#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
+#define cfg_read64(reg, item) ioread64(cfg_pointer(reg, item))
+
+const char *eea_pci_name(struct eea_device *edev)
+{
+ return pci_name(edev->ep_dev->pci_dev);
+}
+
+int eea_pci_domain_nr(struct eea_device *edev)
+{
+ return pci_domain_nr(edev->ep_dev->pci_dev->bus);
+}
+
+u16 eea_pci_bdf(struct eea_device *edev)
+{
+ return pci_dev_id(edev->ep_dev->pci_dev);
+}
+
+static void eea_pci_io_set_status(struct eea_device *edev, u8 status)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+
+ cfg_write8(ep_dev->reg, device_status, status);
+}
+
+static u8 eea_pci_io_get_status(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+
+ return cfg_read8(ep_dev->reg, device_status);
+}
+
+static void eea_add_status(struct eea_device *dev, u32 status)
+{
+ eea_pci_io_set_status(dev, eea_pci_io_get_status(dev) | status);
+}
+
+#define EEA_RESET_TIMEOUT_US (60 * 1000 * 1000)
+
+int eea_device_reset(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ int err;
+ u8 val;
+
+ eea_pci_io_set_status(edev, 0);
+
+ /* We are no longer waiting for device ack during the shutdown flow. */
+ if (ep_dev->shutdown)
+ return 0;
+
+ /* A longer timeout is set here to handle edge cases, though it should
+ * return promptly in most scenarios.
+ *
+ * In our case, all replies are handled by the DPU software, so there is
+ * no race condition between the hardware processes and the register.
+ */
+ err = read_poll_timeout(cfg_read8, val, (!val || val == 0xFF), 20,
+ EEA_RESET_TIMEOUT_US,
+ false, ep_dev->reg, device_status);
+
+ /* Surprise PCIe Removal */
+ if (val == 0xFF)
+ return -EINVAL;
+
+ return err;
+}
+
+int eea_pci_set_aq_up(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ u8 status = eea_pci_io_get_status(edev);
+ int err;
+ u8 val;
+
+ eea_pci_io_set_status(edev, status | EEA_S_OK);
+
+ /* A longer timeout is set here to handle edge cases, though it should
+ * return promptly in most scenarios.
+ *
+ * In our case, all replies are handled by the DPU software, so there is
+ * no race condition between the hardware processes and the register.
+ */
+ err = read_poll_timeout(cfg_read8, val,
+ val & (EEA_S_OK | EEA_S_FAILED),
+ 20, EEA_RESET_TIMEOUT_US,
+ false, ep_dev->reg, device_status);
+
+ /* Surprise PCIe Removal */
+ if (val == 0xFF)
+ return -EINVAL;
+
+ /* device fail */
+ if (val & EEA_S_FAILED)
+ return -EINVAL;
+
+ return err;
+}
+
+static int eea_negotiate(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev;
+ u32 status;
+
+ ep_dev = edev->ep_dev;
+
+ edev->features = 0;
+
+ cfg_write32(ep_dev->reg, drv_f_idx, 0);
+ cfg_write32(ep_dev->reg, drv_f, lower_32_bits(edev->features));
+ cfg_write32(ep_dev->reg, drv_f_idx, 1);
+ cfg_write32(ep_dev->reg, drv_f, upper_32_bits(edev->features));
+
+ eea_add_status(edev, EEA_S_FEATURE_DONE);
+ status = eea_pci_io_get_status(edev);
+
+ /* Surprise PCIe Removal */
+ if (status == 0xFF)
+ return -EINVAL;
+
+ if (!(status & EEA_S_FEATURE_DONE))
+ return -ENODEV;
+
+ return 0;
+}
+
+static void eea_pci_release_resource(struct eea_pci_device *ep_dev)
+{
+ struct pci_dev *pci_dev = ep_dev->pci_dev;
+
+ if (ep_dev->reg) {
+ pci_iounmap(pci_dev, ep_dev->reg);
+ ep_dev->reg = NULL;
+ }
+
+ if (ep_dev->msix_vec_n) {
+ ep_dev->msix_vec_n = 0;
+ pci_free_irq_vectors(ep_dev->pci_dev);
+ }
+
+ pci_clear_master(pci_dev);
+ pci_release_regions(pci_dev);
+ pci_disable_device(pci_dev);
+}
+
+static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
+{
+ int err, n, ret, len;
+
+ ep_dev->pci_dev = pci_dev;
+
+ err = pci_enable_device(pci_dev);
+ if (err)
+ return err;
+
+ err = pci_request_regions(pci_dev, "EEA");
+ if (err)
+ goto err_disable_dev;
+
+ if (pci_resource_len(pci_dev, 0) < EEA_PCI_DB_OFFSET) {
+ dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
+ (u64)pci_resource_len(pci_dev, 0));
+ err = -EINVAL;
+ goto err_release_regions;
+ }
+
+ ep_dev->reg = pci_iomap(pci_dev, 0, 0);
+ if (!ep_dev->reg) {
+ dev_err(&pci_dev->dev, "Failed to map pci bar!\n");
+ err = -ENOMEM;
+ goto err_release_regions;
+ }
+
+ err = eea_device_reset(&ep_dev->edev);
+ if (err) {
+ dev_err(&pci_dev->dev, "Failed to reset device for setup!\n");
+ goto err_unmap_reg;
+ }
+
+ err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
+ if (err) {
+ dev_warn(&pci_dev->dev, "Failed to enable 64-bit DMA.\n");
+ goto err_unmap_reg;
+ }
+
+ pci_set_master(pci_dev);
+
+ ep_dev->edev.rx_num = cfg_read32(ep_dev->reg, rx_num_max);
+ ep_dev->edev.tx_num = cfg_read32(ep_dev->reg, tx_num_max);
+
+ if (ep_dev->edev.rx_num > EEA_PCI_Q_MAX_NUM ||
+ ep_dev->edev.tx_num > EEA_PCI_Q_MAX_NUM) {
+ dev_err(&pci_dev->dev, "Invalid queue num %u %u\n",
+ ep_dev->edev.rx_num,
+ ep_dev->edev.tx_num);
+ err = -EINVAL;
+ goto err_clear_master;
+ }
+
+ ep_dev->edev.db_blk_size = cfg_read32(ep_dev->reg, db_blk_size);
+ if (!IS_ALIGNED(ep_dev->edev.db_blk_size, 8) ||
+ ep_dev->edev.db_blk_size > EEA_PCI_DB_MAX_SIZE ||
+ ep_dev->edev.db_blk_size < EEA_PCI_DB_MIN_SIZE) {
+ dev_err(&pci_dev->dev, "Invalid db size %u\n",
+ ep_dev->edev.db_blk_size);
+ err = -EINVAL;
+ goto err_clear_master;
+ }
+
+ ep_dev->db_len = ep_dev->edev.db_blk_size * (ep_dev->edev.rx_num +
+ ep_dev->edev.tx_num + 1);
+ ep_dev->db_base = ep_dev->reg + EEA_PCI_DB_OFFSET;
+ ep_dev->db_end = ep_dev->db_base + ep_dev->db_len;
+
+ len = ep_dev->db_end - ep_dev->reg;
+
+ if (pci_resource_len(pci_dev, 0) < len) {
+ dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
+ (u64)pci_resource_len(pci_dev, 0));
+ err = -EINVAL;
+ goto err_clear_master;
+ }
+
+ /* In our design, the number of hardware interrupts matches the maximum
+ * number of queues. If pci_alloc_irq_vectors failed, return directly.
+ *
+ * 2: adminq, error handle
+ */
+ n = ep_dev->edev.rx_num + 2;
+ ret = pci_alloc_irq_vectors(ep_dev->pci_dev, n, n, PCI_IRQ_MSIX);
+ if (ret != n) {
+ err = ret;
+ goto err_clear_master;
+ }
+
+ ep_dev->msix_vec_n = ret;
+
+ return 0;
+
+err_clear_master:
+ pci_clear_master(pci_dev);
+
+err_unmap_reg:
+ pci_iounmap(pci_dev, ep_dev->reg);
+ ep_dev->reg = NULL;
+
+err_release_regions:
+ pci_release_regions(pci_dev);
+
+err_disable_dev:
+ pci_disable_device(pci_dev);
+
+ return err;
+}
+
+void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off)
+{
+ u32 max_off;
+
+ if (!IS_ALIGNED(off, 8))
+ return NULL;
+
+ max_off = edev->ep_dev->db_len - edev->db_blk_size;
+
+ if (off > max_off)
+ return NULL;
+
+ return edev->ep_dev->db_base + off;
+}
+
+u64 eea_pci_device_ts(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+
+ return cfg_read64(ep_dev->reg, hw_ts);
+}
+
+static int eea_init_device(struct eea_device *edev)
+{
+ int err;
+
+ err = eea_device_reset(edev);
+ if (err)
+ return err;
+
+ eea_pci_io_set_status(edev, EEA_S_INIT);
+
+ err = eea_negotiate(edev);
+ if (err)
+ goto err;
+
+ /* do net device probe ... */
+
+ return 0;
+err:
+ eea_add_status(edev, EEA_S_FAILED);
+ return err;
+}
+
+static int __eea_pci_probe(struct pci_dev *pci_dev,
+ struct eea_pci_device *ep_dev)
+{
+ int err;
+
+ pci_set_drvdata(pci_dev, ep_dev);
+
+ err = eea_pci_setup(pci_dev, ep_dev);
+ if (err)
+ return err;
+
+ err = eea_init_device(&ep_dev->edev);
+ if (err)
+ goto err_pci_rel;
+
+ return 0;
+
+err_pci_rel:
+ eea_pci_release_resource(ep_dev);
+ return err;
+}
+
+static void __eea_pci_remove(struct pci_dev *pci_dev)
+{
+ struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+ struct device *dev = get_device(&ep_dev->pci_dev->dev);
+
+ eea_pci_release_resource(ep_dev);
+
+ put_device(dev);
+}
+
+static int eea_pci_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *id)
+{
+ struct eea_pci_device *ep_dev;
+ struct eea_device *edev;
+ int err;
+
+ ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL);
+ if (!ep_dev)
+ return -ENOMEM;
+
+ edev = &ep_dev->edev;
+
+ edev->ep_dev = ep_dev;
+ edev->dma_dev = &pci_dev->dev;
+
+ ep_dev->pci_dev = pci_dev;
+
+ err = __eea_pci_probe(pci_dev, ep_dev);
+ if (err) {
+ pci_set_drvdata(pci_dev, NULL);
+ kfree(ep_dev);
+ }
+
+ return err;
+}
+
+static void eea_pci_remove(struct pci_dev *pci_dev)
+{
+ struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+
+ eea_device_reset(&ep_dev->edev);
+
+ __eea_pci_remove(pci_dev);
+
+ pci_set_drvdata(pci_dev, NULL);
+ kfree(ep_dev);
+}
+
+static void eea_pci_shutdown(struct pci_dev *pci_dev)
+{
+ struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+ struct eea_device *edev;
+
+ edev = &ep_dev->edev;
+
+ ep_dev->shutdown = true;
+
+ /* do net device stop and clear. */
+
+ eea_device_reset(edev);
+
+ pci_clear_master(pci_dev);
+}
+
+static const struct pci_device_id eea_pci_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ALIBABA, 0x500B) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, eea_pci_id_table);
+
+static struct pci_driver eea_pci_driver = {
+ .name = "alibaba_eea",
+ .id_table = eea_pci_id_table,
+ .probe = eea_pci_probe,
+ .remove = eea_pci_remove,
+ .shutdown = eea_pci_shutdown,
+ .sriov_configure = pci_sriov_configure_simple,
+};
+
+static __init int eea_pci_init(void)
+{
+ return pci_register_driver(&eea_pci_driver);
+}
+
+static __exit void eea_pci_exit(void)
+{
+ pci_unregister_driver(&eea_pci_driver);
+}
+
+module_init(eea_pci_init);
+module_exit(eea_pci_exit);
+
+MODULE_DESCRIPTION("Driver for Alibaba Elastic Ethernet Adapter");
+MODULE_AUTHOR("Xuan Zhuo <xuanzhuo@linux.alibaba.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
new file mode 100644
index 000000000000..746cce4dd68e
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_PCI_H__
+#define __EEA_PCI_H__
+
+#include <linux/pci.h>
+
+struct eea_pci_cap {
+ __u8 cap_vndr;
+ __u8 cap_next;
+ __u8 cap_len;
+ __u8 cfg_type;
+};
+
+struct eea_pci_reset_reg {
+ struct eea_pci_cap cap;
+ __le16 driver;
+ __le16 device;
+};
+
+struct eea_pci_device;
+
+struct eea_device {
+ struct eea_pci_device *ep_dev;
+ struct device *dma_dev;
+ struct eea_net *enet;
+
+ u64 features;
+
+ u32 rx_num;
+ u32 tx_num;
+ u32 db_blk_size;
+};
+
+const char *eea_pci_name(struct eea_device *edev);
+int eea_pci_domain_nr(struct eea_device *edev);
+u16 eea_pci_bdf(struct eea_device *edev);
+
+int eea_device_reset(struct eea_device *dev);
+int eea_pci_set_aq_up(struct eea_device *dev);
+
+u64 eea_pci_device_ts(struct eea_device *edev);
+
+void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off);
+#endif
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v42 2/8] eea: introduce ring and descriptor structures
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
In-Reply-To: <20260504144439.29570-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces the ring and descriptor implementations.
These structures and ring APIs are used by the RX, TX, and admin queues.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 3 +-
drivers/net/ethernet/alibaba/eea/eea_desc.h | 138 +++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 249 ++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 99 ++++++++
4 files changed, 488 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_desc.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index a07f3019ec1f..7d8e7e8c2f3a 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_ALIBABA_EEA) += eea.o
-eea-y := eea_pci.o
+eea-y := eea_ring.o \
+ eea_pci.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_desc.h b/drivers/net/ethernet/alibaba/eea/eea_desc.h
new file mode 100644
index 000000000000..8d94a0f0f237
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_desc.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_DESC_H__
+#define __EEA_DESC_H__
+
+#define EEA_DESC_TS_MASK GENMASK_ULL(47, 0)
+#define EEA_DESC_TS(desc) (le64_to_cpu((desc)->ts) & EEA_DESC_TS_MASK)
+
+struct eea_aq_desc {
+ __le16 flags;
+ __le16 id;
+ __le16 reserved;
+ u8 classid;
+ u8 command;
+ __le64 data_addr;
+ __le64 reply_addr;
+ __le32 data_len;
+ __le32 reply_len;
+};
+
+struct eea_aq_cdesc {
+ __le16 flags;
+ __le16 id;
+#define EEA_OK 0
+#define EEA_ERR 0xffffffff
+ __le32 status;
+ __le32 reply_len;
+ __le32 reserved1;
+
+ __le64 reserved2;
+ __le64 reserved3;
+};
+
+struct eea_rx_desc_no_hdr {
+ __le16 flags;
+ __le16 id;
+ __le16 len;
+ __le16 reserved1;
+
+ __le64 addr;
+};
+
+struct eea_rx_desc {
+ __le16 flags;
+ __le16 id;
+ __le16 len;
+ __le16 reserved1;
+
+ __le64 addr;
+
+ __le64 hdr_addr;
+ __le32 reserved2;
+ __le32 reserved3;
+};
+
+#define EEA_RX_CDESC_HDR_LEN_MASK GENMASK_ULL(9, 0)
+
+struct eea_rx_cdesc {
+#define EEA_DESC_F_DATA_VALID BIT(6)
+#define EEA_DESC_F_SPLIT_HDR BIT(5)
+ __le16 flags;
+ __le16 id;
+ __le16 len;
+#define EEA_NET_PT_NONE 0
+#define EEA_NET_PT_IPv4 1
+#define EEA_NET_PT_TCPv4 2
+#define EEA_NET_PT_UDPv4 3
+#define EEA_NET_PT_IPv6 4
+#define EEA_NET_PT_TCPv6 5
+#define EEA_NET_PT_UDPv6 6
+#define EEA_NET_PT_IPv6_EX 7
+#define EEA_NET_PT_TCPv6_EX 8
+#define EEA_NET_PT_UDPv6_EX 9
+ /* [9:0] is packet type. */
+ __le16 type;
+
+ /* hw timestamp [0:47]: ts */
+ __le64 ts;
+
+ __le32 hash;
+
+ /* 0-9: hdr_len split header
+ * 10-15: reserved1
+ */
+ __le16 len_ex;
+ __le16 reserved2;
+
+ __le32 reserved3;
+ __le32 reserved4;
+};
+
+#define EEA_TX_GSO_NONE 0
+#define EEA_TX_GSO_TCPV4 1
+#define EEA_TX_GSO_TCPV6 4
+#define EEA_TX_GSO_UDP_L4 5
+#define EEA_TX_GSO_ECN 0x80
+
+struct eea_tx_desc {
+#define EEA_DESC_F_DO_CSUM BIT(6)
+ __le16 flags;
+ __le16 id;
+ __le16 len;
+ __le16 reserved1;
+
+ __le64 addr;
+
+ __le16 csum_start;
+ __le16 csum_offset;
+ u8 gso_type;
+ u8 reserved2;
+ __le16 gso_size;
+ __le64 reserved3;
+};
+
+struct eea_tx_cdesc {
+ __le16 flags;
+ __le16 id;
+ __le16 len;
+ __le16 reserved1;
+
+ /* hw timestamp [0:47]: ts */
+ __le64 ts;
+};
+
+#define EEA_DB_FLAGS_OFF 0
+#define EEA_DB_IDX_OFF (2 * 8)
+#define EEA_DB_TX_CQ_HEAD_OFF (4 * 8)
+#define EEA_DB_RX_CQ_HEAD_OFF (6 * 8)
+
+#define EEA_IDX_PRESENT BIT(0)
+#define EEA_IRQ_MASK BIT(1)
+#define EEA_IRQ_UNMASK BIT(2)
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ring.c b/drivers/net/ethernet/alibaba/eea/eea_ring.c
new file mode 100644
index 000000000000..99dcabd094b8
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include "eea_pci.h"
+#include "eea_ring.h"
+
+void eea_ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering)
+{
+ u64 value = 0, rx_idx, tx_idx;
+
+ tx_idx = (u64)tx_ering->cq.hw_idx;
+ rx_idx = (u64)ering->cq.hw_idx;
+
+ value |= EEA_IRQ_UNMASK << EEA_DB_FLAGS_OFF;
+ value |= tx_idx << EEA_DB_TX_CQ_HEAD_OFF;
+ value |= rx_idx << EEA_DB_RX_CQ_HEAD_OFF;
+
+ writeq(value, ering->db);
+}
+
+void *eea_ering_cq_get_desc(const struct eea_ring *ering)
+{
+ u8 phase;
+ u8 *desc;
+
+ desc = ering->cq.desc + (ering->cq.head << ering->cq.desc_size_shift);
+
+ phase = READ_ONCE(*(u8 *)(desc + ering->cq.desc_size - 1));
+
+ if ((phase & EEA_RING_DESC_F_CQ_PHASE) == ering->cq.phase) {
+ dma_rmb();
+ return desc;
+ }
+
+ return NULL;
+}
+
+/* sq api */
+void *eea_ering_sq_alloc_desc(struct eea_ring *ering, u16 id, bool is_last,
+ u16 flags)
+{
+ struct eea_ring_sq *sq = &ering->sq;
+ struct eea_common_desc *desc;
+
+ if (!sq->shadow_num) {
+ sq->shadow_idx = sq->head;
+ sq->shadow_id = cpu_to_le16(id);
+ }
+
+ if (!is_last)
+ flags |= EEA_RING_DESC_F_MORE;
+
+ desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);
+
+ desc->flags = cpu_to_le16(flags);
+ desc->id = sq->shadow_id;
+
+ if (unlikely(++sq->shadow_idx >= ering->num))
+ sq->shadow_idx = 0;
+
+ ++sq->shadow_num;
+
+ return desc;
+}
+
+/* This is an allocation API for admin Q. For each call to admin Q, only one
+ * desc will be allocated.
+ */
+void *eea_ering_aq_alloc_desc(struct eea_ring *ering)
+{
+ struct eea_ring_sq *sq = &ering->sq;
+ struct eea_common_desc *desc;
+
+ if (!sq->shadow_num)
+ sq->shadow_idx = sq->head;
+
+ desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);
+
+ if (unlikely(++sq->shadow_idx >= ering->num))
+ sq->shadow_idx = 0;
+
+ ++sq->shadow_num;
+
+ return desc;
+}
+
+void eea_ering_sq_commit_desc(struct eea_ring *ering)
+{
+ struct eea_ring_sq *sq = &ering->sq;
+ int num;
+
+ num = sq->shadow_num;
+
+ ering->num_free -= num;
+
+ sq->head = sq->shadow_idx;
+ sq->hw_idx += num;
+ sq->shadow_num = 0;
+}
+
+void eea_ering_sq_cancel(struct eea_ring *ering)
+{
+ ering->sq.shadow_num = 0;
+}
+
+/* cq api */
+void eea_ering_cq_ack_desc(struct eea_ring *ering, u32 num)
+{
+ struct eea_ring_cq *cq = &ering->cq;
+
+ cq->head += num;
+ cq->hw_idx += num;
+
+ if (unlikely(cq->head >= ering->num)) {
+ cq->head -= ering->num;
+ cq->phase ^= EEA_RING_DESC_F_CQ_PHASE;
+ }
+
+ ering->num_free += num;
+}
+
+/* notify */
+void eea_ering_kick(struct eea_ring *ering)
+{
+ u64 value = 0, idx;
+
+ idx = (u64)ering->sq.hw_idx;
+
+ value |= EEA_IDX_PRESENT << EEA_DB_FLAGS_OFF;
+ value |= idx << EEA_DB_IDX_OFF;
+
+ writeq(value, ering->db);
+}
+
+/* ering alloc/free */
+static void ering_free_queue(struct eea_device *edev, size_t size,
+ void *queue, dma_addr_t dma_handle)
+{
+ dma_free_coherent(edev->dma_dev, size, queue, dma_handle);
+}
+
+static void *ering_alloc_queue(struct eea_device *edev, size_t size,
+ dma_addr_t *dma_handle)
+{
+ gfp_t flags = GFP_KERNEL | __GFP_NOWARN;
+
+ return dma_alloc_coherent(edev->dma_dev, size, dma_handle, flags);
+}
+
+static int ering_alloc_queues(struct eea_ring *ering, struct eea_device *edev,
+ size_t num, u8 sq_desc_size, u8 cq_desc_size)
+{
+ dma_addr_t addr;
+ size_t size;
+ void *ring;
+
+ size = num * sq_desc_size;
+
+ ring = ering_alloc_queue(edev, size, &addr);
+ if (!ring)
+ return -ENOMEM;
+
+ ering->sq.desc = ring;
+ ering->sq.dma_addr = addr;
+ ering->sq.dma_size = size;
+ ering->sq.desc_size = sq_desc_size;
+ ering->sq.desc_size_shift = fls(sq_desc_size) - 1;
+
+ size = num * cq_desc_size;
+
+ ring = ering_alloc_queue(edev, size, &addr);
+ if (!ring)
+ goto err_free_sq;
+
+ ering->cq.desc = ring;
+ ering->cq.dma_addr = addr;
+ ering->cq.dma_size = size;
+ ering->cq.desc_size = cq_desc_size;
+ ering->cq.desc_size_shift = fls(cq_desc_size) - 1;
+
+ ering->num = num;
+
+ return 0;
+
+err_free_sq:
+ ering_free_queue(ering->edev, ering->sq.dma_size,
+ ering->sq.desc, ering->sq.dma_addr);
+ return -ENOMEM;
+}
+
+static void ering_init(struct eea_ring *ering)
+{
+ ering->cq.phase = EEA_RING_DESC_F_CQ_PHASE;
+ ering->num_free = ering->num;
+}
+
+struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev,
+ u8 sq_desc_size, u8 cq_desc_size,
+ const char *name)
+{
+ struct eea_ring *ering;
+
+ if (num > EEA_NET_IO_HW_RING_DEPTH_MAX ||
+ num < EEA_NET_IO_RING_DEPTH_MIN)
+ return NULL;
+
+ if (!is_power_of_2(num))
+ return NULL;
+
+ if (!sq_desc_size || !is_power_of_2(sq_desc_size))
+ return NULL;
+
+ if (!cq_desc_size || !is_power_of_2(cq_desc_size))
+ return NULL;
+
+ ering = kzalloc(sizeof(*ering), GFP_KERNEL);
+ if (!ering)
+ return NULL;
+
+ ering->edev = edev;
+ ering->name = name;
+ ering->index = index;
+
+ if (ering_alloc_queues(ering, edev, num, sq_desc_size, cq_desc_size))
+ goto err_free;
+
+ ering_init(ering);
+
+ return ering;
+
+err_free:
+ kfree(ering);
+ return NULL;
+}
+
+void eea_ering_free(struct eea_ring *ering)
+{
+ ering_free_queue(ering->edev, ering->cq.dma_size,
+ ering->cq.desc, ering->cq.dma_addr);
+
+ ering_free_queue(ering->edev, ering->sq.dma_size,
+ ering->sq.desc, ering->sq.dma_addr);
+
+ kfree(ering);
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ring.h b/drivers/net/ethernet/alibaba/eea/eea_ring.h
new file mode 100644
index 000000000000..a7ce465943a5
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_RING_H__
+#define __EEA_RING_H__
+
+#include <linux/dma-mapping.h>
+#include "eea_desc.h"
+
+#define EEA_RING_DESC_F_MORE BIT(0)
+#define EEA_RING_DESC_F_CQ_PHASE BIT(7)
+
+/* These two values define the bounds for the queue depth returned by the
+ * hardware.
+ */
+#define EEA_NET_IO_HW_RING_DEPTH_MAX (32 * 1024)
+#define EEA_NET_IO_HW_RING_DEPTH_MIN 128
+
+/* This value constrains the minimum queue depth that the driver configures for
+ * the hardware, which typically applies to user-provided settings. Naturally,
+ * the configured depth must also not exceed the maximum capacity supported by
+ * the hardware.
+ */
+#define EEA_NET_IO_RING_DEPTH_MIN 64
+
+struct eea_common_desc {
+ __le16 flags;
+ __le16 id;
+};
+
+struct eea_device;
+
+struct eea_ring_sq {
+ void *desc;
+
+ u16 head;
+ u16 hw_idx;
+
+ u16 shadow_idx;
+ __le16 shadow_id;
+ u16 shadow_num;
+
+ u8 desc_size;
+ u8 desc_size_shift;
+
+ dma_addr_t dma_addr;
+ u32 dma_size;
+};
+
+struct eea_ring_cq {
+ void *desc;
+
+ u16 head;
+ u16 hw_idx;
+
+ u8 phase;
+ u8 desc_size_shift;
+ u8 desc_size;
+
+ dma_addr_t dma_addr;
+ u32 dma_size;
+};
+
+struct eea_ring {
+ const char *name;
+ struct eea_device *edev;
+ u32 index;
+ void __iomem *db;
+ u16 msix_vec;
+
+ u32 num;
+
+ u32 num_free;
+
+ struct eea_ring_sq sq;
+ struct eea_ring_cq cq;
+};
+
+struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev,
+ u8 sq_desc_size, u8 cq_desc_size,
+ const char *name);
+void eea_ering_free(struct eea_ring *ering);
+void eea_ering_kick(struct eea_ring *ering);
+
+void *eea_ering_sq_alloc_desc(struct eea_ring *ering, u16 id,
+ bool is_last, u16 flags);
+void *eea_ering_aq_alloc_desc(struct eea_ring *ering);
+void eea_ering_sq_commit_desc(struct eea_ring *ering);
+void eea_ering_sq_cancel(struct eea_ring *ering);
+
+void eea_ering_cq_ack_desc(struct eea_ring *ering, u32 num);
+
+void eea_ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering);
+void *eea_ering_cq_get_desc(const struct eea_ring *ering);
+#endif
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v42 3/8] eea: probe the netdevice and create adminq
From: Xuan Zhuo @ 2026-05-04 14:44 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Mingyu Wang, Heiner Kallweit, Dust Li
In-Reply-To: <20260504144439.29570-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit creates the netdevice after PCI probe,
and initializes the admin queue to send commands to the device.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 6 +-
drivers/net/ethernet/alibaba/eea/eea_adminq.c | 518 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 83 +++
drivers/net/ethernet/alibaba/eea/eea_net.c | 249 +++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 137 +++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 35 +-
drivers/net/ethernet/alibaba/eea/eea_pci.h | 3 +
7 files changed, 1023 insertions(+), 8 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index 7d8e7e8c2f3a..a842ac416ae8 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,4 +1,6 @@
obj-$(CONFIG_ALIBABA_EEA) += eea.o
-eea-y := eea_ring.o \
- eea_pci.o
+eea-y := eea_ring.o \
+ eea_net.o \
+ eea_pci.o \
+ eea_adminq.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_adminq.c b/drivers/net/ethernet/alibaba/eea/eea_adminq.c
new file mode 100644
index 000000000000..c73a12757d45
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.c
@@ -0,0 +1,518 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/iopoll.h>
+#include <linux/utsname.h>
+#include <linux/version.h>
+
+#include "eea_adminq.h"
+#include "eea_net.h"
+#include "eea_pci.h"
+#include "eea_ring.h"
+
+#define EEA_AQ_CMD_CFG_QUERY ((0 << 8) | 0)
+
+#define EEA_AQ_CMD_QUEUE_CREATE ((1 << 8) | 0)
+#define EEA_AQ_CMD_QUEUE_DESTROY_ALL ((1 << 8) | 1)
+
+#define EEA_AQ_CMD_HOST_INFO ((2 << 8) | 0)
+
+#define EEA_AQ_CMD_DEV_STATUS ((3 << 8) | 0)
+
+#define EEA_RING_DESC_F_AQ_PHASE (BIT(15) | BIT(7))
+
+#define EEA_QUEUE_FLAGS_HW_SPLIT_HDR BIT(0)
+#define EEA_QUEUE_FLAGS_SQCQ BIT(1)
+#define EEA_QUEUE_FLAGS_HWTS BIT(2)
+
+struct eea_aq_create {
+ __le32 flags;
+ /* queue index.
+ * rx: 0 == qidx % 2
+ * tx: 1 == qidx % 2
+ */
+ __le16 qidx;
+ /* the depth of the queue */
+ __le16 depth;
+ /* 0: without SPLIT HDR
+ * 1: 128B
+ * 2: 256B
+ * 3: 512B
+ */
+ u8 hdr_buf_size;
+ u8 sq_desc_size;
+ u8 cq_desc_size;
+ u8 reserve0;
+ /* The vector for the irq. rx,tx share the same vector */
+ __le16 msix_vector;
+ __le16 reserve;
+ /* sq ring cfg. */
+ __le32 sq_addr_low;
+ __le32 sq_addr_high;
+ /* cq ring cfg. Just valid when flags include EEA_QUEUE_FLAGS_SQCQ. */
+ __le32 cq_addr_low;
+ __le32 cq_addr_high;
+};
+
+struct eea_aq_queue_drv_status {
+ __le16 qidx;
+
+ __le16 sq_head;
+ __le16 cq_head;
+ __le16 reserved;
+};
+
+#define EEA_OS_DISTRO 0
+#define EEA_DRV_TYPE 0
+#define EEA_OS_LINUX 1
+#define EEA_SPEC_VER_MAJOR 1
+#define EEA_SPEC_VER_MINOR 0
+
+struct eea_aq_host_info_cfg {
+ __le16 os_type;
+ __le16 os_dist;
+ __le16 drv_type;
+
+ __le16 kern_ver_major;
+ __le16 kern_ver_minor;
+ __le16 kern_ver_sub_minor;
+
+ __le16 drv_ver_major;
+ __le16 drv_ver_minor;
+ __le16 drv_ver_sub_minor;
+
+ __le16 spec_ver_major;
+ __le16 spec_ver_minor;
+ __le16 pci_bdf;
+ __le32 pci_domain;
+
+ u8 os_ver_str[64];
+ u8 isa_str[64];
+};
+
+#define EEA_HINFO_MAX_REP_LEN 1024
+#define EEA_HINFO_REP_BAD 2
+
+struct eea_aq_host_info_rep {
+ u8 op_code;
+ u8 has_reply;
+ u8 reply_str[EEA_HINFO_MAX_REP_LEN];
+};
+
+static struct eea_ring *qid_to_ering(struct eea_net *enet, u32 qid)
+{
+ struct eea_ring *ering;
+
+ if (qid % 2 == 0)
+ ering = enet->rx[qid / 2]->ering;
+ else
+ ering = enet->tx[qid / 2].ering;
+
+ return ering;
+}
+
+#define EEA_AQ_TIMEOUT_US (60 * 1000 * 1000)
+
+static int eea_adminq_submit(struct eea_net *enet, u16 cmd,
+ dma_addr_t req_addr, dma_addr_t res_addr,
+ u32 req_size, u32 res_size, u32 *reply_len)
+{
+ struct eea_aq_cdesc *cdesc;
+ struct eea_aq_desc *desc;
+ int ret;
+
+ desc = eea_ering_aq_alloc_desc(enet->adminq.ring);
+
+ desc->classid = cmd >> 8;
+ desc->command = cmd & 0xff;
+
+ desc->data_addr = cpu_to_le64(req_addr);
+ desc->data_len = cpu_to_le32(req_size);
+
+ desc->reply_addr = cpu_to_le64(res_addr);
+ desc->reply_len = cpu_to_le32(res_size);
+
+ /* for update flags */
+ dma_wmb();
+
+ desc->flags = cpu_to_le16(enet->adminq.phase);
+
+ eea_ering_sq_commit_desc(enet->adminq.ring);
+
+ eea_ering_kick(enet->adminq.ring);
+
+ ++enet->adminq.num;
+
+ if ((enet->adminq.num % enet->adminq.ring->num) == 0)
+ enet->adminq.phase ^= EEA_RING_DESC_F_AQ_PHASE;
+
+ ret = read_poll_timeout(eea_ering_cq_get_desc, cdesc, cdesc, 10,
+ EEA_AQ_TIMEOUT_US, false, enet->adminq.ring);
+ if (ret) {
+ netdev_err(enet->netdev,
+ "adminq exec timeout. cmd: %d reset device.\n",
+ cmd);
+ /* The device must be reset before unmapping buffers to avoid
+ * potential DMA writes after the memory is freed.
+ */
+ eea_device_reset(enet->edev);
+ enet->adminq.broken = true;
+ return ret;
+ }
+
+ /* Returns 0 on success, or a negative error code on failure. */
+ ret = le32_to_cpu(cdesc->status);
+
+ eea_ering_cq_ack_desc(enet->adminq.ring, 1);
+
+ if (ret)
+ netdev_err(enet->netdev,
+ "adminq exec failed. cmd: %d ret %d\n", cmd, ret);
+ else
+ *reply_len = le32_to_cpu(cdesc->reply_len);
+
+ return ret;
+}
+
+static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
+ void *req, u32 req_size,
+ void *res, u32 res_size,
+ u32 *reply)
+{
+ dma_addr_t req_addr = 0, res_addr = 0;
+ struct device *dma;
+ u32 reply_len = 0;
+ int ret;
+
+ if (enet->adminq.broken)
+ return -EIO;
+
+ if (reply)
+ *reply = 0;
+
+ dma = enet->edev->dma_dev;
+
+ if (req) {
+ req_addr = dma_map_single(dma, req, req_size, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dma, req_addr)))
+ return -ENOMEM;
+ }
+
+ if (res) {
+ res_addr = dma_map_single(dma, res, res_size, DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(dma, res_addr))) {
+ ret = -ENOMEM;
+ goto err_unmap_req;
+ }
+ }
+
+ mutex_lock(&enet->adminq.lock);
+ ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
+ req_size, res_size, &reply_len);
+ mutex_unlock(&enet->adminq.lock);
+ if (res) {
+ dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
+
+ if (ret)
+ memset(res, 0, res_size);
+ else if (res_size > reply_len)
+ memset(res + reply_len, 0, res_size - reply_len);
+
+ if (reply)
+ *reply = reply_len;
+ }
+
+err_unmap_req:
+ if (req)
+ dma_unmap_single(dma, req_addr, req_size, DMA_TO_DEVICE);
+
+ return ret;
+}
+
+void eea_destroy_adminq(struct eea_net *enet)
+{
+ struct eea_aq *aq;
+
+ aq = &enet->adminq;
+
+ if (aq->ring) {
+ eea_ering_free(aq->ring);
+ aq->ring = NULL;
+ aq->phase = 0;
+ }
+
+ kfree(aq->q_req_buf);
+ kfree(aq->q_res_buf);
+
+ aq->q_req_buf = NULL;
+ aq->q_res_buf = NULL;
+}
+
+int eea_create_adminq(struct eea_net *enet, u32 qid)
+{
+ u32 db_size, q_size, num;
+ struct eea_ring *ering;
+ struct eea_aq *aq;
+ int err = -ENOMEM;
+
+ num = enet->edev->rx_num + enet->edev->tx_num;
+ aq = &enet->adminq;
+
+ ering = eea_ering_alloc(qid, 64, enet->edev, sizeof(struct eea_aq_desc),
+ sizeof(struct eea_aq_cdesc), "adminq");
+ if (!ering)
+ return -ENOMEM;
+
+ aq->ring = ering;
+
+ err = eea_pci_active_aq(ering, qid / 2 + 1);
+ if (err)
+ goto err;
+
+ aq->phase = BIT(7);
+ aq->num = 0;
+
+ q_size = sizeof(*aq->q_req_buf) * num;
+ db_size = sizeof(*aq->q_res_buf) * num;
+
+ aq->q_req_size = q_size;
+ aq->q_res_size = db_size;
+
+ err = -ENOMEM;
+
+ aq->q_req_buf = kzalloc(q_size, GFP_KERNEL);
+ if (!aq->q_req_buf)
+ goto err;
+
+ aq->q_res_buf = kzalloc(db_size, GFP_KERNEL);
+ if (!aq->q_res_buf)
+ goto err;
+
+ /* Before we set up the AQ, the device remains in an inactive state, so
+ * there will be no DMA operations. If the 'set up AQ' process fails, we
+ * can safely free the DMA-related memory.
+ */
+ err = eea_pci_set_aq_up(enet->edev);
+ if (err)
+ goto err;
+
+ aq->broken = false;
+
+ mutex_init(&aq->lock);
+
+ return 0;
+
+err:
+ eea_destroy_adminq(enet);
+ return err;
+}
+
+int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg)
+{
+ return eea_adminq_exec(enet, EEA_AQ_CMD_CFG_QUERY, NULL, 0, cfg,
+ sizeof(*cfg), NULL);
+}
+
+static void qcfg_fill(struct eea_aq_create *qcfg, struct eea_ring *ering,
+ u32 flags)
+{
+ qcfg->flags = cpu_to_le32(flags);
+ qcfg->qidx = cpu_to_le16(ering->index);
+ qcfg->depth = cpu_to_le16(ering->num);
+
+ qcfg->hdr_buf_size = flags & EEA_QUEUE_FLAGS_HW_SPLIT_HDR ? 1 : 0;
+ qcfg->sq_desc_size = ering->sq.desc_size;
+ qcfg->cq_desc_size = ering->cq.desc_size;
+ qcfg->msix_vector = cpu_to_le16(ering->msix_vec);
+
+ qcfg->sq_addr_low = cpu_to_le32(lower_32_bits(ering->sq.dma_addr));
+ qcfg->sq_addr_high = cpu_to_le32(upper_32_bits(ering->sq.dma_addr));
+
+ qcfg->cq_addr_low = cpu_to_le32(lower_32_bits(ering->cq.dma_addr));
+ qcfg->cq_addr_high = cpu_to_le32(upper_32_bits(ering->cq.dma_addr));
+}
+
+int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags)
+{
+ int i, db_size, q_size, err = -ENOMEM;
+ struct eea_net_cfg *cfg;
+ struct eea_ring *ering;
+ struct eea_aq *aq;
+ u32 reply_len;
+
+ cfg = &enet->cfg;
+ aq = &enet->adminq;
+
+ if (cfg->split_hdr)
+ flags |= EEA_QUEUE_FLAGS_HW_SPLIT_HDR;
+
+ flags |= EEA_QUEUE_FLAGS_SQCQ;
+ flags |= EEA_QUEUE_FLAGS_HWTS;
+
+ q_size = sizeof(*aq->q_req_buf) * num;
+ db_size = sizeof(*aq->q_res_buf) * num;
+
+ for (i = 0; i < num; i++) {
+ ering = qid_to_ering(enet, i);
+ qcfg_fill(aq->q_req_buf + i, ering, flags);
+ }
+
+ err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_CREATE,
+ aq->q_req_buf, q_size,
+ aq->q_res_buf, db_size,
+ &reply_len);
+ if (err)
+ return err;
+
+ if (reply_len != db_size) {
+ eea_adminq_destroy_all_q(enet);
+ netdev_err(enet->netdev, "invalid reply len %u\n", reply_len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num; i++) {
+ ering = qid_to_ering(enet, i);
+ ering->db = eea_pci_db_addr(ering->edev,
+ le32_to_cpu(aq->q_res_buf[i]));
+ if (!ering->db) {
+ netdev_err(enet->netdev, "invalid db off %u\n",
+ le32_to_cpu(aq->q_res_buf[i]));
+ goto err;
+ }
+ }
+
+ return err;
+
+err:
+ eea_adminq_destroy_all_q(enet);
+ for (i = 0; i < num; i++) {
+ ering = qid_to_ering(enet, i);
+ ering->db = NULL;
+ }
+
+ return -EIO;
+}
+
+int eea_adminq_destroy_all_q(struct eea_net *enet)
+{
+ return eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_DESTROY_ALL, NULL, 0,
+ NULL, 0, NULL);
+}
+
+/* The caller must ensure that both the 'rx' and 'tx' arrays are valid. */
+int eea_adminq_dev_status(struct eea_net *enet,
+ struct eea_aq_dev_status *dstatus)
+{
+ struct eea_aq_queue_drv_status *drv_status;
+ struct __eea_aq_dev_status *dev_status;
+ int err, i, io_num, size, q_num;
+ struct eea_ring *ering;
+ void *rep, *req;
+
+ q_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num + 1;
+ io_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num;
+
+ req = kcalloc(q_num, sizeof(struct eea_aq_queue_drv_status),
+ GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ size = struct_size(dev_status, q_status, q_num);
+
+ rep = kzalloc(size, GFP_KERNEL);
+ if (!rep) {
+ kfree(req);
+ return -ENOMEM;
+ }
+
+ drv_status = req;
+ for (i = 0; i < io_num; ++i, ++drv_status) {
+ ering = qid_to_ering(enet, i);
+ drv_status->qidx = cpu_to_le16(i);
+ drv_status->cq_head = cpu_to_le16(ering->cq.head);
+ drv_status->sq_head = cpu_to_le16(ering->sq.head);
+ }
+
+ drv_status->qidx = cpu_to_le16(i);
+ drv_status->cq_head = cpu_to_le16(enet->adminq.ring->cq.head);
+ drv_status->sq_head = cpu_to_le16(enet->adminq.ring->sq.head);
+
+ err = eea_adminq_exec(enet, EEA_AQ_CMD_DEV_STATUS, req,
+ q_num * sizeof(struct eea_aq_queue_drv_status),
+ rep, size, NULL);
+ kfree(req);
+ if (err) {
+ kfree(rep);
+ return err;
+ }
+
+ dstatus->num = q_num;
+ dstatus->status = rep;
+
+ return 0;
+}
+
+void eea_adminq_config_host_info(struct eea_net *enet)
+{
+ struct device *dev = enet->edev->dma_dev;
+ struct eea_aq_host_info_cfg *cfg;
+ struct eea_aq_host_info_rep *rep;
+ int rc = -ENOMEM;
+
+ cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return;
+
+ rep = kzalloc(sizeof(*rep), GFP_KERNEL);
+ if (!rep)
+ goto err_free_cfg;
+
+ cfg->os_type = cpu_to_le16(EEA_OS_LINUX);
+ cfg->os_dist = cpu_to_le16(EEA_OS_DISTRO);
+ cfg->drv_type = cpu_to_le16(EEA_DRV_TYPE);
+
+ cfg->kern_ver_major = cpu_to_le16(LINUX_VERSION_MAJOR);
+ cfg->kern_ver_minor = cpu_to_le16(LINUX_VERSION_PATCHLEVEL);
+ cfg->kern_ver_sub_minor = cpu_to_le16(LINUX_VERSION_SUBLEVEL);
+
+ cfg->drv_ver_major = cpu_to_le16(EEA_VER_MAJOR);
+ cfg->drv_ver_minor = cpu_to_le16(EEA_VER_MINOR);
+ cfg->drv_ver_sub_minor = cpu_to_le16(EEA_VER_SUB_MINOR);
+
+ cfg->spec_ver_major = cpu_to_le16(EEA_SPEC_VER_MAJOR);
+ cfg->spec_ver_minor = cpu_to_le16(EEA_SPEC_VER_MINOR);
+
+ cfg->pci_bdf = cpu_to_le16(eea_pci_bdf(enet->edev));
+ cfg->pci_domain = cpu_to_le32(eea_pci_domain_nr(enet->edev));
+
+ strscpy(cfg->os_ver_str, utsname()->release, sizeof(cfg->os_ver_str));
+ strscpy(cfg->isa_str, utsname()->machine, sizeof(cfg->isa_str));
+
+ rc = eea_adminq_exec(enet, EEA_AQ_CMD_HOST_INFO,
+ cfg, sizeof(*cfg), rep, sizeof(*rep), NULL);
+
+ if (!rc) {
+ if (rep->op_code == EEA_HINFO_REP_BAD)
+ dev_warn(dev, "The hardware-driven state validation may be abnormal.\n");
+
+ if (rep->has_reply) {
+ char buf[EEA_HINFO_MAX_REP_LEN];
+
+ rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
+
+ string_escape_str(rep->reply_str, buf, sizeof(buf),
+ ESCAPE_NP, NULL);
+
+ dev_warn(dev, "Device replied: %s\n", buf);
+ }
+ }
+
+ kfree(rep);
+err_free_cfg:
+ kfree(cfg);
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_adminq.h b/drivers/net/ethernet/alibaba/eea/eea_adminq.h
new file mode 100644
index 000000000000..0182f5641fcf
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_ADMINQ_H__
+#define __EEA_ADMINQ_H__
+
+struct eea_aq_cfg {
+ __le32 rx_depth_max;
+ __le32 rx_depth_def;
+
+ __le32 tx_depth_max;
+ __le32 tx_depth_def;
+
+ __le32 max_tso_size;
+ __le32 max_tso_segs;
+
+ u8 mac[ETH_ALEN];
+ __le16 status;
+
+ __le16 mtu;
+ __le16 reserved0;
+ __le16 reserved1;
+ u8 reserved2;
+ u8 reserved3;
+
+ __le16 reserved4;
+ __le16 reserved5;
+ __le16 reserved6;
+};
+
+struct eea_aq_queue_status {
+ __le16 qidx;
+#define EEA_QUEUE_STATUS_OK 0
+#define EEA_QUEUE_STATUS_NEED_RESET 1
+ __le16 status;
+};
+
+struct __eea_aq_dev_status {
+#define EEA_LINK_DOWN_STATUS 0
+#define EEA_LINK_UP_STATUS 1
+ __le16 link_status;
+ __le16 reserved;
+
+ struct eea_aq_queue_status q_status[];
+};
+
+struct eea_aq_dev_status {
+ u32 num;
+ struct __eea_aq_dev_status *status;
+};
+
+struct eea_aq {
+ struct eea_ring *ring;
+ u32 num;
+ bool broken;
+ u16 phase;
+
+ /* lock for adminq exec */
+ struct mutex lock;
+
+ u32 q_req_size;
+ u32 q_res_size;
+ struct eea_aq_create *q_req_buf;
+ __le32 *q_res_buf;
+};
+
+struct eea_net;
+
+int eea_create_adminq(struct eea_net *enet, u32 qid);
+void eea_destroy_adminq(struct eea_net *enet);
+
+int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg);
+
+int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags);
+int eea_adminq_destroy_all_q(struct eea_net *enet);
+int eea_adminq_dev_status(struct eea_net *enet,
+ struct eea_aq_dev_status *dstatus);
+void eea_adminq_config_host_info(struct eea_net *enet);
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
new file mode 100644
index 000000000000..bb8a49f8c6df
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <net/netdev_queues.h>
+
+#include "eea_adminq.h"
+#include "eea_net.h"
+#include "eea_pci.h"
+#include "eea_ring.h"
+
+#define EEA_SPLIT_HDR_SIZE ALIGN(128, L1_CACHE_BYTES)
+
+static int eea_update_cfg(struct eea_net *enet,
+ struct eea_device *edev,
+ struct eea_aq_cfg *hwcfg)
+{
+ u32 rx_max = le32_to_cpu(hwcfg->rx_depth_max);
+ u32 tx_max = le32_to_cpu(hwcfg->tx_depth_max);
+ u32 rx_def = le32_to_cpu(hwcfg->rx_depth_def);
+ u32 tx_def = le32_to_cpu(hwcfg->tx_depth_def);
+
+ /* Now, we assert that the rx ring num is equal to the tx ring num. */
+ if (edev->rx_num != edev->tx_num) {
+ dev_err(edev->dma_dev, "Inconsistent ring num: RX %u, TX %u\n",
+ edev->rx_num, edev->tx_num);
+ return -EINVAL;
+ }
+
+ if (rx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
+ rx_max < EEA_NET_IO_HW_RING_DEPTH_MIN ||
+ tx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
+ tx_max < EEA_NET_IO_HW_RING_DEPTH_MIN) {
+ dev_err(edev->dma_dev, "Invalid HW max depth: RX %u, TX %u\n",
+ rx_max, tx_max);
+ return -EINVAL;
+ }
+
+ if (rx_def > rx_max ||
+ tx_def > tx_max ||
+ rx_def < EEA_NET_IO_HW_RING_DEPTH_MIN ||
+ tx_def < EEA_NET_IO_HW_RING_DEPTH_MIN) {
+ dev_err(edev->dma_dev, "Invalid default depth: RX %u (max %u), TX %u (max %u)\n",
+ rx_def, rx_max, tx_def, tx_max);
+ return -EINVAL;
+ }
+
+ if (!is_power_of_2(rx_max) || !is_power_of_2(tx_max) ||
+ !is_power_of_2(rx_def) || !is_power_of_2(tx_def)) {
+ dev_err(edev->dma_dev, "Ring depth must be power of 2\n");
+ return -EINVAL;
+ }
+
+ enet->cfg_hw.rx_ring_depth = rx_max;
+ enet->cfg_hw.tx_ring_depth = tx_max;
+ enet->cfg_hw.rx_ring_num = edev->rx_num;
+ enet->cfg_hw.tx_ring_num = edev->tx_num;
+ enet->cfg_hw.split_hdr = EEA_SPLIT_HDR_SIZE;
+
+ enet->cfg.rx_ring_depth = rx_def;
+ enet->cfg.tx_ring_depth = tx_def;
+ enet->cfg.rx_ring_num = edev->rx_num;
+ enet->cfg.tx_ring_num = edev->tx_num;
+
+ return 0;
+}
+
+static int eea_netdev_init_features(struct net_device *netdev,
+ struct eea_net *enet,
+ struct eea_device *edev)
+{
+ struct eea_aq_cfg *cfg;
+ int err;
+ u32 mtu;
+
+ cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ err = eea_adminq_query_cfg(enet, cfg);
+ if (err)
+ goto err_free;
+
+ mtu = le16_to_cpu(cfg->mtu);
+ if (mtu < ETH_MIN_MTU) {
+ dev_err(edev->dma_dev, "The device gave us an invalid MTU. Here we can only exit the initialization. %u < %u\n",
+ mtu, ETH_MIN_MTU);
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ err = eea_update_cfg(enet, edev, cfg);
+ if (err)
+ goto err_free;
+
+ netdev->priv_flags |= IFF_UNICAST_FLT;
+ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+
+ netdev->hw_features |= NETIF_F_HW_CSUM;
+ netdev->hw_features |= NETIF_F_GRO_HW;
+ netdev->hw_features |= NETIF_F_SG;
+ netdev->hw_features |= NETIF_F_TSO;
+ netdev->hw_features |= NETIF_F_TSO_ECN;
+ netdev->hw_features |= NETIF_F_TSO6;
+ netdev->hw_features |= NETIF_F_GSO_UDP_L4;
+
+ netdev->features |= NETIF_F_HIGHDMA;
+ netdev->features |= NETIF_F_HW_CSUM;
+ netdev->features |= NETIF_F_SG;
+ netdev->features |= NETIF_F_GSO_ROBUST;
+ netdev->features |= netdev->hw_features & NETIF_F_ALL_TSO;
+ netdev->features |= NETIF_F_RXCSUM;
+ netdev->features |= NETIF_F_GRO_HW;
+
+ netdev->vlan_features = netdev->features;
+
+ if (!is_valid_ether_addr(cfg->mac)) {
+ dev_err(edev->dma_dev, "The device gave invalid mac %pM\n",
+ cfg->mac);
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ eth_hw_addr_set(netdev, cfg->mac);
+
+ enet->speed = SPEED_UNKNOWN;
+ enet->duplex = DUPLEX_UNKNOWN;
+
+ netdev->min_mtu = ETH_MIN_MTU;
+
+ netdev->mtu = mtu;
+
+ /* If jumbo frames are already enabled, then the returned MTU will be a
+ * jumbo MTU, and the driver will automatically enable jumbo frame
+ * support by default.
+ */
+ netdev->max_mtu = mtu;
+
+err_free:
+ kfree(cfg);
+ return err;
+}
+
+static const struct net_device_ops eea_netdev = {
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_features_check = passthru_features_check,
+};
+
+static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
+{
+ struct net_device *netdev;
+ struct eea_net *enet;
+
+ netdev = alloc_etherdev_mq(sizeof(struct eea_net), pairs);
+ if (!netdev) {
+ dev_err(edev->dma_dev,
+ "alloc_etherdev_mq failed with pairs %d\n", pairs);
+ return NULL;
+ }
+
+ netdev->netdev_ops = &eea_netdev;
+ SET_NETDEV_DEV(netdev, edev->dma_dev);
+
+ enet = netdev_priv(netdev);
+ enet->netdev = netdev;
+ enet->edev = edev;
+ edev->enet = enet;
+
+ return enet;
+}
+
+int eea_net_probe(struct eea_device *edev)
+{
+ struct eea_net *enet;
+ int err = -ENOMEM;
+
+ enet = eea_netdev_alloc(edev, edev->rx_num);
+ if (!enet)
+ return -ENOMEM;
+
+ err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
+ if (err)
+ goto err_free_netdev;
+
+ eea_adminq_config_host_info(enet);
+
+ err = eea_netdev_init_features(enet->netdev, enet, edev);
+ if (err)
+ goto err_reset_dev;
+
+ netdev_dbg(enet->netdev, "eea probe success.\n");
+
+ /* Queue TX/RX implementation is still in progress. register_netdev is
+ * deferred until these are completed in subsequent commits.
+ */
+
+ return 0;
+
+err_reset_dev:
+ eea_device_reset(edev);
+ eea_destroy_adminq(enet);
+
+err_free_netdev:
+ free_netdev(enet->netdev);
+ return err;
+}
+
+void eea_net_remove(struct eea_device *edev)
+{
+ struct net_device *netdev;
+ struct eea_net *enet;
+
+ enet = edev->enet;
+ netdev = enet->netdev;
+
+ netdev_dbg(enet->netdev, "eea removed.\n");
+
+ eea_device_reset(edev);
+
+ eea_destroy_adminq(enet);
+
+ free_netdev(netdev);
+}
+
+void eea_net_shutdown(struct eea_device *edev)
+{
+ struct net_device *netdev;
+ struct eea_net *enet;
+
+ enet = edev->enet;
+ netdev = enet->netdev;
+
+ rtnl_lock();
+
+ netif_device_detach(netdev);
+
+ eea_device_reset(edev);
+
+ eea_destroy_adminq(enet);
+
+ rtnl_unlock();
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
new file mode 100644
index 000000000000..fa0eec8af21b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_NET_H__
+#define __EEA_NET_H__
+
+#include <linux/ethtool.h>
+#include <linux/netdevice.h>
+
+#include "eea_adminq.h"
+#include "eea_ring.h"
+
+#define EEA_VER_MAJOR 1
+#define EEA_VER_MINOR 0
+#define EEA_VER_SUB_MINOR 0
+
+struct eea_net_tx {
+ struct eea_net *enet;
+
+ struct eea_ring *ering;
+
+ struct eea_tx_meta *meta;
+ struct eea_tx_meta *free;
+
+ struct device *dma_dev;
+
+ u32 index;
+
+ char name[16];
+};
+
+struct eea_rx_meta {
+ struct eea_rx_meta *next;
+
+ struct page *page;
+ dma_addr_t dma;
+ u32 offset;
+ u32 frags;
+
+ struct page *hdr_page;
+ void *hdr_addr;
+ dma_addr_t hdr_dma;
+
+ u32 id;
+
+ u32 truesize;
+ u32 headroom;
+ u32 tailroom;
+
+ u32 len;
+};
+
+struct eea_net_rx_pkt_ctx {
+ u16 idx;
+
+ bool data_valid;
+ bool do_drop;
+
+ struct sk_buff *head_skb;
+};
+
+struct eea_net_rx {
+ struct eea_net *enet;
+
+ struct eea_ring *ering;
+
+ struct eea_rx_meta *meta;
+ struct eea_rx_meta *free;
+
+ struct device *dma_dev;
+
+ u32 index;
+
+ u32 flags;
+
+ u32 headroom;
+
+ struct napi_struct *napi;
+
+ char name[16];
+
+ struct eea_net_rx_pkt_ctx pkt;
+
+ struct page_pool *pp;
+};
+
+struct eea_net_cfg {
+ u32 rx_ring_depth;
+ u32 tx_ring_depth;
+ u32 rx_ring_num;
+ u32 tx_ring_num;
+
+ u8 rx_sq_desc_size;
+ u8 rx_cq_desc_size;
+ u8 tx_sq_desc_size;
+ u8 tx_cq_desc_size;
+
+ u32 split_hdr;
+};
+
+enum {
+ EEA_LINK_ERR_NONE,
+ EEA_LINK_ERR_HA_RESET_DEV,
+ EEA_LINK_ERR_LINK_DOWN,
+};
+
+struct eea_net {
+ struct eea_device *edev;
+ struct net_device *netdev;
+
+ struct eea_aq adminq;
+
+ struct eea_net_tx *tx;
+ struct eea_net_rx **rx;
+
+ struct eea_net_cfg cfg;
+ struct eea_net_cfg cfg_hw;
+
+ u32 link_err;
+
+ bool started;
+
+ u8 duplex;
+ u32 speed;
+
+ u64 hw_ts_offset;
+};
+
+int eea_net_probe(struct eea_device *edev);
+void eea_net_remove(struct eea_device *edev);
+void eea_net_shutdown(struct eea_device *edev);
+
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
index 185774dee38e..e7874979155b 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -8,6 +8,7 @@
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
+#include "eea_net.h"
#include "eea_pci.h"
#define EEA_PCI_DB_OFFSET 4096
@@ -66,7 +67,9 @@ struct eea_pci_device {
((void __iomem *)((reg) + offsetof(struct eea_pci_cfg, item)))
#define cfg_write8(reg, item, val) iowrite8(val, cfg_pointer(reg, item))
+#define cfg_write16(reg, item, val) iowrite16(val, cfg_pointer(reg, item))
#define cfg_write32(reg, item, val) iowrite32(val, cfg_pointer(reg, item))
+#define cfg_write64(reg, item, val) iowrite64_lo_hi(val, cfg_pointer(reg, item))
#define cfg_read8(reg, item) ioread8(cfg_pointer(reg, item))
#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
@@ -339,6 +342,25 @@ void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off)
return edev->ep_dev->db_base + off;
}
+int eea_pci_active_aq(struct eea_ring *ering, int msix_vec)
+{
+ struct eea_pci_device *ep_dev = ering->edev->ep_dev;
+
+ cfg_write16(ep_dev->reg, aq_size, ering->num);
+ cfg_write16(ep_dev->reg, aq_msix_vector, msix_vec);
+
+ cfg_write64(ep_dev->reg, aq_sq_addr, ering->sq.dma_addr);
+ cfg_write64(ep_dev->reg, aq_cq_addr, ering->cq.dma_addr);
+
+ ering->db = eea_pci_db_addr(ering->edev,
+ cfg_read32(ep_dev->reg, aq_db_off));
+
+ if (!ering->db)
+ return -EIO;
+
+ return 0;
+}
+
u64 eea_pci_device_ts(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
@@ -360,7 +382,9 @@ static int eea_init_device(struct eea_device *edev)
if (err)
goto err;
- /* do net device probe ... */
+ err = eea_net_probe(edev);
+ if (err)
+ goto err;
return 0;
err:
@@ -394,6 +418,9 @@ static void __eea_pci_remove(struct pci_dev *pci_dev)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
struct device *dev = get_device(&ep_dev->pci_dev->dev);
+ struct eea_device *edev = &ep_dev->edev;
+
+ eea_net_remove(edev);
eea_pci_release_resource(ep_dev);
@@ -431,8 +458,6 @@ static void eea_pci_remove(struct pci_dev *pci_dev)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
- eea_device_reset(&ep_dev->edev);
-
__eea_pci_remove(pci_dev);
pci_set_drvdata(pci_dev, NULL);
@@ -448,9 +473,7 @@ static void eea_pci_shutdown(struct pci_dev *pci_dev)
ep_dev->shutdown = true;
- /* do net device stop and clear. */
-
- eea_device_reset(edev);
+ eea_net_shutdown(edev);
pci_clear_master(pci_dev);
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
index 746cce4dd68e..cfd278e2efde 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -10,6 +10,8 @@
#include <linux/pci.h>
+#include "eea_ring.h"
+
struct eea_pci_cap {
__u8 cap_vndr;
__u8 cap_next;
@@ -43,6 +45,7 @@ u16 eea_pci_bdf(struct eea_device *edev);
int eea_device_reset(struct eea_device *dev);
int eea_pci_set_aq_up(struct eea_device *dev);
+int eea_pci_active_aq(struct eea_ring *ering, int msix_vec);
u64 eea_pci_device_ts(struct eea_device *edev);
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox