| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/22 16:32 | flow | patch-triage |
50mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch adds XDP support to the Airoha Ethernet driver (drivers/net/ethernet/airoha/airoha_eth.c). This is a platform driver for Airoha SoCs (e.g. EN7581) requiring specific device tree bindings and physical SoC hardware resources (MMIO registers, reset lines, clocks) that are not emulated in standard QEMU/GCE virtualized environments on the target architecture (amd64). As such, the modified code is structurally unreachable.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/22 16:32 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 281615644eeb6e7c93837e03b73f70115302a9d9\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Sep 22 16:32:11 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/airoha/Kconfig b/drivers/net/ethernet/airoha/Kconfig\nindex 1f6640a15fc94..3fa7683a79e9d 100644\n--- a/drivers/net/ethernet/airoha/Kconfig\n+++ b/drivers/net/ethernet/airoha/Kconfig\n@@ -20,6 +20,7 @@ config NET_AIROHA\n \tdepends on NET_DSA || !NET_DSA\n \tselect NET_AIROHA_NPU\n \tselect PAGE_POOL\n+\tselect PAGE_POOL_STATS\n \thelp\n \t This driver supports the gigabit ethernet MACs in the\n \t Airoha SoC family.\ndiff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c\nindex 64619e9a704dd..29b8372048422 100644\n--- a/drivers/net/ethernet/airoha/airoha_eth.c\n+++ b/drivers/net/ethernet/airoha/airoha_eth.c\n@@ -13,6 +13,7 @@\n #include \u003cnet/page_pool/helpers.h\u003e\n #include \u003cnet/pkt_cls.h\u003e\n #include \u003cuapi/linux/ppp_defs.h\u003e\n+#include \u003clinux/bpf_trace.h\u003e\n \n #include \"airoha_regs.h\"\n #include \"airoha_eth.h\"\n@@ -657,6 +658,276 @@ airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)\n \treturn port-\u003edevs[d] ? port-\u003edevs[d] : ERR_PTR(-ENODEV);\n }\n \n+static void airoha_unmap_xmit_buf(struct airoha_eth *eth,\n+\t\t\t\t struct airoha_queue_entry *e)\n+{\n+\tswitch (e-\u003edma_type) {\n+\tcase AIROHA_DMA_MAP_PAGE:\n+\t\tdma_unmap_page(eth-\u003edev, e-\u003edma_addr, e-\u003edma_len,\n+\t\t\t DMA_TO_DEVICE);\n+\t\tbreak;\n+\tcase AIROHA_DMA_MAP_SINGLE:\n+\t\tdma_unmap_single(eth-\u003edev, e-\u003edma_addr, e-\u003edma_len,\n+\t\t\t\t DMA_TO_DEVICE);\n+\t\tbreak;\n+\tcase AIROHA_DMA_UNMAPPED:\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\te-\u003edma_type = AIROHA_DMA_UNMAPPED;\n+}\n+\n+static int airoha_xdp_submit_frame(struct airoha_gdm_dev *dev,\n+\t\t\t\t struct xdp_frame *xdpf,\n+\t\t\t\t struct airoha_queue *q,\n+\t\t\t\t int qid, u32 msg0, u32 msg1,\n+\t\t\t\t bool dma_map)\n+{\n+\tstruct airoha_queue_entry *e, *next_e;\n+\tint len = xdpf-\u003elen, nr_frags, i;\n+\tstruct airoha_qdma_desc *desc;\n+\tstruct skb_shared_info *sinfo;\n+\tvoid *data = xdpf-\u003edata;\n+\tu16 index, next_index;\n+\tLIST_HEAD(tx_list);\n+\tdma_addr_t addr;\n+\tu32 val;\n+\n+\tsinfo = xdp_get_shared_info_from_frame(xdpf);\n+\tnr_frags = unlikely(xdp_frame_has_frags(xdpf)) ? sinfo-\u003enr_frags : 0;\n+\n+\tif (q-\u003equeued \u003e= q-\u003endesc - 1 - nr_frags)\n+\t\treturn -EBUSY;\n+\n+\tfor (i = 0; i \u003c= nr_frags; i++) {\n+\t\tif (dma_map) {\n+\t\t\tif (i == 0)\n+\t\t\t\taddr = dma_map_single(dev-\u003eeth-\u003edev, data, len,\n+\t\t\t\t\t\t DMA_TO_DEVICE);\n+\t\t\telse\n+\t\t\t\taddr = dma_map_page(dev-\u003eeth-\u003edev, virt_to_page(data),\n+\t\t\t\t\t\t offset_in_page(data), len,\n+\t\t\t\t\t\t DMA_TO_DEVICE);\n+\t\t\tif (unlikely(dma_mapping_error(dev-\u003eeth-\u003edev, addr)))\n+\t\t\t\tgoto unmap;\n+\t\t} else {\n+\t\t\tstruct page *page = virt_to_head_page(data);\n+\n+\t\t\taddr = page_pool_get_dma_addr(page) +\n+\t\t\t (data - (void *)page_address(page));\n+\t\t\tdma_sync_single_for_device(dev-\u003eeth-\u003edev, addr, len,\n+\t\t\t\t\t\t DMA_BIDIRECTIONAL);\n+\t\t}\n+\n+\t\te = list_first_entry(\u0026q-\u003etx_list, struct airoha_queue_entry, list);\n+\t\tlist_move_tail(\u0026e-\u003elist, \u0026tx_list);\n+\n+\t\tindex = e - q-\u003eentry;\n+\t\tdesc = \u0026q-\u003edesc[index];\n+\n+\t\te-\u003eis_xdpf = true;\n+\t\te-\u003exdpf = (i == nr_frags) ? xdpf : NULL;\n+\t\te-\u003edma_addr = addr;\n+\t\te-\u003edma_len = len;\n+\t\tif (dma_map)\n+\t\t\te-\u003edma_type = i == 0 ? AIROHA_DMA_MAP_SINGLE : AIROHA_DMA_MAP_PAGE;\n+\t\telse\n+\t\t\te-\u003edma_type = AIROHA_DMA_UNMAPPED;\n+\n+\t\tnext_e = list_first_entry(\u0026q-\u003etx_list, struct airoha_queue_entry, list);\n+\t\tnext_index = next_e - q-\u003eentry;\n+\n+\t\tval = FIELD_PREP(QDMA_DESC_LEN_MASK, len);\n+\t\tif (i \u003c nr_frags)\n+\t\t\tval |= FIELD_PREP(QDMA_DESC_MORE_MASK, 1);\n+\t\tWRITE_ONCE(desc-\u003ectrl, cpu_to_le32(val));\n+\t\tWRITE_ONCE(desc-\u003eaddr, cpu_to_le32(addr));\n+\t\tval = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, next_index);\n+\t\tWRITE_ONCE(desc-\u003edata, cpu_to_le32(val));\n+\t\tWRITE_ONCE(desc-\u003emsg0, cpu_to_le32(msg0));\n+\t\tWRITE_ONCE(desc-\u003emsg1, cpu_to_le32(msg1));\n+\t\tWRITE_ONCE(desc-\u003emsg2, cpu_to_le32(0xffff));\n+\n+\t\tq-\u003equeued++;\n+\n+\t\tif (i \u003c nr_frags) {\n+\t\t\tskb_frag_t *frag = \u0026sinfo-\u003efrags[i];\n+\n+\t\t\tdata = skb_frag_address(frag);\n+\t\t\tlen = skb_frag_size(frag);\n+\t\t}\n+\t}\n+\n+\treturn next_index;\n+\n+unmap:\n+\tlist_for_each_entry(e, \u0026tx_list, list) {\n+\t\tairoha_unmap_xmit_buf(dev-\u003eeth, e);\n+\t\te-\u003eis_xdpf = false;\n+\t\te-\u003exdpf = NULL;\n+\t\tq-\u003equeued--;\n+\t}\n+\tlist_splice(\u0026tx_list, \u0026q-\u003etx_list);\n+\n+\treturn -ENOMEM;\n+}\n+\n+static int airoha_xdp_xmit_back(struct net_device *netdev, struct xdp_buff *xdp,\n+\t\t\t\tstruct airoha_qdma *qdma)\n+{\n+\tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n+\tstruct airoha_queue *q;\n+\tstruct xdp_frame *xdpf;\n+\tu32 msg0, msg1;\n+\tint qid, index;\n+\tu8 fport;\n+\n+\txdpf = xdp_convert_buff_to_frame(xdp);\n+\tif (unlikely(!xdpf))\n+\t\treturn -EOVERFLOW;\n+\n+\tqid = airoha_qdma_get_txq(qdma, smp_processor_id());\n+\tq = \u0026qdma-\u003eq_tx[qid];\n+\n+\tmsg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,\n+\t\t\t qid / AIROHA_NUM_QOS_QUEUES) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,\n+\t\t\t qid % AIROHA_NUM_QOS_QUEUES);\n+\n+\tfport = airoha_get_fe_port(dev);\n+\tmsg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev-\u003enbq) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);\n+\n+\tspin_lock(\u0026q-\u003elock);\n+\tindex = airoha_xdp_submit_frame(dev, xdpf, q, qid, msg0, msg1, false);\n+\tif (unlikely(index \u003c 0)) {\n+\t\tspin_unlock(\u0026q-\u003elock);\n+\t\treturn index;\n+\t}\n+\n+\tairoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),\n+\t\t\tTX_RING_CPU_IDX_MASK,\n+\t\t\tFIELD_PREP(TX_RING_CPU_IDX_MASK, index));\n+\tspin_unlock(\u0026q-\u003elock);\n+\n+\treturn 0;\n+}\n+\n+static int airoha_xdp_xmit(struct net_device *netdev, int n,\n+\t\t\t struct xdp_frame **frames, u32 flags)\n+{\n+\tint qid, index = 0, last_index = -1, i, drops = 0;\n+\tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n+\tstruct airoha_qdma *qdma;\n+\tstruct airoha_queue *q;\n+\tu32 msg0, msg1;\n+\tu8 fport;\n+\n+\tif (unlikely(flags \u0026 ~XDP_XMIT_FLAGS_MASK))\n+\t\treturn -EINVAL;\n+\n+\trcu_read_lock();\n+\tqdma = rcu_dereference(dev-\u003eqdma);\n+\tif (!qdma) {\n+\t\trcu_read_unlock();\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tqid = airoha_qdma_get_txq(qdma, smp_processor_id());\n+\tq = \u0026qdma-\u003eq_tx[qid];\n+\n+\tmsg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,\n+\t\t\t qid / AIROHA_NUM_QOS_QUEUES) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,\n+\t\t\t qid % AIROHA_NUM_QOS_QUEUES);\n+\n+\tfport = airoha_get_fe_port(dev);\n+\tmsg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev-\u003enbq) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |\n+\t FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);\n+\n+\tspin_lock(\u0026q-\u003elock);\n+\tfor (i = 0; i \u003c n; i++) {\n+\t\tindex = airoha_xdp_submit_frame(dev, frames[i], q, qid, msg0, msg1, true);\n+\t\tif (unlikely(index \u003c 0)) {\n+\t\t\txdp_return_frame_rx_napi(frames[i]);\n+\t\t\tdrops++;\n+\t\t} else {\n+\t\t\tlast_index = index;\n+\t\t}\n+\t}\n+\tif (n \u003e drops \u0026\u0026 (flags \u0026 XDP_XMIT_FLUSH))\n+\t\tairoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),\n+\t\t\t\tTX_RING_CPU_IDX_MASK,\n+\t\t\t\tFIELD_PREP(TX_RING_CPU_IDX_MASK, last_index));\n+\tspin_unlock(\u0026q-\u003elock);\n+\trcu_read_unlock();\n+\n+\tu64_stats_update_begin(\u0026dev-\u003estats.syncp);\n+\tdev-\u003estats.xdp_stats.tx_xdp_xmit += n - drops;\n+\tdev-\u003estats.xdp_stats.tx_xdp_xmit_errors += drops;\n+\tu64_stats_update_end(\u0026dev-\u003estats.syncp);\n+\n+\treturn n - drops;\n+}\n+\n+static bool airoha_run_xdp(struct net_device *netdev, struct bpf_prog *prog,\n+\t\t\t struct xdp_buff *xdp, struct airoha_queue *q,\n+\t\t\t struct airoha_queue_entry *e, struct page *page)\n+{\n+\tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n+\tstruct airoha_hw_stats *hw_stats = \u0026dev-\u003estats;\n+\tu32 act = bpf_prog_run_xdp(prog, xdp);\n+\tu64 *count;\n+\n+\tswitch (act) {\n+\tcase XDP_PASS:\n+\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_pass;\n+\t\tgoto update_stats;\n+\tcase XDP_TX:\n+\t\tif (unlikely(airoha_xdp_xmit_back(netdev, xdp, q-\u003eqdma) \u003c 0)) {\n+\t\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_tx_errors;\n+\t\t\ttrace_xdp_exception(netdev, prog, act);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\te-\u003ebuf = NULL;\n+\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_tx;\n+\t\tgoto update_stats;\n+\tcase XDP_REDIRECT:\n+\t\tif (unlikely(xdp_do_redirect(netdev, xdp, prog) \u003c 0)) {\n+\t\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_redirect_errors;\n+\t\t\ttrace_xdp_exception(netdev, prog, act);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\te-\u003ebuf = NULL;\n+\t\tq-\u003exdp_flush = true;\n+\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_redirect;\n+\t\tgoto update_stats;\n+\tdefault:\n+\t\tbpf_warn_invalid_xdp_action(netdev, prog, act);\n+\t\tfallthrough;\n+\tcase XDP_ABORTED:\n+\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_aborted;\n+\t\ttrace_xdp_exception(netdev, prog, act);\n+\t\tbreak;\n+\tcase XDP_DROP:\n+\t\tcount = \u0026hw_stats-\u003exdp_stats.rx_xdp_drop;\n+\t\tbreak;\n+\t}\n+\n+\tpage_pool_put_full_page(q-\u003epage_pool, page, true);\n+\n+update_stats:\n+\tu64_stats_update_begin(\u0026hw_stats-\u003esyncp);\n+\t*count = *count + 1;\n+\tu64_stats_update_end(\u0026hw_stats-\u003esyncp);\n+\n+\treturn act != XDP_PASS;\n+}\n+\n static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)\n {\n \tenum dma_data_direction dir = page_pool_get_dma_dir(q-\u003epage_pool);\n@@ -698,6 +969,27 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)\n \n \t\tnetdev = netdev_from_priv(dev);\n \t\tif (!q-\u003eskb) { /* first buffer */\n+\t\t\tstruct bpf_prog *xdp_prog;\n+\n+\t\t\trcu_read_lock();\n+\t\t\txdp_prog = rcu_dereference(dev-\u003exdp_prog);\n+\t\t\tif (xdp_prog) {\n+\t\t\t\tstruct xdp_buff xdp;\n+\n+\t\t\t\txdp_init_buff(\u0026xdp, q-\u003ebuf_size, \u0026q-\u003exdp_rxq);\n+\t\t\t\txdp_prepare_buff(\u0026xdp, e-\u003ebuf - AIROHA_RX_HEADROOM,\n+\t\t\t\t\t\t AIROHA_RX_HEADROOM, len, false);\n+\n+\t\t\t\tif (airoha_run_xdp(netdev, xdp_prog, \u0026xdp, q, e, page)) {\n+\t\t\t\t\trcu_read_unlock();\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tlen = xdp.data_end - xdp.data;\n+\t\t\t\te-\u003ebuf = xdp.data;\n+\t\t\t}\n+\t\t\trcu_read_unlock();\n+\n \t\t\tq-\u003eskb = napi_build_skb(e-\u003ebuf - AIROHA_RX_HEADROOM,\n \t\t\t\t\t\tq-\u003ebuf_size);\n \t\t\tif (!q-\u003eskb)\n@@ -779,6 +1071,11 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)\n \t\tdone += cur;\n \t} while (cur \u0026\u0026 done \u003c budget);\n \n+\tif (q-\u003exdp_flush) {\n+\t\txdp_do_flush();\n+\t\tq-\u003exdp_flush = false;\n+\t}\n+\n \tif (done \u003c budget \u0026\u0026 napi_complete(napi)) {\n \t\tstruct airoha_qdma *qdma = q-\u003eqdma;\n \t\tint i, qid = q - \u0026qdma-\u003eq_rx[0];\n@@ -804,17 +1101,17 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,\n \t\t.order = 0,\n \t\t.pool_size = 256,\n \t\t.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,\n-\t\t.dma_dir = DMA_FROM_DEVICE,\n+\t\t.dma_dir = DMA_BIDIRECTIONAL,\n \t\t.max_len = PAGE_SIZE,\n \t\t.nid = NUMA_NO_NODE,\n \t\t.dev = qdma-\u003eeth-\u003edev,\n \t\t.napi = \u0026q-\u003enapi,\n \t};\n+\tint qid = q - \u0026qdma-\u003eq_rx[0], thr, err;\n \tstruct airoha_eth *eth = qdma-\u003eeth;\n-\tint qid = q - \u0026qdma-\u003eq_rx[0], thr;\n \tdma_addr_t dma_addr;\n \n-\tq-\u003ebuf_size = PAGE_SIZE / 2;\n+\tq-\u003ebuf_size = PAGE_SIZE;\n \tq-\u003eqdma = qdma;\n \n \tq-\u003eentry = devm_kzalloc(eth-\u003edev, ndesc * sizeof(*q-\u003eentry),\n@@ -829,15 +1126,22 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,\n \n \tq-\u003epage_pool = page_pool_create(\u0026pp_params);\n \tif (IS_ERR(q-\u003epage_pool)) {\n-\t\tint err = PTR_ERR(q-\u003epage_pool);\n-\n-\t\tq-\u003epage_pool = NULL;\n-\t\treturn err;\n+\t\terr = PTR_ERR(q-\u003epage_pool);\n+\t\tgoto err_page_pool_create;\n \t}\n \n \tq-\u003endesc = ndesc;\n \tnetif_napi_add(eth-\u003enapi_dev, \u0026q-\u003enapi, airoha_qdma_rx_napi_poll);\n \n+\terr = xdp_rxq_info_reg(\u0026q-\u003exdp_rxq, eth-\u003enapi_dev, qid, q-\u003enapi.napi_id);\n+\tif (err)\n+\t\tgoto err_xdp_rxq_info_reg;\n+\n+\terr = xdp_rxq_info_reg_mem_model(\u0026q-\u003exdp_rxq, MEM_TYPE_PAGE_POOL,\n+\t\t\t\t\t q-\u003epage_pool);\n+\tif (err)\n+\t\tgoto err_xdp_rxq_info_reg_mem_model;\n+\n \tairoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);\n \tairoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),\n \t\t\tRX_RING_SIZE_MASK,\n@@ -853,6 +1157,14 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,\n \tairoha_qdma_fill_rx_queue(q);\n \n \treturn 0;\n+\n+err_xdp_rxq_info_reg_mem_model:\n+\txdp_rxq_info_unreg(\u0026q-\u003exdp_rxq);\n+err_xdp_rxq_info_reg:\n+\tpage_pool_destroy(q-\u003epage_pool);\n+err_page_pool_create:\n+\tq-\u003epage_pool = NULL;\n+\treturn err;\n }\n \n static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)\n@@ -882,6 +1194,9 @@ static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)\n \t\tq-\u003equeued--;\n \t}\n \n+\tif (xdp_rxq_info_is_reg(\u0026q-\u003exdp_rxq))\n+\t\txdp_rxq_info_unreg(\u0026q-\u003exdp_rxq);\n+\n \tq-\u003ehead = q-\u003etail;\n \t/* Set RX_DMA_IDX to RX_CPU_IDX to notify the hw the QDMA RX ring is\n \t * empty.\n@@ -949,25 +1264,6 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)\n \tq-\u003etxq_stopped = false;\n }\n \n-static void airoha_unmap_xmit_buf(struct airoha_eth *eth,\n-\t\t\t\t struct airoha_queue_entry *e)\n-{\n-\tswitch (e-\u003edma_type) {\n-\tcase AIROHA_DMA_MAP_PAGE:\n-\t\tdma_unmap_page(eth-\u003edev, e-\u003edma_addr, e-\u003edma_len,\n-\t\t\t DMA_TO_DEVICE);\n-\t\tbreak;\n-\tcase AIROHA_DMA_MAP_SINGLE:\n-\t\tdma_unmap_single(eth-\u003edev, e-\u003edma_addr, e-\u003edma_len,\n-\t\t\t\t DMA_TO_DEVICE);\n-\t\tbreak;\n-\tcase AIROHA_DMA_UNMAPPED:\n-\tdefault:\n-\t\tbreak;\n-\t}\n-\te-\u003edma_type = AIROHA_DMA_UNMAPPED;\n-}\n-\n static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)\n {\n \tstruct airoha_tx_irq_queue *irq_q;\n@@ -1037,7 +1333,12 @@ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)\n \t\tWRITE_ONCE(desc-\u003emsg1, 0);\n \t\tq-\u003equeued--;\n \n-\t\tif (skb) {\n+\t\tif (e-\u003eis_xdpf) {\n+\t\t\tif (e-\u003exdpf)\n+\t\t\t\txdp_return_frame(e-\u003exdpf);\n+\t\t\te-\u003eis_xdpf = false;\n+\t\t\te-\u003exdpf = NULL;\n+\t\t} else if (e-\u003eskb) {\n \t\t\tstruct airoha_gdm_dev *dev = netdev_priv(skb-\u003edev);\n \t\t\tu16 qidx = skb_get_queue_mapping(skb);\n \t\t\tstruct netdev_queue *txq;\n@@ -1218,7 +1519,12 @@ static void airoha_qdma_tx_cleanup(struct airoha_qdma *qdma)\n \t\t\tWRITE_ONCE(desc-\u003emsg1, 0);\n \t\t\tWRITE_ONCE(desc-\u003emsg2, 0);\n \n-\t\t\tif (skb) {\n+\t\t\tif (e-\u003eis_xdpf) {\n+\t\t\t\tif (e-\u003exdpf)\n+\t\t\t\t\txdp_return_frame(e-\u003exdpf);\n+\t\t\t\te-\u003eis_xdpf = false;\n+\t\t\t\te-\u003exdpf = NULL;\n+\t\t\t} else if (skb) {\n \t\t\t\tstruct netdev_queue *txq;\n \n \t\t\t\ttxq = skb_get_tx_queue(skb-\u003edev, skb);\n@@ -2201,6 +2507,12 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)\n \tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n \tstruct airoha_gdm_port *port = dev-\u003eport;\n \n+\tif (rcu_access_pointer(dev-\u003exdp_prog) \u0026\u0026 mtu \u003e AIROHA_RX_MAX_BUF_SIZE) {\n+\t\tnetdev_err(netdev, \"MTU too large for XDP (max %lu)\\n\",\n+\t\t\t AIROHA_RX_MAX_BUF_SIZE);\n+\t\treturn -EINVAL;\n+\t}\n+\n \tWRITE_ONCE(netdev-\u003emtu, mtu);\n \tif (port-\u003eusers)\n \t\tairoha_dev_set_xmit_frame_size(netdev);\n@@ -2377,6 +2689,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,\n \n \t\tlist_move_tail(\u0026e-\u003elist, \u0026tx_list);\n \t\te-\u003eskb = i == nr_frags - 1 ? skb : NULL;\n+\t\te-\u003eis_xdpf = false;\n \t\te-\u003edma_addr = addr;\n \t\te-\u003edma_len = len;\n \n@@ -3321,6 +3634,39 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,\n \treturn 0;\n }\n \n+static int airoha_xdp_setup(struct net_device *netdev, struct bpf_prog *prog,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n+\tstruct bpf_prog *old_prog;\n+\n+\tif (netdev-\u003efeatures \u0026 NETIF_F_LRO) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"XDP is not supported with LRO\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tif (netdev-\u003emtu \u003e AIROHA_RX_MAX_BUF_SIZE) {\n+\t\tNL_SET_ERR_MSG_MOD(extack, \"MTU too large for XDP\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\told_prog = rcu_replace_pointer(dev-\u003exdp_prog, prog, lockdep_rtnl_is_held());\n+\tif (old_prog)\n+\t\tbpf_prog_put(old_prog);\n+\n+\treturn 0;\n+}\n+\n+static int airoha_xdp_bpf(struct net_device *netdev, struct netdev_bpf *xdp)\n+{\n+\tswitch (xdp-\u003ecommand) {\n+\tcase XDP_SETUP_PROG:\n+\t\treturn airoha_xdp_setup(netdev, xdp-\u003eprog, xdp-\u003eextack);\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+}\n+\n static int airoha_dev_tc_setup(struct net_device *dev,\n \t\t\t enum tc_setup_type type, void *type_data)\n {\n@@ -3347,14 +3693,100 @@ static const struct net_device_ops airoha_netdev_ops = {\n \t.ndo_get_stats64 = airoha_dev_get_stats64,\n \t.ndo_set_mac_address\t= airoha_dev_set_macaddr,\n \t.ndo_setup_tc\t\t= airoha_dev_tc_setup,\n+\t.ndo_bpf\t\t= airoha_xdp_bpf,\n+\t.ndo_xdp_xmit\t\t= airoha_xdp_xmit,\n };\n \n+#define AIROHA_ETHTOOL_XDP_STAT(x) { #x, \\\n+\t\t\t\t offsetof(struct airoha_hw_stats, xdp_stats.x) / \\\n+\t\t\t\t sizeof(u64) }\n+\n+static const struct {\n+\tconst char name[ETH_GSTRING_LEN];\n+\tu32 offset;\n+} airoha_ethtool_xdp_stats[] = {\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_redirect_errors),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_pass),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_aborted),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_drop),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx),\n+\tAIROHA_ETHTOOL_XDP_STAT(rx_xdp_tx_errors),\n+\tAIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit),\n+\tAIROHA_ETHTOOL_XDP_STAT(tx_xdp_xmit_errors),\n+};\n+\n+static void airoha_ethtool_get_strings(struct net_device *netdev, u32 stringset, u8 *data)\n+{\n+\tint i;\n+\n+\tswitch (stringset) {\n+\tcase ETH_SS_STATS:\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(airoha_ethtool_xdp_stats); i++) {\n+\t\t\tstrscpy(data, airoha_ethtool_xdp_stats[i].name, ETH_GSTRING_LEN);\n+\t\t\tdata += ETH_GSTRING_LEN;\n+\t\t}\n+\t\tpage_pool_ethtool_stats_get_strings(data);\n+\t\tbreak;\n+\t}\n+}\n+\n+static int airoha_ethtool_get_sset_count(struct net_device *netdev, int stringset)\n+{\n+\tswitch (stringset) {\n+\tcase ETH_SS_STATS:\n+\t\treturn ARRAY_SIZE(airoha_ethtool_xdp_stats) + page_pool_ethtool_stats_get_count();\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+}\n+\n+static void airoha_ethtool_get_ethtool_stats(struct net_device *netdev,\n+\t\t\t\t\t struct ethtool_stats *stats,\n+\t\t\t\t\t u64 *data)\n+{\n+\tstruct airoha_gdm_dev *dev = netdev_priv(netdev);\n+\tstruct airoha_hw_stats *hw_stats = \u0026dev-\u003estats;\n+\tu64 *hw_stats_base = (u64 *)hw_stats;\n+\tstruct page_pool_stats pp_stats = {};\n+\tstruct airoha_qdma *qdma;\n+\tunsigned int start;\n+\tint i;\n+\n+\tif (netif_running(netdev))\n+\t\tairoha_update_hw_stats(dev);\n+\n+\tdo {\n+\t\tstart = u64_stats_fetch_begin(\u0026hw_stats-\u003esyncp);\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(airoha_ethtool_xdp_stats); i++)\n+\t\t\tdata[i] = hw_stats_base[airoha_ethtool_xdp_stats[i].offset];\n+\t} while (u64_stats_fetch_retry(\u0026hw_stats-\u003esyncp, start));\n+\n+\trcu_read_lock();\n+\tqdma = rcu_dereference(dev-\u003eqdma);\n+\tif (qdma) {\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(qdma-\u003eq_rx); i++) {\n+\t\t\tstruct airoha_queue *q = \u0026qdma-\u003eq_rx[i];\n+\n+\t\t\tif (q-\u003epage_pool)\n+\t\t\t\tpage_pool_get_stats(q-\u003epage_pool, \u0026pp_stats);\n+\t\t}\n+\t}\n+\trcu_read_unlock();\n+\n+\tpage_pool_ethtool_stats_get(data + ARRAY_SIZE(airoha_ethtool_xdp_stats),\n+\t\t\t\t \u0026pp_stats);\n+}\n+\n static const struct ethtool_ops airoha_ethtool_ops = {\n \t.get_drvinfo\t\t= airoha_ethtool_get_drvinfo,\n \t.get_eth_mac_stats = airoha_ethtool_get_mac_stats,\n \t.get_rmon_stats\t\t= airoha_ethtool_get_rmon_stats,\n \t.get_link_ksettings\t= phy_ethtool_get_link_ksettings,\n \t.get_link\t\t= ethtool_op_get_link,\n+\t.get_strings\t\t= airoha_ethtool_get_strings,\n+\t.get_sset_count\t\t= airoha_ethtool_get_sset_count,\n+\t.get_ethtool_stats\t= airoha_ethtool_get_ethtool_stats,\n };\n \n static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)\n@@ -3435,6 +3867,9 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,\n \t\t\t NETIF_F_HW_TC;\n \tnetdev-\u003efeatures |= netdev-\u003ehw_features;\n \tnetdev-\u003evlan_features = netdev-\u003ehw_features;\n+\tnetdev-\u003exdp_features = NETDEV_XDP_ACT_BASIC |\n+\t\t\t\tNETDEV_XDP_ACT_REDIRECT |\n+\t\t\t\tNETDEV_XDP_ACT_NDO_XMIT;\n \tSET_NETDEV_DEV(netdev, eth-\u003edev);\n \n \t/* reserve hw queues for HTB offloading */\ndiff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h\nindex 8277c1c87bb32..d2ac295c49e9a 100644\n--- a/drivers/net/ethernet/airoha/airoha_eth.h\n+++ b/drivers/net/ethernet/airoha/airoha_eth.h\n@@ -7,6 +7,7 @@\n #ifndef AIROHA_ETH_H\n #define AIROHA_ETH_H\n \n+#include \u003clinux/bpf.h\u003e\n #include \u003clinux/debugfs.h\u003e\n #include \u003clinux/etherdevice.h\u003e\n #include \u003clinux/iopoll.h\u003e\n@@ -15,6 +16,7 @@\n #include \u003clinux/reset.h\u003e\n #include \u003clinux/soc/airoha/airoha_offload.h\u003e\n #include \u003cnet/dsa.h\u003e\n+#include \u003cnet/xdp.h\u003e\n \n #define AIROHA_MAX_NUM_GDM_PORTS\t4\n #define AIROHA_MAX_NUM_GDM_DEVS\t\t2\n@@ -34,7 +36,11 @@\n #define AIROHA_FE_MC_MAX_VLAN_TABLE\t64\n #define AIROHA_FE_MC_MAX_VLAN_PORT\t16\n #define AIROHA_NUM_TX_IRQ\t\t2\n-#define AIROHA_RX_HEADROOM\t\t(NET_SKB_PAD + NET_IP_ALIGN)\n+#define AIROHA_RX_HEADROOM\t\t(XDP_PACKET_HEADROOM + NET_IP_ALIGN)\n+#define AIROHA_RX_PAD\t\t\t(AIROHA_RX_HEADROOM + \\\n+\t\t\t\t\t SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))\n+#define AIROHA_RX_MAX_BUF_SIZE\t\t(PAGE_SIZE - AIROHA_RX_PAD - \\\n+\t\t\t\t\t VLAN_ETH_HLEN - ETH_FCS_LEN)\n #define AIROHA_RX_LEN(_n)\t\t((_n) - AIROHA_RX_HEADROOM)\n #define HW_DSCP_NUM\t\t\t2048\n #define IRQ_QUEUE_LEN(_n)\t\t((_n) ? 1024 : 2048)\n@@ -182,12 +188,16 @@ struct airoha_queue_entry {\n \t\tvoid *buf;\n \t\tstruct {\n \t\t\tstruct list_head list;\n-\t\t\tstruct sk_buff *skb;\n+\t\t\tunion {\n+\t\t\t\tstruct sk_buff *skb;\n+\t\t\t\tstruct xdp_frame *xdpf;\n+\t\t\t};\n \t\t\tenum airoha_dma_map_type dma_type;\n \t\t};\n \t};\n \tdma_addr_t dma_addr;\n \tu16 dma_len;\n+\tbool is_xdpf;\n };\n \n struct airoha_queue {\n@@ -211,6 +221,9 @@ struct airoha_queue {\n \tstruct page_pool *page_pool;\n \tstruct sk_buff *skb;\n \n+\tstruct xdp_rxq_info xdp_rxq;\n+\tbool xdp_flush;\n+\n \tstruct list_head tx_list;\n };\n \n@@ -223,6 +236,18 @@ struct airoha_tx_irq_queue {\n \tu32 *q;\n };\n \n+struct airoha_xdp_stats {\n+\tu64 rx_xdp_redirect;\n+\tu64 rx_xdp_redirect_errors;\n+\tu64 rx_xdp_pass;\n+\tu64 rx_xdp_aborted;\n+\tu64 rx_xdp_drop;\n+\tu64 rx_xdp_tx;\n+\tu64 rx_xdp_tx_errors;\n+\tu64 tx_xdp_xmit;\n+\tu64 tx_xdp_xmit_errors;\n+};\n+\n struct airoha_hw_stats {\n \tstruct u64_stats_sync syncp;\n \n@@ -246,6 +271,8 @@ struct airoha_hw_stats {\n \tu64 rx_jabber;\n \tu64 rx_len[7];\n \n+\tstruct airoha_xdp_stats xdp_stats;\n+\n \tstruct {\n \t\t/* Previous HW register values for 32-bit counter delta\n \t\t * tracking. Storing the last seen value and accumulating\n@@ -589,6 +616,8 @@ struct airoha_gdm_dev {\n \tunsigned long flags;\n \tint nbq;\n \n+\tstruct bpf_prog __rcu *xdp_prog;\n+\n \tstruct airoha_hw_stats stats;\n \n \t/* Serialize netdev_tx_completed_queue() calls per TX queue during\n"
} |
| 2/1 | 2026/09/22 16:32 | agent | patch-evaluator |
50mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds XDP support to the Airoha Ethernet driver (drivers/net/ethernet/airoha/airoha_eth.c). This is a platform driver for Airoha SoCs (e.g. EN7581) requiring specific device tree bindings and physical SoC hardware resources (MMIO registers, reset lines, clocks) that are not emulated in standard QEMU/GCE virtualized environments on the target architecture (amd64). As such, the modified code is structurally unreachable.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 281615644eeb6e7c93837e03b73f70115302a9d9
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 22 16:32:11 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/airoha/Kconfig b/drivers/net/ethernet/airoha/Kconfig
index 1f6640a15fc94..3fa7683a79e9d 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 64619e9a704dd..29b8372048422 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,276 @@ 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();
+
+ 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;
+}
+
+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:
+ 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);
+ 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);
+ 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);
+ break;
+ case XDP_DROP:
+ count = &hw_stats->xdp_stats.rx_xdp_drop;
+ break;
+ }
+
+ 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)
{
enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
@@ -698,6 +969,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 +1071,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 +1101,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 +1126,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 +1157,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 +1194,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 +1264,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 +1333,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 +1519,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 +2507,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 +2689,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 +3634,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,14 +3693,100 @@ 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,
};
+#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)
@@ -3435,6 +3867,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 8277c1c87bb32..d2ac295c49e9a 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;
};
@@ -223,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;
@@ -246,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
@@ -589,6 +616,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
|
| 3/2 | 2026/09/22 16:32 | llm | patch-evaluator |
50mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12773 output: 38 thoughts: 218 Thoughts: **Evaluating the Patch Series for Syzkaller Fuzzing Suitability** Okay, so the task is to determine if this kernel patch series is a good candidate for fuzzing with syzkaller. I need to make a quick assessment based on a few key factors. First, the target architecture is amd64, which is a good starting point as syzkaller supports it well. Now, the core triage philosophy needs to be applied, so keep the following in mind: REACHABILITY IS KEY! That means focusing on patches affecting functionality that's easily triggered in a virtualized environment. Things like GCE or QEMU using emulated devices are preferred, specifically software emulated USB gadgets, netdev, or tun/tap. We want to avoid anything structurally unreachable, like drivers that rely on special hardware that isn't commonly emulated in standard QEMU. That includes vendor-specific PCIe switches, SmartNICs, GPU drivers, or anything requiring physical ASIC/PCIe/SoC cards. I should also be mindful of drivers that require teardown callbacks and so on. The question is, what's in `drivers/net/ethernet/airoha/`? This sounds like it could be interesting since it's network-related, which often interacts well with syzkaller. Let's quickly investigate the code. I'll need to check the `Kconfig` file and the main driver file, `airoha_eth.c`. The easiest way to get an initial overview is to use `grepper` or `read-file` to get the lay of the land. Then I can figure out whether it's worth the effort of getting it fuzzed. |
| 4/2 | 2026/09/22 17:22 | tool | read-file |
0mArgs:{
"File": "drivers/net/ethernet/airoha/Kconfig",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\tconfig NET_VENDOR_AIROHA\n 3:\t\tbool \"Airoha devices\"\n 4:\t\tdepends on ARCH_AIROHA || COMPILE_TEST\n 5:\t\thelp\n 6:\t\t If you have a Airoha SoC with ethernet, say Y.\n 7:\t\n 8:\tif NET_VENDOR_AIROHA\n 9:\t\n 10:\tconfig NET_AIROHA_NPU\n 11:\t\ttristate \"Airoha NPU support\"\n 12:\t\tselect WANT_DEV_COREDUMP\n 13:\t\tselect REGMAP_MMIO\n 14:\t\thelp\n 15:\t\t This driver supports Airoha Network Processor (NPU) available\n 16:\t\t on the Airoha Soc family.\n 17:\t\n 18:\tconfig NET_AIROHA\n 19:\t\ttristate \"Airoha SoC Gigabit Ethernet support\"\n 20:\t\tdepends on NET_DSA || !NET_DSA\n 21:\t\tselect NET_AIROHA_NPU\n 22:\t\tselect PAGE_POOL\n 23:\t\tselect PAGE_POOL_STATS\n 24:\t\thelp\n 25:\t\t This driver supports the gigabit ethernet MACs in the\n 26:\t\t Airoha SoC family.\n 27:\t\n 28:\tconfig NET_AIROHA_FLOW_STATS\n 29:\t\tdefault y\n 30:\t\tbool \"Airoha flow stats\"\n 31:\t\tdepends on NET_AIROHA \u0026\u0026 NET_AIROHA_NPU\n 32:\t\thelp\n 33:\t\t Enable Airoha flowtable statistic counters.\n 34:\t\n 35:\tendif #NET_VENDOR_AIROHA\n"
} |
| 5/2 | 2026/09/22 17:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 13845 output: 39 thoughts: 60 Thoughts: **Analyzing the Airoha Driver Dependency** Okay, let's break this down. My initial observation is that `NET_VENDOR_AIROHA` hinges on either `ARCH_AIROHA` or `COMPILE_TEST`. That's a conditional dependency, nothing unusual there. The next logical step is to understand what kind of driver `NET_AIROHA` actually *is*. Is it an Ethernet driver, a Wi-Fi driver, something else entirely? I need context. To get the necessary context I will dive into `airoha_eth.c` and begin by checking how the driver probes. Specifically, I'm going to look for the `probe` function. That's where the driver will introduce itself to the hardware and the kernel, giving me the details I need to start forming a clear picture. |
| 6/2 | 2026/09/22 17:22 | tool | grepper |
0mArgs:{
"Expression": "module_platform_driver|platform_driver",
"PathPrefix": "drivers/net/ethernet/airoha/"
}Results: {
"Output": "drivers/net/ethernet/airoha/airoha_eth.c=4387=MODULE_DEVICE_TABLE(of, of_airoha_match);\ndrivers/net/ethernet/airoha/airoha_eth.c-4388-\ndrivers/net/ethernet/airoha/airoha_eth.c:4389:static struct platform_driver airoha_driver = {\ndrivers/net/ethernet/airoha/airoha_eth.c-4390-\t.probe = airoha_probe,\n--\ndrivers/net/ethernet/airoha/airoha_eth.c-4396-};\ndrivers/net/ethernet/airoha/airoha_eth.c:4397:module_platform_driver(airoha_driver);\ndrivers/net/ethernet/airoha/airoha_eth.c-4398-\n--\ndrivers/net/ethernet/airoha/airoha_npu.c=806=static void airoha_npu_remove(struct platform_device *pdev)\n--\ndrivers/net/ethernet/airoha/airoha_npu.c-814-\ndrivers/net/ethernet/airoha/airoha_npu.c:815:static struct platform_driver airoha_npu_driver = {\ndrivers/net/ethernet/airoha/airoha_npu.c-816-\t.probe = airoha_npu_probe,\n--\ndrivers/net/ethernet/airoha/airoha_npu.c-822-};\ndrivers/net/ethernet/airoha/airoha_npu.c:823:module_platform_driver(airoha_npu_driver);\ndrivers/net/ethernet/airoha/airoha_npu.c-824-\n"
} |
| 7/2 | 2026/09/22 17:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14571 output: 36 thoughts: 31 Thoughts: Here's my analysis of the provided text, rendered as a first-person expert summary: **Diving into the Airoha Gigabit Ethernet Driver** Okay, so I see we're dealing with the `airoha_driver`, and immediately, my brain zeroes in on the key context: this is a *platform driver* specifically for the Airoha SoC's Gigabit Ethernet. That's a crucial piece of information, setting the stage for what I need to understand. Platform drivers are pretty standard, so I'll be thinking about the standard driver model here, with potential customizations for this specific SoC's architecture. Now, let's get down to brass tacks. The next thing I'm going to want to understand is how the driver identifies its hardware. The text mentions `of_airoha_match`. Given my experience, I'd bet this is the device tree match table, crucial for associating the driver with the right hardware nodes in the system. I'll need to scrutinize that table to see which device IDs or compatible strings it uses, along with any other identifying information. I'll need to check the match table entries to ensure they're correctly specifying the hardware this driver is meant for. |
| 8/2 | 2026/09/22 17:22 | tool | grepper |
0mArgs:{
"Expression": "of_airoha_match",
"PathPrefix": "drivers/net/ethernet/airoha/"
}Results: {
"Output": "drivers/net/ethernet/airoha/airoha_eth.c=4370=static const struct airoha_eth_soc_data an7583_soc_data = {\n--\ndrivers/net/ethernet/airoha/airoha_eth.c-4381-\ndrivers/net/ethernet/airoha/airoha_eth.c:4382:static const struct of_device_id of_airoha_match[] = {\ndrivers/net/ethernet/airoha/airoha_eth.c-4383-\t{ .compatible = \"airoha,en7581-eth\", .data = \u0026en7581_soc_data },\n--\ndrivers/net/ethernet/airoha/airoha_eth.c-4386-};\ndrivers/net/ethernet/airoha/airoha_eth.c:4387:MODULE_DEVICE_TABLE(of, of_airoha_match);\ndrivers/net/ethernet/airoha/airoha_eth.c-4388-\ndrivers/net/ethernet/airoha/airoha_eth.c=4389=static struct platform_driver airoha_driver = {\n--\ndrivers/net/ethernet/airoha/airoha_eth.c-4393-\t\t.name = KBUILD_MODNAME,\ndrivers/net/ethernet/airoha/airoha_eth.c:4394:\t\t.of_match_table = of_airoha_match,\ndrivers/net/ethernet/airoha/airoha_eth.c-4395-\t},\n"
} |
| 9/2 | 2026/09/22 17:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15291 output: 37 thoughts: 145 Thoughts: **My Assessment of the Airoha Driver** Alright, let's break down this `airoha_driver` situation. My initial thought is that it's designed to interface with the OpenFirmware or DeviceTree, specifically looking for those tell-tale markers: "airoha,en7581-eth" and similar identifiers. Clearly, we're talking about a platform driver here, designed for the Airoha EN7581 SoC family. I'm seeing it supports both ARM and MIPS based SoCs. Now, here's the kicker – the target architecture is listed as amd64, which raises a flag for me. My immediate concern is that this is an embedded SoC Gigabit Ethernet MAC. These devices are very dependent on the specific SoC hardware – the MMIO registers, the clocking, resets, interrupts, the works. All these things are tightly coupled with the "airoha,en7581-eth" Device Tree node. This isn't something that's casually emulated in standard x86_64 virtualization environments like QEMU or GCE. Given all of this, let's take a closer look at `airoha_probe`. I need to understand how the driver attempts to initialize and interact with the hardware. |
| 10/2 | 2026/09/22 17:22 | tool | grepper |
0mArgs:{
"Expression": "static int airoha_probe",
"PathPrefix": "drivers/net/ethernet/airoha/"
}Results: {
"Output": "drivers/net/ethernet/airoha/airoha_eth.c=3995=static int airoha_register_gdm_devices(struct airoha_eth *eth)\n--\ndrivers/net/ethernet/airoha/airoha_eth.c-4023-\ndrivers/net/ethernet/airoha/airoha_eth.c:4024:static int airoha_probe(struct platform_device *pdev)\ndrivers/net/ethernet/airoha/airoha_eth.c-4025-{\n"
} |
| 11/2 | 2026/09/22 17:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15887 output: 45 thoughts: 19 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue: **Deep Dive Verification Required** Right, let's just make absolutely certain of this. Before I proceed, I need to consult `airoha_probe`. I'm confident in my understanding, but given the critical nature of this area, I can't afford any assumptions or omissions. I need to corroborate my existing knowledge with the definitive source on this. It's time to meticulously examine the `airoha_probe` output to ensure complete accuracy and to confirm that my interpretation aligns perfectly with the current and canonical definition. No room for error here. This isn't just a cursory review; it's a deep dive to eliminate any residual doubt. |
| 12/2 | 2026/09/22 17:22 | tool | read-file |
0mArgs:{
"File": "drivers/net/ethernet/airoha/airoha_eth.c",
"FirstLine": 4024,
"LineCount": 50
}Results: {
"Contents": "4024:\tstatic int airoha_probe(struct platform_device *pdev)\n4025:\t{\n4026:\t\tstruct reset_control_bulk_data *xsi_rsts;\n4027:\t\tstruct device_node *np;\n4028:\t\tstruct airoha_eth *eth;\n4029:\t\tint i, err;\n4030:\t\n4031:\t\teth = devm_kzalloc(\u0026pdev-\u003edev, sizeof(*eth), GFP_KERNEL);\n4032:\t\tif (!eth)\n4033:\t\t\treturn -ENOMEM;\n4034:\t\n4035:\t\teth-\u003esoc = of_device_get_match_data(\u0026pdev-\u003edev);\n4036:\t\tif (!eth-\u003esoc)\n4037:\t\t\treturn -EINVAL;\n4038:\t\n4039:\t\teth-\u003edev = \u0026pdev-\u003edev;\n4040:\t\n4041:\t\terr = dma_set_mask_and_coherent(eth-\u003edev, DMA_BIT_MASK(32));\n4042:\t\tif (err) {\n4043:\t\t\tdev_err(eth-\u003edev, \"failed configuring DMA mask\\n\");\n4044:\t\t\treturn err;\n4045:\t\t}\n4046:\t\n4047:\t\teth-\u003efe_regs = devm_platform_ioremap_resource_byname(pdev, \"fe\");\n4048:\t\tif (IS_ERR(eth-\u003efe_regs))\n4049:\t\t\treturn dev_err_probe(eth-\u003edev, PTR_ERR(eth-\u003efe_regs),\n4050:\t\t\t\t\t \"failed to iomap fe regs\\n\");\n4051:\t\n4052:\t\teth-\u003ersts[0].id = \"fe\";\n4053:\t\teth-\u003ersts[1].id = \"pdma\";\n4054:\t\teth-\u003ersts[2].id = \"qdma\";\n4055:\t\terr = devm_reset_control_bulk_get_exclusive(eth-\u003edev,\n4056:\t\t\t\t\t\t\t ARRAY_SIZE(eth-\u003ersts),\n4057:\t\t\t\t\t\t\t eth-\u003ersts);\n4058:\t\tif (err) {\n4059:\t\t\tdev_err(eth-\u003edev, \"failed to get bulk reset lines\\n\");\n4060:\t\t\treturn err;\n4061:\t\t}\n4062:\t\n4063:\t\txsi_rsts = devm_kcalloc(eth-\u003edev,\n4064:\t\t\t\t\teth-\u003esoc-\u003enum_xsi_rsts, sizeof(*xsi_rsts),\n4065:\t\t\t\t\tGFP_KERNEL);\n4066:\t\tif (!xsi_rsts)\n4067:\t\t\treturn -ENOMEM;\n4068:\t\n4069:\t\teth-\u003exsi_rsts = xsi_rsts;\n4070:\t\tfor (i = 0; i \u003c eth-\u003esoc-\u003enum_xsi_rsts; i++)\n4071:\t\t\teth-\u003exsi_rsts[i].id = eth-\u003esoc-\u003exsi_rsts_names[i];\n4072:\t\n4073:\t\terr = devm_reset_control_bulk_get_exclusive(eth-\u003edev,\n"
} |
| 13/2 | 2026/09/22 17:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16952 output: 139 thoughts: 404 Thoughts: **My Assessment of the Airoha Ethernet Driver Patch** Alright, let's break this down. From what I'm seeing, this is a patch targeting the Airoha EN7581 SoC's Ethernet hardware driver. My immediate thought is, let's see if this warrants fuzzing. I need to make sure this meets the negative criteria defined earlier, specifically around "WorthFuzzing=false" scenarios. Based on the criteria provided, I need to evaluate if the modified code falls into any of the exclusion categories. The modified code resides within `drivers/net/ethernet/airoha/airoha_eth.c`. From my understanding of the codebase and the description, this driver is a platform driver specifically for the Airoha SoC family, with the EN7581 as the target. It's an ARM-based SoC, and crucial to this assessment, it requires very specific device-tree nodes and physical SoC-level resources. Think MMIO registers, reset controllers, clocks, all of that low-level hardware stuff. My analysis confirms that this driver's functionality is fundamentally linked to dedicated hardware components that aren't emulated or accessible in standard environments like amd64 QEMU or GCE. This code is structurally unreachable within the usual test environment. Therefore, applying the "negative criteria" in the checklist, it is my considered view that: * `WorthFuzzing`: false. The code modifications are not reachable in standard fuzzing environments. * `EnableConfigs`: \[]. No specific configurations are enabled to permit fuzzing. * `FocusSymbols`: \[]. No focus symbols are applicable, as this is structurally unreachable. * `Reasoning`: The patch modifies the Airoha SoC Ethernet driver (`drivers/net/ethernet/airoha/`) which is an OF platform driver for Airoha SoCs (ARCH_AIROHA). It requires dedicated SoC hardware resources (MMIO registers, reset controllers, clocks) not emulated in standard QEMU/GCE virtualized environments on amd64, making the modified code structurally unreachable for fuzzing. I've carefully considered all aspects, I'm confident in my assessment, and my findings strictly adhere to the instructions and format. Let's execute the `set-results` command and move forward. |
| 14/2 | 2026/09/22 17:22 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds XDP support to the Airoha Ethernet driver (drivers/net/ethernet/airoha/airoha_eth.c). This is a platform driver for Airoha SoCs (e.g. EN7581) requiring specific device tree bindings and physical SoC hardware resources (MMIO registers, reset lines, clocks) that are not emulated in standard QEMU/GCE virtualized environments on the target architecture (amd64). As such, the modified code is structurally unreachable.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds XDP support to the Airoha Ethernet driver (drivers/net/ethernet/airoha/airoha_eth.c). This is a platform driver for Airoha SoCs (e.g. EN7581) requiring specific device tree bindings and physical SoC hardware resources (MMIO registers, reset lines, clocks) that are not emulated in standard QEMU/GCE virtualized environments on the target architecture (amd64). As such, the modified code is structurally unreachable.",
"WorthFuzzing": false
} |
| 15/1 | 2026/09/22 17:22 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|