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: 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 ` [RFC PATCH net-next 04/13] net: knod: add offload device core and control plane Taehee Yoo
2026-07-21 7:17 ` 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 ` Taehee Yoo [this message]
2026-07-21 7:17 ` [RFC PATCH net-next 07/13] drm/amdkfd: add knod provider core 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-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 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.