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 07/13] drm/amdkfd: add knod provider core
Date: Sun, 19 Jul 2026 17:58:51 +0000 [thread overview]
Message-ID: <20260719175857.4071636-8-ap420073@gmail.com> (raw)
In-Reply-To: <20260719175857.4071636-1-ap420073@gmail.com>
Add the knod accelerator provider, built into amdgpu. It allocates and
drives GPU AQL/SDMA queues, manages GPU memory for the RX data path,
and registers a knod accelerator that binds to a NIC via the knod core.
Feature workers (BPF, IPsec) plug in on top through the accel ops.
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
(cherry picked from commit acaff825a0e87188906fd59f8a377c04d0b0ed2e)
---
drivers/gpu/drm/amd/amdgpu/Makefile | 1 +
drivers/gpu/drm/amd/amdkfd/Kconfig | 11 +
drivers/gpu/drm/amd/amdkfd/Makefile | 4 +
drivers/gpu/drm/amd/amdkfd/kfd_knod.c | 2202 ++++++++++++++++++++
drivers/gpu/drm/amd/amdkfd/knod/kfd_knod.h | 270 +++
5 files changed, 2488 insertions(+)
create mode 100644 drivers/gpu/drm/amd/amdkfd/kfd_knod.c
create mode 100644 drivers/gpu/drm/amd/amdkfd/knod/kfd_knod.h
diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile
index ba80542ead9d..7a9826df9f37 100644
--- a/drivers/gpu/drm/amd/amdgpu/Makefile
+++ b/drivers/gpu/drm/amd/amdgpu/Makefile
@@ -42,6 +42,7 @@ ccflags-y := -I$(FULL_AMD_PATH)/include/asic_reg \
-I$(FULL_AMD_DISPLAY_PATH)/dc \
-I$(FULL_AMD_DISPLAY_PATH)/amdgpu_dm \
-I$(FULL_AMD_PATH)/amdkfd \
+ -I$(FULL_AMD_PATH)/amdkfd/knod \
-I$(FULL_AMD_PATH)/ras/ras_mgr
# Locally disable W=1 warnings enabled in drm subsystem Makefile
diff --git a/drivers/gpu/drm/amd/amdkfd/Kconfig b/drivers/gpu/drm/amd/amdkfd/Kconfig
index a5d7467c2f34..d93f1af749ff 100644
--- a/drivers/gpu/drm/amd/amdkfd/Kconfig
+++ b/drivers/gpu/drm/amd/amdkfd/Kconfig
@@ -38,3 +38,14 @@ config HSA_AMD_P2P
GPUs with large memory BARs that expose the entire VRAM in PCIe bus
address space within the physical address limits of the GPUs.
+config HSA_AMD_KNOD
+ bool "KNOD GPU network offload core"
+ depends on HSA_AMD
+ default y
+ help
+ Core framework for KNOD, GPU-accelerated zero-copy network packet
+ processing on AMD GPUs. It is built into amdgpu and cannot be a
+ module because it relies on non-exported amdgpu/amdkfd internals.
+
+ Say N to drop the KNOD core along with the BPF and IPsec offloads
+ layered on top of it. If unsure, say Y.
diff --git a/drivers/gpu/drm/amd/amdkfd/Makefile b/drivers/gpu/drm/amd/amdkfd/Makefile
index 85fc67d521e5..1834faa54863 100644
--- a/drivers/gpu/drm/amd/amdkfd/Makefile
+++ b/drivers/gpu/drm/amd/amdkfd/Makefile
@@ -71,3 +71,7 @@ ifneq ($(CONFIG_HSA_AMD_SVM),)
AMDKFD_FILES += $(AMDKFD_PATH)/kfd_svm.o \
$(AMDKFD_PATH)/kfd_migrate.o
endif
+
+ifneq ($(CONFIG_HSA_AMD_KNOD),)
+AMDKFD_FILES += $(AMDKFD_PATH)/kfd_knod.o
+endif
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_knod.c b/drivers/gpu/drm/amd/amdkfd/kfd_knod.c
new file mode 100644
index 000000000000..03fff054dcb9
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_knod.c
@@ -0,0 +1,2202 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/file.h>
+#include <linux/dma-buf.h>
+#include <crypto/algapi.h>
+#include <crypto/internal/simd.h>
+#include "kfd_priv.h"
+#include "kfd_hsa.h"
+#include "kfd_knod.h"
+#include "kfd_topology.h"
+#include "kfd_device_queue_manager.h"
+#include "kfd_events.h"
+#include <crypto/skcipher.h>
+#include <crypto/internal/skcipher.h>
+#include <linux/pid.h>
+#include <linux/debugfs.h>
+#include <linux/sched/signal.h>
+#include "../amdgpu/amdgpu_amdkfd.h"
+#include "../amdgpu/amdgpu_gfx.h"
+#include "../amdgpu/./navi10_sdma_pkt_open.h"
+#include "amdgpu_dpm.h"
+#include "kgd_pp_interface.h"
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <drm/ttm/ttm_tt.h>
+#include <linux/seq_file.h>
+#include "knod_bpf.h"
+#include <net/page_pool/helpers.h>
+#include <linux/netdevice.h>
+
+struct umh_data {
+ pid_t pid;
+};
+
+LIST_HEAD(ctx_list);
+
+/*
+ * AQL/SDMA queue pair count requested by the highest-demand accel
+ * consumer (currently knod_ipsec's parallel-dispatcher machinery).
+ * Each accel module sets this via knod_request_queue_cnt() during its
+ * module_init BEFORE the NOD attach happens, so knod_attach() creates
+ * a context with enough kaql[]/sdma[] pairs for the worst-case
+ * consumer.
+ *
+ * The value is a high-water mark across all accel types - whichever
+ * consumer asks for the most queue pairs wins; a consumer that needs
+ * fewer just leaves the extra pairs unused (a minor, harmless GPU
+ * resource waste).
+ *
+ * Default is 1 when nothing has called the setter - mirrors the
+ * historical single-queue behaviour.
+ */
+static int knod_requested_queue_cnt = 1;
+
+static struct knod_accel *accels[KNOD_MAX_AQL];
+static int nr_accels;
+static struct knod_accel_ops accel_ops;
+
+/*
+ * Thin wrappers around the KFD core entry points. knod drives KFD from
+ * kernel context using kernel-internal types, so it calls these directly
+ * instead of going through the kfd_ioctl_* handlers (which marshal user
+ * data and stay static to KFD).
+ */
+static int knod_create_queue(struct kfd_process *p,
+ struct queue_properties *qp, u32 gpu_id,
+ u32 *queue_id, u64 *doorbell_offset)
+{
+ return kfd_create_queue(p, qp, gpu_id, queue_id, doorbell_offset);
+}
+
+static int knod_destroy_queue(struct kfd_process *p, u32 queue_id)
+{
+ int ret;
+
+ mutex_lock(&p->mutex);
+ ret = pqm_destroy_queue(&p->pqm, queue_id);
+ mutex_unlock(&p->mutex);
+ return ret;
+}
+
+static int knod_create_event(struct kfd_process *p, u32 event_type,
+ bool auto_reset, u32 node_id,
+ struct knod_event *event)
+{
+ u32 event_trigger_data;
+ u64 event_page_offset = 0;
+
+ return kfd_event_create(NULL, p, event_type, auto_reset, node_id,
+ &event->id, &event_trigger_data,
+ &event_page_offset, &event->slot);
+}
+
+static int knod_destroy_event(struct kfd_process *p, u32 event_id)
+{
+ return kfd_event_destroy(p, event_id);
+}
+
+struct knod_mem *__knod_alloc_mem(struct knod *knod, size_t size,
+ int flags)
+{
+ struct kfd_process_device *pdd = knod->process->pdds[0];
+ struct kfd_node *kdev = pdd->dev;
+ struct knod_mem *mem;
+ int err;
+
+ mem = kzalloc_obj(struct knod_mem, GFP_KERNEL);
+ if (!mem)
+ return ERR_PTR(-ENOMEM);
+
+ size = ALIGN(size, PAGE_SIZE);
+ mem->flags = flags;
+ mem->size = size;
+ mem->gaddr = gen_pool_alloc(knod->pool, size);
+ if (!mem->gaddr) {
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ err = amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu(kdev->adev, mem->gaddr,
+ size, pdd->drm_priv,
+ &mem->mem, NULL, flags,
+ false);
+ if (err) {
+ knod_err(" failed to alloc mem\n");
+ gen_pool_free(knod->pool, mem->gaddr, mem->size);
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ err = amdgpu_amdkfd_gpuvm_sync_memory(kdev->adev, mem->mem, true);
+ if (err) {
+ pr_debug("Sync memory failed, wait interrupted by user signal\n");
+ amdgpu_amdkfd_gpuvm_free_memory_of_gpu(kdev->adev, mem->mem,
+ pdd->drm_priv, NULL);
+ gen_pool_free(knod->pool, mem->gaddr, mem->size);
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ list_add_tail(&mem->list, &knod->active_list);
+
+ return mem;
+}
+
+int __knod_export_dma_buf(struct knod *knod, struct knod_mem *mem)
+{
+ struct dma_buf *dmabuf;
+ int err;
+
+ err = amdgpu_amdkfd_gpuvm_export_dmabuf(mem->mem, &dmabuf);
+ if (err) {
+ pr_debug("export dmabuf failed\n");
+ return -ENOMEM;
+ }
+
+ dma_buf_put(dmabuf);
+
+ return 0;
+}
+
+struct knod_mem *knod_alloc_mem(struct knod *knod, size_t size, int flags)
+{
+ struct knod_mem *mem;
+ int err;
+
+ mem = kzalloc_obj(struct knod_mem, GFP_KERNEL);
+ if (!mem)
+ return ERR_PTR(-ENOMEM);
+
+ size = ALIGN(size, PAGE_SIZE);
+ mem->flags = flags;
+ mem->size = size;
+ mem->gaddr = gen_pool_alloc(knod->pool, size);
+ if (!mem->gaddr) {
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ err = kfd_process_alloc_gpuvm(knod->process->pdds[0],
+ mem->gaddr,
+ mem->size,
+ flags,
+ &mem->mem,
+ &mem->kaddr);
+
+ if (err) {
+ knod_err(" err = %d\n", err);
+ gen_pool_free(knod->pool, mem->gaddr, mem->size);
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ list_add_tail(&mem->list, &knod->active_list);
+
+ return mem;
+}
+EXPORT_SYMBOL(knod_alloc_mem);
+
+int __knod_map_kaddr(struct knod *knod, struct knod_mem *mem)
+{
+ int err = 0;
+
+ if (mem->flags & KFD_IOC_ALLOC_MEM_FLAGS_GTT) {
+ err = amdgpu_amdkfd_gpuvm_map_gtt_bo_to_kernel(mem->mem,
+ &mem->kaddr,
+ NULL);
+ if (err) {
+ pr_debug("Map GTT BO to kernel failed\n");
+ err = -ENOMEM;
+ }
+ } else if (mem->flags & KFD_IOC_ALLOC_MEM_FLAGS_VRAM) {
+ err = amdgpu_amdkfd_gpuvm_map_vram_bo_to_kernel(mem->mem,
+ &mem->kaddr,
+ NULL);
+ if (err) {
+ pr_debug("Map VRAM BO to kernel failed\n");
+ err = -ENOMEM;
+ }
+ }
+
+ return err;
+}
+
+int __knod_map_mem(struct knod *knod, struct knod_mem *mem)
+{
+ struct kfd_process_device *pdd = knod->process->pdds[0];
+ struct kfd_node *kdev = pdd->dev;
+ int err;
+
+ err = amdgpu_amdkfd_gpuvm_map_memory_to_gpu(kdev->adev, mem->mem,
+ pdd->drm_priv);
+ if (err) {
+ knod_err(" failed to map gpu\n");
+ return 1;
+ }
+
+ err = amdgpu_amdkfd_gpuvm_sync_memory(kdev->adev, mem->mem, true);
+ if (err) {
+ pr_debug("Sync memory failed, wait interrupted by user signal\n");
+ return 1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(__knod_map_mem);
+
+void knod_free_mem(struct knod *knod, struct knod_mem *mem)
+{
+ if (mem) {
+ list_del_init(&mem->list);
+ kfd_process_free_gpuvm(mem->mem, knod->process->pdds[0],
+ &mem->kaddr);
+ gen_pool_free(knod->pool, mem->gaddr, mem->size);
+ kfree(mem);
+ }
+}
+EXPORT_SYMBOL(knod_free_mem);
+
+void knod_sdma_copy(struct knod *knod, u64 dst_offset,
+ u64 src_offset, int idx, int size)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u32 ring_mask = (sdma->sdma->size / 4) - 1;
+ u64 *wptr = (u64 *)sdma->queue->kaddr + 1;
+ u32 *ptr = sdma->sdma->kaddr;
+
+ ptr[sdma->idx++ & ring_mask] = SDMA_PKT_HEADER_OP(SDMA_OP_COPY) |
+ SDMA_PKT_HEADER_SUB_OP(SDMA_SUBOP_COPY_LINEAR) |
+ SDMA_PKT_COPY_LINEAR_HEADER_TMZ((0));
+ ptr[sdma->idx++ & ring_mask] = size - 1;
+ ptr[sdma->idx++ & ring_mask] = 0; /* src/dst endian swap */
+ ptr[sdma->idx++ & ring_mask] = lower_32_bits(src_offset);
+ ptr[sdma->idx++ & ring_mask] = upper_32_bits(src_offset);
+ ptr[sdma->idx++ & ring_mask] = lower_32_bits(dst_offset);
+ ptr[sdma->idx++ & ring_mask] = upper_32_bits(dst_offset);
+
+ *wptr += 7 * 4;
+}
+
+void knod_sdma_fence(struct knod *knod, u64 fence_addr, u32 fence_val,
+ int idx)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u32 ring_mask = (sdma->sdma->size / 4) - 1;
+ u64 *wptr = (u64 *)sdma->queue->kaddr + 1;
+ u32 *ptr = sdma->sdma->kaddr;
+ u32 hdr = SDMA_PKT_HEADER_OP(SDMA_OP_FENCE);
+
+ if (knod->isa_version >= 10)
+ hdr |= SDMA_PKT_FENCE_HEADER_MTYPE(3);
+
+ ptr[sdma->idx++ & ring_mask] = hdr;
+ ptr[sdma->idx++ & ring_mask] = lower_32_bits(fence_addr);
+ ptr[sdma->idx++ & ring_mask] = upper_32_bits(fence_addr);
+ ptr[sdma->idx++ & ring_mask] = fence_val;
+
+ *wptr += 4 * 4;
+}
+
+void knod_sdma_trap(struct knod *knod, int idx)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u32 ring_mask = (sdma->sdma->size / 4) - 1;
+ u64 *wptr = (u64 *)sdma->queue->kaddr + 1;
+ u32 *ptr = sdma->sdma->kaddr;
+ u32 ctx = knod->sdma_event[idx].id & 0x0fffffff;
+ u64 slot_addr = knod->mailbox->gaddr +
+ knod->sdma_event[idx].slot * 8;
+ u32 hdr;
+
+ /* Write 0 to the signal page slot so lookup_signaled_event
+ * sees it as non-UNSIGNALED (0xFFFFFFFFFFFFFFFF).
+ */
+ hdr = SDMA_PKT_HEADER_OP(SDMA_OP_FENCE);
+ if (knod->isa_version >= 10)
+ hdr |= SDMA_PKT_FENCE_HEADER_MTYPE(3);
+
+ ptr[sdma->idx++ & ring_mask] = hdr;
+ ptr[sdma->idx++ & ring_mask] = lower_32_bits(slot_addr);
+ ptr[sdma->idx++ & ring_mask] = upper_32_bits(slot_addr);
+ ptr[sdma->idx++ & ring_mask] = 0;
+
+ ptr[sdma->idx++ & ring_mask] = SDMA_PKT_HEADER_OP(SDMA_OP_TRAP);
+ ptr[sdma->idx++ & ring_mask] = ctx;
+
+ *wptr += 6 * 4;
+}
+
+void knod_sdma_doorbell(struct knod *knod, int idx)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u64 *wptr = (u64 *)sdma->queue->kaddr + 1;
+
+ /* ensure the SDMA ring writes are visible before ringing doorbell */
+ wmb();
+ writeq(*wptr, sdma->doorbell);
+}
+
+u32 knod_sdma_submit(struct knod *knod, int idx,
+ const struct knod_sdma_copy_desc *copies, int n)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u32 capacity = sdma->sdma->size / 4;
+ u32 completed, inflight;
+ int i;
+
+ /*
+ * knod_sdma_copy has no overflow guard (sdma->idx is the dword write
+ * cursor that just wraps), so drop the whole batch once the ring is
+ * near full. @completed is the last fenced cursor (knod_sdma_kick
+ * writes sdma->idx into the signal), so @inflight is the unprocessed
+ * span; reserve room for these @n copies (7 dwords each) plus a fence.
+ * Signed compare so a stale signal ahead of the cursor (e.g. at
+ * startup) reads as "negative" inflight rather than a false full.
+ */
+ completed = (u32)READ_ONCE(((struct amd_signal *)
+ sdma->queue_signal->kaddr)->value);
+ inflight = (u32)sdma->idx - completed;
+ if ((s32)(inflight + n * 7) >= (s32)(capacity - 64))
+ return 0;
+
+ for (i = 0; i < n; i++)
+ knod_sdma_copy(knod, copies[i].dst, copies[i].src, idx,
+ copies[i].len);
+
+ return (u32)sdma->idx;
+}
+EXPORT_SYMBOL(knod_sdma_submit);
+
+void knod_sdma_kick(struct knod *knod, int idx)
+{
+ struct knod_sdma *sdma = &knod->sdma[idx];
+ u64 fence_addr;
+
+ fence_addr = sdma->queue_signal->gaddr +
+ offsetof(struct amd_signal, value);
+ knod_sdma_fence(knod, fence_addr, (u32)sdma->idx, idx);
+ knod_sdma_doorbell(knod, idx);
+}
+EXPORT_SYMBOL(knod_sdma_kick);
+
+int knod_gart_map(struct amdgpu_device *adev, u64 npages,
+ dma_addr_t *addr, u64 *gart_addr, u64 flags)
+{
+ struct amdgpu_ring *ring =
+ to_amdgpu_ring(adev->mman.buffer_funcs_scheds[0]);
+ struct amdgpu_job *job;
+ unsigned int num_dw, num_bytes;
+ struct dma_fence *fence;
+ u64 src_addr, dst_addr;
+ u64 pte_flags;
+ void *cpu_addr;
+ int r;
+
+ /* use gart window 0 */
+ *gart_addr = adev->gmc.gart_start;
+
+ num_dw = ALIGN(adev->mman.buffer_funcs->copy_num_dw, 8);
+ num_bytes = npages * 8;
+
+ r = amdgpu_job_alloc_with_ib(adev, &adev->mman.default_entity.base,
+ AMDGPU_FENCE_OWNER_UNDEFINED,
+ num_dw * 4 + num_bytes,
+ AMDGPU_IB_POOL_DELAYED,
+ &job,
+ AMDGPU_KERNEL_JOB_ID_TTM_MAP_BUFFER);
+ if (r)
+ return r;
+
+ src_addr = num_dw * 4;
+ src_addr += job->ibs[0].gpu_addr;
+
+ dst_addr = amdgpu_bo_gpu_offset(adev->gart.bo);
+ amdgpu_emit_copy_buffer(adev, &job->ibs[0], src_addr,
+ dst_addr, num_bytes, 0);
+
+ amdgpu_ring_pad_ib(ring, &job->ibs[0]);
+ WARN_ON(job->ibs[0].length_dw > num_dw);
+
+ pte_flags = AMDGPU_PTE_VALID | AMDGPU_PTE_READABLE;
+ pte_flags |= AMDGPU_PTE_SYSTEM | AMDGPU_PTE_SNOOPED;
+ if (!(flags & KFD_IOCTL_SVM_FLAG_GPU_RO))
+ pte_flags |= AMDGPU_PTE_WRITEABLE;
+ pte_flags |= adev->gart.gart_pte_flags;
+
+ cpu_addr = &job->ibs[0].ptr[num_dw];
+
+ amdgpu_gart_map(adev, 0, npages, addr, pte_flags, cpu_addr);
+ fence = amdgpu_job_submit(job);
+ dma_fence_put(fence);
+
+ return r;
+}
+
+static void knod_init_aql_queue(struct knod *knod, int qid, int idx)
+{
+ struct amd_queue *amd_queue =
+ (struct amd_queue *)knod->kaql[idx].queue->kaddr;
+ union knod_aql_rsrc1 rsrc1;
+ u32 scratch_gaddr;
+ int i;
+
+ for (i = 0; i < knod->nr_aql_ring; i++)
+ knod_setup_invalidate(knod, i, idx);
+
+ amd_queue->hsa_queue.type = HSA_QUEUE_TYPE_MULTI;
+ amd_queue->hsa_queue.features = HSA_QUEUE_FEATURE_KERNEL_DISPATCH;
+ amd_queue->hsa_queue.base_address = (void *)knod->kaql[idx].aql->gaddr;
+ amd_queue->hsa_queue.doorbell_signal =
+ knod->kaql[idx].queue_signal->gaddr;
+ amd_queue->hsa_queue.size = knod->nr_aql_ring;
+ amd_queue->hsa_queue.reserved1 = 0;
+ amd_queue->hsa_queue.id = qid;
+
+ amd_queue->write_dispatch_id = 0; /* id is index */
+ amd_queue->group_segment_aperture_base_hi = 0;
+ amd_queue->private_segment_aperture_base_hi = 0;
+ amd_queue->max_cu_id = -1;
+ amd_queue->max_wave_id = -1;
+ amd_queue->max_legacy_doorbell_dispatch_id_plus_1 = 0;
+ amd_queue->legacy_doorbell_lock = 0;
+ amd_queue->read_dispatch_id = 0; /* id is index */
+ amd_queue->read_dispatch_id_field_base_byte_offset = 0x80;
+ /* scratch resource descriptor - use allocated scratch BO address */
+ scratch_gaddr = (u32)(knod->kaql[idx].scratch->gaddr & 0xffffffff);
+ amd_queue->scratch_resource_descriptor[0] = scratch_gaddr;
+ scratch_gaddr = (u32)((knod->kaql[idx].scratch->gaddr >> 32) & 0xffff);
+ rsrc1.base_address_hi = scratch_gaddr;
+ rsrc1.stride = 0;
+ rsrc1.cache_swizzle = 0;
+ rsrc1.swizzle_enable = 1;
+ memcpy(&amd_queue->scratch_resource_descriptor[1], &rsrc1, sizeof(u32));
+ amd_queue->scratch_resource_descriptor[2] =
+ knod->kaql[idx].scratch->size;
+ amd_queue->scratch_resource_descriptor[3] = 0x00ffffff;
+ amd_queue->scratch_backing_memory_location =
+ knod->kaql[idx].scratch->gaddr;
+ amd_queue->scratch_backing_memory_byte_size =
+ knod->kaql[idx].scratch->size;
+ amd_queue->scratch_wave64_lane_byte_size = 64;
+
+ amd_queue->queue_properties.is_ptr64 = 1;
+ amd_queue->queue_properties.enable_trap_handler_debug_sgprs = 0;
+}
+
+static void knod_init_sdma_queue(struct knod *knod, int qid, int idx)
+{
+}
+
+static void knod_init_queue(struct knod *knod, int qid, int idx, int type)
+{
+ if (type == KFD_IOC_QUEUE_TYPE_COMPUTE_AQL)
+ knod_init_aql_queue(knod, qid, idx);
+ else if (type == KFD_IOC_QUEUE_TYPE_SDMA)
+ knod_init_sdma_queue(knod, qid, idx);
+}
+
+static void stop_umh(pid_t umh_pid)
+{
+ struct pid *p = find_get_pid(umh_pid);
+
+ if (!p)
+ return;
+
+ kill_pid(p, SIGKILL, 1);
+ put_pid(p);
+}
+
+static int umh_init(struct subprocess_info *info, struct cred *new)
+{
+ struct umh_data *d = info->data;
+
+ d->pid = current->pid;
+
+ return 0;
+}
+
+static int launch_and_get_pid(void)
+{
+ static const char * const argv[] = { "/bin/sleep", "2147483647", NULL };
+ static const char * const envp[] = { "HOME=/", "PATH=/sbin:/bin", NULL };
+ struct umh_data data = { .pid = -1 };
+ struct subprocess_info *info;
+ int ret;
+
+ info = call_usermodehelper_setup(argv[0], (char **)argv,
+ (char **)envp, GFP_KERNEL,
+ umh_init, NULL, &data);
+ if (!info) {
+ pr_err("UMH setup failed\n");
+ return 0;
+ }
+
+ ret = call_usermodehelper_exec(info, UMH_WAIT_EXEC);
+ if (ret < 0) {
+ pr_err("UMH exec failed: %d\n", ret);
+ return 0;
+ }
+
+ return data.pid;
+}
+
+static int knod_set_isa(struct knod *knod)
+{
+ enum amd_asic_type asic_type;
+
+ asic_type = knod->process->pdds[0]->dev->adev->asic_type;
+ if (knod->isa_version != 9 && knod->isa_version != 10)
+ knod->isa_version = 0;
+ if (knod->isa_version == 0) {
+ if (asic_type <= CHIP_VEGAM) {
+ pr_err("Not supported chip");
+ return -EOPNOTSUPP;
+ } else if (asic_type == CHIP_VEGA10 ||
+ asic_type == CHIP_VEGA12 ||
+ asic_type == CHIP_VEGA20 ||
+ asic_type == CHIP_RAVEN ||
+ asic_type == CHIP_RENOIR) {
+ pr_debug("GCN5 is detected");
+ knod->isa_version = 9;
+ } else if (asic_type == CHIP_NAVI10 ||
+ asic_type == CHIP_NAVI12 ||
+ asic_type == CHIP_NAVI14 ||
+ asic_type == CHIP_CYAN_SKILLFISH) {
+ pr_err("RDNA1 is not supported yet");
+ return -EOPNOTSUPP;
+ } else if (asic_type == CHIP_SIENNA_CICHLID ||
+ asic_type == CHIP_NAVY_FLOUNDER ||
+ asic_type == CHIP_DIMGREY_CAVEFISH ||
+ asic_type == CHIP_BEIGE_GOBY ||
+ asic_type == CHIP_YELLOW_CARP) {
+ pr_debug("RDNA2 is detected");
+ knod->isa_version = 10;
+ } else if (asic_type == CHIP_ARCTURUS ||
+ asic_type == CHIP_ALDEBARAN) {
+ pr_err("CDNA is not supported yet");
+ return -EOPNOTSUPP;
+ } else if (asic_type == CHIP_IP_DISCOVERY) {
+ pr_err("Can't detect chip version, firmware update may be needed");
+ return -EOPNOTSUPP;
+ } else {
+ pr_err("Not supported chip");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ knod->igpu = false;
+ if (asic_type == CHIP_RENOIR || asic_type == CHIP_RAVEN ||
+ asic_type == CHIP_CYAN_SKILLFISH || asic_type == CHIP_BEIGE_GOBY ||
+ asic_type == CHIP_YELLOW_CARP) {
+ pr_debug("iGPU is detected");
+ knod->igpu = true;
+ }
+
+ return 0;
+}
+
+static void knod_destroy_one_queue(struct knod *knod, int idx)
+{
+ /* Destroy queue and event BEFORE freeing BOs.
+ * Queue holds references to BO VAs; freeing BOs first causes
+ * NULL deref in kfd_queue_unref_bo_vas when UMH exits.
+ * Use the created flag (not queue_id value) since KFD IDR can
+ * assign queue_id=0 to the first queue.
+ */
+ if (knod->aql_queue_created[idx]) {
+ int ret;
+
+ ret = knod_destroy_queue(knod->process,
+ knod->aql_queue_id[idx]);
+ if (ret)
+ pr_err("knod: destroy_queue failed: %d (queue_id=%u)\n",
+ ret, knod->aql_queue_id[idx]);
+ knod->aql_queue_created[idx] = false;
+ knod->aql_queue_id[idx] = 0;
+ }
+ if (knod->aql_event[idx].id) {
+ knod_destroy_event(knod->process, knod->aql_event[idx].id);
+ knod->aql_event[idx].id = 0;
+ }
+
+ knod_free_mem(knod, knod->kaql[idx].aql);
+ knod_free_mem(knod, knod->kaql[idx].queue);
+ knod_free_mem(knod, knod->kaql[idx].eop);
+ knod_free_mem(knod, knod->kaql[idx].ctx);
+ knod_free_mem(knod, knod->kaql[idx].queue_signal);
+ knod_free_mem(knod, knod->kaql[idx].amd_queue);
+ knod_free_mem(knod, knod->kaql[idx].scratch);
+}
+
+static int knod_alloc_one_queue(struct knod *knod, int idx,
+ struct kfd_topology_device *topo_dev,
+ struct kfd_process_device *pdd, void *ptr)
+{
+ int buf_flags = KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM;
+ int flags = KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT;
+ struct amd_signal *queue_signal;
+ struct amd_queue *amd_queue;
+ struct queue_properties qp;
+ u32 total_cwsr_size;
+ int err;
+
+ knod->kaql[idx].aql = knod_alloc_mem(knod,
+ (NR_AQL_RING *
+ sizeof(struct hsa_kernel_dispatch_packet) * 2),
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_UNCACHED |
+ KFD_IOC_ALLOC_MEM_FLAGS_AQL_QUEUE_MEM |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE);
+ if (IS_ERR(knod->kaql[idx].aql)) {
+ err = PTR_ERR(knod->kaql[idx].aql);
+ knod->kaql[idx].aql = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].queue = knod_alloc_mem(knod, PAGE_SIZE, flags);
+ if (IS_ERR(knod->kaql[idx].queue)) {
+ err = PTR_ERR(knod->kaql[idx].queue);
+ knod->kaql[idx].queue = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].eop = knod_alloc_mem(knod, PAGE_SIZE, flags);
+ if (IS_ERR(knod->kaql[idx].eop)) {
+ err = PTR_ERR(knod->kaql[idx].eop);
+ knod->kaql[idx].eop = NULL;
+ goto err_mem;
+ }
+
+ total_cwsr_size = (topo_dev->node_props.cwsr_size +
+ topo_dev->node_props.debug_memory_size)
+ * NUM_XCC(pdd->dev->xcc_mask);
+ total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE);
+
+ knod->kaql[idx].ctx = knod_alloc_mem(knod, total_cwsr_size, buf_flags);
+ if (IS_ERR(knod->kaql[idx].ctx)) {
+ err = PTR_ERR(knod->kaql[idx].ctx);
+ knod->kaql[idx].ctx = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].queue_signal = knod_alloc_mem(knod,
+ PAGE_SIZE << 5,
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE);
+ if (IS_ERR(knod->kaql[idx].queue_signal)) {
+ err = PTR_ERR(knod->kaql[idx].queue_signal);
+ knod->kaql[idx].queue_signal = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].amd_queue = knod_alloc_mem(knod,
+ PAGE_SIZE << 5,
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT);
+ if (IS_ERR(knod->kaql[idx].amd_queue)) {
+ err = PTR_ERR(knod->kaql[idx].amd_queue);
+ knod->kaql[idx].amd_queue = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].scratch = knod_alloc_mem(knod,
+ PAGE_SIZE << 5,
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT);
+ if (IS_ERR(knod->kaql[idx].scratch)) {
+ err = PTR_ERR(knod->kaql[idx].scratch);
+ knod->kaql[idx].scratch = NULL;
+ goto err_mem;
+ }
+
+ knod->kaql[idx].idx = 0;
+
+ /*
+ * KNOD dispatchers poll queue_signal->value directly. Do not attach a
+ * KFD signal event to every AQL completion; at high packet rates the
+ * unused event notifications overflow the KFD interrupt/event path and
+ * add latency without contributing to completion detection.
+ */
+ knod->aql_event[idx].id = 0;
+ knod->aql_event[idx].slot = 0;
+ knod->signal_eid = 0;
+
+ /* Zero ALL queue-related GPU memory BEFORE creating the HW queue.
+ * kfd_ioctl_create_queue loads HQD immediately - if the memory
+ * contains stale data from a previous session (write/read pointers,
+ * AQL dispatch packets, doorbell values), the GPU starts processing
+ * garbage packets and goes to 100%.
+ */
+ memset(knod->kaql[idx].queue->kaddr, 0, knod->kaql[idx].queue->size);
+ /* AQL ring is allocated 2x with AQL_QUEUE_MEM flag; the second
+ * half is a GPU-side mirror for wrap-around. kaddr only covers
+ * the first half.
+ */
+ memset(knod->kaql[idx].aql->kaddr, 0, knod->kaql[idx].aql->size / 2);
+ memset(knod->kaql[idx].eop->kaddr, 0, knod->kaql[idx].eop->size);
+ memset(knod->kaql[idx].amd_queue->kaddr, 0,
+ knod->kaql[idx].amd_queue->size);
+ knod_init_queue(knod, idx, idx, KFD_IOC_QUEUE_TYPE_COMPUTE_AQL);
+
+ memset(&qp, 0, sizeof(qp));
+ qp.type = KFD_QUEUE_TYPE_COMPUTE;
+ qp.format = KFD_QUEUE_FORMAT_AQL;
+ qp.queue_percent = 100;
+ qp.priority = 15;
+ qp.ctl_stack_size = topo_dev->node_props.ctl_stack_size;
+ qp.ctx_save_restore_area_size = topo_dev->node_props.cwsr_size;
+ qp.ctx_save_restore_area_address = (u64)knod->kaql[idx].ctx->gaddr;
+ qp.queue_address = (u64)knod->kaql[idx].aql->gaddr;
+ qp.queue_size = knod->kaql[idx].aql->size / 2;
+ qp.write_ptr =
+ (void __user *)((u64)knod->kaql[idx].queue->gaddr + 0x38);
+ qp.read_ptr = (void __user *)((u64)knod->kaql[idx].queue->gaddr + 0x80);
+ qp.eop_ring_buffer_address = (u64)knod->kaql[idx].eop->gaddr;
+ qp.eop_ring_buffer_size = knod->kaql[idx].eop->size;
+
+ err = knod_create_event(knod->process, KFD_IOC_EVENT_SIGNAL, true, 1,
+ &knod->aql_event[idx]);
+ if (err) {
+ knod_err(" failed to create AQL event[%d] err=%d\n", idx, err);
+ goto err_mem;
+ }
+
+ queue_signal = (struct amd_signal *)knod->kaql[idx].queue_signal->kaddr;
+ queue_signal->kind = AMD_SIGNAL_KIND_USER;
+ if (queue_signal->kind == AMD_SIGNAL_KIND_DOORBELL) {
+ queue_signal->value = 0;
+ queue_signal->event_id = 0;
+ queue_signal->queue_ptr = (void *)knod->kaql[idx].queue->gaddr;
+ queue_signal->hardware_doorbell_ptr = ptr;
+ queue_signal->event_mailbox_ptr = 0;
+ } else if (queue_signal->kind == AMD_SIGNAL_KIND_USER) {
+ queue_signal->event_id = knod->aql_event[idx].id;
+ queue_signal->queue_ptr = 0;
+ queue_signal->event_mailbox_ptr = knod->mailbox->gaddr +
+ knod->aql_event[idx].slot * 8;
+ /* value is in union with hardware_doorbell_ptr - set last */
+ queue_signal->value = 0xffffffffffffffff;
+ }
+ knod_dbg(" pre-create-queue signal=%lld kaddr=%p gaddr=0x%llx\n",
+ READ_ONCE(queue_signal->value), queue_signal,
+ knod->kaql[idx].queue_signal->gaddr);
+
+ err = knod_create_queue(knod->process, &qp, pdd->user_gpu_id,
+ &knod->aql_queue_id[idx],
+ &knod->aql_doorbell_offset[idx]);
+ if (err) {
+ knod_err(" failed to create queue, err = %d\n", err);
+ goto err_mem;
+ }
+ knod->aql_queue_created[idx] = true;
+
+ knod_dbg(" post-create-queue signal=%lld queue_id=%d\n",
+ READ_ONCE(queue_signal->value), knod->aql_queue_id[idx]);
+
+ /* Update queue id with the actual KFD-assigned value */
+ amd_queue = (struct amd_queue *)knod->kaql[idx].queue->kaddr;
+ amd_queue->hsa_queue.id = knod->aql_queue_id[idx];
+ return 0;
+
+err_mem:
+ if (knod->aql_event[idx].id) {
+ knod_destroy_event(knod->process, knod->aql_event[idx].id);
+ knod->aql_event[idx].id = 0;
+ }
+ knod_free_mem(knod, knod->kaql[idx].aql);
+ knod_free_mem(knod, knod->kaql[idx].queue);
+ knod_free_mem(knod, knod->kaql[idx].eop);
+ knod_free_mem(knod, knod->kaql[idx].ctx);
+ knod_free_mem(knod, knod->kaql[idx].queue_signal);
+ knod_free_mem(knod, knod->kaql[idx].amd_queue);
+ knod_free_mem(knod, knod->kaql[idx].scratch);
+ memset(&knod->kaql[idx], 0, sizeof(knod->kaql[idx]));
+ return err;
+}
+
+/*
+ * Write a minimal no-op kernel into the kernel BO so that any dispatch
+ * before a real shader (BPF/IPsec/MACsec/WG) is loaded executes a
+ * harmless s_endpgm instead of faulting on uninitialised VRAM.
+ *
+ * Layout:
+ * [0..63] kernel_descriptor (kernel_code_entry_byte_offset = 256)
+ * [256..259] s_endpgm (0xBF810000)
+ * [260..1023] s_code_end padding (GFX10 SQC prefetch safety)
+ */
+#define KNOD_DEFAULT_KD_ENTRY_OFFSET 256
+#define KNOD_S_ENDPGM 0xBF810000u
+#define KNOD_S_CODE_END 0xBF9F0000u
+
+static void knod_init_default_kernel(struct knod *knod)
+{
+ struct kernel_descriptor *kd = knod->kernels[0]->kaddr;
+ u32 *code = (u32 *)((u8 *)knod->kernels[0]->kaddr +
+ KNOD_DEFAULT_KD_ENTRY_OFFSET);
+ int i;
+
+ memset(kd, 0, sizeof(*kd));
+ kd->kernel_code_entry_byte_offset = KNOD_DEFAULT_KD_ENTRY_OFFSET;
+ kd->compute_pgm_rsrc1.granulated_workitem_vgpr_count = 0;
+ kd->compute_pgm_rsrc1.granulated_wavefront_sgpr_count = 0;
+ kd->compute_pgm_rsrc1.float_denorm_mode_32 = 3;
+ kd->compute_pgm_rsrc1.float_denorm_mode_16_64 = 3;
+ kd->compute_pgm_rsrc1.enable_dx10_clamp = 1;
+ kd->compute_pgm_rsrc1.enable_ieee_mode = 1;
+ if (knod->isa_version >= 10)
+ kd->compute_pgm_rsrc1.mem_ordered = 1;
+ kd->compute_pgm_rsrc2.enable_sgpr_workgroup_id_x = 1;
+
+ code[0] = KNOD_S_ENDPGM;
+ for (i = 1; i < (1024 - KNOD_DEFAULT_KD_ENTRY_OFFSET) / 4; i++)
+ code[i] = KNOD_S_CODE_END;
+}
+
+#define KNOD_DEFAULT_BATCH 64
+
+static struct knod_accel_xdp_ops *registered_xdp_ops;
+
+/*
+ * feature=none has no per-feature ops: the default worker stamps XDP_PASS and
+ * the NIC act handler delivers via knod_d2h_copy / knod_d2h_drain.
+ */
+static struct knod_accel_xdp_ops default_xdp_ops = {
+};
+
+static int knod_default_worker(void *arg)
+{
+ struct knod *knod = arg;
+ struct spsc_bd *bds[KNOD_DEFAULT_BATCH];
+ struct knod_dev *knodev;
+ unsigned int cnt;
+ int qi, i;
+
+ while (!kthread_should_stop()) {
+ knodev = READ_ONCE(knod->accel->knodev);
+ if (!knodev || !knodev->started) {
+ usleep_range(1000, 2000);
+ continue;
+ }
+
+ for (qi = 0; qi < knod->channels; qi++) {
+ struct knod_work_priv *wpriv = &knodev->wpriv[qi];
+
+ if (!wpriv->napi)
+ continue;
+
+ if (spsc_peek(&wpriv->spsc_bds, (void **)bds,
+ KNOD_DEFAULT_BATCH, &cnt))
+ continue;
+
+ /*
+ * feature=none has no program, so every packet passes.
+ * Stamp the verdict before advancing the consumer
+ * cursor: spsc_acquire() publishes the window with a
+ * release barrier the act handler pairs with, so the
+ * verdict has to be written first or the act handler
+ * races a POISON read. The NIC act handler does the
+ * device->host delivery via knod_d2h_copy.
+ */
+ for (i = 0; i < cnt; i++)
+ WRITE_ONCE(bds[i]->act, XDP_PASS);
+
+ spsc_acquire(&wpriv->spsc_bds, NULL, cnt, NULL);
+ knod_napi_kick(wpriv);
+ }
+
+ usleep_range(100, 200);
+ }
+ return 0;
+}
+
+static int knod_start_default_worker(struct knod *knod)
+{
+ struct task_struct *p;
+
+ knod->worker_fn = knod_default_worker;
+ knod->flush_fn = NULL;
+ knod->worker_ctx = knod;
+ p = kthread_run(knod_default_worker, knod, "knod_dflt_%d",
+ knod->accel->id);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ get_task_struct(p);
+ knod->worker = p;
+ return 0;
+}
+
+static void knod_stop_worker(struct knod *knod)
+{
+ if (!knod->worker)
+ return;
+
+ if (knod->flush_fn)
+ knod->flush_fn(knod->worker_ctx);
+
+ kthread_stop(knod->worker);
+ put_task_struct(knod->worker);
+ knod->worker = NULL;
+ knod->worker_fn = NULL;
+ knod->flush_fn = NULL;
+ knod->worker_ctx = NULL;
+}
+
+int knod_register_worker(struct knod *knod, knod_worker_fn_t fn,
+ knod_flush_fn_t flush, void *ctx)
+{
+ struct task_struct *p;
+
+ knod_stop_worker(knod);
+
+ knod->worker_fn = fn;
+ knod->flush_fn = flush;
+ knod->worker_ctx = ctx;
+ p = kthread_run(fn, ctx, "knod_%d", knod->accel->id);
+ if (IS_ERR(p)) {
+ knod->worker_fn = NULL;
+ knod->flush_fn = NULL;
+ knod->worker_ctx = NULL;
+ return PTR_ERR(p);
+ }
+
+ get_task_struct(p);
+ knod->worker = p;
+ return 0;
+}
+
+void knod_unregister_worker(struct knod *knod)
+{
+ knod_stop_worker(knod);
+ knod_start_default_worker(knod);
+}
+
+int knod_wait_on_events(struct kfd_process *p, u32 num_events,
+ void __user *data, bool all, u32 *user_timeout_ms,
+ u32 *wait_result)
+{
+ return kfd_wait_on_events_kernel(p, num_events, data, all,
+ user_timeout_ms, wait_result);
+}
+EXPORT_SYMBOL(knod_wait_on_events);
+
+static int knod_alloc_ctx_init(struct knod *knod, int id, void **doorbell,
+ struct kfd_topology_device **out_topo_dev,
+ struct kfd_process_device **out_pdd)
+{
+ struct kfd_topology_device *topo_dev;
+ struct kfd_process_device *pdd;
+ struct file *drm_file;
+ size_t mem_size;
+ char path[64];
+ int err;
+
+ sprintf(path, "/dev/dri/renderD%d", id);
+
+ drm_file = filp_open(path, O_RDWR, 0);
+ if (IS_ERR(drm_file))
+ return PTR_ERR(drm_file);
+
+ knod->drm_file = drm_file;
+
+ knod->process = kfd_create_process(knod->umh_task);
+ if (IS_ERR(knod->process)) {
+ err = PTR_ERR(knod->process);
+ goto err_filp_close;
+ }
+ kref_get(&knod->process->ref);
+
+ err = knod_set_isa(knod);
+ if (err < 0)
+ goto err_unref_process;
+
+ pdd = knod->process->pdds[0];
+
+ topo_dev = kfd_topology_device_by_id(pdd->dev->id);
+ if (!topo_dev) {
+ pr_err("knod: can't find topo_dev for dev id %u\n",
+ pdd->dev->id);
+ err = -ENODEV;
+ goto err_unref_process;
+ }
+
+ /* Acquire VM directly without installing an fd into any process's
+ * fdtable. get_file() provides the ref that pdd->drm_file will own.
+ * kfd_process_destroy_pdds will fput it during process cleanup.
+ */
+ get_file(drm_file);
+ mutex_lock(&knod->process->mutex);
+ err = kfd_process_device_init_vm(pdd, drm_file);
+ mutex_unlock(&knod->process->mutex);
+ if (err) {
+ fput(drm_file);
+ goto err_unref_process;
+ }
+
+ *doorbell = kfd_kernel_doorbell_mmap(pdd->dev, knod->process);
+ if (!*doorbell) {
+ err = -ENOMEM;
+ goto err_unref_process;
+ }
+
+ knod->pool = gen_pool_create(PAGE_SHIFT,
+ dev_to_node(pdd->dev->adev->dev));
+ if (!knod->pool) {
+ err = -ENOMEM;
+ goto err_release_doorbell;
+ }
+
+ knod->dev = pdd->dev;
+ knod->reserved_addr = pdd->gpuvm_base << 2;
+ knod->limit_addr = pdd->gpuvm_limit;
+ mem_size = knod->limit_addr - knod->reserved_addr;
+
+ err = gen_pool_add(knod->pool, knod->reserved_addr, mem_size,
+ dev_to_node(pdd->dev->adev->dev));
+ if (err)
+ goto err_gen_pool_destroy;
+
+ knod->kernels[0] = knod_alloc_mem(knod, PAGE_SIZE << 10,
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE);
+ if (IS_ERR(knod->kernels[0])) {
+ err = PTR_ERR(knod->kernels[0]);
+ knod->kernels[0] = NULL;
+ goto err_gen_pool_destroy;
+ }
+ knod_init_default_kernel(knod);
+
+ /*
+ * Second dispatch slot for the BPF ping-pong swap, allocated right
+ * after knod->kernels[0] so both kernel BOs sit in the same low VA
+ * region.
+ * Allocating it later (at feature activate, past the RX buffers) put it
+ * at a high VA, and switching the dispatch kernel_object to that BO
+ * mid-stream wedged the compute queue.
+ */
+ knod->kernels[1] = knod_alloc_mem(knod, PAGE_SIZE << 10,
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE);
+ if (IS_ERR(knod->kernels[1])) {
+ err = PTR_ERR(knod->kernels[1]);
+ knod->kernels[1] = NULL;
+ goto err_free_kernel;
+ }
+
+ knod->mailbox = knod_alloc_mem(knod, PAGE_SIZE << 5,
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM);
+ if (IS_ERR(knod->mailbox)) {
+ err = PTR_ERR(knod->mailbox);
+ knod->mailbox = NULL;
+ goto err_free_kernel;
+ }
+
+ *out_topo_dev = topo_dev;
+ *out_pdd = pdd;
+ return 0;
+
+err_free_kernel:
+ if (knod->kernels[1])
+ knod_free_mem(knod, knod->kernels[1]);
+ knod->kernels[1] = NULL;
+ knod_free_mem(knod, knod->kernels[0]);
+ knod->kernels[0] = NULL;
+err_gen_pool_destroy:
+ gen_pool_destroy(knod->pool);
+ knod->pool = NULL;
+err_release_doorbell:
+ iounmap(*doorbell);
+ *doorbell = NULL;
+err_unref_process:
+ kfd_unref_process(knod->process);
+err_filp_close:
+ fput(knod->drm_file);
+ return err;
+}
+
+struct knod *knod_alloc_ctx(struct knod_dev *knodev, int queue_cnt, int id,
+ int channels)
+{
+ int buf_flags = KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM;
+ struct kfd_topology_device *topo_dev;
+ struct kfd_process_device *pdd;
+ struct process_queue_node *pqn;
+ struct knod_work_priv *wpriv;
+ struct task_struct *task;
+ unsigned int sdma_cnt;
+ void *ptr = NULL;
+ struct knod *knod;
+ struct knod_mem *buf;
+ struct pid *spid;
+ struct queue *q;
+ int err, size;
+ pid_t pid;
+ int idx;
+
+ pid = launch_and_get_pid();
+ if (!pid) {
+ knod_err(" failed to create UMH\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ spid = find_get_pid(pid);
+ if (!spid) {
+ knod_err(" failed to find pid\n");
+ return ERR_PTR(-ESRCH);
+ }
+
+ task = get_pid_task(spid, PIDTYPE_PID);
+ put_pid(spid);
+ if (!task) {
+ knod_err(" no task found\n");
+ return ERR_PTR(-ESRCH);
+ }
+
+ knod = kzalloc(sizeof(struct knod), GFP_KERNEL);
+ if (!knod) {
+ put_task_struct(task);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ knod->queue_cnt = queue_cnt;
+ knod->sdma_cnt = queue_cnt;
+ knod->channels = channels;
+ knod->umh_pid = pid;
+ knod->umh_task = task;
+ knod->nr_aql_ring = NR_AQL_RING;
+ INIT_LIST_HEAD(&knod->list);
+ INIT_LIST_HEAD(&knod->active_list);
+
+ err = knod_alloc_ctx_init(knod, id, &ptr, &topo_dev, &pdd);
+ if (err)
+ goto err_free_knod;
+
+ sdma_cnt = topo_dev->node_props.num_sdma_engines *
+ topo_dev->node_props.num_sdma_queues_per_engine;
+ if (!sdma_cnt)
+ sdma_cnt = queue_cnt;
+ knod->sdma_cnt = min_t(int, queue_cnt, sdma_cnt);
+ if (!knod->sdma_cnt)
+ knod->sdma_cnt = 1;
+ pr_info("knod: AQL queues=%d SDMA queues=%d channels=%d\n",
+ queue_cnt, knod->sdma_cnt, channels);
+
+ knod->doorbell_base = ptr;
+
+ knod->buf = kmalloc_array(channels, sizeof(struct knod_mem *),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!knod->buf) {
+ err = -ENOMEM;
+ goto err_free_mailbox;
+ }
+
+ if (knod->igpu)
+ size = PAGE_SIZE << MAX_PAGE_ORDER;
+ else
+ size = PAGE_SIZE << 14;
+
+ for (idx = 0; idx < channels; idx++) {
+ wpriv = &knodev->wpriv[idx];
+ buf = __knod_alloc_mem(knod, size, buf_flags);
+ if (IS_ERR(buf)) {
+ err = PTR_ERR(buf);
+ goto err_free_bufs;
+ }
+ if (__knod_export_dma_buf(knod, buf)) {
+ knod_free_mem(knod, buf);
+ err = -ENOMEM;
+ goto err_free_bufs;
+ }
+ if (__knod_map_kaddr(knod, buf)) {
+ knod_free_mem(knod, buf);
+ err = -ENOMEM;
+ goto err_free_bufs;
+ }
+ if (__knod_map_mem(knod, buf)) {
+ knod_free_mem(knod, buf);
+ err = -ENOMEM;
+ goto err_free_bufs;
+ }
+ wpriv->dmabuf = buf->mem->dmabuf;
+ wpriv->index = idx;
+ knod->buf[idx] = buf;
+ }
+
+ /*
+ * GPU->host delivery buffer + per-queue page_pools are owned by the
+ * NOD framework (knod_pass_attach), allocated via accel_ops->alloc_mem.
+ */
+
+ memset(knod->mailbox->kaddr, 0, knod->mailbox->size);
+
+ err = kfd_event_page_set(knod->process,
+ knod->mailbox->kaddr,
+ KFD_SIGNAL_EVENT_LIMIT * 8,
+ knod->mailbox->gaddr);
+ if (err < 0)
+ goto err_free_bufs;
+
+ for (idx = 0; idx < queue_cnt; idx++) {
+ err = knod_alloc_one_queue(knod, idx, topo_dev, pdd, ptr);
+ if (err)
+ goto err_free_queues;
+ }
+
+ /* Allocate only the SDMA queues the device can actually provide. BPF
+ * XDP_TX does not use SDMA for the verdict path, while PASS/d2h maps RX
+ * queues onto the available SDMA queues modulo sdma_cnt.
+ */
+ for (idx = 0; idx < knod->sdma_cnt; idx++) {
+ struct amd_signal *sdma_signal;
+ struct queue_properties qp;
+ long *sdma_lptr;
+ int sdma_flags = KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT;
+
+ knod->sdma[idx].sdma = knod_alloc_mem(knod, PAGE_SIZE << 4,
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_UNCACHED |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE);
+ if (IS_ERR(knod->sdma[idx].sdma)) {
+ err = PTR_ERR(knod->sdma[idx].sdma);
+ knod->sdma[idx].sdma = NULL;
+ goto err_free_sdma;
+ }
+ knod->sdma[idx].queue = knod_alloc_mem(knod, PAGE_SIZE,
+ sdma_flags);
+ if (IS_ERR(knod->sdma[idx].queue)) {
+ err = PTR_ERR(knod->sdma[idx].queue);
+ knod->sdma[idx].queue = NULL;
+ goto err_free_sdma;
+ }
+ knod->sdma[idx].queue_signal = knod_alloc_mem(knod,
+ PAGE_SIZE << 5,
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE);
+ if (IS_ERR(knod->sdma[idx].queue_signal)) {
+ err = PTR_ERR(knod->sdma[idx].queue_signal);
+ knod->sdma[idx].queue_signal = NULL;
+ goto err_free_sdma;
+ }
+ knod->sdma[idx].idx = 0;
+
+ err = knod_create_event(knod->process, KFD_IOC_EVENT_SIGNAL,
+ true, 1, &knod->sdma_event[idx]);
+ if (err) {
+ knod_err(" failed to create SDMA event[%d] err=%d\n",
+ idx, err);
+ goto err_free_sdma;
+ }
+
+ memset(&qp, 0, sizeof(qp));
+ qp.type = KFD_QUEUE_TYPE_SDMA;
+ qp.format = KFD_QUEUE_FORMAT_PM4;
+ qp.queue_percent = 100;
+ qp.priority = 15;
+ qp.queue_address = (u64)knod->sdma[idx].sdma->gaddr;
+ qp.queue_size = knod->sdma[idx].sdma->size;
+ qp.write_ptr = (void __user *)
+ ((u64)knod->sdma[idx].queue->gaddr + 0x08);
+ qp.read_ptr = (void __user *)
+ ((u64)knod->sdma[idx].queue->gaddr + 0x10);
+
+ sdma_signal = (struct amd_signal *)
+ knod->sdma[idx].queue_signal->kaddr;
+ sdma_signal->kind = AMD_SIGNAL_KIND_USER;
+ sdma_signal->event_id = knod->sdma_event[idx].id;
+ sdma_signal->queue_ptr = 0;
+ sdma_signal->event_mailbox_ptr = knod->mailbox->gaddr +
+ (knod->sdma_event[idx].slot * 8);
+ /* SDMA fence writes u32 to low 32 bits of value.
+ * Init to 0 (not -1 like AQL) so comparison works.
+ * value is in union with hardware_doorbell_ptr - set last.
+ */
+ sdma_signal->value = 0;
+ sdma_lptr = knod->mailbox->kaddr + (idx * 8);
+ sdma_lptr[knod->sdma_event[idx].slot] = 0;
+
+ err = knod_create_queue(knod->process, &qp, pdd->user_gpu_id,
+ &knod->sdma_queue_id[idx],
+ &knod->sdma_doorbell_offset[idx]);
+ if (err) {
+ knod_err(" failed to create SDMA queue[%d] err=%d\n",
+ idx, err);
+ goto err_free_sdma;
+ }
+ knod->sdma_queue_created[idx] = true;
+
+ knod_init_queue(knod, knod->sdma_queue_id[idx], idx,
+ KFD_IOC_QUEUE_TYPE_SDMA);
+ knod->sdma[idx].doorbell =
+ (u64 *)((u8 *)ptr +
+ (u32)knod->sdma_doorbell_offset[idx]);
+ knod_dbg(" SDMA queue[%d] created: id=%d doorbell_offset=0x%x doorbell=%p\n",
+ idx, knod->sdma_queue_id[idx],
+ (u32)knod->sdma_doorbell_offset[idx],
+ knod->sdma[idx].doorbell);
+ }
+
+ list_for_each_entry(pqn, &knod->process->pqm.queues,
+ process_queue_list) {
+ if (!pqn->q)
+ continue;
+ q = pqn->q;
+ for (idx = 0; idx < queue_cnt; idx++) {
+ if (q->properties.queue_id == idx) {
+ knod->kaql[idx].doorbell =
+ (u64 *)((u8 *)ptr +
+ (u32)knod->aql_doorbell_offset[idx]);
+ q->properties.doorbell_ptr =
+ knod->kaql[idx].doorbell;
+ knod_dbg(" doorbell: idx=%d qid=%d door_off=0x%x doorbell=%p\n",
+ idx, q->properties.queue_id,
+ (u32)knod->aql_doorbell_offset[idx],
+ knod->kaql[idx].doorbell);
+ }
+ }
+ }
+
+ list_add_tail(&knod->list, &ctx_list);
+
+ /* Create shared debugfs directory under dri/<N>/knod/ */
+ {
+ struct drm_minor *minor = adev_to_drm(pdd->dev->adev)->render;
+
+ if (minor && minor->debugfs_root) {
+ struct dentry *dir;
+
+ dir = debugfs_lookup("knod", minor->debugfs_root);
+ if (!dir) {
+ dir = debugfs_create_dir("knod",
+ minor->debugfs_root);
+ if (IS_ERR(dir))
+ dir = NULL;
+ }
+ knod->debug_dir = dir;
+ }
+ }
+
+ return knod;
+
+err_free_sdma:
+ {
+ int j;
+
+ for (j = idx; j >= 0; j--) {
+ if (knod->sdma_queue_created[j]) {
+ knod_destroy_queue(knod->process,
+ knod->sdma_queue_id[j]);
+ knod->sdma_queue_created[j] = false;
+ }
+ if (knod->sdma_event[j].id) {
+ knod_destroy_event(knod->process,
+ knod->sdma_event[j].id);
+ knod->sdma_event[j].id = 0;
+ }
+ knod_free_mem(knod, knod->sdma[j].sdma);
+ knod_free_mem(knod, knod->sdma[j].queue);
+ knod_free_mem(knod, knod->sdma[j].queue_signal);
+ }
+ }
+ idx = queue_cnt;
+err_free_queues:
+ while (idx-- > 0)
+ knod_destroy_one_queue(knod, idx);
+err_free_bufs:
+ for (idx = 0; idx < channels; idx++)
+ knod_free_mem(knod, knod->buf[idx]);
+ kfree(knod->buf);
+err_free_mailbox:
+ knod_free_mem(knod, knod->mailbox);
+ knod_free_mem(knod, knod->kernels[1]);
+ knod_free_mem(knod, knod->kernels[0]);
+ gen_pool_destroy(knod->pool);
+ iounmap(ptr);
+ kfd_unref_process(knod->process);
+ fput(knod->drm_file);
+err_free_knod:
+ put_task_struct(knod->umh_task);
+ stop_umh(knod->umh_pid);
+ kfree(knod);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(knod_alloc_ctx);
+
+void knod_release_ctx(struct knod *knod)
+{
+ int idx;
+
+ list_del(&knod->list);
+
+ debugfs_remove_recursive(knod->debug_dir);
+
+ /* Release SDMA queues - destroy queue/event BEFORE freeing BOs.
+ * Same ordering as knod_destroy_one_queue() for AQL: the queue
+ * holds references to BO VAs, so freeing BOs first causes the
+ * queue destroy to fail and leaves internal BOs in kfd_bo_list.
+ */
+ for (idx = 0; idx < knod->sdma_cnt; idx++) {
+ knod_destroy_queue(knod->process, knod->sdma_queue_id[idx]);
+ knod_destroy_event(knod->process, knod->sdma_event[idx].id);
+ knod_free_mem(knod, knod->sdma[idx].sdma);
+ knod_free_mem(knod, knod->sdma[idx].queue);
+ knod_free_mem(knod, knod->sdma[idx].queue_signal);
+ }
+
+ for (idx = 0; idx < knod->queue_cnt; idx++)
+ knod_destroy_one_queue(knod, idx);
+
+ for (idx = 0; idx < knod->channels; idx++)
+ knod_free_mem(knod, knod->buf[idx]);
+ kfree(knod->buf);
+
+ knod_free_mem(knod, knod->mailbox);
+ knod_free_mem(knod, knod->kernels[1]);
+ knod_free_mem(knod, knod->kernels[0]);
+ gen_pool_destroy(knod->pool);
+ iounmap(knod->doorbell_base);
+
+ /* Tear down the KFD process. It holds 3 refs:
+ * ref 1: "open" ref from kfd_create_process (normally dropped
+ * by kfd_release when /dev/kfd is closed - KNOD never
+ * opens /dev/kfd so we must drop this ourselves)
+ * ref 2: KNOD's explicit kref_get in knod_alloc_ctx
+ * ref 3: mmu_notifier alloc_notifier ref (dropped by
+ * free_notifier callback via SRCU after mmu_notifier_put)
+ *
+ * kfd_process_notifier_release_internal removes from hash,
+ * destroys queues, and calls mmu_notifier_put which schedules
+ * the SRCU callback to drop ref 3. We drop refs 1 and 2 here.
+ * After SRCU fires, ref reaches 0 -> kfd_process_wq_release
+ * runs (sysfs removal, BO/PDD/doorbell/event cleanup, kfree).
+ *
+ * kfd_process_destroy_pdds fput's pdd->drm_file (the get_file
+ * ref from init_vm), bringing the DRM file ref from 2->1.
+ * The file/VM stays alive until we fput the filp_open ref below.
+ */
+ kfd_process_notifier_release_internal(knod->process);
+ kfd_unref_process(knod->process);
+ kfd_unref_process(knod->process);
+ mmu_notifier_synchronize();
+ kfd_process_flush_wq();
+
+ /* Drop the filp_open ref (file ref 1->0). kfd_process_destroy_pdds
+ * already fput'd the get_file ref during wq_release above.
+ */
+ fput(knod->drm_file);
+ flush_delayed_fput();
+
+ stop_umh(knod->umh_pid);
+ put_task_struct(knod->umh_task);
+ kfree(knod);
+}
+EXPORT_SYMBOL(knod_release_ctx);
+
+/* ---- feature control (knod genetlink) ---- */
+
+static void *knod_feature_ops(enum knod_feature feat)
+{
+ switch (feat) {
+ case KNOD_FEATURE_BPF:
+ return registered_xdp_ops;
+ case KNOD_FEATURE_IPSEC:
+ return accel_ops.ipsec_ops;
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * Feature lifecycle across three independent axes:
+ * init/exit attach/detach permanent per-attach state
+ * activate/deactivate feature select feature GPU resources
+ * start/stop interface up/down worker + GPU in-flight drain
+ *
+ * The worker only does useful work while (interface up AND a feature is
+ * selected), so a feature switch is bracketed stop -> change -> start: the
+ * worker is stopped and its GPU dispatch drained before deactivate() frees
+ * the resources it used, then restarted afterwards. start/stop never touch
+ * the framework/NIC-owned RX SPSC ring (mlx5 co-owns it via rq->knodev), so
+ * they are safe to run while the interface is up and traffic is flowing.
+ */
+static void knod_feature_stop(struct knod *knod)
+{
+ struct knod_dev *knodev = knod->accel->knodev;
+
+ knod_stop_worker(knod);
+
+ switch (knod->active_feature) {
+ case KNOD_FEATURE_BPF:
+ if (registered_xdp_ops && registered_xdp_ops->stop)
+ registered_xdp_ops->stop(knodev);
+ break;
+ case KNOD_FEATURE_IPSEC:
+ if (accel_ops.ipsec_ops && accel_ops.ipsec_ops->stop)
+ accel_ops.ipsec_ops->stop(knodev);
+ break;
+ default:
+ break;
+ }
+}
+
+static void knod_feature_start(struct knod *knod)
+{
+ struct knod_dev *knodev = knod->accel->knodev;
+
+ switch (knod->active_feature) {
+ case KNOD_FEATURE_BPF:
+ if (registered_xdp_ops && registered_xdp_ops->start)
+ registered_xdp_ops->start(knodev);
+ break;
+ case KNOD_FEATURE_IPSEC:
+ if (accel_ops.ipsec_ops && accel_ops.ipsec_ops->start)
+ accel_ops.ipsec_ops->start(knodev);
+ break;
+ default:
+ knod_start_default_worker(knod);
+ break;
+ }
+}
+
+static void knod_feature_deactivate(struct knod *knod)
+{
+ struct knod_dev *knodev = knod->accel->knodev;
+
+ switch (knod->active_feature) {
+ case KNOD_FEATURE_BPF:
+ /*
+ * Phase 1: unregister the offload dev - this force-frees any
+ * user XDP progs/maps still bound. The map-free ndo routes
+ * back through accel_ops.xdp_ops->xdp_install, so xdp_ops must
+ * still point at the BPF ops (and priv must be alive) here.
+ */
+ if (registered_xdp_ops &&
+ registered_xdp_ops->xdp_offload_uninit)
+ registered_xdp_ops->xdp_offload_uninit(knodev);
+ /*
+ * Phase 2: repoint RX delivery at the default drain_pass and
+ * wait out the in-flight NAPI readers.
+ */
+ WRITE_ONCE(accel_ops.xdp_ops, &default_xdp_ops);
+ synchronize_net();
+ knod_stop_worker(knod);
+ if (registered_xdp_ops && registered_xdp_ops->deactivate)
+ registered_xdp_ops->deactivate(knodev);
+ break;
+ case KNOD_FEATURE_IPSEC:
+ /*
+ * knod_ipsec_detach() clears the netdev xfrm flags and runs
+ * ->deactivate(), which NULLs ipsec_priv and does its own
+ * synchronize_net() before freeing.
+ */
+ knod_ipsec_detach(knodev);
+ break;
+ default:
+ break;
+ }
+}
+
+static int knod_feature_activate(struct knod *knod)
+{
+ struct knod_dev *knodev = knod->accel->knodev;
+ int err;
+
+ switch (knod->active_feature) {
+ case KNOD_FEATURE_BPF:
+ if (!registered_xdp_ops)
+ return -ENODEV;
+ /* Phase A: GPU compute buffers. */
+ if (registered_xdp_ops->activate) {
+ err = registered_xdp_ops->activate(knodev);
+ if (err)
+ return err;
+ }
+ /*
+ * Publish xdp_ops, then phase B: register the offload dev so
+ * user XDP progs/maps can bind (their install/free route
+ * through accel_ops.xdp_ops->xdp_install).
+ */
+ WRITE_ONCE(accel_ops.xdp_ops, registered_xdp_ops);
+ if (registered_xdp_ops->xdp_offload_init) {
+ err = registered_xdp_ops->xdp_offload_init(knodev);
+ if (err) {
+ WRITE_ONCE(accel_ops.xdp_ops, &default_xdp_ops);
+ synchronize_net();
+ knod_stop_worker(knod);
+ if (registered_xdp_ops->deactivate)
+ registered_xdp_ops->deactivate(knodev);
+ return err;
+ }
+ }
+ return 0;
+ case KNOD_FEATURE_IPSEC:
+ if (!accel_ops.ipsec_ops)
+ return -ENODEV;
+ /* knod_ipsec_attach() runs ->activate() + sets netdev flags. */
+ return knod_ipsec_attach(knodev);
+ case KNOD_FEATURE_NONE:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int knod_accel_feature_get(struct knod_accel *accel,
+ u32 *ena, u32 *cap)
+{
+ struct knod *knod = READ_ONCE(accel->priv);
+ u32 mask = 0;
+ int i;
+
+ /* A registered-but-not-attached accel has no context yet. */
+ *ena = knod ? knod->active_feature : KNOD_FEATURE_NONE;
+ for (i = 0; i < KNOD_FEATURE_MAX; i++)
+ if (i == KNOD_FEATURE_NONE || knod_feature_ops(i))
+ mask |= BIT(i);
+ *cap = mask;
+ return 0;
+}
+
+static int knod_accel_feature_set(struct knod_accel *accel, u32 feature,
+ struct netlink_ext_ack *extack)
+{
+ struct knod *knod = READ_ONCE(accel->priv);
+ struct knod_dev *knodev;
+ bool started;
+ int err = 0;
+
+ if (feature >= KNOD_FEATURE_MAX) {
+ NL_SET_ERR_MSG(extack, "unknown feature");
+ return -EINVAL;
+ }
+ if (feature != KNOD_FEATURE_NONE && !knod_feature_ops(feature)) {
+ NL_SET_ERR_MSG(extack, "feature not available on this accelerator");
+ return -ENOENT;
+ }
+ knodev = knod ? READ_ONCE(knod->accel->knodev) : NULL;
+ if (!knod || !knodev) {
+ NL_SET_ERR_MSG(extack, "accelerator not attached to a NIC");
+ return -ENODEV;
+ }
+ if (feature == knod->active_feature)
+ return 0;
+
+ /*
+ * Refuse to leave BPF while a user XDP prog or offloaded map is still
+ * bound - tearing the offload down underneath them is unsafe (the GPU
+ * dispatch keeps running against freed state). The user must detach
+ * first, e.g. "ip link set dev <if> xdp off".
+ */
+ if (knod->active_feature == KNOD_FEATURE_BPF && registered_xdp_ops &&
+ registered_xdp_ops->busy && registered_xdp_ops->busy(knodev)) {
+ NL_SET_ERR_MSG(extack, "detach the XDP program/maps first");
+ return -EBUSY;
+ }
+ if (knod->active_feature == KNOD_FEATURE_IPSEC && accel_ops.ipsec_ops &&
+ accel_ops.ipsec_ops->busy && accel_ops.ipsec_ops->busy(knodev)) {
+ NL_SET_ERR_MSG(extack, "remove the offloaded xfrm SAs first");
+ return -EBUSY;
+ }
+
+ /*
+ * stop -> change -> start. The worker only runs while the interface
+ * is up; bracket the resource change with worker stop/start in that
+ * case. stop() drains the GPU in-flight so deactivate() frees safely.
+ */
+ started = READ_ONCE(knodev->started);
+ if (started)
+ knod_feature_stop(knod);
+ knod_feature_deactivate(knod);
+ knod->active_feature = KNOD_FEATURE_NONE;
+
+ if (feature != KNOD_FEATURE_NONE) {
+ knod->active_feature = feature;
+ err = knod_feature_activate(knod);
+ if (err) {
+ knod->active_feature = KNOD_FEATURE_NONE;
+ NL_SET_ERR_MSG(extack, "failed to activate feature");
+ }
+ }
+ if (started)
+ knod_feature_start(knod);
+ return err;
+}
+
+static int knod_attach(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ int render_idx = accel->id / KNOD_MAX_AQL;
+ struct net_device *netdev = knodev->netdev;
+ struct amdgpu_device *adev;
+ struct knod *knod;
+
+ {
+ int q_cnt = clamp(READ_ONCE(knod_requested_queue_cnt), 1,
+ KNOD_MAX_QUEUE_CNT);
+
+ knod = knod_alloc_ctx(knodev, q_cnt, render_idx,
+ min(netdev->num_rx_queues,
+ KNOD_SPSC_MAX));
+ }
+ if (IS_ERR(knod)) {
+ pr_err("knod: Failed to allocate context\n");
+ return -EINVAL;
+ }
+
+ accel->priv = knod;
+ knod->accel = accel;
+
+ /*
+ * Keep GFX engine out of GFXOFF while a KNOD accel is attached.
+ * On RDNA2 (tested RX6600 / gfx1032) the very first AQL dispatch
+ * after boot hangs with signal=-1 when GFXOFF is active - SMU exit
+ * from GFXOFF races with the doorbell ring and the completion
+ * signal is never decremented. Forcing GFXOFF off for the attached
+ * lifetime avoids the race entirely.
+ *
+ * Pin MCLK soft-min to HW max. KNOD's RX path is bandwidth-bound
+ * on VRAM (NIC->GPU p2pdma delivers frames directly to VRAM, then
+ * the shader streams them back out) but the individual dispatches
+ * are too short for SMU's activity monitor to react - MCLK sticks
+ * at the lowest DPM (~96 MHz on RX6600) in AUTO mode and caps
+ * throughput far below what the compute path can sustain. Switching
+ * to the COMPUTE power profile did not help on RDNA2: the COMPUTE
+ * DpmActivityMonitor coefficients tune GFX upclock aggressively
+ * but leave memory activity detection conservative.
+ *
+ * Setting soft_min via SetSoftMinByFreq is independent of
+ * pp_power_profile_mode and of power_dpm_force_performance_level,
+ * so AUTO governance stays in effect for SCLK/voltage - we get the
+ * same 30W full-throughput state the user reaches via "force
+ * performance = manual + echo 3 > pp_dpm_mclk", without the 75W
+ * voltage pin that PROFILE_PEAK imposes. Passing 0xFFFF MHz lets
+ * SMU firmware clamp to the actual hardware max.
+ */
+ adev = knod->process->pdds[0]->dev->adev;
+ amdgpu_gfx_off_ctrl_immediate(adev, false);
+ amdgpu_dpm_set_soft_freq_range(adev, PP_MCLK, 0xFFFF, 0xFFFF);
+
+ /*
+ * Attach settles in KNOD_FEATURE_NONE with no worker running. The
+ * worker is started by the NIC driver bringing the interface up
+ * (knod_dev_start -> ->dev_start), and each feature's GPU resources
+ * are allocated when the feature is selected (->activate).
+ */
+ knod->active_feature = KNOD_FEATURE_NONE;
+
+ /*
+ * Permanent per-attach feature state (e.g. the BPF bpf_offload_dev,
+ * which must outlive feature switches so user XDP progs/maps survive).
+ */
+ if (registered_xdp_ops && registered_xdp_ops->init)
+ registered_xdp_ops->init(knodev);
+ return 0;
+}
+
+static void knod_pre_detach(struct knod_dev *knodev)
+{
+ struct knod *knod = knodev->accel->priv;
+
+ /*
+ * Detach requires the interface down, so the worker is already
+ * stopped; stop again defensively, free the active feature's
+ * resources, then tear down the permanent per-attach state.
+ */
+ knod_feature_stop(knod);
+ knod_feature_deactivate(knod);
+ knod->active_feature = KNOD_FEATURE_NONE;
+
+ if (registered_xdp_ops && registered_xdp_ops->exit)
+ registered_xdp_ops->exit(knodev);
+}
+
+static void knod_dev_start_worker(struct knod_dev *knodev)
+{
+ struct knod *knod = knodev->accel->priv;
+
+ /* Interface up: start the current feature's worker. */
+ if (knod)
+ knod_feature_start(knod);
+}
+
+static void knod_dev_stop_worker(struct knod_dev *knodev)
+{
+ struct knod *knod = knodev->accel->priv;
+
+ /* Interface down: stop the worker + drain the GPU in-flight. */
+ if (knod)
+ knod_feature_stop(knod);
+}
+
+static void knod_detach(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod *knod = accel->priv;
+ struct amdgpu_device *adev = knod->process->pdds[0]->dev->adev;
+
+ knod_stop_worker(knod);
+ knod_release_ctx(knod);
+ WRITE_ONCE(accel->priv, NULL);
+
+ /*
+ * Restore default MCLK range. min=1 triggers SetSoftMinByFreq (the
+ * API skips the call when min==0) and SMU clamps to HW min; max
+ * 0xFFFF clamps to HW max. Together this matches the pre-attach
+ * "no soft constraint" state so DPM can idle MCLK back down.
+ */
+ amdgpu_dpm_set_soft_freq_range(adev, PP_MCLK, 1, 0xFFFF);
+ amdgpu_gfx_off_ctrl(adev, true);
+}
+
+static void *knod_accel_alloc_mem(struct knod_dev *knodev, size_t size,
+ u64 *gaddr, struct page ***pages, void **priv)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod *knod = accel->priv;
+ struct knod_mem *mem;
+ struct ttm_tt *tt;
+ u32 flags;
+
+ /* SPSC rings are hot producer/consumer control data. Keep them plain
+ * GTT; only host-read delivery buffers need coherent CPU visibility.
+ */
+ flags = KFD_IOC_ALLOC_MEM_FLAGS_GTT | KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE;
+ if (pages)
+ flags |= KFD_IOC_ALLOC_MEM_FLAGS_COHERENT;
+
+ mem = knod_alloc_mem(knod, size, flags);
+ if (IS_ERR(mem))
+ return NULL;
+
+ if (pages) {
+ tt = mem->mem->bo ? mem->mem->bo->tbo.ttm : NULL;
+ if (!tt || !tt->pages) {
+ knod_free_mem(knod, mem);
+ return NULL;
+ }
+ *pages = tt->pages;
+ }
+
+ *gaddr = mem->gaddr;
+ *priv = mem;
+ return mem->kaddr;
+}
+
+static void knod_accel_free_mem(struct knod_dev *knodev, void *priv)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod *knod = accel->priv;
+ struct knod_mem *mem = priv;
+
+ knod_free_mem(knod, mem);
+}
+
+/*
+ * Device->host copy primitives for the common knod_d2h_copy/knod_d2h_drain
+ * path. Thin wrappers over the SDMA engine (sdma[0]); the framework owns the
+ * pending ring, fence counter and dst pool.
+ */
+static u32 knod_accel_d2h_submit(struct knod_dev *knodev, u64 dst, int queue,
+ u32 page_idx, u16 off, u32 len)
+{
+ struct knod *knod = knodev->accel->priv;
+ struct knod_sdma_copy_desc c;
+
+ /* The netmem page_pool dma_addr is the NIC address; SDMA needs
+ * the GPU VM address, so derive the source from buf[queue].
+ */
+ c.src = knod->buf[queue]->gaddr + ((u64)page_idx << PAGE_SHIFT) + off;
+ c.dst = dst;
+ c.len = len;
+ return knod_sdma_submit(knod, 0, &c, 1);
+}
+
+static void knod_accel_d2h_kick(struct knod_dev *knodev)
+{
+ knod_sdma_kick(knodev->accel->priv, 0);
+}
+
+static u32 knod_accel_d2h_fence(struct knod_dev *knodev, int sdma_idx)
+{
+ struct knod *knod = READ_ONCE(knodev->accel->priv);
+ struct knod_mem *signal;
+
+ if (!knod || sdma_idx < 0 || sdma_idx >= READ_ONCE(knod->sdma_cnt))
+ return 0;
+ signal = READ_ONCE(knod->sdma[sdma_idx].queue_signal);
+ if (!signal || !signal->kaddr)
+ return 0;
+
+ return (u32)READ_ONCE(((struct amd_signal *)signal->kaddr)->value);
+}
+
+static int knod_accel_mp_map(struct knod_dev *knodev)
+{
+ struct knod *knod = knodev->accel->priv;
+ int i;
+
+ if (!knod)
+ return -ENODEV;
+
+ for (i = 0; i < knod->channels; i++)
+ if (__knod_map_mem(knod, knod->buf[i]))
+ knod_err(" mp_map failed ch %d\n", i);
+
+ return 0;
+}
+
+static struct knod_accel_ops accel_ops = {
+ .attach = knod_attach,
+ .pre_detach = knod_pre_detach,
+ .detach = knod_detach,
+ .dev_start = knod_dev_start_worker,
+ .dev_stop = knod_dev_stop_worker,
+ .alloc_mem = knod_accel_alloc_mem,
+ .free_mem = knod_accel_free_mem,
+ .mp_map = knod_accel_mp_map,
+ .d2h_submit = knod_accel_d2h_submit,
+ .d2h_kick = knod_accel_d2h_kick,
+ .d2h_fence = knod_accel_d2h_fence,
+ .xdp_ops = &default_xdp_ops,
+ .feature_get = knod_accel_feature_get,
+ .feature_set = knod_accel_feature_set,
+};
+
+/*
+ * Tear a feature down on every accel still using it before its ops pointer
+ * is cleared (module unload). Mirrors the feature_set transition to NONE.
+ */
+static void knod_feature_force_none(enum knod_feature feat)
+{
+ int i;
+
+ for (i = 0; i < nr_accels; i++) {
+ struct knod_dev *knodev;
+ struct knod *knod;
+ bool started;
+
+ if (!accels[i])
+ continue;
+ knod = READ_ONCE(accels[i]->priv);
+ knodev = knod ? READ_ONCE(accels[i]->knodev) : NULL;
+ if (!knod || !knodev || knod->active_feature != feat)
+ continue;
+ started = READ_ONCE(knodev->started);
+ if (started)
+ knod_feature_stop(knod);
+ knod_feature_deactivate(knod);
+ knod->active_feature = KNOD_FEATURE_NONE;
+ if (started)
+ knod_feature_start(knod);
+ }
+}
+
+void knod_accel_xdp_register(struct knod_accel_xdp_ops *xdp_ops)
+{
+ int i;
+
+ /*
+ * Module load advertises the feature and sets up the permanent
+ * per-attach state (bpf_offload_dev) on already-attached accels;
+ * GPU compute resources wait for ->activate() on feature select.
+ */
+ WRITE_ONCE(registered_xdp_ops, xdp_ops);
+ for (i = 0; i < nr_accels; i++) {
+ struct knod_dev *knodev;
+
+ if (!accels[i])
+ continue;
+ knodev = READ_ONCE(accels[i]->knodev);
+ if (knodev && xdp_ops->init)
+ xdp_ops->init(knodev);
+ }
+}
+EXPORT_SYMBOL(knod_accel_xdp_register);
+
+void knod_accel_xdp_unregister(void)
+{
+ int i;
+
+ /* Force any active BPF feature off, then drop the permanent state. */
+ knod_feature_force_none(KNOD_FEATURE_BPF);
+ for (i = 0; i < nr_accels; i++) {
+ struct knod_dev *knodev;
+
+ if (!accels[i])
+ continue;
+ knodev = READ_ONCE(accels[i]->knodev);
+ if (knodev && registered_xdp_ops && registered_xdp_ops->exit)
+ registered_xdp_ops->exit(knodev);
+ }
+ WRITE_ONCE(registered_xdp_ops, NULL);
+}
+EXPORT_SYMBOL(knod_accel_xdp_unregister);
+
+void knod_accel_ipsec_register(struct knod_accel_ipsec_ops *ipsec_ops)
+{
+ /* Advertise only; resources are allocated by ->activate() on select. */
+ WRITE_ONCE(accel_ops.ipsec_ops, ipsec_ops);
+}
+EXPORT_SYMBOL(knod_accel_ipsec_register);
+
+void knod_accel_ipsec_unregister(void)
+{
+ knod_feature_force_none(KNOD_FEATURE_IPSEC);
+ WRITE_ONCE(accel_ops.ipsec_ops, NULL);
+}
+EXPORT_SYMBOL(knod_accel_ipsec_unregister);
+
+/*
+ * Accel modules call this from their module_init before NOD attach to
+ * request the minimum AQL/SDMA queue pair count their dispatcher
+ * infrastructure needs. The value is a high-water mark: multiple
+ * callers raise it but never lower it, so whichever module needs the
+ * most queues wins.
+ *
+ * Must be called before knod_attach() runs - i.e. before any
+ * `echo X,0 > /sys/kernel/debug/knod_dev/attach` - otherwise
+ * the already-created knod context keeps its old queue_cnt and the
+ * caller must unbind/rebind to pick up the new value.
+ *
+ * Clamped to [1, KNOD_MAX_QUEUE_CNT]. Values outside that range are ignored
+ * (the internal cap can grow later without ABI break).
+ */
+void knod_request_queue_cnt(int n)
+{
+ int cur;
+
+ if (n < 1 || n > KNOD_MAX_QUEUE_CNT)
+ return;
+
+ do {
+ cur = READ_ONCE(knod_requested_queue_cnt);
+ if (n <= cur)
+ return;
+ } while (cmpxchg(&knod_requested_queue_cnt, cur, n) != cur);
+}
+EXPORT_SYMBOL(knod_request_queue_cnt);
+
+int knod_init(struct amdgpu_device *adev)
+{
+ int base_id = adev_to_drm(adev)->render->index * KNOD_MAX_AQL;
+ struct knod_accel *accel;
+ int i, n;
+
+ n = KNOD_MAX_AQL;
+
+ for (i = 0; i < n; i++) {
+ accel = kzalloc_obj(struct knod_accel, GFP_KERNEL);
+ if (!accel) {
+ pr_err("knod: Failed to allocate accel node %d\n", i);
+ goto err;
+ }
+
+ INIT_LIST_HEAD(&accel->list);
+ accel->type = KNOD_TYPE_GPU;
+ accel->accel_ops = &accel_ops;
+ accel->owner = THIS_MODULE;
+ accel->id = base_id + i;
+ snprintf(accel->name, sizeof(accel->name), "amdgpu-%d", i);
+ knod_accel_register(accel);
+ accels[i] = accel;
+ }
+ nr_accels = n;
+
+ return 0;
+
+err:
+ while (i-- > 0) {
+ knod_accel_unregister(accels[i]);
+ kfree(accels[i]);
+ accels[i] = NULL;
+ }
+ return 0;
+}
+
+void knod_fini(struct amdgpu_device *adev)
+{
+ int base_id = adev_to_drm(adev)->render->index * KNOD_MAX_AQL;
+ int i;
+
+ for (i = 0; i < nr_accels; i++) {
+ if (!accels[i] ||
+ accels[i]->id / KNOD_MAX_AQL != base_id / KNOD_MAX_AQL)
+ continue;
+ knod_accel_unregister(accels[i]);
+ kfree(accels[i]);
+ accels[i] = NULL;
+ }
+}
+
+void knod_exit(void)
+{
+ struct knod *knod, *tmp;
+ int i;
+
+ for (i = 0; i < nr_accels; i++) {
+ if (!accels[i])
+ continue;
+ knod_accel_unregister(accels[i]);
+ kfree(accels[i]);
+ accels[i] = NULL;
+ }
+ nr_accels = 0;
+
+ list_for_each_entry_safe(knod, tmp, &ctx_list, list) {
+ knod_release_ctx(knod);
+ }
+}
diff --git a/drivers/gpu/drm/amd/amdkfd/knod/kfd_knod.h b/drivers/gpu/drm/amd/amdkfd/knod/kfd_knod.h
new file mode 100644
index 000000000000..a1eafb13a410
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdkfd/knod/kfd_knod.h
@@ -0,0 +1,270 @@
+/* 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 KFD_KNOD_H_
+#define KFD_KNOD_H_
+#include <linux/kfd_ioctl.h>
+#include <net/knod.h>
+#include <linux/genalloc.h>
+#include "kfd_hsa.h"
+#include <linux/completion.h>
+
+/*
+ * knod_dbg() is a pr_debug(), so it is off by default and toggled with
+ * dynamic debug; knod_err() always fires.
+ */
+#define knod_dbg(fmt, ...) \
+ pr_debug("knod %s:%d" fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define knod_err(fmt, ...) \
+ pr_err("knod %s:%d" fmt, __func__, __LINE__, ##__VA_ARGS__)
+
+struct page_pool;
+
+struct knod_mem {
+ struct list_head list;
+ struct kgd_mem *mem;
+ void *kaddr;
+ u32 flags;
+ u32 size;
+ u32 order;
+ u64 gaddr;
+};
+
+union knod_aql_rsrc1 {
+ struct {
+#if defined(__LITTLE_ENDIAN)
+ unsigned int base_address_hi : 16;
+ unsigned int stride : 14;
+ unsigned int cache_swizzle : 1;
+ unsigned int swizzle_enable : 1;
+#elif defined(__LITTLE_ENDIAN)
+ unsigned int swizzle_enable : 1;
+ unsigned int cache_swizzle : 1;
+ unsigned int stride : 14;
+ unsigned int base_address_hi : 16;
+#endif
+ };
+};
+
+struct knod_aql {
+ struct knod_mem *aql;
+ struct knod_mem *ctx;
+ struct knod_mem *queue;
+ struct knod_mem *scratch;
+ struct knod_mem *eop;
+ struct knod_mem *queue_signal;
+ struct knod_mem *tba;
+ struct knod_mem *tma;
+ struct knod_mem *amd_queue;
+ u64 *doorbell;
+ int idx;
+};
+
+struct knod_sdma {
+ struct knod_mem *sdma;
+ struct knod_mem *queue;
+ struct knod_mem *queue_signal;
+ u64 *doorbell;
+ int idx;
+};
+
+/* KFD event handle as knod tracks it: signal event id + its slot index. */
+struct knod_event {
+ u32 id;
+ u32 slot;
+};
+
+typedef int (*knod_worker_fn_t)(void *ctx);
+typedef void (*knod_flush_fn_t)(void *ctx);
+
+enum knod_feature {
+ KNOD_FEATURE_NONE = 0,
+ KNOD_FEATURE_BPF,
+ KNOD_FEATURE_IPSEC,
+ KNOD_FEATURE_MAX,
+};
+
+/*
+ * One accel per GPU: NIC:GPU is fixed 1:1 so a single NIC owns the whole
+ * device. The accel id is just the DRM render index (stride 1). Pipeline
+ * depth within the one accel is provided by KNOD_MAX_QUEUE_CNT HW queues,
+ * unrelated to this stride.
+ */
+#define KNOD_MAX_AQL 1
+
+/* Internal AQL/SDMA queue pairs per attached knod context. BPF can keep these
+ * queues in flight independently while the public accel-id ABI remains stable.
+ */
+#define KNOD_MAX_QUEUE_CNT 32
+
+#define NR_AQL_RING 16384
+#define AQL_STRUCT_SIZE 128
+struct knod {
+ struct list_head list;
+ struct list_head active_list;
+
+ struct gen_pool *pool;
+
+ struct hsa_kernel_dispatch_packet *dp;
+ pid_t umh_pid;
+ struct task_struct *umh_task;
+ u32 nr_aql_ring;
+ /* AQLs */
+ int queue_cnt;
+ int sdma_cnt;
+ int igpu;
+ int isa_version;
+ /* NAPIs */
+ int channels;
+ struct kfd_process *process;
+ struct mm_struct *mm;
+ struct kfd_node *dev;
+ struct file *drm_file;
+ void __iomem *doorbell_base;
+ u64 reserved_addr;
+ u64 limit_addr;
+ struct mutex lock;
+ struct hsa_event *event;
+ struct knod_mem *kernels[2]; /* dispatch slots: [0] default/pass, [1] BPF alt */
+ struct knod_mem *mailbox;
+ /* packet data path buf */
+ struct knod_mem **buf;
+
+ u32 signal_eid;
+ u32 completion_eid;
+ struct knod_aql kaql[NR_CPUS];
+ struct knod_sdma sdma[NR_CPUS];
+ struct knod_event aql_event[NR_CPUS];
+ struct knod_event sdma_event[NR_CPUS];
+ u32 aql_queue_id[NR_CPUS];
+ u32 sdma_queue_id[NR_CPUS];
+ u64 aql_doorbell_offset[NR_CPUS];
+ u64 sdma_doorbell_offset[NR_CPUS];
+ bool aql_queue_created[NR_CPUS];
+ bool sdma_queue_created[NR_CPUS];
+ struct kfd_event_data *event_data;
+ struct knod_accel *accel;
+ struct dentry *debug_dir;
+ /* Worker callback - one active worker at a time */
+ enum knod_feature active_feature;
+ knod_worker_fn_t worker_fn;
+ knod_flush_fn_t flush_fn;
+ void *worker_ctx;
+ struct task_struct *worker;
+};
+
+struct knod_dispatch_params {
+ u16 workgroup_size_x;
+ u32 grid_size_x;
+ u32 grid_size_y;
+ u32 private_segment_size;
+ u32 group_segment_size;
+ u64 kernel_object;
+ u64 kernarg_address;
+};
+
+static inline void
+knod_setup_invalidate(struct knod *knod, int idx, int q_idx)
+{
+ struct hsa_kernel_dispatch_packet *dp = knod->kaql[q_idx].aql->kaddr;
+
+ dp += idx;
+ dp->header = HSA_PACKET_TYPE_INVALID << HSA_PACKET_HEADER_TYPE;
+}
+
+static inline void
+knod_setup_dispatch(struct knod *knod, int idx,
+ const struct knod_dispatch_params *p, int q_idx)
+{
+ struct hsa_kernel_dispatch_packet *dp = knod->kaql[q_idx].aql->kaddr;
+
+ dp += idx;
+ dp->setup = 2;
+ dp->workgroup_size_x = p->workgroup_size_x;
+ dp->workgroup_size_y = 1;
+ dp->workgroup_size_z = 1;
+ dp->grid_size_x = p->grid_size_x;
+ dp->grid_size_y = p->grid_size_y;
+ dp->grid_size_z = 1;
+ dp->private_segment_size = p->private_segment_size;
+ dp->group_segment_size = p->group_segment_size;
+ dp->kernel_object = p->kernel_object;
+ dp->kernarg_address = (void *)p->kernarg_address;
+ dp->completion_signal = knod->kaql[q_idx].queue_signal->gaddr;
+ /* publish the packet body before the valid header (WRITE_ONCE below) */
+ wmb();
+ WRITE_ONCE(dp->header,
+ (HSA_PACKET_TYPE_KERNEL_DISPATCH <<
+ HSA_PACKET_HEADER_TYPE) |
+ (HSA_FENCE_SCOPE_SYSTEM <<
+ HSA_PACKET_HEADER_ACQUIRE_FENCE_SCOPE) |
+ (HSA_FENCE_SCOPE_SYSTEM <<
+ HSA_PACKET_HEADER_RELEASE_FENCE_SCOPE));
+}
+
+static inline void
+knod_setup_header(struct knod *knod,
+ const struct knod_dispatch_params *p, int q_idx)
+{
+ struct amd_queue *amd_queue = (struct amd_queue *)knod->kaql[q_idx].amd_queue->kaddr;
+ int curr_idx = knod->kaql[q_idx].idx;
+ int next_idx = curr_idx + 1;
+ u64 *ptr = knod->kaql[q_idx].doorbell;
+
+ knod_setup_invalidate(knod, next_idx % knod->nr_aql_ring, q_idx);
+ knod_setup_dispatch(knod, curr_idx % knod->nr_aql_ring, p, q_idx);
+ WRITE_ONCE(amd_queue->write_dispatch_id, curr_idx);
+ writeq(curr_idx, ptr);
+ knod->kaql[q_idx].idx = next_idx;
+}
+
+#define KNOD_NR_AQL_DEFAULT 1
+struct knod *knod_alloc_ctx(struct knod_dev *knodev, int queue_cnt, int id,
+ int channels);
+void knod_release_ctx(struct knod *knod);
+void knod_accel_xdp_register(struct knod_accel_xdp_ops *xdp_ops);
+void knod_accel_xdp_unregister(void);
+void knod_accel_ipsec_register(struct knod_accel_ipsec_ops *ipsec_ops);
+void knod_accel_ipsec_unregister(void);
+void knod_request_queue_cnt(int n);
+struct knod_mem *knod_alloc_mem(struct knod *knod, size_t size, int flags);
+struct knod_mem *__knod_alloc_mem(struct knod *knod, size_t size, int flags);
+int __knod_map_mem(struct knod *knod, struct knod_mem *mem);
+int __knod_export_dma_buf(struct knod *knod, struct knod_mem *mem);
+int __knod_map_kaddr(struct knod *knod, struct knod_mem *mem);
+void knod_free_mem(struct knod *pknod, struct knod_mem *mem);
+void knod_sdma_copy(struct knod *knod, u64 dst_gart_addr, u64 src_gart_addr,
+ int idx, int size);
+void knod_sdma_fence(struct knod *knod, u64 fence_addr, u32 fence_val,
+ int idx);
+void knod_sdma_trap(struct knod *knod, int idx);
+void knod_sdma_doorbell(struct knod *knod, int idx);
+
+/* One linear GPU->host SDMA copy (GPU VM addresses). */
+struct knod_sdma_copy_desc {
+ u64 dst;
+ u64 src;
+ u32 len;
+};
+
+/*
+ * Emit @n copies on sdma[@idx] as one batch with ring backpressure.
+ * Returns the post-batch ring position to fence/await, or 0 if the ring
+ * is too full (caller drops the whole batch). Pair with knod_sdma_kick().
+ */
+u32 knod_sdma_submit(struct knod *knod, int idx,
+ const struct knod_sdma_copy_desc *copies, int n);
+void knod_sdma_kick(struct knod *knod, int idx);
+
+int knod_gart_map(struct amdgpu_device *adev, u64 npages,
+ dma_addr_t *addr, u64 *gart_addr, u64 flags);
+int knod_register_worker(struct knod *knod, knod_worker_fn_t fn,
+ knod_flush_fn_t flush, void *ctx);
+void knod_unregister_worker(struct knod *knod);
+int knod_wait_on_events(struct kfd_process *p, u32 num_events,
+ void __user *data, bool all, u32 *user_timeout_ms,
+ u32 *wait_result);
+
+#endif /* KFD_KNOD_H_ */
--
2.43.0
next prev parent reply other threads:[~2026-07-19 18:01 UTC|newest]
Thread overview: 19+ 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-19 17:58 ` [RFC PATCH net-next 03/13] net: core: add XDP_MODE_HW offload hook " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 04/13] net: knod: add offload device core and control plane Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 05/13] bpf: offload: allow PERCPU_ARRAY maps for offloaded programs Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 06/13] drm/amdkfd: prepare kfd core for the knod provider Taehee Yoo
2026-07-19 17:58 ` Taehee Yoo [this message]
2026-07-19 17:58 ` [RFC PATCH net-next 08/13] drm/amdkfd: add GPU instruction emitter and disassembler Taehee Yoo
2026-07-20 20:05 ` Natalie Vock
2026-07-20 20:53 ` Andrew Lunn
2026-07-19 17:58 ` [RFC PATCH net-next 09/13] drm/amdkfd: add BPF-to-GPU JIT offload Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 10/13] net/mlx5e: add knod XDP offload support Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 11/13] bnxt_en: " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 12/13] selftests: drivers/net: add knod tests Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 13/13] drm/amdkfd: add IPsec full-packet offload Taehee Yoo
2026-07-20 19:18 ` [RFC PATCH net-next 00/13] net: knod: in-kernel network offload device Mina Almasry
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-8-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