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: 32+ 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-21 7:17 ` sashiko-bot
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-21 7:17 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 03/13] net: core: add XDP_MODE_HW offload hook " Taehee Yoo
2026-07-21 7:17 ` sashiko-bot
2026-07-19 17:58 ` Taehee Yoo [this message]
2026-07-21 7:17 ` [RFC PATCH net-next 04/13] net: knod: add offload device core and control plane sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 05/13] bpf: offload: allow PERCPU_ARRAY maps for offloaded programs Taehee Yoo
2026-07-21 7:17 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 06/13] drm/amdkfd: prepare kfd core for the knod provider Taehee Yoo
2026-07-21 7:17 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 07/13] drm/amdkfd: add knod provider core Taehee Yoo
2026-07-21 7:17 ` sashiko-bot
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-21 7:17 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 11/13] bnxt_en: " Taehee Yoo
2026-07-21 7:18 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 12/13] selftests: drivers/net: add knod tests Taehee Yoo
2026-07-21 7:18 ` sashiko-bot
2026-07-19 17:58 ` [RFC PATCH net-next 13/13] drm/amdkfd: add IPsec full-packet offload Taehee Yoo
2026-07-21 7:18 ` sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.