* [PATCH net-next v39 0/8] eea: Add basic driver framework for Alibaba Elastic Ethernet Adaptor
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, 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.
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
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 | 482 ++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 74 ++
drivers/net/ethernet/alibaba/eea/eea_desc.h | 131 +++
.../net/ethernet/alibaba/eea/eea_ethtool.c | 253 ++++++
.../net/ethernet/alibaba/eea/eea_ethtool.h | 49 +
drivers/net/ethernet/alibaba/eea/eea_net.c | 853 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 197 ++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 706 +++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_pci.h | 73 ++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 243 +++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 86 ++
drivers/net/ethernet/alibaba/eea/eea_rx.c | 810 +++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_tx.c | 455 ++++++++++
19 files changed, 4464 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 v39 2/8] eea: introduce ring and descriptor structures
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-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 | 131 +++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 243 ++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 86 +++++++
4 files changed, 462 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 cf2acf1733fd..e5e4007810a6 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_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..4ce1e57c906b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_desc.h
@@ -0,0 +1,131 @@
+/* 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 {
+ __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;
+ __le64 reserved2;
+ __le64 reserved3;
+};
+
+#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..b5003924264c
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.c
@@ -0,0 +1,243 @@
+// 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 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, (void __iomem *)ering->db);
+}
+
+void *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 *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 *ering_aq_alloc_desc(struct eea_ring *ering)
+{
+ struct eea_ring_sq *sq = &ering->sq;
+ struct eea_common_desc *desc;
+
+ 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 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 ering_sq_cancel(struct eea_ring *ering)
+{
+ ering->sq.shadow_num = 0;
+}
+
+/* cq api */
+void 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 */
+bool 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, (void __iomem *)ering->db);
+
+ return true;
+}
+
+/* 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 *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 (!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 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..8e4ba3032699
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.h
@@ -0,0 +1,86 @@
+/* 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)
+
+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 *ering_alloc(u32 index, u32 num, struct eea_device *edev,
+ u8 sq_desc_size, u8 cq_desc_size,
+ const char *name);
+void ering_free(struct eea_ring *ering);
+bool ering_kick(struct eea_ring *ering);
+
+void *ering_sq_alloc_desc(struct eea_ring *ering, u16 id,
+ bool is_last, u16 flags);
+void *ering_aq_alloc_desc(struct eea_ring *ering);
+void ering_sq_commit_desc(struct eea_ring *ering);
+void ering_sq_cancel(struct eea_ring *ering);
+
+void ering_cq_ack_desc(struct eea_ring *ering, u32 num);
+
+void ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering);
+void *ering_cq_get_desc(const struct eea_ring *ering);
+#endif
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v39 3/8] eea: probe the netdevice and create adminq
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-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 | 482 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 74 +++
drivers/net/ethernet/alibaba/eea/eea_net.c | 219 ++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 136 +++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 29 +-
drivers/net/ethernet/alibaba/eea/eea_pci.h | 3 +
7 files changed, 946 insertions(+), 3 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 e5e4007810a6..91f318e8e046 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,4 +1,6 @@
obj-$(CONFIG_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..c36714a932eb
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.c
@@ -0,0 +1,482 @@
+// 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_REJECT 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)
+
+/* Operations are fully serialized under the protection of a lock
+ * (e.g., rtlock)
+ *
+ * If the hardware fails to complete the command correctly, a device reset will
+ * be triggered.
+ */
+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)
+{
+ struct eea_aq_cdesc *cdesc;
+ struct eea_aq_desc *desc;
+ int ret;
+
+ desc = 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);
+
+ ering_sq_commit_desc(enet->adminq.ring);
+
+ 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(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);
+
+ ering_cq_ack_desc(enet->adminq.ring, 1);
+
+ if (ret)
+ netdev_err(enet->netdev,
+ "adminq exec failed. cmd: %d ret %d\n", cmd, ret);
+
+ return ret;
+}
+
+static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
+ void *req, u32 req_size, void *res, u32 res_size)
+{
+ dma_addr_t req_addr = 0, res_addr = 0;
+ struct device *dma;
+ int ret;
+
+ if (enet->adminq.broken)
+ return -EIO;
+
+ 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;
+ }
+ }
+
+ ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
+ req_size, res_size);
+ if (res)
+ dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
+
+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) {
+ 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 = 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;
+
+ 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;
+
+ err = eea_pci_set_aq_up(enet->edev);
+ if (err)
+ goto err;
+
+ aq->broken = false;
+
+ return 0;
+
+err:
+ /* For the adminq, we can safely free the ring before setting it up. */
+ 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));
+}
+
+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;
+
+ 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);
+ if (err)
+ return err;
+
+ 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);
+}
+
+/* The caller must ensure that both the 'rt' and 'tx' arrays are valid. */
+struct eea_aq_dev_status *eea_adminq_dev_status(struct eea_net *enet)
+{
+ 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 NULL;
+
+ size = struct_size(dev_status, q_status, q_num);
+
+ rep = kzalloc(size, GFP_KERNEL);
+ if (!rep) {
+ kfree(req);
+ return NULL;
+ }
+
+ 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);
+ kfree(req);
+ if (err) {
+ kfree(rep);
+ return NULL;
+ }
+
+ return rep;
+}
+
+int 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 rc;
+
+ 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_dev_id(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));
+
+ if (!rc) {
+ if (rep->op_code == EEA_HINFO_REP_REJECT) {
+ dev_err(dev, "Device has refused the initialization due to provided host information\n");
+ rc = -ENODEV;
+ }
+ if (rep->has_reply) {
+ rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
+ dev_warn(dev, "Device replied: %s\n",
+ rep->reply_str);
+ }
+ }
+
+ kfree(rep);
+err_free_cfg:
+ kfree(cfg);
+ return rc;
+}
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..816059193b3b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.h
@@ -0,0 +1,74 @@
+/* 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 {
+ struct eea_ring *ring;
+ u32 num;
+ bool broken;
+ u16 phase;
+
+ 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);
+struct eea_aq_dev_status *eea_adminq_dev_status(struct eea_net *enet);
+int 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..ff43b90a6a68
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -0,0 +1,219 @@
+// 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)
+#define EEA_NET_IO_RING_DEPTH_MAX (32 * 1024)
+#define EEA_NET_IO_RING_DEPTH_MIN 128
+
+static int eea_update_cfg(struct eea_net *enet,
+ struct eea_device *edev,
+ struct eea_aq_cfg *hwcfg)
+{
+ enet->cfg_hw.rx_ring_depth = le32_to_cpu(hwcfg->rx_depth_max);
+ enet->cfg_hw.tx_ring_depth = le32_to_cpu(hwcfg->tx_depth_max);
+
+ enet->cfg_hw.rx_ring_num = edev->rx_num;
+ enet->cfg_hw.tx_ring_num = edev->tx_num;
+
+ enet->cfg.rx_ring_depth = le32_to_cpu(hwcfg->rx_depth_def);
+ enet->cfg.tx_ring_depth = le32_to_cpu(hwcfg->tx_depth_def);
+
+ enet->cfg.rx_ring_num = edev->rx_num;
+ enet->cfg.tx_ring_num = edev->tx_num;
+
+ enet->cfg_hw.split_hdr = EEA_SPLIT_HDR_SIZE;
+
+ /* Now, we assert that the rx ring num is equal to the tx ring num. */
+ if (enet->cfg_hw.rx_ring_num != enet->cfg_hw.tx_ring_num) {
+ dev_err(edev->dma_dev, "The device gave us invalid rx,tx ring num. %d %d\n",
+ enet->cfg.rx_ring_num,
+ enet->cfg.tx_ring_num);
+ return -EINVAL;
+ }
+
+ if (enet->cfg_hw.rx_ring_depth > EEA_NET_IO_RING_DEPTH_MAX ||
+ enet->cfg_hw.rx_ring_depth < EEA_NET_IO_RING_DEPTH_MIN) {
+ dev_err(edev->dma_dev, "The device gave us an invalid rx depth. %d\n",
+ enet->cfg_hw.rx_ring_depth);
+ return -EINVAL;
+ }
+
+ if (enet->cfg_hw.tx_ring_depth > EEA_NET_IO_RING_DEPTH_MAX ||
+ enet->cfg_hw.tx_ring_depth < EEA_NET_IO_RING_DEPTH_MIN) {
+ dev_err(edev->dma_dev, "The device gave us an invalid tx depth. %d\n",
+ enet->cfg_hw.tx_ring_depth);
+ return -EINVAL;
+ }
+
+ 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;
+
+ err = eea_adminq_config_host_info(enet);
+ if (err)
+ goto err_reset_dev;
+
+ err = eea_netdev_init_features(enet->netdev, enet, edev);
+ if (err)
+ goto err_reset_dev;
+
+ netdev_dbg(enet->netdev, "eea probe success.\n");
+
+ 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);
+}
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..239312456c5b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -0,0 +1,136 @@
+/* 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);
+
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
index 0b165f14a162..7258271d3260 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
@@ -64,7 +65,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))
@@ -309,6 +312,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;
@@ -330,7 +352,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:
@@ -364,6 +388,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);
pci_disable_sriov(pci_dev);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
index be4e75b4ed2f..d0094c419f59 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_dev_id(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
* [PATCH net-next v39 5/8] eea: implement packet receive logic
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-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 | 528 ++++++++++++++++++++-
2 files changed, 531 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index ca35d28211fc..001113aad835 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;
};
@@ -166,6 +170,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 8019b01a4b24..7ca99e39a85a 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,425 @@ 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)
+{
+ 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)
{
- /* Empty function; will be implemented in a subsequent commit. */
+ 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);
+
+ skb_mark_for_recycle(head_skb);
+
+ 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)) {
+ netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
+ return -EINVAL;
+ }
+
+ ctx->meta = &rx->meta[id];
+ if (!ctx->meta->in_use) {
+ 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)) {
+ 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 = 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:
+ 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 bool 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);
+
+ addr = meta->dma + meta->offset + headroom;
+
+ desc = 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);
+ }
+
+ 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)
+ ering_kick(rx->ering);
+
+ /* true means busy, napi should be called again. */
+ return !!err;
+}
+
+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);
+
+ 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.
+ */
+ busy |= eea_rx_post(rx);
+
+ busy |= received >= budget;
+
+ if (busy)
+ return budget;
+
+ if (napi_complete_done(napi, received))
+ 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 v39 4/8] eea: create/destroy rx,tx queues for netdevice open and stop
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces the implementation for the netdevice open and
stop.
This commit introduces HA to restore the device when error occurs,
but in HA scenarios the driver can't ensure to restore the status
correctly.
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 | 4 +-
drivers/net/ethernet/alibaba/eea/eea_net.c | 570 ++++++++++++++++++++-
drivers/net/ethernet/alibaba/eea/eea_net.h | 48 +-
drivers/net/ethernet/alibaba/eea/eea_pci.c | 228 ++++++++-
drivers/net/ethernet/alibaba/eea/eea_pci.h | 20 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 264 ++++++++++
drivers/net/ethernet/alibaba/eea/eea_tx.c | 102 ++++
7 files changed, 1225 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_rx.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_tx.c
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index 91f318e8e046..fa34a005fa01 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -3,4 +3,6 @@ obj-$(CONFIG_EEA) += eea.o
eea-y := eea_ring.o \
eea_net.o \
eea_pci.o \
- eea_adminq.o
+ eea_adminq.o \
+ eea_tx.o \
+ eea_rx.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index ff43b90a6a68..58ce385826ea 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -20,6 +20,462 @@
#define EEA_NET_IO_RING_DEPTH_MAX (32 * 1024)
#define EEA_NET_IO_RING_DEPTH_MIN 128
+static irqreturn_t eea_irq_handler(int irq, void *data)
+{
+ struct eea_irq_blk *blk = data;
+
+ napi_schedule_irqoff(&blk->napi);
+
+ return IRQ_HANDLED;
+}
+
+static void eea_free_irq_blk(struct eea_net *enet)
+{
+ struct eea_irq_blk *blk;
+ u32 num;
+ int i;
+
+ if (!enet->irq_blks)
+ return;
+
+ num = enet->edev->rx_num;
+
+ for (i = 0; i < num; i++) {
+ blk = &enet->irq_blks[i];
+
+ if (blk->ready)
+ eea_pci_free_irq(blk);
+
+ blk->ready = false;
+ }
+
+ kvfree(enet->irq_blks);
+ enet->irq_blks = NULL;
+}
+
+/* The driver will always attempt to allocate IRQ blocks based on the maximum
+ * possible queue num.
+ */
+static int eea_alloc_irq_blks(struct eea_net *enet)
+{
+ struct eea_device *edev = enet->edev;
+ struct eea_irq_blk *blk, *irq_blks;
+ int i, err, num;
+
+ num = enet->edev->rx_num;
+
+ irq_blks = kvcalloc(num, sizeof(*blk), GFP_KERNEL);
+ if (!irq_blks)
+ return -ENOMEM;
+
+ enet->irq_blks = irq_blks;
+
+ for (i = 0; i < num; i++) {
+ blk = &irq_blks[i];
+ blk->idx = i;
+
+ /* vec 0 is for error notify. */
+ blk->msix_vec = i + 1;
+
+ err = eea_pci_request_irq(edev, blk, eea_irq_handler);
+ if (err)
+ goto err_free_irq_blk;
+
+ blk->ready = true;
+ }
+
+ return 0;
+
+err_free_irq_blk:
+ eea_free_irq_blk(enet);
+ return err;
+}
+
+static int eea_update_queues(struct eea_net *enet)
+{
+ return netif_set_real_num_queues(enet->netdev, enet->cfg.tx_ring_num,
+ enet->cfg.rx_ring_num);
+}
+
+void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->netdev = enet->netdev;
+ ctx->edev = enet->edev;
+ ctx->cfg = enet->cfg;
+}
+
+static void eea_bind_q_and_cfg(struct eea_net *enet,
+ struct eea_net_init_ctx *ctx)
+{
+ struct eea_irq_blk *blk;
+ struct eea_net_rx *rx;
+ struct eea_net_tx *tx;
+ int i;
+
+ enet->cfg = ctx->cfg;
+ enet->rx = ctx->rx;
+ enet->tx = ctx->tx;
+
+ for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
+ blk = &enet->irq_blks[i];
+
+ rx = ctx->rx[i];
+ tx = &ctx->tx[i];
+
+ rx->enet = enet;
+ rx->napi = &blk->napi;
+ rx->ering->msix_vec = blk->msix_vec;
+
+ tx->enet = enet;
+ tx->ering->msix_vec = blk->msix_vec;
+
+ blk->rx = rx;
+ }
+}
+
+static void eea_unbind_q_and_cfg(struct eea_net *enet,
+ struct eea_net_init_ctx *ctx)
+{
+ struct eea_irq_blk *blk;
+ struct eea_net_rx *rx;
+ int i;
+
+ ctx->cfg = enet->cfg;
+ ctx->rx = enet->rx;
+ ctx->tx = enet->tx;
+
+ enet->rx = NULL;
+ enet->tx = NULL;
+
+ for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
+ blk = &enet->irq_blks[i];
+
+ rx = ctx->rx[i];
+
+ rx->napi = NULL;
+
+ blk->rx = NULL;
+ }
+}
+
+static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
+{
+ struct eea_net_rx *rx;
+ struct eea_net_tx *tx;
+ int i;
+
+ for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
+ rx = ctx->rx[i];
+ tx = &ctx->tx[i];
+
+ eea_free_rx(rx, &ctx->cfg);
+ eea_free_tx(tx, &ctx->cfg);
+ }
+
+ kvfree(ctx->rx);
+ kvfree(ctx->tx);
+}
+
+/* alloc tx/rx: struct, ring, meta, pp, napi */
+static int eea_alloc_rxtx_q_mem(struct eea_net_init_ctx *ctx)
+{
+ struct eea_net_rx *rx;
+ struct eea_net_tx *tx;
+ int err, i;
+
+ ctx->tx = kvcalloc(ctx->cfg.tx_ring_num, sizeof(*ctx->tx), GFP_KERNEL);
+ if (!ctx->tx)
+ return -ENOMEM;
+
+ ctx->rx = kvcalloc(ctx->cfg.rx_ring_num, sizeof(*ctx->rx), GFP_KERNEL);
+ if (!ctx->rx)
+ goto err_free_tx;
+
+ ctx->cfg.rx_sq_desc_size = sizeof(struct eea_rx_desc);
+ ctx->cfg.rx_cq_desc_size = sizeof(struct eea_rx_cdesc);
+ ctx->cfg.tx_sq_desc_size = sizeof(struct eea_tx_desc);
+ ctx->cfg.tx_cq_desc_size = sizeof(struct eea_tx_cdesc);
+
+ ctx->cfg.tx_cq_desc_size /= 2;
+
+ if (!ctx->cfg.split_hdr)
+ ctx->cfg.rx_sq_desc_size /= 2;
+
+ for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
+ rx = eea_alloc_rx(ctx, i);
+ if (!rx)
+ goto err_free;
+
+ ctx->rx[i] = rx;
+
+ tx = ctx->tx + i;
+ err = eea_alloc_tx(ctx, tx, i);
+ if (err)
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
+ rx = ctx->rx[i];
+ tx = ctx->tx + i;
+
+ eea_free_rx(rx, &ctx->cfg);
+ eea_free_tx(tx, &ctx->cfg);
+ }
+
+ kvfree(ctx->rx);
+
+err_free_tx:
+ kvfree(ctx->tx);
+ return -ENOMEM;
+}
+
+static int eea_hw_active_ring(struct eea_net *enet)
+{
+ return eea_adminq_create_q(enet, enet->cfg.rx_ring_num
+ + enet->cfg.tx_ring_num, 0);
+}
+
+static int eea_hw_unactive_ring(struct eea_net *enet)
+{
+ int err;
+
+ err = eea_adminq_destroy_all_q(enet);
+ if (err)
+ netdev_warn(enet->netdev, "unactive rxtx ring failed.\n");
+
+ return err;
+}
+
+/* stop rx napi, stop tx queue. */
+static void eea_stop_rxtx(struct net_device *netdev)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ int i;
+
+ netif_tx_disable(netdev);
+
+ for (i = 0; i < enet->cfg.rx_ring_num; i++)
+ enet_rx_stop(enet->rx[i]);
+
+ netif_carrier_off(netdev);
+}
+
+static void eea_start_rxtx(struct eea_net *enet)
+{
+ int i;
+
+ for (i = 0; i < enet->cfg.rx_ring_num; i++)
+ enet_rx_start(enet->rx[i]);
+
+ netif_tx_start_all_queues(enet->netdev);
+ netif_carrier_on(enet->netdev);
+
+ enet->started = true;
+}
+
+static int eea_netdev_stop(struct net_device *netdev)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ struct eea_net_init_ctx ctx;
+
+ /* This function can be called during device anomaly recovery. To
+ * prevent duplicate stop operations, the `started` flag is introduced
+ * for checking.
+ */
+
+ if (!enet->started) {
+ netdev_warn(netdev, "eea netdev stop: but dev is not started.\n");
+ return 0;
+ }
+
+ eea_stop_rxtx(netdev);
+ eea_hw_unactive_ring(enet);
+ eea_unbind_q_and_cfg(enet, &ctx);
+ eea_free_rxtx_q_mem(&ctx);
+
+ enet->started = false;
+
+ return 0;
+}
+
+static int eea_netdev_open(struct net_device *netdev)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ struct eea_net_init_ctx ctx;
+ int err;
+
+ if (enet->link_err) {
+ netdev_err(netdev, "netdev open err, because link error: %d\n",
+ enet->link_err);
+ return -EBUSY;
+ }
+
+ eea_init_ctx(enet, &ctx);
+
+ err = eea_alloc_rxtx_q_mem(&ctx);
+ if (err)
+ goto err_done;
+
+ eea_bind_q_and_cfg(enet, &ctx);
+
+ err = eea_update_queues(enet);
+ if (err)
+ goto err_free_q;
+
+ err = eea_hw_active_ring(enet);
+ if (err)
+ goto err_free_q;
+
+ eea_start_rxtx(enet);
+
+ return 0;
+
+err_free_q:
+ eea_unbind_q_and_cfg(enet, &ctx);
+ eea_free_rxtx_q_mem(&ctx);
+
+err_done:
+ return err;
+}
+
+/* resources: ring, buffers, irq */
+int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
+{
+ struct eea_net_init_ctx ctx_old = {0};
+ int err, error;
+
+ if (!netif_running(enet->netdev) || !enet->started) {
+ enet->cfg = ctx->cfg;
+ return 0;
+ }
+
+ err = eea_alloc_rxtx_q_mem(ctx);
+ if (err) {
+ netdev_warn(enet->netdev,
+ "eea reset: alloc q failed. stop reset. err %d\n",
+ err);
+ return err;
+ }
+
+ eea_stop_rxtx(enet->netdev);
+ eea_hw_unactive_ring(enet);
+
+ eea_unbind_q_and_cfg(enet, &ctx_old);
+ eea_bind_q_and_cfg(enet, ctx);
+
+ err = eea_update_queues(enet);
+ if (err) {
+ netdev_err(enet->netdev,
+ "eea reset: set real num queues failed. err %d\n",
+ err);
+ goto err_bind_old;
+ }
+
+ err = eea_hw_active_ring(enet);
+ if (err) {
+ netdev_err(enet->netdev, "eea reset: active new ring. err %d\n",
+ err);
+ eea_unbind_q_and_cfg(enet, ctx);
+ goto err_free_q;
+ }
+
+ eea_start_rxtx(enet);
+ eea_free_rxtx_q_mem(&ctx_old);
+ return 0;
+
+err_bind_old:
+ eea_unbind_q_and_cfg(enet, ctx);
+ eea_bind_q_and_cfg(enet, &ctx_old);
+ error = eea_hw_active_ring(enet);
+ if (error) {
+ netdev_err(enet->netdev, "eea reset: active old ring. err %d\n",
+ error);
+ eea_unbind_q_and_cfg(enet, &ctx_old);
+ err = error;
+ goto err_free_q;
+ }
+
+ eea_start_rxtx(enet);
+ eea_free_rxtx_q_mem(ctx);
+ return err;
+
+err_free_q:
+
+ /* An exception occurred at the hardware level, and there's not much we
+ * can do about it -- we can only release the resources first.
+ */
+ eea_free_rxtx_q_mem(ctx);
+ eea_free_rxtx_q_mem(&ctx_old);
+ enet->started = false;
+ return err;
+}
+
+int eea_queues_check_and_reset(struct eea_device *edev)
+{
+ struct eea_aq_queue_status *qstatus;
+ struct eea_aq_dev_status *dstatus;
+ struct eea_aq_queue_status *qs;
+ struct eea_net_init_ctx ctx;
+ bool need_reset = false;
+ int num, i, err = 0;
+
+ rtnl_lock();
+
+ if (!netif_running(edev->enet->netdev))
+ goto err_unlock;
+
+ /* Maybe stopped by ha. */
+ if (!edev->enet->started || edev->enet->link_err)
+ goto err_unlock;
+
+ num = edev->enet->cfg.rx_ring_num + edev->enet->cfg.tx_ring_num + 1;
+
+ dstatus = eea_adminq_dev_status(edev->enet);
+ if (!dstatus) {
+ netdev_warn(edev->enet->netdev, "query queue status failed.\n");
+ err = -ENOMEM;
+ goto err_unlock;
+ }
+
+ if (le16_to_cpu(dstatus->link_status) == EEA_LINK_DOWN_STATUS) {
+ /* The device is broken, can not be up. */
+ eea_netdev_stop(edev->enet->netdev);
+ edev->enet->link_err = EEA_LINK_ERR_LINK_DOWN;
+ netdev_warn(edev->enet->netdev, "device link is down. stop device.\n");
+ goto err_free;
+ }
+
+ qstatus = dstatus->q_status;
+
+ for (i = 0; i < num; ++i) {
+ qs = &qstatus[i];
+
+ if (le16_to_cpu(qs->status) == EEA_QUEUE_STATUS_NEED_RESET) {
+ netdev_warn(edev->enet->netdev,
+ "queue status: queue %u needs to reset\n",
+ le16_to_cpu(qs->qidx));
+ need_reset = true;
+ }
+ }
+
+ if (need_reset) {
+ eea_init_ctx(edev->enet, &ctx);
+ err = eea_reset_hw_resources(edev->enet, &ctx);
+ }
+
+err_free:
+ kfree(dstatus);
+
+err_unlock:
+ rtnl_unlock();
+ return err;
+}
+
static int eea_update_cfg(struct eea_net *enet,
struct eea_device *edev,
struct eea_aq_cfg *hwcfg)
@@ -140,6 +596,9 @@ static int eea_netdev_init_features(struct net_device *netdev,
}
static const struct net_device_ops eea_netdev = {
+ .ndo_open = eea_netdev_open,
+ .ndo_stop = eea_netdev_stop,
+ .ndo_start_xmit = eea_tx_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_features_check = passthru_features_check,
};
@@ -148,6 +607,7 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
{
struct net_device *netdev;
struct eea_net *enet;
+ int err;
netdev = alloc_etherdev_mq(sizeof(struct eea_net), pairs);
if (!netdev) {
@@ -164,14 +624,83 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
enet->edev = edev;
edev->enet = enet;
+ err = eea_alloc_irq_blks(enet);
+ if (err) {
+ dev_err(edev->dma_dev,
+ "eea_alloc_irq_blks failed with pairs %d\n", pairs);
+ free_netdev(netdev);
+ return NULL;
+ }
+
return enet;
}
+static void eea_update_ts_off(struct eea_device *edev, struct eea_net *enet)
+{
+ u64 ts;
+
+ ts = eea_pci_device_ts(edev);
+
+ enet->hw_ts_offset = ktime_get_real() - ts;
+}
+
+static int eea_net_reprobe(struct eea_device *edev)
+{
+ struct eea_net *enet = edev->enet;
+ int err = 0;
+
+ enet->edev = edev;
+
+ if (!enet->adminq.ring) {
+ err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
+ if (err)
+ return err;
+ }
+
+ err = eea_alloc_irq_blks(enet);
+ if (err)
+ goto err_destroy_aq;
+
+ eea_update_ts_off(edev, enet);
+
+ rtnl_lock();
+
+ enet->link_err = 0;
+ if (edev->ha_reset_netdev_running &&
+ netif_running(edev->enet->netdev)) {
+ err = eea_netdev_open(enet->netdev);
+ if (err) {
+ enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
+ rtnl_unlock();
+ goto err_free_irq_blks;
+ }
+ }
+
+ rtnl_unlock();
+
+ enet->wait_pci_ready = false;
+ return 0;
+
+err_free_irq_blks:
+ eea_free_irq_blk(enet);
+
+err_destroy_aq:
+ eea_destroy_adminq(enet);
+
+ return err;
+}
+
int eea_net_probe(struct eea_device *edev)
{
struct eea_net *enet;
int err = -ENOMEM;
+ /* If edev->enet is not null, then this is called from ha reset worker.
+ * Call eea_net_reprobe() directly.
+ */
+ if (edev->enet)
+ return eea_net_reprobe(edev);
+
enet = eea_netdev_alloc(edev, edev->rx_num);
if (!enet)
return -ENOMEM;
@@ -188,6 +717,8 @@ int eea_net_probe(struct eea_device *edev)
if (err)
goto err_reset_dev;
+ eea_update_ts_off(edev, enet);
+
netdev_dbg(enet->netdev, "eea probe success.\n");
return 0;
@@ -197,11 +728,34 @@ int eea_net_probe(struct eea_device *edev)
eea_destroy_adminq(enet);
err_free_netdev:
+ eea_free_irq_blk(enet);
free_netdev(enet->netdev);
return err;
}
-void eea_net_remove(struct eea_device *edev)
+static void eea_net_ha_reset_remove(struct eea_net *enet,
+ struct eea_device *edev)
+{
+ rtnl_lock();
+ edev->ha_reset_netdev_running = false;
+ if (netif_running(enet->netdev)) {
+ eea_netdev_stop(enet->netdev);
+ edev->ha_reset_netdev_running = true;
+ }
+
+ /* Prevent that the user set up the net device. */
+ enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
+
+ rtnl_unlock();
+
+ eea_device_reset(edev);
+ eea_destroy_adminq(enet);
+ eea_free_irq_blk(enet);
+
+ enet->wait_pci_ready = true;
+}
+
+void eea_net_remove(struct eea_device *edev, bool ha)
{
struct net_device *netdev;
struct eea_net *enet;
@@ -209,11 +763,19 @@ void eea_net_remove(struct eea_device *edev)
enet = edev->enet;
netdev = enet->netdev;
- netdev_dbg(enet->netdev, "eea removed.\n");
+ if (ha) {
+ if (enet->wait_pci_ready)
+ return;
- eea_device_reset(edev);
+ eea_net_ha_reset_remove(enet, edev);
+ return;
+ }
- eea_destroy_adminq(enet);
+ if (!enet->wait_pci_ready) {
+ eea_device_reset(edev);
+ eea_destroy_adminq(enet);
+ eea_free_irq_blk(enet);
+ }
free_netdev(netdev);
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 239312456c5b..ca35d28211fc 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -18,6 +18,8 @@
#define EEA_VER_MINOR 0
#define EEA_VER_SUB_MINOR 0
+struct eea_tx_meta;
+
struct eea_net_tx {
struct eea_net *enet;
@@ -100,6 +102,18 @@ struct eea_net_cfg {
u8 tx_cq_desc_size;
u32 split_hdr;
+
+ struct hwtstamp_config ts_cfg;
+};
+
+struct eea_net_init_ctx {
+ struct eea_net_cfg cfg;
+
+ struct eea_net_tx *tx;
+ struct eea_net_rx **rx;
+
+ struct net_device *netdev;
+ struct eea_device *edev;
};
enum {
@@ -108,6 +122,17 @@ enum {
EEA_LINK_ERR_LINK_DOWN,
};
+struct eea_irq_blk {
+ struct napi_struct napi;
+ u16 msix_vec;
+ bool ready;
+ struct eea_net_rx *rx;
+ char irq_name[32];
+ int irq;
+ int idx;
+
+};
+
struct eea_net {
struct eea_device *edev;
struct net_device *netdev;
@@ -120,9 +145,12 @@ struct eea_net {
struct eea_net_cfg cfg;
struct eea_net_cfg cfg_hw;
+ struct eea_irq_blk *irq_blks;
+
u32 link_err;
bool started;
+ bool wait_pci_ready;
u8 duplex;
u32 speed;
@@ -131,6 +159,24 @@ struct eea_net {
};
int eea_net_probe(struct eea_device *edev);
-void eea_net_remove(struct eea_device *edev);
+void eea_net_remove(struct eea_device *edev, bool ha);
+
+int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx);
+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);
+
+void eea_free_rx(struct eea_net_rx *rx, struct eea_net_cfg *cfg);
+struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx);
+
+/* tx apis */
+bool eea_poll_tx(struct eea_net_tx *tx, int budget);
+netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev);
+
+void eea_free_tx(struct eea_net_tx *tx, struct eea_net_cfg *cfg);
+int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx);
#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
index 7258271d3260..b213c1d6d82a 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -16,6 +16,9 @@
#define EEA_PCI_DB_MAX_SIZE 512
#define EEA_PCI_Q_MAX_NUM 1000
+#define EEA_PCI_CAP_RESET_DEVICE 0xFA
+#define EEA_PCI_CAP_RESET_FLAG BIT(1)
+
struct eea_pci_cfg {
__le32 reserve0;
__le32 reserve1;
@@ -57,8 +60,10 @@ struct eea_pci_device {
void __iomem *db_base;
void __iomem *db_end;
+ struct work_struct ha_handle_work;
char ha_irq_name[32];
u8 reset_pos;
+ bool ha_ready;
};
#define cfg_pointer(reg, item) \
@@ -73,6 +78,11 @@ struct eea_pci_device {
#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
#define cfg_readq(reg, item) readq(cfg_pointer(reg, item))
+/* Due to circular references, we have to add function definitions here. */
+static int __eea_pci_probe(struct pci_dev *pci_dev,
+ struct eea_pci_device *ep_dev, bool pci_probe);
+static void __eea_pci_remove(struct pci_dev *pci_dev, bool pci_remove);
+
const char *eea_pci_name(struct eea_device *edev)
{
return pci_name(edev->ep_dev->pci_dev);
@@ -179,6 +189,12 @@ static int eea_negotiate(struct eea_device *edev)
static void eea_pci_release_resource(struct eea_pci_device *ep_dev)
{
struct pci_dev *pci_dev = ep_dev->pci_dev;
+ struct eea_device *edev;
+
+ edev = &ep_dev->edev;
+
+ if (edev->status < EEA_PCI_STATUS_READY)
+ return;
if (ep_dev->reg) {
pci_iounmap(pci_dev, ep_dev->reg);
@@ -192,12 +208,16 @@ static void eea_pci_release_resource(struct eea_pci_device *ep_dev)
pci_release_regions(pci_dev);
pci_disable_device(pci_dev);
+
+ edev->status = EEA_PCI_STATUS_NONE;
}
static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
{
int err, n, ret, len;
+ ep_dev->edev.status = EEA_PCI_STATUS_ERR;
+
ep_dev->pci_dev = pci_dev;
err = pci_enable_device(pci_dev);
@@ -282,6 +302,8 @@ static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
ep_dev->msix_vec_n = ret;
+ ep_dev->edev.status = EEA_PCI_STATUS_READY;
+
return 0;
err_unmap_reg:
@@ -331,6 +353,174 @@ int eea_pci_active_aq(struct eea_ring *ering, int msix_vec)
return 0;
}
+void eea_pci_free_irq(struct eea_irq_blk *blk)
+{
+ irq_update_affinity_hint(blk->irq, NULL);
+ free_irq(blk->irq, blk);
+}
+
+int eea_pci_request_irq(struct eea_device *edev, struct eea_irq_blk *blk,
+ irqreturn_t (*callback)(int irq, void *data))
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ int irq;
+
+ snprintf(blk->irq_name, sizeof(blk->irq_name), "eea-q%d@%s", blk->idx,
+ pci_name(ep_dev->pci_dev));
+
+ irq = pci_irq_vector(ep_dev->pci_dev, blk->msix_vec);
+
+ blk->irq = irq;
+
+ return request_irq(irq, callback, IRQF_NO_AUTOEN, blk->irq_name, blk);
+}
+
+static void eea_ha_handle_reset(struct eea_pci_device *ep_dev)
+{
+ struct eea_device *edev;
+ struct pci_dev *pci_dev;
+ u16 reset;
+ int err;
+
+ if (!ep_dev->reset_pos) {
+ eea_queues_check_and_reset(&ep_dev->edev);
+ return;
+ }
+
+ edev = &ep_dev->edev;
+
+ pci_read_config_word(ep_dev->pci_dev, ep_dev->reset_pos, &reset);
+
+ /* Clear bits using 0xFFFF and ignore all previous messages. */
+ pci_write_config_word(ep_dev->pci_dev, ep_dev->reset_pos, 0xFFFF);
+
+ if (reset & EEA_PCI_CAP_RESET_FLAG) {
+ dev_warn(&ep_dev->pci_dev->dev, "recv device reset request.\n");
+
+ pci_dev = ep_dev->pci_dev;
+
+ /* The pci remove callback may hold this lock. If the
+ * pci remove callback is called, then we can ignore the
+ * ha interrupt.
+ */
+ if (mutex_trylock(&edev->ha_lock)) {
+ if (edev->status != EEA_PCI_STATUS_DONE) {
+ dev_err(&ep_dev->pci_dev->dev, "ha: reset device: pci status is %d. skip it.\n",
+ edev->status);
+
+ mutex_unlock(&edev->ha_lock);
+ return;
+ }
+
+ __eea_pci_remove(pci_dev, false);
+ err = __eea_pci_probe(pci_dev, ep_dev, false);
+ if (err)
+ /* Currently, for some reason, PCI
+ * initialization or network device re-probing
+ * has failed. Waiting for the PCI subsystem to
+ * call the remove callback to release the
+ * remaining resources.
+ */
+ dev_err(&ep_dev->pci_dev->dev,
+ "ha: re-setup failed.\n");
+
+ mutex_unlock(&edev->ha_lock);
+ } else {
+ /* Device removal is in progress, so return directly. */
+ dev_warn(&ep_dev->pci_dev->dev,
+ "ha device reset: trylock failed.\n");
+ }
+ return;
+ }
+
+ eea_queues_check_and_reset(&ep_dev->edev);
+}
+
+/* ha handle code */
+static void eea_ha_handle_work(struct work_struct *work)
+{
+ struct eea_pci_device *ep_dev;
+
+ ep_dev = container_of(work, struct eea_pci_device, ha_handle_work);
+
+ /* Ha interrupt is triggered, so there maybe some error, we may need to
+ * reset the device or reset some queues.
+ */
+ dev_warn(&ep_dev->pci_dev->dev, "recv ha interrupt.\n");
+
+ eea_ha_handle_reset(ep_dev);
+}
+
+static irqreturn_t eea_pci_ha_handle(int irq, void *data)
+{
+ struct eea_device *edev = data;
+
+ schedule_work(&edev->ep_dev->ha_handle_work);
+
+ return IRQ_HANDLED;
+}
+
+static void eea_pci_free_ha_irq(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ int irq;
+
+ if (ep_dev->ha_ready) {
+ irq = pci_irq_vector(ep_dev->pci_dev, 0);
+ free_irq(irq, edev);
+ ep_dev->ha_ready = false;
+ }
+}
+
+static int eea_pci_ha_init(struct eea_device *edev, struct pci_dev *pci_dev,
+ bool pci_probe)
+{
+ u8 pos, cfg_type_off, type, cfg_drv_off, cfg_dev_off;
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ int irq, err;
+
+ snprintf(ep_dev->ha_irq_name, sizeof(ep_dev->ha_irq_name), "eea-ha@%s",
+ pci_name(ep_dev->pci_dev));
+
+ irq = pci_irq_vector(ep_dev->pci_dev, 0);
+
+ if (pci_probe)
+ INIT_WORK(&ep_dev->ha_handle_work, eea_ha_handle_work);
+
+ /* This irq is not only work for ha, so request it always. */
+ err = request_irq(irq, eea_pci_ha_handle, 0, ep_dev->ha_irq_name, edev);
+ if (err)
+ return err;
+
+ ep_dev->ha_ready = true;
+ ep_dev->reset_pos = 0;
+
+ cfg_type_off = offsetof(struct eea_pci_cap, cfg_type);
+ cfg_drv_off = offsetof(struct eea_pci_reset_reg, driver);
+ cfg_dev_off = offsetof(struct eea_pci_reset_reg, device);
+
+ for (pos = pci_find_capability(pci_dev, PCI_CAP_ID_VNDR);
+ pos > 0;
+ pos = pci_find_next_capability(pci_dev, pos, PCI_CAP_ID_VNDR)) {
+ pci_read_config_byte(pci_dev, pos + cfg_type_off, &type);
+
+ if (type == EEA_PCI_CAP_RESET_DEVICE) {
+ /* notify device, driver support this feature. */
+ pci_write_config_word(pci_dev, pos + cfg_drv_off,
+ EEA_PCI_CAP_RESET_FLAG);
+ pci_write_config_word(pci_dev, pos + cfg_dev_off,
+ 0xFFFF);
+
+ edev->ep_dev->reset_pos = pos + cfg_dev_off;
+ return 0;
+ }
+ }
+
+ /* irq just for event notify */
+ dev_warn(&edev->ep_dev->pci_dev->dev, "Not Found reset cap.\n");
+ return 0;
+}
+
u64 eea_pci_device_ts(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
@@ -363,12 +553,16 @@ static int eea_init_device(struct eea_device *edev)
}
static int __eea_pci_probe(struct pci_dev *pci_dev,
- struct eea_pci_device *ep_dev)
+ struct eea_pci_device *ep_dev,
+ bool pci_probe)
{
+ struct eea_device *edev;
int err;
pci_set_drvdata(pci_dev, ep_dev);
+ edev = &ep_dev->edev;
+
err = eea_pci_setup(pci_dev, ep_dev);
if (err)
return err;
@@ -377,20 +571,34 @@ static int __eea_pci_probe(struct pci_dev *pci_dev,
if (err)
goto err_pci_rel;
+ err = eea_pci_ha_init(edev, pci_dev, pci_probe);
+ if (err)
+ goto err_net_rm;
+
+ edev->status = EEA_PCI_STATUS_DONE;
+
return 0;
+err_net_rm:
+ eea_net_remove(edev, !pci_probe);
+
err_pci_rel:
eea_pci_release_resource(ep_dev);
return err;
}
-static void __eea_pci_remove(struct pci_dev *pci_dev)
+static void __eea_pci_remove(struct pci_dev *pci_dev, bool pci_remove)
{
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_free_ha_irq(edev);
+
+ if (pci_remove)
+ flush_work(&ep_dev->ha_handle_work);
+
+ eea_net_remove(edev, !pci_remove);
pci_disable_sriov(pci_dev);
@@ -417,8 +625,11 @@ static int eea_pci_probe(struct pci_dev *pci_dev,
ep_dev->pci_dev = pci_dev;
- err = __eea_pci_probe(pci_dev, ep_dev);
+ mutex_init(&edev->ha_lock);
+
+ err = __eea_pci_probe(pci_dev, ep_dev, true);
if (err) {
+ mutex_destroy(&edev->ha_lock);
pci_set_drvdata(pci_dev, NULL);
kfree(ep_dev);
}
@@ -429,10 +640,17 @@ static int eea_pci_probe(struct pci_dev *pci_dev,
static void eea_pci_remove(struct pci_dev *pci_dev)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+ struct eea_device *edev;
- __eea_pci_remove(pci_dev);
+ edev = &ep_dev->edev;
+
+ mutex_lock(&edev->ha_lock);
+ __eea_pci_remove(pci_dev, true);
+ mutex_unlock(&edev->ha_lock);
pci_set_drvdata(pci_dev, NULL);
+
+ mutex_destroy(&edev->ha_lock);
kfree(ep_dev);
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
index d0094c419f59..575d0f89169d 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -10,8 +10,18 @@
#include <linux/pci.h>
+#include "eea_net.h"
#include "eea_ring.h"
+enum eea_pci_status {
+ EEA_PCI_STATUS_NONE,
+ EEA_PCI_STATUS_ERR,
+ EEA_PCI_STATUS_READY,
+ EEA_PCI_STATUS_DONE,
+};
+
+struct eea_irq_blk;
+
struct eea_pci_cap {
__u8 cap_vndr;
__u8 cap_next;
@@ -34,6 +44,12 @@ struct eea_device {
u64 features;
+ enum eea_pci_status status;
+ bool ha_reset_netdev_running;
+
+ /* ha lock for the race between ha work and pci remove */
+ struct mutex ha_lock;
+
u32 rx_num;
u32 tx_num;
u32 db_blk_size;
@@ -47,6 +63,10 @@ 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);
+int eea_pci_request_irq(struct eea_device *edev, struct eea_irq_blk *blk,
+ irqreturn_t (*callback)(int irq, void *data));
+void eea_pci_free_irq(struct eea_irq_blk *blk);
+
u64 eea_pci_device_ts(struct eea_device *edev);
void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
new file mode 100644
index 000000000000..8019b01a4b24
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <net/netdev_rx_queue.h>
+#include <net/page_pool/helpers.h>
+
+#include "eea_adminq.h"
+#include "eea_net.h"
+#include "eea_pci.h"
+#include "eea_ring.h"
+
+#define EEA_ENABLE_F_NAPI BIT(0)
+
+#define EEA_PAGE_FRAGS_NUM 1024
+
+static void eea_free_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta,
+ bool allow_direct)
+{
+ u32 drain_count;
+
+ drain_count = EEA_PAGE_FRAGS_NUM - meta->frags;
+
+ if (page_pool_unref_page(meta->page, drain_count) == 0)
+ page_pool_put_unrefed_page(rx->pp, meta->page, -1,
+ allow_direct);
+
+ meta->page = NULL;
+}
+
+static void eea_free_rx_hdr(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
+{
+ struct eea_rx_meta *meta;
+ int i;
+
+ for (i = 0; i < cfg->rx_ring_depth; ++i) {
+ meta = &rx->meta[i];
+ meta->hdr_addr = NULL;
+
+ if (!meta->hdr_page)
+ continue;
+
+ dma_unmap_page(rx->dma_dev, meta->hdr_dma, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ put_page(meta->hdr_page);
+
+ meta->hdr_page = NULL;
+ }
+}
+
+static int eea_alloc_rx_hdr(struct eea_net_init_ctx *ctx, struct eea_net_rx *rx)
+{
+ struct page *hdr_page = NULL;
+ struct eea_rx_meta *meta;
+ u32 offset = 0, hdrsize;
+ struct device *dmadev;
+ dma_addr_t dma;
+ int i;
+
+ dmadev = ctx->edev->dma_dev;
+ hdrsize = ctx->cfg.split_hdr;
+
+ for (i = 0; i < ctx->cfg.rx_ring_depth; ++i) {
+ meta = &rx->meta[i];
+ meta->hdr_page = NULL;
+
+ if (!hdr_page || offset + hdrsize > PAGE_SIZE) {
+ hdr_page = dev_alloc_page();
+ if (!hdr_page)
+ goto err;
+
+ dma = dma_map_page(dmadev, hdr_page, 0, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+
+ if (unlikely(dma_mapping_error(dmadev, dma))) {
+ put_page(hdr_page);
+ goto err;
+ }
+
+ offset = 0;
+ meta->hdr_page = hdr_page;
+ }
+
+ meta->hdr_dma = dma + offset;
+ meta->hdr_addr = page_address(hdr_page) + offset;
+ offset += hdrsize;
+ }
+
+ return 0;
+
+err:
+ eea_free_rx_hdr(rx, &ctx->cfg);
+ return -ENOMEM;
+}
+
+static int eea_poll(struct napi_struct *napi, int budget)
+{
+ /* Empty function; will be implemented in a subsequent commit. */
+ return 0;
+}
+
+static void eea_free_rx_buffers(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
+{
+ struct eea_rx_meta *meta;
+ u32 i;
+
+ for (i = 0; i < cfg->rx_ring_depth; ++i) {
+ meta = &rx->meta[i];
+ if (!meta->page)
+ continue;
+
+ eea_free_rx_buffer(rx, meta, false);
+ }
+}
+
+static struct page_pool *eea_create_pp(struct eea_net_init_ctx *ctx, u32 idx)
+{
+ struct page_pool_params pp_params = {0};
+
+ pp_params.order = 0;
+ pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+ pp_params.pool_size = ctx->cfg.rx_ring_depth;
+ pp_params.nid = dev_to_node(ctx->edev->dma_dev);
+ pp_params.dev = ctx->edev->dma_dev;
+ pp_params.netdev = ctx->netdev;
+ pp_params.dma_dir = DMA_FROM_DEVICE;
+ pp_params.max_len = PAGE_SIZE;
+ pp_params.queue_idx = idx;
+
+ return page_pool_create(&pp_params);
+}
+
+static void eea_destroy_page_pool(struct eea_net_rx *rx)
+{
+ if (rx->pp)
+ page_pool_destroy(rx->pp);
+}
+
+void enet_rx_stop(struct eea_net_rx *rx)
+{
+ if (rx->flags & EEA_ENABLE_F_NAPI) {
+ rx->flags &= ~EEA_ENABLE_F_NAPI;
+
+ disable_irq(rx->enet->irq_blks[rx->index].irq);
+ napi_disable(rx->napi);
+
+ page_pool_disable_direct_recycling(rx->pp);
+ netif_napi_del(rx->napi);
+ }
+}
+
+void enet_rx_start(struct eea_net_rx *rx)
+{
+ netif_napi_add(rx->enet->netdev, rx->napi, eea_poll);
+
+ page_pool_enable_direct_recycling(rx->pp, rx->napi);
+
+ napi_enable(rx->napi);
+
+ rx->flags |= EEA_ENABLE_F_NAPI;
+
+ local_bh_disable();
+ napi_schedule(rx->napi);
+ local_bh_enable();
+
+ enable_irq(rx->enet->irq_blks[rx->index].irq);
+}
+
+/* Maybe called before eea_bind_q_and_cfg. So the cfg must be passed. */
+void eea_free_rx(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
+{
+ if (!rx)
+ return;
+
+ if (rx->ering) {
+ ering_free(rx->ering);
+ rx->ering = NULL;
+ }
+
+ if (rx->meta) {
+ eea_free_rx_buffers(rx, cfg);
+ eea_free_rx_hdr(rx, cfg);
+ kvfree(rx->meta);
+ rx->meta = NULL;
+ }
+
+ if (rx->pp) {
+ eea_destroy_page_pool(rx);
+ rx->pp = NULL;
+ }
+
+ kfree(rx);
+}
+
+static void eea_rx_meta_init(struct eea_net_rx *rx, u32 num)
+{
+ struct eea_rx_meta *meta;
+ int i;
+
+ rx->free = NULL;
+
+ for (i = 0; i < num; ++i) {
+ meta = &rx->meta[i];
+ meta->id = i;
+ meta->next = rx->free;
+ rx->free = meta;
+ }
+}
+
+struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx)
+{
+ struct eea_ring *ering;
+ struct eea_net_rx *rx;
+ int err;
+
+ rx = kzalloc(sizeof(*rx), GFP_KERNEL);
+ if (!rx)
+ return rx;
+
+ rx->index = idx;
+ snprintf(rx->name, sizeof(rx->name), "rx.%u", idx);
+
+ /* ering */
+ ering = ering_alloc(idx * 2, ctx->cfg.rx_ring_depth, ctx->edev,
+ ctx->cfg.rx_sq_desc_size,
+ ctx->cfg.rx_cq_desc_size,
+ rx->name);
+ if (!ering)
+ goto err_free_rx;
+
+ rx->ering = ering;
+
+ rx->dma_dev = ctx->edev->dma_dev;
+
+ /* meta */
+ rx->meta = kvcalloc(ctx->cfg.rx_ring_depth,
+ sizeof(*rx->meta), GFP_KERNEL);
+ if (!rx->meta)
+ goto err_free_rx;
+
+ eea_rx_meta_init(rx, ctx->cfg.rx_ring_depth);
+
+ if (ctx->cfg.split_hdr) {
+ err = eea_alloc_rx_hdr(ctx, rx);
+ if (err)
+ goto err_free_rx;
+ }
+
+ rx->pp = eea_create_pp(ctx, idx);
+ if (IS_ERR(rx->pp)) {
+ err = PTR_ERR(rx->pp);
+ rx->pp = NULL;
+ goto err_free_rx;
+ }
+
+ return rx;
+
+err_free_rx:
+ eea_free_rx(rx, &ctx->cfg);
+ return NULL;
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
new file mode 100644
index 000000000000..e01168d4e437
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <net/netdev_queues.h>
+
+#include "eea_net.h"
+#include "eea_pci.h"
+#include "eea_ring.h"
+
+struct eea_tx_meta {
+ struct eea_tx_meta *next;
+
+ u32 id;
+
+ union {
+ struct sk_buff *skb;
+ void *data;
+ };
+
+ u32 num;
+
+ dma_addr_t dma_addr;
+ struct eea_tx_desc *desc;
+ u32 dma_len;
+};
+
+bool eea_poll_tx(struct eea_net_tx *tx, int budget)
+{
+ /* Empty function; will be implemented in a subsequent commit. */
+ return true;
+}
+
+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);
+ return NETDEV_TX_OK;
+}
+
+static void eea_free_meta(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
+{
+ kvfree(tx->meta);
+ tx->meta = NULL;
+}
+
+/* Maybe called before eea_bind_q_and_cfg. So the cfg must be passed. */
+void eea_free_tx(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
+{
+ if (!tx)
+ return;
+
+ if (tx->ering) {
+ ering_free(tx->ering);
+ tx->ering = NULL;
+ }
+
+ if (tx->meta)
+ eea_free_meta(tx, cfg);
+}
+
+int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx)
+{
+ struct eea_tx_meta *meta;
+ struct eea_ring *ering;
+ u32 i;
+
+ snprintf(tx->name, sizeof(tx->name), "tx.%u", idx);
+
+ ering = ering_alloc(idx * 2 + 1, ctx->cfg.tx_ring_depth, ctx->edev,
+ ctx->cfg.tx_sq_desc_size,
+ ctx->cfg.tx_cq_desc_size,
+ tx->name);
+ if (!ering)
+ goto err_free_tx;
+
+ tx->ering = ering;
+ tx->index = idx;
+ tx->dma_dev = ctx->edev->dma_dev;
+
+ /* meta */
+ tx->meta = kvcalloc(ctx->cfg.tx_ring_depth,
+ sizeof(*tx->meta), GFP_KERNEL);
+ if (!tx->meta)
+ goto err_free_tx;
+
+ for (i = 0; i < ctx->cfg.tx_ring_depth; ++i) {
+ meta = &tx->meta[i];
+ meta->id = i;
+ meta->next = tx->free;
+ tx->free = meta;
+ }
+
+ return 0;
+
+err_free_tx:
+ eea_free_tx(tx, &ctx->cfg);
+ return -ENOMEM;
+}
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v39 8/8] eea: introduce callback for ndo_get_stats64
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-1-xuanzhuo@linux.alibaba.com>
This commit introduces ndo_get_stats64 support.
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.c | 63 ++++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 5 ++
2 files changed, 68 insertions(+)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index c70c0be7b1ea..8335131b8753 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -114,6 +114,11 @@ static void eea_bind_q_and_cfg(struct eea_net *enet,
struct eea_net_tx *tx;
int i;
+ /* Since 'ndo_get_stats64' is not called in softirq context, there is no
+ * need to use 'spin_lock_bh'.
+ */
+ spin_lock(&enet->stats_lock);
+
enet->cfg = ctx->cfg;
enet->rx = ctx->rx;
enet->tx = ctx->tx;
@@ -133,6 +138,8 @@ static void eea_bind_q_and_cfg(struct eea_net *enet,
blk->rx = rx;
}
+
+ spin_unlock(&enet->stats_lock);
}
static void eea_unbind_q_and_cfg(struct eea_net *enet,
@@ -142,6 +149,8 @@ static void eea_unbind_q_and_cfg(struct eea_net *enet,
struct eea_net_rx *rx;
int i;
+ spin_lock(&enet->stats_lock);
+
ctx->cfg = enet->cfg;
ctx->rx = enet->rx;
ctx->tx = enet->tx;
@@ -158,6 +167,8 @@ static void eea_unbind_q_and_cfg(struct eea_net *enet,
blk->rx = NULL;
}
+
+ spin_unlock(&enet->stats_lock);
}
static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
@@ -343,6 +354,53 @@ static int eea_netdev_open(struct net_device *netdev)
return err;
}
+/* Statistics may be reset to zero upon device reset. This is expected behavior
+ * for now and will be addressed in the future.
+ */
+static void eea_stats(struct net_device *netdev, struct rtnl_link_stats64 *tot)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ u64 packets, bytes;
+ u32 start;
+ int i;
+
+ spin_lock(&enet->stats_lock);
+
+ if (enet->rx) {
+ for (i = 0; i < enet->cfg.rx_ring_num; i++) {
+ struct eea_net_rx *rx = enet->rx[i];
+
+ do {
+ start = u64_stats_fetch_begin(&rx->stats.syncp);
+ packets = u64_stats_read(&rx->stats.packets);
+ bytes = u64_stats_read(&rx->stats.bytes);
+ } while (u64_stats_fetch_retry(&rx->stats.syncp,
+ start));
+
+ tot->rx_packets += packets;
+ tot->rx_bytes += bytes;
+ }
+ }
+
+ if (enet->tx) {
+ for (i = 0; i < enet->cfg.tx_ring_num; i++) {
+ struct eea_net_tx *tx = &enet->tx[i];
+
+ do {
+ start = u64_stats_fetch_begin(&tx->stats.syncp);
+ packets = u64_stats_read(&tx->stats.packets);
+ bytes = u64_stats_read(&tx->stats.bytes);
+ } while (u64_stats_fetch_retry(&tx->stats.syncp,
+ start));
+
+ tot->tx_packets += packets;
+ tot->tx_bytes += bytes;
+ }
+ }
+
+ spin_unlock(&enet->stats_lock);
+}
+
/* resources: ring, buffers, irq */
int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
{
@@ -350,7 +408,9 @@ int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
int err, error;
if (!netif_running(enet->netdev) || !enet->started) {
+ spin_lock(&enet->stats_lock);
enet->cfg = ctx->cfg;
+ spin_unlock(&enet->stats_lock);
return 0;
}
@@ -600,6 +660,7 @@ static const struct net_device_ops eea_netdev = {
.ndo_stop = eea_netdev_stop,
.ndo_start_xmit = eea_tx_xmit,
.ndo_validate_addr = eth_validate_addr,
+ .ndo_get_stats64 = eea_stats,
.ndo_features_check = passthru_features_check,
};
@@ -633,6 +694,8 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
return NULL;
}
+ spin_lock_init(&enet->stats_lock);
+
return enet;
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 5124d112a046..6aeaad0193da 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -165,6 +165,11 @@ struct eea_net {
u32 speed;
u64 hw_ts_offset;
+
+ /* Protect the tx and rx of struct eea_net, when eea_stats accesses the
+ * stats from rx and tx queues.
+ */
+ spinlock_t stats_lock;
};
int eea_net_probe(struct eea_device *edev);
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v39 6/8] eea: implement packet transmit logic
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260408122027.80303-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_net.c | 8 +
drivers/net/ethernet/alibaba/eea/eea_tx.c | 338 ++++++++++++++++++++-
2 files changed, 342 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index 58ce385826ea..a7865d0eb118 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -719,6 +719,12 @@ int eea_net_probe(struct eea_device *edev)
eea_update_ts_off(edev, enet);
+ netif_carrier_off(enet->netdev);
+
+ err = register_netdev(enet->netdev);
+ if (err)
+ goto err_reset_dev;
+
netdev_dbg(enet->netdev, "eea probe success.\n");
return 0;
@@ -771,6 +777,8 @@ void eea_net_remove(struct eea_device *edev, bool ha)
return;
}
+ unregister_netdev(netdev);
+
if (!enet->wait_pci_ready) {
eea_device_reset(edev);
eea_destroy_adminq(enet);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
index e01168d4e437..074cf4c4451c 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;
@@ -28,21 +33,346 @@ struct eea_tx_meta {
u32 dma_len;
};
+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) {
+ /* For split descriptors, the second meta's dma_len is
+ * zero.
+ */
+ if (meta->dma_len) {
+ if (head == meta)
+ 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 bool 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 = ering_cq_get_desc(tx->ering);
+ if (!desc)
+ break;
+
+ id = le16_to_cpu(desc->id);
+ if (unlikely(id >= tx->ering->num)) {
+ netdev_err(tx->enet->netdev, "tx invalid id %d\n", id);
+ 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 {
+ netdev_err(tx->enet->netdev,
+ "tx meta->data is null. id %d num: %d\n",
+ meta->id, meta->num);
+ desc_n = 1;
+ }
+
+ ering_cq_ack_desc(tx->ering, desc_n);
+ }
+
+ return stats.packets == budget;
+}
+
bool eea_poll_tx(struct eea_net_tx *tx, int budget)
{
- /* Empty function; will be implemented in a subsequent commit. */
- return true;
+ struct eea_net *enet = tx->enet;
+ u32 index = tx - enet->tx;
+ struct netdev_queue *txq;
+ bool busy;
+
+ txq = netdev_get_tx_queue(enet->netdev, index);
+
+ __netif_tx_lock(txq, smp_processor_id());
+
+ busy = 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 busy;
+}
+
+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)
+{
+ struct eea_tx_meta *meta;
+ struct eea_tx_desc *desc;
+
+ meta = eea_tx_meta_get(tx);
+
+ desc = 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;
+
+ 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;
+
+ /* 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);
+
+ 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);
+ }
+
+ 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;
+ dma_addr_t addr;
+ int i, err;
+ u16 flags;
+
+ addr = dma_map_single(tx->dma_dev, skb->data, hlen, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
+ return -ENOMEM;
+
+ flags = skb->ip_summed == CHECKSUM_PARTIAL ? EEA_DESC_F_DO_CSUM : 0;
+
+ meta = eea_tx_desc_fill(tx, NULL, addr, hlen, !shinfo->nr_frags,
+ skb, flags);
+
+ err = eea_fill_desc_from_skb(skb, meta->desc);
+ if (err)
+ goto err_cancel;
+
+ for (i = 0; i < shinfo->nr_frags; i++) {
+ const skb_frag_t *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;
+ }
+
+ ering_sq_commit_desc(tx->ering);
+
+ return 0;
+
+err_cancel:
+ 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)
+{
+ ering_kick(tx->ering);
}
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, n;
+
+ txq = netdev_get_tx_queue(netdev, qnum);
+
+ 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.
+ * 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;
+ netif_txq_maybe_stop(txq, tx->ering->num_free, n, n);
+
+ 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 v39 7/8] eea: introduce ethtool support
From: Xuan Zhuo @ 2026-04-08 12:20 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, Jes Sorensen, Heiner Kallweit, Dust Li, Andrew Lunn
In-Reply-To: <20260408122027.80303-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces ethtool support.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
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 | 1 +
.../net/ethernet/alibaba/eea/eea_ethtool.c | 253 ++++++++++++++++++
.../net/ethernet/alibaba/eea/eea_ethtool.h | 49 ++++
drivers/net/ethernet/alibaba/eea/eea_net.c | 1 +
drivers/net/ethernet/alibaba/eea/eea_net.h | 5 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 32 ++-
drivers/net/ethernet/alibaba/eea/eea_tx.c | 27 +-
7 files changed, 361 insertions(+), 7 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index fa34a005fa01..8f8fbb8d2d9a 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -4,5 +4,6 @@ eea-y := eea_ring.o \
eea_net.o \
eea_pci.o \
eea_adminq.o \
+ eea_ethtool.o \
eea_tx.o \
eea_rx.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ethtool.c b/drivers/net/ethernet/alibaba/eea/eea_ethtool.c
new file mode 100644
index 000000000000..106eb8e6abe6
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ethtool.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "eea_adminq.h"
+#include "eea_net.h"
+#include "eea_pci.h"
+
+#define EEA_NET_MIN_DEPTH 64
+
+struct eea_stat_desc {
+ char desc[ETH_GSTRING_LEN];
+ size_t offset;
+};
+
+#define EEA_TX_STAT(m) {#m, offsetof(struct eea_tx_stats, m)}
+#define EEA_RX_STAT(m) {#m, offsetof(struct eea_rx_stats, m)}
+
+static const struct eea_stat_desc eea_rx_stats_desc[] = {
+ EEA_RX_STAT(descs),
+ EEA_RX_STAT(kicks),
+};
+
+static const struct eea_stat_desc eea_tx_stats_desc[] = {
+ EEA_TX_STAT(descs),
+ EEA_TX_STAT(kicks),
+};
+
+#define EEA_TX_STATS_LEN ARRAY_SIZE(eea_tx_stats_desc)
+#define EEA_RX_STATS_LEN ARRAY_SIZE(eea_rx_stats_desc)
+
+static void eea_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *info)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ struct eea_device *edev = enet->edev;
+
+ strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver));
+ strscpy(info->bus_info, eea_pci_name(edev), sizeof(info->bus_info));
+}
+
+static void eea_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+
+ ring->rx_max_pending = enet->cfg_hw.rx_ring_depth;
+ ring->tx_max_pending = enet->cfg_hw.tx_ring_depth;
+ ring->rx_pending = enet->cfg.rx_ring_depth;
+ ring->tx_pending = enet->cfg.tx_ring_depth;
+
+ kernel_ring->tcp_data_split = enet->cfg.split_hdr ?
+ ETHTOOL_TCP_DATA_SPLIT_ENABLED :
+ ETHTOOL_TCP_DATA_SPLIT_DISABLED;
+}
+
+static int eea_set_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ struct eea_net_init_ctx ctx;
+ bool need_update = false;
+ struct eea_net_cfg *cfg;
+ bool sh;
+
+ if (ring->rx_pending < EEA_NET_MIN_DEPTH ||
+ ring->tx_pending < EEA_NET_MIN_DEPTH)
+ return -EINVAL;
+
+ eea_init_ctx(enet, &ctx);
+
+ cfg = &ctx.cfg;
+
+ if (ring->rx_pending != cfg->rx_ring_depth)
+ need_update = true;
+
+ if (ring->tx_pending != cfg->tx_ring_depth)
+ need_update = true;
+
+ sh = kernel_ring->tcp_data_split == ETHTOOL_TCP_DATA_SPLIT_ENABLED;
+ if (sh != !!(cfg->split_hdr))
+ need_update = true;
+
+ if (!need_update)
+ return 0;
+
+ cfg->rx_ring_depth = ring->rx_pending;
+ cfg->tx_ring_depth = ring->tx_pending;
+
+ /* By default, enet->cfg_hw.split_hdr is 128. */
+ cfg->split_hdr = sh ? enet->cfg_hw.split_hdr : 0;
+
+ return eea_reset_hw_resources(enet, &ctx);
+}
+
+static int eea_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ u16 queue_pairs = channels->combined_count;
+ struct eea_net_init_ctx ctx;
+ struct eea_net_cfg *cfg;
+
+ eea_init_ctx(enet, &ctx);
+
+ cfg = &ctx.cfg;
+
+ cfg->rx_ring_num = queue_pairs;
+ cfg->tx_ring_num = queue_pairs;
+
+ return eea_reset_hw_resources(enet, &ctx);
+}
+
+static void eea_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+
+ channels->combined_count = enet->cfg.rx_ring_num;
+ channels->max_combined = enet->cfg_hw.rx_ring_num;
+}
+
+static void eea_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ u8 *p = data;
+ u32 i, j;
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < enet->cfg.rx_ring_num; i++) {
+ for (j = 0; j < EEA_RX_STATS_LEN; j++)
+ ethtool_sprintf(&p, "rx%u_%s", i,
+ eea_rx_stats_desc[j].desc);
+ }
+
+ for (i = 0; i < enet->cfg.tx_ring_num; i++) {
+ for (j = 0; j < EEA_TX_STATS_LEN; j++)
+ ethtool_sprintf(&p, "tx%u_%s", i,
+ eea_tx_stats_desc[j].desc);
+ }
+}
+
+static int eea_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+
+ if (sset != ETH_SS_STATS)
+ return -EOPNOTSUPP;
+
+ return enet->cfg.rx_ring_num * EEA_RX_STATS_LEN +
+ enet->cfg.tx_ring_num * EEA_TX_STATS_LEN;
+}
+
+static void eea_stats_fill_for_q(struct u64_stats_sync *syncp, u32 num,
+ const struct eea_stat_desc *desc,
+ u64 *data, u32 idx)
+{
+ void *stats_base = syncp;
+ u32 start, i;
+
+ do {
+ start = u64_stats_fetch_begin(syncp);
+ for (i = 0; i < num; i++)
+ data[idx + i] =
+ u64_stats_read(stats_base + desc[i].offset);
+
+ } while (u64_stats_fetch_retry(syncp, start));
+}
+
+static void eea_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ u32 i, idx = 0;
+
+ ASSERT_RTNL();
+
+ if (enet->rx) {
+ for (i = 0; i < enet->cfg.rx_ring_num; i++) {
+ struct eea_net_rx *rx = enet->rx[i];
+
+ eea_stats_fill_for_q(&rx->stats.syncp, EEA_RX_STATS_LEN,
+ eea_rx_stats_desc, data, idx);
+
+ idx += EEA_RX_STATS_LEN;
+ }
+ }
+
+ if (enet->tx) {
+ for (i = 0; i < enet->cfg.tx_ring_num; i++) {
+ struct eea_net_tx *tx = &enet->tx[i];
+
+ eea_stats_fill_for_q(&tx->stats.syncp, EEA_TX_STATS_LEN,
+ eea_tx_stats_desc, data, idx);
+
+ idx += EEA_TX_STATS_LEN;
+ }
+ }
+}
+
+void eea_update_rx_stats(struct eea_rx_stats *rx_stats,
+ struct eea_rx_ctx_stats *stats)
+{
+ u64_stats_update_begin(&rx_stats->syncp);
+ u64_stats_add(&rx_stats->descs, stats->descs);
+ u64_stats_add(&rx_stats->packets, stats->packets);
+ u64_stats_add(&rx_stats->bytes, stats->bytes);
+ u64_stats_add(&rx_stats->drops, stats->drops);
+ u64_stats_add(&rx_stats->split_hdr_bytes, stats->split_hdr_bytes);
+ u64_stats_add(&rx_stats->split_hdr_packets, stats->split_hdr_packets);
+ u64_stats_add(&rx_stats->length_errors, stats->length_errors);
+ u64_stats_add(&rx_stats->kicks, stats->kicks);
+ u64_stats_update_end(&rx_stats->syncp);
+}
+
+static int eea_get_link_ksettings(struct net_device *netdev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+
+ cmd->base.speed = enet->speed;
+ cmd->base.duplex = enet->duplex;
+ cmd->base.port = PORT_OTHER;
+
+ return 0;
+}
+
+const struct ethtool_ops eea_ethtool_ops = {
+ .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT,
+ .get_drvinfo = eea_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_ringparam = eea_get_ringparam,
+ .set_ringparam = eea_set_ringparam,
+ .set_channels = eea_set_channels,
+ .get_channels = eea_get_channels,
+ .get_strings = eea_get_strings,
+ .get_sset_count = eea_get_sset_count,
+ .get_ethtool_stats = eea_get_ethtool_stats,
+ .get_link_ksettings = eea_get_link_ksettings,
+};
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ethtool.h b/drivers/net/ethernet/alibaba/eea/eea_ethtool.h
new file mode 100644
index 000000000000..72e1986b7f2e
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ethtool.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_ETHTOOL_H__
+#define __EEA_ETHTOOL_H__
+
+struct eea_tx_stats {
+ struct u64_stats_sync syncp;
+ u64_stats_t descs;
+ u64_stats_t packets;
+ u64_stats_t bytes;
+ u64_stats_t drops;
+ u64_stats_t kicks;
+};
+
+struct eea_rx_ctx_stats {
+ u64 descs;
+ u64 packets;
+ u64 bytes;
+ u64 drops;
+ u64 split_hdr_bytes;
+ u64 split_hdr_packets;
+ u64 kicks;
+ u64 length_errors;
+};
+
+struct eea_rx_stats {
+ struct u64_stats_sync syncp;
+ u64_stats_t descs;
+ u64_stats_t packets;
+ u64_stats_t bytes;
+ u64_stats_t drops;
+ u64_stats_t kicks;
+ u64_stats_t split_hdr_bytes;
+ u64_stats_t split_hdr_packets;
+
+ u64_stats_t length_errors;
+};
+
+void eea_update_rx_stats(struct eea_rx_stats *rx_stats,
+ struct eea_rx_ctx_stats *stats);
+
+extern const struct ethtool_ops eea_ethtool_ops;
+
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index a7865d0eb118..c70c0be7b1ea 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -617,6 +617,7 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
}
netdev->netdev_ops = &eea_netdev;
+ netdev->ethtool_ops = &eea_ethtool_ops;
SET_NETDEV_DEV(netdev, edev->dma_dev);
enet = netdev_priv(netdev);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 001113aad835..5124d112a046 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -12,6 +12,7 @@
#include <linux/netdevice.h>
#include "eea_adminq.h"
+#include "eea_ethtool.h"
#include "eea_ring.h"
#define EEA_VER_MAJOR 1
@@ -33,6 +34,8 @@ struct eea_net_tx {
u32 index;
char name[16];
+
+ struct eea_tx_stats stats;
};
struct eea_rx_meta {
@@ -87,6 +90,8 @@ struct eea_net_rx {
struct napi_struct *napi;
+ struct eea_rx_stats stats;
+
char name[16];
struct eea_net_rx_pkt_ctx pkt;
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
index 7ca99e39a85a..61d28e7df72f 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_rx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -29,6 +29,8 @@ struct eea_rx_ctx {
bool more;
struct eea_rx_meta *meta;
+
+ struct eea_rx_ctx_stats stats;
};
static struct eea_rx_meta *eea_rx_meta_get(struct eea_net_rx *rx)
@@ -232,6 +234,7 @@ static int eea_harden_check_overflow(struct eea_rx_ctx *ctx,
if (unlikely(ctx->len > max_len)) {
pr_debug("%s: rx error: len %u exceeds truesize %u\n",
enet->netdev->name, ctx->len, max_len);
+ ++ctx->stats.length_errors;
return -EINVAL;
}
@@ -250,6 +253,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
if (unlikely(ctx->hdr_len < ETH_HLEN)) {
pr_debug("%s: short hdr %u\n", enet->netdev->name,
ctx->hdr_len);
+ ++ctx->stats.length_errors;
return -EINVAL;
}
@@ -257,6 +261,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
pr_debug("%s: rx error: hdr len %u exceeds hdr buffer size %u\n",
enet->netdev->name, ctx->hdr_len,
enet->cfg.split_hdr);
+ ++ctx->stats.length_errors;
return -EINVAL;
}
@@ -265,6 +270,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
if (unlikely(ctx->len < ETH_HLEN)) {
pr_debug("%s: short packet %u\n", enet->netdev->name, ctx->len);
+ ++ctx->stats.length_errors;
return -EINVAL;
}
@@ -375,6 +381,7 @@ static void process_remain_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
err:
dev_kfree_skb(rx->pkt.head_skb);
+ ++ctx->stats.drops;
rx->pkt.do_drop = true;
rx->pkt.head_skb = NULL;
}
@@ -402,6 +409,7 @@ static void process_first_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
return;
err:
+ ++ctx->stats.drops;
rx->pkt.do_drop = true;
}
@@ -459,6 +467,8 @@ static int eea_rx_desc_to_ctx(struct eea_net_rx *rx,
if (ctx->flags & EEA_DESC_F_SPLIT_HDR) {
ctx->hdr_len = le16_to_cpu(desc->len_ex) &
EEA_RX_CDESC_HDR_LEN_MASK;
+ ctx->stats.split_hdr_bytes += ctx->hdr_len;
+ ++ctx->stats.split_hdr_packets;
}
ctx->more = ctx->flags & EEA_RING_DESC_F_MORE;
@@ -483,8 +493,10 @@ static int eea_cleanrx(struct eea_net_rx *rx, int budget,
if (ctx->meta)
eea_rx_meta_put(rx, ctx->meta);
- if (rx->pkt.head_skb)
+ if (rx->pkt.head_skb) {
dev_kfree_skb(rx->pkt.head_skb);
+ ++ctx->stats.drops;
+ }
/* A hardware error occurred; we are attempting to
* mitigate the impact. Subsequent packets may be
@@ -511,13 +523,17 @@ static int eea_cleanrx(struct eea_net_rx *rx, int budget,
++rx->pkt.idx;
- if (!ctx->more && rx->pkt.head_skb)
+ if (!ctx->more && rx->pkt.head_skb) {
eea_submit_skb(rx, rx->pkt.head_skb, desc);
+ ctx->stats.bytes += rx->pkt.recv_len;
+ ++ctx->stats.packets;
+ }
skip:
eea_rx_meta_put(rx, meta);
ack:
ering_cq_ack_desc(rx->ering, 1);
+ ++ctx->stats.descs;
if (!ctx->more) {
memset(&rx->pkt, 0, sizeof(rx->pkt));
@@ -536,7 +552,7 @@ static void eea_rx_dma_sync_hdr(struct eea_net_rx *rx, dma_addr_t addr)
}
/* Only be called from napi. */
-static bool eea_rx_post(struct eea_net_rx *rx)
+static bool eea_rx_post(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
{
u32 tailroom, headroom, room, len;
struct eea_rx_meta *meta;
@@ -583,8 +599,10 @@ static bool eea_rx_post(struct eea_net_rx *rx)
++num;
}
- if (num)
+ if (num) {
ering_kick(rx->ering);
+ ++ctx->stats.kicks;
+ }
/* true means busy, napi should be called again. */
return !!err;
@@ -608,7 +626,9 @@ static int eea_poll(struct napi_struct *napi, int budget)
* buffers are exhausted. Therefore, we should proactively
* pre-fill the buffers to avoid starvation.
*/
- busy |= eea_rx_post(rx);
+ busy |= eea_rx_post(rx, &ctx);
+
+ eea_update_rx_stats(&rx->stats, &ctx.stats);
busy |= received >= budget;
@@ -747,6 +767,8 @@ struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx)
rx->index = idx;
snprintf(rx->name, sizeof(rx->name), "rx.%u", idx);
+ u64_stats_init(&rx->stats.syncp);
+
/* ering */
ering = ering_alloc(idx * 2, ctx->cfg.rx_ring_depth, ctx->edev,
ctx->cfg.rx_sq_desc_size,
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
index 074cf4c4451c..2f6d34b48472 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_tx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -94,6 +94,7 @@ static void eea_meta_free_xmit(struct eea_net_tx *tx,
}
++stats->packets;
+ stats->bytes += meta->skb->len;
napi_consume_skb(meta->skb, budget);
meta->data = NULL;
@@ -135,6 +136,13 @@ static bool eea_clean_tx(struct eea_net_tx *tx, int budget)
ering_cq_ack_desc(tx->ering, desc_n);
}
+ if (stats.packets) {
+ u64_stats_update_begin(&tx->stats.syncp);
+ u64_stats_add(&tx->stats.bytes, stats.bytes);
+ u64_stats_add(&tx->stats.packets, stats.packets);
+ u64_stats_update_end(&tx->stats.syncp);
+ }
+
return stats.packets == budget;
}
@@ -310,6 +318,10 @@ static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
ering_sq_commit_desc(tx->ering);
+ u64_stats_update_begin(&tx->stats.syncp);
+ u64_stats_add(&tx->stats.descs, meta->num);
+ u64_stats_update_end(&tx->stats.syncp);
+
return 0;
err_cancel:
@@ -322,6 +334,10 @@ static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
static void eea_tx_kick(struct eea_net_tx *tx)
{
ering_kick(tx->ering);
+
+ u64_stats_update_begin(&tx->stats.syncp);
+ u64_stats_inc(&tx->stats.kicks);
+ u64_stats_update_end(&tx->stats.syncp);
}
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
@@ -335,10 +351,15 @@ netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
txq = netdev_get_tx_queue(netdev, qnum);
err = eea_tx_post_skb(tx, skb);
- if (unlikely(err))
+ if (unlikely(err)) {
+ u64_stats_update_begin(&tx->stats.syncp);
+ u64_stats_inc(&tx->stats.drops);
+ u64_stats_update_end(&tx->stats.syncp);
+
dev_kfree_skb_any(skb);
- else
+ } else {
skb_tx_timestamp(skb);
+ }
/* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue.
* MAX_SKB_FRAGS + 1: Covers the skb linear head and all paged fragments
@@ -398,6 +419,8 @@ int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx)
struct eea_ring *ering;
u32 i;
+ u64_stats_init(&tx->stats.syncp);
+
snprintf(tx->name, sizeof(tx->name), "tx.%u", idx);
ering = ering_alloc(idx * 2 + 1, ctx->cfg.tx_ring_depth, ctx->edev,
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH v2 0/4] Rust netlink support + use in Rust Binder
From: Alice Ryhl @ 2026-04-08 12:20 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: linux-kernel, rust-for-linux, netdev, Alice Ryhl
The C Binder driver exposes messages over netlink when transactions
fail, so that a userpace daemon can respond to processes with many
failing transactions.
This patch series adds netlink support from Rust, then implements an
equivalent API in Rust Binder.
As Binder only uses broadcast messages, I did not add support for other
kinds of messages.
Based on char-misc-next.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
Changes in v2:
- Make inclusion of to_pid conditional too.
- Add note about file name in second patch.
- Make it clear that the sk_buff wrapper is netlink-specific.
- Better handle bitfield in patch 1.
- Link to v1: https://lore.kernel.org/r/20260306-binder-netlink-v1-0-daceb5bc83f2@google.com
---
Alice Ryhl (3):
rust: netlink: add raw netlink abstraction
ynl_gen: generate Rust files from yaml files
rust_binder: add generated netlink.rs file
Carlos Llamas (1):
rust_binder: report netlink transactions
drivers/android/Kconfig | 2 +-
drivers/android/binder/netlink.rs | 113 ++++++++++
drivers/android/binder/rust_binder_main.rs | 8 +-
drivers/android/binder/thread.rs | 9 +
drivers/android/binder/transaction.rs | 40 ++++
rust/bindings/bindings_helper.h | 3 +
rust/helpers/genetlink.c | 46 ++++
rust/helpers/helpers.c | 1 +
rust/kernel/lib.rs | 1 +
rust/kernel/netlink.rs | 329 +++++++++++++++++++++++++++++
rust/uapi/uapi_helper.h | 1 +
tools/net/ynl/pyynl/ynl_gen_c.py | 132 +++++++++++-
tools/net/ynl/ynl-regen.sh | 2 +-
13 files changed, 682 insertions(+), 5 deletions(-)
---
base-commit: 0990a71f678aa0f045f2c126b39b6b581844d3b0
change-id: 20260306-binder-netlink-c82110b2fb74
Best regards,
--
Alice Ryhl <aliceryhl@google.com>
^ permalink raw reply
* [PATCH v2 1/4] rust: netlink: add raw netlink abstraction
From: Alice Ryhl @ 2026-04-08 12:20 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: linux-kernel, rust-for-linux, netdev, Alice Ryhl
In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com>
This implements a safe and relatively simple API over the netlink API,
that allows you to add different attributes to a netlink message and
broadcast it. As the first user of this API only makes use of broadcast,
only broadcast messages are supported here.
This API is intended to be safe and to be easy to use in *generated*
code. This is because netlink is generally used with yaml files that
describe the underlying API, and the python generator outputs C code
(or, soon, Rust code) that lets you use the API more easily. So for
example, if there is a string field, the code generator will output a
method that internall calls `put_string()` with the right attr type.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
rust/bindings/bindings_helper.h | 3 +
rust/helpers/genetlink.c | 46 ++++++
rust/helpers/helpers.c | 1 +
rust/kernel/lib.rs | 1 +
rust/kernel/netlink.rs | 329 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 380 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 083cc44aa952..8abb626fce6c 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -88,6 +88,8 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/xarray.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
#include <trace/events/rust_sample.h>
/*
@@ -105,6 +107,7 @@
const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN;
const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN;
const size_t RUST_CONST_HELPER_PAGE_SIZE = PAGE_SIZE;
+const size_t RUST_CONST_HELPER_GENLMSG_DEFAULT_SIZE = GENLMSG_DEFAULT_SIZE;
const gfp_t RUST_CONST_HELPER_GFP_ATOMIC = GFP_ATOMIC;
const gfp_t RUST_CONST_HELPER_GFP_KERNEL = GFP_KERNEL;
const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT;
diff --git a/rust/helpers/genetlink.c b/rust/helpers/genetlink.c
new file mode 100644
index 000000000000..99ada80cfa41
--- /dev/null
+++ b/rust/helpers/genetlink.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 Google LLC.
+ */
+
+#include <net/genetlink.h>
+
+#ifdef CONFIG_NET
+
+__rust_helper struct sk_buff *rust_helper_genlmsg_new(size_t payload, gfp_t flags)
+{
+ return genlmsg_new(payload, flags);
+}
+
+__rust_helper
+int rust_helper_genlmsg_multicast(const struct genl_family *family,
+ struct sk_buff *skb, u32 portid,
+ unsigned int group, gfp_t flags)
+{
+ return genlmsg_multicast(family, skb, portid, group, flags);
+}
+
+__rust_helper void rust_helper_genlmsg_cancel(struct sk_buff *skb, void *hdr)
+{
+ return genlmsg_cancel(skb, hdr);
+}
+
+__rust_helper void rust_helper_genlmsg_end(struct sk_buff *skb, void *hdr)
+{
+ return genlmsg_end(skb, hdr);
+}
+
+__rust_helper void rust_helper_nlmsg_free(struct sk_buff *skb)
+{
+ return nlmsg_free(skb);
+}
+
+__rust_helper
+int rust_helper_genl_has_listeners(const struct genl_family *family,
+ struct net *net, unsigned int group)
+{
+ return genl_has_listeners(family, net, group);
+}
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3c42e51f00a..0813185d8760 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -32,6 +32,7 @@
#include "err.c"
#include "irq.c"
#include "fs.c"
+#include "genetlink.c"
#include "io.c"
#include "jump_label.c"
#include "kunit.c"
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d93292d47420..f5ea0ae0b6b7 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -122,6 +122,7 @@
pub mod module_param;
#[cfg(CONFIG_NET)]
pub mod net;
+pub mod netlink;
pub mod num;
pub mod of;
#[cfg(CONFIG_PM_OPP)]
diff --git a/rust/kernel/netlink.rs b/rust/kernel/netlink.rs
new file mode 100644
index 000000000000..21f959c95fdc
--- /dev/null
+++ b/rust/kernel/netlink.rs
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2026 Google LLC.
+
+//! Rust support for generic netlink.
+//!
+//! Currently only supports exposing multicast groups.
+//!
+//! C header: [`include/net/genetlink.h`](srctree/include/net/genetlink.h)
+#![cfg(CONFIG_NET)]
+
+use kernel::{
+ alloc::{self, AllocError},
+ error::to_result,
+ prelude::*,
+ transmute::AsBytes,
+ types::Opaque,
+ ThisModule,
+};
+
+use core::{
+ mem::ManuallyDrop,
+ ptr::NonNull, //
+};
+
+/// The default netlink message size.
+pub const GENLMSG_DEFAULT_SIZE: usize = bindings::GENLMSG_DEFAULT_SIZE;
+
+/// A wrapper around `struct sk_buff` for generic netlink messages.
+///
+/// This type is intended to be specific for buffers used with netlink only, and other usecases for
+/// `struct sk_buff` are out-of-scope for this abstraction.
+///
+/// # Invariants
+///
+/// The pointer has ownership over a valid `sk_buff`.
+pub struct NetlinkSkBuff {
+ skb: NonNull<kernel::bindings::sk_buff>,
+}
+
+impl NetlinkSkBuff {
+ /// Creates a new `NetlinkSkBuff` with the given size.
+ pub fn new(size: usize, flags: alloc::Flags) -> Result<NetlinkSkBuff, AllocError> {
+ // SAFETY: `genlmsg_new` only requires its arguments to be valid integers.
+ let skb = unsafe { bindings::genlmsg_new(size, flags.as_raw()) };
+ let skb = NonNull::new(skb).ok_or(AllocError)?;
+ Ok(NetlinkSkBuff { skb })
+ }
+
+ /// Puts a generic netlink header into the `NetlinkSkBuff`.
+ pub fn genlmsg_put(
+ self,
+ portid: u32,
+ seq: u32,
+ family: &'static Family,
+ cmd: u8,
+ ) -> Result<GenlMsg, AllocError> {
+ let skb = self.skb.as_ptr();
+ // SAFETY: The skb and family pointers are valid.
+ let hdr = unsafe { bindings::genlmsg_put(skb, portid, seq, family.as_raw(), 0, cmd) };
+ let hdr = NonNull::new(hdr).ok_or(AllocError)?;
+ Ok(GenlMsg { skb: self, hdr })
+ }
+}
+
+impl Drop for NetlinkSkBuff {
+ fn drop(&mut self) {
+ // SAFETY: We have ownership over the `sk_buff`, so we may free it.
+ unsafe { bindings::nlmsg_free(self.skb.as_ptr()) }
+ }
+}
+
+/// A generic netlink message being constructed.
+///
+/// # Invariants
+///
+/// `hdr` references the header in this netlink message.
+pub struct GenlMsg {
+ skb: NetlinkSkBuff,
+ hdr: NonNull<c_void>,
+}
+
+impl GenlMsg {
+ /// Puts an attribute into the message.
+ #[inline]
+ fn put<T>(&mut self, attrtype: c_int, value: &T) -> Result
+ where
+ T: ?Sized + AsBytes,
+ {
+ let skb = self.skb.skb.as_ptr();
+ let len = size_of_val(value);
+ let ptr = core::ptr::from_ref(value).cast::<c_void>();
+ // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and the provided value is
+ // readable and initialized for its `size_of` bytes.
+ to_result(unsafe { bindings::nla_put(skb, attrtype, len as c_int, ptr) })
+ }
+
+ /// Puts a `u32` attribute into the message.
+ #[inline]
+ pub fn put_u32(&mut self, attrtype: c_int, value: u32) -> Result {
+ self.put(attrtype, &value)
+ }
+
+ /// Puts a string attribute into the message.
+ #[inline]
+ pub fn put_string(&mut self, attrtype: c_int, value: &CStr) -> Result {
+ self.put(attrtype, value.to_bytes_with_nul())
+ }
+
+ /// Puts a flag attribute into the message.
+ #[inline]
+ pub fn put_flag(&mut self, attrtype: c_int) -> Result {
+ let skb = self.skb.skb.as_ptr();
+ // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and a null pointer is valid
+ // when the length is zero.
+ to_result(unsafe { bindings::nla_put(skb, attrtype, 0, core::ptr::null()) })
+ }
+
+ /// Sends the generic netlink message as a multicast message.
+ #[inline]
+ pub fn multicast(
+ self,
+ family: &'static Family,
+ portid: u32,
+ group: u32,
+ flags: alloc::Flags,
+ ) -> Result {
+ let me = ManuallyDrop::new(self);
+ // SAFETY: The `skb` and `family` pointers are valid. We pass ownership of the `skb` to
+ // `genlmsg_multicast` by not dropping `self`.
+ unsafe {
+ bindings::genlmsg_end(me.skb.skb.as_ptr(), me.hdr.as_ptr());
+ to_result(bindings::genlmsg_multicast(
+ family.as_raw(),
+ me.skb.skb.as_ptr(),
+ portid,
+ group,
+ flags.as_raw(),
+ ))
+ }
+ }
+}
+impl Drop for GenlMsg {
+ fn drop(&mut self) {
+ // SAFETY: The `hdr` pointer references the header of this generic netlink message.
+ unsafe { bindings::genlmsg_cancel(self.skb.skb.as_ptr(), self.hdr.as_ptr()) };
+ }
+}
+
+/// Flags for a generic netlink family.
+struct FamilyFlags {
+ /// Whether the family supports network namespaces.
+ netnsok: bool,
+ /// Whether the family supports parallel operations.
+ parallel_ops: bool,
+}
+
+impl FamilyFlags {
+ /// Converts the flags to the bitfield representation used by `genl_family`.
+ const fn into_bitfield(self) -> bindings::__BindgenBitfieldUnit<[u8; 1]> {
+ // The below shifts are verified correct by test_family_flags_bitfield() below.
+ //
+ // Although bindgen generates helpers to change bitfields based on the C headers, these
+ // helpers unfortunately can't be used in const context. Since `Family` needs to be filled
+ // out at build-time, we use this helper instead.
+ let mut bits = 0;
+ if self.netnsok {
+ bits |= 1 << 0;
+ }
+ if self.parallel_ops {
+ bits |= 1 << 1;
+ }
+ // SAFETY: This bitfield is represented as an u8.
+ unsafe { core::mem::transmute::<u8, bindings::__BindgenBitfieldUnit<[u8; 1]>>(bits) }
+ }
+}
+
+/// A generic netlink family.
+#[repr(transparent)]
+pub struct Family {
+ inner: Opaque<bindings::genl_family>,
+}
+
+// SAFETY: The `Family` type is thread safe.
+unsafe impl Sync for Family {}
+
+impl Family {
+ /// Creates a new `Family` instance.
+ pub const fn const_new(
+ module: &ThisModule,
+ name: &[u8],
+ version: u32,
+ mcgrps: &'static [MulticastGroup],
+ ) -> Family {
+ let n_mcgrps = mcgrps.len() as u8;
+ if n_mcgrps as usize != mcgrps.len() {
+ panic!("too many mcgrps");
+ }
+ let mut genl_family = bindings::genl_family {
+ version,
+ _bitfield_1: FamilyFlags {
+ netnsok: true,
+ parallel_ops: true,
+ }
+ .into_bitfield(),
+ module: module.as_ptr(),
+ mcgrps: mcgrps.as_ptr().cast(),
+ n_mcgrps,
+ ..pin_init::zeroed()
+ };
+ if CStr::from_bytes_with_nul(name).is_err() {
+ panic!("genl_family name not nul-terminated");
+ }
+ if genl_family.name.len() < name.len() {
+ panic!("genl_family name too long");
+ }
+ let mut i = 0;
+ while i < name.len() {
+ genl_family.name[i] = name[i];
+ i += 1;
+ }
+ Family {
+ inner: Opaque::new(genl_family),
+ }
+ }
+
+ /// Checks if there are any listeners for the given multicast group.
+ pub fn has_listeners(&self, group: u32) -> bool {
+ // SAFETY: The family and init_net pointers are valid.
+ unsafe {
+ bindings::genl_has_listeners(self.as_raw(), &raw mut bindings::init_net, group) != 0
+ }
+ }
+
+ /// Returns a raw pointer to the underlying `genl_family` structure.
+ pub fn as_raw(&self) -> *mut bindings::genl_family {
+ self.inner.get()
+ }
+}
+
+/// A generic netlink multicast group.
+#[repr(transparent)]
+pub struct MulticastGroup {
+ // No Opaque because fully immutable
+ group: bindings::genl_multicast_group,
+}
+
+// SAFETY: Pure data so thread safe.
+unsafe impl Sync for MulticastGroup {}
+
+impl MulticastGroup {
+ /// Creates a new `MulticastGroup` instance.
+ pub const fn const_new(name: &CStr) -> MulticastGroup {
+ let mut group: bindings::genl_multicast_group = pin_init::zeroed();
+
+ let name = name.to_bytes_with_nul();
+ if group.name.len() < name.len() {
+ panic!("genl_multicast_group name too long");
+ }
+ let mut i = 0;
+ while i < name.len() {
+ group.name[i] = name[i];
+ i += 1;
+ }
+
+ MulticastGroup { group }
+ }
+}
+
+/// A registration of a generic netlink family.
+///
+/// This type represents the registration of a [`Family`]. When an instance of this type is
+/// dropped, its respective generic netlink family will be unregistered from the system.
+///
+/// # Invariants
+///
+/// `self.family` always holds a valid reference to an initialized and registered [`Family`].
+pub struct Registration {
+ family: &'static Family,
+}
+
+impl Family {
+ /// Registers the generic netlink family with the kernel.
+ pub fn register(&'static self) -> Result<Registration> {
+ // SAFETY: `self.as_raw()` is a valid pointer to a `genl_family` struct.
+ // The `genl_family` struct is static, so it will outlive the registration.
+ to_result(unsafe { bindings::genl_register_family(self.as_raw()) })?;
+ Ok(Registration { family: self })
+ }
+}
+
+impl Drop for Registration {
+ fn drop(&mut self) {
+ // SAFETY: `self.family.as_raw()` is a valid pointer to a registered `genl_family` struct.
+ // The `Registration` struct ensures that `genl_unregister_family` is called exactly once
+ // for this family when it goes out of scope.
+ unsafe { bindings::genl_unregister_family(self.family.as_raw()) };
+ }
+}
+
+#[macros::kunit_tests(rust_netlink)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_family_flags_bitfield() {
+ for netnsok in [false, true] {
+ for parallel_ops in [false, true] {
+ let mut b_fam = bindings::genl_family {
+ ..Default::default()
+ };
+ b_fam.set_netnsok(if netnsok { 1 } else { 0 });
+ b_fam.set_parallel_ops(if parallel_ops { 1 } else { 0 });
+
+ let c_bitfield = FamilyFlags {
+ netnsok,
+ parallel_ops,
+ }
+ .into_bitfield();
+
+ // SAFETY: The bit field is stored as u8.
+ let b_val: u8 = unsafe { core::mem::transmute(b_fam._bitfield_1) };
+ // SAFETY: The bit field is stored as u8.
+ let c_val: u8 = unsafe { core::mem::transmute(c_bitfield) };
+ assert_eq!(b_val, c_val);
+ }
+ }
+ }
+}
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v2 2/4] ynl_gen: generate Rust files from yaml files
From: Alice Ryhl @ 2026-04-08 12:20 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: linux-kernel, rust-for-linux, netdev, Alice Ryhl
In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com>
To generate netlink frames from Rust code easily, generate Rust
libraries with methods for generating different netlink messages as
appropriate.
The new 'rust' type corresponds to a Rust version of the C target
'kernel'. There is no Rust version of the 'uapi' target since Rust code
exports its uapi via C headers - choice of language is opaque to
userspace.
This logic is kept in the existing ynl_gen_c.py file to reuse CodeWriter
and other shared pieces of logic in the existing python file. This has
the disadvantage that the gen_c part of the name is now wrong, as it
also generates Rust. One possible solution to this could be to rename
the file.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
tools/net/ynl/pyynl/ynl_gen_c.py | 132 ++++++++++++++++++++++++++++++++++++++-
tools/net/ynl/ynl-regen.sh | 2 +-
2 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py
index 0e1e486c1185..702d34727b6e 100755
--- a/tools/net/ynl/pyynl/ynl_gen_c.py
+++ b/tools/net/ynl/pyynl/ynl_gen_c.py
@@ -19,6 +19,7 @@ import pathlib
import os
import re
import shutil
+import subprocess
import sys
import tempfile
import yaml as pyyaml
@@ -1744,6 +1745,19 @@ class CodeWriter:
else:
self.p('}' + line)
+ def array_start(self, line=''):
+ if line:
+ line = line + ' '
+ self.p(line + '[')
+ self._ind += 1
+
+ def array_end(self, line=''):
+ if line and line[0] not in {';', ','}:
+ line = ' ' + line
+ self._ind -= 1
+ self._nl = False
+ self.p(']' + line)
+
def write_doc_line(self, doc, indent=True):
words = doc.split()
line = ' *'
@@ -3415,10 +3429,119 @@ def find_kernel_root(full_path):
return full_path, sub_path[:-1]
+def render_rust(family, cw):
+ cw.p('#![allow(unreachable_pub, clippy::wrong_self_convention)]')
+ cw.p('use kernel::netlink::{Family, MulticastGroup};')
+ cw.p('use kernel::prelude::*;')
+ cw.nl()
+
+ family_upper = c_upper(family.ident_name)
+ family_name = f'{family_upper}_NL_FAMILY'
+ mcgrps_name = f'{family_name}_MCGRPS'
+
+ cw.p(f'pub static {family_name}: Family = Family::const_new(')
+ cw._ind += 1
+ cw.p('&crate::THIS_MODULE,')
+ cw.p(f'kernel::uapi::{family.fam_key},')
+ cw.p(f'kernel::uapi::{family.ver_key},')
+ cw.p(f'&{mcgrps_name},')
+ cw._ind -= 1
+ cw.p(');')
+ cw.nl()
+
+ if family.mcgrps['list']:
+ cw.array_start(f'static {mcgrps_name}: [MulticastGroup; {len(family.mcgrps["list"])}] = ')
+ for grp in family.mcgrps['list']:
+ cw.p(f'MulticastGroup::const_new(c"{grp["name"]}"),')
+ cw.array_end(';')
+ cw.nl()
+
+ for idx, (op_name, op) in enumerate(item for item in family.msgs.items() if 'event' in item[1]):
+ struct_name = op_name.capitalize()
+
+ if 'doc' in op:
+ doc_lines = op['doc'].strip().split('\n')
+ for line in doc_lines:
+ cw.p(f'/// {line.strip()}')
+
+ cw.block_start(f'pub struct {struct_name}')
+ cw.p('skb: kernel::netlink::GenlMsg,')
+ cw.block_end()
+ cw.nl()
+
+ cw.block_start(f'impl {struct_name}')
+ cw.p('/// Create a new multicast message.')
+ cw.p('pub fn new(')
+ cw._ind += 1
+ cw.p('size: usize,')
+ cw.p('portid: u32,')
+ cw.p('seq: u32,')
+ cw.p('flags: kernel::alloc::Flags,')
+ cw._ind -= 1
+ cw.block_start(') -> Result<Self, kernel::alloc::AllocError>')
+ cw.p(f'const {op.enum_name}: u8 = kernel::uapi::{op.enum_name} as u8;')
+ cw.p('let skb = kernel::netlink::NetlinkSkBuff::new(size, flags)?;')
+ cw.p(f'let skb = skb.genlmsg_put(portid, seq, &{family_name}, {op.enum_name})?;')
+ cw.p('Ok(Self { skb })')
+ cw.block_end()
+ cw.nl()
+
+ cw.p('/// Broadcast this message.')
+ cw.block_start('pub fn multicast(self, portid: u32, flags: kernel::alloc::Flags) -> Result')
+ cw.p(f'self.skb.multicast(&{family_name}, portid, {idx}, flags)')
+ cw.block_end()
+ cw.nl()
+
+ cw.p('/// Check if this message type has listeners.')
+ cw.block_start('pub fn has_listeners() -> bool')
+ cw.p(f'{family_name}.has_listeners({idx})')
+ cw.block_end()
+
+ attr_set_name = op['attribute-set']
+ attr_set = family.attr_sets[attr_set_name]
+ event_attrs = op['event']['attributes']
+
+ for attr_name in event_attrs:
+ attr = attr_set[attr_name]
+ method_name = attr_name.replace('-', '_')
+
+ if attr.type == 'u32':
+ put_fn = 'put_u32'
+ arg_str = ', val'
+ method_args = '(&mut self, val: u32)'
+ elif attr.type == 'string':
+ put_fn = 'put_string'
+ arg_str = ', val'
+ method_args = '(&mut self, val: &CStr)'
+ elif attr.type == 'flag':
+ put_fn = 'put_flag'
+ arg_str = ''
+ method_args = '(&mut self)'
+ else:
+ put_fn = 'put_u32'
+ arg_str = ', val'
+ method_args = f'(&mut self, val: {attr.type})'
+
+ cw.nl()
+ if 'doc' in attr.yaml:
+ doc_lines = attr.yaml['doc'].strip().split('\n')
+ for line in doc_lines:
+ cw.p(f'/// {line.strip()}')
+
+ cw.block_start(f'pub fn {method_name}{method_args} -> Result')
+ cw.p(f'const {attr.enum_name}: c_int = kernel::uapi::{attr.enum_name} as c_int;')
+ cw.p(f'self.skb.{put_fn}({attr.enum_name}{arg_str})')
+ cw.block_end()
+
+ cw.block_end()
+ cw.nl()
+ cw.p(' ')
+
+
def main():
parser = argparse.ArgumentParser(description='Netlink simple parsing generator')
parser.add_argument('--mode', dest='mode', type=str, required=True,
- choices=('user', 'kernel', 'uapi'))
+ choices=('user', 'kernel', 'uapi', 'rust'))
parser.add_argument('--spec', dest='spec', type=str, required=True)
parser.add_argument('--header', dest='header', action='store_true', default=None)
parser.add_argument('--source', dest='header', action='store_false')
@@ -3471,6 +3594,13 @@ def main():
render_uapi(parsed, cw)
return
+ if args.mode == 'rust':
+ render_rust(parsed, cw)
+ cw.close_out_file()
+ if args.out_file:
+ subprocess.run(['rustfmt', '--edition', '2021', args.out_file])
+ return
+
hdr_prot = f"_LINUX_{parsed.c_name.upper()}_GEN_H"
if args.header:
cw.p('#ifndef ' + hdr_prot)
diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh
index d9809276db98..4f5ceb4fe147 100755
--- a/tools/net/ynl/ynl-regen.sh
+++ b/tools/net/ynl/ynl-regen.sh
@@ -17,7 +17,7 @@ done
KDIR=$(dirname $(dirname $(dirname $(dirname $(realpath $0)))))
pushd ${search:-$KDIR} >>/dev/null
-files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\)')
+files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\|rust\)')
for f in $files; do
# params: 0 1 2 3
# $YAML YNL-GEN kernel $mode
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v2 3/4] rust_binder: add generated netlink.rs file
From: Alice Ryhl @ 2026-04-08 12:20 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: linux-kernel, rust-for-linux, netdev, Alice Ryhl
In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com>
To use netlink from Rust Binder, add a new generated netlink file using
the new script and Documentation/netlink/specs/binder.yaml.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
drivers/android/Kconfig | 2 +-
drivers/android/binder/netlink.rs | 113 +++++++++++++++++++++++++++++
drivers/android/binder/rust_binder_main.rs | 9 ++-
rust/uapi/uapi_helper.h | 1 +
4 files changed, 122 insertions(+), 3 deletions(-)
diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig
index e2e402c9d175..606a9d07f774 100644
--- a/drivers/android/Kconfig
+++ b/drivers/android/Kconfig
@@ -16,7 +16,7 @@ config ANDROID_BINDER_IPC
config ANDROID_BINDER_IPC_RUST
bool "Rust version of Android Binder IPC Driver"
- depends on RUST && MMU && !ANDROID_BINDER_IPC
+ depends on RUST && MMU && NET && !ANDROID_BINDER_IPC
help
This enables the Rust implementation of the Binder driver.
diff --git a/drivers/android/binder/netlink.rs b/drivers/android/binder/netlink.rs
new file mode 100644
index 000000000000..2a842c7b1b33
--- /dev/null
+++ b/drivers/android/binder/netlink.rs
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/binder.yaml */
+/* YNL-GEN rust source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#![allow(unreachable_pub, clippy::wrong_self_convention)]
+use kernel::netlink::{Family, MulticastGroup};
+use kernel::prelude::*;
+
+pub static BINDER_NL_FAMILY: Family = Family::const_new(
+ &crate::THIS_MODULE,
+ kernel::uapi::BINDER_FAMILY_NAME,
+ kernel::uapi::BINDER_FAMILY_VERSION,
+ &BINDER_NL_FAMILY_MCGRPS,
+);
+
+static BINDER_NL_FAMILY_MCGRPS: [MulticastGroup; 1] = [MulticastGroup::const_new(c"report")];
+
+/// A multicast event sent to userspace subscribers to notify them about
+/// binder transaction failures. The generated report provides the full
+/// details of the specific transaction that failed. The intention is for
+/// programs to monitor these events and react to the failures as needed.
+pub struct Report {
+ skb: kernel::netlink::GenlMsg,
+}
+
+impl Report {
+ /// Create a new multicast message.
+ pub fn new(
+ size: usize,
+ portid: u32,
+ seq: u32,
+ flags: kernel::alloc::Flags,
+ ) -> Result<Self, kernel::alloc::AllocError> {
+ const BINDER_CMD_REPORT: u8 = kernel::uapi::BINDER_CMD_REPORT as u8;
+ let skb = kernel::netlink::NetlinkSkBuff::new(size, flags)?;
+ let skb = skb.genlmsg_put(portid, seq, &BINDER_NL_FAMILY, BINDER_CMD_REPORT)?;
+ Ok(Self { skb })
+ }
+
+ /// Broadcast this message.
+ pub fn multicast(self, portid: u32, flags: kernel::alloc::Flags) -> Result {
+ self.skb.multicast(&BINDER_NL_FAMILY, portid, 0, flags)
+ }
+
+ /// Check if this message type has listeners.
+ pub fn has_listeners() -> bool {
+ BINDER_NL_FAMILY.has_listeners(0)
+ }
+
+ /// The enum binder_driver_return_protocol returned to the sender.
+ pub fn error(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_ERROR: c_int = kernel::uapi::BINDER_A_REPORT_ERROR as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_ERROR, val)
+ }
+
+ /// The binder context where the transaction occurred.
+ pub fn context(&mut self, val: &CStr) -> Result {
+ const BINDER_A_REPORT_CONTEXT: c_int = kernel::uapi::BINDER_A_REPORT_CONTEXT as c_int;
+ self.skb.put_string(BINDER_A_REPORT_CONTEXT, val)
+ }
+
+ /// The PID of the sender process.
+ pub fn from_pid(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_FROM_PID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_PID as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_FROM_PID, val)
+ }
+
+ /// The TID of the sender thread.
+ pub fn from_tid(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_FROM_TID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_TID as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_FROM_TID, val)
+ }
+
+ /// The PID of the recipient process. This attribute may not be present
+ /// if the target could not be determined.
+ pub fn to_pid(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_TO_PID: c_int = kernel::uapi::BINDER_A_REPORT_TO_PID as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_TO_PID, val)
+ }
+
+ /// The TID of the recipient thread. This attribute may not be present
+ /// if the target could not be determined.
+ pub fn to_tid(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_TO_TID: c_int = kernel::uapi::BINDER_A_REPORT_TO_TID as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_TO_TID, val)
+ }
+
+ /// When present, indicates the failed transaction is a reply.
+ pub fn is_reply(&mut self) -> Result {
+ const BINDER_A_REPORT_IS_REPLY: c_int = kernel::uapi::BINDER_A_REPORT_IS_REPLY as c_int;
+ self.skb.put_flag(BINDER_A_REPORT_IS_REPLY)
+ }
+
+ /// The bitmask of enum transaction_flags from the transaction.
+ pub fn flags(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_FLAGS: c_int = kernel::uapi::BINDER_A_REPORT_FLAGS as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_FLAGS, val)
+ }
+
+ /// The application-defined code from the transaction.
+ pub fn code(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_CODE: c_int = kernel::uapi::BINDER_A_REPORT_CODE as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_CODE, val)
+ }
+
+ /// The transaction payload size in bytes.
+ pub fn data_size(&mut self, val: u32) -> Result {
+ const BINDER_A_REPORT_DATA_SIZE: c_int = kernel::uapi::BINDER_A_REPORT_DATA_SIZE as c_int;
+ self.skb.put_u32(BINDER_A_REPORT_DATA_SIZE, val)
+ }
+}
diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs
index 678e987902aa..9057e5dba7ed 100644
--- a/drivers/android/binder/rust_binder_main.rs
+++ b/drivers/android/binder/rust_binder_main.rs
@@ -36,6 +36,8 @@
mod deferred_close;
mod defs;
mod error;
+#[allow(dead_code)]
+mod netlink;
mod node;
mod page_range;
mod process;
@@ -286,19 +288,22 @@ fn ptr_align(value: usize) -> Option<usize> {
// SAFETY: We call register in `init`.
static BINDER_SHRINKER: Shrinker = unsafe { Shrinker::new() };
-struct BinderModule {}
+struct BinderModule {
+ _netlink: kernel::netlink::Registration,
+}
impl kernel::Module for BinderModule {
fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
// SAFETY: The module initializer never runs twice, so we only call this once.
unsafe { crate::context::CONTEXTS.init() };
+ let netlink = crate::netlink::BINDER_NL_FAMILY.register()?;
BINDER_SHRINKER.register(c"android-binder")?;
// SAFETY: The module is being loaded, so we can initialize binderfs.
unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? };
- Ok(Self {})
+ Ok(Self { _netlink: netlink })
}
}
diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h
index 06d7d1a2e8da..86c7b6b284b0 100644
--- a/rust/uapi/uapi_helper.h
+++ b/rust/uapi/uapi_helper.h
@@ -11,6 +11,7 @@
#include <uapi/drm/nova_drm.h>
#include <uapi/drm/panthor_drm.h>
#include <uapi/linux/android/binder.h>
+#include <uapi/linux/android/binder_netlink.h>
#include <uapi/linux/mdio.h>
#include <uapi/linux/mii.h>
#include <uapi/linux/ethtool.h>
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v2 4/4] rust_binder: report netlink transactions
From: Alice Ryhl @ 2026-04-08 12:20 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: linux-kernel, rust-for-linux, netdev, Alice Ryhl
In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com>
From: Carlos Llamas <cmllamas@google.com>
The Android Binder driver supports a netlink API that reports
transaction *failures* to a userapce daemon. This allows devices to
monitor processes with many failed transactions so that it can e.g. kill
misbehaving apps.
One very important thing that this monitors is when many oneway messages
are sent to a frozen process, so there is special handling to ensure
this scenario is surfaced over netlink.
Signed-off-by: Carlos Llamas <cmllamas@google.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
drivers/android/binder/rust_binder_main.rs | 1 -
drivers/android/binder/thread.rs | 9 +++++++
drivers/android/binder/transaction.rs | 40 ++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs
index 9057e5dba7ed..407cda7bd766 100644
--- a/drivers/android/binder/rust_binder_main.rs
+++ b/drivers/android/binder/rust_binder_main.rs
@@ -36,7 +36,6 @@
mod deferred_close;
mod defs;
mod error;
-#[allow(dead_code)]
mod netlink;
mod node;
mod page_range;
diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs
index 97d5f31e8fe3..e9bff4956ac8 100644
--- a/drivers/android/binder/thread.rs
+++ b/drivers/android/binder/thread.rs
@@ -1263,6 +1263,15 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
}
}
+ if info.oneway_spam_suspect {
+ // If this is both a oneway spam suspect and a failure, we report it twice. This is
+ // useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN.
+ info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx);
+ }
+ if info.reply != 0 {
+ info.report_netlink(info.reply, &self.process.ctx);
+ }
+
Ok(())
}
diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs
index 47d5e4d88b07..3fa7091ed8a6 100644
--- a/drivers/android/binder/transaction.rs
+++ b/drivers/android/binder/transaction.rs
@@ -3,6 +3,7 @@
// Copyright (C) 2025 Google LLC.
use kernel::{
+ netlink::GENLMSG_DEFAULT_SIZE,
prelude::*,
seq_file::SeqFile,
seq_print,
@@ -17,6 +18,7 @@
allocation::{Allocation, TranslatedFds},
defs::*,
error::{BinderError, BinderResult},
+ netlink::Report,
node::{Node, NodeRef},
process::{Process, ProcessInner},
ptr_align,
@@ -49,6 +51,44 @@ impl TransactionInfo {
pub(crate) fn is_oneway(&self) -> bool {
self.flags & TF_ONE_WAY != 0
}
+
+ pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) {
+ if let Err(err) = self.report_netlink_inner(reply, ctx) {
+ pr_warn!(
+ "{}:{} netlink report failed: {err:?}\n",
+ self.from_pid,
+ self.from_tid
+ );
+ }
+ }
+
+ fn report_netlink_inner(&self, reply: u32, ctx: &crate::Context) -> kernel::error::Result {
+ if !Report::has_listeners() {
+ return Ok(());
+ }
+ let mut report = Report::new(GENLMSG_DEFAULT_SIZE, 0, 0, GFP_KERNEL)?;
+
+ report.error(reply)?;
+ report.context(&ctx.name)?;
+ report.from_pid(self.from_pid as u32)?;
+ report.from_tid(self.from_tid as u32)?;
+ if self.to_pid != 0 {
+ report.to_pid(self.to_pid as u32)?;
+ }
+ if self.to_tid != 0 {
+ report.to_tid(self.to_tid as u32)?;
+ }
+
+ if self.is_reply {
+ report.is_reply()?;
+ }
+ report.flags(self.flags)?;
+ report.code(self.code)?;
+ report.data_size(self.data_size as u32)?;
+
+ report.multicast(0, GFP_KERNEL)?;
+ Ok(())
+ }
}
use core::mem::offset_of;
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* Re: [PATCH] usb: rtl8150: avoid using uninitialized CSCR value
From: Andrew Lunn @ 2026-04-08 12:26 UTC (permalink / raw)
To: Michal Pecio
Cc: Petko Manolov, Simon Horman, Morduan Zang, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-usb, netdev, linux-kernel, syzbot+9db6c624635564ad813c
In-Reply-To: <20260408103343.19cf599a.michal.pecio@gmail.com>
> BTW, some functions like rtl8150_reset() pre-set data to a value which
> will be safe in case of get_register() failure. But here, unhandled
> set_register() error is dodgy - the 0x10 bit may never turn on.
rtl8150_reset() is not great, it would be better to use something from
iopoll.h, and return ETIMEDOUT, but it does at least have a
timeout. And it looks like all other such loops have some form of
timeout.
Could this driver use some love? Yes. Does it mean not checking for
get_register() failure makes the driver badly broken? Probably not.
Andrew
^ permalink raw reply
* Re: [PATCH net v2] net/sched: cls_fw: fix NULL dereference of "old" filters before change()
From: Jamal Hadi Salim @ 2026-04-08 12:39 UTC (permalink / raw)
To: Davide Caratti
Cc: Jiri Pirko, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Xiang Mei, netdev, Victor Nogueira
In-Reply-To: <adYRpRrnIdUnglAy@dcaratti.users.ipa.redhat.com>
On Wed, Apr 8, 2026 at 4:28 AM Davide Caratti <dcaratti@redhat.com> wrote:
>
> On Fri, Apr 03, 2026 at 02:59:22PM -0400, Jamal Hadi Salim wrote:
> > On Fri, Apr 3, 2026 at 12:04 PM Davide Caratti <dcaratti@redhat.com> wrote:
> > >
> > > Like pointed out by Sashiko [1],
> >
> > Just found out about this ;-> So bye-bye old AI? ;->
> > I must say, finding this specific bug is impressive.
>
> [...]
>
> > Observation on consistency pov:
> > You will always get a "q" when you invoke tcf_block_q() if there are
> > no shared blocks attached. But that doesnt mean the "create"
> > configuration is complete; it is only complete if step 3 above
> > completes. This is because we are not sure if change() will result in
> > "old" or "new" lookup setup. So the check you added for q may be
> > inconsistent from that perspective and fw should have returned -1 like
> > all classifiers...
>
> "Hello Jamal, you are absolutely right! (emoji with fire) However, ..." :)
>
> > I cant think of a simple solution to verify if the config is
> > "inconsistent" other than to add something that gets checked in the
> > datapath (and when absent, return -1)
>
> ^^ This. Specifically for cls_fw, that would mean converting fw_change() to
> allocate some control data also for the "old" uapi, and I think it's too
> much effort for the legacy.
>
> IIUC the inconsistent behavior is: for a small amount of time, fwmark
> classifier used in the "old" way would classify also when the filter's
> 'handle' is not zero.
>
True.
> > From that perspective, your check is not catastrophic, so it may be ok.
> > At minimal the Fixes: needs to change to Vlads commit? Good news is
> > only fw _seems_ to suffer from this challenge...
>
> This is also what I understood by reading the code. Sure, I will edit
> the Fixes: tag. Thanks for reading!
>
If you resubmit please add my acked-by
cheers,
jamal
^ permalink raw reply
* Re: [PATCH v2] net: dsa: microchip: implement KSZ87xx Module 3 low-loss cable errata
From: Andrew Lunn @ 2026-04-08 12:43 UTC (permalink / raw)
To: Fidelio Lawson
Cc: Woojung Huh, UNGLinuxDriver, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Marek Vasut, Maxime Chevallier,
netdev, devicetree, linux-kernel, Fidelio Lawson
In-Reply-To: <20260408-ksz87xx_errata_low_loss_connections-v2-1-9cfe38691713@exotec.com>
> The control register defines the following modes:
> bits [1:0]:
> 00 = workaround disabled
> 01 = workaround 1 (DSP EQ training adjustment, LinkMD reg 0x3c)
> 10 = workaround 2 (receiver LPF bandwidth, LinkMD reg 0x4c)
There was a comment, which i only read after making the suggestion to
use two bits, of exposing the different low pass filter bandwidths,
rather than just picking one value. How useful is that?
Andrew
^ permalink raw reply
* Re: [PATCH v2] net: dsa: microchip: implement KSZ87xx Module 3 low-loss cable errata
From: Andrew Lunn @ 2026-04-08 12:49 UTC (permalink / raw)
To: Fidelio Lawson
Cc: Woojung Huh, UNGLinuxDriver, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Marek Vasut, Maxime Chevallier,
netdev, devicetree, linux-kernel, Fidelio Lawson
In-Reply-To: <20260408-ksz87xx_errata_low_loss_connections-v2-1-9cfe38691713@exotec.com>
> The register can be read and written from userspace via standard Clause 22 PHY
> accesses (for example using phytool) on DSA user ports.
Please add a phy tunable. .get_tunable/.set_tunable.
Andrew
^ permalink raw reply
* Re: [PATCH 2/3] io_uring/rsrc: use io_cache_free for node in io_buffer_register_bvec error path
From: Jens Axboe @ 2026-04-08 12:49 UTC (permalink / raw)
To: Pavel Begunkov, KobaK
Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel
In-Reply-To: <b3ed9023-3211-4f1f-a264-e71df5ba898b@gmail.com>
On 4/8/26 2:35 AM, Pavel Begunkov wrote:
> On 4/8/26 07:54, KobaK wrote:
>> From: Koba Ko <kobak@nvidia.com>
>>
>> io_buffer_register_bvec() allocates the rsrc node via
>> io_rsrc_node_alloc() which pulls from ctx->node_cache. On imu allocation
>> failure, the node is freed with raw kfree() instead of
>> io_cache_free(&ctx->node_cache, node), bypassing the cache return path
>> and wasting a reuse opportunity. Every other error path in this file
>> correctly uses io_cache_free for nodes.
>>
>> Fixes: 27cb27b6d5ea4 ("io_uring: add support for kernel registered bvecs")
>> Signed-off-by: Koba Ko <kobak@nvidia.com>
>> ---
>> io_uring/rsrc.c | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
>> index 1b96ab5e98c99..6f46cf9cd13d7 100644
>> --- a/io_uring/rsrc.c
>> +++ b/io_uring/rsrc.c
>> @@ -961,7 +961,7 @@ int io_buffer_register_bvec(struct io_uring_cmd *cmd, struct request *rq,
>> */
>> imu = io_alloc_imu(ctx, blk_rq_nr_phys_segments(rq));
>> if (!imu) {
>> - kfree(node);
>> + io_cache_free(&ctx->node_cache, node);
>
> Looks like it was already patched a week ago
Indeed, and main motivation was to eliminate reports like this, where
(clearly) an LLM spotted the "problem" even if there is none. Guess that
was a good move, just need it to land so that we don't need to waste
time on this again.
Nobody cares about "wasting a reuse opportunity" in an error path, any
human would recognize that.
--
Jens Axboe
^ permalink raw reply
* Re: [PATCH 0/3] io_uring: fix resource leak issues
From: Jens Axboe @ 2026-04-08 12:51 UTC (permalink / raw)
To: KobaK, Pavel Begunkov
Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel
In-Reply-To: <20260408065408.2017967-1-kobak@nvidia.com>
On 4/8/26 12:54 AM, KobaK wrote:
> From: Koba Ko <kobak@nvidia.com>
>
> Three resource leak fixes found by code audit:
>
> 1. memmap: pinned pages and pages array leak on WARN_ON path in
> io_region_pin_pages() ? mr->pages is never assigned so the caller's
> cleanup is a no-op.
>
> 2. rsrc: kfree() used instead of io_cache_free() in
> io_buffer_register_bvec() error path ? bypasses cache return.
>
> 3. zcrx: io_import_umem() leaves live pinned pages in a partially
> initialized struct on io_account_mem() failure, and
> io_release_area_mem() is not idempotent (missing pages = NULL),
> creating a double-free hazard.
General advice - anyone can point an LLM at a code base and get some
reports, but please apply some actual critical thinking to the "issues"
found before blindly sending them out.
--
Jens Axboe
^ permalink raw reply
* Re: [PATCH net-next v11 05/10] bng_en: add support for link async events
From: Vadim Fedorenko @ 2026-04-08 12:51 UTC (permalink / raw)
To: Bhargava Marreddy, davem, edumazet, kuba, pabeni, andrew+netdev,
horms
Cc: netdev, linux-kernel, michael.chan, pavan.chebbi,
vsrama-krishna.nemani, vikas.gupta, Rajashekar Hudumula,
Ajit Kumar Khaparde
In-Reply-To: <20260403073724.194360-6-bhargava.marreddy@broadcom.com>
On 03/04/2026 08:37, Bhargava Marreddy wrote:
> Register for firmware asynchronous events, including link-status,
> link-speed, and PHY configuration changes. Upon event reception,
> re-query the PHY and update ethtool settings accordingly.
>
> Signed-off-by: Bhargava Marreddy <bhargava.marreddy@broadcom.com>
> Reviewed-by: Vikas Gupta <vikas.gupta@broadcom.com>
> Reviewed-by: Rajashekar Hudumula <rajashekar.hudumula@broadcom.com>
> Reviewed-by: Ajit Kumar Khaparde <ajit.khaparde@broadcom.com>
> ---
> .../ethernet/broadcom/bnge/bnge_hwrm_lib.c | 19 +++++++++-
> .../net/ethernet/broadcom/bnge/bnge_link.c | 18 ++++++++++
> .../net/ethernet/broadcom/bnge/bnge_link.h | 1 +
> .../net/ethernet/broadcom/bnge/bnge_netdev.c | 23 ++++++++++++
> .../net/ethernet/broadcom/bnge/bnge_txrx.c | 35 ++++++++++++++++---
> 5 files changed, 91 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
> index acb712ddbd7e..de51add96ac3 100644
> --- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
> +++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
> @@ -15,6 +15,13 @@
> #include "bnge_rmem.h"
> #include "bnge_resc.h"
>
> +static const u16 bnge_async_events_arr[] = {
> + ASYNC_EVENT_CMPL_EVENT_ID_LINK_STATUS_CHANGE,
> + ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CHANGE,
> + ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CFG_CHANGE,
> + ASYNC_EVENT_CMPL_EVENT_ID_PORT_PHY_CFG_CHANGE,
> +};
> +
> int bnge_hwrm_ver_get(struct bnge_dev *bd)
> {
> u32 dev_caps_cfg, hwrm_ver, hwrm_spec_code;
> @@ -166,10 +173,12 @@ int bnge_hwrm_fw_set_time(struct bnge_dev *bd)
>
> int bnge_hwrm_func_drv_rgtr(struct bnge_dev *bd)
> {
> + DECLARE_BITMAP(async_events_bmap, 256);
> struct hwrm_func_drv_rgtr_output *resp;
> struct hwrm_func_drv_rgtr_input *req;
> + u32 events[8];
> u32 flags;
> - int rc;
> + int rc, i;
>
> rc = bnge_hwrm_req_init(bd, req, HWRM_FUNC_DRV_RGTR);
> if (rc)
> @@ -190,6 +199,14 @@ int bnge_hwrm_func_drv_rgtr(struct bnge_dev *bd)
> req->ver_min = cpu_to_le16(DRV_VER_MIN);
> req->ver_upd = cpu_to_le16(DRV_VER_UPD);
>
> + memset(async_events_bmap, 0, sizeof(async_events_bmap));
bitmap API has bitmap_zero()
> + for (i = 0; i < ARRAY_SIZE(bnge_async_events_arr); i++)
> + __set_bit(bnge_async_events_arr[i], async_events_bmap);
> +
> + bitmap_to_arr32(events, async_events_bmap, 256);
> + for (i = 0; i < ARRAY_SIZE(req->async_event_fwd); i++)
> + req->async_event_fwd[i] |= cpu_to_le32(events[i]);
> +
> resp = bnge_hwrm_req_hold(bd, req);
> rc = bnge_hwrm_req_send(bd, req);
> if (!rc) {
^ permalink raw reply
* [PATCH v5] net: caif: fix stack out-of-bounds write in cfctrl_link_setup()
From: Kangzheng Gu @ 2026-04-08 12:53 UTC (permalink / raw)
To: pabeni, davem, edumazet, kuba, horms, kees, thorsten.blum, arnd,
sjur.brandeland, xiaoguai0992
Cc: netdev, linux-kernel, stable
In-Reply-To: <0f9e9d4e-8083-4297-91d3-10d0f614c87c@redhat.com>
cfctrl_link_setup() copies the RFM volume name from a received control
packet into linkparam.u.rfm.volume until a '\0' is found. A malformed
packet can omit the terminator and make the copy run past the 20-byte
stack buffer.
Stop copying once the buffer is full and mark the frame as failed by
setting CFCTRL_ERR_BIT so the link setup is rejected.
Fixes: b482cd2053e3 ("net-caif: add CAIF core protocol stack")
Cc: stable@vger.kernel.org
Signed-off-by: Kangzheng Gu <xiaoguai0992@gmail.com>
---
v5:
- remove the Reported-by.
- print a warn message and reject link setup by setting CFCTRL_ERR_BIT.
- using %zu to adapt the compilation of 32-bit kernel.
- add rate limit to error message
net/caif/cfctrl.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/net/caif/cfctrl.c b/net/caif/cfctrl.c
index c6cc2bfed65d..373ab1dc67a7 100644
--- a/net/caif/cfctrl.c
+++ b/net/caif/cfctrl.c
@@ -416,8 +416,16 @@ static int cfctrl_link_setup(struct cfctrl *cfctrl, struct cfpkt *pkt, u8 cmdrsp
cp = (u8 *) linkparam.u.rfm.volume;
for (tmp = cfpkt_extr_head_u8(pkt);
cfpkt_more(pkt) && tmp != '\0';
- tmp = cfpkt_extr_head_u8(pkt))
+ tmp = cfpkt_extr_head_u8(pkt)) {
+ if (cp >= (u8 *)linkparam.u.rfm.volume +
+ sizeof(linkparam.u.rfm.volume) - 1) {
+ pr_warn_ratelimited("Request reject, volume name length exceeds %zu\n",
+ sizeof(linkparam.u.rfm.volume));
+ cmdrsp |= CFCTRL_ERR_BIT;
+ break;
+ }
*cp++ = tmp;
+ }
*cp = '\0';
if (CFCTRL_ERR_BIT & cmdrsp)
--
2.50.1
^ permalink raw reply related
* Re: [RFC PATCH 0/1] netlink: Netlink process event for cgroup migration
From: Michal Koutný @ 2026-04-08 12:54 UTC (permalink / raw)
To: Prakash Sangappa
Cc: linux-kernel, netdev, cgroups, davem, kuba, edumazet, tj, hannes,
tom.hromatka, kamalesh.babulal, Christian Brauner
In-Reply-To: <20260407172339.2017158-1-prakash.sangappa@oracle.com>
[-- Attachment #1: Type: text/plain, Size: 1142 bytes --]
Hi Prakash.
On Tue, Apr 07, 2026 at 05:23:38PM +0000, Prakash Sangappa <prakash.sangappa@oracle.com> wrote:
> With cgroup based resource management, it becomes useful for
> userspace to be notified when a task changes cgroup membership.
> Unexpected migrations can lead to incorrect resource accounting
> and enforcement resulting in undesirable behavior or failures.
> Applications/userspace have to poll /proc to detect changes to
> cgroup membership, which is inefficient when dealing with a large
> number of tasks.
You may want to check [1] (and followup discussion).
> Add a new netlink proc connector event that gets generated when
> a task migrates between cgroups. This allows applications/tools
> to monitor cgroup membership changes without periodic polling.
This CN_IDX_PROC netlink API haunts me at night.
The hook(s) proposed above are IMO more future proof and robust approach
to the process migration that comes as a surprise (and possibly
interferes with intended resource management).
Thanks,
Michal
[1] https://lore.kernel.org/all/20260220-work-bpf-namespace-v1-2-866207db7b83@kernel.org/
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 265 bytes --]
^ permalink raw reply
* [PATCH net-next 00/15] net/sched: no longer acquire RTNL in qdisc dumps
From: Eric Dumazet @ 2026-04-08 12:55 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, Kuniyuki Iwashima,
netdev, eric.dumazet, Eric Dumazet
This (large) series bring RTNL avoidance to qdisc dumps.
We first add annotations for data-races, so that most dump methods
can run in parallel with data path.
Then change mq and mqprio to no longer acquire each children
qdisc spinlock.
Last patch replaces RTNL with RCU for tc_dump_qdisc() and the
qdisc ops->dump() methods.
Series was too big, RTNL avoidance for class dumps will be done later.
Eric Dumazet (15):
net/sched: rename qstats_overlimit_inc() to qstats_cpu_overlimit_inc()
net/sched: add qstats_cpu_drop_inc() helper
net/sched: add READ_ONCE() in gnet_stats_add_queue[_cpu]
net/sched: add qdisc_qlen_inc() and qdisc_qlen_dec()
net/sched: annotate data-races around sch->qstats.backlog
net/sched: sch_sfb: annotate data-races in sfb_dump_stats()
net/sched: sch_red: annotate data-races in red_dump_stats()
net/sched: sch_fq_codel: remove data-races from fq_codel_dump_stats()
net/sched: sch_pie: annotate data-races in pie_dump_stats()
net/sched: sch_fq_pie: annotate data-races in fq_pie_dump_stats()
net_sched: sch_hhf: annotate data-races in hhf_dump_stats()
net/sched: sch_choke: annotate data-races in choke_dump_stats()
net/sched: sch_cake: annotate data-races in cake_dump_stats()
net/sched: mq: no longer acquire qdisc spinlocks in dump operations
net/sched: convert tc_dump_qdisc() to RCU
include/net/act_api.h | 4 +-
include/net/gen_stats.h | 9 +-
include/net/pie.h | 2 +-
include/net/sch_generic.h | 75 ++++++--
net/core/gen_estimator.c | 24 ++-
net/core/gen_stats.c | 42 ++---
net/sched/act_api.c | 2 +-
net/sched/act_bpf.c | 2 +-
net/sched/act_ife.c | 12 +-
net/sched/act_mpls.c | 2 +-
net/sched/act_police.c | 4 +-
net/sched/act_skbedit.c | 2 +-
net/sched/act_skbmod.c | 2 +-
net/sched/sch_api.c | 25 ++-
net/sched/sch_cake.c | 353 +++++++++++++++++++++-----------------
net/sched/sch_cbs.c | 6 +-
net/sched/sch_choke.c | 34 ++--
net/sched/sch_codel.c | 2 +-
net/sched/sch_drr.c | 6 +-
net/sched/sch_dualpi2.c | 4 +-
net/sched/sch_etf.c | 8 +-
net/sched/sch_ets.c | 6 +-
net/sched/sch_fq.c | 8 +-
net/sched/sch_fq_codel.c | 16 +-
net/sched/sch_fq_pie.c | 31 ++--
net/sched/sch_generic.c | 8 +-
net/sched/sch_gred.c | 4 +-
net/sched/sch_hfsc.c | 6 +-
net/sched/sch_hhf.c | 26 +--
net/sched/sch_htb.c | 6 +-
net/sched/sch_mq.c | 30 ++--
net/sched/sch_mqprio.c | 69 ++++----
net/sched/sch_multiq.c | 4 +-
net/sched/sch_netem.c | 12 +-
net/sched/sch_pie.c | 38 ++--
net/sched/sch_prio.c | 6 +-
net/sched/sch_qfq.c | 8 +-
net/sched/sch_red.c | 37 ++--
net/sched/sch_sfb.c | 54 +++---
net/sched/sch_sfq.c | 9 +-
net/sched/sch_skbprio.c | 4 +-
net/sched/sch_taprio.c | 20 +--
net/sched/sch_tbf.c | 10 +-
net/sched/sch_teql.c | 2 +-
44 files changed, 571 insertions(+), 463 deletions(-)
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply
* [PATCH net-next 01/15] net/sched: rename qstats_overlimit_inc() to qstats_cpu_overlimit_inc()
From: Eric Dumazet @ 2026-04-08 12:55 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, Kuniyuki Iwashima,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260408125611.3592751-1-edumazet@google.com>
qstats_overlimit_inc() is only used to increment per cpu overlimits.
It can use this_cpu_inc() to avoid this_cpu_ptr() extra cost
and avoid potential store tearing.
Change qstats_overlimit_inc() name and its argument type.
Also add a WRITE_ONCE() in qdisc_qstats_overlimit() to prevent
store tearing.
$ scripts/bloat-o-meter -t vmlinux.0 vmlinux.1
add/remove: 0/0 grow/shrink: 0/5 up/down: 0/-72 (-72)
Function old new delta
tcf_skbmod_act 772 764 -8
tcf_police_act 733 725 -8
tcf_mirred_to_dev 1126 1114 -12
tcf_ife_act 1077 1061 -16
tcf_mirred_act 1324 1296 -28
Total: Before=29610901, After=29610829, chg -0.00%
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/act_api.h | 2 +-
include/net/sch_generic.h | 6 +++---
net/sched/act_ife.c | 4 ++--
net/sched/act_police.c | 2 +-
net/sched/act_skbmod.c | 2 +-
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/include/net/act_api.h b/include/net/act_api.h
index d11b791079302f50c47e174979767e0b24afc59a..2ec4ef9a5d0c8e9110f92f135cc3c31a38af0479 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -250,7 +250,7 @@ static inline void tcf_action_inc_drop_qstats(struct tc_action *a)
static inline void tcf_action_inc_overlimit_qstats(struct tc_action *a)
{
if (likely(a->cpu_qstats)) {
- qstats_overlimit_inc(this_cpu_ptr(a->cpu_qstats));
+ qstats_cpu_overlimit_inc(a->cpu_qstats);
return;
}
atomic_inc(&a->tcfa_overlimits);
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index 5af262ec4bbd2d5021904df127a849e52c26178a..3ee383c6fc3f66f1aecd9ebc675fbd143852c150 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -1004,9 +1004,9 @@ static inline void qstats_drop_inc(struct gnet_stats_queue *qstats)
qstats->drops++;
}
-static inline void qstats_overlimit_inc(struct gnet_stats_queue *qstats)
+static inline void qstats_cpu_overlimit_inc(struct gnet_stats_queue __percpu *qstats)
{
- qstats->overlimits++;
+ this_cpu_inc(qstats->overlimits);
}
static inline void qdisc_qstats_drop(struct Qdisc *sch)
@@ -1021,7 +1021,7 @@ static inline void qdisc_qstats_cpu_drop(struct Qdisc *sch)
static inline void qdisc_qstats_overlimit(struct Qdisc *sch)
{
- sch->qstats.overlimits++;
+ WRITE_ONCE(sch->qstats.overlimits, sch->qstats.overlimits + 1);
}
static inline int qdisc_qstats_copy(struct gnet_dump *d, struct Qdisc *sch)
diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c
index d5e8a91bb4eb9f1f1f084e199b5ada4e7f7e7205..e1b825e14900d6f46bbfd1b7f72ab6cd554d8a73 100644
--- a/net/sched/act_ife.c
+++ b/net/sched/act_ife.c
@@ -750,7 +750,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
*/
pr_info_ratelimited("Unknown metaid %d dlen %d\n",
mtype, dlen);
- qstats_overlimit_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_overlimit_inc(ife->common.cpu_qstats);
}
}
@@ -814,7 +814,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a,
/* abuse overlimits to count when we allow packet
* with no metadata
*/
- qstats_overlimit_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_overlimit_inc(ife->common.cpu_qstats);
return action;
}
/* could be stupid policy setup or mtu config
diff --git a/net/sched/act_police.c b/net/sched/act_police.c
index 12ea9e5a600536b603ea73cc99b4c00381287219..8060f43e4d11c0a26e1475db06b76426f50c5975 100644
--- a/net/sched/act_police.c
+++ b/net/sched/act_police.c
@@ -307,7 +307,7 @@ TC_INDIRECT_SCOPE int tcf_police_act(struct sk_buff *skb,
}
inc_overlimits:
- qstats_overlimit_inc(this_cpu_ptr(police->common.cpu_qstats));
+ qstats_cpu_overlimit_inc(police->common.cpu_qstats);
inc_drops:
if (ret == TC_ACT_SHOT)
qstats_drop_inc(this_cpu_ptr(police->common.cpu_qstats));
diff --git a/net/sched/act_skbmod.c b/net/sched/act_skbmod.c
index 23ca46138f040d38de37684439873921bc9c86af..a464b0a3c1b81dba6c28c1141aa38c5c7cad3acb 100644
--- a/net/sched/act_skbmod.c
+++ b/net/sched/act_skbmod.c
@@ -87,7 +87,7 @@ TC_INDIRECT_SCOPE int tcf_skbmod_act(struct sk_buff *skb,
return p->action;
drop:
- qstats_overlimit_inc(this_cpu_ptr(d->common.cpu_qstats));
+ qstats_cpu_overlimit_inc(d->common.cpu_qstats);
return TC_ACT_SHOT;
}
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH net-next 02/15] net/sched: add qstats_cpu_drop_inc() helper
From: Eric Dumazet @ 2026-04-08 12:55 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, Kuniyuki Iwashima,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260408125611.3592751-1-edumazet@google.com>
1) Using this_cpu_inc() is better than going through this_cpu_ptr():
- Single instruction on x86.
- Store tearing prevention.
2) Change tcf_action_update_stats() to use this_cpu_add().
3) Add WRITE_ONCE() to __qdisc_qstats_drop() and qstats_drop_inc().
$ scripts/bloat-o-meter -t vmlinux.1 vmlinux.2
add/remove: 0/0 grow/shrink: 1/10 up/down: 16/-97 (-81)
Function old new delta
tcf_ife_act 1061 1077 +16
tcf_vlan_act 684 676 -8
tcf_skbedit_act 626 618 -8
tcf_police_act 725 717 -8
tcf_mpls_act 1297 1289 -8
tcf_gate_act 310 302 -8
tcf_gact_act 195 187 -8
tcf_csum_act 2905 2897 -8
tcf_bpf_act 749 741 -8
tcf_action_update_stats 124 115 -9
tcf_ct_act 2154 2130 -24
Total: Before=29739602, After=29739521, chg -0.00%
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/act_api.h | 2 +-
include/net/sch_generic.h | 9 +++++++--
net/sched/act_api.c | 2 +-
net/sched/act_bpf.c | 2 +-
net/sched/act_ife.c | 8 ++++----
net/sched/act_mpls.c | 2 +-
net/sched/act_police.c | 2 +-
net/sched/act_skbedit.c | 2 +-
8 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/include/net/act_api.h b/include/net/act_api.h
index 2ec4ef9a5d0c8e9110f92f135cc3c31a38af0479..167435c5615e09f491a05d01ec86b0c9f9f4fd5b 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -241,7 +241,7 @@ static inline void tcf_action_update_bstats(struct tc_action *a,
static inline void tcf_action_inc_drop_qstats(struct tc_action *a)
{
if (likely(a->cpu_qstats)) {
- qstats_drop_inc(this_cpu_ptr(a->cpu_qstats));
+ qstats_cpu_drop_inc(a->cpu_qstats);
return;
}
atomic_inc(&a->tcfa_drops);
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index 3ee383c6fc3f66f1aecd9ebc675fbd143852c150..b22579671e4b4dd04c5dfa810b714daaac74af2a 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -996,12 +996,17 @@ static inline void qdisc_qstats_cpu_requeues_inc(struct Qdisc *sch)
static inline void __qdisc_qstats_drop(struct Qdisc *sch, int count)
{
- sch->qstats.drops += count;
+ WRITE_ONCE(sch->qstats.drops, sch->qstats.drops + count);
}
static inline void qstats_drop_inc(struct gnet_stats_queue *qstats)
{
- qstats->drops++;
+ WRITE_ONCE(qstats->drops, qstats->drops + 1);
+}
+
+static inline void qstats_cpu_drop_inc(struct gnet_stats_queue __percpu *qstats)
+{
+ this_cpu_inc(qstats->drops);
}
static inline void qstats_cpu_overlimit_inc(struct gnet_stats_queue __percpu *qstats)
diff --git a/net/sched/act_api.c b/net/sched/act_api.c
index 332fd9695e54a1fc63bb869c28cacf5f2ed14971..551992683d9e69c247b8d9c613a69e2a897a1e79 100644
--- a/net/sched/act_api.c
+++ b/net/sched/act_api.c
@@ -1578,7 +1578,7 @@ void tcf_action_update_stats(struct tc_action *a, u64 bytes, u64 packets,
if (a->cpu_bstats) {
_bstats_update(this_cpu_ptr(a->cpu_bstats), bytes, packets);
- this_cpu_ptr(a->cpu_qstats)->drops += drops;
+ this_cpu_add(a->cpu_qstats->drops, drops);
if (hw)
_bstats_update(this_cpu_ptr(a->cpu_bstats_hw),
diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c
index c2b5bc19e09118857d1ef3c4aed566b8225f2e9a..58a074651176730fd1bd370ba8420dfbed0d4e9c 100644
--- a/net/sched/act_bpf.c
+++ b/net/sched/act_bpf.c
@@ -76,7 +76,7 @@ TC_INDIRECT_SCOPE int tcf_bpf_act(struct sk_buff *skb,
break;
case TC_ACT_SHOT:
action = filter_res;
- qstats_drop_inc(this_cpu_ptr(prog->common.cpu_qstats));
+ qstats_cpu_drop_inc(prog->common.cpu_qstats);
break;
case TC_ACT_UNSPEC:
action = prog->tcf_action;
diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c
index e1b825e14900d6f46bbfd1b7f72ab6cd554d8a73..065228026c58eb0f8ff3b3a08758e4ef0d6ea708 100644
--- a/net/sched/act_ife.c
+++ b/net/sched/act_ife.c
@@ -727,7 +727,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
tlv_data = ife_decode(skb, &metalen);
if (unlikely(!tlv_data)) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -740,7 +740,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype,
&dlen, NULL);
if (!curr_data) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -755,7 +755,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
}
if (WARN_ON(tlv_data != ifehdr_end)) {
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
@@ -821,7 +821,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a,
* so lets be conservative.. */
if ((action == TC_ACT_SHOT) || exceed_mtu) {
drop:
- qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats));
+ qstats_cpu_drop_inc(ife->common.cpu_qstats);
return TC_ACT_SHOT;
}
diff --git a/net/sched/act_mpls.c b/net/sched/act_mpls.c
index 1abfaf9d99f1fce0fe7cafa2a9e35c80a3969ce7..4ea8b2e08c3a4dddfe1670af72a5d487a5219f5e 100644
--- a/net/sched/act_mpls.c
+++ b/net/sched/act_mpls.c
@@ -123,7 +123,7 @@ TC_INDIRECT_SCOPE int tcf_mpls_act(struct sk_buff *skb,
return p->action;
drop:
- qstats_drop_inc(this_cpu_ptr(m->common.cpu_qstats));
+ qstats_cpu_drop_inc(m->common.cpu_qstats);
return TC_ACT_SHOT;
}
diff --git a/net/sched/act_police.c b/net/sched/act_police.c
index 8060f43e4d11c0a26e1475db06b76426f50c5975..b16468a98c55e32260e8d4cb1fe3d771fca65120 100644
--- a/net/sched/act_police.c
+++ b/net/sched/act_police.c
@@ -310,7 +310,7 @@ TC_INDIRECT_SCOPE int tcf_police_act(struct sk_buff *skb,
qstats_cpu_overlimit_inc(police->common.cpu_qstats);
inc_drops:
if (ret == TC_ACT_SHOT)
- qstats_drop_inc(this_cpu_ptr(police->common.cpu_qstats));
+ qstats_cpu_drop_inc(police->common.cpu_qstats);
end:
return ret;
}
diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c
index a778cdba9258c2c776ee5ba0751cca1b73c984df..bfec6b66841031cd566d0c2bdc3d120cec41e3e4 100644
--- a/net/sched/act_skbedit.c
+++ b/net/sched/act_skbedit.c
@@ -86,7 +86,7 @@ TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb,
return params->action;
err:
- qstats_drop_inc(this_cpu_ptr(d->common.cpu_qstats));
+ qstats_cpu_drop_inc(d->common.cpu_qstats);
return TC_ACT_SHOT;
}
--
2.53.0.1213.gd9a14994de-goog
^ 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