From: Taehee Yoo <ap420073@gmail.com>
To: "Alex Deucher" <alexander.deucher@amd.com>,
"Alexei Starovoitov" <ast@kernel.org>,
amd-gfx@lists.freedesktop.org,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Bill Wendling" <morbo@google.com>,
bpf@vger.kernel.org, "Christian König" <christian.koenig@amd.com>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"David Airlie" <airlied@gmail.com>,
"David S. Miller" <davem@davemloft.net>,
"Donald Hunter" <donald.hunter@gmail.com>,
dri-devel@lists.freedesktop.org,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Emil Tsalapatis" <emil@etsalapatis.com>,
"Eric Dumazet" <edumazet@google.com>,
"Felix Kuehling" <Felix.Kuehling@amd.com>,
"Hoyeon Lee" <hoyeon.rhee@gmail.com>,
"Ilias Apalodimas" <ilias.apalodimas@linaro.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Jesper Dangaard Brouer" <hawk@kernel.org>,
"Jiri Olsa" <jolsa@kernel.org>,
"John Fastabend" <john.fastabend@gmail.com>,
"Justin Stitt" <justinstitt@google.com>,
"Kees Cook" <kees@kernel.org>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Leon Romanovsky" <leon@kernel.org>,
linaro-mm-sig@lists.linaro.org, linux-hardening@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-media@vger.kernel.org, linux-rdma@vger.kernel.org,
llvm@lists.linux.dev, "Mark Bloch" <mbloch@nvidia.com>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Michael Chan" <michael.chan@broadcom.com>,
"Nathan Chancellor" <nathan@kernel.org>,
netdev@vger.kernel.org,
"Nick Desaulniers" <ndesaulniers@google.com>,
"Paolo Abeni" <pabeni@redhat.com>,
"Pavan Chebbi" <pavan.chebbi@broadcom.com>,
"Saeed Mahameed" <saeedm@nvidia.com>,
"Shuah Khan" <shuah@kernel.org>,
"Simona Vetter" <simona@ffwll.ch>,
"Simon Horman" <horms@kernel.org>, "Song Liu" <song@kernel.org>,
"Stanislav Fomichev" <sdf@fomichev.me>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Taehee Yoo" <ap420073@gmail.com>,
"Tariq Toukan" <tariqt@nvidia.com>,
"Yonghong Song" <yonghong.song@linux.dev>
Subject: [RFC PATCH net-next 01/13] net: knod: add uapi and core headers
Date: Sun, 19 Jul 2026 17:58:45 +0000 [thread overview]
Message-ID: <20260719175857.4071636-2-ap420073@gmail.com> (raw)
In-Reply-To: <20260719175857.4071636-1-ap420073@gmail.com>
Add the uAPI and core headers for KNOD, an in-kernel network offload
device that connects a NIC RX path to an accelerator (e.g. a GPU) for
zero-copy packet processing.
- include/uapi/linux/knod.h: genetlink uAPI
- Documentation/netlink/specs/knod.yaml: netlink spec
- include/net/knod.h: core framework types and ops
- include/net/spsc_ring.h: lock-free SPSC ring for the data path
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
(cherry picked from commit d93829acbce14528fcf70e155a0594c0c6151cce)
---
Documentation/netlink/specs/knod.yaml | 176 +++++++
include/net/knod.h | 467 +++++++++++++++++++
include/net/spsc_ring.h | 645 ++++++++++++++++++++++++++
include/uapi/linux/knod.h | 67 +++
4 files changed, 1355 insertions(+)
create mode 100644 Documentation/netlink/specs/knod.yaml
create mode 100644 include/net/knod.h
create mode 100644 include/net/spsc_ring.h
create mode 100644 include/uapi/linux/knod.h
diff --git a/Documentation/netlink/specs/knod.yaml b/Documentation/netlink/specs/knod.yaml
new file mode 100644
index 000000000000..7c64df467634
--- /dev/null
+++ b/Documentation/netlink/specs/knod.yaml
@@ -0,0 +1,176 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+---
+name: knod
+
+doc:
+ KNOD (KFD network offload) control plane.
+
+ Bind a NIC netdevice to a GPU/DPU offload accelerator and select which
+ offload feature (BPF, IPsec) the accelerator runs.
+
+definitions:
+ -
+ type: enum
+ name: feature
+ entries: [none, bpf, ipsec]
+ -
+ type: enum
+ name: accel-type
+ entries: [gpu, dpu]
+
+attribute-sets:
+ -
+ name: accel
+ attributes:
+ -
+ name: id
+ doc: Accelerator id.
+ type: u32
+ checks:
+ min: 1
+ -
+ name: name
+ doc: Accelerator name.
+ type: string
+ -
+ name: type
+ doc: Accelerator type.
+ type: u32
+ enum: accel-type
+ -
+ name: feature-cap
+ doc: Bitmask of offload features the accelerator supports.
+ type: u32
+ enum: feature
+ enum-as-flags: true
+ -
+ name: feature-ena
+ doc: Currently active offload feature on the accelerator.
+ type: u32
+ enum: feature
+ -
+ name: nic
+ attributes:
+ -
+ name: ifindex
+ doc: ifindex of the NIC netdevice registered with KNOD.
+ type: u32
+ checks:
+ min: 1
+ -
+ name: name
+ doc: Name of the NIC netdevice.
+ type: string
+ -
+ name: dev
+ attributes:
+ -
+ name: nic-ifindex
+ doc: ifindex of the bound NIC netdevice.
+ type: u32
+ checks:
+ min: 1
+ -
+ name: accel-id
+ doc: id of the bound accelerator.
+ type: u32
+ checks:
+ min: 1
+
+operations:
+ list:
+ -
+ name: accel-get
+ doc: Get / dump accelerators registered on the system.
+ attribute-set: accel
+ do:
+ request:
+ attributes:
+ - id
+ reply: &accel-all
+ attributes:
+ - id
+ - name
+ - type
+ - feature-cap
+ - feature-ena
+ dump:
+ reply: *accel-all
+ -
+ name: accel-set
+ doc: Select the active offload feature of an accelerator.
+ attribute-set: accel
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - id
+ - feature-ena
+ reply:
+ attributes: []
+ -
+ name: nic-get
+ doc: Get / dump NICs registered with the KNOD framework.
+ attribute-set: nic
+ do:
+ request:
+ attributes:
+ - ifindex
+ reply: &nic-all
+ attributes:
+ - ifindex
+ - name
+ dump:
+ reply: *nic-all
+ -
+ name: attach
+ doc: Attach a NIC netdevice to an accelerator.
+ attribute-set: dev
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - nic-ifindex
+ - accel-id
+ reply:
+ attributes: []
+ -
+ name: detach
+ doc: Detach a NIC netdevice from its accelerator.
+ attribute-set: dev
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - nic-ifindex
+ reply:
+ attributes: []
+ -
+ name: dev-get
+ doc: Get / dump active NIC <-> accelerator bindings.
+ attribute-set: dev
+ do:
+ request:
+ attributes:
+ - nic-ifindex
+ reply: &dev-all
+ attributes:
+ - nic-ifindex
+ - accel-id
+ dump:
+ reply: *dev-all
+ -
+ name: dev-add-ntf
+ doc: Notification about a new NIC <-> accelerator binding.
+ notify: dev-get
+ mcgrp: mgmt
+ -
+ name: dev-del-ntf
+ doc: Notification about a removed NIC <-> accelerator binding.
+ notify: dev-get
+ mcgrp: mgmt
+
+mcast-groups:
+ list:
+ -
+ name: mgmt
diff --git a/include/net/knod.h b/include/net/knod.h
new file mode 100644
index 000000000000..50333b0e028b
--- /dev/null
+++ b/include/net/knod.h
@@ -0,0 +1,467 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __NET_KNOD_H
+#define __NET_KNOD_H
+
+#include <linux/bpf.h>
+#include <linux/dma-buf.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/rtnetlink.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/if_link.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/atomic.h>
+#include <net/xdp.h>
+#include <net/spsc_ring.h>
+
+struct knod_dev;
+struct knod_netdev;
+struct knod_accel;
+struct gen_pool;
+struct page_pool;
+struct netlink_ext_ack;
+struct net_devmem_dmabuf_binding;
+
+extern struct mutex knod_lock;
+
+struct spsc_bd {
+ netmem_ref netmem;
+ u64 act;
+ u16 off;
+ u16 len;
+ u32 page_idx;
+ struct page_pool *pp;
+};
+
+/*
+ * KNOD action codes for spsc_bd.act
+ *
+ * Base actions (compatible with XDP constants for BPF/XDP path):
+ */
+#define KNOD_ABORTED XDP_ABORTED /* 0 */
+#define KNOD_DROP XDP_DROP /* 1 */
+#define KNOD_PASS XDP_PASS /* 2 */
+#define KNOD_TX XDP_TX /* 3 */
+#define KNOD_REDIRECT XDP_REDIRECT /* 4 */
+
+/*
+ * Extended actions - accel-specific, must not collide with XDP range [0..7].
+ * The NIC act_handler treats any unknown code as "in-flight to accel":
+ * stop releasing at that entry and wait for the accel to update bd->act.
+ */
+#define KNOD_ACT_INFLIGHT 0x80 /* accel processing in progress */
+#define KNOD_IPSEC_INFLIGHT 0x100 /* GPU IPsec dispatch in progress */
+#define KNOD_IPSEC_PASS 0x101 /* GPU IPsec done, recycle page */
+#define KNOD_IPSEC_DROP 0x102 /* GPU IPsec done, drop + recycle */
+
+/*
+ * PASS hand-off descriptor: the DD (NIC act_handler) fills one per PASS bd
+ * during its single act traversal and hands a batch to accel_ops->pass_copy.
+ * @netmem is the source RX page (GPU memory); @off/@len are post-BPF (may
+ * differ from the bd's original values if the program adjusted head/tail).
+ */
+struct spsc_pass_bd {
+ /* source RX page, recycled after the copy lands */
+ netmem_ref netmem;
+ u32 page_idx; /* page index in the queue's dmabuf RX buffer
+ * (the accel turns this into the GPU src addr;
+ * the netmem dma_addr is the NIC's, not the
+ * GPU's)
+ */
+ u16 off; /* packet offset within the page (post-BPF) */
+ u16 len; /* packet length (post-BPF) */
+};
+
+/*
+ * Host-page page_pool provider context (GPU->host delivery pools). The
+ * framework fills the public fields, points page_pool_params.mp_priv at it and
+ * sets .mp_ops to the NOD-private page_pool_hostmem_ops (knod_dev.c);
+ * ->init() builds @genpool and ->destroy() tears it down. Must outlive the
+ * page_pool; @freed fires once the pool has fully drained.
+ */
+struct page_pool_hostmem {
+ struct page **pages; /* owner-supplied, @count real pages */
+ unsigned int count;
+ dma_addr_t base_addr; /* device addr of pages[0] */
+ void (*freed)(void *arg); /* called once the pool fully drains */
+ void *arg;
+ struct gen_pool *genpool; /* private: managed by the provider */
+};
+
+struct knod_work_priv {
+ struct dma_buf *dmabuf;
+ netmem_ref *netmems;
+ unsigned int *data_lens;
+ int *data_offs;
+ int cnt;
+ int index;
+ struct napi_struct *napi;
+ struct spsc_ring spsc_bds;
+ void *spsc_pool_priv; /* accel driver priv for spsc pool memory */
+ u64 spsc_pool_gaddr; /* device-visible address of spsc pool */
+ /* framework-owned delivery pool */
+ struct page_pool *pass_pool;
+ /* provider ctx (owner storage) */
+ struct page_pool_hostmem pass_hm;
+ /* d2h: SDMA-issued, awaiting drain */
+ struct spsc_ring pass_pending;
+} ____cacheline_aligned_in_smp;
+
+static inline void knod_napi_kick(struct knod_work_priv *wpriv)
+{
+ struct napi_struct *napi;
+
+ rcu_read_lock();
+ napi = READ_ONCE(wpriv->napi);
+ if (napi)
+ napi_schedule(napi);
+ rcu_read_unlock();
+}
+
+#define KNOD_DEFAULT_PASS_SLOTS 64
+
+/*
+ * GPU->host delivery descriptor: a packet at @off (preserved headroom) for
+ * @len bytes within @netmem, a page from the framework delivery pool. The
+ * d2h path fills these into the per-queue pass_pending ring; knod_d2h_drain()
+ * turns each into an skb once its SDMA copy lands and hands it to the stack.
+ */
+struct knod_pass_desc {
+ u16 len; /* head_frag length (ipsec: inner_len) */
+ u16 off; /* head_frag offset (ipsec: inner_off) */
+ netmem_ref netmem; /* dst: framework delivery-pool page */
+ netmem_ref src; /* src: RX page recycled once the copy lands, or
+ * 0 when the producer recycles it elsewhere
+ * (ipsec: NIC act handler recycles via the bd)
+ */
+ /* SDMA fence to await before delivery (async) */
+ u32 fence_val;
+ u8 sdma_idx; /* which accel SDMA queue's fence to await */
+ /* Feature finalisation context, consumed by knod_dev->post_copy. */
+ struct {
+ u32 sa_slot; /* ipsec SA table slot */
+ u32 seq_lo; /* ESP sequence low 32 bits */
+ u32 seq_hi; /* ESN high 32 bits (0 if !ESN) */
+ u8 mode; /* XFRM_MODE_TRANSPORT / _TUNNEL */
+ u8 next_hdr; /* ESP trailer next-header */
+ u8 family; /* AF_INET / AF_INET6 */
+ } feat;
+};
+
+struct knod_accel_xdp_ops {
+ /* init/exit: permanent per-attach setup (attach/detach). */
+ int (*init)(struct knod_dev *knodev);
+ void (*exit)(struct knod_dev *knodev);
+ /* activate/deactivate: feature resource alloc/free (feature select). */
+ int (*activate)(struct knod_dev *knodev);
+ void (*deactivate)(struct knod_dev *knodev);
+ /*
+ * true while user XDP progs/maps are still bound (blocks feature
+ * switch).
+ */
+ bool (*busy)(struct knod_dev *knodev);
+ int (*xdp_offload_init)(struct knod_dev *knodev);
+ void (*xdp_offload_uninit)(struct knod_dev *knodev);
+ int (*xdp_install)(struct knod_dev *knodev,
+ struct netdev_bpf *bpf);
+ int (*rx_netmem)(struct knod_dev *knodev, netmem_ref netmem,
+ unsigned int data_len, int data_offset, int index);
+ int (*rx_netmem_bulk)(struct knod_dev *knodev,
+ struct knod_work_priv *wpriv);
+ /* Direct dispatch */
+ int (*dispatch)(struct knod_dev *knodev, int index);
+ /* Direct finish */
+ int (*finish)(struct knod_dev *knodev, int index);
+ void (*start)(struct knod_dev *knodev);
+ void (*stop)(struct knod_dev *knodev);
+};
+
+struct xfrm_state;
+struct xfrm_policy;
+struct netlink_ext_ack;
+
+struct knod_accel_ipsec_ops {
+ /* init/exit: permanent per-attach setup (attach/detach). */
+ int (*init)(struct knod_dev *knodev);
+ void (*exit)(struct knod_dev *knodev);
+ /* activate/deactivate: feature resource alloc/free (feature select). */
+ int (*activate)(struct knod_dev *knodev);
+ void (*deactivate)(struct knod_dev *knodev);
+ /*
+ * true while offloaded xfrm SAs are still bound (blocks feature
+ * switch).
+ */
+ bool (*busy)(struct knod_dev *knodev);
+ /*
+ * start/stop: worker/dispatcher start + GPU drain (interface
+ * up/down).
+ */
+ void (*start)(struct knod_dev *knodev);
+ void (*stop)(struct knod_dev *knodev);
+ int (*xdo_dev_state_add)(struct knod_dev *knodev,
+ struct xfrm_state *x,
+ struct netlink_ext_ack *extack);
+ void (*xdo_dev_state_delete)(struct knod_dev *knodev,
+ struct xfrm_state *x);
+ void (*xdo_dev_state_free)(struct knod_dev *knodev,
+ struct xfrm_state *x);
+ bool (*xdo_dev_offload_ok)(struct knod_dev *knodev,
+ struct sk_buff *skb,
+ struct xfrm_state *x);
+ void (*xdo_dev_state_advance_esn)(struct knod_dev *knodev,
+ struct xfrm_state *x);
+ void (*xdo_dev_state_update_stats)(struct knod_dev *knodev,
+ struct xfrm_state *x);
+ int (*xdo_dev_policy_add)(struct knod_dev *knodev,
+ struct xfrm_policy *x,
+ struct netlink_ext_ack *extack);
+ void (*xdo_dev_policy_delete)(struct knod_dev *knodev,
+ struct xfrm_policy *x);
+ void (*xdo_dev_policy_free)(struct knod_dev *knodev,
+ struct xfrm_policy *x);
+};
+
+struct knod_accel_ops {
+ int (*attach)(struct knod_dev *knodev);
+ void (*pre_detach)(struct knod_dev *knodev);
+ void (*detach)(struct knod_dev *knodev);
+ /* interface up/down (NIC driver knod_dev_start/stop): worker only. */
+ void (*dev_start)(struct knod_dev *knodev);
+ void (*dev_stop)(struct knod_dev *knodev);
+ void *(*alloc_mem)(struct knod_dev *knodev, size_t size,
+ u64 *gaddr, struct page ***pages, void **priv);
+ void (*free_mem)(struct knod_dev *knodev, void *priv);
+ /*
+ * Map dmabuf RX BOs into the GPU VM; must run after
+ * knod_dmabuf_attach().
+ */
+ int (*mp_map)(struct knod_dev *knodev);
+ /*
+ * Device->host copy primitives, used by the common knod_d2h_copy /
+ * knod_d2h_drain delivery path. The accel owns the SDMA engine (and
+ * its fence/ring); the framework owns the pending ring and dst pool.
+ * d2h_submit: queue one GPU->host copy. Returns a monotonic fence
+ * position to tag the descriptor with, or 0 if the SDMA
+ * ring is full (caller drops -- backpressure).
+ * d2h_kick: publish the batch (fence + doorbell).
+ * d2h_fence: current completed fence position of an SDMA queue
+ * (drain compares the descriptor's tag against this).
+ */
+ u32 (*d2h_submit)(struct knod_dev *knodev, u64 dst, int queue,
+ u32 page_idx, u16 off, u32 len);
+ void (*d2h_kick)(struct knod_dev *knodev);
+ u32 (*d2h_fence)(struct knod_dev *knodev, int sdma_idx);
+ struct knod_accel_xdp_ops *xdp_ops;
+ struct knod_accel_ipsec_ops *ipsec_ops;
+
+ /* control plane (knod genetlink) feature select */
+ int (*feature_get)(struct knod_accel *accel, u32 *ena, u32 *cap);
+ int (*feature_set)(struct knod_accel *accel, u32 feature,
+ struct netlink_ext_ack *extack);
+};
+
+struct knod_nic_ops {
+ int (*attach)(struct knod_dev *knodev);
+ int (*detach)(struct knod_dev *knodev);
+ int (*tx_handler)(struct knod_dev *knodev, struct spsc_bd **bds,
+ int cnt, int napi_index, void *priv);
+ int (*redir_handler)(struct knod_dev *knodev, netmem_ref *netmems,
+ u16 *lens, int cnt, int napi_index,
+ struct net_device *target_dev, void *priv);
+ int (*drop_handler)(struct knod_dev *knodev, netmem_ref *netmems,
+ u16 *lens, int cnt, void *priv);
+};
+
+struct knod_dev_stats {
+ u64_stats_t tx_packets;
+ u64_stats_t tx_bytes;
+ struct u64_stats_sync syncp;
+ u32 tx_dropped;
+ u32 tx_errors;
+};
+
+#define __NOD_FLAGS_XDP 0
+#define __NOD_FLAGS_IPSEC 2
+#define __NOD_FLAGS_KTLS 3
+#define __NOD_FLAGS_MAX (__NOD_FLAGS_KTLS + 1)
+#define KNOD_FLAGS_XDP (1 << __NOD_FLAGS_XDP)
+#define KNOD_FLAGS_IPSEC (1 << __NOD_FLAGS_IPSEC)
+#define KNOD_FLAGS_KTLS (1 << __NOD_FLAGS_KTLS)
+
+#define KNOD_TYPE_GPU 0
+#define KNOD_TYPE_DPU 1
+#define KNOD_TYPE_MAX (KNOD_TYPE_DPU + 1)
+
+#define KNOD_SPSC_MAX 32
+#define KNOD_SPSC_ELEMS_MAX 8192
+
+/* Per-RX-queue GPU->host delivery pages (in-flight cap; sized for the deepest
+ * feature pipeline, independent of any per-feature descriptor ring size).
+ * Must cover the worst case where one RSS-concentrated flow lands every
+ * in-flight work on a single queue: ipsec holds a full batch of delivery
+ * pages per work (alloc precedes the SDMA copy into them), so the cap needs
+ * KNOD_IPSEC_NR_WORK * KNOD_IPSEC_PKT_BATCH (= 4 * 512) plus the pass_pending
+ * ring depth. Sized to absorb a full bd ring (KNOD_SPSC_ELEMS_MAX = 8192)
+ * worth of decrypted-but-undelivered packets plus the in-flight works. The
+ * backing is GTT, sized nqueues * KNOD_PASS_SLOTS * PAGE_SIZE (2 GiB at the
+ * 32-queue cap), which is why the alloc size path is size_t rather than int.
+ */
+#define KNOD_PASS_SLOTS 16384
+
+#define KNOD_STATUS_FREE 0
+#define KNOD_STATUS_USED 1
+
+struct knod_netdev {
+ struct list_head list;
+ struct net_device *dev;
+ struct knod_dev *knodev;
+ struct knod_accel *accel;
+ struct knod_nic_ops *nic_ops;
+ struct module *owner;
+ int flags;
+ int status;
+ void *priv;
+};
+
+struct knod_accel_xdp {
+ struct bpf_offload_dev *bpf_dev;
+ struct xdp_attachment_info xdp;
+ struct xdp_attachment_info xdp_hw;
+ struct bpf_prog *bpf_offloaded;
+ struct list_head bound_maps;
+ void *priv;
+};
+
+struct knod_accel {
+ struct list_head list;
+ struct knod_accel_ops *accel_ops;
+ struct module *owner;
+ struct knod_accel_xdp xdp;
+ struct knod_dev *knodev;
+ struct knod_netdev *knetdev;
+ int status;
+ int type;
+ int flags;
+ int id;
+ void *priv;
+ char name[16];
+};
+
+struct knod_dev {
+ struct list_head list;
+ struct knod_netdev *knetdev;
+ struct knod_accel *accel;
+ struct net_devmem_dmabuf_binding *bindings[KNOD_SPSC_MAX];
+ struct mutex lock;
+ struct net_device *netdev;
+ struct knod_dev_stats *stats __percpu;
+
+ struct knod_nic_ops *nic_ops;
+ struct knod_accel_ops *accel_ops;
+ /* per-queue priv data */
+ struct knod_work_priv *wpriv;
+ bool started;
+
+ /* IPsec proxy: original NIC xfrmdev_ops/feature state saved at attach,
+ * restored at detach so a NIC's native offload is not clobbered.
+ */
+ const struct xfrmdev_ops *ipsec_orig_xfrmdev_ops;
+ bool ipsec_added_hw_esp;
+
+ /* framework-owned GPU->host delivery (default pass): drain barrier */
+ /* accel handle for the delivery buffer */
+ void *pass_priv;
+ atomic_t pp_live; /* live delivery page_pools */
+ struct completion pp_drained; /* all pools drained (teardown) */
+ /*
+ * device->host (d2h) delivery: the accel owns the fence counter (its
+ * SDMA ring position); this lock just serialises the shared SDMA
+ * submit path across the per-queue NAPIs that drive knod_d2h_copy.
+ */
+ spinlock_t d2h_lock;
+
+ /*
+ * Feature delivery hook, set by the active feature on activate (NULL
+ * for bpf/none). knod_d2h_drain calls it after building the head_frag
+ * skb to run feature-specific finalisation (ipsec: SA/replay/secpath);
+ * it returns false to drop the packet.
+ */
+ bool (*post_copy)(struct knod_dev *knodev, struct sk_buff *skb,
+ const struct knod_pass_desc *desc, int queue_idx);
+};
+
+static inline bool knod_dev_active(struct knod_dev *knodev)
+{
+ return !!knodev->accel->xdp.xdp_hw.prog;
+}
+
+static inline struct bpf_prog *
+knod_dev_offloaded(struct knod_dev *knodev)
+{
+ return knodev->accel->xdp.bpf_offloaded;
+}
+
+static inline void knod_dev_offload(struct knod_dev *knodev,
+ struct bpf_prog *bpf_offloaded)
+{
+ knodev->accel->xdp.bpf_offloaded = bpf_offloaded;
+}
+
+static inline bool knod_dev_map_empty(struct knod_dev *knodev)
+{
+ return list_empty(&knodev->accel->xdp.bound_maps);
+}
+
+void knod_netdev_register(struct knod_netdev *netdev);
+void knod_netdev_unregister(struct knod_netdev *netdev);
+void knod_accel_register(struct knod_accel *accel);
+void knod_accel_unregister(struct knod_accel *accel);
+void knod_dev_start(struct knod_dev *knodev);
+void knod_dev_stop(struct knod_dev *knodev);
+int knod_dev_xdp_install(struct knod_dev *knodev,
+ struct netdev_bpf *xdp);
+void knod_dev_get_stats64(struct knod_dev *knodev,
+ struct rtnl_link_stats64 *stats);
+void knod_dev_lock(void);
+void knod_dev_unlock(void);
+struct sk_buff *knod_pass_build_skb(netmem_ref netmem, u16 off, u16 len,
+ struct page_pool *pool, bool napi);
+int knod_d2h_copy(struct knod_dev *knodev, int napi_index,
+ struct spsc_pass_bd *bds, int cnt);
+int knod_d2h_drain(struct knod_dev *knodev, int napi_index,
+ struct napi_struct *napi, int budget);
+
+extern struct list_head knod_dev_list;
+extern struct list_head knod_netdev_list;
+extern struct list_head knod_accel_list;
+
+#define for_each_xdev(d) \
+ list_for_each_entry(d, &knod_dev_list, list)
+#define for_each_xdev_safe(d) \
+ list_for_each_entry_safe(d, n, &knod_dev_list, list)
+#define for_each_nodev(d) \
+ list_for_each_entry(d, &knod_netdev_list, list)
+#define for_each_nodev_safe(d, n) \
+ list_for_each_entry_safe(d, n, &knod_netdev_list, list)
+#define for_each_accel(d) \
+ list_for_each_entry(d, &knod_accel_list, list)
+#define for_each_accel_safe(d, n) \
+ list_for_each_entry_safe(d, n, &knod_accel_list, list)
+
+/* IPsec proxy functions */
+#if IS_ENABLED(CONFIG_XFRM_OFFLOAD)
+int knod_ipsec_attach(struct knod_dev *knodev);
+void knod_ipsec_detach(struct knod_dev *knodev);
+#else
+#endif
+
+/* XDP PASS drain - called from NIC NAPI poll */
+int knod_dev_xdp_drain_pass(struct knod_dev *knodev,
+ struct napi_struct *napi,
+ int queue_idx, int budget);
+
+#endif
diff --git a/include/net/spsc_ring.h b/include/net/spsc_ring.h
new file mode 100644
index 000000000000..1eccadc927d6
--- /dev/null
+++ b/include/net/spsc_ring.h
@@ -0,0 +1,645 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * spsc_ring.h - Lock-free SPSC Ring Buffer with embedded element pool
+ *
+ * Single-Producer Single-Consumer ring buffer. The ring owns a
+ * contiguous page-backed memory pool. Each slot is permanently bound
+ * to a cacheline-aligned element inside that pool.
+ *
+ * Producer calls spsc_produce() -> receives a pointer to a free
+ * element, writes data into it, then calls spsc_produce_commit()
+ * to publish.
+ *
+ * Consumer calls spsc_acquire() / spsc_pop() -> receives a pointer
+ * to a filled element. After spsc_release() the slot becomes
+ * available to the producer again - the element pointer is reused
+ * automatically because it is fixed to the slot.
+ *
+ * Two consumer modes (do NOT mix on the same instance):
+ *
+ * Mode 1 - Sliding window (2-step consumer):
+ * spsc_produce / commit -> peek -> acquire -> release
+ *
+ * Mode 2 - Simple queue:
+ * spsc_push -> pop
+ *
+ * Return convention:
+ * 0 success
+ * -ENOSPC ring full (producer side)
+ * -ENOENT ring empty (consumer side)
+ * -EINVAL bad parameter
+ * -ENOMEM allocation failure
+ *
+ * Memory ordering:
+ * Producer: write data -> smp_store_release(head)
+ * Consumer: smp_load_acquire(head) -> read data
+ * Consumer: done -> smp_store_release(tail)
+ * Producer: smp_load_acquire(tail) -> write data
+ *
+ * Capacity is always a power of two.
+ */
+
+#ifndef _SPSC_RING_H
+#define _SPSC_RING_H
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/log2.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+#include <linux/minmax.h>
+#include <linux/mm.h>
+#include <linux/cache.h>
+#include <asm/barrier.h>
+
+struct spsc_ring {
+ void **slots; /* pointer-per-slot into pool */
+ unsigned int mask; /* capacity - 1 */
+
+ /* Producer side */
+ unsigned int head; /* next slot to publish */
+
+ /* Consumer side */
+ unsigned int tail; /* oldest unconsumed slot */
+ unsigned int acquired; /* sliding window read cursor
+ * tail <= acquired <= head
+ */
+
+ /* Element pool */
+ struct page *pool_page; /* compound page backing elements */
+ unsigned int pool_order; /* page order */
+ unsigned int elem_stride; /* cacheline-aligned element size */
+} ____cacheline_aligned_in_smp;
+
+/* ================================================================== */
+/* Init / Destroy */
+/* ================================================================== */
+
+/**
+ * __spsc_init - initialize ring with a pre-allocated element pool
+ * @r: pointer to caller-allocated spsc_ring
+ * @elem_size: size of each element (rounded up to cacheline)
+ * @capacity: number of elements (rounded up to power of 2)
+ * @pool: pre-allocated pool memory (must be at least stride * capacity)
+ * @gfp: allocation flags (for slots array only)
+ *
+ * The caller owns the pool memory; spsc_destroy will NOT free it.
+ * Returns 0 on success, negative errno on failure.
+ */
+static inline int __spsc_init(struct spsc_ring *r, unsigned int elem_size,
+ unsigned int capacity, void *pool, gfp_t gfp)
+{
+ unsigned int stride = ALIGN(elem_size, SMP_CACHE_BYTES);
+ unsigned int i;
+ char *base = pool;
+
+ if (elem_size == 0 || capacity == 0 || !pool)
+ return -EINVAL;
+
+ capacity = roundup_pow_of_two(capacity);
+
+ r->slots = kcalloc(capacity, sizeof(void *), gfp);
+ if (!r->slots)
+ return -ENOMEM;
+
+ for (i = 0; i < capacity; i++)
+ r->slots[i] = base + (unsigned long)stride * i;
+
+ r->mask = capacity - 1;
+ r->head = 0;
+ r->tail = 0;
+ r->acquired = 0;
+ r->pool_page = NULL;
+ r->pool_order = 0;
+ r->elem_stride = stride;
+
+ return 0;
+}
+
+/**
+ * spsc_init - allocate ring and element pool
+ * @r: pointer to caller-allocated spsc_ring
+ * @elem_size: size of each element (rounded up to cacheline)
+ * @capacity: number of elements (rounded up to power of 2)
+ * @gfp: allocation flags
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+static inline int spsc_init(struct spsc_ring *r, unsigned int elem_size,
+ unsigned int capacity, gfp_t gfp)
+{
+ unsigned int stride = ALIGN(elem_size, SMP_CACHE_BYTES);
+ unsigned long pool_bytes;
+ unsigned int order;
+ struct page *page;
+
+ if (elem_size == 0 || capacity == 0)
+ return -EINVAL;
+
+ capacity = roundup_pow_of_two(capacity);
+ pool_bytes = (unsigned long)stride * capacity;
+ order = get_order(pool_bytes);
+
+ if (pool_bytes > (PAGE_SIZE << order))
+ return -EOVERFLOW;
+
+ /* Allocate element pool */
+ page = alloc_pages(gfp | __GFP_COMP | __GFP_ZERO, order);
+ if (!page)
+ return -ENOMEM;
+
+ if (__spsc_init(r, elem_size, capacity, page_address(page), gfp)) {
+ __free_pages(page, order);
+ return -ENOMEM;
+ }
+
+ r->pool_page = page;
+ r->pool_order = order;
+
+ return 0;
+}
+
+/**
+ * spsc_destroy - free ring and element pool
+ */
+static inline void spsc_destroy(struct spsc_ring *r)
+{
+ if (r->pool_page) {
+ __free_pages(r->pool_page, r->pool_order);
+ r->pool_page = NULL;
+ }
+ kfree(r->slots);
+ r->slots = NULL;
+}
+
+/* ================================================================== */
+/* Status helpers */
+/* ================================================================== */
+
+static inline unsigned int spsc_capacity(const struct spsc_ring *r)
+{
+ return r->mask + 1;
+}
+
+static inline unsigned int spsc_elem_size(const struct spsc_ring *r)
+{
+ return r->elem_stride;
+}
+
+/** spsc_count - total unconsumed entries (including acquired) */
+static inline unsigned int spsc_count(const struct spsc_ring *r)
+{
+ /* acquire head so a concurrent producer's slot writes are observed */
+ return smp_load_acquire(&((struct spsc_ring *)r)->head) - r->tail;
+}
+
+static inline bool spsc_empty(const struct spsc_ring *r)
+{
+ return spsc_count(r) == 0;
+}
+
+static inline bool spsc_full(const struct spsc_ring *r)
+{
+ return spsc_count(r) > r->mask;
+}
+
+/** spsc_pending - entries acquired but not yet released */
+static inline unsigned int spsc_pending(const struct spsc_ring *r)
+{
+ return r->acquired - r->tail;
+}
+
+/* ================================================================== */
+/* Producer API (single thread only, shared by both modes) */
+/* */
+/* Two-phase produce: */
+/* 1) spsc_produce() -> get pointer to free element */
+/* 2) caller writes data */
+/* 3) spsc_produce_commit() -> publish to consumer */
+/* */
+/* Or one-shot: spsc_push() for pre-filled elements. */
+/* ================================================================== */
+
+/**
+ * spsc_produce - reserve one slot and return its element pointer
+ * @r: ring buffer
+ * @out: receives pointer to the element to write into
+ *
+ * The slot is NOT yet visible to the consumer. Caller must write
+ * data into *out and then call spsc_produce_commit().
+ *
+ * Returns 0 on success, -ENOSPC if full.
+ */
+static inline int spsc_produce(struct spsc_ring *r, void **out)
+{
+ unsigned int head = r->head;
+ unsigned int tail;
+
+ /* acquire tail to observe the slots the consumer has released */
+ tail = smp_load_acquire(&r->tail);
+
+ if (head - tail > r->mask)
+ return -ENOSPC;
+
+ *out = r->slots[head & r->mask];
+
+ return 0;
+}
+
+/**
+ * spsc_produce_commit - publish the previously reserved slot
+ * @r: ring buffer
+ *
+ * Must be called exactly once after each successful spsc_produce().
+ */
+static inline void spsc_produce_commit(struct spsc_ring *r)
+{
+ /* wmb() (sfence on x86) is needed when the pool backing memory
+ * is mapped Write-Combining (e.g. GPU GTT). WC stores are not
+ * ordered by x86 TSO, so smp_store_release (compiler barrier)
+ * alone cannot guarantee the element writes are visible before
+ * the head update reaches the consumer.
+ */
+ wmb();
+ /* release: publish the reserved slot; pairs with the head acquire */
+ smp_store_release(&r->head, r->head + 1);
+}
+
+/**
+ * spsc_produce_n - reserve up to @n slots
+ * @r: ring buffer
+ * @out: destination array for element pointers
+ * @n: max slots to reserve
+ * @cnt: out - number actually reserved (may be NULL)
+ *
+ * Caller must write data into each out[i] and then call
+ * spsc_produce_commit_n(r, *cnt).
+ *
+ * Returns 0 on success, -ENOSPC if zero could be reserved.
+ */
+static inline int spsc_produce_n(struct spsc_ring *r, void **out,
+ unsigned int n, unsigned int *cnt)
+{
+ /* acquire tail to observe the slots the consumer has released */
+ unsigned int tail = smp_load_acquire(&r->tail);
+ unsigned int head = r->head;
+ unsigned int free;
+ unsigned int i;
+
+ free = (r->mask + 1) - (head - tail);
+
+ n = min(n, free);
+ if (n == 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOSPC;
+ }
+
+ for (i = 0; i < n; i++)
+ out[i] = r->slots[(head + i) & r->mask];
+
+ if (cnt)
+ *cnt = n;
+ return 0;
+}
+
+/**
+ * spsc_produce_commit_n - publish @n previously reserved slots
+ * @r: ring buffer
+ * @n: number of slots to publish (must match produce_n count)
+ */
+static inline void spsc_produce_commit_n(struct spsc_ring *r, unsigned int n)
+{
+ /* drain WC element stores before the head update (see commit above) */
+ wmb();
+ /* release: publish the reserved slots; pairs with the head acquire */
+ smp_store_release(&r->head, r->head + n);
+}
+
+/* ================================================================== */
+/* Mode 1: Sliding window consumer (single thread only) */
+/* */
+/* peek -> read from acquired cursor, no cursor movement */
+/* acquire -> advance acquired cursor, return element pointers */
+/* release -> advance tail, slots become reusable by producer */
+/* rewind -> reset acquired back to tail */
+/* ================================================================== */
+
+/**
+ * spsc_peek - read element pointers from acquired cursor (read-only)
+ * @r: ring buffer
+ * @out: destination array for element pointers
+ * @max: max entries to peek
+ * @cnt: out - number of entries peeked (may be NULL)
+ *
+ * Does NOT move any cursor.
+ *
+ * Returns 0 on success, -ENOENT if nothing to peek.
+ */
+static inline int spsc_peek(struct spsc_ring *r, void **out, unsigned int max,
+ unsigned int *cnt)
+{
+ /* acquire the producer's head; slots it published are now visible */
+ unsigned int head = smp_load_acquire(&r->head);
+ unsigned int acq = r->acquired;
+ unsigned int avail;
+ unsigned int i;
+
+ avail = head - acq;
+ avail = min(avail, max);
+
+ if (avail == 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOENT;
+ }
+
+ for (i = 0; i < avail; i++)
+ out[i] = r->slots[(acq + i) & r->mask];
+
+ if (cnt)
+ *cnt = avail;
+ return 0;
+}
+
+/**
+ * spsc_peek_at - peek starting at an offset past the acquired cursor
+ * @r: ring buffer
+ * @skip: number of entries to skip past r->acquired
+ * @out: destination array for element pointers
+ * @max: max entries to peek
+ * @cnt: out - number of entries peeked (may be NULL)
+ *
+ * Lets the consumer stage a second batch past entries that have been
+ * read by a previous peek but not yet committed via spsc_acquire.
+ * Does NOT move any cursor. The caller is responsible for tracking
+ * the cumulative skip across staged batches; when those batches are
+ * eventually released to the producer via spsc_acquire, pass the same
+ * count so r->acquired catches up.
+ *
+ * Returns 0 on success, -ENOENT if nothing to peek at that offset.
+ */
+static inline int spsc_peek_at(struct spsc_ring *r, unsigned int skip,
+ void **out, unsigned int max, unsigned int *cnt)
+{
+ /* acquire the producer's head; slots it published are now visible */
+ unsigned int head = smp_load_acquire(&r->head);
+ unsigned int pos = r->acquired + skip;
+ unsigned int avail;
+ unsigned int i;
+
+ if ((int)(head - pos) <= 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOENT;
+ }
+
+ avail = min(head - pos, max);
+ for (i = 0; i < avail; i++)
+ out[i] = r->slots[(pos + i) & r->mask];
+
+ if (cnt)
+ *cnt = avail;
+ return 0;
+}
+
+/**
+ * spsc_acquire - advance acquired cursor (step 1)
+ * @r: ring buffer
+ * @out: destination array for element pointers, or NULL to skip
+ * @max: max entries to acquire
+ * @cnt: out - number acquired (may be NULL)
+ *
+ * Returns 0 on success, -ENOENT if nothing to acquire.
+ */
+static inline int spsc_acquire(struct spsc_ring *r, void **out,
+ unsigned int max, unsigned int *cnt)
+{
+ /* acquire the producer's head; slots it published are now visible */
+ unsigned int head = smp_load_acquire(&r->head);
+ unsigned int acq = r->acquired;
+ unsigned int avail;
+ unsigned int i;
+
+ avail = head - acq;
+ avail = min(avail, max);
+
+ if (avail == 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOENT;
+ }
+
+ if (out) {
+ for (i = 0; i < avail; i++)
+ out[i] = r->slots[(acq + i) & r->mask];
+ }
+
+ /* Publish the window to the releasing consumer (a different CPU than
+ * this acquirer): pair with the smp_load_acquire() in spsc_release()
+ * so it cannot observe the advanced cursor before the element stores
+ * (e.g. an accel verdict) those slots now point at.
+ */
+ smp_store_release(&r->acquired, acq + avail);
+
+ if (cnt)
+ *cnt = avail;
+ return 0;
+}
+
+/**
+ * spsc_acquire_all - acquire all available entries
+ */
+static inline int spsc_acquire_all(struct spsc_ring *r, void **out,
+ unsigned int *cnt)
+{
+ return spsc_acquire(r, out, UINT_MAX, cnt);
+}
+
+/**
+ * spsc_release - get element pointers of acquired entries (release step 1)
+ * @r: ring buffer
+ * @out: destination array for element pointers, or NULL to skip
+ * @n: number of entries to release (<= pending)
+ * @cnt: out - number of entries prepared for release (may be NULL)
+ *
+ * Returns element pointers for the oldest @n acquired entries but
+ * does NOT advance tail - the producer still cannot reuse these slots.
+ * Caller processes the elements, then calls spsc_release_commit() to
+ * actually free them.
+ *
+ * Returns 0 on success, -ENOENT if nothing to release.
+ */
+static inline int spsc_release(struct spsc_ring *r, void **out, unsigned int n,
+ unsigned int *cnt)
+{
+ unsigned int tail = r->tail;
+ unsigned int pending;
+ unsigned int i;
+
+ /* Pairs with smp_store_release(&r->acquired) in spsc_acquire(): once
+ * we see the advanced cursor we are guaranteed to see the element
+ * stores (e.g. the accel verdict) for the slots it exposes.
+ */
+ pending = smp_load_acquire(&r->acquired) - tail;
+
+ n = min(n, pending);
+ if (n == 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOENT;
+ }
+
+ if (out) {
+ for (i = 0; i < n; i++)
+ out[i] = r->slots[(tail + i) & r->mask];
+ }
+
+ if (cnt)
+ *cnt = n;
+ return 0;
+}
+
+/**
+ * spsc_release_commit - advance tail, free slots for producer (release step 2)
+ * @r: ring buffer
+ * @n: number of entries to commit (must match prior spsc_release count)
+ *
+ * After this call the producer may reuse these slots.
+ */
+static inline void spsc_release_commit(struct spsc_ring *r, unsigned int n)
+{
+ /* release: hand the consumed slots back to the producer */
+ smp_store_release(&r->tail, r->tail + n);
+}
+
+/**
+ * spsc_release_all - get all acquired entries' pointers (release step 1)
+ * @r: ring buffer
+ * @out: destination array for element pointers, or NULL to skip
+ * @cnt: out - number of entries prepared for release (may be NULL)
+ *
+ * Convenience for spsc_release(r, out, pending, cnt).
+ * Caller must still call spsc_release_commit(r, *cnt) afterward.
+ *
+ * Returns 0 on success, -ENOENT if nothing pending.
+ */
+static inline int spsc_release_all(struct spsc_ring *r, void **out,
+ unsigned int *cnt)
+{
+ return spsc_release(r, out, r->acquired - r->tail, cnt);
+}
+
+/**
+ * spsc_rewind - undo acquires, reset acquired cursor to tail
+ */
+static inline void spsc_rewind(struct spsc_ring *r)
+{
+ r->acquired = r->tail;
+}
+
+/* ================================================================== */
+/* Mode 2: Simple push / pop (single thread per side) */
+/* */
+/* One-shot convenience wrappers. */
+/* Do NOT mix with Mode 1 acquire/release on the same instance. */
+/* ================================================================== */
+
+/**
+ * spsc_push - reserve, let caller fill, and publish in one shot
+ * @r: ring buffer
+ * @out: receives pointer to the element to write into
+ *
+ * Unlike produce/commit, the slot is published immediately.
+ * Caller must fill *out BEFORE this function returns if another
+ * thread could consume it - but since this is SPSC with push/pop
+ * the typical pattern is:
+ *
+ * spsc_push(&r, &elem);
+ * fill(elem); // safe: consumer hasn't seen it yet?
+ *
+ * NO - push publishes immediately. Use produce/commit if you need
+ * to fill before publishing. push is an alias for produce+commit.
+ *
+ * Returns 0 on success, -ENOSPC if full.
+ */
+static inline int spsc_push(struct spsc_ring *r, void **out)
+{
+ int ret;
+
+ ret = spsc_produce(r, out);
+ if (ret)
+ return ret;
+
+ spsc_produce_commit(r);
+ return 0;
+}
+
+/**
+ * spsc_pop - dequeue one element
+ * @r: ring buffer
+ * @out: receives pointer to the consumed element
+ *
+ * The element pointer remains valid until the next spsc_push() or
+ * spsc_produce() reuses that slot.
+ *
+ * Returns 0 on success, -ENOENT if empty.
+ */
+static inline int spsc_pop(struct spsc_ring *r, void **out)
+{
+ /* acquire the producer's head; slots it published are now visible */
+ unsigned int head = smp_load_acquire(&r->head);
+ unsigned int tail = r->tail;
+
+ if (tail == head)
+ return -ENOENT;
+
+ *out = r->slots[tail & r->mask];
+
+ r->acquired = tail + 1;
+ /* release: hand the consumed slot back to the producer */
+ smp_store_release(&r->tail, tail + 1);
+
+ return 0;
+}
+
+/**
+ * spsc_pop_n - dequeue up to @n elements
+ * @r: ring buffer
+ * @out: destination array for element pointers
+ * @n: max entries to dequeue
+ * @cnt: out - number dequeued (may be NULL)
+ *
+ * Returns 0 on success, -ENOENT if empty.
+ */
+static inline int spsc_pop_n(struct spsc_ring *r, void **out, unsigned int n,
+ unsigned int *cnt)
+{
+ /* acquire the producer's head; slots it published are now visible */
+ unsigned int head = smp_load_acquire(&r->head);
+ unsigned int tail = r->tail;
+ unsigned int avail;
+ unsigned int i;
+
+ avail = head - tail;
+ n = min(n, avail);
+
+ if (n == 0) {
+ if (cnt)
+ *cnt = 0;
+ return -ENOENT;
+ }
+
+ for (i = 0; i < n; i++)
+ out[i] = r->slots[(tail + i) & r->mask];
+
+ r->acquired = tail + n;
+ /* release: hand the consumed slots back to the producer */
+ smp_store_release(&r->tail, tail + n);
+
+ if (cnt)
+ *cnt = n;
+ return 0;
+}
+
+#endif /* _SPSC_RING_H */
diff --git a/include/uapi/linux/knod.h b/include/uapi/linux/knod.h
new file mode 100644
index 000000000000..57602685c14e
--- /dev/null
+++ b/include/uapi/linux/knod.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/knod.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_KNOD_H
+#define _UAPI_LINUX_KNOD_H
+
+#define KNOD_FAMILY_NAME "knod"
+#define KNOD_FAMILY_VERSION 1
+
+enum knod_feature {
+ KNOD_FEATURE_NONE,
+ KNOD_FEATURE_BPF,
+ KNOD_FEATURE_IPSEC,
+};
+
+enum knod_accel_type {
+ KNOD_ACCEL_TYPE_GPU,
+ KNOD_ACCEL_TYPE_DPU,
+};
+
+enum {
+ KNOD_A_ACCEL_ID = 1,
+ KNOD_A_ACCEL_NAME,
+ KNOD_A_ACCEL_TYPE,
+ KNOD_A_ACCEL_FEATURE_CAP,
+ KNOD_A_ACCEL_FEATURE_ENA,
+
+ __KNOD_A_ACCEL_MAX,
+ KNOD_A_ACCEL_MAX = (__KNOD_A_ACCEL_MAX - 1)
+};
+
+enum {
+ KNOD_A_NIC_IFINDEX = 1,
+ KNOD_A_NIC_NAME,
+
+ __KNOD_A_NIC_MAX,
+ KNOD_A_NIC_MAX = (__KNOD_A_NIC_MAX - 1)
+};
+
+enum {
+ KNOD_A_DEV_NIC_IFINDEX = 1,
+ KNOD_A_DEV_ACCEL_ID,
+
+ __KNOD_A_DEV_MAX,
+ KNOD_A_DEV_MAX = (__KNOD_A_DEV_MAX - 1)
+};
+
+enum {
+ KNOD_CMD_ACCEL_GET = 1,
+ KNOD_CMD_ACCEL_SET,
+ KNOD_CMD_NIC_GET,
+ KNOD_CMD_ATTACH,
+ KNOD_CMD_DETACH,
+ KNOD_CMD_DEV_GET,
+ KNOD_CMD_DEV_ADD_NTF,
+ KNOD_CMD_DEV_DEL_NTF,
+
+ __KNOD_CMD_MAX,
+ KNOD_CMD_MAX = (__KNOD_CMD_MAX - 1)
+};
+
+#define KNOD_MCGRP_MGMT "mgmt"
+
+#endif /* _UAPI_LINUX_KNOD_H */
--
2.43.0
next prev parent reply other threads:[~2026-07-19 17:59 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-19 17:58 [RFC PATCH net-next 00/13] net: knod: in-kernel network offload device Taehee Yoo
2026-07-19 17:58 ` Taehee Yoo [this message]
2026-07-19 17:58 ` [RFC PATCH net-next 02/13] net: devmem: extend memory provider for knod Taehee Yoo
2026-07-20 19:43 ` Mina Almasry
2026-07-19 17:58 ` [RFC PATCH net-next 03/13] net: core: add XDP_MODE_HW offload hook " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 04/13] net: knod: add offload device core and control plane Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 05/13] bpf: offload: allow PERCPU_ARRAY maps for offloaded programs Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 06/13] drm/amdkfd: prepare kfd core for the knod provider Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 07/13] drm/amdkfd: add knod provider core Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 08/13] drm/amdkfd: add GPU instruction emitter and disassembler Taehee Yoo
2026-07-20 20:05 ` Natalie Vock
2026-07-20 20:53 ` Andrew Lunn
2026-07-19 17:58 ` [RFC PATCH net-next 09/13] drm/amdkfd: add BPF-to-GPU JIT offload Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 10/13] net/mlx5e: add knod XDP offload support Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 11/13] bnxt_en: " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 12/13] selftests: drivers/net: add knod tests Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 13/13] drm/amdkfd: add IPsec full-packet offload Taehee Yoo
2026-07-20 19:18 ` [RFC PATCH net-next 00/13] net: knod: in-kernel network offload device Mina Almasry
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260719175857.4071636-2-ap420073@gmail.com \
--to=ap420073@gmail.com \
--cc=Felix.Kuehling@amd.com \
--cc=airlied@gmail.com \
--cc=alexander.deucher@amd.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=andrew+netdev@lunn.ch \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=christian.koenig@amd.com \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=emil@etsalapatis.com \
--cc=hawk@kernel.org \
--cc=horms@kernel.org \
--cc=hoyeon.rhee@gmail.com \
--cc=ilias.apalodimas@linaro.org \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=justinstitt@google.com \
--cc=kees@kernel.org \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linaro-mm-sig@lists.linaro.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=martin.lau@linux.dev \
--cc=mbloch@nvidia.com \
--cc=memxor@gmail.com \
--cc=michael.chan@broadcom.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pavan.chebbi@broadcom.com \
--cc=saeedm@nvidia.com \
--cc=sdf@fomichev.me \
--cc=shuah@kernel.org \
--cc=simona@ffwll.ch \
--cc=song@kernel.org \
--cc=sumit.semwal@linaro.org \
--cc=tariqt@nvidia.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.