* [PATCH net-next 0/2] net: airoha: Add XDP support
@ 2026-09-20 15:43 Til Kaiser
2026-09-20 15:43 ` [PATCH net-next 1/2] " Til Kaiser
2026-09-20 15:43 ` [PATCH net-next 2/2] net: airoha: Add XDP statistics Til Kaiser
0 siblings, 2 replies; 5+ messages in thread
From: Til Kaiser @ 2026-09-20 15:43 UTC (permalink / raw)
To: netdev
Cc: lorenzo, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, linux-mediatek, linux-kernel, Til Kaiser
Hello,
This series adds XDP support to the Airoha Ethernet driver.
Patch 1 adds the RX- and TX-path support needed for XDP_PASS, XDP_DROP,
XDP_TX, and XDP_REDIRECT.
Patch 2 exposes XDP and page-pool statistics through ethtool.
Thanks,
Til
Til Kaiser (2):
net: airoha: Add XDP support
net: airoha: Add XDP statistics
drivers/net/ethernet/airoha/Kconfig | 1 +
drivers/net/ethernet/airoha/airoha_eth.c | 493 +++++++++++++++++++++--
drivers/net/ethernet/airoha/airoha_eth.h | 32 +-
3 files changed, 496 insertions(+), 30 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net-next 1/2] net: airoha: Add XDP support
2026-09-20 15:43 [PATCH net-next 0/2] net: airoha: Add XDP support Til Kaiser
@ 2026-09-20 15:43 ` Til Kaiser
2026-09-21 7:30 ` Lorenzo Bianconi
2026-09-20 15:43 ` [PATCH net-next 2/2] net: airoha: Add XDP statistics Til Kaiser
1 sibling, 1 reply; 5+ messages in thread
From: Til Kaiser @ 2026-09-20 15:43 UTC (permalink / raw)
To: netdev
Cc: lorenzo, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, linux-mediatek, linux-kernel, Til Kaiser
Implement eXpress Data Path (XDP) support for the Airoha Ethernet driver.
XDP programs are attached per net_device via the ndo_bpf hook. The BPF
program reference is stored as an RCU-protected pointer in struct
airoha_gdm_dev and replaced atomically on program load/unload via
rcu_replace_pointer().
To support XDP, the following changes are made to the RX path:
- The page pool DMA direction is switched from DMA_FROM_DEVICE to
DMA_BIDIRECTIONAL, which is required for XDP_TX since the same page
can be reused for transmit.
- The RX headroom is increased from NET_SKB_PAD to XDP_PACKET_HEADROOM
so that XDP programs have the required headroom available in the data
buffer.
- RX buffers use a full page rather than a half-page fragment. This leaves
sufficient space for XDP headroom, skb_shared_info tailroom, and a
standard 1500-byte MTU.
- AIROHA_RX_MAX_BUF_SIZE expresses the maximum MTU that fits in an RX
buffer after headroom, shared-info, Ethernet, VLAN, and FCS overhead.
MTU changes are rejected when an XDP program is loaded and the requested
MTU would exceed this limit.
- xdp_rxq_info is registered for each RX queue during initialisation and
unregistered on cleanup, with the page pool set as the memory model.
The XDP program is run in airoha_run_xdp() for each received buffer.
The following actions are supported:
- XDP_PASS: the buffer is passed to the normal networking stack.
- XDP_TX: the buffer is transmitted back out the same interface using
the new airoha_xdp_xmit_back() helper.
- XDP_REDIRECT: the buffer is redirected to another interface or map
via xdp_do_redirect(), with xdp_do_flush() deferred to the end of
the NAPI poll via a per-queue xdp_flush flag.
- XDP_DROP (and unknown actions): the page is returned to the page pool.
Two new TX path helpers are introduced:
- airoha_xdp_submit_frame() maps an xdp_frame for DMA (or reuses the
page pool DMA address for XDP_TX) and enqueues it into a TX ring,
handling multi-buffer frames via skb_shared_info fragments.
- airoha_xdp_xmit_back() converts an xdp_buff to an xdp_frame and
submits it to the TX queue corresponding to the current CPU.
The ndo_xdp_xmit handler airoha_xdp_xmit() is also added to allow
XDP redirect from other drivers into the Airoha interface. Frames are
submitted using airoha_xdp_submit_frame() with dma_map=true.
struct airoha_queue_entry is extended with an is_xdpf flag and an
xdp_frame pointer (in a union with the existing sk_buff pointer) so
that the TX completion path can correctly free either XDP frames or
SKBs.
The advertised XDP feature flags are set to:
NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_NDO_XMIT
Signed-off-by: Til Kaiser <mail@tk154.de>
---
drivers/net/ethernet/airoha/airoha_eth.c | 386 +++++++++++++++++++++--
drivers/net/ethernet/airoha/airoha_eth.h | 19 +-
2 files changed, 375 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 64619e9a704d..71b25f225a8a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -13,6 +13,7 @@
#include <net/page_pool/helpers.h>
#include <net/pkt_cls.h>
#include <uapi/linux/ppp_defs.h>
+#include <linux/bpf_trace.h>
#include "airoha_regs.h"
#include "airoha_eth.h"
@@ -657,6 +658,255 @@ airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)
return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
}
+static void airoha_unmap_xmit_buf(struct airoha_eth *eth,
+ struct airoha_queue_entry *e)
+{
+ switch (e->dma_type) {
+ case AIROHA_DMA_MAP_PAGE:
+ dma_unmap_page(eth->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ break;
+ case AIROHA_DMA_MAP_SINGLE:
+ dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ break;
+ case AIROHA_DMA_UNMAPPED:
+ default:
+ break;
+ }
+ e->dma_type = AIROHA_DMA_UNMAPPED;
+}
+
+static int airoha_xdp_submit_frame(struct airoha_gdm_dev *dev,
+ struct xdp_frame *xdpf,
+ struct airoha_queue *q,
+ int qid, u32 msg0, u32 msg1,
+ bool dma_map)
+{
+ struct airoha_queue_entry *e, *next_e;
+ int len = xdpf->len, nr_frags, i;
+ struct airoha_qdma_desc *desc;
+ struct skb_shared_info *sinfo;
+ void *data = xdpf->data;
+ u16 index, next_index;
+ LIST_HEAD(tx_list);
+ dma_addr_t addr;
+ u32 val;
+
+ sinfo = xdp_get_shared_info_from_frame(xdpf);
+ nr_frags = unlikely(xdp_frame_has_frags(xdpf)) ? sinfo->nr_frags : 0;
+
+ if (q->queued >= q->ndesc - 1 - nr_frags)
+ return -EBUSY;
+
+ for (i = 0; i <= nr_frags; i++) {
+ if (dma_map) {
+ if (i == 0)
+ addr = dma_map_single(dev->eth->dev, data, len,
+ DMA_TO_DEVICE);
+ else
+ addr = dma_map_page(dev->eth->dev, virt_to_page(data),
+ offset_in_page(data), len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev->eth->dev, addr)))
+ goto unmap;
+ } else {
+ struct page *page = virt_to_head_page(data);
+
+ addr = page_pool_get_dma_addr(page) +
+ (data - (void *)page_address(page));
+ dma_sync_single_for_device(dev->eth->dev, addr, len,
+ DMA_BIDIRECTIONAL);
+ }
+
+ e = list_first_entry(&q->tx_list, struct airoha_queue_entry, list);
+ list_move_tail(&e->list, &tx_list);
+
+ index = e - q->entry;
+ desc = &q->desc[index];
+
+ e->is_xdpf = true;
+ e->xdpf = (i == nr_frags) ? xdpf : NULL;
+ e->dma_addr = addr;
+ e->dma_len = len;
+ if (dma_map)
+ e->dma_type = i == 0 ? AIROHA_DMA_MAP_SINGLE : AIROHA_DMA_MAP_PAGE;
+ else
+ e->dma_type = AIROHA_DMA_UNMAPPED;
+
+ next_e = list_first_entry(&q->tx_list, struct airoha_queue_entry, list);
+ next_index = next_e - q->entry;
+
+ val = FIELD_PREP(QDMA_DESC_LEN_MASK, len);
+ if (i < nr_frags)
+ val |= FIELD_PREP(QDMA_DESC_MORE_MASK, 1);
+ WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
+ WRITE_ONCE(desc->addr, cpu_to_le32(addr));
+ val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, next_index);
+ WRITE_ONCE(desc->data, cpu_to_le32(val));
+ WRITE_ONCE(desc->msg0, cpu_to_le32(msg0));
+ WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
+ WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
+
+ q->queued++;
+
+ if (i < nr_frags) {
+ skb_frag_t *frag = &sinfo->frags[i];
+
+ data = skb_frag_address(frag);
+ len = skb_frag_size(frag);
+ }
+ }
+
+ return next_index;
+
+unmap:
+ list_for_each_entry(e, &tx_list, list) {
+ airoha_unmap_xmit_buf(dev->eth, e);
+ e->is_xdpf = false;
+ e->xdpf = NULL;
+ q->queued--;
+ }
+ list_splice(&tx_list, &q->tx_list);
+
+ return -ENOMEM;
+}
+
+static int airoha_xdp_xmit_back(struct net_device *netdev, struct xdp_buff *xdp,
+ struct airoha_qdma *qdma)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_queue *q;
+ struct xdp_frame *xdpf;
+ u32 msg0, msg1;
+ int qid, index;
+ u8 fport;
+
+ xdpf = xdp_convert_buff_to_frame(xdp);
+ if (unlikely(!xdpf))
+ return -EOVERFLOW;
+
+ qid = airoha_qdma_get_txq(qdma, smp_processor_id());
+ q = &qdma->q_tx[qid];
+
+ msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+ qid / AIROHA_NUM_QOS_QUEUES) |
+ FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
+ qid % AIROHA_NUM_QOS_QUEUES);
+
+ fport = airoha_get_fe_port(dev);
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
+ FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+ spin_lock(&q->lock);
+ index = airoha_xdp_submit_frame(dev, xdpf, q, qid, msg0, msg1, false);
+ if (unlikely(index < 0)) {
+ spin_unlock(&q->lock);
+ return index;
+ }
+
+ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
+ TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
+ spin_unlock(&q->lock);
+
+ return 0;
+}
+
+static int airoha_xdp_xmit(struct net_device *netdev, int n,
+ struct xdp_frame **frames, u32 flags)
+{
+ int qid, index = 0, last_index = -1, i, drops = 0;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_qdma *qdma;
+ struct airoha_queue *q;
+ u32 msg0, msg1;
+ u8 fport;
+
+ if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+ return -EINVAL;
+
+ rcu_read_lock();
+ qdma = rcu_dereference(dev->qdma);
+ if (!qdma) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+
+ qid = airoha_qdma_get_txq(qdma, smp_processor_id());
+ q = &qdma->q_tx[qid];
+
+ msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+ qid / AIROHA_NUM_QOS_QUEUES) |
+ FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
+ qid % AIROHA_NUM_QOS_QUEUES);
+
+ fport = airoha_get_fe_port(dev);
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
+ FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+ spin_lock(&q->lock);
+ for (i = 0; i < n; i++) {
+ index = airoha_xdp_submit_frame(dev, frames[i], q, qid, msg0, msg1, true);
+ if (unlikely(index < 0)) {
+ xdp_return_frame_rx_napi(frames[i]);
+ drops++;
+ } else {
+ last_index = index;
+ }
+ }
+ if (n > drops && (flags & XDP_XMIT_FLUSH))
+ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
+ TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, last_index));
+ spin_unlock(&q->lock);
+ rcu_read_unlock();
+
+ return n - drops;
+}
+
+static bool airoha_run_xdp(struct net_device *netdev, struct bpf_prog *prog,
+ struct xdp_buff *xdp, struct airoha_queue *q,
+ struct airoha_queue_entry *e, struct page *page)
+{
+ u32 act = bpf_prog_run_xdp(prog, xdp);
+
+ switch (act) {
+ case XDP_PASS:
+ return false;
+ case XDP_TX:
+ if (unlikely(airoha_xdp_xmit_back(netdev, xdp, q->qdma) < 0)) {
+ trace_xdp_exception(netdev, prog, act);
+ page_pool_put_full_page(q->page_pool, page, true);
+ } else {
+ e->buf = NULL;
+ }
+ break;
+ case XDP_REDIRECT:
+ if (unlikely(xdp_do_redirect(netdev, xdp, prog) < 0)) {
+ trace_xdp_exception(netdev, prog, act);
+ page_pool_put_full_page(q->page_pool, page, true);
+ } else {
+ q->xdp_flush = true;
+ e->buf = NULL;
+ }
+ break;
+ default:
+ bpf_warn_invalid_xdp_action(netdev, prog, act);
+ fallthrough;
+ case XDP_ABORTED:
+ trace_xdp_exception(netdev, prog, act);
+ fallthrough;
+ case XDP_DROP:
+ page_pool_put_full_page(q->page_pool, page, true);
+ break;
+ }
+
+ return true;
+}
+
static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
{
enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
@@ -698,6 +948,27 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
netdev = netdev_from_priv(dev);
if (!q->skb) { /* first buffer */
+ struct bpf_prog *xdp_prog;
+
+ rcu_read_lock();
+ xdp_prog = rcu_dereference(dev->xdp_prog);
+ if (xdp_prog) {
+ struct xdp_buff xdp;
+
+ xdp_init_buff(&xdp, q->buf_size, &q->xdp_rxq);
+ xdp_prepare_buff(&xdp, e->buf - AIROHA_RX_HEADROOM,
+ AIROHA_RX_HEADROOM, len, false);
+
+ if (airoha_run_xdp(netdev, xdp_prog, &xdp, q, e, page)) {
+ rcu_read_unlock();
+ continue;
+ }
+
+ len = xdp.data_end - xdp.data;
+ e->buf = xdp.data;
+ }
+ rcu_read_unlock();
+
q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
q->buf_size);
if (!q->skb)
@@ -779,6 +1050,11 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
done += cur;
} while (cur && done < budget);
+ if (q->xdp_flush) {
+ xdp_do_flush();
+ q->xdp_flush = false;
+ }
+
if (done < budget && napi_complete(napi)) {
struct airoha_qdma *qdma = q->qdma;
int i, qid = q - &qdma->q_rx[0];
@@ -804,17 +1080,17 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
.order = 0,
.pool_size = 256,
.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
- .dma_dir = DMA_FROM_DEVICE,
+ .dma_dir = DMA_BIDIRECTIONAL,
.max_len = PAGE_SIZE,
.nid = NUMA_NO_NODE,
.dev = qdma->eth->dev,
.napi = &q->napi,
};
+ int qid = q - &qdma->q_rx[0], thr, err;
struct airoha_eth *eth = qdma->eth;
- int qid = q - &qdma->q_rx[0], thr;
dma_addr_t dma_addr;
- q->buf_size = PAGE_SIZE / 2;
+ q->buf_size = PAGE_SIZE;
q->qdma = qdma;
q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
@@ -829,15 +1105,22 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
q->page_pool = page_pool_create(&pp_params);
if (IS_ERR(q->page_pool)) {
- int err = PTR_ERR(q->page_pool);
-
- q->page_pool = NULL;
- return err;
+ err = PTR_ERR(q->page_pool);
+ goto err_page_pool_create;
}
q->ndesc = ndesc;
netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
+ err = xdp_rxq_info_reg(&q->xdp_rxq, eth->napi_dev, qid, q->napi.napi_id);
+ if (err)
+ goto err_xdp_rxq_info_reg;
+
+ err = xdp_rxq_info_reg_mem_model(&q->xdp_rxq, MEM_TYPE_PAGE_POOL,
+ q->page_pool);
+ if (err)
+ goto err_xdp_rxq_info_reg_mem_model;
+
airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),
RX_RING_SIZE_MASK,
@@ -853,6 +1136,14 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
airoha_qdma_fill_rx_queue(q);
return 0;
+
+err_xdp_rxq_info_reg_mem_model:
+ xdp_rxq_info_unreg(&q->xdp_rxq);
+err_xdp_rxq_info_reg:
+ page_pool_destroy(q->page_pool);
+err_page_pool_create:
+ q->page_pool = NULL;
+ return err;
}
static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
@@ -882,6 +1173,9 @@ static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
q->queued--;
}
+ if (xdp_rxq_info_is_reg(&q->xdp_rxq))
+ xdp_rxq_info_unreg(&q->xdp_rxq);
+
q->head = q->tail;
/* Set RX_DMA_IDX to RX_CPU_IDX to notify the hw the QDMA RX ring is
* empty.
@@ -949,25 +1243,6 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
q->txq_stopped = false;
}
-static void airoha_unmap_xmit_buf(struct airoha_eth *eth,
- struct airoha_queue_entry *e)
-{
- switch (e->dma_type) {
- case AIROHA_DMA_MAP_PAGE:
- dma_unmap_page(eth->dev, e->dma_addr, e->dma_len,
- DMA_TO_DEVICE);
- break;
- case AIROHA_DMA_MAP_SINGLE:
- dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
- DMA_TO_DEVICE);
- break;
- case AIROHA_DMA_UNMAPPED:
- default:
- break;
- }
- e->dma_type = AIROHA_DMA_UNMAPPED;
-}
-
static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
{
struct airoha_tx_irq_queue *irq_q;
@@ -1037,7 +1312,12 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
WRITE_ONCE(desc->msg1, 0);
q->queued--;
- if (skb) {
+ if (e->is_xdpf) {
+ if (e->xdpf)
+ xdp_return_frame(e->xdpf);
+ e->is_xdpf = false;
+ e->xdpf = NULL;
+ } else if (e->skb) {
struct airoha_gdm_dev *dev = netdev_priv(skb->dev);
u16 qidx = skb_get_queue_mapping(skb);
struct netdev_queue *txq;
@@ -1218,7 +1498,12 @@ static void airoha_qdma_tx_cleanup(struct airoha_qdma *qdma)
WRITE_ONCE(desc->msg1, 0);
WRITE_ONCE(desc->msg2, 0);
- if (skb) {
+ if (e->is_xdpf) {
+ if (e->xdpf)
+ xdp_return_frame(e->xdpf);
+ e->is_xdpf = false;
+ e->xdpf = NULL;
+ } else if (skb) {
struct netdev_queue *txq;
txq = skb_get_tx_queue(skb->dev, skb);
@@ -2201,6 +2486,12 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ if (rcu_access_pointer(dev->xdp_prog) && mtu > AIROHA_RX_MAX_BUF_SIZE) {
+ netdev_err(netdev, "MTU too large for XDP (max %lu)\n",
+ AIROHA_RX_MAX_BUF_SIZE);
+ return -EINVAL;
+ }
+
WRITE_ONCE(netdev->mtu, mtu);
if (port->users)
airoha_dev_set_xmit_frame_size(netdev);
@@ -2377,6 +2668,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
list_move_tail(&e->list, &tx_list);
e->skb = i == nr_frags - 1 ? skb : NULL;
+ e->is_xdpf = false;
e->dma_addr = addr;
e->dma_len = len;
@@ -3321,6 +3613,39 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
return 0;
}
+static int airoha_xdp_setup(struct net_device *netdev, struct bpf_prog *prog,
+ struct netlink_ext_ack *extack)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct bpf_prog *old_prog;
+
+ if (netdev->features & NETIF_F_LRO) {
+ NL_SET_ERR_MSG_MOD(extack, "XDP is not supported with LRO");
+ return -EOPNOTSUPP;
+ }
+
+ if (netdev->mtu > AIROHA_RX_MAX_BUF_SIZE) {
+ NL_SET_ERR_MSG_MOD(extack, "MTU too large for XDP");
+ return -EOPNOTSUPP;
+ }
+
+ old_prog = rcu_replace_pointer(dev->xdp_prog, prog, lockdep_rtnl_is_held());
+ if (old_prog)
+ bpf_prog_put(old_prog);
+
+ return 0;
+}
+
+static int airoha_xdp_bpf(struct net_device *netdev, struct netdev_bpf *xdp)
+{
+ switch (xdp->command) {
+ case XDP_SETUP_PROG:
+ return airoha_xdp_setup(netdev, xdp->prog, xdp->extack);
+ default:
+ return -EINVAL;
+ }
+}
+
static int airoha_dev_tc_setup(struct net_device *dev,
enum tc_setup_type type, void *type_data)
{
@@ -3347,6 +3672,8 @@ static const struct net_device_ops airoha_netdev_ops = {
.ndo_get_stats64 = airoha_dev_get_stats64,
.ndo_set_mac_address = airoha_dev_set_macaddr,
.ndo_setup_tc = airoha_dev_tc_setup,
+ .ndo_bpf = airoha_xdp_bpf,
+ .ndo_xdp_xmit = airoha_xdp_xmit,
};
static const struct ethtool_ops airoha_ethtool_ops = {
@@ -3435,6 +3762,9 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
NETIF_F_HW_TC;
netdev->features |= netdev->hw_features;
netdev->vlan_features = netdev->hw_features;
+ netdev->xdp_features = NETDEV_XDP_ACT_BASIC |
+ NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_NDO_XMIT;
SET_NETDEV_DEV(netdev, eth->dev);
/* reserve hw queues for HTB offloading */
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 8277c1c87bb3..cfbc0b8f5bf9 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -7,6 +7,7 @@
#ifndef AIROHA_ETH_H
#define AIROHA_ETH_H
+#include <linux/bpf.h>
#include <linux/debugfs.h>
#include <linux/etherdevice.h>
#include <linux/iopoll.h>
@@ -15,6 +16,7 @@
#include <linux/reset.h>
#include <linux/soc/airoha/airoha_offload.h>
#include <net/dsa.h>
+#include <net/xdp.h>
#define AIROHA_MAX_NUM_GDM_PORTS 4
#define AIROHA_MAX_NUM_GDM_DEVS 2
@@ -34,7 +36,11 @@
#define AIROHA_FE_MC_MAX_VLAN_TABLE 64
#define AIROHA_FE_MC_MAX_VLAN_PORT 16
#define AIROHA_NUM_TX_IRQ 2
-#define AIROHA_RX_HEADROOM (NET_SKB_PAD + NET_IP_ALIGN)
+#define AIROHA_RX_HEADROOM (XDP_PACKET_HEADROOM + NET_IP_ALIGN)
+#define AIROHA_RX_PAD (AIROHA_RX_HEADROOM + \
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
+#define AIROHA_RX_MAX_BUF_SIZE (PAGE_SIZE - AIROHA_RX_PAD - \
+ VLAN_ETH_HLEN - ETH_FCS_LEN)
#define AIROHA_RX_LEN(_n) ((_n) - AIROHA_RX_HEADROOM)
#define HW_DSCP_NUM 2048
#define IRQ_QUEUE_LEN(_n) ((_n) ? 1024 : 2048)
@@ -182,12 +188,16 @@ struct airoha_queue_entry {
void *buf;
struct {
struct list_head list;
- struct sk_buff *skb;
+ union {
+ struct sk_buff *skb;
+ struct xdp_frame *xdpf;
+ };
enum airoha_dma_map_type dma_type;
};
};
dma_addr_t dma_addr;
u16 dma_len;
+ bool is_xdpf;
};
struct airoha_queue {
@@ -211,6 +221,9 @@ struct airoha_queue {
struct page_pool *page_pool;
struct sk_buff *skb;
+ struct xdp_rxq_info xdp_rxq;
+ bool xdp_flush;
+
struct list_head tx_list;
};
@@ -589,6 +602,8 @@ struct airoha_gdm_dev {
unsigned long flags;
int nbq;
+ struct bpf_prog __rcu *xdp_prog;
+
struct airoha_hw_stats stats;
/* Serialize netdev_tx_completed_queue() calls per TX queue during
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net-next 2/2] net: airoha: Add XDP statistics
2026-09-20 15:43 [PATCH net-next 0/2] net: airoha: Add XDP support Til Kaiser
2026-09-20 15:43 ` [PATCH net-next 1/2] " Til Kaiser
@ 2026-09-20 15:43 ` Til Kaiser
2026-09-21 8:05 ` Lorenzo Bianconi
1 sibling, 1 reply; 5+ messages in thread
From: Til Kaiser @ 2026-09-20 15:43 UTC (permalink / raw)
To: netdev
Cc: lorenzo, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, linux-mediatek, linux-kernel, Til Kaiser
Track and expose per-device XDP statistics via ethtool, covering both
the RX and TX sides of the XDP data path.
A new struct airoha_xdp_stats is introduced and embedded in the existing
struct airoha_hw_stats. It contains the following counters, all protected
by the existing u64_stats_sync:
rx_xdp_pass - frames passed to the networking stack
rx_xdp_aborted - frames with XDP_ABORTED or unknown action
rx_xdp_drop - frames dropped by the BPF program or on error
rx_xdp_tx - frames sent back out via XDP_TX
rx_xdp_tx_errors - XDP_TX failures
rx_xdp_redirect - frames successfully redirected
rx_xdp_redirect_errors - XDP_REDIRECT failures
tx_xdp_xmit - frames successfully submitted via ndo_xdp_xmit
tx_xdp_xmit_errors - frames dropped in ndo_xdp_xmit
The airoha_run_xdp() function is reworked to update the appropriate
counter after each XDP action. The airoha_xdp_xmit() ndo_xdp_xmit handler
is updated to record the number of successfully transmitted and dropped
frames.
The ethtool interface is extended with three new callbacks:
- get_strings: emits the XDP counter names followed by the page pool
stat strings obtained from page_pool_ethtool_stats_get_strings().
- get_sset_count: returns the combined count of XDP and page pool stats.
- get_ethtool_stats: reads the XDP counters under the u64_stats_sync
seqcount and appends page pool stats collected from all active RX
queues via page_pool_get_stats().
PAGE_POOL_STATS is selected in Kconfig to enable the page pool statistics
infrastructure required by the ethtool callbacks.
Signed-off-by: Til Kaiser <mail@tk154.de>
---
drivers/net/ethernet/airoha/Kconfig | 1 +
drivers/net/ethernet/airoha/airoha_eth.c | 131 ++++++++++++++++++++---
drivers/net/ethernet/airoha/airoha_eth.h | 14 +++
3 files changed, 133 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/airoha/Kconfig b/drivers/net/ethernet/airoha/Kconfig
index 1f6640a15fc9..3fa7683a79e9 100644
--- a/drivers/net/ethernet/airoha/Kconfig
+++ b/drivers/net/ethernet/airoha/Kconfig
@@ -20,6 +20,7 @@ config NET_AIROHA
depends on NET_DSA || !NET_DSA
select NET_AIROHA_NPU
select PAGE_POOL
+ select PAGE_POOL_STATS
help
This driver supports the gigabit ethernet MACs in the
Airoha SoC family.
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 71b25f225a8a..29b837204842 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -864,6 +864,11 @@ static int airoha_xdp_xmit(struct net_device *netdev, int n,
spin_unlock(&q->lock);
rcu_read_unlock();
+ u64_stats_update_begin(&dev->stats.syncp);
+ dev->stats.xdp_stats.tx_xdp_xmit += n - drops;
+ dev->stats.xdp_stats.tx_xdp_xmit_errors += drops;
+ u64_stats_update_end(&dev->stats.syncp);
+
return n - drops;
}
@@ -871,40 +876,56 @@ static bool airoha_run_xdp(struct net_device *netdev, struct bpf_prog *prog,
struct xdp_buff *xdp, struct airoha_queue *q,
struct airoha_queue_entry *e, struct page *page)
{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_hw_stats *hw_stats = &dev->stats;
u32 act = bpf_prog_run_xdp(prog, xdp);
+ u64 *count;
switch (act) {
case XDP_PASS:
- return false;
+ count = &hw_stats->xdp_stats.rx_xdp_pass;
+ goto update_stats;
case XDP_TX:
if (unlikely(airoha_xdp_xmit_back(netdev, xdp, q->qdma) < 0)) {
+ count = &hw_stats->xdp_stats.rx_xdp_tx_errors;
trace_xdp_exception(netdev, prog, act);
- page_pool_put_full_page(q->page_pool, page, true);
- } else {
- e->buf = NULL;
+ break;
}
- break;
+
+ e->buf = NULL;
+ count = &hw_stats->xdp_stats.rx_xdp_tx;
+ goto update_stats;
case XDP_REDIRECT:
if (unlikely(xdp_do_redirect(netdev, xdp, prog) < 0)) {
+ count = &hw_stats->xdp_stats.rx_xdp_redirect_errors;
trace_xdp_exception(netdev, prog, act);
- page_pool_put_full_page(q->page_pool, page, true);
- } else {
- q->xdp_flush = true;
- e->buf = NULL;
+ break;
}
- break;
+
+ e->buf = NULL;
+ q->xdp_flush = true;
+ count = &hw_stats->xdp_stats.rx_xdp_redirect;
+ goto update_stats;
default:
bpf_warn_invalid_xdp_action(netdev, prog, act);
fallthrough;
case XDP_ABORTED:
+ count = &hw_stats->xdp_stats.rx_xdp_aborted;
trace_xdp_exception(netdev, prog, act);
- fallthrough;
+ break;
case XDP_DROP:
- page_pool_put_full_page(q->page_pool, page, true);
+ count = &hw_stats->xdp_stats.rx_xdp_drop;
break;
}
- return true;
+ page_pool_put_full_page(q->page_pool, page, true);
+
+update_stats:
+ u64_stats_update_begin(&hw_stats->syncp);
+ *count = *count + 1;
+ u64_stats_update_end(&hw_stats->syncp);
+
+ return act != XDP_PASS;
}
static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
@@ -3676,12 +3697,96 @@ static const struct net_device_ops airoha_netdev_ops = {
.ndo_xdp_xmit = airoha_xdp_xmit,
};
+#define AIROHA_ETHTOOL_XDP_STAT(x) { #x, \
+ offsetof(struct airoha_hw_stats, xdp_stats.x) / \
+ sizeof(u64) }
+
+static const struct {
+ const char name[ETH_GSTRING_LEN];
+ u32 offset;
+} airoha_ethtool_xdp_stats[] = {
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect_errors),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_pass),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_aborted),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_drop),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx),
+ AIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx_errors),
+ AIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit),
+ AIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit_errors),
+};
+
+static void airoha_ethtool_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
+{
+ int i;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ for (i = 0; i < ARRAY_SIZE(airoha_ethtool_xdp_stats); i++) {
+ strscpy(data, airoha_ethtool_xdp_stats[i].name, ETH_GSTRING_LEN);
+ data += ETH_GSTRING_LEN;
+ }
+ page_pool_ethtool_stats_get_strings(data);
+ break;
+ }
+}
+
+static int airoha_ethtool_get_sset_count(struct net_device *netdev, int stringset)
+{
+ switch (stringset) {
+ case ETH_SS_STATS:
+ return ARRAY_SIZE(airoha_ethtool_xdp_stats) + page_pool_ethtool_stats_get_count();
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void airoha_ethtool_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats,
+ u64 *data)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_hw_stats *hw_stats = &dev->stats;
+ u64 *hw_stats_base = (u64 *)hw_stats;
+ struct page_pool_stats pp_stats = {};
+ struct airoha_qdma *qdma;
+ unsigned int start;
+ int i;
+
+ if (netif_running(netdev))
+ airoha_update_hw_stats(dev);
+
+ do {
+ start = u64_stats_fetch_begin(&hw_stats->syncp);
+ for (i = 0; i < ARRAY_SIZE(airoha_ethtool_xdp_stats); i++)
+ data[i] = hw_stats_base[airoha_ethtool_xdp_stats[i].offset];
+ } while (u64_stats_fetch_retry(&hw_stats->syncp, start));
+
+ rcu_read_lock();
+ qdma = rcu_dereference(dev->qdma);
+ if (qdma) {
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ struct airoha_queue *q = &qdma->q_rx[i];
+
+ if (q->page_pool)
+ page_pool_get_stats(q->page_pool, &pp_stats);
+ }
+ }
+ rcu_read_unlock();
+
+ page_pool_ethtool_stats_get(data + ARRAY_SIZE(airoha_ethtool_xdp_stats),
+ &pp_stats);
+}
+
static const struct ethtool_ops airoha_ethtool_ops = {
.get_drvinfo = airoha_ethtool_get_drvinfo,
.get_eth_mac_stats = airoha_ethtool_get_mac_stats,
.get_rmon_stats = airoha_ethtool_get_rmon_stats,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.get_link = ethtool_op_get_link,
+ .get_strings = airoha_ethtool_get_strings,
+ .get_sset_count = airoha_ethtool_get_sset_count,
+ .get_ethtool_stats = airoha_ethtool_get_ethtool_stats,
};
static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index cfbc0b8f5bf9..d2ac295c49e9 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -236,6 +236,18 @@ struct airoha_tx_irq_queue {
u32 *q;
};
+struct airoha_xdp_stats {
+ u64 rx_xdp_redirect;
+ u64 rx_xdp_redirect_errors;
+ u64 rx_xdp_pass;
+ u64 rx_xdp_aborted;
+ u64 rx_xdp_drop;
+ u64 rx_xdp_tx;
+ u64 rx_xdp_tx_errors;
+ u64 tx_xdp_xmit;
+ u64 tx_xdp_xmit_errors;
+};
+
struct airoha_hw_stats {
struct u64_stats_sync syncp;
@@ -259,6 +271,8 @@ struct airoha_hw_stats {
u64 rx_jabber;
u64 rx_len[7];
+ struct airoha_xdp_stats xdp_stats;
+
struct {
/* Previous HW register values for 32-bit counter delta
* tracking. Storing the last seen value and accumulating
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next 1/2] net: airoha: Add XDP support
2026-09-20 15:43 ` [PATCH net-next 1/2] " Til Kaiser
@ 2026-09-21 7:30 ` Lorenzo Bianconi
0 siblings, 0 replies; 5+ messages in thread
From: Lorenzo Bianconi @ 2026-09-21 7:30 UTC (permalink / raw)
To: Til Kaiser
Cc: netdev, lorenzo, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, linux-mediatek, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 22666 bytes --]
> Implement eXpress Data Path (XDP) support for the Airoha Ethernet driver.
Hi Til,
thanks to work on it. Some comments inline.
Regards,
Lorenzo
>
> XDP programs are attached per net_device via the ndo_bpf hook. The BPF
> program reference is stored as an RCU-protected pointer in struct
> airoha_gdm_dev and replaced atomically on program load/unload via
> rcu_replace_pointer().
>
> To support XDP, the following changes are made to the RX path:
>
> - The page pool DMA direction is switched from DMA_FROM_DEVICE to
> DMA_BIDIRECTIONAL, which is required for XDP_TX since the same page
> can be reused for transmit.
> - The RX headroom is increased from NET_SKB_PAD to XDP_PACKET_HEADROOM
> so that XDP programs have the required headroom available in the data
> buffer.
> - RX buffers use a full page rather than a half-page fragment. This leaves
> sufficient space for XDP headroom, skb_shared_info tailroom, and a
> standard 1500-byte MTU.
> - AIROHA_RX_MAX_BUF_SIZE expresses the maximum MTU that fits in an RX
> buffer after headroom, shared-info, Ethernet, VLAN, and FCS overhead.
> MTU changes are rejected when an XDP program is loaded and the requested
> MTU would exceed this limit.
> - xdp_rxq_info is registered for each RX queue during initialisation and
> unregistered on cleanup, with the page pool set as the memory model.
>
> The XDP program is run in airoha_run_xdp() for each received buffer.
> The following actions are supported:
>
> - XDP_PASS: the buffer is passed to the normal networking stack.
> - XDP_TX: the buffer is transmitted back out the same interface using
> the new airoha_xdp_xmit_back() helper.
> - XDP_REDIRECT: the buffer is redirected to another interface or map
> via xdp_do_redirect(), with xdp_do_flush() deferred to the end of
> the NAPI poll via a per-queue xdp_flush flag.
> - XDP_DROP (and unknown actions): the page is returned to the page pool.
>
> Two new TX path helpers are introduced:
>
> - airoha_xdp_submit_frame() maps an xdp_frame for DMA (or reuses the
> page pool DMA address for XDP_TX) and enqueues it into a TX ring,
> handling multi-buffer frames via skb_shared_info fragments.
> - airoha_xdp_xmit_back() converts an xdp_buff to an xdp_frame and
> submits it to the TX queue corresponding to the current CPU.
>
> The ndo_xdp_xmit handler airoha_xdp_xmit() is also added to allow
> XDP redirect from other drivers into the Airoha interface. Frames are
> submitted using airoha_xdp_submit_frame() with dma_map=true.
>
> struct airoha_queue_entry is extended with an is_xdpf flag and an
> xdp_frame pointer (in a union with the existing sk_buff pointer) so
> that the TX completion path can correctly free either XDP frames or
> SKBs.
>
> The advertised XDP feature flags are set to:
> NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_NDO_XMIT
>
> Signed-off-by: Til Kaiser <mail@tk154.de>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 386 +++++++++++++++++++++--
> drivers/net/ethernet/airoha/airoha_eth.h | 19 +-
> 2 files changed, 375 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 64619e9a704d..71b25f225a8a 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -13,6 +13,7 @@
> #include <net/page_pool/helpers.h>
> #include <net/pkt_cls.h>
> #include <uapi/linux/ppp_defs.h>
> +#include <linux/bpf_trace.h>
please respect alphabetic order
>
> #include "airoha_regs.h"
> #include "airoha_eth.h"
> @@ -657,6 +658,255 @@ airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)
> return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
> }
>
> +static void airoha_unmap_xmit_buf(struct airoha_eth *eth,
> + struct airoha_queue_entry *e)
> +{
> + switch (e->dma_type) {
> + case AIROHA_DMA_MAP_PAGE:
> + dma_unmap_page(eth->dev, e->dma_addr, e->dma_len,
> + DMA_TO_DEVICE);
> + break;
> + case AIROHA_DMA_MAP_SINGLE:
> + dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
> + DMA_TO_DEVICE);
> + break;
> + case AIROHA_DMA_UNMAPPED:
> + default:
> + break;
> + }
> + e->dma_type = AIROHA_DMA_UNMAPPED;
> +}
> +
> +static int airoha_xdp_submit_frame(struct airoha_gdm_dev *dev,
> + struct xdp_frame *xdpf,
> + struct airoha_queue *q,
> + int qid, u32 msg0, u32 msg1,
> + bool dma_map)
can you please run checkpatch.pl on the patch?
> +{
> + struct airoha_queue_entry *e, *next_e;
> + int len = xdpf->len, nr_frags, i;
> + struct airoha_qdma_desc *desc;
> + struct skb_shared_info *sinfo;
> + void *data = xdpf->data;
> + u16 index, next_index;
> + LIST_HEAD(tx_list);
> + dma_addr_t addr;
> + u32 val;
> +
> + sinfo = xdp_get_shared_info_from_frame(xdpf);
> + nr_frags = unlikely(xdp_frame_has_frags(xdpf)) ? sinfo->nr_frags : 0;
> +
> + if (q->queued >= q->ndesc - 1 - nr_frags)
nit: I guess it is more readable if you do:
if (q->queued + 1 + nr_frags >= q->ndesc)
return -EBUSY;
> + return -EBUSY;
> +
> + for (i = 0; i <= nr_frags; i++) {
> + if (dma_map) {
> + if (i == 0)
> + addr = dma_map_single(dev->eth->dev, data, len,
> + DMA_TO_DEVICE);
> + else
> + addr = dma_map_page(dev->eth->dev, virt_to_page(data),
> + offset_in_page(data), len,
> + DMA_TO_DEVICE);
> + if (unlikely(dma_mapping_error(dev->eth->dev, addr)))
> + goto unmap;
> + } else {
> + struct page *page = virt_to_head_page(data);
> +
> + addr = page_pool_get_dma_addr(page) +
> + (data - (void *)page_address(page));
> + dma_sync_single_for_device(dev->eth->dev, addr, len,
> + DMA_BIDIRECTIONAL);
> + }
> +
> + e = list_first_entry(&q->tx_list, struct airoha_queue_entry, list);
> + list_move_tail(&e->list, &tx_list);
> +
> + index = e - q->entry;
> + desc = &q->desc[index];
> +
> + e->is_xdpf = true;
> + e->xdpf = (i == nr_frags) ? xdpf : NULL;
nit: you do not need brackets here.
> + e->dma_addr = addr;
> + e->dma_len = len;
> + if (dma_map)
> + e->dma_type = i == 0 ? AIROHA_DMA_MAP_SINGLE : AIROHA_DMA_MAP_PAGE;
> + else
> + e->dma_type = AIROHA_DMA_UNMAPPED;
I thin you can move this chunk in the above if/else block
> +
> + next_e = list_first_entry(&q->tx_list, struct airoha_queue_entry, list);
> + next_index = next_e - q->entry;
> +
> + val = FIELD_PREP(QDMA_DESC_LEN_MASK, len);
> + if (i < nr_frags)
> + val |= FIELD_PREP(QDMA_DESC_MORE_MASK, 1);
> + WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
> + WRITE_ONCE(desc->addr, cpu_to_le32(addr));
> + val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, next_index);
> + WRITE_ONCE(desc->data, cpu_to_le32(val));
> + WRITE_ONCE(desc->msg0, cpu_to_le32(msg0));
> + WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
> + WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
> +
> + q->queued++;
> +
> + if (i < nr_frags) {
> + skb_frag_t *frag = &sinfo->frags[i];
> +
> + data = skb_frag_address(frag);
> + len = skb_frag_size(frag);
> + }
> + }
> +
> + return next_index;
> +
> +unmap:
> + list_for_each_entry(e, &tx_list, list) {
> + airoha_unmap_xmit_buf(dev->eth, e);
> + e->is_xdpf = false;
> + e->xdpf = NULL;
> + q->queued--;
> + }
> + list_splice(&tx_list, &q->tx_list);
> +
> + return -ENOMEM;
> +}
> +
> +static int airoha_xdp_xmit_back(struct net_device *netdev, struct xdp_buff *xdp,
> + struct airoha_qdma *qdma)
> +{
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_queue *q;
> + struct xdp_frame *xdpf;
> + u32 msg0, msg1;
> + int qid, index;
> + u8 fport;
> +
> + xdpf = xdp_convert_buff_to_frame(xdp);
> + if (unlikely(!xdpf))
> + return -EOVERFLOW;
> +
> + qid = airoha_qdma_get_txq(qdma, smp_processor_id());
> + q = &qdma->q_tx[qid];
> +
> + msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
> + qid / AIROHA_NUM_QOS_QUEUES) |
> + FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
> + qid % AIROHA_NUM_QOS_QUEUES);
> +
> + fport = airoha_get_fe_port(dev);
> + msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
> + FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
> + FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
msg0/msg1 are in common with airoha_xdp_xmit(), right? Can you please move them
in airoha_xdp_submit_frame()?
> +
> + spin_lock(&q->lock);
> + index = airoha_xdp_submit_frame(dev, xdpf, q, qid, msg0, msg1, false);
> + if (unlikely(index < 0)) {
> + spin_unlock(&q->lock);
> + return index;
> + }
> +
> + airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
> + TX_RING_CPU_IDX_MASK,
> + FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
> + spin_unlock(&q->lock);
> +
> + return 0;
> +}
> +
> +static int airoha_xdp_xmit(struct net_device *netdev, int n,
> + struct xdp_frame **frames, u32 flags)
> +{
> + int qid, index = 0, last_index = -1, i, drops = 0;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_qdma *qdma;
> + struct airoha_queue *q;
> + u32 msg0, msg1;
> + u8 fport;
> +
> + if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
> + return -EINVAL;
> +
> + rcu_read_lock();
> + qdma = rcu_dereference(dev->qdma);
> + if (!qdma) {
> + rcu_read_unlock();
> + return -ENODEV;
> + }
> +
> + qid = airoha_qdma_get_txq(qdma, smp_processor_id());
> + q = &qdma->q_tx[qid];
> +
> + msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
> + qid / AIROHA_NUM_QOS_QUEUES) |
> + FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
> + qid % AIROHA_NUM_QOS_QUEUES);
> +
> + fport = airoha_get_fe_port(dev);
> + msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
> + FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
> + FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
> +
> + spin_lock(&q->lock);
> + for (i = 0; i < n; i++) {
> + index = airoha_xdp_submit_frame(dev, frames[i], q, qid, msg0, msg1, true);
> + if (unlikely(index < 0)) {
> + xdp_return_frame_rx_napi(frames[i]);
> + drops++;
> + } else {
> + last_index = index;
> + }
> + }
> + if (n > drops && (flags & XDP_XMIT_FLUSH))
Do you mean to skip it if drops == n? If so, I guess it is more readable to
count number of successful transmissions
> + airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
> + TX_RING_CPU_IDX_MASK,
> + FIELD_PREP(TX_RING_CPU_IDX_MASK, last_index));
> + spin_unlock(&q->lock);
> + rcu_read_unlock();
> +
> + return n - drops;
same here
> +}
> +
> +static bool airoha_run_xdp(struct net_device *netdev, struct bpf_prog *prog,
> + struct xdp_buff *xdp, struct airoha_queue *q,
> + struct airoha_queue_entry *e, struct page *page)
> +{
> + u32 act = bpf_prog_run_xdp(prog, xdp);
> +
> + switch (act) {
> + case XDP_PASS:
> + return false;
> + case XDP_TX:
> + if (unlikely(airoha_xdp_xmit_back(netdev, xdp, q->qdma) < 0)) {
> + trace_xdp_exception(netdev, prog, act);
> + page_pool_put_full_page(q->page_pool, page, true);
> + } else {
> + e->buf = NULL;
> + }
> + break;
> + case XDP_REDIRECT:
> + if (unlikely(xdp_do_redirect(netdev, xdp, prog) < 0)) {
> + trace_xdp_exception(netdev, prog, act);
> + page_pool_put_full_page(q->page_pool, page, true);
> + } else {
> + q->xdp_flush = true;
> + e->buf = NULL;
> + }
> + break;
> + default:
> + bpf_warn_invalid_xdp_action(netdev, prog, act);
> + fallthrough;
> + case XDP_ABORTED:
> + trace_xdp_exception(netdev, prog, act);
> + fallthrough;
> + case XDP_DROP:
> + page_pool_put_full_page(q->page_pool, page, true);
> + break;
> + }
> +
> + return true;
> +}
> +
> static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> {
> enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
> @@ -698,6 +948,27 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
>
> netdev = netdev_from_priv(dev);
> if (!q->skb) { /* first buffer */
> + struct bpf_prog *xdp_prog;
nit: prog seems better to me
> +
> + rcu_read_lock();
> + xdp_prog = rcu_dereference(dev->xdp_prog);
> + if (xdp_prog) {
> + struct xdp_buff xdp;
> +
> + xdp_init_buff(&xdp, q->buf_size, &q->xdp_rxq);
> + xdp_prepare_buff(&xdp, e->buf - AIROHA_RX_HEADROOM,
> + AIROHA_RX_HEADROOM, len, false);
> +
> + if (airoha_run_xdp(netdev, xdp_prog, &xdp, q, e, page)) {
> + rcu_read_unlock();
> + continue;
> + }
> +
> + len = xdp.data_end - xdp.data;
> + e->buf = xdp.data;
> + }
> + rcu_read_unlock();
> +
> q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
> q->buf_size);
> if (!q->skb)
> @@ -779,6 +1050,11 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
> done += cur;
> } while (cur && done < budget);
>
> + if (q->xdp_flush) {
> + xdp_do_flush();
> + q->xdp_flush = false;
> + }
> +
> if (done < budget && napi_complete(napi)) {
> struct airoha_qdma *qdma = q->qdma;
> int i, qid = q - &qdma->q_rx[0];
> @@ -804,17 +1080,17 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
> .order = 0,
> .pool_size = 256,
> .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
> - .dma_dir = DMA_FROM_DEVICE,
> + .dma_dir = DMA_BIDIRECTIONAL,
> .max_len = PAGE_SIZE,
> .nid = NUMA_NO_NODE,
> .dev = qdma->eth->dev,
> .napi = &q->napi,
> };
> + int qid = q - &qdma->q_rx[0], thr, err;
> struct airoha_eth *eth = qdma->eth;
> - int qid = q - &qdma->q_rx[0], thr;
> dma_addr_t dma_addr;
>
> - q->buf_size = PAGE_SIZE / 2;
> + q->buf_size = PAGE_SIZE;
I guess this can introduce a performance penalty in the non-xdp case since now
we need a full page for a single buffer while in the current codebase we can
have 2 fragments in a single page. Moreover, it seems to me you are not using
latest codebase here.
> q->qdma = qdma;
>
> q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
> @@ -829,15 +1105,22 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
>
> q->page_pool = page_pool_create(&pp_params);
> if (IS_ERR(q->page_pool)) {
> - int err = PTR_ERR(q->page_pool);
> -
> - q->page_pool = NULL;
> - return err;
> + err = PTR_ERR(q->page_pool);
> + goto err_page_pool_create;
> }
>
> q->ndesc = ndesc;
> netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
>
> + err = xdp_rxq_info_reg(&q->xdp_rxq, eth->napi_dev, qid, q->napi.napi_id);
> + if (err)
> + goto err_xdp_rxq_info_reg;
> +
> + err = xdp_rxq_info_reg_mem_model(&q->xdp_rxq, MEM_TYPE_PAGE_POOL,
> + q->page_pool);
> + if (err)
> + goto err_xdp_rxq_info_reg_mem_model;
> +
> airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
> airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),
> RX_RING_SIZE_MASK,
> @@ -853,6 +1136,14 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
> airoha_qdma_fill_rx_queue(q);
>
> return 0;
> +
> +err_xdp_rxq_info_reg_mem_model:
> + xdp_rxq_info_unreg(&q->xdp_rxq);
> +err_xdp_rxq_info_reg:
> + page_pool_destroy(q->page_pool);
> +err_page_pool_create:
> + q->page_pool = NULL;
> + return err;
> }
>
> static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
> @@ -882,6 +1173,9 @@ static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
> q->queued--;
> }
>
> + if (xdp_rxq_info_is_reg(&q->xdp_rxq))
> + xdp_rxq_info_unreg(&q->xdp_rxq);
> +
> q->head = q->tail;
> /* Set RX_DMA_IDX to RX_CPU_IDX to notify the hw the QDMA RX ring is
> * empty.
> @@ -949,25 +1243,6 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
> q->txq_stopped = false;
> }
>
> -static void airoha_unmap_xmit_buf(struct airoha_eth *eth,
> - struct airoha_queue_entry *e)
> -{
> - switch (e->dma_type) {
> - case AIROHA_DMA_MAP_PAGE:
> - dma_unmap_page(eth->dev, e->dma_addr, e->dma_len,
> - DMA_TO_DEVICE);
> - break;
> - case AIROHA_DMA_MAP_SINGLE:
> - dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
> - DMA_TO_DEVICE);
> - break;
> - case AIROHA_DMA_UNMAPPED:
> - default:
> - break;
> - }
> - e->dma_type = AIROHA_DMA_UNMAPPED;
> -}
> -
> static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
> {
> struct airoha_tx_irq_queue *irq_q;
> @@ -1037,7 +1312,12 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
> WRITE_ONCE(desc->msg1, 0);
> q->queued--;
>
> - if (skb) {
> + if (e->is_xdpf) {
> + if (e->xdpf)
> + xdp_return_frame(e->xdpf);
> + e->is_xdpf = false;
> + e->xdpf = NULL;
> + } else if (e->skb) {
> struct airoha_gdm_dev *dev = netdev_priv(skb->dev);
> u16 qidx = skb_get_queue_mapping(skb);
> struct netdev_queue *txq;
> @@ -1218,7 +1498,12 @@ static void airoha_qdma_tx_cleanup(struct airoha_qdma *qdma)
> WRITE_ONCE(desc->msg1, 0);
> WRITE_ONCE(desc->msg2, 0);
>
> - if (skb) {
> + if (e->is_xdpf) {
> + if (e->xdpf)
> + xdp_return_frame(e->xdpf);
> + e->is_xdpf = false;
> + e->xdpf = NULL;
> + } else if (skb) {
> struct netdev_queue *txq;
>
> txq = skb_get_tx_queue(skb->dev, skb);
> @@ -2201,6 +2486,12 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> struct airoha_gdm_port *port = dev->port;
>
> + if (rcu_access_pointer(dev->xdp_prog) && mtu > AIROHA_RX_MAX_BUF_SIZE) {
> + netdev_err(netdev, "MTU too large for XDP (max %lu)\n",
> + AIROHA_RX_MAX_BUF_SIZE);
> + return -EINVAL;
> + }
> +
> WRITE_ONCE(netdev->mtu, mtu);
> if (port->users)
> airoha_dev_set_xmit_frame_size(netdev);
> @@ -2377,6 +2668,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
>
> list_move_tail(&e->list, &tx_list);
> e->skb = i == nr_frags - 1 ? skb : NULL;
> + e->is_xdpf = false;
> e->dma_addr = addr;
> e->dma_len = len;
>
> @@ -3321,6 +3613,39 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
> return 0;
> }
>
> +static int airoha_xdp_setup(struct net_device *netdev, struct bpf_prog *prog,
> + struct netlink_ext_ack *extack)
> +{
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct bpf_prog *old_prog;
> +
> + if (netdev->features & NETIF_F_LRO) {
> + NL_SET_ERR_MSG_MOD(extack, "XDP is not supported with LRO");
> + return -EOPNOTSUPP;
> + }
> +
> + if (netdev->mtu > AIROHA_RX_MAX_BUF_SIZE) {
> + NL_SET_ERR_MSG_MOD(extack, "MTU too large for XDP");
> + return -EOPNOTSUPP;
> + }
> +
> + old_prog = rcu_replace_pointer(dev->xdp_prog, prog, lockdep_rtnl_is_held());
> + if (old_prog)
> + bpf_prog_put(old_prog);
> +
> + return 0;
> +}
> +
> +static int airoha_xdp_bpf(struct net_device *netdev, struct netdev_bpf *xdp)
nit: airoha_dev_bpf()?
> +{
> + switch (xdp->command) {
> + case XDP_SETUP_PROG:
> + return airoha_xdp_setup(netdev, xdp->prog, xdp->extack);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> static int airoha_dev_tc_setup(struct net_device *dev,
> enum tc_setup_type type, void *type_data)
> {
> @@ -3347,6 +3672,8 @@ static const struct net_device_ops airoha_netdev_ops = {
> .ndo_get_stats64 = airoha_dev_get_stats64,
> .ndo_set_mac_address = airoha_dev_set_macaddr,
> .ndo_setup_tc = airoha_dev_tc_setup,
> + .ndo_bpf = airoha_xdp_bpf,
> + .ndo_xdp_xmit = airoha_xdp_xmit,
> };
>
> static const struct ethtool_ops airoha_ethtool_ops = {
> @@ -3435,6 +3762,9 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
> NETIF_F_HW_TC;
> netdev->features |= netdev->hw_features;
> netdev->vlan_features = netdev->hw_features;
> + netdev->xdp_features = NETDEV_XDP_ACT_BASIC |
> + NETDEV_XDP_ACT_REDIRECT |
> + NETDEV_XDP_ACT_NDO_XMIT;
please align it:
netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
NETDEV_XDP_ACT_NDO_XMIT;
> SET_NETDEV_DEV(netdev, eth->dev);
>
> /* reserve hw queues for HTB offloading */
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index 8277c1c87bb3..cfbc0b8f5bf9 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -7,6 +7,7 @@
> #ifndef AIROHA_ETH_H
> #define AIROHA_ETH_H
>
> +#include <linux/bpf.h>
> #include <linux/debugfs.h>
> #include <linux/etherdevice.h>
> #include <linux/iopoll.h>
> @@ -15,6 +16,7 @@
> #include <linux/reset.h>
> #include <linux/soc/airoha/airoha_offload.h>
> #include <net/dsa.h>
> +#include <net/xdp.h>
>
> #define AIROHA_MAX_NUM_GDM_PORTS 4
> #define AIROHA_MAX_NUM_GDM_DEVS 2
> @@ -34,7 +36,11 @@
> #define AIROHA_FE_MC_MAX_VLAN_TABLE 64
> #define AIROHA_FE_MC_MAX_VLAN_PORT 16
> #define AIROHA_NUM_TX_IRQ 2
> -#define AIROHA_RX_HEADROOM (NET_SKB_PAD + NET_IP_ALIGN)
> +#define AIROHA_RX_HEADROOM (XDP_PACKET_HEADROOM + NET_IP_ALIGN)
> +#define AIROHA_RX_PAD (AIROHA_RX_HEADROOM + \
> + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
> +#define AIROHA_RX_MAX_BUF_SIZE (PAGE_SIZE - AIROHA_RX_PAD - \
> + VLAN_ETH_HLEN - ETH_FCS_LEN)
same as above, I think this can introduce a performance penalty in the non-xdp
case. Can you please check? (e.g. on a 10Gbps link).
> #define AIROHA_RX_LEN(_n) ((_n) - AIROHA_RX_HEADROOM)
> #define HW_DSCP_NUM 2048
> #define IRQ_QUEUE_LEN(_n) ((_n) ? 1024 : 2048)
> @@ -182,12 +188,16 @@ struct airoha_queue_entry {
> void *buf;
> struct {
> struct list_head list;
> - struct sk_buff *skb;
> + union {
> + struct sk_buff *skb;
> + struct xdp_frame *xdpf;
> + };
> enum airoha_dma_map_type dma_type;
> };
> };
> dma_addr_t dma_addr;
> u16 dma_len;
> + bool is_xdpf;
> };
>
> struct airoha_queue {
> @@ -211,6 +221,9 @@ struct airoha_queue {
> struct page_pool *page_pool;
> struct sk_buff *skb;
>
> + struct xdp_rxq_info xdp_rxq;
> + bool xdp_flush;
> +
> struct list_head tx_list;
> };
>
> @@ -589,6 +602,8 @@ struct airoha_gdm_dev {
> unsigned long flags;
> int nbq;
>
> + struct bpf_prog __rcu *xdp_prog;
nit: prog
> +
> struct airoha_hw_stats stats;
>
> /* Serialize netdev_tx_completed_queue() calls per TX queue during
> --
> 2.55.0
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next 2/2] net: airoha: Add XDP statistics
2026-09-20 15:43 ` [PATCH net-next 2/2] net: airoha: Add XDP statistics Til Kaiser
@ 2026-09-21 8:05 ` Lorenzo Bianconi
0 siblings, 0 replies; 5+ messages in thread
From: Lorenzo Bianconi @ 2026-09-21 8:05 UTC (permalink / raw)
To: Til Kaiser
Cc: netdev, lorenzo, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, linux-mediatek, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 10127 bytes --]
> Track and expose per-device XDP statistics via ethtool, covering both
> the RX and TX sides of the XDP data path.
>
> A new struct airoha_xdp_stats is introduced and embedded in the existing
> struct airoha_hw_stats. It contains the following counters, all protected
> by the existing u64_stats_sync:
>
> rx_xdp_pass - frames passed to the networking stack
> rx_xdp_aborted - frames with XDP_ABORTED or unknown action
> rx_xdp_drop - frames dropped by the BPF program or on error
> rx_xdp_tx - frames sent back out via XDP_TX
> rx_xdp_tx_errors - XDP_TX failures
> rx_xdp_redirect - frames successfully redirected
> rx_xdp_redirect_errors - XDP_REDIRECT failures
> tx_xdp_xmit - frames successfully submitted via ndo_xdp_xmit
> tx_xdp_xmit_errors - frames dropped in ndo_xdp_xmit
>
> The airoha_run_xdp() function is reworked to update the appropriate
> counter after each XDP action. The airoha_xdp_xmit() ndo_xdp_xmit handler
> is updated to record the number of successfully transmitted and dropped
> frames.
>
> The ethtool interface is extended with three new callbacks:
>
> - get_strings: emits the XDP counter names followed by the page pool
> stat strings obtained from page_pool_ethtool_stats_get_strings().
> - get_sset_count: returns the combined count of XDP and page pool stats.
> - get_ethtool_stats: reads the XDP counters under the u64_stats_sync
> seqcount and appends page pool stats collected from all active RX
> queues via page_pool_get_stats().
>
> PAGE_POOL_STATS is selected in Kconfig to enable the page pool statistics
> infrastructure required by the ethtool callbacks.
>
> Signed-off-by: Til Kaiser <mail@tk154.de>
> ---
> drivers/net/ethernet/airoha/Kconfig | 1 +
> drivers/net/ethernet/airoha/airoha_eth.c | 131 ++++++++++++++++++++---
> drivers/net/ethernet/airoha/airoha_eth.h | 14 +++
> 3 files changed, 133 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/Kconfig b/drivers/net/ethernet/airoha/Kconfig
> index 1f6640a15fc9..3fa7683a79e9 100644
> --- a/drivers/net/ethernet/airoha/Kconfig
> +++ b/drivers/net/ethernet/airoha/Kconfig
> @@ -20,6 +20,7 @@ config NET_AIROHA
> depends on NET_DSA || !NET_DSA
> select NET_AIROHA_NPU
> select PAGE_POOL
> + select PAGE_POOL_STATS
> help
> This driver supports the gigabit ethernet MACs in the
> Airoha SoC family.
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 71b25f225a8a..29b837204842 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -864,6 +864,11 @@ static int airoha_xdp_xmit(struct net_device *netdev, int n,
> spin_unlock(&q->lock);
> rcu_read_unlock();
>
> + u64_stats_update_begin(&dev->stats.syncp);
> + dev->stats.xdp_stats.tx_xdp_xmit += n - drops;
> + dev->stats.xdp_stats.tx_xdp_xmit_errors += drops;
> + u64_stats_update_end(&dev->stats.syncp);
> +
> return n - drops;
> }
>
> @@ -871,40 +876,56 @@ static bool airoha_run_xdp(struct net_device *netdev, struct bpf_prog *prog,
> struct xdp_buff *xdp, struct airoha_queue *q,
> struct airoha_queue_entry *e, struct page *page)
> {
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_hw_stats *hw_stats = &dev->stats;
> u32 act = bpf_prog_run_xdp(prog, xdp);
> + u64 *count;
>
> switch (act) {
> case XDP_PASS:
> - return false;
> + count = &hw_stats->xdp_stats.rx_xdp_pass;
> + goto update_stats;
> case XDP_TX:
> if (unlikely(airoha_xdp_xmit_back(netdev, xdp, q->qdma) < 0)) {
> + count = &hw_stats->xdp_stats.rx_xdp_tx_errors;
> trace_xdp_exception(netdev, prog, act);
> - page_pool_put_full_page(q->page_pool, page, true);
> - } else {
> - e->buf = NULL;
> + break;
> }
> - break;
> +
> + e->buf = NULL;
> + count = &hw_stats->xdp_stats.rx_xdp_tx;
> + goto update_stats;
> case XDP_REDIRECT:
> if (unlikely(xdp_do_redirect(netdev, xdp, prog) < 0)) {
> + count = &hw_stats->xdp_stats.rx_xdp_redirect_errors;
> trace_xdp_exception(netdev, prog, act);
> - page_pool_put_full_page(q->page_pool, page, true);
> - } else {
> - q->xdp_flush = true;
> - e->buf = NULL;
> + break;
> }
> - break;
> +
> + e->buf = NULL;
> + q->xdp_flush = true;
> + count = &hw_stats->xdp_stats.rx_xdp_redirect;
> + goto update_stats;
> default:
> bpf_warn_invalid_xdp_action(netdev, prog, act);
> fallthrough;
> case XDP_ABORTED:
> + count = &hw_stats->xdp_stats.rx_xdp_aborted;
> trace_xdp_exception(netdev, prog, act);
> - fallthrough;
> + break;
> case XDP_DROP:
> - page_pool_put_full_page(q->page_pool, page, true);
> + count = &hw_stats->xdp_stats.rx_xdp_drop;
> break;
> }
>
> - return true;
> + page_pool_put_full_page(q->page_pool, page, true);
> +
> +update_stats:
> + u64_stats_update_begin(&hw_stats->syncp);
> + *count = *count + 1;
> + u64_stats_update_end(&hw_stats->syncp);
Would it better if we update the stats in airoha_qdma_rx_process() passing a
airoha_xdp_stats struct to airoha_run_xdp(). I guess doing so we can even drop
xdp_flush field in airoha_qdma_queue struct.
> +
> + return act != XDP_PASS;
> }
>
> static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> @@ -3676,12 +3697,96 @@ static const struct net_device_ops airoha_netdev_ops = {
> .ndo_xdp_xmit = airoha_xdp_xmit,
> };
>
> +#define AIROHA_ETHTOOL_XDP_STAT(x) { #x, \
> + offsetof(struct airoha_hw_stats, xdp_stats.x) / \
> + sizeof(u64) }
> +
> +static const struct {
> + const char name[ETH_GSTRING_LEN];
> + u32 offset;
> +} airoha_ethtool_xdp_stats[] = {
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect_errors),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_pass),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_aborted),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_drop),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx),
> + AIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx_errors),
> + AIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit),
> + AIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit_errors),
> +};
> +
> +static void airoha_ethtool_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
nit I like old style :)
static void airoha_ethtool_get_strings(struct net_device *netdev,
u32 stringset, u8 *data)
{
....
}
> +{
> + int i;
> +
> + switch (stringset) {
> + case ETH_SS_STATS:
> + for (i = 0; i < ARRAY_SIZE(airoha_ethtool_xdp_stats); i++) {
> + strscpy(data, airoha_ethtool_xdp_stats[i].name, ETH_GSTRING_LEN);
> + data += ETH_GSTRING_LEN;
> + }
> + page_pool_ethtool_stats_get_strings(data);
> + break;
> + }
> +}
> +
> +static int airoha_ethtool_get_sset_count(struct net_device *netdev, int stringset)
> +{
> + switch (stringset) {
> + case ETH_SS_STATS:
> + return ARRAY_SIZE(airoha_ethtool_xdp_stats) + page_pool_ethtool_stats_get_count();
same here:
return ARRAY_SIZE(airoha_ethtool_xdp_stats) +
page_pool_ethtool_stats_get_count();
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static void airoha_ethtool_get_ethtool_stats(struct net_device *netdev,
> + struct ethtool_stats *stats,
> + u64 *data)
> +{
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_hw_stats *hw_stats = &dev->stats;
> + u64 *hw_stats_base = (u64 *)hw_stats;
> + struct page_pool_stats pp_stats = {};
> + struct airoha_qdma *qdma;
> + unsigned int start;
> + int i;
> +
> + if (netif_running(netdev))
> + airoha_update_hw_stats(dev);
> +
> + do {
> + start = u64_stats_fetch_begin(&hw_stats->syncp);
> + for (i = 0; i < ARRAY_SIZE(airoha_ethtool_xdp_stats); i++)
> + data[i] = hw_stats_base[airoha_ethtool_xdp_stats[i].offset];
> + } while (u64_stats_fetch_retry(&hw_stats->syncp, start));
> +
> + rcu_read_lock();
> + qdma = rcu_dereference(dev->qdma);
IIRC airoha_ethtool_get_ethtool_stats() is running under RTNL so you can just
use airoha_qdma_deref() here.
Regards,
Lorenzo
> + if (qdma) {
> + for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
> + struct airoha_queue *q = &qdma->q_rx[i];
> +
> + if (q->page_pool)
> + page_pool_get_stats(q->page_pool, &pp_stats);
> + }
> + }
> + rcu_read_unlock();
> +
> + page_pool_ethtool_stats_get(data + ARRAY_SIZE(airoha_ethtool_xdp_stats),
> + &pp_stats);
> +}
> +
> static const struct ethtool_ops airoha_ethtool_ops = {
> .get_drvinfo = airoha_ethtool_get_drvinfo,
> .get_eth_mac_stats = airoha_ethtool_get_mac_stats,
> .get_rmon_stats = airoha_ethtool_get_rmon_stats,
> .get_link_ksettings = phy_ethtool_get_link_ksettings,
> .get_link = ethtool_op_get_link,
> + .get_strings = airoha_ethtool_get_strings,
> + .get_sset_count = airoha_ethtool_get_sset_count,
> + .get_ethtool_stats = airoha_ethtool_get_ethtool_stats,
> };
>
> static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index cfbc0b8f5bf9..d2ac295c49e9 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -236,6 +236,18 @@ struct airoha_tx_irq_queue {
> u32 *q;
> };
>
> +struct airoha_xdp_stats {
> + u64 rx_xdp_redirect;
> + u64 rx_xdp_redirect_errors;
> + u64 rx_xdp_pass;
> + u64 rx_xdp_aborted;
> + u64 rx_xdp_drop;
> + u64 rx_xdp_tx;
> + u64 rx_xdp_tx_errors;
> + u64 tx_xdp_xmit;
> + u64 tx_xdp_xmit_errors;
> +};
> +
> struct airoha_hw_stats {
> struct u64_stats_sync syncp;
>
> @@ -259,6 +271,8 @@ struct airoha_hw_stats {
> u64 rx_jabber;
> u64 rx_len[7];
>
> + struct airoha_xdp_stats xdp_stats;
> +
> struct {
> /* Previous HW register values for 32-bit counter delta
> * tracking. Storing the last seen value and accumulating
> --
> 2.55.0
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-21 8:05 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-20 15:43 [PATCH net-next 0/2] net: airoha: Add XDP support Til Kaiser
2026-09-20 15:43 ` [PATCH net-next 1/2] " Til Kaiser
2026-09-21 7:30 ` Lorenzo Bianconi
2026-09-20 15:43 ` [PATCH net-next 2/2] net: airoha: Add XDP statistics Til Kaiser
2026-09-21 8:05 ` Lorenzo Bianconi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®