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 04/13] net: knod: add offload device core and control plane
Date: Sun, 19 Jul 2026 17:58:48 +0000 [thread overview]
Message-ID: <20260719175857.4071636-5-ap420073@gmail.com> (raw)
In-Reply-To: <20260719175857.4071636-1-ap420073@gmail.com>
Add the KNOD core framework and its genetlink control plane.
The core maintains three object types - a NIC-side netdev, an
accelerator, and a dynamically created offload device that binds them -
and drives the RX data path over per-queue SPSC rings backed by a
page_pool of accelerator memory.
The genetlink family ("knod") exposes attach/detach, device/nic/accel
inventory, and per-accelerator feature selection as a stable uAPI.
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
(cherry picked from commit f17f82a39be852aa8c520aea7177ce0086f7e811)
---
net/Kconfig | 2 +
net/Makefile | 1 +
net/knod/Kconfig | 5 +
net/knod/Makefile | 8 +
net/knod/knod-nl-gen.c | 121 ++++
net/knod/knod-nl-gen.h | 31 +
net/knod/knod.h | 26 +
net/knod/knod_core.c | 1231 ++++++++++++++++++++++++++++++++++++++++
net/knod/knod_nl.c | 406 +++++++++++++
9 files changed, 1831 insertions(+)
create mode 100644 net/knod/Kconfig
create mode 100644 net/knod/Makefile
create mode 100644 net/knod/knod-nl-gen.c
create mode 100644 net/knod/knod-nl-gen.h
create mode 100644 net/knod/knod.h
create mode 100644 net/knod/knod_core.c
create mode 100644 net/knod/knod_nl.c
diff --git a/net/Kconfig b/net/Kconfig
index e38477393551..e7a2200e92cc 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -72,6 +72,8 @@ config NET_DEVMEM
depends on DMA_SHARED_BUFFER
depends on PAGE_POOL
+source "net/knod/Kconfig"
+
config NET_SHAPER
bool
diff --git a/net/Makefile b/net/Makefile
index 5b2dd7f07a85..ea4ef5c5906d 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_TLS) += tls/
obj-$(CONFIG_XFRM) += xfrm/
obj-$(CONFIG_UNIX) += unix/
obj-$(CONFIG_INET_PSP) += psp/
+obj-$(CONFIG_KNOD) += knod/
obj-y += ipv6/
obj-$(CONFIG_PACKET) += packet/
obj-$(CONFIG_NET_KEY) += key/
diff --git a/net/knod/Kconfig b/net/knod/Kconfig
new file mode 100644
index 000000000000..d5a6d76d33e3
--- /dev/null
+++ b/net/knod/Kconfig
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+config KNOD
+ def_bool y
+ depends on NET_DEVMEM
diff --git a/net/knod/Makefile b/net/knod/Makefile
new file mode 100644
index 000000000000..73abe5c99f8c
--- /dev/null
+++ b/net/knod/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for the KNOD (KFD network offload) control plane.
+#
+
+obj-$(CONFIG_KNOD) += knod.o
+
+knod-y := knod_core.o knod_nl.o knod-nl-gen.o
diff --git a/net/knod/knod-nl-gen.c b/net/knod/knod-nl-gen.c
new file mode 100644
index 000000000000..96042f0c15c9
--- /dev/null
+++ b/net/knod/knod-nl-gen.c
@@ -0,0 +1,121 @@
+// 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 kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "knod-nl-gen.h"
+
+#include <uapi/linux/knod.h>
+
+/* KNOD_CMD_ACCEL_GET - do */
+static const struct nla_policy knod_accel_get_nl_policy[KNOD_A_ACCEL_ID + 1] = {
+ [KNOD_A_ACCEL_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+};
+
+/* KNOD_CMD_ACCEL_SET - do */
+static const struct nla_policy knod_accel_set_nl_policy[KNOD_A_ACCEL_FEATURE_ENA + 1] = {
+ [KNOD_A_ACCEL_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+ [KNOD_A_ACCEL_FEATURE_ENA] = NLA_POLICY_MAX(NLA_U32, 2),
+};
+
+/* KNOD_CMD_NIC_GET - do */
+static const struct nla_policy knod_nic_get_nl_policy[KNOD_A_NIC_IFINDEX + 1] = {
+ [KNOD_A_NIC_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
+};
+
+/* KNOD_CMD_ATTACH - do */
+static const struct nla_policy knod_attach_nl_policy[KNOD_A_DEV_ACCEL_ID + 1] = {
+ [KNOD_A_DEV_NIC_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
+ [KNOD_A_DEV_ACCEL_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+};
+
+/* KNOD_CMD_DETACH - do */
+static const struct nla_policy knod_detach_nl_policy[KNOD_A_DEV_NIC_IFINDEX + 1] = {
+ [KNOD_A_DEV_NIC_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
+};
+
+/* KNOD_CMD_DEV_GET - do */
+static const struct nla_policy knod_dev_get_nl_policy[KNOD_A_DEV_NIC_IFINDEX + 1] = {
+ [KNOD_A_DEV_NIC_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
+};
+
+/* Ops table for knod */
+static const struct genl_split_ops knod_nl_ops[] = {
+ {
+ .cmd = KNOD_CMD_ACCEL_GET,
+ .doit = knod_nl_accel_get_doit,
+ .policy = knod_accel_get_nl_policy,
+ .maxattr = KNOD_A_ACCEL_ID,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_ACCEL_GET,
+ .dumpit = knod_nl_accel_get_dumpit,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = KNOD_CMD_ACCEL_SET,
+ .doit = knod_nl_accel_set_doit,
+ .policy = knod_accel_set_nl_policy,
+ .maxattr = KNOD_A_ACCEL_FEATURE_ENA,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_NIC_GET,
+ .doit = knod_nl_nic_get_doit,
+ .policy = knod_nic_get_nl_policy,
+ .maxattr = KNOD_A_NIC_IFINDEX,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_NIC_GET,
+ .dumpit = knod_nl_nic_get_dumpit,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = KNOD_CMD_ATTACH,
+ .doit = knod_nl_attach_doit,
+ .policy = knod_attach_nl_policy,
+ .maxattr = KNOD_A_DEV_ACCEL_ID,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_DETACH,
+ .doit = knod_nl_detach_doit,
+ .policy = knod_detach_nl_policy,
+ .maxattr = KNOD_A_DEV_NIC_IFINDEX,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_DEV_GET,
+ .doit = knod_nl_dev_get_doit,
+ .policy = knod_dev_get_nl_policy,
+ .maxattr = KNOD_A_DEV_NIC_IFINDEX,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = KNOD_CMD_DEV_GET,
+ .dumpit = knod_nl_dev_get_dumpit,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+};
+
+static const struct genl_multicast_group knod_nl_mcgrps[] = {
+ [KNOD_NLGRP_MGMT] = { "mgmt", },
+};
+
+struct genl_family knod_nl_family __ro_after_init = {
+ .name = KNOD_FAMILY_NAME,
+ .version = KNOD_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = knod_nl_ops,
+ .n_split_ops = ARRAY_SIZE(knod_nl_ops),
+ .mcgrps = knod_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(knod_nl_mcgrps),
+};
diff --git a/net/knod/knod-nl-gen.h b/net/knod/knod-nl-gen.h
new file mode 100644
index 000000000000..a12b6a174bed
--- /dev/null
+++ b/net/knod/knod-nl-gen.h
@@ -0,0 +1,31 @@
+/* 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 kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_KNOD_GEN_H
+#define _LINUX_KNOD_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/knod.h>
+
+int knod_nl_accel_get_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_accel_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int knod_nl_accel_set_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_nic_get_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_nic_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int knod_nl_attach_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_detach_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_dev_get_doit(struct sk_buff *skb, struct genl_info *info);
+int knod_nl_dev_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+
+enum {
+ KNOD_NLGRP_MGMT,
+};
+
+extern struct genl_family knod_nl_family;
+
+#endif /* _LINUX_KNOD_GEN_H */
diff --git a/net/knod/knod.h b/net/knod/knod.h
new file mode 100644
index 000000000000..eaff0e82ca23
--- /dev/null
+++ b/net/knod/knod.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ */
+
+#ifndef _NET_KNOD_KNOD_H
+#define _NET_KNOD_KNOD_H
+
+#include <net/knod.h>
+
+#include "knod-nl-gen.h"
+
+struct net_device;
+
+/* knod_core.c */
+struct knod_netdev *knod_netdev_lookup(struct net_device *dev);
+struct knod_dev *knod_dev_lookup(struct net_device *dev);
+struct knod_accel *knod_accel_lookup(int id);
+int knod_dev_attach(struct knod_netdev *knetdev,
+ struct knod_accel *accel);
+int knod_dev_detach(struct knod_dev *knodev);
+
+/* knod_nl.c */
+void knod_nl_notify_dev(struct knod_dev *knodev, u32 cmd);
+
+#endif /* _NET_KNOD_KNOD_H */
diff --git a/net/knod/knod_core.c b/net/knod/knod_core.c
new file mode 100644
index 000000000000..af50a69bca65
--- /dev/null
+++ b/net/knod/knod_core.c
@@ -0,0 +1,1231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ */
+
+#include <net/knod.h>
+#include <net/spsc_ring.h>
+#include <net/page_pool/types.h>
+#include <net/page_pool/helpers.h>
+#include <net/page_pool/memory_provider.h>
+#include <net/xfrm.h>
+#include <linux/genalloc.h>
+#include <trace/events/page_pool.h>
+#include <net/devmem.h>
+
+#include "knod.h"
+
+DEFINE_MUTEX(knod_lock);
+LIST_HEAD(knod_dev_list);
+EXPORT_SYMBOL_GPL(knod_dev_list);
+LIST_HEAD(knod_netdev_list);
+EXPORT_SYMBOL_GPL(knod_netdev_list);
+LIST_HEAD(knod_accel_list);
+EXPORT_SYMBOL_GPL(knod_accel_list);
+
+void knod_dev_lock(void)
+{
+ mutex_lock(&knod_lock);
+}
+EXPORT_SYMBOL_GPL(knod_dev_lock);
+
+void knod_dev_unlock(void)
+{
+ mutex_unlock(&knod_lock);
+}
+EXPORT_SYMBOL_GPL(knod_dev_unlock);
+
+void knod_netdev_register(struct knod_netdev *knetdev)
+{
+ mutex_lock(&knod_lock);
+ knetdev->status = KNOD_STATUS_FREE;
+ list_add(&knetdev->list, &knod_netdev_list);
+ mutex_unlock(&knod_lock);
+}
+EXPORT_SYMBOL(knod_netdev_register);
+
+void knod_netdev_unregister(struct knod_netdev *knetdev)
+{
+ mutex_lock(&knod_lock);
+ list_del(&knetdev->list);
+ mutex_unlock(&knod_lock);
+}
+EXPORT_SYMBOL(knod_netdev_unregister);
+
+void knod_accel_register(struct knod_accel *accel)
+{
+ mutex_lock(&knod_lock);
+ accel->status = KNOD_STATUS_FREE;
+ list_add(&accel->list, &knod_accel_list);
+ mutex_unlock(&knod_lock);
+}
+EXPORT_SYMBOL(knod_accel_register);
+
+void knod_accel_unregister(struct knod_accel *accel)
+{
+ mutex_lock(&knod_lock);
+ list_del(&accel->list);
+ mutex_unlock(&knod_lock);
+}
+EXPORT_SYMBOL(knod_accel_unregister);
+
+struct knod_netdev *knod_netdev_lookup(struct net_device *dev)
+{
+ struct knod_netdev *knetdev;
+
+ list_for_each_entry(knetdev, &knod_netdev_list, list)
+ if (knetdev->dev == dev)
+ return knetdev;
+
+ return NULL;
+}
+
+struct knod_dev *knod_dev_lookup(struct net_device *dev)
+{
+ struct knod_dev *knodev;
+
+ list_for_each_entry(knodev, &knod_dev_list, list)
+ if (knodev->netdev == dev)
+ return knodev;
+
+ return NULL;
+}
+
+struct knod_accel *knod_accel_lookup(int id)
+{
+ struct knod_accel *accel;
+
+ list_for_each_entry(accel, &knod_accel_list, list)
+ if (accel->id == id)
+ return accel;
+
+ return NULL;
+}
+
+void knod_dev_start(struct knod_dev *knodev)
+{
+ /* Interface up: start the active feature's worker. */
+ knodev->started = true;
+ if (knodev->accel_ops->dev_start)
+ knodev->accel_ops->dev_start(knodev);
+}
+EXPORT_SYMBOL(knod_dev_start);
+
+void knod_dev_stop(struct knod_dev *knodev)
+{
+ /* Interface down: stop the worker + drain the GPU in-flight. */
+ if (knodev->accel_ops->dev_stop)
+ knodev->accel_ops->dev_stop(knodev);
+ knodev->started = false;
+}
+EXPORT_SYMBOL(knod_dev_stop);
+
+int knod_dev_xdp_install(struct knod_dev *knodev, struct netdev_bpf *xdp)
+{
+ if (!knodev->accel_ops->xdp_ops ||
+ !knodev->accel_ops->xdp_ops->xdp_install)
+ return -EOPNOTSUPP;
+ return knodev->accel_ops->xdp_ops->xdp_install(knodev, xdp);
+}
+EXPORT_SYMBOL(knod_dev_xdp_install);
+
+void knod_dev_get_stats64(struct knod_dev *knodev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct knod_dev_stats *p;
+ u32 tx_dropped = 0, tx_errors = 0;
+ u64 tx_packets, tx_bytes;
+ unsigned int start;
+ int i;
+
+ for_each_possible_cpu(i) {
+ p = per_cpu_ptr(knodev->stats, i);
+ do {
+ start = u64_stats_fetch_begin(&p->syncp);
+ tx_packets = u64_stats_read(&p->tx_packets);
+ tx_bytes = u64_stats_read(&p->tx_bytes);
+ } while (u64_stats_fetch_retry(&p->syncp, start));
+
+ stats->tx_packets += tx_packets;
+ stats->tx_bytes += tx_bytes;
+ tx_dropped += READ_ONCE(p->tx_dropped);
+ tx_errors += READ_ONCE(p->tx_errors);
+ }
+ stats->tx_dropped += tx_dropped;
+ stats->tx_errors += tx_errors;
+}
+EXPORT_SYMBOL(knod_dev_get_stats64);
+
+/*
+ * Wrap a delivery-pool page as a zero-copy head_frag skb: the packet sits at
+ * @off (preserved headroom) for @len bytes. Building it linear keeps
+ * skb->data on the packet so callers can edit headers in place. The page
+ * recycles to @pool when the skb is freed. On oversize or alloc failure the
+ * page is returned to @pool and NULL is returned. @napi selects the NAPI skb
+ * cache; callers outside softirq pass false.
+ */
+struct sk_buff *knod_pass_build_skb(netmem_ref netmem, u16 off, u16 len,
+ struct page_pool *pool, bool napi)
+{
+ struct page *pg = netmem_to_page(netmem);
+ struct sk_buff *skb;
+
+ /* Must fit alongside skb_shared_info at the page tail; MTU is capped
+ * below this, so drop the rare oversized outlier.
+ */
+ if (off + len > SKB_WITH_OVERHEAD(PAGE_SIZE)) {
+ if (pool)
+ page_pool_put_full_netmem(pool, netmem, false);
+ return NULL;
+ }
+
+ if (napi)
+ skb = napi_build_skb(page_address(pg), PAGE_SIZE);
+ else
+ skb = build_skb(page_address(pg), PAGE_SIZE);
+ if (unlikely(!skb)) {
+ if (pool)
+ page_pool_put_full_netmem(pool, netmem, false);
+ return NULL;
+ }
+
+ skb_mark_for_recycle(skb);
+ skb_reserve(skb, off);
+ skb_put(skb, len);
+ return skb;
+}
+EXPORT_SYMBOL(knod_pass_build_skb);
+
+/*
+ * Device->host copy: the NIC DD hands the PASS bds it accumulated during its
+ * single act traversal. Allocate a delivery page per packet, issue the accel
+ * SDMA (GPU -> page) asynchronously, and queue a descriptor on the per-queue
+ * pending ring tagged with this batch's fence; knod_d2h_drain delivers them
+ * once the fence lands and recycles the source. Sources that cannot be queued
+ * (bad len / ring full / pool empty) are recycled here. Returns the count
+ * queued. Producer and the drain consumer run on the same per-queue NAPI, so
+ * the ring is single-threaded; @d2h_lock only guards the shared SDMA submit.
+ */
+int knod_d2h_copy(struct knod_dev *knodev, int napi_index,
+ struct spsc_pass_bd *bds, int cnt)
+{
+ struct knod_accel_ops *ops = knodev->accel_ops;
+ struct knod_work_priv *wpriv;
+ struct page_pool *pool;
+ bool submitted = false;
+ int i, produced = 0;
+
+ if (napi_index < 0 || napi_index >= KNOD_SPSC_MAX || !ops->d2h_submit)
+ goto drop_all;
+ wpriv = &knodev->wpriv[napi_index];
+ pool = READ_ONCE(wpriv->pass_pool);
+ if (!pool || !wpriv->pass_pending.slots)
+ goto drop_all;
+
+ spin_lock(&knodev->d2h_lock);
+
+ for (i = 0; i < cnt; i++) {
+ netmem_ref src = bds[i].netmem;
+ struct knod_pass_desc *desc;
+ netmem_ref dst;
+ void *ptr;
+ u32 fv;
+ u16 off = bds[i].off;
+ u16 len = bds[i].len;
+
+ if (!len || off + len > SKB_WITH_OVERHEAD(PAGE_SIZE))
+ goto drop;
+ if (spsc_produce(&wpriv->pass_pending, &ptr))
+ goto drop;
+ dst = page_pool_dev_alloc_netmems(pool);
+ if (!dst)
+ goto drop; /* slot left uncommitted, reused next */
+
+ fv = ops->d2h_submit(knodev,
+ page_pool_get_dma_addr_netmem(dst) + off,
+ napi_index, bds[i].page_idx, off, len);
+ if (!fv) { /* SDMA ring full: backpressure drop */
+ page_pool_put_full_netmem(pool, dst, false);
+ goto drop;
+ }
+ submitted = true;
+
+ desc = ptr;
+ desc->netmem = dst;
+ desc->src = src;
+ desc->off = off;
+ desc->len = len;
+ desc->fence_val = fv;
+ desc->sdma_idx = 0;
+ spsc_produce_commit(&wpriv->pass_pending);
+ produced++;
+ continue;
+drop:
+ page_pool_recycle_direct_netmem(netmem_get_pp(src), src);
+ }
+
+ if (submitted)
+ ops->d2h_kick(knodev);
+ spin_unlock(&knodev->d2h_lock);
+
+ /* The copies are async; re-arm the NAPI so the drain runs once the
+ * SDMA lands -- at low rate, RX traffic alone may not poll again soon.
+ */
+ if (submitted)
+ knod_napi_kick(wpriv);
+ return produced;
+
+drop_all:
+ for (i = 0; i < cnt; i++)
+ page_pool_recycle_direct_netmem(netmem_get_pp(bds[i].netmem),
+ bds[i].netmem);
+ return 0;
+}
+EXPORT_SYMBOL(knod_d2h_copy);
+
+/*
+ * Drain the per-queue pending ring: deliver every descriptor whose batch
+ * fence has landed (accel_ops->d2h_fence) as a zero-copy head_frag skb from
+ * the delivery page, recycling the source RX page. Stops at the first
+ * not-yet-landed descriptor -- the ring is in fence order. Runs on the NIC
+ * NAPI, the same thread as the knod_d2h_copy producer.
+ */
+int knod_d2h_drain(struct knod_dev *knodev, int napi_index,
+ struct napi_struct *napi, int budget)
+{
+ void *ptrs[KNOD_DEFAULT_PASS_SLOTS];
+ struct knod_work_priv *wpriv;
+ struct knod_pass_desc *d0;
+ struct page_pool *pool;
+ LIST_HEAD(deliver_list);
+ unsigned int got = 0, i, n = 0;
+ int delivered = 0;
+ u32 cur_fence;
+
+ if (napi_index < 0 || napi_index >= KNOD_SPSC_MAX ||
+ !knodev->accel_ops->d2h_fence)
+ return 0;
+ wpriv = &knodev->wpriv[napi_index];
+ if (!wpriv->pass_pending.slots)
+ return 0;
+ pool = READ_ONCE(wpriv->pass_pool);
+
+ if (spsc_peek(&wpriv->pass_pending, ptrs,
+ min_t(unsigned int, budget, KNOD_DEFAULT_PASS_SLOTS),
+ &got) < 0 || got == 0)
+ return 0;
+
+ d0 = ptrs[0];
+ cur_fence = knodev->accel_ops->d2h_fence(knodev, d0->sdma_idx);
+
+ for (i = 0; i < got; i++) {
+ struct knod_pass_desc *desc = ptrs[i];
+ struct sk_buff *skb;
+
+ if ((s32)(cur_fence - desc->fence_val) < 0)
+ break; /* not landed yet; later descs are newer */
+
+ /* bpf carries the RX page as src for post-copy recycle; ipsec
+ * leaves it 0 (the NIC act handler recycles via the bd).
+ */
+ if (desc->src)
+ page_pool_recycle_direct_netmem(
+ netmem_get_pp(desc->src), desc->src);
+ skb = knod_pass_build_skb(desc->netmem, desc->off, desc->len,
+ pool, true);
+ n++;
+ if (!skb)
+ continue;
+ if (knodev->post_copy) {
+ if (!knodev->post_copy(knodev, skb, desc, napi_index)) {
+ kfree_skb(skb);
+ continue;
+ }
+ } else if (likely(skb->len >= ETH_HLEN)) {
+ skb->protocol = eth_type_trans(skb, knodev->netdev);
+ } else {
+ kfree_skb(skb);
+ continue;
+ }
+ list_add_tail(&skb->list, &deliver_list);
+ delivered++;
+ }
+
+ if (n) {
+ spsc_acquire(&wpriv->pass_pending, NULL, n, NULL);
+ spsc_release_commit(&wpriv->pass_pending, n);
+ }
+
+ /* Descriptors whose copy has not landed yet remain queued; re-arm so
+ * we poll again instead of waiting for the next RX event.
+ */
+ if (spsc_count(&wpriv->pass_pending))
+ knod_napi_kick(wpriv);
+
+ if (!list_empty(&deliver_list))
+ netif_receive_skb_list(&deliver_list);
+ return delivered;
+}
+EXPORT_SYMBOL(knod_d2h_drain);
+
+/* ======================================================================
+ * NOD IPsec proxy - bridges standard kernel xfrmdev_ops to
+ * knod_accel_ipsec_ops on the GPU accelerator.
+ * ======================================================================
+ */
+#if IS_ENABLED(CONFIG_XFRM_OFFLOAD)
+
+static struct knod_dev *knod_ipsec_find_xdev(struct net_device *dev)
+{
+ struct knod_dev *knodev;
+
+ list_for_each_entry(knodev, &knod_dev_list, list) {
+ if (knodev->netdev == dev)
+ return knodev;
+ }
+ return NULL;
+}
+
+static int knod_ipsec_xdo_state_add(struct net_device *dev,
+ struct xfrm_state *x,
+ struct netlink_ext_ack *extack)
+{
+ struct knod_dev *knodev = knod_ipsec_find_xdev(dev);
+
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_state_add)
+ return -EOPNOTSUPP;
+ return knodev->accel_ops->ipsec_ops->xdo_dev_state_add(knodev, x,
+ extack);
+}
+
+static void knod_ipsec_xdo_state_delete(struct net_device *dev,
+ struct xfrm_state *x)
+{
+ struct knod_dev *knodev = knod_ipsec_find_xdev(dev);
+
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_state_delete)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_state_delete(knodev, x);
+}
+
+static void knod_ipsec_xdo_state_free(struct net_device *dev,
+ struct xfrm_state *x)
+{
+ struct knod_dev *knodev = knod_ipsec_find_xdev(dev);
+
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_state_free)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_state_free(knodev, x);
+}
+
+static bool knod_ipsec_xdo_offload_ok(struct sk_buff *skb,
+ struct xfrm_state *x)
+{
+ struct knod_dev *knodev = knod_ipsec_find_xdev(x->xso.dev);
+
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_offload_ok)
+ return false;
+ return knodev->accel_ops->ipsec_ops->xdo_dev_offload_ok(knodev, skb, x);
+}
+
+static void knod_ipsec_xdo_state_advance_esn(struct xfrm_state *x)
+{
+ struct knod_dev *knodev;
+
+ if (!x->xso.dev)
+ return;
+ knodev = knod_ipsec_find_xdev(x->xso.dev);
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_state_advance_esn)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_state_advance_esn(knodev, x);
+}
+
+static void knod_ipsec_xdo_state_update_stats(struct xfrm_state *x)
+{
+ struct knod_dev *knodev;
+
+ if (!x->xso.dev)
+ return;
+ knodev = knod_ipsec_find_xdev(x->xso.dev);
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_state_update_stats)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_state_update_stats(knodev, x);
+}
+
+static int knod_ipsec_xdo_policy_add(struct xfrm_policy *xp,
+ struct netlink_ext_ack *extack)
+{
+ struct knod_dev *knodev;
+
+ if (!xp->xdo.dev)
+ return -EOPNOTSUPP;
+ knodev = knod_ipsec_find_xdev(xp->xdo.dev);
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_policy_add)
+ return -EOPNOTSUPP;
+ return knodev->accel_ops->ipsec_ops->xdo_dev_policy_add(knodev, xp,
+ extack);
+}
+
+static void knod_ipsec_xdo_policy_delete(struct xfrm_policy *xp)
+{
+ struct knod_dev *knodev;
+
+ if (!xp->xdo.dev)
+ return;
+ knodev = knod_ipsec_find_xdev(xp->xdo.dev);
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_policy_delete)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_policy_delete(knodev, xp);
+}
+
+static void knod_ipsec_xdo_policy_free(struct xfrm_policy *xp)
+{
+ struct knod_dev *knodev;
+
+ if (!xp->xdo.dev)
+ return;
+ knodev = knod_ipsec_find_xdev(xp->xdo.dev);
+ if (!knodev || !knodev->accel_ops->ipsec_ops ||
+ !knodev->accel_ops->ipsec_ops->xdo_dev_policy_free)
+ return;
+ knodev->accel_ops->ipsec_ops->xdo_dev_policy_free(knodev, xp);
+}
+
+static const struct xfrmdev_ops knod_ipsec_xfrmdev_ops = {
+ .xdo_dev_state_add = knod_ipsec_xdo_state_add,
+ .xdo_dev_state_delete = knod_ipsec_xdo_state_delete,
+ .xdo_dev_state_free = knod_ipsec_xdo_state_free,
+ .xdo_dev_offload_ok = knod_ipsec_xdo_offload_ok,
+ .xdo_dev_state_advance_esn = knod_ipsec_xdo_state_advance_esn,
+ .xdo_dev_state_update_stats = knod_ipsec_xdo_state_update_stats,
+ .xdo_dev_policy_add = knod_ipsec_xdo_policy_add,
+ .xdo_dev_policy_delete = knod_ipsec_xdo_policy_delete,
+ .xdo_dev_policy_free = knod_ipsec_xdo_policy_free,
+};
+
+int knod_dev_xdp_drain_pass(struct knod_dev *knodev, struct napi_struct *napi,
+ int queue_idx, int budget)
+{
+ if (!knodev || !knodev->accel_ops)
+ return 0;
+ /* Common device->host delivery drain. PASS bds were SDMA-copied by
+ * knod_d2h_copy from the NIC act handler; deliver the ones whose copy
+ * has landed.
+ */
+ return knod_d2h_drain(knodev, queue_idx, napi, budget);
+}
+EXPORT_SYMBOL_GPL(knod_dev_xdp_drain_pass);
+
+int knod_ipsec_attach(struct knod_dev *knodev)
+{
+ struct knod_accel_ipsec_ops *ops;
+
+ if (!knodev->accel_ops->ipsec_ops)
+ return 0;
+
+ ops = knodev->accel_ops->ipsec_ops;
+ if (ops->activate) {
+ int err = ops->activate(knodev);
+
+ if (err) {
+ pr_err("nod: IPsec activate failed on %s (%d)\n",
+ netdev_name(knodev->netdev), err);
+ return err;
+ }
+ }
+
+ /* Take over xfrmdev_ops, saving the NIC's original so detach can
+ * restore it (and only clear NETIF_F_HW_ESP if we added it).
+ */
+ knodev->ipsec_orig_xfrmdev_ops = knodev->netdev->xfrmdev_ops;
+ knodev->ipsec_added_hw_esp =
+ !(knodev->netdev->features & NETIF_F_HW_ESP);
+ knodev->netdev->xfrmdev_ops = &knod_ipsec_xfrmdev_ops;
+ knodev->netdev->features |= NETIF_F_HW_ESP;
+ knodev->netdev->hw_enc_features |= NETIF_F_HW_ESP;
+
+ pr_info("nod: IPsec offload enabled on %s\n",
+ netdev_name(knodev->netdev));
+ return 0;
+}
+EXPORT_SYMBOL(knod_ipsec_attach);
+
+void knod_ipsec_detach(struct knod_dev *knodev)
+{
+ struct knod_accel_ipsec_ops *ops = knodev->accel_ops->ipsec_ops;
+
+ if (!ops)
+ return;
+ if (knodev->netdev->xfrmdev_ops != &knod_ipsec_xfrmdev_ops)
+ return;
+
+ if (knodev->ipsec_added_hw_esp) {
+ knodev->netdev->features &= ~NETIF_F_HW_ESP;
+ knodev->netdev->hw_enc_features &= ~NETIF_F_HW_ESP;
+ }
+ knodev->netdev->xfrmdev_ops = knodev->ipsec_orig_xfrmdev_ops;
+
+ if (ops->deactivate)
+ ops->deactivate(knodev);
+
+ pr_info("nod: IPsec offload disabled on %s\n",
+ netdev_name(knodev->netdev));
+}
+EXPORT_SYMBOL(knod_ipsec_detach);
+
+#endif /* CONFIG_XFRM_OFFLOAD */
+
+/*
+ * Host-page page_pool memory provider for GPU->host delivery (NOD-private).
+ * Unlike the devmem/dma-buf providers (which hand out unreadable net_iov), this
+ * returns real host-readable pages (a GPU GTT buffer also mapped into system
+ * memory), so delivered data becomes skbs on the normal receive path. The
+ * pages stay owned by the accel and are never returned to the buddy; the
+ * gen_pool is only the slow-path backing store (page_pool's cache/ring absorb
+ * the per-packet churn), and the device address it hands out doubles as the
+ * netmem dma_addr (the worker's SDMA destination). Selected via
+ * page_pool_params.mp_ops in knod_pass_attach().
+ */
+static int hostmem_pp_init(struct page_pool *pool)
+{
+ struct page_pool_hostmem *hm = pool->mp_priv;
+ int err;
+
+ if (pool->p.order != 0)
+ return -E2BIG;
+ if (!hm || !hm->pages || !hm->count)
+ return -EINVAL;
+
+ hm->genpool = gen_pool_create(PAGE_SHIFT, NUMA_NO_NODE);
+ if (!hm->genpool)
+ return -ENOMEM;
+
+ err = gen_pool_add(hm->genpool, (unsigned long)hm->base_addr,
+ (size_t)hm->count * PAGE_SIZE, NUMA_NO_NODE);
+ if (err) {
+ gen_pool_destroy(hm->genpool);
+ hm->genpool = NULL;
+ return err;
+ }
+
+ /* Device addresses are pre-set; page_pool must not DMA-sync them. */
+ pool->dma_sync = false;
+ pool->dma_sync_for_cpu = false;
+
+ return 0;
+}
+
+static netmem_ref hostmem_pp_alloc_netmems(struct page_pool *pool, gfp_t gfp)
+{
+ struct page_pool_hostmem *hm = pool->mp_priv;
+ unsigned long addr;
+ netmem_ref netmem;
+ unsigned int idx;
+
+ addr = gen_pool_alloc(hm->genpool, PAGE_SIZE);
+ if (!addr)
+ return 0;
+
+ idx = (addr - hm->base_addr) >> PAGE_SHIFT;
+ if (WARN_ON_ONCE(idx >= hm->count)) {
+ gen_pool_free(hm->genpool, addr, PAGE_SIZE);
+ return 0;
+ }
+
+ netmem = page_to_netmem(hm->pages[idx]);
+ page_pool_provider_set_netmem(pool, netmem, addr);
+
+ pool->pages_state_hold_cnt++;
+ trace_page_pool_state_hold(pool, netmem, pool->pages_state_hold_cnt);
+
+ return netmem;
+}
+
+static bool hostmem_pp_release_netmem(struct page_pool *pool, netmem_ref netmem)
+{
+ struct page_pool_hostmem *hm = pool->mp_priv;
+ unsigned long addr = page_pool_get_dma_addr_netmem(netmem);
+
+ page_pool_clear_pp_info(netmem);
+ gen_pool_free(hm->genpool, addr, PAGE_SIZE);
+
+ /* Pages are accel-owned: never put_page() them to the buddy. */
+ return false;
+}
+
+static void hostmem_pp_destroy(struct page_pool *pool)
+{
+ struct page_pool_hostmem *hm = pool->mp_priv;
+
+ gen_pool_destroy(hm->genpool);
+ hm->genpool = NULL;
+
+ /* The pool has fully drained (inflight == 0); tell the owner it may now
+ * release the backing pages. The struct itself is owner-allocated.
+ */
+ if (hm->freed)
+ hm->freed(hm->arg);
+}
+
+static int hostmem_pp_nl_fill(void *mp_priv, struct sk_buff *rsp,
+ struct netdev_rx_queue *rxq)
+{
+ /* Nothing provider-specific to report; page_pool_user.c calls this
+ * unconditionally once mp_ops is set, so it must not be NULL.
+ */
+ return 0;
+}
+
+static const struct memory_provider_ops page_pool_hostmem_ops = {
+ .init = hostmem_pp_init,
+ .alloc_netmems = hostmem_pp_alloc_netmems,
+ .release_netmem = hostmem_pp_release_netmem,
+ .destroy = hostmem_pp_destroy,
+ .nl_fill = hostmem_pp_nl_fill,
+ /*
+ * .uninstall is only invoked for rxq-bound pools and is
+ * NULL-checked.
+ */
+};
+
+/* Provider drain callback: fires once a delivery pool has no inflight pages. */
+static void knod_pass_drained(void *arg)
+{
+ struct knod_dev *knodev = arg;
+
+ if (atomic_dec_and_test(&knodev->pp_live))
+ complete(&knodev->pp_drained);
+}
+
+/*
+ * Create one GPU->host delivery page_pool per RX queue, backed by a single
+ * accel-allocated GTT buffer. Mirrors the spsc-pool setup: alloc_mem() hands
+ * back the buffer (kaddr/gaddr/pages/priv); the framework owns the page_pools
+ * and the drain barrier. The hostmem provider hands out the real GTT pages
+ * with their device address as dma_addr, so the worker's SDMA lands directly
+ * in the page that later becomes the skb frag. page_pool inflight accounting
+ * then keeps the buffer alive until every delivered skb has drained.
+ */
+static int knod_pass_attach(struct knod_dev *knodev)
+{
+ struct page_pool_params pp = {
+ .order = 0,
+ .pool_size = KNOD_PASS_SLOTS,
+ .nid = NUMA_NO_NODE,
+ .dev = knodev->netdev->dev.parent,
+ .dma_dir = DMA_FROM_DEVICE,
+ .max_len = PAGE_SIZE,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV |
+ PP_FLAG_CUSTOM_MEMORY_PROVIDER,
+ .mp_ops = &page_pool_hostmem_ops,
+ };
+ unsigned int nqueues = min(knodev->netdev->num_rx_queues,
+ KNOD_SPSC_MAX);
+ struct page **pages;
+ void *pass_priv;
+ u64 base_gaddr;
+ size_t total;
+ void *kaddr;
+ int qi;
+
+ /* Accels without GPU memory simply run without zero-copy delivery. */
+ if (!knodev->accel_ops->alloc_mem)
+ return 0;
+
+ total = nqueues * KNOD_PASS_SLOTS * PAGE_SIZE;
+ kaddr = knodev->accel_ops->alloc_mem(knodev, total, &base_gaddr, &pages,
+ &pass_priv);
+ if (!kaddr) {
+ pr_err("%s: delivery alloc_mem failed\n", __func__);
+ return -ENOMEM;
+ }
+ if (!pages) {
+ knodev->accel_ops->free_mem(knodev, pass_priv);
+ return -ENOMEM;
+ }
+
+ init_completion(&knodev->pp_drained);
+ atomic_set(&knodev->pp_live, 0);
+ spin_lock_init(&knodev->d2h_lock);
+
+ for (qi = 0; qi < nqueues; qi++) {
+ struct knod_work_priv *wpriv = &knodev->wpriv[qi];
+ int base = qi * KNOD_PASS_SLOTS;
+
+ if (spsc_init(&wpriv->pass_pending,
+ sizeof(struct knod_pass_desc),
+ KNOD_PASS_SLOTS, GFP_KERNEL))
+ goto err_destroy;
+
+ wpriv->pass_hm.pages = &pages[base];
+ wpriv->pass_hm.count = KNOD_PASS_SLOTS;
+ wpriv->pass_hm.base_addr = base_gaddr + (u64)base * PAGE_SIZE;
+ wpriv->pass_hm.freed = knod_pass_drained;
+ wpriv->pass_hm.arg = knodev;
+ pp.mp_priv = &wpriv->pass_hm;
+
+ wpriv->pass_pool = page_pool_create(&pp);
+ if (IS_ERR(wpriv->pass_pool)) {
+ wpriv->pass_pool = NULL;
+ spsc_destroy(&wpriv->pass_pending);
+ goto err_destroy;
+ }
+ atomic_inc(&knodev->pp_live);
+ }
+
+ knodev->pass_priv = pass_priv;
+ return 0;
+
+err_destroy:
+ while (qi-- > 0) {
+ spsc_destroy(&knodev->wpriv[qi].pass_pending);
+ page_pool_destroy(knodev->wpriv[qi].pass_pool);
+ knodev->wpriv[qi].pass_pool = NULL;
+ }
+ if (atomic_read(&knodev->pp_live))
+ wait_for_completion_timeout(&knodev->pp_drained,
+ msecs_to_jiffies(5000));
+ knodev->accel_ops->free_mem(knodev, pass_priv);
+ return -ENOMEM;
+}
+
+/*
+ * Drain a queue's pass_pending ring on teardown. The interface is down so no
+ * new copies are submitted; wait for each queued copy to land, then return its
+ * pages to the pool instead of delivering. Without this, page_pool_destroy()
+ * stalls on the pages a detach raced against in-flight d2h copies.
+ */
+static void knod_pass_flush(struct knod_dev *knodev, unsigned int qi)
+{
+ struct knod_work_priv *wpriv = &knodev->wpriv[qi];
+ struct page_pool *pool = wpriv->pass_pool;
+ void *ptrs[KNOD_DEFAULT_PASS_SLOTS];
+ unsigned int got, i;
+
+ if (!wpriv->pass_pending.slots || !pool)
+ return;
+
+ while (spsc_peek(&wpriv->pass_pending, ptrs,
+ KNOD_DEFAULT_PASS_SLOTS, &got) >= 0 && got) {
+ for (i = 0; i < got; i++) {
+ struct knod_pass_desc *desc = ptrs[i];
+ int spins = 1000000;
+
+ while (knodev->accel_ops->d2h_fence && spins-- &&
+ (s32)(knodev->accel_ops->d2h_fence(knodev,
+ desc->sdma_idx) - desc->fence_val) < 0)
+ cpu_relax();
+
+ WARN_ONCE(knodev->accel_ops->d2h_fence &&
+ (s32)(knodev->accel_ops->d2h_fence(knodev,
+ desc->sdma_idx) - desc->fence_val) < 0,
+ "knod: d2h fence timeout on pass flush q%u idx%u\n",
+ qi, desc->sdma_idx);
+
+ if (desc->src) {
+ struct page_pool *src_pp =
+ netmem_get_pp(desc->src);
+
+ page_pool_recycle_direct_netmem(src_pp,
+ desc->src);
+ }
+ page_pool_put_full_netmem(pool, desc->netmem, false);
+ }
+ spsc_acquire(&wpriv->pass_pending, NULL, got, NULL);
+ spsc_release_commit(&wpriv->pass_pending, got);
+ }
+}
+
+/*
+ * Tear down the delivery pools and free the backing buffer. page_pool_destroy
+ * is async, so wait for every pool to drain (knod_pass_drained) before handing
+ * the buffer back to the accel -- otherwise an inflight skb frag would outlive
+ * the BO. Idempotent: a no-op for accels that never allocated.
+ */
+static void knod_pass_detach(struct knod_dev *knodev)
+{
+ unsigned int qi;
+
+ if (!knodev->pass_priv)
+ return;
+
+ /* Iterate the full array (like the spsc teardown): destroy every pool
+ * that was created, so the drain barrier is guaranteed to reach zero.
+ */
+ for (qi = 0; qi < KNOD_SPSC_MAX; qi++) {
+ if (!knodev->wpriv[qi].pass_pool)
+ continue;
+ /* Return any queued (now-landed) d2h pages before destroying
+ * the pool, or page_pool_destroy() stalls on the inflight
+ * count a detach raced against in-flight d2h copies.
+ */
+ knod_pass_flush(knodev, qi);
+ spsc_destroy(&knodev->wpriv[qi].pass_pending);
+ page_pool_destroy(knodev->wpriv[qi].pass_pool);
+ knodev->wpriv[qi].pass_pool = NULL;
+ }
+ if (atomic_read(&knodev->pp_live))
+ wait_for_completion_timeout(&knodev->pp_drained,
+ msecs_to_jiffies(5000));
+ knodev->accel_ops->free_mem(knodev, knodev->pass_priv);
+ knodev->pass_priv = NULL;
+}
+
+static void knod_dmabuf_move_notify(struct dma_buf_attachment *attach)
+{
+}
+
+static const struct dma_buf_attach_ops knod_dmabuf_attach_ops = {
+ .allow_peer2peer = true,
+ .invalidate_mappings = knod_dmabuf_move_notify,
+};
+
+static int knod_dmabuf_attach(struct knod_dev *knodev)
+{
+ struct net_device *dev = knodev->netdev;
+ struct net_devmem_dmabuf_binding *binding;
+ struct dma_buf *dmabuf;
+ int i, err;
+
+ for (i = 0; i < min(dev->num_rx_queues, KNOD_SPSC_MAX); i++) {
+ dmabuf = knodev->wpriv[i].dmabuf;
+ if (!dmabuf)
+ continue;
+
+ /* The binding owns a dmabuf reference that
+ * __net_devmem_dmabuf_binding_free() drops on unbind, mirroring
+ * the dma_buf_get(fd) in the fd-based net_devmem_bind_dmabuf().
+ * We hand it the BO's dmabuf directly, so take that reference
+ * here -- otherwise the unbind put races the BO free's put and
+ * underflows the dmabuf file refcount.
+ */
+ get_dma_buf(dmabuf);
+ binding = __net_devmem_binding_create(dev, dev->dev.parent,
+ dmabuf, DMA_BIDIRECTIONAL,
+ &knod_dmabuf_attach_ops,
+ NULL);
+ if (IS_ERR(binding)) {
+ dma_buf_put(dmabuf);
+ err = PTR_ERR(binding);
+ goto err_unwind;
+ }
+
+ err = net_devmem_bind_dmabuf_to_queue_direct(dev, i, binding);
+ if (err) {
+ net_devmem_unbind_dmabuf_direct(binding);
+ goto err_unwind;
+ }
+
+ knodev->bindings[i] = binding;
+ }
+
+ return 0;
+
+err_unwind:
+ while (i-- > 0) {
+ if (knodev->bindings[i]) {
+ net_devmem_unbind_dmabuf_direct(knodev->bindings[i]);
+ knodev->bindings[i] = NULL;
+ }
+ }
+ return err;
+}
+
+static void knod_dmabuf_detach(struct knod_dev *knodev)
+{
+ int i;
+
+ for (i = 0; i < KNOD_SPSC_MAX; i++) {
+ if (!knodev->bindings[i])
+ continue;
+
+ net_devmem_unbind_dmabuf_direct(knodev->bindings[i]);
+ knodev->bindings[i] = NULL;
+ }
+}
+
+int knod_dev_attach(struct knod_netdev *knetdev, struct knod_accel *accel)
+{
+ struct knod_dev *knodev;
+ int err = -EINVAL, i;
+
+ if (knetdev->status == KNOD_STATUS_USED ||
+ accel->status == KNOD_STATUS_USED) {
+ pr_err("knod: %s already attached\n",
+ netdev_name(knetdev->dev));
+ return -EINVAL;
+ }
+
+ knodev = kzalloc(sizeof(struct knod_dev), GFP_KERNEL);
+ if (!knodev)
+ return -ENOMEM;
+
+ if (!try_module_get(knetdev->owner)) {
+ pr_err("knod: NIC driver for %s is unloading\n",
+ netdev_name(knetdev->dev));
+ kfree(knodev);
+ return -ENODEV;
+ }
+ if (!try_module_get(accel->owner)) {
+ pr_err("knod: accelerator driver is unloading\n");
+ module_put(knetdev->owner);
+ kfree(knodev);
+ return -ENODEV;
+ }
+
+ knetdev->accel = accel;
+ knetdev->knodev = knodev;
+ accel->knetdev = knetdev;
+ accel->knodev = knodev;
+ knodev->knetdev = knetdev;
+ knodev->accel = accel;
+ knodev->netdev = knetdev->dev;
+ knodev->accel_ops = accel->accel_ops;
+ knodev->nic_ops = knetdev->nic_ops;
+ mutex_init(&knodev->lock);
+
+ knodev->stats = netdev_alloc_pcpu_stats(struct knod_dev_stats);
+ if (!knodev->stats) {
+ err = -ENOMEM;
+ pr_err("knod: failed to allocate stats for %s\n",
+ netdev_name(knetdev->dev));
+ goto free_xdev;
+ }
+
+ knodev->wpriv = kmalloc_array(KNOD_SPSC_MAX,
+ sizeof(struct knod_work_priv),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!knodev->wpriv) {
+ pr_err("knod: failed to allocate work priv for %s\n",
+ netdev_name(knetdev->dev));
+ err = -ENOMEM;
+ goto free_percpu;
+ }
+
+ netdev_lock(knodev->netdev);
+ err = knodev->nic_ops->attach(knodev);
+ if (err) {
+ err = -ENOMEM;
+ pr_err("knod: NIC attach failed on %s\n",
+ netdev_name(knetdev->dev));
+ goto unlock;
+ }
+
+ err = knodev->accel_ops->attach(knodev);
+ if (err) {
+ err = -ENOMEM;
+ pr_err("knod: accelerator attach failed on %s\n",
+ netdev_name(knetdev->dev));
+ goto nic_detach;
+ }
+
+ {
+ unsigned int nqueues = min(knodev->netdev->num_rx_queues,
+ KNOD_SPSC_MAX);
+ unsigned int stride = ALIGN(sizeof(struct spsc_bd),
+ SMP_CACHE_BYTES);
+ unsigned int cap = roundup_pow_of_two(KNOD_SPSC_ELEMS_MAX);
+ size_t pool_size = (size_t)stride * cap;
+
+ if (knodev->accel_ops->alloc_mem) {
+ size_t total = pool_size * nqueues;
+ u64 base_gaddr;
+ void *base_pool;
+ void *pool_priv;
+
+ base_pool = knodev->accel_ops->alloc_mem(knodev, total,
+ &base_gaddr, NULL, &pool_priv);
+ if (!base_pool) {
+ err = -ENOMEM;
+ pr_err("%s: alloc_mem failed\n", __func__);
+ goto free_spsc;
+ }
+ memset(base_pool, 0, total);
+
+ /* First queue owns the BO, others reference it */
+ knodev->wpriv[0].spsc_pool_priv = pool_priv;
+ for (i = 0; i < nqueues; i++) {
+ void *pool = base_pool +
+ (unsigned long)i * pool_size;
+
+ knodev->wpriv[i].spsc_pool_gaddr =
+ base_gaddr + (u64)i * pool_size;
+ err = __spsc_init(&knodev->wpriv[i].spsc_bds,
+ sizeof(struct spsc_bd),
+ KNOD_SPSC_ELEMS_MAX, pool,
+ GFP_KERNEL);
+ if (err) {
+ pr_err("%s: spsc_init failed q%d\n",
+ __func__, i);
+ goto free_spsc;
+ }
+ }
+ } else {
+ for (i = 0; i < nqueues; i++) {
+ err = spsc_init(&knodev->wpriv[i].spsc_bds,
+ sizeof(struct spsc_bd),
+ KNOD_SPSC_ELEMS_MAX,
+ GFP_KERNEL);
+ if (err) {
+ pr_err("%s: spsc_init failed q%d\n",
+ __func__, i);
+ goto free_spsc;
+ }
+ }
+ }
+ }
+ goto spsc_done;
+
+free_spsc:
+ for (i--; i >= 0; i--)
+ spsc_destroy(&knodev->wpriv[i].spsc_bds);
+ if (knodev->wpriv[0].spsc_pool_priv)
+ knodev->accel_ops->free_mem(knodev,
+ knodev->wpriv[0].spsc_pool_priv);
+ knodev->accel_ops->detach(knodev);
+ goto nic_detach;
+spsc_done:
+
+ err = knod_pass_attach(knodev);
+ if (err) {
+ pr_err("%s: knod_pass_attach failed (%d)\n", __func__, err);
+ goto accel_detach;
+ }
+
+ err = knod_dmabuf_attach(knodev);
+ if (err) {
+ err = -ENOMEM;
+ pr_err("knod: dmabuf attach failed on %s\n",
+ netdev_name(knetdev->dev));
+ goto accel_detach;
+ }
+
+ pr_info("knod: %s attached to accel %d\n",
+ netdev_name(knodev->netdev), accel->id);
+ list_add(&knodev->list, &knod_dev_list);
+ knetdev->status = KNOD_STATUS_USED;
+ accel->status = KNOD_STATUS_USED;
+
+ if (knodev->accel_ops->xdp_ops && knodev->accel_ops->xdp_ops->init) {
+ err = knodev->accel_ops->xdp_ops->init(knodev);
+ if (err) {
+ pr_err("knod: XDP init failed on %s\n",
+ netdev_name(knetdev->dev));
+ goto xdp_err;
+ }
+ }
+
+ /*
+ * Feature offloads (BPF, IPsec) allocate their GPU resources and
+ * advertise their netdev capabilities only when the feature is
+ * selected via knod_accel_feature_set(), not at attach.
+ */
+ netdev_unlock(knodev->netdev);
+
+ if (knodev->accel_ops->mp_map) {
+ err = knodev->accel_ops->mp_map(knodev);
+ if (err) {
+ pr_err("knod: mp_map failed (%d)\n", err);
+ goto dmabuf_detach;
+ }
+ }
+
+ return err;
+
+dmabuf_detach:
+ netdev_lock(knodev->netdev);
+xdp_err:
+ list_del(&knodev->list);
+ knetdev->status = KNOD_STATUS_FREE;
+ accel->status = KNOD_STATUS_FREE;
+ knod_dmabuf_detach(knodev);
+accel_detach:
+ knod_pass_detach(knodev);
+ for (i = 0; i < KNOD_SPSC_MAX; i++)
+ spsc_destroy(&knodev->wpriv[i].spsc_bds);
+ if (knodev->wpriv[0].spsc_pool_priv)
+ knodev->accel_ops->free_mem(knodev,
+ knodev->wpriv[0].spsc_pool_priv);
+ knodev->accel_ops->detach(knodev);
+nic_detach:
+ knodev->nic_ops->detach(knodev);
+unlock:
+ netdev_unlock(knodev->netdev);
+ kfree(knodev->wpriv);
+free_percpu:
+ free_percpu(knodev->stats);
+free_xdev:
+ /* Drop the accel<->knetdev<->knodev links set above before freeing
+ * knodev, or a reader (e.g. knod_default_worker via accel->knodev)
+ * dereferences a dangling pointer after a failed attach.
+ */
+ accel->knodev = NULL;
+ accel->knetdev = NULL;
+ knetdev->knodev = NULL;
+ knetdev->accel = NULL;
+ module_put(accel->owner);
+ module_put(knetdev->owner);
+ kfree(knodev);
+ return err;
+}
+
+int knod_dev_detach(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_netdev *knetdev = knodev->knetdev;
+ int i;
+
+ if (netif_running(knodev->netdev)) {
+ pr_err("knod_dev: interface is up\n");
+ return -EINVAL;
+ }
+
+ netdev_lock(knodev->netdev);
+ /*
+ * pre_detach() runs knod_feature_stop()+knod_feature_deactivate(),
+ * which tears down whatever feature is active and frees its
+ * resources, so no per-feature teardown is needed here.
+ */
+ if (knodev->accel_ops->pre_detach)
+ knodev->accel_ops->pre_detach(knodev);
+ list_del(&knodev->list);
+ knod_dmabuf_detach(knodev);
+ knod_pass_detach(knodev);
+ for (i = 0; i < KNOD_SPSC_MAX; i++)
+ spsc_destroy(&knodev->wpriv[i].spsc_bds);
+ if (knodev->wpriv[0].spsc_pool_priv)
+ knodev->accel_ops->free_mem(knodev,
+ knodev->wpriv[0].spsc_pool_priv);
+ knodev->nic_ops->detach(knodev);
+ knodev->accel_ops->detach(knodev);
+ netdev_unlock(knodev->netdev);
+ knetdev->status = KNOD_STATUS_FREE;
+ knetdev->accel = NULL;
+ knetdev->knodev = NULL;
+ accel->knetdev = NULL;
+ accel->knodev = NULL;
+ accel->status = KNOD_STATUS_FREE;
+ kfree(knodev->wpriv);
+ free_percpu(knodev->stats);
+ kfree(knodev);
+
+ module_put(accel->owner);
+ module_put(knetdev->owner);
+ return 0;
+}
+
+static int __init knod_dev_init(void)
+{
+ return genl_register_family(&knod_nl_family);
+}
+
+core_initcall(knod_dev_init);
+
diff --git a/net/knod/knod_nl.c b/net/knod/knod_nl.c
new file mode 100644
index 000000000000..77799202abcf
--- /dev/null
+++ b/net/knod/knod_nl.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ */
+
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <net/genetlink.h>
+#include <net/knod.h>
+#include <net/sock.h>
+
+#include "knod-nl-gen.h"
+#include "knod.h"
+
+/* ---- accel ---- */
+
+static int knod_nl_accel_fill(struct knod_accel *accel,
+ struct sk_buff *rsp, const struct genl_info *info)
+{
+ u32 ena = 0, cap = 0;
+ void *hdr;
+
+ if (accel->accel_ops->feature_get)
+ accel->accel_ops->feature_get(accel, &ena, &cap);
+
+ hdr = genlmsg_iput(rsp, info);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(rsp, KNOD_A_ACCEL_ID, accel->id) ||
+ nla_put_string(rsp, KNOD_A_ACCEL_NAME, accel->name) ||
+ nla_put_u32(rsp, KNOD_A_ACCEL_TYPE, accel->type) ||
+ nla_put_u32(rsp, KNOD_A_ACCEL_FEATURE_CAP, cap) ||
+ nla_put_u32(rsp, KNOD_A_ACCEL_FEATURE_ENA, ena)) {
+ genlmsg_cancel(rsp, hdr);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(rsp, hdr);
+ return 0;
+}
+
+int knod_nl_accel_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_accel *accel;
+ struct sk_buff *rsp;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_ACCEL_ID))
+ return -EINVAL;
+
+ rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!rsp)
+ return -ENOMEM;
+
+ mutex_lock(&knod_lock);
+ accel = knod_accel_lookup(nla_get_u32(info->attrs[KNOD_A_ACCEL_ID]));
+ if (!accel) {
+ mutex_unlock(&knod_lock);
+ err = -ENODEV;
+ goto err_free;
+ }
+ err = knod_nl_accel_fill(accel, rsp, info);
+ mutex_unlock(&knod_lock);
+ if (err)
+ goto err_free;
+
+ return genlmsg_reply(rsp, info);
+
+err_free:
+ nlmsg_free(rsp);
+ return err;
+}
+
+int knod_nl_accel_get_dumpit(struct sk_buff *rsp, struct netlink_callback *cb)
+{
+ struct knod_accel *accel;
+ int idx = 0, s_idx = cb->args[0];
+
+ mutex_lock(&knod_lock);
+ list_for_each_entry(accel, &knod_accel_list, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (knod_nl_accel_fill(accel, rsp, genl_info_dump(cb)) < 0)
+ break;
+cont:
+ idx++;
+ }
+ mutex_unlock(&knod_lock);
+
+ cb->args[0] = idx;
+ return rsp->len;
+}
+
+int knod_nl_accel_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_accel *accel;
+ u32 feature;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_ACCEL_ID) ||
+ GENL_REQ_ATTR_CHECK(info, KNOD_A_ACCEL_FEATURE_ENA))
+ return -EINVAL;
+
+ feature = nla_get_u32(info->attrs[KNOD_A_ACCEL_FEATURE_ENA]);
+
+ /*
+ * rtnl serialises the feature switch against attach/detach (which
+ * also take rtnl) and against the NIC driver's interface up/down
+ * (knod_dev_start/stop), and lets feature_set touch netdev->features
+ * and run an expedited synchronize_net(). knod_lock still guards the
+ * accel list walk (lock order: rtnl -> knod_lock).
+ */
+ rtnl_lock();
+ mutex_lock(&knod_lock);
+ accel = knod_accel_lookup(nla_get_u32(info->attrs[KNOD_A_ACCEL_ID]));
+ if (!accel) {
+ err = -ENODEV;
+ goto unlock;
+ }
+ if (!accel->accel_ops->feature_set) {
+ err = -EOPNOTSUPP;
+ goto unlock;
+ }
+ err = accel->accel_ops->feature_set(accel, feature, info->extack);
+unlock:
+ mutex_unlock(&knod_lock);
+ rtnl_unlock();
+ return err;
+}
+
+/* ---- nic ---- */
+
+static int knod_nl_nic_fill(struct knod_netdev *knetdev,
+ struct sk_buff *rsp, const struct genl_info *info)
+{
+ void *hdr;
+
+ hdr = genlmsg_iput(rsp, info);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(rsp, KNOD_A_NIC_IFINDEX, knetdev->dev->ifindex) ||
+ nla_put_string(rsp, KNOD_A_NIC_NAME, netdev_name(knetdev->dev))) {
+ genlmsg_cancel(rsp, hdr);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(rsp, hdr);
+ return 0;
+}
+
+int knod_nl_nic_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_netdev *knetdev;
+ struct net_device *dev;
+ struct sk_buff *rsp;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_NIC_IFINDEX))
+ return -EINVAL;
+
+ rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!rsp)
+ return -ENOMEM;
+
+ rtnl_lock();
+ mutex_lock(&knod_lock);
+ dev = __dev_get_by_index(genl_info_net(info),
+ nla_get_u32(info->attrs[KNOD_A_NIC_IFINDEX]));
+ knetdev = dev ? knod_netdev_lookup(dev) : NULL;
+ if (!knetdev) {
+ mutex_unlock(&knod_lock);
+ rtnl_unlock();
+ err = -ENODEV;
+ goto err_free;
+ }
+ err = knod_nl_nic_fill(knetdev, rsp, info);
+ mutex_unlock(&knod_lock);
+ rtnl_unlock();
+ if (err)
+ goto err_free;
+
+ return genlmsg_reply(rsp, info);
+
+err_free:
+ nlmsg_free(rsp);
+ return err;
+}
+
+int knod_nl_nic_get_dumpit(struct sk_buff *rsp, struct netlink_callback *cb)
+{
+ struct net *net = sock_net(rsp->sk);
+ struct knod_netdev *knetdev;
+ int idx = 0, s_idx = cb->args[0];
+
+ rtnl_lock();
+ mutex_lock(&knod_lock);
+ list_for_each_entry(knetdev, &knod_netdev_list, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (dev_net(knetdev->dev) != net)
+ goto cont;
+ if (knod_nl_nic_fill(knetdev, rsp, genl_info_dump(cb)) < 0)
+ break;
+cont:
+ idx++;
+ }
+ mutex_unlock(&knod_lock);
+ rtnl_unlock();
+
+ cb->args[0] = idx;
+ return rsp->len;
+}
+
+/* ---- dev (NIC <-> accel binding) ---- */
+
+static int knod_nl_dev_fill(struct knod_dev *knodev, struct sk_buff *rsp,
+ const struct genl_info *info)
+{
+ void *hdr;
+
+ hdr = genlmsg_iput(rsp, info);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(rsp, KNOD_A_DEV_NIC_IFINDEX, knodev->netdev->ifindex) ||
+ nla_put_u32(rsp, KNOD_A_DEV_ACCEL_ID, knodev->accel->id)) {
+ genlmsg_cancel(rsp, hdr);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(rsp, hdr);
+ return 0;
+}
+
+void knod_nl_notify_dev(struct knod_dev *knodev, u32 cmd)
+{
+ struct net *net = dev_net(knodev->netdev);
+ struct genl_info info;
+ struct sk_buff *ntf;
+
+ if (!genl_has_listeners(&knod_nl_family, net, KNOD_NLGRP_MGMT))
+ return;
+
+ ntf = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!ntf)
+ return;
+
+ genl_info_init_ntf(&info, &knod_nl_family, cmd);
+ if (knod_nl_dev_fill(knodev, ntf, &info)) {
+ nlmsg_free(ntf);
+ return;
+ }
+
+ genlmsg_multicast_netns(&knod_nl_family, net, ntf, 0,
+ KNOD_NLGRP_MGMT, GFP_KERNEL);
+}
+
+int knod_nl_attach_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_netdev *knetdev;
+ struct knod_accel *accel;
+ struct net_device *dev;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_DEV_NIC_IFINDEX) ||
+ GENL_REQ_ATTR_CHECK(info, KNOD_A_DEV_ACCEL_ID))
+ return -EINVAL;
+
+ rtnl_lock();
+ mutex_lock(&knod_lock);
+
+ dev = __dev_get_by_index(genl_info_net(info),
+ nla_get_u32(
+ info->attrs[KNOD_A_DEV_NIC_IFINDEX]));
+ if (!dev) {
+ NL_SET_ERR_MSG(info->extack, "no such netdevice");
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ knetdev = knod_netdev_lookup(dev);
+ if (!knetdev) {
+ NL_SET_ERR_MSG(info->extack, "netdevice not registered with knod");
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ if (netif_running(dev)) {
+ NL_SET_ERR_MSG(info->extack, "bring the netdevice down first");
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ accel = knod_accel_lookup(
+ nla_get_u32(info->attrs[KNOD_A_DEV_ACCEL_ID]));
+ if (!accel) {
+ NL_SET_ERR_MSG(info->extack, "no such accelerator");
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ err = knod_dev_attach(knetdev, accel);
+ if (!err)
+ knod_nl_notify_dev(knetdev->knodev, KNOD_CMD_DEV_ADD_NTF);
+
+unlock:
+ mutex_unlock(&knod_lock);
+ rtnl_unlock();
+ return err;
+}
+
+int knod_nl_detach_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_dev *knodev;
+ struct net_device *dev;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_DEV_NIC_IFINDEX))
+ return -EINVAL;
+
+ rtnl_lock();
+
+ dev = __dev_get_by_index(genl_info_net(info),
+ nla_get_u32(
+ info->attrs[KNOD_A_DEV_NIC_IFINDEX]));
+ knodev = dev ? knod_dev_lookup(dev) : NULL;
+ if (!knodev) {
+ NL_SET_ERR_MSG(info->extack, "netdevice not attached");
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ if (netif_running(dev)) {
+ NL_SET_ERR_MSG(info->extack, "bring the netdevice down first");
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ knod_nl_notify_dev(knodev, KNOD_CMD_DEV_DEL_NTF);
+ err = knod_dev_detach(knodev);
+
+unlock:
+ rtnl_unlock();
+ return err;
+}
+
+int knod_nl_dev_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct knod_dev *knodev;
+ struct net_device *dev;
+ struct sk_buff *rsp;
+ int err;
+
+ if (GENL_REQ_ATTR_CHECK(info, KNOD_A_DEV_NIC_IFINDEX))
+ return -EINVAL;
+
+ rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!rsp)
+ return -ENOMEM;
+
+ rtnl_lock();
+ dev = __dev_get_by_index(genl_info_net(info),
+ nla_get_u32(
+ info->attrs[KNOD_A_DEV_NIC_IFINDEX]));
+ knodev = dev ? knod_dev_lookup(dev) : NULL;
+ if (!knodev) {
+ rtnl_unlock();
+ err = -ENODEV;
+ goto err_free;
+ }
+ err = knod_nl_dev_fill(knodev, rsp, info);
+ rtnl_unlock();
+ if (err)
+ goto err_free;
+
+ return genlmsg_reply(rsp, info);
+
+err_free:
+ nlmsg_free(rsp);
+ return err;
+}
+
+int knod_nl_dev_get_dumpit(struct sk_buff *rsp, struct netlink_callback *cb)
+{
+ struct net *net = sock_net(rsp->sk);
+ struct knod_dev *knodev;
+ int idx = 0, s_idx = cb->args[0];
+
+ rtnl_lock();
+ list_for_each_entry(knodev, &knod_dev_list, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (dev_net(knodev->netdev) != net)
+ goto cont;
+ if (knod_nl_dev_fill(knodev, rsp, genl_info_dump(cb)) < 0)
+ break;
+cont:
+ idx++;
+ }
+ rtnl_unlock();
+
+ cb->args[0] = idx;
+ return rsp->len;
+}
--
2.43.0
next prev parent reply other threads:[~2026-07-19 18:00 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 ` [RFC PATCH net-next 01/13] net: knod: add uapi and core headers Taehee Yoo
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 ` Taehee Yoo [this message]
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-5-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