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 <[email protected]>
(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 <[email protected]>
+ * Copyright (c) 2021 Hoyeon Lee <[email protected]>
+ *
+ * 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 <[email protected]>
+ * Copyright (c) 2021 Hoyeon Lee <[email protected]>
+ */
+
+#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



Reply via email to