| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/19 14:21 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"XDP_SOCKETS"
],
"FocusSymbols": [
"xsk_tx_peek_release_desc_batch",
"xsk_tx_completed",
"__xsk_generic_xmit",
"xsk_build_skb",
"xsk_build_skb_zerocopy"
],
"Reasoning": "The patch modifies the AF_XDP socket implementation to handle invalid descriptors and completion ring behavior, particularly in the Tx path. It introduces batching and reclaim logic for descriptors. These are functional changes to the core AF_XDP logic and are reachable via standard AF_XDP socket operations (e.g., using generic XDP mode).",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/19 14:21 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 7c0d8584573390f7ad59dfa5fe580fbd96ce5c42\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Jul 19 14:21:24 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst\nindex 50d92084a49c6a..cc3f0d16b28fb6 100644\n--- a/Documentation/networking/af_xdp.rst\n+++ b/Documentation/networking/af_xdp.rst\n@@ -43,12 +43,13 @@ UMEM also has two rings: the FILL ring and the COMPLETION ring. The\n FILL ring is used by the application to send down addr for the kernel\n to fill in with RX packet data. References to these frames will then\n appear in the RX ring once each packet has been received. The\n-COMPLETION ring, on the other hand, contains frame addr that the\n-kernel has transmitted completely and can now be used again by user\n-space, for either TX or RX. Thus, the frame addrs appearing in the\n-COMPLETION ring are addrs that were previously transmitted using the\n-TX ring. In summary, the RX and FILL rings are used for the RX path\n-and the TX and COMPLETION rings are used for the TX path.\n+COMPLETION ring, on the other hand, contains frame addresses from Tx\n+descriptors that the kernel has finished processing and that can now be\n+used again by user space, for either Tx or Rx. This includes frames whose\n+transmission has completed as well as frames referenced by invalid Tx\n+descriptors rejected by the kernel. A completion therefore returns\n+ownership of a frame to user space, but does not by itself guarantee that\n+the packet was successfully transmitted.\n \n The socket is then finally bound with a bind() call to a device and a\n specific queue id on that device, and it is not until bind is\n@@ -169,14 +170,15 @@ chunks mode, then the incoming addr will be left untouched.\n UMEM Completion Ring\n ~~~~~~~~~~~~~~~~~~~~\n \n-The COMPLETION Ring is used transfer ownership of UMEM frames from\n+The COMPLETION Ring is used to transfer ownership of UMEM frames from\n kernel-space to user-space. Just like the FILL ring, UMEM indices are\n-used.\n-\n-Frames passed from the kernel to user-space are frames that has been\n-sent (TX ring) and can be used by user-space again.\n-\n-The user application consumes UMEM addrs from this ring.\n+used. Frames passed from the kernel to user-space are frames referenced\n+by Tx descriptors that the kernel has finished processing and can be\n+used by user-space again. This includes both frames whose transmission\n+has completed and frames referenced by invalid Tx descriptors that were\n+rejected and reclaimed by the kernel. A completion entry does not\n+guarantee successful packet transmission. The user application consumes\n+UMEM addrs from this ring.\n \n \n RX Ring\n@@ -504,21 +506,25 @@ will be treated as an invalid descriptor.\n These are the semantics for producing packets onto AF_XDP Tx ring\n consisting of multiple frames:\n \n-* When an invalid descriptor is found, all the other\n- descriptors/frames of this packet are marked as invalid and not\n- completed. The next descriptor is treated as the start of a new\n- packet, even if this was not the intent (because we cannot guess\n- the intent). As before, if your program is producing invalid\n- descriptors you have a bug that must be fixed.\n+* When an invalid descriptor is found, the complete packet is treated as\n+ invalid. The kernel consumes descriptors through the descriptor marking\n+ the end of the packet and returns all their frame addresses through the\n+ COMPLETION ring. A standalone invalid descriptor is treated as a\n+ one-descriptor invalid packet. The descriptor following the end of the\n+ invalid packet is treated as the start of a new packet. As before, if\n+ your program is producing invalid descriptors you have a bug that must\n+ be fixed. Rejected descriptors are reported in the ``tx_invalid_descs``\n+ statistic.\n \n * Zero length descriptors are treated as invalid descriptors.\n \n * For copy mode, the maximum supported number of frames in a packet is\n- equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all\n- descriptors accumulated so far are dropped and treated as\n- invalid. To produce an application that will work on any system\n- regardless of this config setting, limit the number of frags to 18,\n- as the minimum value of the config is 17.\n+ equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all descriptors\n+ through the end of the oversized packet are consumed, treated as invalid,\n+ and their frame addresses are returned through the COMPLETION ring. To\n+ produce an application that will work on any system regardless of this\n+ config setting, limit the number of frags to 18, as the minimum value of\n+ the config is 17.\n \n * For zero-copy mode, the limit is up to what the NIC HW\n supports. Usually at least five on the NICs we have checked. We\ndiff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h\nindex ebac60a3d8a17b..8b51876efbed1a 100644\n--- a/include/net/xdp_sock.h\n+++ b/include/net/xdp_sock.h\n@@ -80,6 +80,7 @@ struct xdp_sock {\n \t * call of __xsk_generic_xmit().\n \t */\n \tstruct sk_buff *skb;\n+\tbool drain_cont;\n \n \tstruct list_head map_list;\n \t/* Protects map_list */\ndiff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h\nindex ccb3b350001f24..2bb1d122b1bc6b 100644\n--- a/include/net/xsk_buff_pool.h\n+++ b/include/net/xsk_buff_pool.h\n@@ -78,6 +78,9 @@ struct xsk_buff_pool {\n \tu32 chunk_size;\n \tu32 chunk_shift;\n \tu32 frame_len;\n+\tu32 tx_descs_nentries;\n+\tu32 reclaim_descs;\n+\tu32 tx_zc_pending_descs;\n \tu32 xdp_zc_max_segs;\n \tu8 tx_metadata_len; /* inherited from umem */\n \tu8 cached_need_wakeup;\n@@ -102,12 +105,14 @@ struct xsk_buff_pool {\n \n /* AF_XDP core. */\n struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,\n-\t\t\t\t\t\tstruct xdp_umem *umem);\n+\t\t\t\t\t\tstruct xdp_umem *umem,\n+\t\t\t\t\t\tu32 max_segs);\n int xp_assign_dev(struct xsk_buff_pool *pool, struct net_device *dev,\n \t\t u16 queue_id, u16 flags);\n int xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs,\n \t\t\t struct net_device *dev, u16 queue_id);\n-int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs);\n+int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs,\n+\t\t u32 max_segs);\n void xp_destroy(struct xsk_buff_pool *pool);\n void xp_get_pool(struct xsk_buff_pool *pool);\n bool xp_put_pool(struct xsk_buff_pool *pool);\ndiff --git a/net/xdp/xsk.c b/net/xdp/xsk.c\nindex b970f30ea9b9d8..f906d51b669904 100644\n--- a/net/xdp/xsk.c\n+++ b/net/xdp/xsk.c\n@@ -499,6 +499,23 @@ void __xsk_map_flush(struct list_head *flush_list)\n \n void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries)\n {\n+\tu32 reclaim_descs = READ_ONCE(pool-\u003ereclaim_descs);\n+\n+\tif (unlikely(reclaim_descs)) {\n+\t\tu32 pending_descs = READ_ONCE(pool-\u003etx_zc_pending_descs);\n+\n+\t\tif (nb_entries \u003c pending_descs) {\n+\t\t\tWRITE_ONCE(pool-\u003etx_zc_pending_descs,\n+\t\t\t\t pending_descs - nb_entries);\n+\t\t\txskq_prod_submit_n(pool-\u003ecq, nb_entries);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tWRITE_ONCE(pool-\u003etx_zc_pending_descs, 0);\n+\t\tnb_entries += reclaim_descs;\n+\t\tWRITE_ONCE(pool-\u003ereclaim_descs, 0);\n+\t}\n+\n \txskq_prod_submit_n(pool-\u003ecq, nb_entries);\n }\n EXPORT_SYMBOL(xsk_tx_completed);\n@@ -574,24 +591,157 @@ static u32 xsk_tx_peek_release_fallback(struct xsk_buff_pool *pool, u32 max_entr\n \treturn nb_pkts;\n }\n \n+static void xsk_tx_commit_batch(struct xsk_buff_pool *pool,\n+\t\t\t\tstruct xsk_tx_batch *batch)\n+{\n+\tu32 nb_descs = xsk_tx_batch_cq_descs(batch);\n+\tu32 cq_cached_prod;\n+\n+\tif (!nb_descs)\n+\t\treturn;\n+\n+\tcq_cached_prod = pool-\u003ecq-\u003ecached_prod;\n+\txskq_prod_write_addr_batch(pool-\u003ecq, pool-\u003etx_descs, nb_descs);\n+\n+\tif (unlikely(batch-\u003ereclaim_descs)) {\n+\t\tu32 cq_pending_descs;\n+\n+\t\t/* CQ is positional. Descriptors already written but not\n+\t\t * submitted must complete before any reclaim-only descriptors\n+\t\t * appended below.\n+\t\t */\n+\t\tcq_pending_descs = cq_cached_prod - xskq_get_prod(pool-\u003ecq);\n+\n+\t\tWRITE_ONCE(pool-\u003etx_zc_pending_descs,\n+\t\t\t batch-\u003etx_descs + cq_pending_descs);\n+\t\tWRITE_ONCE(pool-\u003ereclaim_descs, batch-\u003ereclaim_descs);\n+\t\tif (unlikely(!pool-\u003etx_zc_pending_descs))\n+\t\t\txsk_tx_completed(pool, 0);\n+\t}\n+}\n+\n+static struct xsk_tx_batch\n+__xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, struct xdp_sock *xs,\n+\t\t\t\t struct xdp_desc *descs, u32 max_descs)\n+{\n+\tstruct xsk_tx_batch batch = {};\n+\tu32 entries;\n+\n+\tentries = xskq_cons_nb_entries(xs-\u003etx, max_descs);\n+\tif (!entries)\n+\t\treturn batch;\n+\n+\tbatch = xskq_cons_read_desc_batch(xs, pool, descs, max_descs);\n+\tif (!xsk_tx_batch_cq_descs(\u0026batch)) {\n+\t\txs-\u003etx-\u003equeue_empty_descs++;\n+\t} else {\n+\t\t__xskq_cons_release(xs-\u003etx);\n+\t\txs-\u003esk.sk_write_space(\u0026xs-\u003esk);\n+\t}\n+\treturn batch;\n+}\n+\n+static struct xsk_tx_batch\n+xsk_tx_peek_release_shared_desc_batch(struct xsk_buff_pool *pool, u32 max_descs)\n+{\n+\tu32 cq_descs_before, cq_descs_after;\n+\tstruct xsk_tx_batch sum_batch = {};\n+\tbool budget_exhausted;\n+\tu32 per_socket_budget;\n+\tstruct xdp_sock *xs;\n+\n+\t/* The fairness quota must allow one maximum-sized valid packet. */\n+\tper_socket_budget = max_t(u32, MAX_PER_SOCKET_BUDGET,\n+\t\t\t\t pool-\u003exdp_zc_max_segs);\n+\n+again:\n+\tbudget_exhausted = false;\n+\tcq_descs_before = xsk_tx_batch_cq_descs(\u0026sum_batch);\n+\tlist_for_each_entry_rcu(xs, \u0026pool-\u003exsk_tx_list, tx_list) {\n+\t\tu32 budget, budget_left, offset, remaining, used;\n+\t\tstruct xsk_tx_batch curr_batch;\n+\n+\t\t/* Once reclaim-only descriptors have been appended to the CQ\n+\t\t * address area, do not append driver-visible Tx descriptors\n+\t\t * from another socket after them. xsk_tx_completed() relies on\n+\t\t * all driver-visible descriptors preceding all reclaim-only\n+\t\t * descriptors in CQ order.\n+\t\t */\n+\t\tif (sum_batch.reclaim_descs)\n+\t\t\tbreak;\n+\n+\t\t/* be gentle when playing with pool-\u003etx_descs */\n+\t\toffset = xsk_tx_batch_cq_descs(\u0026sum_batch);\n+\t\tif (offset \u003e= max_descs)\n+\t\t\tbreak;\n+\n+\t\tif (xs-\u003etx_budget_spent \u003e= per_socket_budget) {\n+\t\t\tif (xskq_cons_nb_entries(xs-\u003etx, 1))\n+\t\t\t\tbudget_exhausted = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tbudget_left = per_socket_budget - xs-\u003etx_budget_spent;\n+\t\tremaining = max_descs - offset;\n+\t\tbudget = min(remaining, budget_left);\n+\n+\t\tcurr_batch = __xsk_tx_peek_release_desc_batch(pool, xs,\n+\t\t\t\t\t\t\t pool-\u003etx_descs + offset,\n+\t\t\t\t\t\t\t budget);\n+\t\tused = xsk_tx_batch_cq_descs(\u0026curr_batch);\n+\t\tif (!used) {\n+\t\t\tif (curr_batch.budget_limited \u0026\u0026 budget_left \u003c remaining)\n+\t\t\t\tbudget_exhausted = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\txs-\u003etx_budget_spent += used;\n+\t\tsum_batch.tx_descs += curr_batch.tx_descs;\n+\t\tsum_batch.reclaim_descs = curr_batch.reclaim_descs;\n+\t}\n+\n+\tcq_descs_after = xsk_tx_batch_cq_descs(\u0026sum_batch);\n+\n+\tif (sum_batch.reclaim_descs || cq_descs_after \u003e= max_descs)\n+\t\treturn sum_batch;\n+\n+\t/* Continue filling the batch while this pass made progress */\n+\tif (cq_descs_before != cq_descs_after)\n+\t\tgoto again;\n+\n+\tif (!budget_exhausted)\n+\t\treturn sum_batch;\n+\n+\tlist_for_each_entry_rcu(xs, \u0026pool-\u003exsk_tx_list, tx_list)\n+\t\txs-\u003etx_budget_spent = 0;\n+\tgoto again;\n+}\n+\n u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts)\n {\n+\tstruct xsk_tx_batch batch = {};\n \tstruct xdp_sock *xs;\n+\tbool umem_shared;\n \n \trcu_read_lock();\n-\tif (!list_is_singular(\u0026pool-\u003exsk_tx_list)) {\n-\t\t/* Fallback to the non-batched version */\n-\t\trcu_read_unlock();\n-\t\treturn xsk_tx_peek_release_fallback(pool, nb_pkts);\n-\t}\n+\tif (unlikely(READ_ONCE(pool-\u003ereclaim_descs)))\n+\t\tgoto out;\n \n-\txs = list_first_or_null_rcu(\u0026pool-\u003exsk_tx_list, struct xdp_sock, tx_list);\n-\tif (!xs) {\n-\t\tnb_pkts = 0;\n+\txs = list_first_or_null_rcu(\u0026pool-\u003exsk_tx_list, struct xdp_sock,\n+\t\t\t\t tx_list);\n+\tif (!xs)\n \t\tgoto out;\n-\t}\n \n-\tnb_pkts = xskq_cons_nb_entries(xs-\u003etx, nb_pkts);\n+\tnb_pkts = min(nb_pkts, pool-\u003etx_descs_nentries);\n+\tif (!nb_pkts)\n+\t\tgoto out;\n+\n+\tumem_shared = !list_is_singular(\u0026pool-\u003exsk_tx_list);\n+\n+\tif (umem_shared \u0026\u0026 !(pool-\u003eumem-\u003eflags \u0026 XDP_UMEM_SG_FLAG)) {\n+\t\trcu_read_unlock();\n+\t\treturn xsk_tx_peek_release_fallback(pool, nb_pkts);\n+\t}\n \n \t/* This is the backpressure mechanism for the Tx path. Try to\n \t * reserve space in the completion queue for all packets, but\n@@ -603,19 +753,16 @@ u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts)\n \tif (!nb_pkts)\n \t\tgoto out;\n \n-\tnb_pkts = xskq_cons_read_desc_batch(xs-\u003etx, pool, nb_pkts);\n-\tif (!nb_pkts) {\n-\t\txs-\u003etx-\u003equeue_empty_descs++;\n-\t\tgoto out;\n-\t}\n-\n-\t__xskq_cons_release(xs-\u003etx);\n-\txskq_prod_write_addr_batch(pool-\u003ecq, pool-\u003etx_descs, nb_pkts);\n-\txs-\u003esk.sk_write_space(\u0026xs-\u003esk);\n+\tbatch = umem_shared ?\n+\t\txsk_tx_peek_release_shared_desc_batch(pool, nb_pkts) :\n+\t\t__xsk_tx_peek_release_desc_batch(pool, xs,\n+\t\t\t\t\t\t pool-\u003etx_descs,\n+\t\t\t\t\t\t nb_pkts);\n+\txsk_tx_commit_batch(pool, \u0026batch);\n \n out:\n \trcu_read_unlock();\n-\treturn nb_pkts;\n+\treturn batch.tx_descs;\n }\n EXPORT_SYMBOL(xsk_tx_peek_release_desc_batch);\n \n@@ -737,6 +884,19 @@ static void xsk_cq_submit_addr_locked(struct xsk_buff_pool *pool,\n \tspin_unlock_irqrestore(\u0026pool-\u003ecq_prod_lock, flags);\n }\n \n+static void xsk_cq_submit_addr_single_locked(struct xsk_buff_pool *pool,\n+\t\t\t\t\t struct xdp_desc *desc)\n+{\n+\tunsigned long flags;\n+\tu32 idx;\n+\n+\tspin_lock_irqsave(\u0026pool-\u003ecq_prod_lock, flags);\n+\tidx = xskq_get_prod(pool-\u003ecq);\n+\txskq_prod_write_addr(pool-\u003ecq, idx, desc-\u003eaddr);\n+\txskq_prod_submit_n(pool-\u003ecq, 1);\n+\tspin_unlock_irqrestore(\u0026pool-\u003ecq_prod_lock, flags);\n+}\n+\n static void xsk_cq_cancel_locked(struct xsk_buff_pool *pool, u32 n)\n {\n \tspin_lock(\u0026pool-\u003ecq-\u003ecq_cached_prod_lock);\n@@ -794,8 +954,11 @@ static void xsk_consume_skb(struct sk_buff *skb)\n \n static void xsk_drop_skb(struct sk_buff *skb)\n {\n-\txdp_sk(skb-\u003esk)-\u003etx-\u003einvalid_descs += xsk_get_num_desc(skb);\n-\txsk_consume_skb(skb);\n+\tstruct xdp_sock *xs = xdp_sk(skb-\u003esk);\n+\n+\txs-\u003etx-\u003einvalid_descs += xsk_get_num_desc(skb);\n+\tconsume_skb(skb);\n+\txs-\u003eskb = NULL;\n }\n \n static int xsk_skb_metadata(struct sk_buff *skb, void *buffer,\n@@ -877,7 +1040,7 @@ static struct sk_buff *xsk_build_skb_zerocopy(struct xdp_sock *xs,\n \t\t\treturn ERR_PTR(-ENOMEM);\n \n \t\t/* in case of -EOVERFLOW that could happen below,\n-\t\t * xsk_consume_skb() will release this node as whole skb\n+\t\t * xsk_drop_skb() will release this node as whole skb\n \t\t * would be dropped, which implies freeing all list elements\n \t\t */\n \t\txsk_addr-\u003eaddrs[xsk_addr-\u003enum_descs] = desc-\u003eaddr;\n@@ -969,6 +1132,8 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,\n \t\t\t\tgoto free_err;\n \t\t\t}\n \n+\t\t\txsk_addr-\u003eaddrs[xsk_addr-\u003enum_descs] = desc-\u003eaddr;\n+\n \t\t\tif (unlikely(nr_frags == (MAX_SKB_FRAGS - 1) \u0026\u0026 xp_mb_desc(desc))) {\n \t\t\t\terr = -EOVERFLOW;\n \t\t\t\tgoto free_err;\n@@ -986,8 +1151,6 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,\n \n \t\t\tskb_add_rx_frag(skb, nr_frags, page, 0, len, PAGE_SIZE);\n \t\t\trefcount_add(PAGE_SIZE, \u0026xs-\u003esk.sk_wmem_alloc);\n-\n-\t\t\txsk_addr-\u003eaddrs[xsk_addr-\u003enum_descs] = desc-\u003eaddr;\n \t\t}\n \t}\n \n@@ -1025,13 +1188,14 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,\n static int __xsk_generic_xmit(struct sock *sk)\n {\n \tstruct xdp_sock *xs = xdp_sk(sk);\n-\tbool sent_frame = false;\n \tstruct xdp_desc desc;\n \tstruct sk_buff *skb;\n+\tu32 cached_cons;\n \tu32 max_batch;\n \tint err = 0;\n \n \tmutex_lock(\u0026xs-\u003emutex);\n+\tcached_cons = xs-\u003etx-\u003ecached_cons;\n \n \t/* Since we dropped the RCU read lock, the socket state might have changed. */\n \tif (unlikely(!xsk_is_bound(xs))) {\n@@ -1060,11 +1224,21 @@ static int __xsk_generic_xmit(struct sock *sk)\n \t\t\tgoto out;\n \t\t}\n \n+\t\tif (unlikely(xs-\u003edrain_cont)) {\n+\t\t\txsk_cq_submit_addr_single_locked(xs-\u003epool, \u0026desc);\n+\t\t\txs-\u003etx-\u003einvalid_descs++;\n+\t\t\txskq_cons_release(xs-\u003etx);\n+\t\t\txs-\u003edrain_cont = xp_mb_desc(\u0026desc);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tskb = xsk_build_skb(xs, \u0026desc);\n \t\tif (IS_ERR(skb)) {\n \t\t\terr = PTR_ERR(skb);\n \t\t\tif (err != -EOVERFLOW)\n \t\t\t\tgoto out;\n+\t\t\tif (xp_mb_desc(\u0026desc))\n+\t\t\t\txs-\u003edrain_cont = true;\n \t\t\terr = 0;\n \t\t\tcontinue;\n \t\t}\n@@ -1093,18 +1267,33 @@ static int __xsk_generic_xmit(struct sock *sk)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tsent_frame = true;\n \t\txs-\u003eskb = NULL;\n \t}\n \n \tif (xskq_has_descs(xs-\u003etx)) {\n+\t\tbool drain = xs-\u003eskb || xs-\u003edrain_cont || xp_mb_desc(\u0026desc);\n+\n+\t\terr = xsk_cq_reserve_locked(xs-\u003epool);\n+\t\tif (err) {\n+\t\t\txs-\u003etx-\u003einvalid_descs--;\n+\t\t\tif (xs-\u003eskb)\n+\t\t\t\txsk_drop_skb(xs-\u003eskb);\n+\t\t\txs-\u003edrain_cont = drain;\n+\t\t\terr = -EAGAIN;\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tif (xs-\u003eskb)\n \t\t\txsk_drop_skb(xs-\u003eskb);\n+\n+\t\txsk_cq_submit_addr_single_locked(xs-\u003epool, \u0026desc);\n+\n \t\txskq_cons_release(xs-\u003etx);\n+\t\txs-\u003edrain_cont = xp_mb_desc(\u0026desc);\n \t}\n \n out:\n-\tif (sent_frame)\n+\tif (xs-\u003etx-\u003ecached_cons != cached_cons)\n \t\t__xsk_tx_release(xs);\n \n \tmutex_unlock(\u0026xs-\u003emutex);\n@@ -1483,7 +1672,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr\n \t\t\t * and/or device.\n \t\t\t */\n \t\t\txs-\u003epool = xp_create_and_assign_umem(xs,\n-\t\t\t\t\t\t\t umem_xs-\u003eumem);\n+\t\t\t\t\t\t\t umem_xs-\u003eumem,\n+\t\t\t\t\t\t\t dev-\u003exdp_zc_max_segs);\n \t\t\tif (!xs-\u003epool) {\n \t\t\t\terr = -ENOMEM;\n \t\t\t\tsockfd_put(sock);\n@@ -1515,7 +1705,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr\n \t\t\t * utilizes\n \t\t\t */\n \t\t\tif (xs-\u003etx \u0026\u0026 !xs-\u003epool-\u003etx_descs) {\n-\t\t\t\terr = xp_alloc_tx_descs(xs-\u003epool, xs);\n+\t\t\t\terr = xp_alloc_tx_descs(xs-\u003epool, xs,\n+\t\t\t\t\t\t\tdev-\u003exdp_zc_max_segs);\n \t\t\t\tif (err) {\n \t\t\t\t\txp_put_pool(xs-\u003epool);\n \t\t\t\t\txs-\u003epool = NULL;\n@@ -1533,7 +1724,9 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr\n \t\tgoto out_unlock;\n \t} else {\n \t\t/* This xsk has its own umem. */\n-\t\txs-\u003epool = xp_create_and_assign_umem(xs, xs-\u003eumem);\n+\t\txs-\u003epool = xp_create_and_assign_umem(xs, xs-\u003eumem,\n+\t\t\t\t\t\t dev-\u003exdp_zc_max_segs);\n+\n \t\tif (!xs-\u003epool) {\n \t\t\terr = -ENOMEM;\n \t\t\tgoto out_unlock;\ndiff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c\nindex 1f28a9641571e4..a4089480b22b25 100644\n--- a/net/xdp/xsk_buff_pool.c\n+++ b/net/xdp/xsk_buff_pool.c\n@@ -42,17 +42,22 @@ void xp_destroy(struct xsk_buff_pool *pool)\n \tkvfree(pool);\n }\n \n-int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs)\n+int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs,\n+\t\t u32 max_segs)\n {\n-\tpool-\u003etx_descs = kvzalloc_objs(*pool-\u003etx_descs, xs-\u003etx-\u003enentries);\n+\tu32 nentries = max(xs-\u003etx-\u003enentries, max_segs);\n+\n+\tpool-\u003etx_descs = kvzalloc_objs(*pool-\u003etx_descs, nentries);\n \tif (!pool-\u003etx_descs)\n \t\treturn -ENOMEM;\n \n+\tpool-\u003etx_descs_nentries = nentries;\n \treturn 0;\n }\n \n struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,\n-\t\t\t\t\t\tstruct xdp_umem *umem)\n+\t\t\t\t\t\tstruct xdp_umem *umem,\n+\t\t\t\t\t\tu32 max_segs)\n {\n \tbool unaligned = umem-\u003eflags \u0026 XDP_UMEM_UNALIGNED_CHUNK_FLAG;\n \tstruct xsk_buff_pool *pool;\n@@ -69,7 +74,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,\n \t\tgoto out;\n \n \tif (xs-\u003etx)\n-\t\tif (xp_alloc_tx_descs(pool, xs))\n+\t\tif (xp_alloc_tx_descs(pool, xs, max_segs))\n \t\t\tgoto out;\n \n \tpool-\u003echunk_mask = ~((u64)umem-\u003echunk_size - 1);\ndiff --git a/net/xdp/xsk_queue.h b/net/xdp/xsk_queue.h\nindex 3e3fbb73d23e2f..1bc42c8902f4ba 100644\n--- a/net/xdp/xsk_queue.h\n+++ b/net/xdp/xsk_queue.h\n@@ -58,6 +58,17 @@ struct parsed_desc {\n \tu32 valid;\n };\n \n+struct xsk_tx_batch {\n+\tu32 tx_descs;\n+\tu32 reclaim_descs;\n+\tbool budget_limited;\n+};\n+\n+static inline u32 xsk_tx_batch_cq_descs(const struct xsk_tx_batch *batch)\n+{\n+\treturn batch-\u003etx_descs + batch-\u003ereclaim_descs;\n+}\n+\n /* The structure of the shared state of the rings are a simple\n * circular buffer, as outlined in\n * Documentation/core-api/circular-buffers.rst. For the Rx and\n@@ -263,17 +274,18 @@ static inline void parse_desc(struct xsk_queue *q, struct xsk_buff_pool *pool,\n \tparsed-\u003emb = xp_mb_desc(desc);\n }\n \n-static inline\n-u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool,\n-\t\t\t u32 max)\n+static inline struct xsk_tx_batch\n+xskq_cons_read_desc_batch(struct xdp_sock *xs, struct xsk_buff_pool *pool,\n+\t\t\t struct xdp_desc *descs, u32 max)\n {\n-\tu32 cached_cons = q-\u003ecached_cons, nb_entries = 0;\n-\tstruct xdp_desc *descs = pool-\u003etx_descs;\n-\tu32 total_descs = 0, nr_frags = 0;\n+\tbool drain = READ_ONCE(xs-\u003edrain_cont);\n+\tu32 cached_cons, nb_entries = 0;\n+\tstruct xsk_tx_batch batch = {};\n+\tstruct xsk_queue *q = xs-\u003etx;\n+\tu32 nr_frags = 0;\n+\n+\tcached_cons = q-\u003ecached_cons;\n \n-\t/* track first entry, if stumble upon *any* invalid descriptor, rewind\n-\t * current packet that consists of frags and stop the processing\n-\t */\n \twhile (cached_cons != q-\u003ecached_prod \u0026\u0026 nb_entries \u003c max) {\n \t\tstruct xdp_rxtx_ring *ring = (struct xdp_rxtx_ring *)q-\u003ering;\n \t\tu32 idx = cached_cons \u0026 q-\u003ering_mask;\n@@ -283,25 +295,42 @@ u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool,\n \t\tcached_cons++;\n \t\tparse_desc(q, pool, \u0026descs[nb_entries], \u0026parsed);\n \t\tif (unlikely(!parsed.valid))\n-\t\t\tbreak;\n+\t\t\tdrain = true;\n+\n+\t\tnr_frags++;\n+\t\tnb_entries++;\n \n \t\tif (likely(!parsed.mb)) {\n-\t\t\ttotal_descs += (nr_frags + 1);\n-\t\t\tnr_frags = 0;\n-\t\t} else {\n-\t\t\tnr_frags++;\n-\t\t\tif (nr_frags == pool-\u003exdp_zc_max_segs) {\n+\t\t\tif (unlikely(drain)) {\n+\t\t\t\tbatch.reclaim_descs = nr_frags;\n+\t\t\t\tWRITE_ONCE(xs-\u003edrain_cont, false);\n \t\t\t\tnr_frags = 0;\n \t\t\t\tbreak;\n \t\t\t}\n+\n+\t\t\tbatch.tx_descs += nr_frags;\n+\t\t\tnr_frags = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (nr_frags == pool-\u003exdp_zc_max_segs)\n+\t\t\tdrain = true;\n+\t}\n+\n+\tif (nr_frags) {\n+\t\tif (drain) {\n+\t\t\tbatch.reclaim_descs = nr_frags;\n+\t\t\tWRITE_ONCE(xs-\u003edrain_cont, true);\n+\t\t} else {\n+\t\t\tif (nb_entries == max)\n+\t\t\t\tbatch.budget_limited = true;\n+\t\t\tcached_cons -= nr_frags;\n \t\t}\n-\t\tnb_entries++;\n \t}\n \n-\tcached_cons -= nr_frags;\n \t/* Release valid plus any invalid entries */\n \txskq_cons_release_n(q, cached_cons - q-\u003ecached_cons);\n-\treturn total_descs;\n+\treturn batch;\n }\n \n /* Functions for consumers */\ndiff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c\nindex 477aedbb01bad8..4549358cc8c21d 100644\n--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c\n+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c\n@@ -427,14 +427,14 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk\n \t}\n \n \t/* Search for the end of the packet in verbatim mode */\n-\tif (!pkt_continues(pkt-\u003eoptions) || !pkt-\u003evalid)\n+\tif (!pkt_continues(pkt-\u003eoptions))\n \t\treturn nb_frags;\n \n \tnext_frag = pkt_stream-\u003ecurrent_pkt_nb;\n \tpkt++;\n \twhile (next_frag++ \u003c pkt_stream-\u003enb_pkts) {\n \t\tnb_frags++;\n-\t\tif (!pkt_continues(pkt-\u003eoptions) || !pkt-\u003evalid)\n+\t\tif (!pkt_continues(pkt-\u003eoptions))\n \t\t\tbreak;\n \t\tpkt++;\n \t}\n@@ -665,11 +665,11 @@ static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, s\n \t\t\tif (!frame-\u003evalid || !pkt_continues(frame-\u003eoptions))\n \t\t\t\tpayload++;\n \t\t} else {\n-\t\t\tif (frame-\u003evalid)\n+\t\t\tif (frame-\u003evalid) {\n \t\t\t\tlen += frame-\u003elen;\n-\t\t\tif (frame-\u003evalid \u0026\u0026 pkt_continues(frame-\u003eoptions))\n-\t\t\t\tcontinue;\n-\n+\t\t\t\tif (pkt_continues(frame-\u003eoptions))\n+\t\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tpkt-\u003epkt_nb = pkt_nb;\n \t\t\tpkt-\u003elen = len;\n \t\t\tpkt-\u003evalid = frame-\u003evalid;\n@@ -1250,10 +1250,9 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,\n \t\t\t}\n \t\t}\n \n-\t\tif (pkt \u0026\u0026 pkt-\u003evalid) {\n+\t\tif (pkt \u0026\u0026 pkt-\u003evalid)\n \t\t\tvalid_pkts++;\n-\t\t\tvalid_frags += nb_frags;\n-\t\t}\n+\t\tvalid_frags += nb_frags;\n \t}\n \n \tpthread_mutex_lock(\u0026pacing_mutex);\n@@ -2099,13 +2098,16 @@ int testapp_invalid_desc_mb(struct test_spec *test)\n \t\t{0, 0, 0, false, 0},\n \t\t/* Invalid address in the second frame */\n \t\t{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n-\t\t{umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n+\t\t{umem_sz * 2, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n+\t\t{0, MIN_PKT_SIZE, 0, false, 0},\n \t\t/* Invalid len in the middle */\n \t\t{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n \t\t{0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n+\t\t{0, MIN_PKT_SIZE, 0, false, 0},\n \t\t/* Invalid options in the middle */\n \t\t{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},\n \t\t{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION},\n+\t\t{0, MIN_PKT_SIZE, 0, false, 0},\n \t\t/* Transmit 2 frags, receive 3 */\n \t\t{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD},\n \t\t{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0},\n@@ -2117,8 +2119,8 @@ int testapp_invalid_desc_mb(struct test_spec *test)\n \n \tif (umem-\u003eunaligned_mode) {\n \t\t/* Crossing a chunk boundary allowed */\n-\t\tpkts[12].valid = true;\n-\t\tpkts[13].valid = true;\n+\t\tpkts[15].valid = true;\n+\t\tpkts[16].valid = true;\n \t}\n \n \ttest-\u003emtu = MAX_ETH_JUMBO_SIZE;\n@@ -2270,7 +2272,7 @@ int testapp_too_many_frags(struct test_spec *test)\n \t\tmax_frags += 1;\n \t}\n \n-\tpkts = calloc(2 * max_frags + 2, sizeof(struct pkt));\n+\tpkts = calloc(2 * max_frags + 3, sizeof(struct pkt));\n \tif (!pkts)\n \t\treturn TEST_FAILURE;\n \n@@ -2288,24 +2290,30 @@ int testapp_too_many_frags(struct test_spec *test)\n \t}\n \tpkts[max_frags].options = 0;\n \n-\t/* An invalid packet with the max amount of frags but signals packet\n-\t * continues on the last frag\n-\t */\n-\tfor (i = max_frags + 1; i \u003c 2 * max_frags + 1; i++) {\n+\t/* An invalid packet with the max + 1 amount of frags */\n+\tfor (i = max_frags + 1; i \u003c 2 * max_frags + 2; i++) {\n \t\tpkts[i].len = MIN_PKT_SIZE;\n \t\tpkts[i].options = XDP_PKT_CONTD;\n-\t\tpkts[i].valid = false;\n+\t\tpkts[i].valid = true;\n \t}\n+\tpkts[2 * max_frags + 1].options = 0;\n \n \t/* Valid packet for synch */\n-\tpkts[2 * max_frags + 1].len = MIN_PKT_SIZE;\n-\tpkts[2 * max_frags + 1].valid = true;\n+\tpkts[2 * max_frags + 2].len = MIN_PKT_SIZE;\n+\tpkts[2 * max_frags + 2].valid = true;\n \n-\tif (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2)) {\n+\tif (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 3)) {\n \t\tfree(pkts);\n \t\treturn TEST_FAILURE;\n \t}\n \n+\t/* The generated Tx stream must keep the too-big packet valid so that\n+\t * __send_pkts() accounts its descriptors in outstanding_tx. The Rx\n+\t * stream, however, must not expect this packet on the wire.\n+\t */\n+\ttest-\u003eifobj_rx-\u003exsk-\u003epkt_stream-\u003epkts[2].valid = false;\n+\ttest-\u003eifobj_rx-\u003exsk-\u003epkt_stream-\u003enb_valid_entries--;\n+\n \tret = testapp_validate_traffic(test);\n \tfree(pkts);\n \treturn ret;\n"
} |
| 2/1 | 2026/07/19 14:21 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"XDP_SOCKETS"
],
"FocusSymbols": [
"xsk_tx_peek_release_desc_batch",
"xsk_tx_completed",
"__xsk_generic_xmit",
"xsk_build_skb",
"xsk_build_skb_zerocopy"
],
"Reasoning": "The patch modifies the AF_XDP socket implementation to handle invalid descriptors and completion ring behavior, particularly in the Tx path. It introduces batching and reclaim logic for descriptors. These are functional changes to the core AF_XDP logic and are reachable via standard AF_XDP socket operations (e.g., using generic XDP mode).",
"WorthFuzzing": true
}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). 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. 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. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. 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 7c0d8584573390f7ad59dfa5fe580fbd96ce5c42
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Jul 19 14:21:24 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst
index 50d92084a49c6a..cc3f0d16b28fb6 100644
--- a/Documentation/networking/af_xdp.rst
+++ b/Documentation/networking/af_xdp.rst
@@ -43,12 +43,13 @@ UMEM also has two rings: the FILL ring and the COMPLETION ring. The
FILL ring is used by the application to send down addr for the kernel
to fill in with RX packet data. References to these frames will then
appear in the RX ring once each packet has been received. The
-COMPLETION ring, on the other hand, contains frame addr that the
-kernel has transmitted completely and can now be used again by user
-space, for either TX or RX. Thus, the frame addrs appearing in the
-COMPLETION ring are addrs that were previously transmitted using the
-TX ring. In summary, the RX and FILL rings are used for the RX path
-and the TX and COMPLETION rings are used for the TX path.
+COMPLETION ring, on the other hand, contains frame addresses from Tx
+descriptors that the kernel has finished processing and that can now be
+used again by user space, for either Tx or Rx. This includes frames whose
+transmission has completed as well as frames referenced by invalid Tx
+descriptors rejected by the kernel. A completion therefore returns
+ownership of a frame to user space, but does not by itself guarantee that
+the packet was successfully transmitted.
The socket is then finally bound with a bind() call to a device and a
specific queue id on that device, and it is not until bind is
@@ -169,14 +170,15 @@ chunks mode, then the incoming addr will be left untouched.
UMEM Completion Ring
~~~~~~~~~~~~~~~~~~~~
-The COMPLETION Ring is used transfer ownership of UMEM frames from
+The COMPLETION Ring is used to transfer ownership of UMEM frames from
kernel-space to user-space. Just like the FILL ring, UMEM indices are
-used.
-
-Frames passed from the kernel to user-space are frames that has been
-sent (TX ring) and can be used by user-space again.
-
-The user application consumes UMEM addrs from this ring.
+used. Frames passed from the kernel to user-space are frames referenced
+by Tx descriptors that the kernel has finished processing and can be
+used by user-space again. This includes both frames whose transmission
+has completed and frames referenced by invalid Tx descriptors that were
+rejected and reclaimed by the kernel. A completion entry does not
+guarantee successful packet transmission. The user application consumes
+UMEM addrs from this ring.
RX Ring
@@ -504,21 +506,25 @@ will be treated as an invalid descriptor.
These are the semantics for producing packets onto AF_XDP Tx ring
consisting of multiple frames:
-* When an invalid descriptor is found, all the other
- descriptors/frames of this packet are marked as invalid and not
- completed. The next descriptor is treated as the start of a new
- packet, even if this was not the intent (because we cannot guess
- the intent). As before, if your program is producing invalid
- descriptors you have a bug that must be fixed.
+* When an invalid descriptor is found, the complete packet is treated as
+ invalid. The kernel consumes descriptors through the descriptor marking
+ the end of the packet and returns all their frame addresses through the
+ COMPLETION ring. A standalone invalid descriptor is treated as a
+ one-descriptor invalid packet. The descriptor following the end of the
+ invalid packet is treated as the start of a new packet. As before, if
+ your program is producing invalid descriptors you have a bug that must
+ be fixed. Rejected descriptors are reported in the ``tx_invalid_descs``
+ statistic.
* Zero length descriptors are treated as invalid descriptors.
* For copy mode, the maximum supported number of frames in a packet is
- equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all
- descriptors accumulated so far are dropped and treated as
- invalid. To produce an application that will work on any system
- regardless of this config setting, limit the number of frags to 18,
- as the minimum value of the config is 17.
+ equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all descriptors
+ through the end of the oversized packet are consumed, treated as invalid,
+ and their frame addresses are returned through the COMPLETION ring. To
+ produce an application that will work on any system regardless of this
+ config setting, limit the number of frags to 18, as the minimum value of
+ the config is 17.
* For zero-copy mode, the limit is up to what the NIC HW
supports. Usually at least five on the NICs we have checked. We
diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h
index ebac60a3d8a17b..8b51876efbed1a 100644
--- a/include/net/xdp_sock.h
+++ b/include/net/xdp_sock.h
@@ -80,6 +80,7 @@ struct xdp_sock {
* call of __xsk_generic_xmit().
*/
struct sk_buff *skb;
+ bool drain_cont;
struct list_head map_list;
/* Protects map_list */
diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h
index ccb3b350001f24..2bb1d122b1bc6b 100644
--- a/include/net/xsk_buff_pool.h
+++ b/include/net/xsk_buff_pool.h
@@ -78,6 +78,9 @@ struct xsk_buff_pool {
u32 chunk_size;
u32 chunk_shift;
u32 frame_len;
+ u32 tx_descs_nentries;
+ u32 reclaim_descs;
+ u32 tx_zc_pending_descs;
u32 xdp_zc_max_segs;
u8 tx_metadata_len; /* inherited from umem */
u8 cached_need_wakeup;
@@ -102,12 +105,14 @@ struct xsk_buff_pool {
/* AF_XDP core. */
struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
- struct xdp_umem *umem);
+ struct xdp_umem *umem,
+ u32 max_segs);
int xp_assign_dev(struct xsk_buff_pool *pool, struct net_device *dev,
u16 queue_id, u16 flags);
int xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs,
struct net_device *dev, u16 queue_id);
-int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs);
+int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs,
+ u32 max_segs);
void xp_destroy(struct xsk_buff_pool *pool);
void xp_get_pool(struct xsk_buff_pool *pool);
bool xp_put_pool(struct xsk_buff_pool *pool);
diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
index b970f30ea9b9d8..f906d51b669904 100644
--- a/net/xdp/xsk.c
+++ b/net/xdp/xsk.c
@@ -499,6 +499,23 @@ void __xsk_map_flush(struct list_head *flush_list)
void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries)
{
+ u32 reclaim_descs = READ_ONCE(pool->reclaim_descs);
+
+ if (unlikely(reclaim_descs)) {
+ u32 pending_descs = READ_ONCE(pool->tx_zc_pending_descs);
+
+ if (nb_entries < pending_descs) {
+ WRITE_ONCE(pool->tx_zc_pending_descs,
+ pending_descs - nb_entries);
+ xskq_prod_submit_n(pool->cq, nb_entries);
+ return;
+ }
+
+ WRITE_ONCE(pool->tx_zc_pending_descs, 0);
+ nb_entries += reclaim_descs;
+ WRITE_ONCE(pool->reclaim_descs, 0);
+ }
+
xskq_prod_submit_n(pool->cq, nb_entries);
}
EXPORT_SYMBOL(xsk_tx_completed);
@@ -574,24 +591,157 @@ static u32 xsk_tx_peek_release_fallback(struct xsk_buff_pool *pool, u32 max_entr
return nb_pkts;
}
+static void xsk_tx_commit_batch(struct xsk_buff_pool *pool,
+ struct xsk_tx_batch *batch)
+{
+ u32 nb_descs = xsk_tx_batch_cq_descs(batch);
+ u32 cq_cached_prod;
+
+ if (!nb_descs)
+ return;
+
+ cq_cached_prod = pool->cq->cached_prod;
+ xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_descs);
+
+ if (unlikely(batch->reclaim_descs)) {
+ u32 cq_pending_descs;
+
+ /* CQ is positional. Descriptors already written but not
+ * submitted must complete before any reclaim-only descriptors
+ * appended below.
+ */
+ cq_pending_descs = cq_cached_prod - xskq_get_prod(pool->cq);
+
+ WRITE_ONCE(pool->tx_zc_pending_descs,
+ batch->tx_descs + cq_pending_descs);
+ WRITE_ONCE(pool->reclaim_descs, batch->reclaim_descs);
+ if (unlikely(!pool->tx_zc_pending_descs))
+ xsk_tx_completed(pool, 0);
+ }
+}
+
+static struct xsk_tx_batch
+__xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, struct xdp_sock *xs,
+ struct xdp_desc *descs, u32 max_descs)
+{
+ struct xsk_tx_batch batch = {};
+ u32 entries;
+
+ entries = xskq_cons_nb_entries(xs->tx, max_descs);
+ if (!entries)
+ return batch;
+
+ batch = xskq_cons_read_desc_batch(xs, pool, descs, max_descs);
+ if (!xsk_tx_batch_cq_descs(&batch)) {
+ xs->tx->queue_empty_descs++;
+ } else {
+ __xskq_cons_release(xs->tx);
+ xs->sk.sk_write_space(&xs->sk);
+ }
+ return batch;
+}
+
+static struct xsk_tx_batch
+xsk_tx_peek_release_shared_desc_batch(struct xsk_buff_pool *pool, u32 max_descs)
+{
+ u32 cq_descs_before, cq_descs_after;
+ struct xsk_tx_batch sum_batch = {};
+ bool budget_exhausted;
+ u32 per_socket_budget;
+ struct xdp_sock *xs;
+
+ /* The fairness quota must allow one maximum-sized valid packet. */
+ per_socket_budget = max_t(u32, MAX_PER_SOCKET_BUDGET,
+ pool->xdp_zc_max_segs);
+
+again:
+ budget_exhausted = false;
+ cq_descs_before = xsk_tx_batch_cq_descs(&sum_batch);
+ list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list) {
+ u32 budget, budget_left, offset, remaining, used;
+ struct xsk_tx_batch curr_batch;
+
+ /* Once reclaim-only descriptors have been appended to the CQ
+ * address area, do not append driver-visible Tx descriptors
+ * from another socket after them. xsk_tx_completed() relies on
+ * all driver-visible descriptors preceding all reclaim-only
+ * descriptors in CQ order.
+ */
+ if (sum_batch.reclaim_descs)
+ break;
+
+ /* be gentle when playing with pool->tx_descs */
+ offset = xsk_tx_batch_cq_descs(&sum_batch);
+ if (offset >= max_descs)
+ break;
+
+ if (xs->tx_budget_spent >= per_socket_budget) {
+ if (xskq_cons_nb_entries(xs->tx, 1))
+ budget_exhausted = true;
+ continue;
+ }
+
+ budget_left = per_socket_budget - xs->tx_budget_spent;
+ remaining = max_descs - offset;
+ budget = min(remaining, budget_left);
+
+ curr_batch = __xsk_tx_peek_release_desc_batch(pool, xs,
+ pool->tx_descs + offset,
+ budget);
+ used = xsk_tx_batch_cq_descs(&curr_batch);
+ if (!used) {
+ if (curr_batch.budget_limited && budget_left < remaining)
+ budget_exhausted = true;
+ continue;
+ }
+
+ xs->tx_budget_spent += used;
+ sum_batch.tx_descs += curr_batch.tx_descs;
+ sum_batch.reclaim_descs = curr_batch.reclaim_descs;
+ }
+
+ cq_descs_after = xsk_tx_batch_cq_descs(&sum_batch);
+
+ if (sum_batch.reclaim_descs || cq_descs_after >= max_descs)
+ return sum_batch;
+
+ /* Continue filling the batch while this pass made progress */
+ if (cq_descs_before != cq_descs_after)
+ goto again;
+
+ if (!budget_exhausted)
+ return sum_batch;
+
+ list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list)
+ xs->tx_budget_spent = 0;
+ goto again;
+}
+
u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts)
{
+ struct xsk_tx_batch batch = {};
struct xdp_sock *xs;
+ bool umem_shared;
rcu_read_lock();
- if (!list_is_singular(&pool->xsk_tx_list)) {
- /* Fallback to the non-batched version */
- rcu_read_unlock();
- return xsk_tx_peek_release_fallback(pool, nb_pkts);
- }
+ if (unlikely(READ_ONCE(pool->reclaim_descs)))
+ goto out;
- xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock, tx_list);
- if (!xs) {
- nb_pkts = 0;
+ xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock,
+ tx_list);
+ if (!xs)
goto out;
- }
- nb_pkts = xskq_cons_nb_entries(xs->tx, nb_pkts);
+ nb_pkts = min(nb_pkts, pool->tx_descs_nentries);
+ if (!nb_pkts)
+ goto out;
+
+ umem_shared = !list_is_singular(&pool->xsk_tx_list);
+
+ if (umem_shared && !(pool->umem->flags & XDP_UMEM_SG_FLAG)) {
+ rcu_read_unlock();
+ return xsk_tx_peek_release_fallback(pool, nb_pkts);
+ }
/* This is the backpressure mechanism for the Tx path. Try to
* reserve space in the completion queue for all packets, but
@@ -603,19 +753,16 @@ u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts)
if (!nb_pkts)
goto out;
- nb_pkts = xskq_cons_read_desc_batch(xs->tx, pool, nb_pkts);
- if (!nb_pkts) {
- xs->tx->queue_empty_descs++;
- goto out;
- }
-
- __xskq_cons_release(xs->tx);
- xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_pkts);
- xs->sk.sk_write_space(&xs->sk);
+ batch = umem_shared ?
+ xsk_tx_peek_release_shared_desc_batch(pool, nb_pkts) :
+ __xsk_tx_peek_release_desc_batch(pool, xs,
+ pool->tx_descs,
+ nb_pkts);
+ xsk_tx_commit_batch(pool, &batch);
out:
rcu_read_unlock();
- return nb_pkts;
+ return batch.tx_descs;
}
EXPORT_SYMBOL(xsk_tx_peek_release_desc_batch);
@@ -737,6 +884,19 @@ static void xsk_cq_submit_addr_locked(struct xsk_buff_pool *pool,
spin_unlock_irqrestore(&pool->cq_prod_lock, flags);
}
+static void xsk_cq_submit_addr_single_locked(struct xsk_buff_pool *pool,
+ struct xdp_desc *desc)
+{
+ unsigned long flags;
+ u32 idx;
+
+ spin_lock_irqsave(&pool->cq_prod_lock, flags);
+ idx = xskq_get_prod(pool->cq);
+ xskq_prod_write_addr(pool->cq, idx, desc->addr);
+ xskq_prod_submit_n(pool->cq, 1);
+ spin_unlock_irqrestore(&pool->cq_prod_lock, flags);
+}
+
static void xsk_cq_cancel_locked(struct xsk_buff_pool *pool, u32 n)
{
spin_lock(&pool->cq->cq_cached_prod_lock);
@@ -794,8 +954,11 @@ static void xsk_consume_skb(struct sk_buff *skb)
static void xsk_drop_skb(struct sk_buff *skb)
{
- xdp_sk(skb->sk)->tx->invalid_descs += xsk_get_num_desc(skb);
- xsk_consume_skb(skb);
+ struct xdp_sock *xs = xdp_sk(skb->sk);
+
+ xs->tx->invalid_descs += xsk_get_num_desc(skb);
+ consume_skb(skb);
+ xs->skb = NULL;
}
static int xsk_skb_metadata(struct sk_buff *skb, void *buffer,
@@ -877,7 +1040,7 @@ static struct sk_buff *xsk_build_skb_zerocopy(struct xdp_sock *xs,
return ERR_PTR(-ENOMEM);
/* in case of -EOVERFLOW that could happen below,
- * xsk_consume_skb() will release this node as whole skb
+ * xsk_drop_skb() will release this node as whole skb
* would be dropped, which implies freeing all list elements
*/
xsk_addr->addrs[xsk_addr->num_descs] = desc->addr;
@@ -969,6 +1132,8 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,
goto free_err;
}
+ xsk_addr->addrs[xsk_addr->num_descs] = desc->addr;
+
if (unlikely(nr_frags == (MAX_SKB_FRAGS - 1) && xp_mb_desc(desc))) {
err = -EOVERFLOW;
goto free_err;
@@ -986,8 +1151,6 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,
skb_add_rx_frag(skb, nr_frags, page, 0, len, PAGE_SIZE);
refcount_add(PAGE_SIZE, &xs->sk.sk_wmem_alloc);
-
- xsk_addr->addrs[xsk_addr->num_descs] = desc->addr;
}
}
@@ -1025,13 +1188,14 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,
static int __xsk_generic_xmit(struct sock *sk)
{
struct xdp_sock *xs = xdp_sk(sk);
- bool sent_frame = false;
struct xdp_desc desc;
struct sk_buff *skb;
+ u32 cached_cons;
u32 max_batch;
int err = 0;
mutex_lock(&xs->mutex);
+ cached_cons = xs->tx->cached_cons;
/* Since we dropped the RCU read lock, the socket state might have changed. */
if (unlikely(!xsk_is_bound(xs))) {
@@ -1060,11 +1224,21 @@ static int __xsk_generic_xmit(struct sock *sk)
goto out;
}
+ if (unlikely(xs->drain_cont)) {
+ xsk_cq_submit_addr_single_locked(xs->pool, &desc);
+ xs->tx->invalid_descs++;
+ xskq_cons_release(xs->tx);
+ xs->drain_cont = xp_mb_desc(&desc);
+ continue;
+ }
+
skb = xsk_build_skb(xs, &desc);
if (IS_ERR(skb)) {
err = PTR_ERR(skb);
if (err != -EOVERFLOW)
goto out;
+ if (xp_mb_desc(&desc))
+ xs->drain_cont = true;
err = 0;
continue;
}
@@ -1093,18 +1267,33 @@ static int __xsk_generic_xmit(struct sock *sk)
goto out;
}
- sent_frame = true;
xs->skb = NULL;
}
if (xskq_has_descs(xs->tx)) {
+ bool drain = xs->skb || xs->drain_cont || xp_mb_desc(&desc);
+
+ err = xsk_cq_reserve_locked(xs->pool);
+ if (err) {
+ xs->tx->invalid_descs--;
+ if (xs->skb)
+ xsk_drop_skb(xs->skb);
+ xs->drain_cont = drain;
+ err = -EAGAIN;
+ goto out;
+ }
+
if (xs->skb)
xsk_drop_skb(xs->skb);
+
+ xsk_cq_submit_addr_single_locked(xs->pool, &desc);
+
xskq_cons_release(xs->tx);
+ xs->drain_cont = xp_mb_desc(&desc);
}
out:
- if (sent_frame)
+ if (xs->tx->cached_cons != cached_cons)
__xsk_tx_release(xs);
mutex_unlock(&xs->mutex);
@@ -1483,7 +1672,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr
* and/or device.
*/
xs->pool = xp_create_and_assign_umem(xs,
- umem_xs->umem);
+ umem_xs->umem,
+ dev->xdp_zc_max_segs);
if (!xs->pool) {
err = -ENOMEM;
sockfd_put(sock);
@@ -1515,7 +1705,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr
* utilizes
*/
if (xs->tx && !xs->pool->tx_descs) {
- err = xp_alloc_tx_descs(xs->pool, xs);
+ err = xp_alloc_tx_descs(xs->pool, xs,
+ dev->xdp_zc_max_segs);
if (err) {
xp_put_pool(xs->pool);
xs->pool = NULL;
@@ -1533,7 +1724,9 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr
goto out_unlock;
} else {
/* This xsk has its own umem. */
- xs->pool = xp_create_and_assign_umem(xs, xs->umem);
+ xs->pool = xp_create_and_assign_umem(xs, xs->umem,
+ dev->xdp_zc_max_segs);
+
if (!xs->pool) {
err = -ENOMEM;
goto out_unlock;
diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
index 1f28a9641571e4..a4089480b22b25 100644
--- a/net/xdp/xsk_buff_pool.c
+++ b/net/xdp/xsk_buff_pool.c
@@ -42,17 +42,22 @@ void xp_destroy(struct xsk_buff_pool *pool)
kvfree(pool);
}
-int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs)
+int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs,
+ u32 max_segs)
{
- pool->tx_descs = kvzalloc_objs(*pool->tx_descs, xs->tx->nentries);
+ u32 nentries = max(xs->tx->nentries, max_segs);
+
+ pool->tx_descs = kvzalloc_objs(*pool->tx_descs, nentries);
if (!pool->tx_descs)
return -ENOMEM;
+ pool->tx_descs_nentries = nentries;
return 0;
}
struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
- struct xdp_umem *umem)
+ struct xdp_umem *umem,
+ u32 max_segs)
{
bool unaligned = umem->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG;
struct xsk_buff_pool *pool;
@@ -69,7 +74,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
goto out;
if (xs->tx)
- if (xp_alloc_tx_descs(pool, xs))
+ if (xp_alloc_tx_descs(pool, xs, max_segs))
goto out;
pool->chunk_mask = ~((u64)umem->chunk_size - 1);
diff --git a/net/xdp/xsk_queue.h b/net/xdp/xsk_queue.h
index 3e3fbb73d23e2f..1bc42c8902f4ba 100644
--- a/net/xdp/xsk_queue.h
+++ b/net/xdp/xsk_queue.h
@@ -58,6 +58,17 @@ struct parsed_desc {
u32 valid;
};
+struct xsk_tx_batch {
+ u32 tx_descs;
+ u32 reclaim_descs;
+ bool budget_limited;
+};
+
+static inline u32 xsk_tx_batch_cq_descs(const struct xsk_tx_batch *batch)
+{
+ return batch->tx_descs + batch->reclaim_descs;
+}
+
/* The structure of the shared state of the rings are a simple
* circular buffer, as outlined in
* Documentation/core-api/circular-buffers.rst. For the Rx and
@@ -263,17 +274,18 @@ static inline void parse_desc(struct xsk_queue *q, struct xsk_buff_pool *pool,
parsed->mb = xp_mb_desc(desc);
}
-static inline
-u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool,
- u32 max)
+static inline struct xsk_tx_batch
+xskq_cons_read_desc_batch(struct xdp_sock *xs, struct xsk_buff_pool *pool,
+ struct xdp_desc *descs, u32 max)
{
- u32 cached_cons = q->cached_cons, nb_entries = 0;
- struct xdp_desc *descs = pool->tx_descs;
- u32 total_descs = 0, nr_frags = 0;
+ bool drain = READ_ONCE(xs->drain_cont);
+ u32 cached_cons, nb_entries = 0;
+ struct xsk_tx_batch batch = {};
+ struct xsk_queue *q = xs->tx;
+ u32 nr_frags = 0;
+
+ cached_cons = q->cached_cons;
- /* track first entry, if stumble upon *any* invalid descriptor, rewind
- * current packet that consists of frags and stop the processing
- */
while (cached_cons != q->cached_prod && nb_entries < max) {
struct xdp_rxtx_ring *ring = (struct xdp_rxtx_ring *)q->ring;
u32 idx = cached_cons & q->ring_mask;
@@ -283,25 +295,42 @@ u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool,
cached_cons++;
parse_desc(q, pool, &descs[nb_entries], &parsed);
if (unlikely(!parsed.valid))
- break;
+ drain = true;
+
+ nr_frags++;
+ nb_entries++;
if (likely(!parsed.mb)) {
- total_descs += (nr_frags + 1);
- nr_frags = 0;
- } else {
- nr_frags++;
- if (nr_frags == pool->xdp_zc_max_segs) {
+ if (unlikely(drain)) {
+ batch.reclaim_descs = nr_frags;
+ WRITE_ONCE(xs->drain_cont, false);
nr_frags = 0;
break;
}
+
+ batch.tx_descs += nr_frags;
+ nr_frags = 0;
+ continue;
+ }
+
+ if (nr_frags == pool->xdp_zc_max_segs)
+ drain = true;
+ }
+
+ if (nr_frags) {
+ if (drain) {
+ batch.reclaim_descs = nr_frags;
+ WRITE_ONCE(xs->drain_cont, true);
+ } else {
+ if (nb_entries == max)
+ batch.budget_limited = true;
+ cached_cons -= nr_frags;
}
- nb_entries++;
}
- cached_cons -= nr_frags;
/* Release valid plus any invalid entries */
xskq_cons_release_n(q, cached_cons - q->cached_cons);
- return total_descs;
+ return batch;
}
/* Functions for consumers */
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 477aedbb01bad8..4549358cc8c21d 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -427,14 +427,14 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk
}
/* Search for the end of the packet in verbatim mode */
- if (!pkt_continues(pkt->options) || !pkt->valid)
+ if (!pkt_continues(pkt->options))
return nb_frags;
next_frag = pkt_stream->current_pkt_nb;
pkt++;
while (next_frag++ < pkt_stream->nb_pkts) {
nb_frags++;
- if (!pkt_continues(pkt->options) || !pkt->valid)
+ if (!pkt_continues(pkt->options))
break;
pkt++;
}
@@ -665,11 +665,11 @@ static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, s
if (!frame->valid || !pkt_continues(frame->options))
payload++;
} else {
- if (frame->valid)
+ if (frame->valid) {
len += frame->len;
- if (frame->valid && pkt_continues(frame->options))
- continue;
-
+ if (pkt_continues(frame->options))
+ continue;
+ }
pkt->pkt_nb = pkt_nb;
pkt->len = len;
pkt->valid = frame->valid;
@@ -1250,10 +1250,9 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
}
}
- if (pkt && pkt->valid) {
+ if (pkt && pkt->valid)
valid_pkts++;
- valid_frags += nb_frags;
- }
+ valid_frags += nb_frags;
}
pthread_mutex_lock(&pacing_mutex);
@@ -2099,13 +2098,16 @@ int testapp_invalid_desc_mb(struct test_spec *test)
{0, 0, 0, false, 0},
/* Invalid address in the second frame */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
- {umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {umem_sz * 2, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Invalid len in the middle */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
{0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Invalid options in the middle */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Transmit 2 frags, receive 3 */
{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD},
{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0},
@@ -2117,8 +2119,8 @@ int testapp_invalid_desc_mb(struct test_spec *test)
if (umem->unaligned_mode) {
/* Crossing a chunk boundary allowed */
- pkts[12].valid = true;
- pkts[13].valid = true;
+ pkts[15].valid = true;
+ pkts[16].valid = true;
}
test->mtu = MAX_ETH_JUMBO_SIZE;
@@ -2270,7 +2272,7 @@ int testapp_too_many_frags(struct test_spec *test)
max_frags += 1;
}
- pkts = calloc(2 * max_frags + 2, sizeof(struct pkt));
+ pkts = calloc(2 * max_frags + 3, sizeof(struct pkt));
if (!pkts)
return TEST_FAILURE;
@@ -2288,24 +2290,30 @@ int testapp_too_many_frags(struct test_spec *test)
}
pkts[max_frags].options = 0;
- /* An invalid packet with the max amount of frags but signals packet
- * continues on the last frag
- */
- for (i = max_frags + 1; i < 2 * max_frags + 1; i++) {
+ /* An invalid packet with the max + 1 amount of frags */
+ for (i = max_frags + 1; i < 2 * max_frags + 2; i++) {
pkts[i].len = MIN_PKT_SIZE;
pkts[i].options = XDP_PKT_CONTD;
- pkts[i].valid = false;
+ pkts[i].valid = true;
}
+ pkts[2 * max_frags + 1].options = 0;
/* Valid packet for synch */
- pkts[2 * max_frags + 1].len = MIN_PKT_SIZE;
- pkts[2 * max_frags + 1].valid = true;
+ pkts[2 * max_frags + 2].len = MIN_PKT_SIZE;
+ pkts[2 * max_frags + 2].valid = true;
- if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2)) {
+ if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 3)) {
free(pkts);
return TEST_FAILURE;
}
+ /* The generated Tx stream must keep the too-big packet valid so that
+ * __send_pkts() accounts its descriptors in outstanding_tx. The Rx
+ * stream, however, must not expect this packet on the wire.
+ */
+ test->ifobj_rx->xsk->pkt_stream->pkts[2].valid = false;
+ test->ifobj_rx->xsk->pkt_stream->nb_valid_entries--;
+
ret = testapp_validate_traffic(test);
free(pkts);
return ret;
|
| 3/2 | 2026/07/19 14:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12228 output: 19 thoughts: 317 Thoughts: **My Assessment of the AF_XDP Patch** Okay, so I'm looking at this patch for AF_XDP in the Linux kernel. Seems like a fairly involved modification, touching several key files: `Documentation/networking/af_xdp.rst`, various header files (`xdp_sock.h`, `xsk_buff_pool.h`, `xsk_queue.h`), and the core implementation files (`xsk.c`, `xsk_buff_pool.c`). It even includes a modification to a selftest (`test_xsk.c`), which is a good sign. This isn't just a trivial tweak; it's a functional change, focusing on how AF_XDP handles potentially invalid descriptors, the behavior of the completion ring, and batching within `xsk_tx_peek_release_desc_batch`. The key functions being updated here are `xsk_tx_peek_release_desc_batch`, `__xsk_generic_xmit`, `xsk_tx_completed`, and the SKB-building functions, `xsk_build_skb`, and `xsk_build_skb_zerocopy`. This all suggests that there might be some important performance implications or perhaps a fix for some identified edge cases. I'll have to investigate the specific code changes to understand the rationale behind the patch in depth, but my initial thought is, this is dealing with details of descriptor management. The next thing I want to consider is the applicability of this change in virtualized environments. I'm glad they are thinking about this. AF_XDP has proven useful in virtualized environments, but this patch could potentially impact how well it works. I'm glad to see that AF_XDP is supported with generic XDP (SKB mode) and veth/virtio-net. I'll need to double-check the necessary configuration options, like `CONFIG_XDP_SOCKETS`, to ensure this patch doesn't introduce any new dependencies or configuration requirements. I'll search for `XDP_SOCKETS` and `AF_XDP` within the files to be thorough. |
| 4/2 | 2026/07/19 14:21 | tool | grepper |
0mArgs:{
"Expression": "XDP_SOCKETS"
}Results: {
"Output": "arch/loongarch/configs/loongson32_defconfig=115=CONFIG_NET_KEY=y\narch/loongarch/configs/loongson32_defconfig:116:CONFIG_XDP_SOCKETS=y\narch/loongarch/configs/loongson32_defconfig-117-CONFIG_INET=y\n--\narch/loongarch/configs/loongson64_defconfig=131=CONFIG_NET_KEY=y\narch/loongarch/configs/loongson64_defconfig:132:CONFIG_XDP_SOCKETS=y\narch/loongarch/configs/loongson64_defconfig-133-CONFIG_INET=y\n--\narch/m68k/configs/amiga_defconfig=55=CONFIG_NET_KEY=y\narch/m68k/configs/amiga_defconfig:56:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/amiga_defconfig:57:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/amiga_defconfig-58-CONFIG_INET=y\n--\narch/m68k/configs/apollo_defconfig=51=CONFIG_NET_KEY=y\narch/m68k/configs/apollo_defconfig:52:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/apollo_defconfig:53:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/apollo_defconfig-54-CONFIG_INET=y\n--\narch/m68k/configs/atari_defconfig=58=CONFIG_NET_KEY=y\narch/m68k/configs/atari_defconfig:59:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/atari_defconfig:60:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/atari_defconfig-61-CONFIG_INET=y\n--\narch/m68k/configs/bvme6000_defconfig=48=CONFIG_NET_KEY=y\narch/m68k/configs/bvme6000_defconfig:49:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/bvme6000_defconfig:50:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/bvme6000_defconfig-51-CONFIG_INET=y\n--\narch/m68k/configs/hp300_defconfig=50=CONFIG_NET_KEY=y\narch/m68k/configs/hp300_defconfig:51:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/hp300_defconfig:52:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/hp300_defconfig-53-CONFIG_INET=y\n--\narch/m68k/configs/mac_defconfig=49=CONFIG_NET_KEY=y\narch/m68k/configs/mac_defconfig:50:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/mac_defconfig:51:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/mac_defconfig-52-CONFIG_INET=y\n--\narch/m68k/configs/multi_defconfig=69=CONFIG_NET_KEY=y\narch/m68k/configs/multi_defconfig:70:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/multi_defconfig:71:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/multi_defconfig-72-CONFIG_INET=y\n--\narch/m68k/configs/mvme147_defconfig=47=CONFIG_NET_KEY=y\narch/m68k/configs/mvme147_defconfig:48:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/mvme147_defconfig:49:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/mvme147_defconfig-50-CONFIG_INET=y\n--\narch/m68k/configs/mvme16x_defconfig=48=CONFIG_NET_KEY=y\narch/m68k/configs/mvme16x_defconfig:49:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/mvme16x_defconfig:50:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/mvme16x_defconfig-51-CONFIG_INET=y\n--\narch/m68k/configs/q40_defconfig=49=CONFIG_NET_KEY=y\narch/m68k/configs/q40_defconfig:50:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/q40_defconfig:51:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/q40_defconfig-52-CONFIG_INET=y\n--\narch/m68k/configs/sun3_defconfig=44=CONFIG_NET_KEY=y\narch/m68k/configs/sun3_defconfig:45:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/sun3_defconfig:46:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/sun3_defconfig-47-CONFIG_INET=y\n--\narch/m68k/configs/sun3x_defconfig=45=CONFIG_NET_KEY=y\narch/m68k/configs/sun3x_defconfig:46:CONFIG_XDP_SOCKETS=y\narch/m68k/configs/sun3x_defconfig:47:CONFIG_XDP_SOCKETS_DIAG=m\narch/m68k/configs/sun3x_defconfig-48-CONFIG_INET=y\n--\narch/s390/configs/debug_defconfig=133=CONFIG_DIBS_LO=y\narch/s390/configs/debug_defconfig:134:CONFIG_XDP_SOCKETS=y\narch/s390/configs/debug_defconfig:135:CONFIG_XDP_SOCKETS_DIAG=m\narch/s390/configs/debug_defconfig-136-CONFIG_INET=y\n--\narch/s390/configs/defconfig=124=CONFIG_DIBS_LO=y\narch/s390/configs/defconfig:125:CONFIG_XDP_SOCKETS=y\narch/s390/configs/defconfig:126:CONFIG_XDP_SOCKETS_DIAG=m\narch/s390/configs/defconfig-127-CONFIG_INET=y\n--\ndrivers/net/ethernet/intel/ice/Makefile=59=ice-$(CONFIG_RFS_ACCEL) += ice_arfs.o\ndrivers/net/ethernet/intel/ice/Makefile:60:ice-$(CONFIG_XDP_SOCKETS) += ice_xsk.o\ndrivers/net/ethernet/intel/ice/Makefile-61-ice-$(CONFIG_ICE_SWITCHDEV) += ice_eswitch.o ice_eswitch_br.o\n--\ndrivers/net/ethernet/intel/ice/ice_xsk.h=10=struct ice_vsi;\ndrivers/net/ethernet/intel/ice/ice_xsk.h-11-\ndrivers/net/ethernet/intel/ice/ice_xsk.h:12:#ifdef CONFIG_XDP_SOCKETS\ndrivers/net/ethernet/intel/ice/ice_xsk.h-13-int ice_xsk_pool_setup(struct ice_vsi *vsi, struct xsk_buff_pool *pool,\n--\ndrivers/net/ethernet/intel/ice/ice_xsk.h=98=ice_qvec_dis_irq(struct ice_vsi *vsi, struct ice_rx_ring *rx_ring,\ndrivers/net/ethernet/intel/ice/ice_xsk.h-99-\t\t struct ice_q_vector *q_vector) { }\ndrivers/net/ethernet/intel/ice/ice_xsk.h:100:#endif /* CONFIG_XDP_SOCKETS */\ndrivers/net/ethernet/intel/ice/ice_xsk.h-101-#endif /* !_ICE_XSK_H_ */\n--\ninclude/linux/bpf_types.h=117=BPF_MAP_TYPE(BPF_MAP_TYPE_CPUMAP, cpu_map_ops)\ninclude/linux/bpf_types.h:118:#if defined(CONFIG_XDP_SOCKETS)\ninclude/linux/bpf_types.h-119-BPF_MAP_TYPE(BPF_MAP_TYPE_XSKMAP, xsk_map_ops)\n--\ninclude/linux/filter.h=886=static inline void bpf_net_ctx_get_all_used_flush_lists(struct list_head **lh_map,\n--\ninclude/linux/filter.h-907-\tlh = \u0026bpf_net_ctx-\u003exskmap_map_flush_list;\ninclude/linux/filter.h:908:\tif (IS_ENABLED(CONFIG_XDP_SOCKETS) \u0026\u0026\ninclude/linux/filter.h-909-\t kern_flags \u0026 BPF_RI_F_XSK_MAP_INIT \u0026\u0026 !list_empty(lh))\n--\ninclude/linux/netdevice.h=676=struct netdev_queue {\n--\ninclude/linux/netdevice.h-697-\tstruct net_device\t*sb_dev;\ninclude/linux/netdevice.h:698:#ifdef CONFIG_XDP_SOCKETS\ninclude/linux/netdevice.h-699-\t/* \"ops protected\", see comment about net_device::lock */\n--\ninclude/net/libeth/xsk.h-9-\ninclude/net/libeth/xsk.h:10:/* ``XDP_TXMD_FLAGS_VALID`` is defined only under ``CONFIG_XDP_SOCKETS`` */\ninclude/net/libeth/xsk.h-11-#ifdef XDP_TXMD_FLAGS_VALID\n--\ninclude/net/net_namespace.h=62=struct net {\n--\ninclude/net/net_namespace.h-183-#endif\ninclude/net/net_namespace.h:184:#ifdef CONFIG_XDP_SOCKETS\ninclude/net/net_namespace.h-185-\tstruct netns_xdp\txdp;\n--\ninclude/net/netdev_rx_queue.h=14=struct netdev_rx_queue {\n--\ninclude/net/netdev_rx_queue.h-27-\t */\ninclude/net/netdev_rx_queue.h:28:#ifdef CONFIG_XDP_SOCKETS\ninclude/net/netdev_rx_queue.h-29-\tstruct xsk_buff_pool *pool;\n--\ninclude/net/xdp_sock.h=117=struct xsk_tx_metadata_ops {\n--\ninclude/net/xdp_sock.h-123-\ninclude/net/xdp_sock.h:124:#ifdef CONFIG_XDP_SOCKETS\ninclude/net/xdp_sock.h-125-\n--\ninclude/net/xdp_sock.h=240=static inline void xsk_tx_metadata_complete(struct xsk_tx_metadata_compl *compl,\n--\ninclude/net/xdp_sock.h-245-\ninclude/net/xdp_sock.h:246:#endif /* CONFIG_XDP_SOCKETS */\ninclude/net/xdp_sock.h-247-#endif /* _LINUX_XDP_SOCK_H */\n--\ninclude/net/xdp_sock_drv.h=19=struct xsk_cb_desc {\n--\ninclude/net/xdp_sock_drv.h-24-\ninclude/net/xdp_sock_drv.h:25:#ifdef CONFIG_XDP_SOCKETS\ninclude/net/xdp_sock_drv.h-26-\n--\ninclude/net/xdp_sock_drv.h=493=static inline void xsk_buff_raw_dma_sync_for_device(struct xsk_buff_pool *pool,\n--\ninclude/net/xdp_sock_drv.h-498-\ninclude/net/xdp_sock_drv.h:499:#endif /* CONFIG_XDP_SOCKETS */\ninclude/net/xdp_sock_drv.h-500-\n--\nnet/Makefile=72=obj-$(CONFIG_NET_NCSI)\t\t+= ncsi/\nnet/Makefile:73:obj-$(CONFIG_XDP_SOCKETS)\t+= xdp/\nnet/Makefile-74-obj-$(CONFIG_MPTCP)\t\t+= mptcp/\n--\nnet/core/netdev-genl.c=441=__netdev_nl_queue_fill_mp(struct sk_buff *rsp, struct netdev_rx_queue *rxq)\n--\nnet/core/netdev-genl.c-448-\nnet/core/netdev-genl.c:449:#ifdef CONFIG_XDP_SOCKETS\nnet/core/netdev-genl.c-450-\tif (rxq-\u003epool)\n--\nnet/core/netdev-genl.c=475=netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev,\n--\nnet/core/netdev-genl.c-504-\t\t\tgoto nla_put_failure;\nnet/core/netdev-genl.c:505:#ifdef CONFIG_XDP_SOCKETS\nnet/core/netdev-genl.c-506-\t\tif (txq-\u003epool)\n--\nnet/core/netdev_rx_queue.c=187=static int __netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,\n--\nnet/core/netdev_rx_queue.c-220-\t}\nnet/core/netdev_rx_queue.c:221:#ifdef CONFIG_XDP_SOCKETS\nnet/core/netdev_rx_queue.c-222-\tif (rxq-\u003epool) {\n--\nnet/xdp/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nnet/xdp/Kconfig:2:config XDP_SOCKETS\nnet/xdp/Kconfig-3-\tbool \"XDP sockets\"\n--\nnet/xdp/Kconfig-9-\nnet/xdp/Kconfig:10:config XDP_SOCKETS_DIAG\nnet/xdp/Kconfig-11-\ttristate \"XDP sockets: monitoring interface\"\nnet/xdp/Kconfig:12:\tdepends on XDP_SOCKETS\nnet/xdp/Kconfig-13-\tdefault n\n--\nnet/xdp/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\nnet/xdp/Makefile:2:obj-$(CONFIG_XDP_SOCKETS) += xsk.o xdp_umem.o xsk_queue.o xskmap.o\nnet/xdp/Makefile:3:obj-$(CONFIG_XDP_SOCKETS) += xsk_buff_pool.o\nnet/xdp/Makefile:4:obj-$(CONFIG_XDP_SOCKETS_DIAG) += xsk_diag.o\n--\ntools/bpf/bpftool/feature.c=328=static void probe_kernel_image_config(const char *define_prefix)\n--\ntools/bpf/bpftool/feature.c-372-\t\t/* AF_XDP sockets */\ntools/bpf/bpftool/feature.c:373:\t\t{ \"CONFIG_XDP_SOCKETS\", },\ntools/bpf/bpftool/feature.c-374-\t\t/* BPF_PROG_TYPE_LWT_* and related helpers */\n--\ntools/testing/selftests/bpf/config=127=CONFIG_VXLAN=y\ntools/testing/selftests/bpf/config:128:CONFIG_XDP_SOCKETS=y\ntools/testing/selftests/bpf/config-129-CONFIG_XFRM_INTERFACE=y\n--\ntools/testing/selftests/bpf/config.x86_64=223=CONFIG_X86_POWERNOW_K8=y\ntools/testing/selftests/bpf/config.x86_64:224:CONFIG_XDP_SOCKETS_DIAG=y\ntools/testing/selftests/bpf/config.x86_64-225-CONFIG_XFRM_SUB_POLICY=y\n--\ntools/testing/selftests/drivers/net/config=16=CONFIG_VLAN_8021Q=m\ntools/testing/selftests/drivers/net/config:17:CONFIG_XDP_SOCKETS=y\n"
} |
| 5/2 | 2026/07/19 14:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17342 output: 21 thoughts: 498 Thoughts: **Analyzing the AF_XDP Socket Modification** Okay, so this patch is related to `XDP_SOCKETS`, got it. My initial thought is to dive into the core functions being touched. It looks like the patch is modifying a few key components within the AF_XDP socket implementation. Specifically, it's touching these functions: `xsk_tx_peek_release_desc_batch`, `xsk_tx_completed`, `__xsk_generic_xmit`, `xsk_build_skb`, and `xsk_build_skb_zerocopy`. Immediately, I'm zeroing in on `xsk_tx_peek_release_desc_batch` and `__xsk_generic_xmit`. These are definitely on the critical path. `__xsk_generic_xmit` is the generic transmit path, so that's a central function. And `xsk_tx_peek_release_desc_batch` handles retrieving TX descriptors from the AF_XDP socket, a core interaction point with the drivers. Now, let's take a more comprehensive look at related functions to get a clearer picture of the code being changed. The following symbols seem relevant: * `xsk_tx_peek_release_desc_batch` * `xsk_tx_completed` * `__xsk_generic_xmit` * `xsk_build_skb` * `xsk_build_skb_zerocopy` * `xsk_tx_peek_release_shared_desc_batch` * `__xsk_tx_peek_release_desc_batch` * `xsk_tx_commit_batch` * `xskq_cons_read_desc_batch` A quick check confirms that `xsk_tx_peek_release_shared_desc_batch`, `__xsk_tx_peek_release_desc_batch`, and `xsk_tx_commit_batch` are all static. And `xskq_cons_read_desc_batch` is static inline. This means they are likely not directly called from outside of their respective modules, and helps to narrow down the scope of impact. Therefore, my main focus will be on these primary functions: * `xsk_tx_peek_release_desc_batch` * `xsk_tx_completed` * `__xsk_generic_xmit` * `xsk_build_skb` * `xsk_build_skb_zerocopy` Finally, before I dig further, I should just confirm that `xsk_build_skb` and `xsk_build_skb_zerocopy` are not static, in order to avoid misinterpreting where these functions could be called. That's the next step. |
| 6/2 | 2026/07/19 14:21 | tool | grepper |
0mArgs:{
"Expression": "xsk_build_skb"
}Results: {
"Output": "net/xdp/xsk.c=964=static int xsk_skb_metadata(struct sk_buff *skb, void *buffer,\n--\nnet/xdp/xsk.c-1005-\nnet/xdp/xsk.c:1006:static struct sk_buff *xsk_build_skb_zerocopy(struct xdp_sock *xs,\nnet/xdp/xsk.c-1007-\t\t\t\t\t struct xdp_desc *desc)\n--\nnet/xdp/xsk.c-1081-\nnet/xdp/xsk.c:1082:static struct sk_buff *xsk_build_skb(struct xdp_sock *xs,\nnet/xdp/xsk.c-1083-\t\t\t\t struct xdp_desc *desc)\n--\nnet/xdp/xsk.c-1089-\tif (dev-\u003epriv_flags \u0026 IFF_TX_SKB_NO_LINEAR) {\nnet/xdp/xsk.c:1090:\t\tskb = xsk_build_skb_zerocopy(xs, desc);\nnet/xdp/xsk.c-1091-\t\tif (IS_ERR(skb)) {\n--\nnet/xdp/xsk.c=1188=static int __xsk_generic_xmit(struct sock *sk)\n--\nnet/xdp/xsk.c-1234-\nnet/xdp/xsk.c:1235:\t\tskb = xsk_build_skb(xs, \u0026desc);\nnet/xdp/xsk.c-1236-\t\tif (IS_ERR(skb)) {\n"
} |
| 7/2 | 2026/07/19 14:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18926 output: 165 thoughts: 209 Thoughts: **My Assessment of the XDP Patch** Okay, so I've looked over this patch. It's clear that the changes they've made are fairly fundamental, focused on enhancing the Tx path within the AF_XDP socket implementation. Specifically, they're dealing with invalid descriptors and completion ring issues, and introducing some batching and reclamation logic. Given that, I'm confident that these key symbols are the relevant focal points to track: * `xsk_tx_peek_release_desc_batch` * `xsk_tx_completed` * `__xsk_generic_xmit` * `xsk_build_skb` * `xsk_build_skb_zerocopy` Those all seem static and directly address the functional modifications to AF\_XDP. I can confidently include those in the `FocusSymbols` list. Now, as for the configuration options that enable this... it seems pretty straightforward. `XDP_SOCKETS` is the dominant factor here. I'll double check to be extra certain, but I don't see any other flags. The core of this patch seems to be built upon the fundamental AF\_XDP infrastructure. I think we are good to go. Time to call the set-results function and lock these parameters into place. |
| 8/2 | 2026/07/19 14:21 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"XDP_SOCKETS"
],
"FocusSymbols": [
"xsk_tx_peek_release_desc_batch",
"xsk_tx_completed",
"__xsk_generic_xmit",
"xsk_build_skb",
"xsk_build_skb_zerocopy"
],
"Reasoning": "The patch modifies the AF_XDP socket implementation to handle invalid descriptors and completion ring behavior, particularly in the Tx path. It introduces batching and reclaim logic for descriptors. These are functional changes to the core AF_XDP logic and are reachable via standard AF_XDP socket operations (e.g., using generic XDP mode).",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"XDP_SOCKETS"
],
"FocusSymbols": [
"xsk_tx_peek_release_desc_batch",
"xsk_tx_completed",
"__xsk_generic_xmit",
"xsk_build_skb",
"xsk_build_skb_zerocopy"
],
"Reasoning": "The patch modifies the AF_XDP socket implementation to handle invalid descriptors and completion ring behavior, particularly in the Tx path. It introduces batching and reclaim logic for descriptors. These are functional changes to the core AF_XDP logic and are reachable via standard AF_XDP socket operations (e.g., using generic XDP mode).",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|