| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/25 13:48 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch exclusively modifies the aQuantia AQtion Ethernet driver (drivers/net/ethernet/aquantia/). This is a driver for a physical PCIe network card that is not emulated in standard QEMU or virtualized environments, making the modified code paths unreachable during fuzzing.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/25 13:48 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit a1cdba284ef8b1a0d59fe3915d9dafa77848de16\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Jul 25 13:48:24 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/aquantia/Kconfig b/drivers/net/ethernet/aquantia/Kconfig\nindex cec2018c84a9bc..c8fb7b33e5b71a 100644\n--- a/drivers/net/ethernet/aquantia/Kconfig\n+++ b/drivers/net/ethernet/aquantia/Kconfig\n@@ -20,6 +20,7 @@ config AQTION\n \ttristate \"aQuantia AQtion(tm) Support\"\n \tdepends on PCI\n \tdepends on MACSEC || MACSEC=n\n+\tselect PAGE_POOL\n \thelp\n \t This enables the support for the aQuantia AQtion(tm) Ethernet card.\n \ndiff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c\nindex 420af958d4861a..0f5125bd231594 100644\n--- a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c\n+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c\n@@ -100,9 +100,6 @@ static const char * const aq_ethtool_queue_rx_stat_names[] = {\n \t\"%sQueue[%d] AllocFails\",\n \t\"%sQueue[%d] SkbAllocFails\",\n \t\"%sQueue[%d] Polls\",\n-\t\"%sQueue[%d] PageFlips\",\n-\t\"%sQueue[%d] PageReuses\",\n-\t\"%sQueue[%d] PageFrees\",\n \t\"%sQueue[%d] XdpAbort\",\n \t\"%sQueue[%d] XdpDrop\",\n \t\"%sQueue[%d] XdpPass\",\ndiff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c b/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c\nindex 558ac9237f759e..3a40d986cd6785 100644\n--- a/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c\n+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c\n@@ -13,6 +13,7 @@\n #include \u003clinux/ptp_classify.h\u003e\n #include \u003clinux/interrupt.h\u003e\n #include \u003clinux/clocksource.h\u003e\n+#include \u003cnet/xdp.h\u003e\n \n #include \"aq_nic.h\"\n #include \"aq_ptp.h\"\n@@ -1192,12 +1193,23 @@ int aq_ptp_ring_alloc(struct aq_nic_s *aq_nic)\n \tif (err)\n \t\tgoto err_exit_ptp_tx;\n \n+\terr = xdp_rxq_info_reg(\u0026aq_ptp-\u003eptp_rx.xdp_rxq, aq_nic-\u003endev,\n+\t\t\t rx_ring_idx, aq_ptp-\u003enapi.napi_id);\n+\tif (err \u003c 0)\n+\t\tgoto err_exit_ptp_rx;\n+\n+\terr = xdp_rxq_info_reg_mem_model(\u0026aq_ptp-\u003eptp_rx.xdp_rxq,\n+\t\t\t\t\t MEM_TYPE_PAGE_POOL,\n+\t\t\t\t\t aq_ptp-\u003eptp_rx.pg_pool);\n+\tif (err \u003c 0)\n+\t\tgoto err_exit_xdp_rxq;\n+\n \tif (aq_ptp-\u003ea1_ptp) {\n \t\terr = aq_ring_hwts_rx_alloc(\u0026aq_ptp-\u003ehwts_rx, aq_nic, PTP_HWST_RING_IDX,\n \t\t\t\t\t aq_nic-\u003eaq_nic_cfg.rxds,\n \t\t\t\t\t aq_nic-\u003eaq_nic_cfg.aq_hw_caps-\u003erxd_size);\n \t\tif (err)\n-\t\t\tgoto err_exit_ptp_rx;\n+\t\t\tgoto err_exit_xdp_rxq;\n \t}\n \n \terr = aq_ptp_skb_ring_init(\u0026aq_ptp-\u003eskb_ring, aq_nic-\u003eaq_nic_cfg.rxds);\n@@ -1217,6 +1229,8 @@ int aq_ptp_ring_alloc(struct aq_nic_s *aq_nic)\n err_exit_hwts_rx:\n \tif (aq_ptp-\u003ea1_ptp)\n \t\taq_ring_hwts_rx_free(\u0026aq_ptp-\u003ehwts_rx);\n+err_exit_xdp_rxq:\n+\txdp_rxq_info_unreg(\u0026aq_ptp-\u003eptp_rx.xdp_rxq);\n err_exit_ptp_rx:\n \taq_ring_free(\u0026aq_ptp-\u003eptp_rx);\n err_exit_ptp_tx:\n@@ -1233,6 +1247,8 @@ void aq_ptp_ring_free(struct aq_nic_s *aq_nic)\n \t\treturn;\n \n \taq_ring_free(\u0026aq_ptp-\u003eptp_tx);\n+\tif (xdp_rxq_info_is_reg(\u0026aq_ptp-\u003eptp_rx.xdp_rxq))\n+\t\txdp_rxq_info_unreg(\u0026aq_ptp-\u003eptp_rx.xdp_rxq);\n \taq_ring_free(\u0026aq_ptp-\u003eptp_rx);\n \tif (aq_ptp-\u003ea1_ptp)\n \t\taq_ring_hwts_rx_free(\u0026aq_ptp-\u003ehwts_rx);\ndiff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c\nindex 8ff07de2bd5240..9dd88171059427 100644\n--- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c\n+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c\n@@ -14,120 +14,37 @@\n #include \"aq_vec.h\"\n #include \"aq_main.h\"\n \n+#include \u003cnet/page_pool/helpers.h\u003e\n #include \u003cnet/xdp.h\u003e\n #include \u003clinux/filter.h\u003e\n #include \u003clinux/bpf_trace.h\u003e\n #include \u003clinux/netdevice.h\u003e\n #include \u003clinux/etherdevice.h\u003e\n \n-static void aq_get_rxpages_xdp(struct aq_ring_buff_s *buff,\n-\t\t\t struct xdp_buff *xdp)\n-{\n-\tstruct skb_shared_info *sinfo;\n-\tint i;\n-\n-\tif (xdp_buff_has_frags(xdp)) {\n-\t\tsinfo = xdp_get_shared_info_from_buff(xdp);\n-\n-\t\tfor (i = 0; i \u003c sinfo-\u003enr_frags; i++) {\n-\t\t\tskb_frag_t *frag = \u0026sinfo-\u003efrags[i];\n-\n-\t\t\tpage_ref_inc(skb_frag_page(frag));\n-\t\t}\n-\t}\n-\tpage_ref_inc(buff-\u003erxdata.page);\n-}\n-\n-static inline void aq_free_rxpage(struct aq_rxpage *rxpage, struct device *dev)\n-{\n-\tunsigned int len = PAGE_SIZE \u003c\u003c rxpage-\u003eorder;\n-\n-\tdma_unmap_page(dev, rxpage-\u003edaddr, len, DMA_FROM_DEVICE);\n-\n-\t/* Drop the ref for being in the ring. */\n-\t__free_pages(rxpage-\u003epage, rxpage-\u003eorder);\n-\trxpage-\u003epage = NULL;\n-}\n-\n-static int aq_alloc_rxpages(struct aq_rxpage *rxpage, struct aq_ring_s *rx_ring)\n+static int aq_get_rxpages(struct aq_ring_s *self, struct aq_ring_buff_s *rxbuf)\n {\n-\tstruct device *dev = aq_nic_get_dev(rx_ring-\u003eaq_nic);\n-\tunsigned int order = rx_ring-\u003epage_order;\n+\tunsigned int size = self-\u003epage_offset + self-\u003eframe_max +\n+\t\t\t self-\u003etail_size;\n+\tunsigned int pg_off;\n \tstruct page *page;\n-\tint ret = -ENOMEM;\n-\tdma_addr_t daddr;\n-\n-\tpage = dev_alloc_pages(order);\n-\tif (unlikely(!page))\n-\t\tgoto err_exit;\n-\n-\tdaddr = dma_map_page(dev, page, 0, PAGE_SIZE \u003c\u003c order,\n-\t\t\t DMA_FROM_DEVICE);\n-\n-\tif (unlikely(dma_mapping_error(dev, daddr)))\n-\t\tgoto free_page;\n-\n-\trxpage-\u003epage = page;\n-\trxpage-\u003edaddr = daddr;\n-\trxpage-\u003eorder = order;\n-\trxpage-\u003epg_off = rx_ring-\u003epage_offset;\n-\n-\treturn 0;\n-\n-free_page:\n-\t__free_pages(page, order);\n \n-err_exit:\n-\treturn ret;\n-}\n+\t/* Buffers whose page was not passed up the stack are reposted\n+\t * with the data they already carry discarded.\n+\t */\n+\tif (rxbuf-\u003erxdata.page)\n+\t\treturn 0;\n \n-static int aq_get_rxpages(struct aq_ring_s *self, struct aq_ring_buff_s *rxbuf)\n-{\n-\tunsigned int order = self-\u003epage_order;\n-\tu16 page_offset = self-\u003epage_offset;\n-\tu16 frame_max = self-\u003eframe_max;\n-\tu16 tail_size = self-\u003etail_size;\n-\tint ret;\n-\n-\tif (rxbuf-\u003erxdata.page) {\n-\t\t/* One means ring is the only user and can reuse */\n-\t\tif (page_ref_count(rxbuf-\u003erxdata.page) \u003e 1) {\n-\t\t\t/* Try reuse buffer */\n-\t\t\trxbuf-\u003erxdata.pg_off += frame_max + page_offset +\n-\t\t\t\t\t\ttail_size;\n-\t\t\tif (rxbuf-\u003erxdata.pg_off + frame_max + tail_size \u003c=\n-\t\t\t (PAGE_SIZE \u003c\u003c order)) {\n-\t\t\t\tu64_stats_update_begin(\u0026self-\u003estats.rx.syncp);\n-\t\t\t\tself-\u003estats.rx.pg_flips++;\n-\t\t\t\tu64_stats_update_end(\u0026self-\u003estats.rx.syncp);\n-\n-\t\t\t} else {\n-\t\t\t\t/* Buffer exhausted. We have other users and\n-\t\t\t\t * should release this page and realloc\n-\t\t\t\t */\n-\t\t\t\taq_free_rxpage(\u0026rxbuf-\u003erxdata,\n-\t\t\t\t\t aq_nic_get_dev(self-\u003eaq_nic));\n-\t\t\t\tu64_stats_update_begin(\u0026self-\u003estats.rx.syncp);\n-\t\t\t\tself-\u003estats.rx.pg_losts++;\n-\t\t\t\tu64_stats_update_end(\u0026self-\u003estats.rx.syncp);\n-\t\t\t}\n-\t\t} else {\n-\t\t\trxbuf-\u003erxdata.pg_off = page_offset;\n-\t\t\tu64_stats_update_begin(\u0026self-\u003estats.rx.syncp);\n-\t\t\tself-\u003estats.rx.pg_reuses++;\n-\t\t\tu64_stats_update_end(\u0026self-\u003estats.rx.syncp);\n-\t\t}\n+\tpage = page_pool_dev_alloc_frag(self-\u003epg_pool, \u0026pg_off, size);\n+\tif (unlikely(!page)) {\n+\t\tu64_stats_update_begin(\u0026self-\u003estats.rx.syncp);\n+\t\tself-\u003estats.rx.alloc_fails++;\n+\t\tu64_stats_update_end(\u0026self-\u003estats.rx.syncp);\n+\t\treturn -ENOMEM;\n \t}\n \n-\tif (!rxbuf-\u003erxdata.page) {\n-\t\tret = aq_alloc_rxpages(\u0026rxbuf-\u003erxdata, self);\n-\t\tif (ret) {\n-\t\t\tu64_stats_update_begin(\u0026self-\u003estats.rx.syncp);\n-\t\t\tself-\u003estats.rx.alloc_fails++;\n-\t\t\tu64_stats_update_end(\u0026self-\u003estats.rx.syncp);\n-\t\t}\n-\t\treturn ret;\n-\t}\n+\trxbuf-\u003erxdata.page = page;\n+\trxbuf-\u003erxdata.daddr = page_pool_get_dma_addr(page);\n+\trxbuf-\u003erxdata.pg_off = pg_off + self-\u003epage_offset;\n \n \treturn 0;\n }\n@@ -179,6 +96,15 @@ int aq_ring_rx_alloc(struct aq_ring_s *self,\n \t\t unsigned int idx,\n \t\t struct aq_nic_cfg_s *aq_nic_cfg)\n {\n+\tstruct page_pool_params pp_params = {\n+\t\t.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,\n+\t\t.pool_size = aq_nic_cfg-\u003erxds,\n+\t\t.nid = NUMA_NO_NODE,\n+\t\t.dev = aq_nic_get_dev(aq_nic),\n+\t\t.dma_dir = DMA_FROM_DEVICE,\n+\t};\n+\tstruct page_pool *pool;\n+\n \tself-\u003eaq_nic = aq_nic;\n \tself-\u003eidx = idx;\n \tself-\u003esize = aq_nic_cfg-\u003erxds;\n@@ -200,6 +126,18 @@ int aq_ring_rx_alloc(struct aq_ring_s *self,\n \t\tself-\u003etail_size = 0;\n \t}\n \n+\tpp_params.order = self-\u003epage_order;\n+\tpp_params.max_len = PAGE_SIZE \u003c\u003c self-\u003epage_order;\n+\n+\tpool = page_pool_create(\u0026pp_params);\n+\tif (IS_ERR(pool))\n+\t\treturn PTR_ERR(pool);\n+\n+\tself-\u003epg_pool = pool;\n+\n+\t/* On failure aq_ring_alloc() calls aq_ring_free(), which also\n+\t * destroys the page pool.\n+\t */\n \treturn aq_ring_alloc(self, aq_nic);\n }\n \n@@ -346,7 +284,11 @@ bool aq_ring_tx_clean(struct aq_ring_s *self)\n \t\t\t++self-\u003estats.tx.packets;\n \t\t\tself-\u003estats.tx.bytes += xdp_get_frame_len(buff-\u003exdpf);\n \t\t\tu64_stats_update_end(\u0026self-\u003estats.tx.syncp);\n-\t\t\txdp_return_frame_rx_napi(buff-\u003exdpf);\n+\t\t\t/* Frames queued via ndo_xdp_xmit() may come from a\n+\t\t\t * page pool owned by another NAPI context: no direct\n+\t\t\t * recycling.\n+\t\t\t */\n+\t\t\txdp_return_frame(buff-\u003exdpf);\n \t\t}\n \n out:\n@@ -360,6 +302,35 @@ bool aq_ring_tx_clean(struct aq_ring_s *self)\n \treturn !!budget;\n }\n \n+void aq_ring_tx_deinit(struct aq_ring_s *self)\n+{\n+\tif (!self)\n+\t\treturn;\n+\n+\tfor (; self-\u003esw_head != self-\u003esw_tail;\n+\t\tself-\u003esw_head = aq_ring_next_dx(self, self-\u003esw_head)) {\n+\t\tstruct aq_ring_buff_s *buff = \u0026self-\u003ebuff_ring[self-\u003esw_head];\n+\t\tstruct device *ndev = aq_nic_get_dev(self-\u003eaq_nic);\n+\n+\t\tif (buff-\u003eis_mapped) {\n+\t\t\tif (buff-\u003eis_sop) {\n+\t\t\t\tdma_unmap_single(ndev, buff-\u003epa, buff-\u003elen,\n+\t\t\t\t\t\t DMA_TO_DEVICE);\n+\t\t\t} else {\n+\t\t\t\tdma_unmap_page(ndev, buff-\u003epa, buff-\u003elen,\n+\t\t\t\t\t DMA_TO_DEVICE);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (buff-\u003eis_eop) {\n+\t\t\tif (buff-\u003eskb)\n+\t\t\t\tdev_kfree_skb_any(buff-\u003eskb);\n+\t\t\telse if (buff-\u003exdpf)\n+\t\t\t\txdp_return_frame(buff-\u003exdpf);\n+\t\t}\n+\t}\n+}\n+\n static void aq_rx_checksum(struct aq_ring_s *self,\n \t\t\t struct aq_ring_buff_s *buff,\n \t\t\t struct sk_buff *skb)\n@@ -408,22 +379,15 @@ int aq_xdp_xmit(struct net_device *dev, int num_frames,\n }\n \n static struct sk_buff *aq_xdp_build_skb(struct xdp_buff *xdp,\n-\t\t\t\t\tstruct net_device *dev,\n-\t\t\t\t\tstruct aq_ring_buff_s *buff)\n+\t\t\t\t\tstruct net_device *dev)\n {\n \tstruct xdp_frame *xdpf;\n-\tstruct sk_buff *skb;\n \n \txdpf = xdp_convert_buff_to_frame(xdp);\n \tif (unlikely(!xdpf))\n \t\treturn NULL;\n \n-\tskb = xdp_build_skb_from_frame(xdpf, dev);\n-\tif (!skb)\n-\t\treturn NULL;\n-\n-\taq_get_rxpages_xdp(buff, xdp);\n-\treturn skb;\n+\treturn xdp_build_skb_from_frame(xdpf, dev);\n }\n \n static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n@@ -444,8 +408,16 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n \tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n \n \tprog = READ_ONCE(rx_ring-\u003exdp_prog);\n-\tif (!prog)\n-\t\treturn aq_xdp_build_skb(xdp, aq_nic-\u003endev, buff);\n+\tif (!prog) {\n+\t\tskb = aq_xdp_build_skb(xdp, aq_nic-\u003endev);\n+\t\t/* The ring has already handed its page pool reference to the\n+\t\t * xdp_buff, so if the skb could not be built the buffer must\n+\t\t * be returned to the pool here or its fragments would leak.\n+\t\t */\n+\t\tif (!skb)\n+\t\t\txdp_return_buff(xdp);\n+\t\treturn skb;\n+\t}\n \n \tprefetchw(xdp-\u003edata_hard_start); /* xdp_frame write */\n \n@@ -456,7 +428,7 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n \tact = bpf_prog_run_xdp(prog, xdp);\n \tswitch (act) {\n \tcase XDP_PASS:\n-\t\tskb = aq_xdp_build_skb(xdp, aq_nic-\u003endev, buff);\n+\t\tskb = aq_xdp_build_skb(xdp, aq_nic-\u003endev);\n \t\tif (!skb)\n \t\t\tgoto out_aborted;\n \t\tu64_stats_update_begin(\u0026rx_ring-\u003estats.rx.syncp);\n@@ -474,7 +446,6 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n \t\tu64_stats_update_begin(\u0026rx_ring-\u003estats.rx.syncp);\n \t\t++rx_ring-\u003estats.rx.xdp_tx;\n \t\tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n-\t\taq_get_rxpages_xdp(buff, xdp);\n \t\tbreak;\n \tcase XDP_REDIRECT:\n \t\tif (xdp_do_redirect(aq_nic-\u003endev, xdp, prog) \u003c 0)\n@@ -483,7 +454,6 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n \t\tu64_stats_update_begin(\u0026rx_ring-\u003estats.rx.syncp);\n \t\t++rx_ring-\u003estats.rx.xdp_redirect;\n \t\tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n-\t\taq_get_rxpages_xdp(buff, xdp);\n \t\tbreak;\n \tdefault:\n \t\tfallthrough;\n@@ -494,11 +464,13 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,\n \t\tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n \t\ttrace_xdp_exception(aq_nic-\u003endev, prog, act);\n \t\tbpf_warn_invalid_xdp_action(aq_nic-\u003endev, prog, act);\n+\t\txdp_return_buff(xdp);\n \t\tbreak;\n \tcase XDP_DROP:\n \t\tu64_stats_update_begin(\u0026rx_ring-\u003estats.rx.syncp);\n \t\t++rx_ring-\u003estats.rx.xdp_drop;\n \t\tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n+\t\txdp_return_buff(xdp);\n \t\tbreak;\n \t}\n \n@@ -517,8 +489,11 @@ static bool aq_add_rx_fragment(struct device *dev,\n \tdo {\n \t\tskb_frag_t *frag;\n \n-\t\tif (unlikely(sinfo-\u003enr_frags \u003e= MAX_SKB_FRAGS))\n+\t\tif (unlikely(sinfo-\u003enr_frags \u003e= MAX_SKB_FRAGS)) {\n+\t\t\t/* Attached frags must reach xdp_return_buff() */\n+\t\t\txdp_buff_set_frags_flag(xdp);\n \t\t\treturn true;\n+\t\t}\n \n \t\tfrag = \u0026sinfo-\u003efrags[sinfo-\u003enr_frags++];\n \t\tbuff_ = \u0026ring-\u003ebuff_ring[buff_-\u003enext];\n@@ -542,6 +517,11 @@ static bool aq_add_rx_fragment(struct device *dev,\n \t\tif (page_is_pfmemalloc(buff_-\u003erxdata.page))\n \t\t\txdp_buff_set_frag_pfmemalloc(xdp);\n \n+\t\t/* The frag's page pool reference is owned by the xdp_buff\n+\t\t * from now on.\n+\t\t */\n+\t\tbuff_-\u003erxdata.page = NULL;\n+\n \t} while (!buff_-\u003eis_eop);\n \n \txdp_buff_set_frags_flag(xdp);\n@@ -645,6 +625,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,\n \t\t\terr = -ENOMEM;\n \t\t\tgoto err_exit;\n \t\t}\n+\t\tskb_mark_for_recycle(skb);\n \t\tif (is_ptp_ring)\n \t\t\tbuff-\u003elen -=\n \t\t\t\taq_ptp_extract_ts(self-\u003eaq_nic, skb_hwtstamps(skb),\n@@ -665,7 +646,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,\n \t\t\t\t\tbuff-\u003erxdata.pg_off + hdr_len,\n \t\t\t\t\tbuff-\u003elen - hdr_len,\n \t\t\t\t\tself-\u003eframe_max);\n-\t\t\tpage_ref_inc(buff-\u003erxdata.page);\n+\t\t\tbuff-\u003erxdata.page = NULL;\n \t\t}\n \n \t\tif (!buff-\u003eis_eop) {\n@@ -684,7 +665,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,\n \t\t\t\t\t\tbuff_-\u003erxdata.pg_off,\n \t\t\t\t\t\tbuff_-\u003elen,\n \t\t\t\t\t\tself-\u003eframe_max);\n-\t\t\t\tpage_ref_inc(buff_-\u003erxdata.page);\n+\t\t\t\tbuff_-\u003erxdata.page = NULL;\n \t\t\t\tbuff_-\u003eis_cleaned = 1;\n \n \t\t\t\tbuff-\u003eis_ip_cso \u0026= buff_-\u003eis_ip_cso;\n@@ -822,6 +803,11 @@ static int __aq_ring_xdp_clean(struct aq_ring_s *rx_ring,\n \t\txdp_init_buff(\u0026xdp, frame_sz, \u0026rx_ring-\u003exdp_rxq);\n \t\txdp_prepare_buff(\u0026xdp, hard_start, rx_ring-\u003epage_offset,\n \t\t\t\t buff-\u003elen, false);\n+\t\t/* The xdp_buff owns the buffer's page pool reference from\n+\t\t * here on; it comes back through the MEM_TYPE_PAGE_POOL\n+\t\t * memory model on every XDP verdict.\n+\t\t */\n+\t\tbuff-\u003erxdata.page = NULL;\n \t\tif (!buff-\u003eis_eop) {\n \t\t\tif (aq_add_rx_fragment(dev, rx_ring, buff, \u0026xdp)) {\n \t\t\t\tu64_stats_update_begin(\u0026rx_ring-\u003estats.rx.syncp);\n@@ -829,6 +815,7 @@ static int __aq_ring_xdp_clean(struct aq_ring_s *rx_ring,\n \t\t\t\trx_ring-\u003estats.rx.bytes += xdp_get_buff_len(\u0026xdp);\n \t\t\t\t++rx_ring-\u003estats.rx.xdp_aborted;\n \t\t\t\tu64_stats_update_end(\u0026rx_ring-\u003estats.rx.syncp);\n+\t\t\t\txdp_return_buff(\u0026xdp);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -921,15 +908,31 @@ int aq_ring_rx_fill(struct aq_ring_s *self)\n \n void aq_ring_rx_deinit(struct aq_ring_s *self)\n {\n-\tif (!self)\n+\tunsigned int i;\n+\n+\tif (!self || !self-\u003ebuff_ring)\n \t\treturn;\n \n-\tfor (; self-\u003esw_head != self-\u003esw_tail;\n-\t\tself-\u003esw_head = aq_ring_next_dx(self, self-\u003esw_head)) {\n-\t\tstruct aq_ring_buff_s *buff = \u0026self-\u003ebuff_ring[self-\u003esw_head];\n+\t/* Release every page still owned by the ring.\n+\t *\n+\t * Walking [sw_head, sw_tail) is not enough: refill is batched\n+\t * (aq_ring_rx_fill() waits for AQ_CFG_RX_REFILL_THRES free slots),\n+\t * so slots that were cleaned but not yet reposted accumulate in the\n+\t * [sw_tail, sw_head) gap, and they keep their page for reuse. Walk\n+\t * the whole ring and release whatever is left.\n+\t */\n+\tfor (i = 0; i \u003c self-\u003esize; i++) {\n+\t\tstruct aq_ring_buff_s *buff = \u0026self-\u003ebuff_ring[i];\n+\n+\t\tif (!buff-\u003erxdata.page)\n+\t\t\tcontinue;\n \n-\t\taq_free_rxpage(\u0026buff-\u003erxdata, aq_nic_get_dev(self-\u003eaq_nic));\n+\t\tpage_pool_put_full_page(self-\u003epg_pool, buff-\u003erxdata.page,\n+\t\t\t\t\tfalse);\n+\t\tbuff-\u003erxdata.page = NULL;\n \t}\n+\n+\tself-\u003esw_head = self-\u003esw_tail;\n }\n \n void aq_ring_free(struct aq_ring_s *self)\n@@ -940,6 +943,11 @@ void aq_ring_free(struct aq_ring_s *self)\n \tkfree(self-\u003ebuff_ring);\n \tself-\u003ebuff_ring = NULL;\n \n+\tif (self-\u003epg_pool) {\n+\t\tpage_pool_destroy(self-\u003epg_pool);\n+\t\tself-\u003epg_pool = NULL;\n+\t}\n+\n \tif (self-\u003edx_ring) {\n \t\tdma_free_coherent(aq_nic_get_dev(self-\u003eaq_nic),\n \t\t\t\t self-\u003esize * self-\u003edx_size, self-\u003edx_ring,\n@@ -978,9 +986,6 @@ unsigned int aq_ring_fill_stats_data(struct aq_ring_s *self, u64 *data)\n \t\t\tdata[++count] = self-\u003estats.rx.alloc_fails;\n \t\t\tdata[++count] = self-\u003estats.rx.skb_alloc_fails;\n \t\t\tdata[++count] = self-\u003estats.rx.polls;\n-\t\t\tdata[++count] = self-\u003estats.rx.pg_flips;\n-\t\t\tdata[++count] = self-\u003estats.rx.pg_reuses;\n-\t\t\tdata[++count] = self-\u003estats.rx.pg_losts;\n \t\t\tdata[++count] = self-\u003estats.rx.xdp_aborted;\n \t\t\tdata[++count] = self-\u003estats.rx.xdp_drop;\n \t\t\tdata[++count] = self-\u003estats.rx.xdp_pass;\ndiff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h\nindex a70b880ada67e9..58bcadb3e3ccd3 100644\n--- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h\n+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h\n@@ -17,12 +17,12 @@\n #define AQ_XDP_TAILROOM\t\tSKB_DATA_ALIGN(sizeof(struct skb_shared_info))\n \n struct page;\n+struct page_pool;\n struct aq_nic_cfg_s;\n \n struct aq_rxpage {\n \tstruct page *page;\n \tdma_addr_t daddr;\n-\tunsigned int order;\n \tunsigned int pg_off;\n };\n \n@@ -105,9 +105,6 @@ struct aq_ring_stats_rx_s {\n \tu64 alloc_fails;\n \tu64 skb_alloc_fails;\n \tu64 polls;\n-\tu64 pg_losts;\n-\tu64 pg_flips;\n-\tu64 pg_reuses;\n \tu64 xdp_aborted;\n \tu64 xdp_drop;\n \tu64 xdp_pass;\n@@ -151,6 +148,7 @@ struct aq_ring_s {\n \tu16 tail_size;\n \tunion aq_ring_stats_s stats;\n \tdma_addr_t dx_ring_pa;\n+\tstruct page_pool *pg_pool;\n \tstruct bpf_prog *xdp_prog;\n \tenum atl_ring_type ring_type;\n \tstruct xdp_rxq_info xdp_rxq;\n@@ -202,6 +200,7 @@ void aq_ring_update_queue_state(struct aq_ring_s *ring);\n void aq_ring_queue_wake(struct aq_ring_s *ring);\n void aq_ring_queue_stop(struct aq_ring_s *ring);\n bool aq_ring_tx_clean(struct aq_ring_s *self);\n+void aq_ring_tx_deinit(struct aq_ring_s *self);\n int aq_xdp_xmit(struct net_device *dev, int num_frames,\n \t\tstruct xdp_frame **frames, u32 flags);\n int aq_ring_rx_clean(struct aq_ring_s *self,\ndiff --git a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c\nindex 2f9033ceed8ce3..8e15405d49419d 100644\n--- a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c\n+++ b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c\n@@ -146,25 +146,32 @@ int aq_vec_ring_alloc(struct aq_vec_s *self, struct aq_nic_s *aq_nic,\n \t\taq_nic_set_tx_ring(aq_nic, idx_ring, ring);\n \n \t\tring = \u0026self-\u003ering[i][AQ_VEC_RX_ID];\n+\t\t/* Registering the MEM_TYPE_PAGE_POOL memory model below needs\n+\t\t * the page pool created by aq_ring_rx_alloc(), so the ring is\n+\t\t * allocated first. If a registration fails, the ring has to be\n+\t\t * freed explicitly: rx_rings is not incremented yet, so the\n+\t\t * unwind through aq_vec_ring_free() would not cover it.\n+\t\t */\n+\t\terr = aq_ring_rx_alloc(ring, aq_nic, idx_ring, aq_nic_cfg);\n+\t\tif (err)\n+\t\t\tgoto err_exit;\n+\n \t\tif (xdp_rxq_info_reg(\u0026ring-\u003exdp_rxq,\n \t\t\t\t aq_nic-\u003endev, idx,\n \t\t\t\t self-\u003enapi.napi_id) \u003c 0) {\n+\t\t\taq_ring_free(ring);\n \t\t\terr = -ENOMEM;\n \t\t\tgoto err_exit;\n \t\t}\n \t\tif (xdp_rxq_info_reg_mem_model(\u0026ring-\u003exdp_rxq,\n-\t\t\t\t\t MEM_TYPE_PAGE_SHARED, NULL) \u003c 0) {\n+\t\t\t\t\t MEM_TYPE_PAGE_POOL,\n+\t\t\t\t\t ring-\u003epg_pool) \u003c 0) {\n \t\t\txdp_rxq_info_unreg(\u0026ring-\u003exdp_rxq);\n+\t\t\taq_ring_free(ring);\n \t\t\terr = -ENOMEM;\n \t\t\tgoto err_exit;\n \t\t}\n \n-\t\terr = aq_ring_rx_alloc(ring, aq_nic, idx_ring, aq_nic_cfg);\n-\t\tif (err) {\n-\t\t\txdp_rxq_info_unreg(\u0026ring-\u003exdp_rxq);\n-\t\t\tgoto err_exit;\n-\t\t}\n-\n \t\t++self-\u003erx_rings;\n \t}\n \n@@ -275,7 +282,7 @@ void aq_vec_deinit(struct aq_vec_s *self)\n \n \tfor (i = 0U; self-\u003etx_rings \u003e i; ++i) {\n \t\tring = self-\u003ering[i];\n-\t\taq_ring_tx_clean(\u0026ring[AQ_VEC_TX_ID]);\n+\t\taq_ring_tx_deinit(\u0026ring[AQ_VEC_TX_ID]);\n \t\taq_ring_rx_deinit(\u0026ring[AQ_VEC_RX_ID]);\n \t}\n \n"
} |
| 2/1 | 2026/07/25 13:48 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch exclusively modifies the aQuantia AQtion Ethernet driver (drivers/net/ethernet/aquantia/). This is a driver for a physical PCIe network card that is not emulated in standard QEMU or virtualized environments, making the modified code paths unreachable during fuzzing.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit a1cdba284ef8b1a0d59fe3915d9dafa77848de16
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Jul 25 13:48:24 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/aquantia/Kconfig b/drivers/net/ethernet/aquantia/Kconfig
index cec2018c84a9bc..c8fb7b33e5b71a 100644
--- a/drivers/net/ethernet/aquantia/Kconfig
+++ b/drivers/net/ethernet/aquantia/Kconfig
@@ -20,6 +20,7 @@ config AQTION
tristate "aQuantia AQtion(tm) Support"
depends on PCI
depends on MACSEC || MACSEC=n
+ select PAGE_POOL
help
This enables the support for the aQuantia AQtion(tm) Ethernet card.
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
index 420af958d4861a..0f5125bd231594 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
@@ -100,9 +100,6 @@ static const char * const aq_ethtool_queue_rx_stat_names[] = {
"%sQueue[%d] AllocFails",
"%sQueue[%d] SkbAllocFails",
"%sQueue[%d] Polls",
- "%sQueue[%d] PageFlips",
- "%sQueue[%d] PageReuses",
- "%sQueue[%d] PageFrees",
"%sQueue[%d] XdpAbort",
"%sQueue[%d] XdpDrop",
"%sQueue[%d] XdpPass",
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c b/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c
index 558ac9237f759e..3a40d986cd6785 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ptp.c
@@ -13,6 +13,7 @@
#include <linux/ptp_classify.h>
#include <linux/interrupt.h>
#include <linux/clocksource.h>
+#include <net/xdp.h>
#include "aq_nic.h"
#include "aq_ptp.h"
@@ -1192,12 +1193,23 @@ int aq_ptp_ring_alloc(struct aq_nic_s *aq_nic)
if (err)
goto err_exit_ptp_tx;
+ err = xdp_rxq_info_reg(&aq_ptp->ptp_rx.xdp_rxq, aq_nic->ndev,
+ rx_ring_idx, aq_ptp->napi.napi_id);
+ if (err < 0)
+ goto err_exit_ptp_rx;
+
+ err = xdp_rxq_info_reg_mem_model(&aq_ptp->ptp_rx.xdp_rxq,
+ MEM_TYPE_PAGE_POOL,
+ aq_ptp->ptp_rx.pg_pool);
+ if (err < 0)
+ goto err_exit_xdp_rxq;
+
if (aq_ptp->a1_ptp) {
err = aq_ring_hwts_rx_alloc(&aq_ptp->hwts_rx, aq_nic, PTP_HWST_RING_IDX,
aq_nic->aq_nic_cfg.rxds,
aq_nic->aq_nic_cfg.aq_hw_caps->rxd_size);
if (err)
- goto err_exit_ptp_rx;
+ goto err_exit_xdp_rxq;
}
err = aq_ptp_skb_ring_init(&aq_ptp->skb_ring, aq_nic->aq_nic_cfg.rxds);
@@ -1217,6 +1229,8 @@ int aq_ptp_ring_alloc(struct aq_nic_s *aq_nic)
err_exit_hwts_rx:
if (aq_ptp->a1_ptp)
aq_ring_hwts_rx_free(&aq_ptp->hwts_rx);
+err_exit_xdp_rxq:
+ xdp_rxq_info_unreg(&aq_ptp->ptp_rx.xdp_rxq);
err_exit_ptp_rx:
aq_ring_free(&aq_ptp->ptp_rx);
err_exit_ptp_tx:
@@ -1233,6 +1247,8 @@ void aq_ptp_ring_free(struct aq_nic_s *aq_nic)
return;
aq_ring_free(&aq_ptp->ptp_tx);
+ if (xdp_rxq_info_is_reg(&aq_ptp->ptp_rx.xdp_rxq))
+ xdp_rxq_info_unreg(&aq_ptp->ptp_rx.xdp_rxq);
aq_ring_free(&aq_ptp->ptp_rx);
if (aq_ptp->a1_ptp)
aq_ring_hwts_rx_free(&aq_ptp->hwts_rx);
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c
index 8ff07de2bd5240..9dd88171059427 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c
@@ -14,120 +14,37 @@
#include "aq_vec.h"
#include "aq_main.h"
+#include <net/page_pool/helpers.h>
#include <net/xdp.h>
#include <linux/filter.h>
#include <linux/bpf_trace.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
-static void aq_get_rxpages_xdp(struct aq_ring_buff_s *buff,
- struct xdp_buff *xdp)
-{
- struct skb_shared_info *sinfo;
- int i;
-
- if (xdp_buff_has_frags(xdp)) {
- sinfo = xdp_get_shared_info_from_buff(xdp);
-
- for (i = 0; i < sinfo->nr_frags; i++) {
- skb_frag_t *frag = &sinfo->frags[i];
-
- page_ref_inc(skb_frag_page(frag));
- }
- }
- page_ref_inc(buff->rxdata.page);
-}
-
-static inline void aq_free_rxpage(struct aq_rxpage *rxpage, struct device *dev)
-{
- unsigned int len = PAGE_SIZE << rxpage->order;
-
- dma_unmap_page(dev, rxpage->daddr, len, DMA_FROM_DEVICE);
-
- /* Drop the ref for being in the ring. */
- __free_pages(rxpage->page, rxpage->order);
- rxpage->page = NULL;
-}
-
-static int aq_alloc_rxpages(struct aq_rxpage *rxpage, struct aq_ring_s *rx_ring)
+static int aq_get_rxpages(struct aq_ring_s *self, struct aq_ring_buff_s *rxbuf)
{
- struct device *dev = aq_nic_get_dev(rx_ring->aq_nic);
- unsigned int order = rx_ring->page_order;
+ unsigned int size = self->page_offset + self->frame_max +
+ self->tail_size;
+ unsigned int pg_off;
struct page *page;
- int ret = -ENOMEM;
- dma_addr_t daddr;
-
- page = dev_alloc_pages(order);
- if (unlikely(!page))
- goto err_exit;
-
- daddr = dma_map_page(dev, page, 0, PAGE_SIZE << order,
- DMA_FROM_DEVICE);
-
- if (unlikely(dma_mapping_error(dev, daddr)))
- goto free_page;
-
- rxpage->page = page;
- rxpage->daddr = daddr;
- rxpage->order = order;
- rxpage->pg_off = rx_ring->page_offset;
-
- return 0;
-
-free_page:
- __free_pages(page, order);
-err_exit:
- return ret;
-}
+ /* Buffers whose page was not passed up the stack are reposted
+ * with the data they already carry discarded.
+ */
+ if (rxbuf->rxdata.page)
+ return 0;
-static int aq_get_rxpages(struct aq_ring_s *self, struct aq_ring_buff_s *rxbuf)
-{
- unsigned int order = self->page_order;
- u16 page_offset = self->page_offset;
- u16 frame_max = self->frame_max;
- u16 tail_size = self->tail_size;
- int ret;
-
- if (rxbuf->rxdata.page) {
- /* One means ring is the only user and can reuse */
- if (page_ref_count(rxbuf->rxdata.page) > 1) {
- /* Try reuse buffer */
- rxbuf->rxdata.pg_off += frame_max + page_offset +
- tail_size;
- if (rxbuf->rxdata.pg_off + frame_max + tail_size <=
- (PAGE_SIZE << order)) {
- u64_stats_update_begin(&self->stats.rx.syncp);
- self->stats.rx.pg_flips++;
- u64_stats_update_end(&self->stats.rx.syncp);
-
- } else {
- /* Buffer exhausted. We have other users and
- * should release this page and realloc
- */
- aq_free_rxpage(&rxbuf->rxdata,
- aq_nic_get_dev(self->aq_nic));
- u64_stats_update_begin(&self->stats.rx.syncp);
- self->stats.rx.pg_losts++;
- u64_stats_update_end(&self->stats.rx.syncp);
- }
- } else {
- rxbuf->rxdata.pg_off = page_offset;
- u64_stats_update_begin(&self->stats.rx.syncp);
- self->stats.rx.pg_reuses++;
- u64_stats_update_end(&self->stats.rx.syncp);
- }
+ page = page_pool_dev_alloc_frag(self->pg_pool, &pg_off, size);
+ if (unlikely(!page)) {
+ u64_stats_update_begin(&self->stats.rx.syncp);
+ self->stats.rx.alloc_fails++;
+ u64_stats_update_end(&self->stats.rx.syncp);
+ return -ENOMEM;
}
- if (!rxbuf->rxdata.page) {
- ret = aq_alloc_rxpages(&rxbuf->rxdata, self);
- if (ret) {
- u64_stats_update_begin(&self->stats.rx.syncp);
- self->stats.rx.alloc_fails++;
- u64_stats_update_end(&self->stats.rx.syncp);
- }
- return ret;
- }
+ rxbuf->rxdata.page = page;
+ rxbuf->rxdata.daddr = page_pool_get_dma_addr(page);
+ rxbuf->rxdata.pg_off = pg_off + self->page_offset;
return 0;
}
@@ -179,6 +96,15 @@ int aq_ring_rx_alloc(struct aq_ring_s *self,
unsigned int idx,
struct aq_nic_cfg_s *aq_nic_cfg)
{
+ struct page_pool_params pp_params = {
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .pool_size = aq_nic_cfg->rxds,
+ .nid = NUMA_NO_NODE,
+ .dev = aq_nic_get_dev(aq_nic),
+ .dma_dir = DMA_FROM_DEVICE,
+ };
+ struct page_pool *pool;
+
self->aq_nic = aq_nic;
self->idx = idx;
self->size = aq_nic_cfg->rxds;
@@ -200,6 +126,18 @@ int aq_ring_rx_alloc(struct aq_ring_s *self,
self->tail_size = 0;
}
+ pp_params.order = self->page_order;
+ pp_params.max_len = PAGE_SIZE << self->page_order;
+
+ pool = page_pool_create(&pp_params);
+ if (IS_ERR(pool))
+ return PTR_ERR(pool);
+
+ self->pg_pool = pool;
+
+ /* On failure aq_ring_alloc() calls aq_ring_free(), which also
+ * destroys the page pool.
+ */
return aq_ring_alloc(self, aq_nic);
}
@@ -346,7 +284,11 @@ bool aq_ring_tx_clean(struct aq_ring_s *self)
++self->stats.tx.packets;
self->stats.tx.bytes += xdp_get_frame_len(buff->xdpf);
u64_stats_update_end(&self->stats.tx.syncp);
- xdp_return_frame_rx_napi(buff->xdpf);
+ /* Frames queued via ndo_xdp_xmit() may come from a
+ * page pool owned by another NAPI context: no direct
+ * recycling.
+ */
+ xdp_return_frame(buff->xdpf);
}
out:
@@ -360,6 +302,35 @@ bool aq_ring_tx_clean(struct aq_ring_s *self)
return !!budget;
}
+void aq_ring_tx_deinit(struct aq_ring_s *self)
+{
+ if (!self)
+ return;
+
+ for (; self->sw_head != self->sw_tail;
+ self->sw_head = aq_ring_next_dx(self, self->sw_head)) {
+ struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head];
+ struct device *ndev = aq_nic_get_dev(self->aq_nic);
+
+ if (buff->is_mapped) {
+ if (buff->is_sop) {
+ dma_unmap_single(ndev, buff->pa, buff->len,
+ DMA_TO_DEVICE);
+ } else {
+ dma_unmap_page(ndev, buff->pa, buff->len,
+ DMA_TO_DEVICE);
+ }
+ }
+
+ if (buff->is_eop) {
+ if (buff->skb)
+ dev_kfree_skb_any(buff->skb);
+ else if (buff->xdpf)
+ xdp_return_frame(buff->xdpf);
+ }
+ }
+}
+
static void aq_rx_checksum(struct aq_ring_s *self,
struct aq_ring_buff_s *buff,
struct sk_buff *skb)
@@ -408,22 +379,15 @@ int aq_xdp_xmit(struct net_device *dev, int num_frames,
}
static struct sk_buff *aq_xdp_build_skb(struct xdp_buff *xdp,
- struct net_device *dev,
- struct aq_ring_buff_s *buff)
+ struct net_device *dev)
{
struct xdp_frame *xdpf;
- struct sk_buff *skb;
xdpf = xdp_convert_buff_to_frame(xdp);
if (unlikely(!xdpf))
return NULL;
- skb = xdp_build_skb_from_frame(xdpf, dev);
- if (!skb)
- return NULL;
-
- aq_get_rxpages_xdp(buff, xdp);
- return skb;
+ return xdp_build_skb_from_frame(xdpf, dev);
}
static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
@@ -444,8 +408,16 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
u64_stats_update_end(&rx_ring->stats.rx.syncp);
prog = READ_ONCE(rx_ring->xdp_prog);
- if (!prog)
- return aq_xdp_build_skb(xdp, aq_nic->ndev, buff);
+ if (!prog) {
+ skb = aq_xdp_build_skb(xdp, aq_nic->ndev);
+ /* The ring has already handed its page pool reference to the
+ * xdp_buff, so if the skb could not be built the buffer must
+ * be returned to the pool here or its fragments would leak.
+ */
+ if (!skb)
+ xdp_return_buff(xdp);
+ return skb;
+ }
prefetchw(xdp->data_hard_start); /* xdp_frame write */
@@ -456,7 +428,7 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
act = bpf_prog_run_xdp(prog, xdp);
switch (act) {
case XDP_PASS:
- skb = aq_xdp_build_skb(xdp, aq_nic->ndev, buff);
+ skb = aq_xdp_build_skb(xdp, aq_nic->ndev);
if (!skb)
goto out_aborted;
u64_stats_update_begin(&rx_ring->stats.rx.syncp);
@@ -474,7 +446,6 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
u64_stats_update_begin(&rx_ring->stats.rx.syncp);
++rx_ring->stats.rx.xdp_tx;
u64_stats_update_end(&rx_ring->stats.rx.syncp);
- aq_get_rxpages_xdp(buff, xdp);
break;
case XDP_REDIRECT:
if (xdp_do_redirect(aq_nic->ndev, xdp, prog) < 0)
@@ -483,7 +454,6 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
u64_stats_update_begin(&rx_ring->stats.rx.syncp);
++rx_ring->stats.rx.xdp_redirect;
u64_stats_update_end(&rx_ring->stats.rx.syncp);
- aq_get_rxpages_xdp(buff, xdp);
break;
default:
fallthrough;
@@ -494,11 +464,13 @@ static struct sk_buff *aq_xdp_run_prog(struct aq_nic_s *aq_nic,
u64_stats_update_end(&rx_ring->stats.rx.syncp);
trace_xdp_exception(aq_nic->ndev, prog, act);
bpf_warn_invalid_xdp_action(aq_nic->ndev, prog, act);
+ xdp_return_buff(xdp);
break;
case XDP_DROP:
u64_stats_update_begin(&rx_ring->stats.rx.syncp);
++rx_ring->stats.rx.xdp_drop;
u64_stats_update_end(&rx_ring->stats.rx.syncp);
+ xdp_return_buff(xdp);
break;
}
@@ -517,8 +489,11 @@ static bool aq_add_rx_fragment(struct device *dev,
do {
skb_frag_t *frag;
- if (unlikely(sinfo->nr_frags >= MAX_SKB_FRAGS))
+ if (unlikely(sinfo->nr_frags >= MAX_SKB_FRAGS)) {
+ /* Attached frags must reach xdp_return_buff() */
+ xdp_buff_set_frags_flag(xdp);
return true;
+ }
frag = &sinfo->frags[sinfo->nr_frags++];
buff_ = &ring->buff_ring[buff_->next];
@@ -542,6 +517,11 @@ static bool aq_add_rx_fragment(struct device *dev,
if (page_is_pfmemalloc(buff_->rxdata.page))
xdp_buff_set_frag_pfmemalloc(xdp);
+ /* The frag's page pool reference is owned by the xdp_buff
+ * from now on.
+ */
+ buff_->rxdata.page = NULL;
+
} while (!buff_->is_eop);
xdp_buff_set_frags_flag(xdp);
@@ -645,6 +625,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,
err = -ENOMEM;
goto err_exit;
}
+ skb_mark_for_recycle(skb);
if (is_ptp_ring)
buff->len -=
aq_ptp_extract_ts(self->aq_nic, skb_hwtstamps(skb),
@@ -665,7 +646,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,
buff->rxdata.pg_off + hdr_len,
buff->len - hdr_len,
self->frame_max);
- page_ref_inc(buff->rxdata.page);
+ buff->rxdata.page = NULL;
}
if (!buff->is_eop) {
@@ -684,7 +665,7 @@ static int __aq_ring_rx_clean(struct aq_ring_s *self, struct napi_struct *napi,
buff_->rxdata.pg_off,
buff_->len,
self->frame_max);
- page_ref_inc(buff_->rxdata.page);
+ buff_->rxdata.page = NULL;
buff_->is_cleaned = 1;
buff->is_ip_cso &= buff_->is_ip_cso;
@@ -822,6 +803,11 @@ static int __aq_ring_xdp_clean(struct aq_ring_s *rx_ring,
xdp_init_buff(&xdp, frame_sz, &rx_ring->xdp_rxq);
xdp_prepare_buff(&xdp, hard_start, rx_ring->page_offset,
buff->len, false);
+ /* The xdp_buff owns the buffer's page pool reference from
+ * here on; it comes back through the MEM_TYPE_PAGE_POOL
+ * memory model on every XDP verdict.
+ */
+ buff->rxdata.page = NULL;
if (!buff->is_eop) {
if (aq_add_rx_fragment(dev, rx_ring, buff, &xdp)) {
u64_stats_update_begin(&rx_ring->stats.rx.syncp);
@@ -829,6 +815,7 @@ static int __aq_ring_xdp_clean(struct aq_ring_s *rx_ring,
rx_ring->stats.rx.bytes += xdp_get_buff_len(&xdp);
++rx_ring->stats.rx.xdp_aborted;
u64_stats_update_end(&rx_ring->stats.rx.syncp);
+ xdp_return_buff(&xdp);
continue;
}
}
@@ -921,15 +908,31 @@ int aq_ring_rx_fill(struct aq_ring_s *self)
void aq_ring_rx_deinit(struct aq_ring_s *self)
{
- if (!self)
+ unsigned int i;
+
+ if (!self || !self->buff_ring)
return;
- for (; self->sw_head != self->sw_tail;
- self->sw_head = aq_ring_next_dx(self, self->sw_head)) {
- struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head];
+ /* Release every page still owned by the ring.
+ *
+ * Walking [sw_head, sw_tail) is not enough: refill is batched
+ * (aq_ring_rx_fill() waits for AQ_CFG_RX_REFILL_THRES free slots),
+ * so slots that were cleaned but not yet reposted accumulate in the
+ * [sw_tail, sw_head) gap, and they keep their page for reuse. Walk
+ * the whole ring and release whatever is left.
+ */
+ for (i = 0; i < self->size; i++) {
+ struct aq_ring_buff_s *buff = &self->buff_ring[i];
+
+ if (!buff->rxdata.page)
+ continue;
- aq_free_rxpage(&buff->rxdata, aq_nic_get_dev(self->aq_nic));
+ page_pool_put_full_page(self->pg_pool, buff->rxdata.page,
+ false);
+ buff->rxdata.page = NULL;
}
+
+ self->sw_head = self->sw_tail;
}
void aq_ring_free(struct aq_ring_s *self)
@@ -940,6 +943,11 @@ void aq_ring_free(struct aq_ring_s *self)
kfree(self->buff_ring);
self->buff_ring = NULL;
+ if (self->pg_pool) {
+ page_pool_destroy(self->pg_pool);
+ self->pg_pool = NULL;
+ }
+
if (self->dx_ring) {
dma_free_coherent(aq_nic_get_dev(self->aq_nic),
self->size * self->dx_size, self->dx_ring,
@@ -978,9 +986,6 @@ unsigned int aq_ring_fill_stats_data(struct aq_ring_s *self, u64 *data)
data[++count] = self->stats.rx.alloc_fails;
data[++count] = self->stats.rx.skb_alloc_fails;
data[++count] = self->stats.rx.polls;
- data[++count] = self->stats.rx.pg_flips;
- data[++count] = self->stats.rx.pg_reuses;
- data[++count] = self->stats.rx.pg_losts;
data[++count] = self->stats.rx.xdp_aborted;
data[++count] = self->stats.rx.xdp_drop;
data[++count] = self->stats.rx.xdp_pass;
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h
index a70b880ada67e9..58bcadb3e3ccd3 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h
@@ -17,12 +17,12 @@
#define AQ_XDP_TAILROOM SKB_DATA_ALIGN(sizeof(struct skb_shared_info))
struct page;
+struct page_pool;
struct aq_nic_cfg_s;
struct aq_rxpage {
struct page *page;
dma_addr_t daddr;
- unsigned int order;
unsigned int pg_off;
};
@@ -105,9 +105,6 @@ struct aq_ring_stats_rx_s {
u64 alloc_fails;
u64 skb_alloc_fails;
u64 polls;
- u64 pg_losts;
- u64 pg_flips;
- u64 pg_reuses;
u64 xdp_aborted;
u64 xdp_drop;
u64 xdp_pass;
@@ -151,6 +148,7 @@ struct aq_ring_s {
u16 tail_size;
union aq_ring_stats_s stats;
dma_addr_t dx_ring_pa;
+ struct page_pool *pg_pool;
struct bpf_prog *xdp_prog;
enum atl_ring_type ring_type;
struct xdp_rxq_info xdp_rxq;
@@ -202,6 +200,7 @@ void aq_ring_update_queue_state(struct aq_ring_s *ring);
void aq_ring_queue_wake(struct aq_ring_s *ring);
void aq_ring_queue_stop(struct aq_ring_s *ring);
bool aq_ring_tx_clean(struct aq_ring_s *self);
+void aq_ring_tx_deinit(struct aq_ring_s *self);
int aq_xdp_xmit(struct net_device *dev, int num_frames,
struct xdp_frame **frames, u32 flags);
int aq_ring_rx_clean(struct aq_ring_s *self,
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
index 2f9033ceed8ce3..8e15405d49419d 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
@@ -146,25 +146,32 @@ int aq_vec_ring_alloc(struct aq_vec_s *self, struct aq_nic_s *aq_nic,
aq_nic_set_tx_ring(aq_nic, idx_ring, ring);
ring = &self->ring[i][AQ_VEC_RX_ID];
+ /* Registering the MEM_TYPE_PAGE_POOL memory model below needs
+ * the page pool created by aq_ring_rx_alloc(), so the ring is
+ * allocated first. If a registration fails, the ring has to be
+ * freed explicitly: rx_rings is not incremented yet, so the
+ * unwind through aq_vec_ring_free() would not cover it.
+ */
+ err = aq_ring_rx_alloc(ring, aq_nic, idx_ring, aq_nic_cfg);
+ if (err)
+ goto err_exit;
+
if (xdp_rxq_info_reg(&ring->xdp_rxq,
aq_nic->ndev, idx,
self->napi.napi_id) < 0) {
+ aq_ring_free(ring);
err = -ENOMEM;
goto err_exit;
}
if (xdp_rxq_info_reg_mem_model(&ring->xdp_rxq,
- MEM_TYPE_PAGE_SHARED, NULL) < 0) {
+ MEM_TYPE_PAGE_POOL,
+ ring->pg_pool) < 0) {
xdp_rxq_info_unreg(&ring->xdp_rxq);
+ aq_ring_free(ring);
err = -ENOMEM;
goto err_exit;
}
- err = aq_ring_rx_alloc(ring, aq_nic, idx_ring, aq_nic_cfg);
- if (err) {
- xdp_rxq_info_unreg(&ring->xdp_rxq);
- goto err_exit;
- }
-
++self->rx_rings;
}
@@ -275,7 +282,7 @@ void aq_vec_deinit(struct aq_vec_s *self)
for (i = 0U; self->tx_rings > i; ++i) {
ring = self->ring[i];
- aq_ring_tx_clean(&ring[AQ_VEC_TX_ID]);
+ aq_ring_tx_deinit(&ring[AQ_VEC_TX_ID]);
aq_ring_rx_deinit(&ring[AQ_VEC_RX_ID]);
}
|
| 3/2 | 2026/07/25 13:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10698 output: 91 thoughts: 1304 Thoughts: **WorthFuzzing=false: Analyzing the Reachability of an aQuantia Driver Patch** Alright, let's break this down. We've got a patch touching the aQuantia AQtion Ethernet driver, specifically in `drivers/net/ethernet/aquantia/atlantic/`. The key changes involve a new memory allocation model using `PAGE_POOL` for RX rings, modifications to handle XDP frames, and de-initialization of TX buffers. My immediate concern is whether this is even reachable for fuzzing. My first thought is, is this driver accessible within a typical QEMU setup? aQuantia is a 10GbE PCIe card, right? Let's check the source or a quick search... Nope, aQuantia is not emulated in standard QEMU. That's a huge red flag. My immediate thought is to check if it's considered a physical card. Looking at the instructions, they specifically say to mark vendor-specific PCIe switch, SmartNIC, or GPU drivers as not worth fuzzing if they require physical hardware. Does syzkaller offer a way around this? Could it fuzz PCI devices somehow? Wait, yes, it has PCI fuzzing. But can syzkaller *emulate* the device DMA ring buffers? No, it uses a virtual PCI host bridge, or just fuzzes the config space. It specifically says that these paths are unreachable when you require the physical hardware. Also, syzkaller tests patches submitted to mailing lists, so this patch is being tested, but doesn't mean it is fuzzed. Okay, let's verify if aQuantia is a physical PCIe card. Checking the Kconfig in `drivers/net/ethernet/aquantia/Kconfig` reveals: "This enables the support for the aQuantia AQtion(tm) Ethernet card." It depends on PCI. Confirmed: it's a physical Ethernet card. Therefore it falls under the "not worth fuzzing" category. Is there any chance of fuzzing via another route, like a virtual driver? Nope, it's a standard Ethernet driver for a physical PCIe card. Double-checking again... no QEMU emulation. Revisiting the prompt's criteria: "Return WorthFuzzing=false if the patch only contains code in vendor-specific PCIe switch, SmartNIC, or GPU drivers that require physical PCIe hardware cards not emulated in standard QEMU." aQuantia fits that description perfectly. It is a physical PCIe Ethernet card. It isn't emulated in standard QEMU. Just to be absolutely certain, let's eliminate the possibility of a USB connection. The Kconfig says it `depends on PCI`. Okay, that's it. It's strictly within the aQuantia driver. So, I'm confident in saying that WorthFuzzing=false. This patch modifies a driver for a physical PCIe card not emulated in standard environments, so these code paths are unreachable during fuzzing. I'm ready to move on. Let's call set-results. |
| 4/2 | 2026/07/25 13:48 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch exclusively modifies the aQuantia AQtion Ethernet driver (drivers/net/ethernet/aquantia/). This is a driver for a physical PCIe network card that is not emulated in standard QEMU or virtualized environments, making the modified code paths unreachable during fuzzing.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch exclusively modifies the aQuantia AQtion Ethernet driver (drivers/net/ethernet/aquantia/). This is a driver for a physical PCIe network card that is not emulated in standard QEMU or virtualized environments, making the modified code paths unreachable during fuzzing.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|