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: 21+ 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-21 16:15 ` Taehee Yoo
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-21 16:36 ` Hoyeon Lee
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
2026-07-21 15:17 ` Taehee Yoo
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox