From: Taehee Yoo <ap420073@gmail.com>
To: "Alex Deucher" <alexander.deucher@amd.com>,
"Alexei Starovoitov" <ast@kernel.org>,
amd-gfx@lists.freedesktop.org,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Bill Wendling" <morbo@google.com>,
bpf@vger.kernel.org, "Christian König" <christian.koenig@amd.com>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"David Airlie" <airlied@gmail.com>,
"David S. Miller" <davem@davemloft.net>,
"Donald Hunter" <donald.hunter@gmail.com>,
dri-devel@lists.freedesktop.org,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Emil Tsalapatis" <emil@etsalapatis.com>,
"Eric Dumazet" <edumazet@google.com>,
"Felix Kuehling" <Felix.Kuehling@amd.com>,
"Hoyeon Lee" <hoyeon.rhee@gmail.com>,
"Ilias Apalodimas" <ilias.apalodimas@linaro.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Jesper Dangaard Brouer" <hawk@kernel.org>,
"Jiri Olsa" <jolsa@kernel.org>,
"John Fastabend" <john.fastabend@gmail.com>,
"Justin Stitt" <justinstitt@google.com>,
"Kees Cook" <kees@kernel.org>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Leon Romanovsky" <leon@kernel.org>,
linaro-mm-sig@lists.linaro.org, linux-hardening@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-media@vger.kernel.org, linux-rdma@vger.kernel.org,
llvm@lists.linux.dev, "Mark Bloch" <mbloch@nvidia.com>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Michael Chan" <michael.chan@broadcom.com>,
"Nathan Chancellor" <nathan@kernel.org>,
netdev@vger.kernel.org,
"Nick Desaulniers" <ndesaulniers@google.com>,
"Paolo Abeni" <pabeni@redhat.com>,
"Pavan Chebbi" <pavan.chebbi@broadcom.com>,
"Saeed Mahameed" <saeedm@nvidia.com>,
"Shuah Khan" <shuah@kernel.org>,
"Simona Vetter" <simona@ffwll.ch>,
"Simon Horman" <horms@kernel.org>, "Song Liu" <song@kernel.org>,
"Stanislav Fomichev" <sdf@fomichev.me>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Taehee Yoo" <ap420073@gmail.com>,
"Tariq Toukan" <tariqt@nvidia.com>,
"Yonghong Song" <yonghong.song@linux.dev>
Subject: [RFC PATCH net-next 09/13] drm/amdkfd: add BPF-to-GPU JIT offload
Date: Sun, 19 Jul 2026 17:58:53 +0000 [thread overview]
Message-ID: <20260719175857.4071636-10-ap420073@gmail.com> (raw)
In-Reply-To: <20260719175857.4071636-1-ap420073@gmail.com>
Add the knod BPF feature: an XDP program attached in offload mode is
JIT-compiled from eBPF to an AMD GCN shader and dispatched on the GPU
against packets DMA'd into GPU memory, keeping the PASS/DROP/TX verdict
path off the host CPU. Built as a separate module (knod_bpf).
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
(cherry picked from commit 132d51819ffced59b5890e14bd39cab2e9c12eb4)
---
drivers/gpu/drm/amd/amdkfd/Kconfig | 11 +
drivers/gpu/drm/amd/amdkfd/Makefile | 2 +
drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.c | 11554 +++++++++++++++++++
drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.h | 597 +
4 files changed, 12164 insertions(+)
create mode 100644 drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.c
create mode 100644 drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.h
diff --git a/drivers/gpu/drm/amd/amdkfd/Kconfig b/drivers/gpu/drm/amd/amdkfd/Kconfig
index d93f1af749ff..708aa5fc051b 100644
--- a/drivers/gpu/drm/amd/amdkfd/Kconfig
+++ b/drivers/gpu/drm/amd/amdkfd/Kconfig
@@ -49,3 +49,14 @@ config HSA_AMD_KNOD
Say N to drop the KNOD core along with the BPF and IPsec offloads
layered on top of it. If unsure, say Y.
+
+config HSA_AMD_KNOD_BPF
+ tristate "KNOD BPF"
+ depends on HSA_AMD_KNOD
+ help
+ GPU-accelerated XDP/BPF packet processing via KNOD. BPF programs
+ attached in XDP mode are JIT-compiled to AMD GCN shaders and run
+ on the GPU against packets DMA'd directly into GPU memory, keeping
+ the verdict path (PASS/DROP/TX) off the host CPU.
+
+ If unsure, say N.
diff --git a/drivers/gpu/drm/amd/amdkfd/Makefile b/drivers/gpu/drm/amd/amdkfd/Makefile
index 1834faa54863..4df3850e1466 100644
--- a/drivers/gpu/drm/amd/amdkfd/Makefile
+++ b/drivers/gpu/drm/amd/amdkfd/Makefile
@@ -75,3 +75,5 @@ endif
ifneq ($(CONFIG_HSA_AMD_KNOD),)
AMDKFD_FILES += $(AMDKFD_PATH)/kfd_knod.o
endif
+
+obj-$(CONFIG_HSA_AMD_KNOD_BPF) += $(AMDKFD_PATH)/knod/knod_bpf.o
diff --git a/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.c b/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.c
new file mode 100644
index 000000000000..f4f48e1b9f1c
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.c
@@ -0,0 +1,11554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ */
+
+#include <linux/cpumask.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/file.h>
+#include <linux/jhash.h>
+#include <drm/ttm/ttm_tt.h>
+#include <net/page_pool/helpers.h>
+#include "kfd_priv.h"
+#include "kfd_hsa.h"
+#include "knod_bpf.h"
+#include "kfd_migrate.h"
+#include "kfd_events.h"
+#include "kfd_device_queue_manager.h"
+#include <linux/reciprocal_div.h>
+#include <linux/jhash.h>
+#include <net/knod.h>
+#include <net/netdev_rx_queue.h>
+
+/*+--------+---------+-------+------+--+-----+------+------+--------+
+ *| v0-v21 | v22-v59 |v60-v61| v62 |63|64-65|66-67 |68-69 | v70-127|
+ *+--------+---------+-------+------+--+-----+------+------+--------+
+ *|BPF REGS|TMP REGS |CTX REG| WIDX |R |DATA |D_END |PGBASE|PKTCACHE|
+ *+--------+---------+-------+------+--+-----+------+------+--------+
+ *+-----------------+
+ *| v128-v255 |
+ *+-----------------+
+ *| BPF STACK(512B) |
+ *+-----------------+
+ */
+
+/* Temp register map
+ *+-------------+-------------+---------------+---------------+
+ *|TREG0 - TREG2|TREG3 - TREG9|TREG10 - TREG16|TREG17 - TREG18|
+ *+-------------+-------------+---------------+---------------+
+ *| General Use | Key cache A | Key in MAP | JHASH Temp Reg|
+ *+-------------+-------------+---------------+---------------+
+ * Available Key cache size is 56.
+ * So, key size of map can't be exceed 56B.
+ */
+
+#define KNOD_AMDGPU_VREG0_LO 0 /* v0 */
+#define KNOD_AMDGPU_VREG0_HI 1
+#define KNOD_AMDGPU_VREG1_LO 2
+#define KNOD_AMDGPU_VREG1_HI 3
+#define KNOD_AMDGPU_VREG2_LO 4
+#define KNOD_AMDGPU_VREG2_HI 5
+#define KNOD_AMDGPU_VREG3_LO 6
+#define KNOD_AMDGPU_VREG3_HI 7
+#define KNOD_AMDGPU_VREG4_LO 8
+#define KNOD_AMDGPU_VREG4_HI 9
+#define KNOD_AMDGPU_VREG5_LO 10
+#define KNOD_AMDGPU_VREG5_HI 11
+#define KNOD_AMDGPU_VREG6_LO 12
+#define KNOD_AMDGPU_VREG6_HI 13
+#define KNOD_AMDGPU_VREG7_LO 14
+#define KNOD_AMDGPU_VREG7_HI 15
+#define KNOD_AMDGPU_VREG8_LO 16
+#define KNOD_AMDGPU_VREG8_HI 17
+#define KNOD_AMDGPU_VREG9_LO 18
+#define KNOD_AMDGPU_VREG9_HI 19
+#define KNOD_AMDGPU_FRAME_POINTER_VREG_LO 20 /* v20 */
+#define KNOD_AMDGPU_FRAME_POINTER_VREG_HI 21 /* v20 */
+
+#define KNOD_AMDGPU_TMP_VREG0_LO 22
+#define KNOD_AMDGPU_TMP_VREG0_HI 23
+#define KNOD_AMDGPU_TMP_VREG1_LO 24
+#define KNOD_AMDGPU_TMP_VREG1_HI 25
+#define KNOD_AMDGPU_TMP_VREG2_LO 26
+#define KNOD_AMDGPU_TMP_VREG2_HI 27
+#define KNOD_AMDGPU_TMP_VREG3_LO 28
+#define KNOD_AMDGPU_TMP_VREG3_HI 29
+#define KNOD_AMDGPU_TMP_VREG4_LO 30
+#define KNOD_AMDGPU_TMP_VREG4_HI 31
+#define KNOD_AMDGPU_TMP_VREG5_LO 32
+#define KNOD_AMDGPU_TMP_VREG5_HI 33
+#define KNOD_AMDGPU_TMP_VREG6_LO 34
+#define KNOD_AMDGPU_TMP_VREG6_HI 35
+#define KNOD_AMDGPU_TMP_VREG7_LO 36
+#define KNOD_AMDGPU_TMP_VREG7_HI 37
+#define KNOD_AMDGPU_TMP_VREG8_LO 38
+#define KNOD_AMDGPU_TMP_VREG8_HI 39
+#define KNOD_AMDGPU_TMP_VREG9_LO 40
+#define KNOD_AMDGPU_TMP_VREG9_HI 41
+#define KNOD_AMDGPU_TMP_VREG10_LO 42
+#define KNOD_AMDGPU_TMP_VREG10_HI 43
+#define KNOD_AMDGPU_TMP_VREG11_LO 44
+#define KNOD_AMDGPU_TMP_VREG11_HI 45
+#define KNOD_AMDGPU_TMP_VREG12_LO 46
+#define KNOD_AMDGPU_TMP_VREG12_HI 47
+#define KNOD_AMDGPU_TMP_VREG13_LO 48
+#define KNOD_AMDGPU_TMP_VREG13_HI 49
+#define KNOD_AMDGPU_TMP_VREG14_LO 50
+#define KNOD_AMDGPU_TMP_VREG14_HI 51
+#define KNOD_AMDGPU_TMP_VREG15_LO 52
+#define KNOD_AMDGPU_TMP_VREG15_HI 53
+#define KNOD_AMDGPU_TMP_VREG16_LO 54
+#define KNOD_AMDGPU_TMP_VREG16_HI 55
+#define KNOD_AMDGPU_TMP_VREG17_LO 56
+#define KNOD_AMDGPU_TMP_VREG17_HI 57
+#define KNOD_AMDGPU_TMP_VREG18_LO 58
+#define KNOD_AMDGPU_TMP_VREG18_HI 59
+#define KNOD_AMDGPU_TMP_VREG_MAX KNOD_AMDGPU_TMP_VREG18_HI
+#define KNOD_AMDGPU_CTX_VREG_LO 60
+#define KNOD_AMDGPU_CTX_VREG_HI 61
+#define KNOD_AMDGPU_IDX_VREG 62
+#define KNOD_AMDGPU_RESERVED 63
+/*
+ * After prologue step 4, IDX_VREG is no longer needed.
+ * v62:v63 are repurposed to hold slot_addr (spsc_bd GTT address)
+ * through BPF execution and into the epilogue.
+ *
+ * BACKLOG_IDX_VREG (v58) saves the backlog index from IDX_VREG
+ * before step 6 overwrites it. Used in epilogue for XDP_PASS.
+ */
+#define KNOD_AMDGPU_BACKLOG_IDX_VREG KNOD_AMDGPU_TMP_VREG18_LO /* v58 */
+#define KNOD_AMDGPU_SLOT_VREG_LO KNOD_AMDGPU_IDX_VREG /* v62 */
+#define KNOD_AMDGPU_SLOT_VREG_HI KNOD_AMDGPU_RESERVED /* v63 */
+/*
+ * DATA/DATA_END VGPRs: hold packet gaddr and end address.
+ * Set in prologue, read by BPF ctx->data / ctx->data_end accesses.
+ * Replaces GTT round-trip (prologue store -> BPF load).
+ */
+#define KNOD_AMDGPU_DATA_VREG_LO 64
+#define KNOD_AMDGPU_DATA_VREG_HI 65
+#define KNOD_AMDGPU_DATA_END_VREG_LO 66
+#define KNOD_AMDGPU_DATA_END_VREG_HI 67
+#define KNOD_AMDGPU_PAGE_BASE_VREG_LO 68
+#define KNOD_AMDGPU_PAGE_BASE_VREG_HI 69
+#define KNOD_AMDGPU_PKT_CACHE_VREG0 70
+#define KNOD_AMDGPU_PKT_CACHE_VREG_MAX 127 /* 0 ~ 127 vgprs are available */
+#define KNOD_AMDGPU_STACK_VREG0 128
+#define KNOD_AMDGPU_STACK_VREG_MAX 255 /* 128 ~ 255 vgprs are available */
+
+#define KNOD_BPF_PROG_BUF_SIZE 32768
+
+/* Index for r64.
+ * r64[TREG64_0]
+ */
+#define TREG64_0 0
+#define TREG64_1 1
+#define TREG64_2 2
+#define TREG64_3 3
+#define KEY_IN_PKT_64 TREG64_3
+#define TREG64_4 4
+#define TREG64_5 5
+#define TREG64_6 6
+#define TREG64_7 7
+#define TREG64_8 8
+#define TREG64_9 9
+#define TREG64_10 10
+#define KEY_IN_MAP_64 TREG64_10
+#define TREG64_11 11
+#define TREG64_12 12
+#define TREG64_13 13
+#define TREG64_14 14
+#define TREG64_15 15
+#define TREG64_16 16
+#define TREG64_17 17
+#define TREG64_18 18
+
+#define MAX_MAP_KEY_SIZE 56
+
+/* Index for r32.
+ * r32[TREG32_0_LO]
+ */
+#define TREG32_0_LO 0
+#define TREG32_0_HI 1
+#define TREG32_1_LO 2
+#define TREG32_1_HI 3
+#define TREG32_2_LO 4
+#define TREG32_2_HI 5
+#define TREG32_3_LO 6
+#define KEY_IN_PKT_32 TREG32_3_LO
+#define TREG32_3_HI 7
+#define TREG32_4_LO 8
+#define TREG32_4_HI 9
+#define TREG32_5_LO 10
+#define TREG32_5_HI 11
+#define TREG32_6_LO 12
+#define TREG32_6_HI 13
+#define TREG32_7_LO 14
+#define TREG32_7_HI 15
+#define TREG32_8_LO 16
+#define TREG32_8_HI 17
+#define TREG32_9_LO 18
+#define TREG32_9_HI 19
+#define TREG32_10_LO 20
+#define KEY_IN_MAP_32 TREG32_10_LO
+#define TREG32_10_HI 21
+#define TREG32_11_LO 22
+#define TREG32_11_HI 23
+#define TREG32_12_LO 24
+#define TREG32_12_HI 25
+#define TREG32_13_LO 26
+#define TREG32_13_HI 27
+#define TREG32_14_LO 28
+#define TREG32_14_HI 29
+#define TREG32_15_LO 30
+#define TREG32_15_HI 31
+#define TREG32_16_LO 32
+#define TREG32_16_HI 33
+#define TREG32_17_LO 34
+#define TREG32_17_HI 35
+#define TREG32_18_LO 36
+#define TREG32_18_HI 37
+#define TREG32_MAX TREG32_18_HI
+
+/*+--------+--------------------------------------+---+---+
+ *| s[0:3] |s[4:5] s[6:7] s[8:9] s[10:11] s[12:13]|s14|s15|
+ *+--------+--------------------------------------+---+---+
+ *| PSB | USER SGPRs (disp/queue/karg/id/flat) |WGX|QID|
+ *+--------+--------------------------------------+---+---+
+ *+----------------+------+---+---+------+-------+-------------------+
+ *| s[16:27] |s28:29|s30|s31|s32:33|s34:35 | s[36:105] |
+ *+----------------+------+---+---+------+-------+-------------------+
+ *|TMP_SREG 0-5 |PARAM |FP | - | GFX9 | DONE | EXEC_SAVE PAIRS |
+ *|(6 x 64-bit) |SREG | | |BROKE!| MASK | (max 35, GFX10) |
+ *+----------------+------+---+---+------+-------+-------------------+
+ * Implicit: VCC = s[106:107] EXEC = s[126:127]
+ *
+ * user_sgpr_count=14, same on GFX9 and GFX10.
+ * enable_sgpr_private_segment_size is disabled so that workgroup_id_y
+ * lands at s15 and TMP_SREG0_LO stays at s16 (keeps 64-bit SGPR pair
+ * alignment; avoids shifting the entire TMP/PARAM/FRAME layout).
+ */
+#define KNOD_AMDGPU_PSB_SREG 0 /* s[0:3] private_segment_buffer */
+#define KNOD_AMDGPU_DISPATCH_PTR_SREG 4 /* s[4:5] dispatch_ptr */
+#define KNOD_AMDGPU_ARG_SREG 4 /* alias for dispatch_ptr */
+#define KNOD_AMDGPU_QUEUE_PTR_SREG 6 /* s[6:7] queue_ptr */
+#define KNOD_AMDGPU_KERNARG_PTR_SREG 8 /* s[8:9] kernarg_segment_ptr */
+#define KNOD_AMDGPU_DISPATCH_ID_SREG 10 /* s[10:11] dispatch_id */
+#define KNOD_AMDGPU_FLAT_SCR_INIT_SREG 12 /* s[12:13] flat_scratch_init */
+#define KNOD_AMDGPU_WORKGROUP_ID_X_SREG 14 /* s14 workgroup_id_x */
+#define KNOD_AMDGPU_WORKGROUP_ID_Y_SREG 15 /* s15 workgroup_id_y = queue_id */
+#define KNOD_AMDGPU_TMP_SREG0_LO 16
+#define KNOD_AMDGPU_TMP_SREG0_HI 17
+#define KNOD_AMDGPU_TMP_SREG1_LO 18
+#define KNOD_AMDGPU_TMP_SREG1_HI 19
+#define KNOD_AMDGPU_TMP_SREG2_LO 20
+#define KNOD_AMDGPU_TMP_SREG2_HI 21
+#define KNOD_AMDGPU_TMP_SREG3_LO 22
+#define KNOD_AMDGPU_TMP_SREG3_HI 23
+#define KNOD_AMDGPU_TMP_SREG4_LO 24
+#define KNOD_AMDGPU_TMP_SREG4_HI 25
+#define KNOD_AMDGPU_TMP_SREG5_LO 26
+#define KNOD_AMDGPU_TMP_SREG5_HI 27
+#define KNOD_AMDGPU_PARAM_SREG_LO 28 /* s28 */
+#define KNOD_AMDGPU_PARAM_SREG_HI 29 /* s29 */
+#define KNOD_AMDGPU_FRAME_POINTER_SREG 30 /* s30 */
+
+/* Structurized CFG: EXEC mask save/restore SGPRs.
+ * done_mask tracks lanes that have reached BPF_EXIT.
+ * exec_save pairs store EXEC at branch points for restore at merge points.
+ * GFX9: s[0:101] addressable (102 SGPRs), GFX10: s[0:105] (106 SGPRs).
+ * NOTE: s[32:33] is corrupted by GFX9 hardware - do NOT use on GFX9.
+ * GFX10 uses s[32:33] for done_mask and starts exec_save at s[34].
+ */
+/* Common SGPR special register indices (same on GFX9 and GFX10) */
+#define AMDGCN_SREG_VCC_LO 106
+#define AMDGCN_SREG_EXEC_LO 126
+#define AMDGCN_SREG_INTEGER_0 128
+#define AMDGCN_SREG_INTEGER_1 129
+
+/* s[34:35] - must not overlap TMP_SREGs */
+#define KNOD_AMDGPU_DONE_MASK_SREG 34
+#define KNOD_AMDGPU_EXEC_SAVE_SREG_BASE 36 /* s[36:37], s[38:39], ... */
+#define KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX9 100
+#define KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX10 104
+/* exec_save can fill up to each ISA's top usable SGPR pair. GFX9 lays the
+ * initial in-bounds EXEC snapshot immediately after the pairs a program
+ * actually uses, so small programs keep the old 64-SGPR occupancy window.
+ * GFX10 keeps the original high fixed snapshot pair.
+ */
+#define KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX9 99
+#define KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX10 103
+#define KNOD_AMDGPU_MAX_EXEC_SAVE_PAIRS_GFX9 \
+ ((KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX9 - KNOD_AMDGPU_EXEC_SAVE_SREG_BASE + 1) / 2)
+#define KNOD_AMDGPU_MAX_EXEC_SAVE_PAIRS_GFX10 \
+ ((KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX10 - KNOD_AMDGPU_EXEC_SAVE_SREG_BASE + 1) / 2)
+
+static u8 knod_bpf_gfx9_sgpr_granule(unsigned int sgprs_used)
+{
+ if (sgprs_used <= 16)
+ return 0;
+
+ return 2 * (DIV_ROUND_UP(sgprs_used, 16) - 1);
+}
+
+unsigned int knod_bpf_workgroups = KNOD_BPF_WORKGROUPS_DEFAULT;
+MODULE_PARM_DESC(workgroups, "Workgroup size, multiple of 64, Min(64) Default/Max(256)");
+module_param_named(workgroups, knod_bpf_workgroups, int, 0600);
+
+unsigned int knod_bpf_expire = KNOD_BPF_EXPIRE_DEFAULT;
+MODULE_PARM_DESC(queue_expire, "Queue expire time(ms), Min(1), Default(10), Max(1000)");
+module_param_named(queue_expire, knod_bpf_expire, int, 0600);
+
+unsigned int knod_bpf_pkt_cache;
+MODULE_PARM_DESC(packet_cache, "Use packet cache, 0=Off(Default), 1=On");
+module_param_named(packet_cache, knod_bpf_pkt_cache, int, 0600);
+
+unsigned int knod_bpf_wave32;
+MODULE_PARM_DESC(wave32, "Use wave32 0=Off(Default), 1=On");
+module_param_named(wave32, knod_bpf_wave32, int, 0600);
+
+#define KNOD_EA(extack, msg) NL_SET_ERR_MSG_MOD((extack), msg)
+
+DEFINE_STATIC_KEY_FALSE(knod_stats_key);
+
+static const u32 bl_bounds[KNOD_BL_BUCKETS - 1] = {
+ 16, 64, 256, 1024, 4096, 8192, 16384
+};
+
+static const char * const lat_labels[] = {
+ "< 1us", "1-2us", "2-4us", "4-8us", "8-16us",
+ "16-32us", "32-64us", "64-128us", "128-256us", ">= 256us",
+};
+
+static const char * const bl_labels[] = {
+ "1-16", "17-64", "65-256", "257-1K",
+ "1K-4K", "4K-8K", "8K-16K", ">= 16K",
+};
+
+static LIST_HEAD(priv_list);
+struct amdgcn_param64 r64[20], sr64[6], p64[4], bpf_reg64[11];
+struct amdgcn_param32 r32[40]; /* last two is CTX */
+struct amdgcn_param32 stack[128];
+struct amdgcn_param32 pkt_cache[64];
+
+struct amdgcn_label {
+ struct knod_insn_meta *meta;
+ int insn_idx;
+};
+
+struct amdgcn_branch_fixup {
+ struct amdgcn_label *target_label;
+ struct knod_insn_meta *meta;
+ int insn_idx;
+};
+
+struct knod_accel_xdp_ops accel_xdp_ops;
+
+static int knod_prog_prepare_insns(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog);
+static int knod_bpf_worker(void *arg);
+static void knod_bpf_drain_worker(struct knod_bpf_priv *priv);
+static void knod_prog_free(struct knod_prog *knod_prog);
+static void knod_emit_pass_addr_store(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta);
+static void knod_setup_bpf_prog(struct bpf_prog *prog);
+
+static void knod_bpf_gpu_mem_fence(struct knod_bpf_priv *priv)
+{
+ if (!priv)
+ return;
+
+ /* drain the WC store buffer before the GPU reads the map */
+ wmb();
+}
+
+static unsigned int knod_bpf_active_rxq_count(struct net_device *netdev)
+{
+ unsigned int nr_rxq;
+
+ if (!netdev)
+ return 0;
+
+ nr_rxq = READ_ONCE(netdev->real_num_rx_queues);
+ if (!nr_rxq)
+ nr_rxq = netdev->num_rx_queues;
+
+ return min_t(unsigned int, nr_rxq, KNOD_SPSC_MAX);
+}
+
+static void knod_bpf_fill_dispatch(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw,
+ struct knod_dispatch_params *p)
+{
+ struct knod_bpf_param *param = sqw->param->kaddr;
+
+ p->workgroup_size_x = knod_bpf_workgroups;
+ p->grid_size_x = priv->batch_size;
+ p->grid_size_y = param->nr_queues;
+ p->private_segment_size = 8192;
+ p->group_segment_size = 8192;
+ p->kernel_object =
+ (u64)priv->knod->kernels[READ_ONCE(priv->active_idx)]->gaddr;
+ p->kernarg_address = sqw->param->gaddr;
+}
+
+static void debug_kernel_descriptor(struct kernel_descriptor *kernel_code)
+{
+ knod_jit_dbg(" kernel_code->group_segment_fixed_size = %d\n",
+ kernel_code->group_segment_fixed_size);
+ knod_jit_dbg(" kernel_code->private_segment_fixed_size = %d\n",
+ kernel_code->private_segment_fixed_size);
+ knod_jit_dbg(" kernel_code->kernarg_size = %d\n",
+ kernel_code->kernarg_size);
+ knod_jit_dbg(" kernel_code->kernel_code_entry_byte_offset = %lld\n",
+ kernel_code->kernel_code_entry_byte_offset);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc3.accum_offset = %d\n",
+ kernel_code->compute_pgm_rsrc3.accum_offset);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc3.reserved0 = %d\n",
+ kernel_code->compute_pgm_rsrc3.reserved0);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc3.tg_split = %d\n",
+ kernel_code->compute_pgm_rsrc3.tg_split);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc3.reserved1 = %d\n",
+ kernel_code->compute_pgm_rsrc3.reserved1);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.granulated_workitem_vgpr_count = %d\n",
+ kernel_code->compute_pgm_rsrc1.granulated_workitem_vgpr_count);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.granulated_wavefront_sgpr_count = %d\n",
+ kernel_code->compute_pgm_rsrc1.granulated_wavefront_sgpr_count);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.priority = %d\n",
+ kernel_code->compute_pgm_rsrc1.priority);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.float_round_mode_32 = %d\n",
+ kernel_code->compute_pgm_rsrc1.float_round_mode_32);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.float_round_mode_16_64 = %d\n",
+ kernel_code->compute_pgm_rsrc1.float_round_mode_16_64);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.float_denorm_mode_32 = %d\n",
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_32);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.float_denorm_mode_16_64 = %d\n",
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_16_64);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.priv = %d\n",
+ kernel_code->compute_pgm_rsrc1.priv);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.enable_dx10_clamp = %d\n",
+ kernel_code->compute_pgm_rsrc1.enable_dx10_clamp);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.debug_mode = %d\n",
+ kernel_code->compute_pgm_rsrc1.debug_mode);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.enable_ieee_mode = %d\n",
+ kernel_code->compute_pgm_rsrc1.enable_ieee_mode);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.bulky = %d\n",
+ kernel_code->compute_pgm_rsrc1.bulky);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.cdbg_user = %d\n",
+ kernel_code->compute_pgm_rsrc1.cdbg_user);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.fp16_ovfl = %d\n",
+ kernel_code->compute_pgm_rsrc1.fp16_ovfl);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.reserved0 = %d\n",
+ kernel_code->compute_pgm_rsrc1.reserved0);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.wgp_mode = %d\n",
+ kernel_code->compute_pgm_rsrc1.wgp_mode);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.mem_ordered = %d\n",
+ kernel_code->compute_pgm_rsrc1.mem_ordered);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc1.fwd_progress = %d\n",
+ kernel_code->compute_pgm_rsrc1.fwd_progress);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_private_segment = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_private_segment);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.user_sgpr_count = %d\n",
+ kernel_code->compute_pgm_rsrc2.user_sgpr_count);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_trap_handler = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_trap_handler);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_x = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_x);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_y = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_y);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_z = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_z);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_info = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_info);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_vgpr_workitem_id = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_vgpr_workitem_id);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_address_watch = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_exception_address_watch);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_memory = %d\n",
+ kernel_code->compute_pgm_rsrc2.enable_exception_memory);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.granulated_lds_size = %d\n",
+ kernel_code->compute_pgm_rsrc2.granulated_lds_size);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_invalid_operation = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_invalid_operation);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_fp_denormal_source = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_fp_denormal_source);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_division_by_zero = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_division_by_zero);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_overflow = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_overflow);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_underflow = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_underflow);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_inexact = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_inexact);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.enable_exception_int_divide_by_zero = %d\n",
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_int_divide_by_zero);
+ knod_jit_dbg(" kernel_code->compute_pgm_rsrc2.reserved0 = %d\n",
+ kernel_code->compute_pgm_rsrc2.reserved0);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_private_segment_buffer = %d\n",
+ kernel_code->code_properties
+ .enable_sgpr_private_segment_buffer);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_dispatch_ptr = %d\n",
+ kernel_code->code_properties.enable_sgpr_dispatch_ptr);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_queue_ptr = %d\n",
+ kernel_code->code_properties.enable_sgpr_queue_ptr);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_kernarg_segment_ptr = %d\n",
+ kernel_code->code_properties.enable_sgpr_kernarg_segment_ptr);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_dispatch_id = %d\n",
+ kernel_code->code_properties.enable_sgpr_dispatch_id);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_flat_scratch_init = %d\n",
+ kernel_code->code_properties.enable_sgpr_flat_scratch_init);
+ knod_jit_dbg(" kernel_code->code_properties.enable_sgpr_private_segment_size = %d\n",
+ kernel_code->code_properties.enable_sgpr_private_segment_size);
+ knod_jit_dbg(" kernel_code->code_properties.reserved0 = %d\n",
+ kernel_code->code_properties.reserved0);
+ knod_jit_dbg(" kernel_code->code_properties.enable_wavefront_size32 = %d\n",
+ kernel_code->code_properties.enable_wavefront_size32);
+ knod_jit_dbg(" kernel_code->code_properties.uses_dynamic_stack = %d\n",
+ kernel_code->code_properties.uses_dynamic_stack);
+ knod_jit_dbg(" kernel_code->code_properties.reserved1 = %d\n",
+ kernel_code->code_properties.reserved1);
+}
+
+static void kfd_kernel_gfx9_init(struct knod *knod)
+{
+ struct kernel_descriptor *kernel_code = knod->kernels[0]->kaddr;
+
+ kernel_code->group_segment_fixed_size = 0;
+ kernel_code->private_segment_fixed_size = 8192;
+ kernel_code->kernarg_size = 64;
+ kernel_code->kernel_code_entry_byte_offset = 1024;
+
+ /* GFX10+ or GFX90A+ */
+ kernel_code->compute_pgm_rsrc3.accum_offset = 0;
+ kernel_code->compute_pgm_rsrc3.reserved0 = 0;
+ kernel_code->compute_pgm_rsrc3.tg_split = 0;
+ kernel_code->compute_pgm_rsrc3.reserved1 = 0;
+
+ kernel_code->compute_pgm_rsrc1.granulated_workitem_vgpr_count =
+ (256 / 4) - 1;
+ /*
+ * Start with the small GFX9 window. BPF install updates each slot
+ * descriptor when a program needs a larger exec_save/initial_exec
+ * range.
+ */
+ kernel_code->compute_pgm_rsrc1.granulated_wavefront_sgpr_count =
+ knod_bpf_gfx9_sgpr_granule(52);
+ kernel_code->compute_pgm_rsrc1.priority = 0;
+ kernel_code->compute_pgm_rsrc1.float_round_mode_32 = 0;
+ kernel_code->compute_pgm_rsrc1.float_round_mode_16_64 = 0;
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_32 = 3;
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_16_64 = 3;
+ kernel_code->compute_pgm_rsrc1.priv = 0;
+ kernel_code->compute_pgm_rsrc1.enable_dx10_clamp = 1;
+ kernel_code->compute_pgm_rsrc1.debug_mode = 0;
+ kernel_code->compute_pgm_rsrc1.enable_ieee_mode = 1;
+ kernel_code->compute_pgm_rsrc1.bulky = 0;
+ kernel_code->compute_pgm_rsrc1.cdbg_user = 0;
+ kernel_code->compute_pgm_rsrc1.fp16_ovfl = 0;
+ kernel_code->compute_pgm_rsrc1.reserved0 = 0;
+ kernel_code->compute_pgm_rsrc1.wgp_mode = 0;
+ kernel_code->compute_pgm_rsrc1.mem_ordered = 0;
+ kernel_code->compute_pgm_rsrc1.fwd_progress = 0;
+
+ kernel_code->compute_pgm_rsrc2.enable_private_segment = 0;
+ kernel_code->compute_pgm_rsrc2.user_sgpr_count = 14; /* 4+2+2+2+2+2 */
+ kernel_code->compute_pgm_rsrc2.enable_trap_handler = 0;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_x = 1;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_y = 1;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_z = 0;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_info = 0;
+ kernel_code->compute_pgm_rsrc2.enable_vgpr_workitem_id = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_address_watch = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_memory = 0;
+ kernel_code->compute_pgm_rsrc2.granulated_lds_size = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_invalid_operation = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_fp_denormal_source = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_division_by_zero = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_overflow = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_underflow = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_inexact = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_int_divide_by_zero = 0;
+ kernel_code->compute_pgm_rsrc2.reserved0 = 0;
+
+ /*
+ * User SGPR layout - loaded in fixed order, disabled entries are
+ * skipped (not reserved). The resulting SGPR map depends on which
+ * flags are enabled:
+ *
+ * enable_sgpr_private_segment_buffer -> 4 SGPRs (s[0:3])
+ * enable_sgpr_dispatch_ptr -> 2 SGPRs (s[4:5])
+ * enable_sgpr_queue_ptr -> 2 SGPRs
+ * enable_sgpr_kernarg_segment_ptr -> 2 SGPRs
+ * enable_sgpr_dispatch_id -> 2 SGPRs
+ * enable_sgpr_flat_scratch_init -> 2 SGPRs
+ * enable_sgpr_private_segment_size -> 1 SGPR
+ *
+ * System SGPRs (WorkgroupId etc.) follow immediately after the
+ * last user SGPR. user_sgpr_count must match the total above.
+ */
+ /* 4 SGPRs */
+ kernel_code->code_properties.enable_sgpr_private_segment_buffer = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_dispatch_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_queue_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_kernarg_segment_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_dispatch_id = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_flat_scratch_init = 1;
+ /* disabled -> s14/s15 free for workgroup_id */
+ kernel_code->code_properties.enable_sgpr_private_segment_size = 0;
+ /* total = 14 SGPRs */
+ kernel_code->code_properties.reserved0 = 0;
+ /* GFX10+ */
+ kernel_code->code_properties.enable_wavefront_size32 = 0;
+ kernel_code->code_properties.uses_dynamic_stack = 0;
+ kernel_code->code_properties.reserved1 = 0;
+
+ debug_kernel_descriptor(kernel_code);
+}
+
+static void kfd_kernel_gfx10_init(struct knod *knod)
+{
+ struct kernel_descriptor *kernel_code = knod->kernels[0]->kaddr;
+
+ kernel_code->group_segment_fixed_size = 0;
+ kernel_code->private_segment_fixed_size = 8192;
+ kernel_code->kernarg_size = 64;
+ kernel_code->kernel_code_entry_byte_offset = 1024;
+
+ /*
+ * User SGPR layout - loaded in fixed order, disabled entries are
+ * skipped (not reserved). The resulting SGPR map depends on which
+ * flags are enabled:
+ *
+ * enable_sgpr_private_segment_buffer -> 4 SGPRs (s[0:3])
+ * enable_sgpr_dispatch_ptr -> 2 SGPRs (s[4:5])
+ * enable_sgpr_queue_ptr -> 2 SGPRs
+ * enable_sgpr_kernarg_segment_ptr -> 2 SGPRs
+ * enable_sgpr_dispatch_id -> 2 SGPRs
+ * enable_sgpr_flat_scratch_init -> 2 SGPRs
+ * enable_sgpr_private_segment_size -> 1 SGPR
+ *
+ * System SGPRs (WorkgroupId etc.) follow immediately after the
+ * last user SGPR. user_sgpr_count must match the total above.
+ */
+ /* 4 SGPRs */
+ kernel_code->code_properties.enable_sgpr_private_segment_buffer = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_dispatch_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_queue_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_kernarg_segment_ptr = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_dispatch_id = 1;
+ /* 2 SGPRs */
+ kernel_code->code_properties.enable_sgpr_flat_scratch_init = 1;
+ /* disabled -> s14/s15 free for workgroup_id */
+ kernel_code->code_properties.enable_sgpr_private_segment_size = 0;
+ /* total = 14 SGPRs */
+ kernel_code->code_properties.reserved0 = 0;
+ if (knod_bpf_wave32)
+ kernel_code->code_properties.enable_wavefront_size32 = 1;
+ else
+ kernel_code->code_properties.enable_wavefront_size32 = 0;
+ kernel_code->code_properties.uses_dynamic_stack = 0;
+ kernel_code->code_properties.reserved1 = 0;
+
+ kernel_code->compute_pgm_rsrc3.accum_offset = 0;
+ kernel_code->compute_pgm_rsrc3.reserved0 = 0;
+ kernel_code->compute_pgm_rsrc3.tg_split = 0;
+ kernel_code->compute_pgm_rsrc3.reserved1 = 0;
+
+ if (kernel_code->code_properties.enable_wavefront_size32 == 1)
+ kernel_code->compute_pgm_rsrc1.granulated_workitem_vgpr_count =
+ (256 / 8) - 1;
+ else
+ kernel_code->compute_pgm_rsrc1.granulated_workitem_vgpr_count =
+ (256 / 4) - 1;
+ kernel_code->compute_pgm_rsrc1.granulated_wavefront_sgpr_count = 0;
+ kernel_code->compute_pgm_rsrc1.priority = 0;
+ kernel_code->compute_pgm_rsrc1.float_round_mode_32 = 0;
+ kernel_code->compute_pgm_rsrc1.float_round_mode_16_64 = 0;
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_32 = 3;
+ kernel_code->compute_pgm_rsrc1.float_denorm_mode_16_64 = 3;
+ kernel_code->compute_pgm_rsrc1.priv = 0;
+ kernel_code->compute_pgm_rsrc1.enable_dx10_clamp = 1;
+ kernel_code->compute_pgm_rsrc1.debug_mode = 0;
+ kernel_code->compute_pgm_rsrc1.enable_ieee_mode = 1;
+ kernel_code->compute_pgm_rsrc1.bulky = 0;
+ kernel_code->compute_pgm_rsrc1.cdbg_user = 0;
+ kernel_code->compute_pgm_rsrc1.fp16_ovfl = 0;
+ kernel_code->compute_pgm_rsrc1.reserved0 = 0;
+ kernel_code->compute_pgm_rsrc1.wgp_mode = 0;
+ kernel_code->compute_pgm_rsrc1.mem_ordered = 1;
+ kernel_code->compute_pgm_rsrc1.fwd_progress = 0;
+
+ kernel_code->compute_pgm_rsrc2.enable_private_segment = 0;
+ kernel_code->compute_pgm_rsrc2.user_sgpr_count = 14; /* 4+2+2+2+2+2 */
+ kernel_code->compute_pgm_rsrc2.enable_trap_handler = 0;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_x = 1;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_y = 1;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_id_z = 0;
+ kernel_code->compute_pgm_rsrc2.enable_sgpr_workgroup_info = 0;
+ kernel_code->compute_pgm_rsrc2.enable_vgpr_workitem_id = 1;
+ kernel_code->compute_pgm_rsrc2.enable_exception_address_watch = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_memory = 0;
+ kernel_code->compute_pgm_rsrc2.granulated_lds_size = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_invalid_operation = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_fp_denormal_source = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_division_by_zero = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_overflow = 0;
+ kernel_code->compute_pgm_rsrc2
+ .enable_exception_ieee_754_fp_underflow = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_ieee_754_fp_inexact = 0;
+ kernel_code->compute_pgm_rsrc2.enable_exception_int_divide_by_zero = 0;
+ kernel_code->compute_pgm_rsrc2.reserved0 = 0;
+
+ debug_kernel_descriptor(kernel_code);
+}
+
+static int kfd_kernel_init(struct knod *knod, struct knod_bpf_priv *priv)
+{
+ struct kernel_descriptor *kd;
+
+ if (!knod->kernels[1])
+ return -ENOMEM;
+
+ /*
+ * Pass-through starts on slot 0; the first XDP prog attach stages into
+ * slot 1 and flips the active index there, ping-ponging on each
+ * install.
+ */
+ priv->active_idx = 0;
+
+ if (priv->isa_version == 9)
+ kfd_kernel_gfx9_init(knod);
+ else if (priv->isa_version == 10)
+ kfd_kernel_gfx10_init(knod);
+
+ /*
+ * Slot 1 must carry the same kernel-descriptor as slot 0 -- gfx init
+ * only touches slot 0, and slot 1's BO is otherwise uninitialised,
+ * which stalls the compute queue. Copy the kd + pre-code region.
+ */
+ kd = knod->kernels[0]->kaddr;
+ memcpy(knod->kernels[1]->kaddr, knod->kernels[0]->kaddr,
+ kd->kernel_code_entry_byte_offset);
+ knod_bpf_gpu_mem_fence(priv);
+
+ return 0;
+}
+
+static struct knod_bpf_work_sq *
+__knod_get_free_work_sq(struct knod_bpf_priv *priv)
+{
+ return list_first_entry_or_null(&priv->free_list_sqw,
+ struct knod_bpf_work_sq, list);
+}
+
+/* Prepare a dispatch: peek SPSC rings and fill params, but do not submit.
+ * Returns the prepared sqw (with backlogs > 0), or NULL if nothing to do.
+ *
+ * A single in-flight AQL queue means the worker never has to reserve SPSC
+ * ranges ahead of the current dispatch. The SPSC acquired pointer is advanced
+ * only after the GPU finishes the dispatch that consumed those entries.
+ */
+static struct knod_bpf_work_sq *knod_prepare_bpf(struct knod_bpf_priv *priv)
+{
+ int i, cnt, backlogs = 0;
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_work_sq *sqw;
+ struct knod_bpf_param *param;
+
+ if (READ_ONCE(priv->installing_kernel))
+ return NULL;
+
+ if (!priv->pass_prog_buf && !READ_ONCE(priv->prog))
+ return NULL;
+
+ sqw = __knod_get_free_work_sq(priv);
+ if (!sqw)
+ return NULL;
+
+ param = (struct knod_bpf_param *)sqw->param->kaddr;
+ memset(sqw->queue_idx, 0, sizeof(sqw->queue_idx));
+
+ /* 2D dispatch: queue_id = workgroup_id_y, tid = workitem within WG.
+ * Per-queue bds live in sqw->bds[i * batch_size + tid] and shader
+ * indexes sub[] / sqw->bds[] using (queue_id * batch_size + tid).
+ * No cumulative start_idx -- each queue's slot range is fixed by i.
+ */
+ for (i = 0; i < priv->nr_works; i++) {
+ int slot = i * priv->batch_size;
+ unsigned int skip = 0, j;
+
+ /* Stage past every in-flight dispatch's claim on this queue so
+ * the new sqw reads disjoint SPSC slots. Peek self-limits: if
+ * the ring holds fewer entries past @skip, cnt shrinks (or 0).
+ */
+ for (j = 0; j < priv->inflight_cnt; j++)
+ skip += priv->inflight[j]->queue_idx[i];
+
+ param->queues[i].count = 0;
+ spsc_peek_at(&knodev->wpriv[i].spsc_bds, skip,
+ (void **)&sqw->bds[slot],
+ priv->batch_size, &cnt);
+ if (!cnt) {
+ sqw->queue_idx[i] = 0;
+ param->queues[i].count = 0;
+ continue;
+ }
+
+ /* Fill queue descriptor for GPU direct SPSC read.
+ * ring_start is the absolute ring position where this sqw
+ * begins - shader reads slots[(ring_start + tid) & mask].
+ * Offset by skip to keep staged sqws disjoint.
+ */
+ param->queues[i].pool_gaddr = knodev->wpriv[i].spsc_pool_gaddr;
+ param->queues[i].base_gaddr = priv->queue_base_gaddr[i];
+ param->queues[i].count = cnt;
+ param->queues[i].ring_start =
+ knodev->wpriv[i].spsc_bds.acquired + skip;
+ param->queues[i].ring_mask =
+ knodev->wpriv[i].spsc_bds.mask;
+
+ backlogs += cnt;
+ sqw->queue_idx[i] = cnt;
+ }
+ sqw->backlogs = backlogs;
+ param->nr_backlogs = backlogs;
+ param->nr_queues = priv->nr_works;
+ param->spsc_stride = ALIGN(sizeof(struct spsc_bd), SMP_CACHE_BYTES);
+ for (i = 0; i < priv->nr_works; i++) {
+ param->pass_count[i] = 0;
+ param->pass_meta_buf_gaddr[i] = priv->pass_meta_buf ?
+ priv->pass_meta_buf->gaddr +
+ (u64)i * priv->pass_pkts_per_queue *
+ KNOD_PASS_SLOT_SIZE :
+ 0;
+ }
+ param->ktime_ns = ktime_get_ns();
+
+ if (!sqw->backlogs)
+ return NULL;
+
+ list_del_init(&sqw->list);
+ return sqw;
+}
+
+/* Submit a prepared sqw: write AQL packet, ring doorbell, record stats. */
+static void knod_submit_bpf(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct amd_signal *signal =
+ (struct amd_signal *)priv->knod->kaql[0].queue_signal->kaddr;
+ struct knod_bpf_stats *stats = &priv->stats;
+ struct knod_dispatch_params p;
+ int i, bucket = KNOD_BL_BUCKETS - 1;
+
+ /* The @inflight_cnt dispatches already in flight decrement the signal
+ * before this one, so this sqw completes when the signal drops below
+ * (current value - inflight_cnt).
+ */
+ sqw->sigval = signal->value - priv->inflight_cnt;
+ sqw->expire = jiffies + msecs_to_jiffies(knod_bpf_expire);
+ if (static_branch_unlikely(&knod_stats_key)) {
+ sqw->dispatch_time = ktime_get();
+
+ stats->backlogs_total += sqw->backlogs;
+ for (i = 0; i < KNOD_BL_BUCKETS - 1; i++) {
+ if (sqw->backlogs <= bl_bounds[i]) {
+ bucket = i;
+ break;
+ }
+ }
+ stats->backlogs_hist[bucket]++;
+ }
+
+ knod_bpf_fill_dispatch(priv, sqw, &p);
+ /* publish dispatch params before the AQL packet becomes visible */
+ wmb();
+ knod_setup_header(priv->knod, &p, 0);
+}
+
+/* Phase 1: advance SPSC consumer pointers so next dispatch can peek
+ * new entries.
+ */
+static void knod_complete_acquire(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct knod_dev *knodev = priv->knodev;
+ int i;
+
+ for (i = 0; i < priv->nr_works; i++) {
+ if (sqw->queue_idx[i] >= 1) {
+ spsc_acquire(&knodev->wpriv[i].spsc_bds, NULL,
+ sqw->queue_idx[i], NULL);
+ }
+ }
+}
+
+/* Phase 2: schedule NAPI and free sqw. Can run after the next dispatch
+ * has been submitted - napi_schedule overlaps with GPU execution.
+ */
+static void knod_complete_napi(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_stats *stats = &priv->stats;
+ ktime_t start;
+ int i;
+
+ if (static_branch_unlikely(&knod_stats_key))
+ start = ktime_get();
+
+ for (i = 0; i < priv->nr_works; i++) {
+ if (sqw->queue_idx[i] >= 1)
+ knod_napi_kick(&knodev->wpriv[i]);
+ }
+
+ sqw->backlogs = 0;
+ sqw->expire = 0;
+ list_add_tail_rcu(&sqw->list, &priv->free_list_sqw);
+
+ if (static_branch_unlikely(&knod_stats_key)) {
+ u64 ns = ktime_to_ns(ktime_sub(ktime_get(), start));
+
+ stats->decode_act_total_ns += ns;
+ stats->decode_act_count++;
+ if (ns > stats->decode_act_max_ns)
+ stats->decode_act_max_ns = ns;
+ }
+}
+
+static void knod_bpf_update_kernel_descriptor(struct knod_bpf_priv *priv,
+ struct kernel_descriptor *kd,
+ const struct knod_prog *knod_prog)
+{
+ unsigned int sgprs_used;
+
+ if (priv->isa_version != 9 || !knod_prog)
+ return;
+
+ sgprs_used = knod_prog->initial_exec_sreg + 2;
+ kd->compute_pgm_rsrc1.granulated_wavefront_sgpr_count =
+ knod_bpf_gfx9_sgpr_granule(sgprs_used);
+}
+
+/*
+ * Install kernel code into the inactive slot and atomically flip the active
+ * index. The active slot is never modified while the GPU dispatches it, so
+ * the swap never races the live pipeline, and the worker is not touched: new
+ * dispatches pick up the new slot, the in-flight one finishes on the old slot.
+ */
+static void knod_bpf_install_kernel(struct knod_bpf_priv *priv,
+ const struct knod_prog *knod_prog,
+ const void *code, u32 size)
+{
+ struct kernel_descriptor *kd;
+ struct knod *knod = priv->knod;
+ struct knod_mem *slot;
+ u32 entry_off;
+ u32 image_len;
+ int idx;
+
+ if (!code || !size || !knod->kernels[1])
+ return;
+
+ /*
+ * Before the worker runs, install in place; once it is dispatching,
+ * stage into the inactive slot and flip the active index so the live
+ * pipeline never reads a half-written slot.
+ */
+ if (!priv->start || !knod->worker)
+ idx = priv->active_idx;
+ else
+ idx = priv->active_idx ^ 1;
+
+ slot = knod->kernels[idx];
+ kd = slot->kaddr;
+ knod_bpf_update_kernel_descriptor(priv, kd, knod_prog);
+ entry_off = kd->kernel_code_entry_byte_offset;
+ if (WARN_ON(entry_off >= slot->size))
+ return;
+ if (WARN_ON(size > slot->size - entry_off))
+ size = slot->size - entry_off;
+ image_len = entry_off + size;
+
+ memcpy(slot->kaddr + entry_off, code, size);
+ if (image_len < slot->size) {
+ u32 clear_end = min_t(u32, slot->size,
+ entry_off + KNOD_BPF_PROG_BUF_SIZE);
+
+ if (image_len < clear_end)
+ memset(slot->kaddr + image_len, 0,
+ clear_end - image_len);
+ }
+ /*
+ * kernels[] is write-combining VRAM. smp_wmb() is only a compiler
+ * barrier on x86 and does NOT drain the WC buffers, so the GPU could
+ * fetch half-written code and spin. wmb() (sfence) flushes WC to VRAM
+ * before we publish the new slot; the dispatch doorbell is ordered
+ * behind it.
+ */
+ wmb();
+ knod_bpf_gpu_mem_fence(priv);
+ WRITE_ONCE(priv->kernel_image_len[idx], image_len);
+
+ if (idx != priv->active_idx)
+ WRITE_ONCE(priv->active_idx, idx);
+}
+
+/*
+ * Keep the just-built pass-kernel IR for the debugfs "insn" dump, so it can
+ * show the pass-through kernel when no XDP prog is attached. The machine code
+ * already lives in the kernel slot; this only retains the meta list. Rebuilt
+ * on every start (old metas freed first), released in knod_priv_exit().
+ */
+static void knod_bpf_retain_pass_ir(struct knod_bpf_priv *priv,
+ struct knod_prog *src)
+{
+ struct knod_insn_meta *meta, *tmp;
+ struct knod_prog *dst = priv->pass_knod_prog;
+
+ if (!dst) {
+ dst = kzalloc_obj(*dst, GFP_KERNEL);
+ if (!dst)
+ return;
+ INIT_LIST_HEAD(&dst->pre_insns);
+ INIT_LIST_HEAD(&dst->insns);
+ INIT_LIST_HEAD(&dst->post_insns);
+ priv->pass_knod_prog = dst;
+ } else {
+ list_for_each_entry_safe(meta, tmp, &dst->pre_insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ list_for_each_entry_safe(meta, tmp, &dst->insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ list_for_each_entry_safe(meta, tmp, &dst->post_insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ }
+ list_splice_init(&src->pre_insns, &dst->pre_insns);
+ list_splice_init(&src->insns, &dst->insns);
+ list_splice_init(&src->post_insns, &dst->post_insns);
+}
+
+static void knod_bpf_layout_sregs(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog)
+{
+ if (priv->isa_version != 9)
+ return;
+
+ knod_prog->initial_exec_sreg =
+ knod_prog->exec_save_base + knod_prog->exec_save_pairs_used * 2;
+}
+
+static int knod_bpf_jit_pass_kernel(struct knod_bpf_priv *priv)
+{
+ struct list_head *lists[2];
+ struct knod_insn_meta *meta, *tmp, *epi;
+ struct amdgcn_param32 p[3];
+ struct knod *knod = priv->knod;
+ struct knod_prog pass_prog;
+ int pass_branch_idx;
+ u32 pass_dwords;
+ u8 *buf, *ptr;
+ u32 total = 0;
+ int i, j, li, err;
+
+ memset(&pass_prog, 0, sizeof(pass_prog));
+ INIT_LIST_HEAD(&pass_prog.pre_insns);
+ INIT_LIST_HEAD(&pass_prog.insns);
+ INIT_LIST_HEAD(&pass_prog.post_insns);
+ pass_prog.knod = knod;
+ pass_prog.knodev = priv->knodev;
+ if (priv->isa_version == 10) {
+ pass_prog.done_mask_sreg = 32;
+ pass_prog.exec_save_base = 34;
+ pass_prog.initial_exec_sreg =
+ KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX10;
+ } else {
+ pass_prog.done_mask_sreg = KNOD_AMDGPU_DONE_MASK_SREG;
+ pass_prog.exec_save_base = KNOD_AMDGPU_EXEC_SAVE_SREG_BASE;
+ pass_prog.initial_exec_sreg =
+ KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX9;
+ }
+
+ knod_bpf_layout_sregs(priv, &pass_prog);
+ err = knod_prog_prepare_insns(priv, &pass_prog);
+ if (err)
+ return err;
+
+ epi = kzalloc_obj(*epi, GFP_KERNEL);
+ if (!epi) {
+ err = -ENOMEM;
+ goto free_pro;
+ }
+
+ /* BPF/XDP actions are 32-bit values; mlx5 consumes bd->act as low32. */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_iset32(&p[1], XDP_PASS);
+ knod_emit(priv, epi, v_mov_b32_e32, p[0], p[1]);
+
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_SLOT_VREG_LO);
+ knod_emit(priv, epi, global_store_dword, p[0], p[1],
+ offsetof(struct spsc_bd, act));
+
+ /* XDP_PASS detection: v_cmp_eq_u32 XDP_PASS, R0 -> VCC */
+ knod_iset32(&p[0], XDP_PASS);
+ knod_vset32(&p[1], KNOD_AMDGPU_VREG0_LO);
+ knod_emit(priv, epi, v_cmp_eq_u32, p[0], p[1]);
+
+ pass_branch_idx = epi->amdgpu_insns;
+ knod_emit(priv, epi, s_cbranch_vccz, 0);
+
+ /* EXEC &= VCC - only PASS lanes proceed */
+ knod_emit(priv, epi, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* v_mov param addr to VGPR pair for pass_count atomic */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, epi, v_mov_b32_e32, p[0], p[1]);
+
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_HI);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_HI);
+ knod_emit(priv, epi, v_mov_b32_e32, p[0], p[1]);
+
+ /* v_mov v2, s15 (queue_idx -> VGPR) */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG1_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_emit(priv, epi, v_mov_b32_e32, p[0], p[1]);
+
+ /* v_lshlrev_b32 v2, 2, v2 (queue_idx * 4) */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG1_LO);
+ knod_iset32(&p[1], 2);
+ knod_vset32(&p[2], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, epi, v_lshlrev_b32, p[0], p[1], p[2]);
+
+ /* v_add_u32 TMP9_LO, v2, TMP9_LO (param_addr += queue_idx * 4) */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_VREG1_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, epi, v_add_u32, p[0], p[1], p[2]);
+
+ /* v_mov TMP10_LO, 1 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_iset32(&p[1], 1);
+ knod_emit(priv, epi, v_mov_b32_e32, p[0], p[1]);
+
+ /* global_atomic_add pass_count[q]++, GLC=1 -> old_val in TMP10_LO */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_emit(priv, epi, global_atomic_add, p[0], p[1], p[2],
+ offsetof(struct knod_bpf_param, pass_count), 1);
+
+ /* s_waitcnt vmcnt(0) */
+ knod_emit(priv, epi, s_waitcnt_vmcnt);
+
+ /* v_sub_u32 TMP9_LO, TMP9_LO, v2 (restore param_addr_lo) */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, epi, v_sub_u32, p[0], p[1], p[2]);
+
+ /* old_val * 2 for pass_indices u16 stride */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_iset32(&p[1], 1);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_emit(priv, epi, v_lshlrev_b32, p[0], p[1], p[2]);
+
+ /* addr_lo += old_val * 2 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, epi, v_add_u32, p[0], p[1], p[2]);
+
+ /* global_store_short pass_indices[old_val], BACKLOG_IDX_VREG */
+ knod_vset32(&p[0], KNOD_AMDGPU_BACKLOG_IDX_VREG);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, epi, global_store_short, p[0], p[1],
+ offsetof(struct knod_bpf_param, pass_indices));
+
+ /* Store len + src_addr to pass_meta_buf slot header */
+ knod_emit_pass_addr_store(priv, epi);
+
+ /* Patch s_cbranch_vccz offset (skip pass handling) */
+ pass_dwords = 0;
+ for (j = pass_branch_idx + 1; j < epi->amdgpu_insns; j++)
+ pass_dwords += epi->amdgpu_insn[j].size / 4;
+ emit_s_cbranch_vccz(priv->isa_version,
+ &epi->amdgpu_insn[pass_branch_idx], pass_dwords);
+
+ knod_emit(priv, epi, s_endpgm);
+
+ if (priv->isa_version >= 10) {
+ for (j = 0; j < 16 && epi->amdgpu_insns < KNOD_META_INSNS; j++)
+ knod_emit(priv, epi, s_code_end);
+ }
+ list_add_tail(&epi->l, &pass_prog.post_insns);
+
+ /* Linearize prologue + epilogue into pass_prog_buf */
+ lists[0] = &pass_prog.pre_insns;
+ lists[1] = &pass_prog.post_insns;
+
+ for (li = 0; li < 2; li++) {
+ list_for_each_entry(meta, lists[li], l)
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ total += meta->amdgpu_insn[i].size;
+ }
+
+ kfree(priv->pass_prog_buf);
+ buf = kzalloc(total, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto free_all;
+ }
+
+ ptr = buf;
+ for (li = 0; li < 2; li++) {
+ list_for_each_entry(meta, lists[li], l)
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ memcpy(ptr, &meta->amdgpu_insn[i],
+ meta->amdgpu_insn[i].size);
+ ptr += meta->amdgpu_insn[i].size;
+ }
+ }
+
+ priv->pass_prog_buf = buf;
+ priv->pass_prog_size = total;
+
+ knod_bpf_install_kernel(priv, &pass_prog, priv->pass_prog_buf,
+ priv->pass_prog_size);
+ /* Remember which slot now holds pass so detach can flip back to it. */
+ priv->pass_idx = priv->active_idx;
+
+ pr_info("knod_bpf: pass kernel JIT'd %u bytes\n", priv->pass_prog_size);
+ err = 0;
+ /* Retain the IR (moves the lists out) before the cleanup below
+ * frees.
+ */
+ knod_bpf_retain_pass_ir(priv, &pass_prog);
+
+free_all:
+ list_for_each_entry_safe(meta, tmp, &pass_prog.post_insns, l) {
+ list_del_init(&meta->l);
+ kfree(meta);
+ }
+free_pro:
+ list_for_each_entry_safe(meta, tmp, &pass_prog.pre_insns, l) {
+ list_del_init(&meta->l);
+ kfree(meta);
+ }
+ return err;
+}
+
+static void knod_bpf_reset_sqw(struct knod_bpf_work_sq *sqw)
+{
+ if (!sqw)
+ return;
+
+ sqw->backlogs = 0;
+ sqw->expire = 0;
+}
+
+static void knod_bpf_wait_sqw(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct amd_signal *signal;
+ unsigned long deadline;
+
+ if (!sqw)
+ return;
+
+ signal = (struct amd_signal *)
+ priv->knod->kaql[0].queue_signal->kaddr;
+ deadline = jiffies + msecs_to_jiffies(1000);
+
+ while (sqw->sigval <= READ_ONCE(signal->value) &&
+ time_before(jiffies, deadline))
+ usleep_range(100, 200);
+
+ if (sqw->sigval <= READ_ONCE(signal->value))
+ pr_warn("knod: timed out waiting for GPU dispatch completion\n");
+}
+
+static void knod_bpf_drain_worker(struct knod_bpf_priv *priv)
+{
+ struct knod_bpf_work_sq *sqw;
+
+ /* stop() runs on interface-down AND on every feature switch, both
+ * with mlx5 RX possibly still producing into knodev->wpriv[].spsc_bds.
+ * So we only quiesce the GPU here; the NIC-owned RX SPSC rings are
+ * drained on interface-down by mlx5e_rx_offload_stop().
+ */
+ while (priv->inflight_cnt) {
+ sqw = priv->inflight[--priv->inflight_cnt];
+ priv->inflight[priv->inflight_cnt] = NULL;
+ knod_bpf_wait_sqw(priv, sqw);
+ knod_bpf_reset_sqw(sqw);
+ list_add_tail_rcu(&sqw->list, &priv->free_list_sqw);
+ }
+}
+
+static void knod_bpf_drain(struct knod_bpf_priv *priv)
+{
+ knod_bpf_drain_worker(priv);
+}
+
+static void knod_bpf_stop_worker(struct knod_bpf_priv *priv)
+{
+ priv->start = 0;
+ if (priv->worker_task) {
+ kthread_stop(priv->worker_task);
+ put_task_struct(priv->worker_task);
+ priv->worker_task = NULL;
+ }
+ synchronize_net();
+}
+
+static void knod_bpf_configure_worker(struct knod_bpf_priv *priv)
+{
+ knod_bpf_stop_worker(priv);
+ knod_bpf_drain(priv);
+
+ priv->inflight_cnt = 0;
+}
+
+static int knod_bpf_start_worker(struct knod_bpf_priv *priv)
+{
+ struct task_struct *p;
+
+ p = kthread_run(knod_bpf_worker, priv, "knod_%d_0",
+ priv->knodev->accel->id);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ get_task_struct(p);
+ priv->worker_task = p;
+ return 0;
+}
+
+static bool knod_bpf_uses_percpu(struct knod_bpf_priv *priv)
+{
+ struct knod_bpf_map *knod_map;
+
+ list_for_each_entry(knod_map, &priv->knodev->accel->xdp.bound_maps,
+ list)
+ if (knod_map->knod_map_obj->map_type ==
+ BPF_MAP_TYPE_PERCPU_ARRAY)
+ return true;
+ return false;
+}
+
+/* Fan out one workgroup per CU (rounded down to a power of two). PERCPU maps
+ * keep one instance per RX queue, so a queue must stay on a single workgroup;
+ * force xgroups=1 when the program uses them. Non-PERCPU maps are globally
+ * shared with atomics, so fan-out is safe there. This replaces the old manual
+ * xgroups knob.
+ */
+static unsigned int knod_bpf_auto_xgroups(struct knod_bpf_priv *priv)
+{
+ struct amdgpu_device *adev = NULL;
+ unsigned int cus, xgroups;
+
+ if (knod_bpf_uses_percpu(priv))
+ return 1;
+
+ if (priv->knod->dev)
+ adev = priv->knod->dev->adev;
+ else if (priv->knod->process && priv->knod->process->pdds[0])
+ adev = priv->knod->process->pdds[0]->dev->adev;
+ if (!adev || !priv->nr_works)
+ return 1;
+
+ cus = adev->gfx.cu_info.number;
+ xgroups = cus / priv->nr_works;
+ if (!xgroups)
+ xgroups = 1;
+ return rounddown_pow_of_two(xgroups);
+}
+
+/* Per-queue dispatch batch = workgroups * xgroups packets, capped by the
+ * static descriptor array and rounded down to a power of two (the shader
+ * derives the flat slot as queue_id << ilog2(batch_size) + local_idx).
+ */
+static unsigned int knod_bpf_batch_size(struct knod_bpf_priv *priv)
+{
+ unsigned int xgroups = knod_bpf_auto_xgroups(priv);
+ unsigned int max_flat = KNOD_BPF_BACKLOGS_MAX / priv->nr_works;
+ unsigned int batch = min_t(unsigned int,
+ knod_bpf_workgroups * xgroups, max_flat);
+
+ if (!batch)
+ batch = knod_bpf_workgroups;
+ return rounddown_pow_of_two(batch);
+}
+
+static void knod_bpf_start(struct knod_dev *knodev)
+{
+ struct knod_bpf_priv *priv =
+ (struct knod_bpf_priv *)knodev->accel->xdp.priv;
+ struct bpf_prog *prog;
+ unsigned int active_rxq;
+ int err;
+
+ priv->start = 1;
+ active_rxq = knod_bpf_active_rxq_count(knodev->netdev);
+ if (active_rxq && active_rxq != priv->nr_works)
+ pr_warn("knod_bpf: active rx queues changed from %d to %u; using initialized count\n",
+ priv->nr_works, active_rxq);
+
+ priv->batch_size = knod_bpf_batch_size(priv);
+
+ knod_jit_dbg(" batch_size = %d\n", priv->batch_size);
+ knod_bpf_configure_worker(priv);
+ pr_info("knod_bpf: using single AQL queue, rx_works=%d active_rxq=%u batch_size=%d xgroups=%u\n",
+ priv->nr_works, active_rxq, priv->batch_size,
+ priv->batch_size / knod_bpf_workgroups);
+
+ if (knod_bpf_jit_pass_kernel(priv))
+ pr_warn("knod_bpf: pass kernel JIT failed\n");
+
+ prog = READ_ONCE(priv->prog);
+ if (prog)
+ knod_setup_bpf_prog(prog);
+
+ priv->start = 1;
+ err = knod_bpf_start_worker(priv);
+ if (err) {
+ pr_err("knod_bpf: start_worker failed: %d\n", err);
+ priv->start = 0;
+ return;
+ }
+}
+
+static void knod_bpf_stop(struct knod_dev *knodev)
+{
+ struct knod_bpf_priv *priv =
+ (struct knod_bpf_priv *)knodev->accel->xdp.priv;
+
+ knod_bpf_stop_worker(priv);
+ knod_bpf_drain(priv);
+
+ kfree(priv->pass_prog_buf);
+ priv->pass_prog_buf = NULL;
+ priv->pass_prog_size = 0;
+}
+
+/*
+ * Flip the dispatched kernel back to pass-through when the XDP prog is
+ * detached. The pass slot already holds the pass code, so this is just an
+ * atomic index flip -- no re-copy.
+ */
+static void knod_bpf_reload_pass(struct knod_dev *knodev)
+{
+ struct knod_bpf_priv *priv = knodev->accel->xdp.priv;
+
+ if (priv)
+ WRITE_ONCE(priv->active_idx, priv->pass_idx);
+}
+
+static void knod_setup_bpf_prog(struct bpf_prog *prog)
+{
+ struct knod_prog *knod_prog = prog->aux->offload->dev_priv;
+ struct knod_dev *knodev = knod_prog->knodev;
+ struct knod_insn_meta *meta, *tmp;
+ struct knod_bpf_priv *priv;
+ u8 *kernel_ptr, *ptr;
+ u32 total_bytes;
+ u32 *debug_ptr;
+ int i;
+
+ priv = (struct knod_bpf_priv *)knodev->accel->xdp.priv;
+ WRITE_ONCE(priv->installing_kernel, true);
+
+ if (prog) {
+ WRITE_ONCE(priv->prog, NULL);
+ kernel_ptr = priv->prog_buf;
+ memset(priv->prog_buf, 0, KNOD_BPF_PROG_BUF_SIZE);
+
+ list_for_each_entry(meta, &priv->knod_prog->pre_insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ ptr = (u8 *)&meta->amdgpu_insn[i];
+ debug_ptr = (u32 *)ptr;
+
+ memcpy(kernel_ptr, ptr,
+ meta->amdgpu_insn[i].size);
+ kernel_ptr += meta->amdgpu_insn[i].size;
+
+ if (meta->amdgpu_insn[i].size == 4) {
+ knod_jit_dbg(" 0x%.8X\t%.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0]);
+ } else if (meta->amdgpu_insn[i].size == 8) {
+ knod_jit_dbg(" 0x%.8X\t%.8X %.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0], debug_ptr[1]);
+ } else if (meta->amdgpu_insn[i].size == 12) {
+ knod_jit_dbg(" 0x%.8X\t%.8X %.8X %.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0],
+ debug_ptr[1], debug_ptr[2]);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ }
+ }
+
+ list_for_each_entry(meta, &priv->knod_prog->insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ ptr = (u8 *)&meta->amdgpu_insn[i];
+ debug_ptr = (u32 *)ptr;
+
+ memcpy(kernel_ptr, ptr,
+ meta->amdgpu_insn[i].size);
+ kernel_ptr += meta->amdgpu_insn[i].size;
+ if (meta->amdgpu_insn[i].size == 4) {
+ knod_jit_dbg(" 0x%.8X\t%.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0]);
+ } else if (meta->amdgpu_insn[i].size == 8) {
+ knod_jit_dbg(" 0x%.8X\t%.8X %.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0], debug_ptr[1]);
+ } else if (meta->amdgpu_insn[i].size == 12) {
+ knod_jit_dbg(" 0x%.8X\t%.8X %.8X %.8X\n",
+ meta->amdgpu_insn_idx,
+ debug_ptr[0],
+ debug_ptr[1], debug_ptr[2]);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ }
+ }
+
+ list_for_each_entry(meta, &priv->knod_prog->post_insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ ptr = (u8 *)&meta->amdgpu_insn[i];
+ debug_ptr = (u32 *)ptr;
+
+ memcpy(kernel_ptr, ptr,
+ meta->amdgpu_insn[i].size);
+ kernel_ptr += meta->amdgpu_insn[i].size;
+ if (meta->amdgpu_insn[i].size == 4) {
+ knod_jit_dbg(" %.8X\n", debug_ptr[0]);
+ } else if (meta->amdgpu_insn[i].size == 8) {
+ knod_jit_dbg(" %.8X %.8X\n",
+ debug_ptr[0], debug_ptr[1]);
+ } else if (meta->amdgpu_insn[i].size == 12) {
+ knod_jit_dbg(" %.8X %.8X %.8X\n",
+ debug_ptr[0],
+ debug_ptr[1], debug_ptr[2]);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ }
+ }
+ total_bytes = kernel_ptr - (u8 *)priv->prog_buf;
+
+ pr_debug("KNOD JIT: total binary size = %u bytes (limit %u)\n",
+ total_bytes, KNOD_BPF_PROG_BUF_SIZE);
+ if (WARN_ON(total_bytes > KNOD_BPF_PROG_BUF_SIZE))
+ total_bytes = KNOD_BPF_PROG_BUF_SIZE;
+ knod_bpf_install_kernel(priv, knod_prog, priv->prog_buf,
+ total_bytes);
+ WRITE_ONCE(priv->prog, prog);
+ } else {
+ WRITE_ONCE(priv->prog, NULL);
+ list_for_each_entry_safe(meta, tmp, &priv->knod_prog->pre_insns,
+ l) {
+ list_del_init(&meta->l);
+ kfree(meta);
+ }
+
+ list_for_each_entry_safe(meta, tmp, &priv->knod_prog->insns,
+ l) {
+ list_del_init(&meta->l);
+ kfree(meta);
+ }
+
+ list_for_each_entry_safe(meta, tmp,
+ &priv->knod_prog->post_insns, l) {
+ list_del_init(&meta->l);
+ kfree(meta);
+ }
+
+ /* bbs points into the metas just freed */
+ kfree(priv->knod_prog->bbs);
+ priv->knod_prog->bbs = NULL;
+ priv->knod_prog->n_bbs = 0;
+
+ if (priv->pass_prog_buf)
+ knod_bpf_install_kernel(priv, priv->pass_knod_prog,
+ priv->pass_prog_buf,
+ priv->pass_prog_size);
+ }
+ WRITE_ONCE(priv->installing_kernel, false);
+}
+
+static int knod_bpf_map_hash_init_elem(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj)
+{
+ unsigned int *queue = (unsigned int *)knod_map->queue_mem->kaddr;
+ unsigned int *bucket = (unsigned int *)&knod_map_obj->bucket[0];
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ struct knod_bpf_hash_elem_obj *e;
+ int i, elem_size;
+
+ elem_size = sizeof(struct knod_bpf_hash_elem_obj) +
+ roundup(knod_map_obj->key_size, 4) +
+ roundup(knod_map_obj->value_size, 4);
+ knod_map_obj->meta.hmeta.elem_size = elem_size;
+
+ for (i = 0; i < knod_map_obj->meta.hmeta.n_buckets; i++)
+ bucket[i] = KNOD_BPF_HASH_NEXT_END;
+
+ for (i = 0; i < knod_map_obj->max_entries; i++) {
+ e = elems + (i * elem_size);
+ e->next = KNOD_BPF_HASH_NEXT_END;
+ queue[i] = i;
+ }
+ knod_map_obj->meta.hmeta.cur = knod_map_obj->max_entries - 1;
+
+ return 0;
+}
+
+static inline unsigned char *
+knod_bpf_hash_elem_kv(struct knod_bpf_hash_elem_obj *e)
+{
+ return (unsigned char *)e + offsetof(struct knod_bpf_hash_elem_obj, kv);
+}
+
+static inline void *
+knod_bpf_array_value_ptr(struct knod_bpf_map_obj *knod_map_obj,
+ unsigned int idx)
+{
+ return (unsigned char *)knod_map_obj +
+ offsetof(struct knod_bpf_map_obj, bucket) +
+ (size_t)idx * knod_map_obj->value_size;
+}
+
+static int __knod_bpf_map_alloc(struct knod_dev *knodev,
+ struct bpf_offloaded_map *offmap)
+{
+ struct knod_bpf_priv *priv =
+ (struct knod_bpf_priv *)knodev->accel->xdp.priv;
+ struct knod_mem *mem, *queue_mem, *hash_elems_mem, *gc_mem;
+ int order, size, queue_size, i, value_size, nents, err;
+ int n_instances = 1;
+ int flags = KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT |
+ KFD_IOC_ALLOC_MEM_FLAGS_PUBLIC |
+ KFD_IOC_ALLOC_MEM_FLAGS_VRAM;
+ struct knod_bpf_map_obj *knod_map_obj;
+ struct knod *knod = priv->knod;
+ struct knod_bpf_map *knod_map;
+ unsigned int gc_size;
+ unsigned int *q;
+
+ if (offmap->map.map_type == BPF_MAP_TYPE_HASH) {
+ value_size = sizeof(unsigned int);
+ nents = roundup_pow_of_two(offmap->map.max_entries);
+ } else {
+ value_size = offmap->map.value_size;
+ nents = offmap->map.max_entries;
+ }
+
+ /* PERCPU_ARRAY keeps one value array per GPU workgroup (percpu
+ * instance) so each CU updates its own copy - no cross-CU atomic
+ * contention. Instances map 1:1 to the per-cpu value buffer, so
+ * allocate num_possible_cpus of them (workgroup_id_y indexes into it).
+ */
+ if (offmap->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
+ n_instances = num_possible_cpus();
+
+ size = sizeof(struct knod_bpf_map_obj) +
+ (value_size * nents * n_instances);
+ if (offmap->map.map_type == BPF_MAP_TYPE_HASH)
+ size += sizeof(unsigned int) * nents;
+ order = get_order(size);
+
+ mem = knod_alloc_mem(knod, PAGE_SIZE << order, flags);
+ if (IS_ERR(mem))
+ return -ENOMEM;
+
+ memset(mem->kaddr, 0, size);
+ knod_map = kzalloc_obj(struct knod_bpf_map, GFP_KERNEL);
+ if (!knod_map) {
+ knod_free_mem(knod, mem);
+ return -ENOMEM;
+ }
+
+ knod_map->mem = mem;
+ knod_map->queue_mem = NULL;
+ knod_map->hash_elems_mem = NULL;
+ knod_map->offmap = offmap;
+ knod_map->priv = priv;
+ if (offmap->dev_priv)
+ WARN_ON_ONCE(1);
+ offmap->dev_priv = knod_map;
+
+ knod_map_obj = (struct knod_bpf_map_obj *)mem->kaddr;
+ knod_map_obj->key_size = offmap->map.key_size;
+ if (knod_map_obj->key_size > MAX_MAP_KEY_SIZE) {
+ pr_warn("request key size is %d, but max key size is %d\n",
+ knod_map_obj->key_size, MAX_MAP_KEY_SIZE);
+ return -ENOMEM;
+ }
+ knod_map_obj->value_size = offmap->map.value_size;
+ knod_map_obj->max_entries = nents;
+ knod_map_obj->id = offmap->map.id;
+ knod_map_obj->map_type = offmap->map.map_type;
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ knod_map_obj->meta.hmeta.n_buckets = nents;
+ if (offmap->map.map_flags & BPF_F_ZERO_SEED)
+ knod_map_obj->meta.hmeta.hashrnd = 0;
+ else
+ knod_map_obj->meta.hmeta.hashrnd = get_random_u32();
+ } else {
+ knod_map_obj->meta.ameta.per_instance_size = value_size * nents;
+ knod_map_obj->meta.ameta.n_instances = n_instances;
+ }
+ knod_map->knod_map_obj = knod_map_obj;
+ /* map->flags = ? */
+ knod_jit_dbg(" map_id = %d\n", knod_map_obj->id);
+
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ queue_size = sizeof(unsigned int) * nents;
+ queue_size = PAGE_SIZE << get_order(queue_size);
+ queue_mem = knod_alloc_mem(knod, queue_size, flags);
+ if (IS_ERR(queue_mem)) {
+ knod_free_mem(knod, mem);
+ kfree(knod_map);
+ return -ENOMEM;
+ }
+
+ memset(queue_mem->kaddr, 0, queue_mem->size);
+ q = queue_mem->kaddr;
+ for (i = 0; i < knod_map_obj->meta.hmeta.n_buckets; i++)
+ q[i] = i;
+ knod_map->queue_mem = queue_mem;
+ knod_map_obj->meta.hmeta.q = (struct _queue *)queue_mem->gaddr;
+
+ queue_size = (sizeof(struct knod_bpf_hash_elem_obj) +
+ roundup(knod_map_obj->key_size, 4) +
+ roundup(knod_map_obj->value_size, 4)) *
+ knod_map_obj->max_entries;
+ queue_size = PAGE_SIZE << get_order(queue_size);
+
+ hash_elems_mem = knod_alloc_mem(knod, queue_size, flags);
+ if (IS_ERR(hash_elems_mem)) {
+ knod_free_mem(knod, queue_mem);
+ knod_free_mem(knod, mem);
+ kfree(knod_map);
+ return -ENOMEM;
+ }
+
+ memset(hash_elems_mem->kaddr, 0, queue_size);
+ knod_map->hash_elems_mem = hash_elems_mem;
+ knod_map_obj->meta.hmeta.elems = (void *)hash_elems_mem->gaddr;
+ knod_bpf_map_hash_init_elem(knod_map, knod_map_obj);
+
+ /* GC list for GPU-side delete: elem_ids pending unlink */
+ gc_size = sizeof(unsigned int) * nents;
+ gc_size = PAGE_SIZE << get_order(gc_size);
+ gc_mem = knod_alloc_mem(knod, gc_size, flags);
+ if (IS_ERR(gc_mem)) {
+ knod_free_mem(knod, hash_elems_mem);
+ knod_free_mem(knod, queue_mem);
+ knod_free_mem(knod, mem);
+ kfree(knod_map);
+ return -ENOMEM;
+ }
+ memset(gc_mem->kaddr, 0, gc_size);
+ knod_map->gc_mem = gc_mem;
+ knod_map_obj->meta.hmeta.gc_count = 0;
+ knod_map_obj->meta.hmeta.gc_list = (void *)gc_mem->gaddr;
+ }
+
+ err = __knod_map_mem(knod, mem);
+ if (err) {
+ pr_err("knod_bpf: failed to GPU-map map BO\n");
+ goto err_map;
+ }
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ err = __knod_map_mem(knod, queue_mem);
+ if (err) {
+ pr_err("knod_bpf: failed to GPU-map queue BO\n");
+ goto err_map;
+ }
+ err = __knod_map_mem(knod, hash_elems_mem);
+ if (err) {
+ pr_err("knod_bpf: failed to GPU-map hash_elems BO\n");
+ goto err_map;
+ }
+ err = __knod_map_mem(knod, knod_map->gc_mem);
+ if (err) {
+ pr_err("knod_bpf: failed to GPU-map gc BO\n");
+ goto err_map;
+ }
+ }
+ knod_bpf_gpu_mem_fence(priv);
+
+ mutex_lock(&knodev->lock);
+ list_add(&knod_map->list, &knodev->accel->xdp.bound_maps);
+ mutex_unlock(&knodev->lock);
+ return 0;
+
+err_map:
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ knod_free_mem(knod, knod_map->gc_mem);
+ knod_free_mem(knod, hash_elems_mem);
+ knod_free_mem(knod, queue_mem);
+ }
+ knod_free_mem(knod, mem);
+ kfree(knod_map);
+ return err;
+}
+
+static void knod_bpf_map_setup(struct bpf_prog *prog)
+{
+ struct knod_prog *knod_prog = prog->aux->offload->dev_priv;
+ struct knod_dev *knodev = knod_prog->knodev;
+ struct knod_bpf_map *knod_map;
+ struct knod_bpf_map_obj *map;
+ struct knod_mem *mem;
+
+ mutex_lock(&knodev->lock);
+ list_for_each_entry(knod_map, &knodev->accel->xdp.bound_maps, list) {
+ mem = knod_map->mem;
+ map = mem->kaddr;
+ map->id = knod_map->offmap->map.id;
+ map->map_type = knod_map->offmap->map.map_type;
+ knod_jit_dbg(" id = %d type = %d\n", knod_map->offmap->map.id,
+ knod_map->offmap->map.map_type);
+ }
+ mutex_unlock(&knodev->lock);
+}
+
+static struct knod_bpf_hash_elem_obj *
+knod_bpf_map_hash_pop(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj)
+{
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ unsigned int *queue = (unsigned int *)knod_map->queue_mem->kaddr;
+ struct knod_bpf_hash_elem_obj *e;
+ int elem_id;
+
+ if (knod_map_obj->meta.hmeta.cur < 1)
+ return NULL;
+
+ elem_id = queue[knod_map_obj->meta.hmeta.cur];
+ knod_jit_dbg(" elem_id = 0x%x\n", elem_id);
+ e = elems + (elem_id * knod_map_obj->meta.hmeta.elem_size);
+ knod_map_obj->meta.hmeta.cur--;
+ e->next = KNOD_BPF_HASH_NEXT_END;
+
+ return e;
+}
+
+static struct knod_bpf_hash_elem_obj *
+knod_bpf_map_hash_alloc_elem(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj,
+ void *key, void *value)
+{
+ struct knod_bpf_hash_elem_obj *e;
+
+ e = knod_bpf_map_hash_pop(knod_map, knod_map_obj);
+ if (!e)
+ return NULL;
+
+ unsafe_memcpy(knod_bpf_hash_elem_kv(e), key, knod_map_obj->key_size,
+ "knod hash elems are variable-sized GPU map records");
+ unsafe_memcpy(knod_bpf_hash_elem_kv(e) + knod_map_obj->key_size,
+ value, knod_map_obj->value_size,
+ "knod hash elems are variable-sized GPU map records");
+ /* VRAM is ioremap_wc - drain new elem's next and kv stores before
+ * the caller publishes a pointer to this elem.
+ */
+ wmb();
+ return e;
+}
+
+static int knod_bpf_map_hash_lookup_elem(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj,
+ void *key,
+ void *value)
+{
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ unsigned int hash, elem_id, elem_size;
+ struct knod_bpf_hash_elem_obj *e;
+ unsigned int *bucket;
+
+ hash = jhash((const void *)key, knod_map_obj->key_size,
+ knod_map_obj->meta.hmeta.hashrnd);
+ knod_jit_dbg(" hash = %x\n", hash);
+ hash = hash & (knod_map_obj->meta.hmeta.n_buckets - 1);
+ knod_jit_dbg(" hash = %x\n", hash);
+ bucket = (unsigned int *)&knod_map_obj->bucket[0];
+
+ elem_id = bucket[hash];
+ if (elem_id == KNOD_BPF_HASH_NEXT_END)
+ return -ENOENT;
+
+ elem_size = knod_map_obj->meta.hmeta.elem_size;
+
+ e = elems + (elem_id * elem_size);
+ while (1) {
+ if (!(e->next & KNOD_BPF_HASH_NEXT_DELETED) &&
+ !memcmp(&e->kv[0], (const unsigned char *)key,
+ knod_map_obj->key_size)) {
+ memcpy(value,
+ (unsigned char *)&e->kv[0] +
+ knod_map_obj->key_size,
+ knod_map_obj->value_size);
+ return 0;
+ }
+ unsigned int real_next = e->next & KNOD_BPF_HASH_NEXT_MASK;
+
+ if (real_next == KNOD_BPF_HASH_NEXT_END)
+ return -ENOENT;
+ e = elems + (real_next * elem_size);
+ }
+
+ return -ENOENT;
+}
+
+static int knod_bpf_map_hash_update_elem(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj,
+ void *key,
+ void *value)
+{
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ unsigned int hash, elem_id, elem_size;
+ struct knod_bpf_hash_elem_obj *e, *ne;
+ unsigned int *bucket;
+
+ hash = jhash((const void *)key, knod_map_obj->key_size,
+ knod_map_obj->meta.hmeta.hashrnd);
+ hash = hash & (knod_map_obj->meta.hmeta.n_buckets - 1);
+ bucket = (unsigned int *)&knod_map_obj->bucket[0];
+
+ elem_size = knod_map_obj->meta.hmeta.elem_size;
+ elem_id = bucket[hash];
+ if (elem_id == KNOD_BPF_HASH_NEXT_END) {
+ ne = knod_bpf_map_hash_alloc_elem(knod_map, knod_map_obj, key,
+ value);
+ if (!ne)
+ return -ENOMEM;
+ bucket[hash] = ((void *)ne - (void *)elems) / elem_size;
+ return 0;
+ }
+
+ e = elems + (elem_id * elem_size);
+ while (1) {
+ if (!(e->next & KNOD_BPF_HASH_NEXT_DELETED) &&
+ !memcmp(&e->kv[0], (const unsigned char *)key,
+ knod_map_obj->key_size)) {
+ unsafe_memcpy(knod_bpf_hash_elem_kv(e) +
+ knod_map_obj->key_size,
+ value, knod_map_obj->value_size,
+ "knod hash elems are variable-sized GPU map records");
+ return 0;
+ }
+ unsigned int real_next = e->next & KNOD_BPF_HASH_NEXT_MASK;
+
+ if (real_next == KNOD_BPF_HASH_NEXT_END) {
+ ne = knod_bpf_map_hash_alloc_elem(knod_map,
+ knod_map_obj,
+ key, value);
+ if (!ne)
+ return -ENOMEM;
+ e->next = (e->next & KNOD_BPF_HASH_NEXT_DELETED) |
+ (((void *)ne - (void *)elems) / elem_size);
+ return 0;
+ }
+ e = elems + (real_next * elem_size);
+ }
+
+ return -ENOENT;
+}
+
+static int knod_bpf_map_hash_delete_elem(struct knod_bpf_map *knod_map,
+ struct knod_bpf_map_obj *knod_map_obj,
+ void *key)
+{
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ unsigned int *queue = knod_map->queue_mem->kaddr;
+ unsigned int hash, elem_id, elem_size, cur;
+ struct knod_bpf_hash_elem_obj *e, *pe;
+ unsigned int *bucket;
+
+ hash = jhash((const void *)key, knod_map_obj->key_size,
+ knod_map_obj->meta.hmeta.hashrnd);
+ hash = hash & (knod_map_obj->meta.hmeta.n_buckets - 1);
+ bucket = (unsigned int *)&knod_map_obj->bucket[0];
+
+ elem_id = bucket[hash];
+ if (elem_id == KNOD_BPF_HASH_NEXT_END)
+ return -ENOENT;
+
+ elem_size = knod_map_obj->meta.hmeta.elem_size;
+
+ e = elems + (elem_id * elem_size);
+ pe = e;
+ while (1) {
+ if (!(e->next & KNOD_BPF_HASH_NEXT_DELETED) &&
+ !memcmp(&e->kv[0], (const unsigned char *)key,
+ knod_map_obj->key_size)) {
+ unsigned int e_next = e->next & KNOD_BPF_HASH_NEXT_MASK;
+ unsigned int del_id = ((void *)e - elems) / elem_size;
+
+ /* Unlink (GPU is paused - safe) */
+ if (pe != e)
+ pe->next = (pe->next &
+ KNOD_BPF_HASH_NEXT_DELETED) |
+ e_next;
+ else
+ bucket[hash] = e_next;
+
+ e->next = KNOD_BPF_HASH_NEXT_END;
+
+ /* Return elem to queue */
+ cur = knod_map_obj->meta.hmeta.cur;
+ queue[cur] = del_id;
+ knod_map_obj->meta.hmeta.cur = cur + 1;
+ return 0;
+ }
+ unsigned int real_next = e->next & KNOD_BPF_HASH_NEXT_MASK;
+
+ if (real_next == KNOD_BPF_HASH_NEXT_END)
+ return -ENOENT;
+ pe = e;
+ e = elems + (real_next * elem_size);
+ }
+
+ return -ENOENT;
+}
+
+static int knod_bpf_map_hash_get_first_key(struct bpf_offloaded_map *offmap,
+ void *nkey)
+{
+ struct knod_bpf_map *knod_map = (struct knod_bpf_map *)offmap->dev_priv;
+ struct knod_bpf_map_obj *knod_map_obj;
+ unsigned int *bucket, elem_size, i;
+ struct knod_bpf_hash_elem_obj *e;
+ void *elems;
+
+ knod_map_obj = knod_map->knod_map_obj;
+ bucket = (unsigned int *)&knod_map_obj->bucket[0];
+ elems = knod_map->hash_elems_mem->kaddr;
+
+ elem_size = knod_map_obj->meta.hmeta.elem_size;
+
+ for (i = 0; i < knod_map_obj->meta.hmeta.n_buckets; i++) {
+ unsigned int eid;
+
+ if (bucket[i] == KNOD_BPF_HASH_NEXT_END)
+ continue;
+ eid = bucket[i];
+ while (eid != KNOD_BPF_HASH_NEXT_END) {
+ e = elems + (eid * elem_size);
+ if (!(e->next & KNOD_BPF_HASH_NEXT_DELETED)) {
+ unsafe_memcpy(nkey, knod_bpf_hash_elem_kv(e),
+ knod_map_obj->key_size,
+ "knod hash elems are variable-sized GPU map records");
+ return 0;
+ }
+ eid = e->next & KNOD_BPF_HASH_NEXT_MASK;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int knod_bpf_map_hash_get_next_key(struct bpf_offloaded_map *offmap,
+ void *key, void *nkey)
+{
+ struct knod_bpf_map *knod_map = (struct knod_bpf_map *)offmap->dev_priv;
+ struct knod_bpf_map_obj *knod_map_obj;
+ unsigned int *bucket, elem_size, i;
+ struct knod_bpf_hash_elem_obj *e;
+ bool found = false;
+ unsigned int hash;
+ void *elems;
+
+ knod_map_obj = knod_map->knod_map_obj;
+
+ bucket = (unsigned int *)&knod_map_obj->bucket[0];
+ elems = knod_map->hash_elems_mem->kaddr;
+
+ hash = jhash((const void *)key, knod_map_obj->key_size,
+ knod_map_obj->meta.hmeta.hashrnd);
+ hash = hash & (knod_map_obj->meta.hmeta.n_buckets - 1);
+ elem_size = knod_map_obj->meta.hmeta.elem_size;
+
+ for (i = hash; i < knod_map_obj->meta.hmeta.n_buckets; i++) {
+ unsigned int eid;
+
+ if (bucket[i] == KNOD_BPF_HASH_NEXT_END)
+ continue;
+
+ eid = bucket[i];
+ while (eid != KNOD_BPF_HASH_NEXT_END) {
+ e = elems + (eid * elem_size);
+ if (!(e->next & KNOD_BPF_HASH_NEXT_DELETED)) {
+ if (found &&
+ memcmp(&e->kv[0],
+ (const unsigned char *)key,
+ knod_map_obj->key_size)) {
+ unsafe_memcpy(nkey,
+ knod_bpf_hash_elem_kv(e),
+ knod_map_obj->key_size,
+ "knod hash elems are variable-sized GPU map records");
+ return 0;
+ }
+ if (!memcmp(&e->kv[0],
+ (const unsigned char *)key,
+ knod_map_obj->key_size))
+ found = true;
+ }
+ eid = e->next & KNOD_BPF_HASH_NEXT_MASK;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static void knod_bpf_map_free(struct knod_dev *knodev,
+ struct bpf_offloaded_map *offmap)
+{
+ struct knod_bpf_map *knod_map = offmap->dev_priv;
+ struct knod_bpf_priv *priv = knodev->accel->xdp.priv;
+
+ if (!knod_map)
+ return;
+ /*
+ * Defer the BO free: an in-flight prog dispatch may still reference
+ * this map's VRAM. Move it from bound_maps onto dead_maps under
+ * knodev->lock (the lock that guards the add); the worker reaps it
+ * from there after its next completion, by which point the in-flight
+ * dispatch on the old slot has retired (clean atomic flip).
+ */
+ mutex_lock(&knodev->lock);
+ list_del(&knod_map->list);
+ list_add(&knod_map->list, &priv->dead_maps);
+ mutex_unlock(&knodev->lock);
+ offmap->dev_priv = NULL;
+}
+
+static int __knod_bpf_map_lookup_elem(struct bpf_offloaded_map *offmap,
+ void *key, void *value)
+{
+ unsigned int idx = *(unsigned int *)key;
+ struct knod_bpf_map_obj *knod_map_obj;
+ struct knod_bpf_map *knod_map;
+ void *bucket;
+ u32 stride;
+ int i;
+
+ knod_map = (struct knod_bpf_map *)offmap->dev_priv;
+ if (!knod_map || !knod_map->mem || !knod_map->mem->kaddr ||
+ (knod_map->hash_elems_mem && !knod_map->hash_elems_mem->kaddr)) {
+ pr_err("knod_bpf: lookup on freed/invalid map (dev_priv=%p)\n",
+ offmap->dev_priv);
+ return -ENODEV;
+ }
+ knod_map_obj = knod_map->knod_map_obj;
+
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_ARRAY) {
+ if (*(unsigned int *)key >= knod_map_obj->max_entries)
+ return -ENOENT;
+ bucket = knod_bpf_array_value_ptr(knod_map_obj, idx);
+
+ unsafe_memcpy(value, bucket, knod_map_obj->value_size,
+ "knod array values live in a variable-sized GPU map tail");
+ } else if (knod_map_obj->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ if (idx >= knod_map_obj->max_entries)
+ return -ENOENT;
+ stride = round_up(knod_map_obj->value_size, 8);
+ bucket = &knod_map_obj->bucket[0];
+ bucket += (idx * knod_map_obj->value_size);
+ for (i = 0; i < knod_map_obj->meta.ameta.n_instances; i++)
+ unsafe_memcpy(value + i * stride,
+ bucket + i *
+ knod_map_obj->meta.ameta
+ .per_instance_size,
+ knod_map_obj->value_size,
+ "knod percpu array values live in a variable-sized GPU map tail");
+ } else if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ return knod_bpf_map_hash_lookup_elem(knod_map, knod_map_obj,
+ key, value);
+ }
+
+ return 0;
+}
+
+static void knod_bpf_map_op_begin(struct knod_bpf_priv *priv)
+{
+ /*
+ * Serialize concurrent map ops but do NOT park the worker: stopping it
+ * mid-flight strands the in-flight dispatch and stalls the GPU compute
+ * queue. A map value updated while the GPU reads it may be seen torn,
+ * which is a transient inconsistency the BPF prog tolerates.
+ */
+ mutex_lock(&priv->map_op_lock);
+}
+
+static void knod_bpf_map_op_end(struct knod_bpf_priv *priv)
+{
+ knod_bpf_gpu_mem_fence(priv);
+ mutex_unlock(&priv->map_op_lock);
+}
+
+static int __knod_bpf_map_update_elem(struct bpf_offloaded_map *offmap,
+ void *key, void *value, u64 flags)
+{
+ struct knod_bpf_map *knod_map = (struct knod_bpf_map *)offmap->dev_priv;
+ struct knod_bpf_map_obj *knod_map_obj;
+ struct knod_bpf_priv *priv;
+ unsigned int idx = *(unsigned int *)key;
+ struct knod_dev *knodev;
+ void *bucket;
+ u32 stride;
+ int i;
+
+ if (!knod_map || !knod_map->mem || !knod_map->mem->kaddr)
+ return -ENODEV;
+ knod_map_obj = knod_map->knod_map_obj;
+ priv = knod_map->priv;
+ knodev = priv->knodev;
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_ARRAY) {
+ if (idx >= knod_map_obj->max_entries)
+ return -ENOENT;
+
+ bucket = knod_bpf_array_value_ptr(knod_map_obj, idx);
+ unsafe_memcpy(bucket, value, knod_map_obj->value_size,
+ "knod array values live in a variable-sized GPU map tail");
+ knod_bpf_gpu_mem_fence(priv);
+ return 0;
+ } else if (knod_map_obj->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ if (idx >= knod_map_obj->max_entries)
+ return -ENOENT;
+ stride = round_up(knod_map_obj->value_size, 8);
+ bucket = &knod_map_obj->bucket[0];
+ bucket += (idx * knod_map_obj->value_size);
+ for (i = 0; i < knod_map_obj->meta.ameta.n_instances; i++)
+ unsafe_memcpy(bucket + i *
+ knod_map_obj->meta.ameta
+ .per_instance_size,
+ value + i * stride,
+ knod_map_obj->value_size,
+ "knod percpu array values live in a variable-sized GPU map tail");
+ knod_bpf_gpu_mem_fence(priv);
+ return 0;
+ } else if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ int ret = -ENOENT;
+
+ mutex_lock(&knodev->lock);
+ list_for_each_entry(knod_map, &knodev->accel->xdp.bound_maps,
+ list) {
+ if (knod_map->knod_map_obj == knod_map_obj) {
+ mutex_unlock(&knodev->lock);
+ knod_bpf_map_op_begin(priv);
+ ret = knod_bpf_map_hash_update_elem(knod_map,
+ knod_map_obj,
+ key, value);
+ knod_bpf_map_op_end(priv);
+ return ret;
+ }
+ }
+ mutex_unlock(&knodev->lock);
+ }
+
+ return -ENOENT;
+}
+
+static int __knod_bpf_map_delete_elem(struct bpf_offloaded_map *offmap,
+ void *key)
+{
+ struct knod_bpf_map *knod_map = (struct knod_bpf_map *)offmap->dev_priv;
+ struct knod_bpf_map_obj *knod_map_obj;
+ struct knod_bpf_priv *priv;
+ int ret;
+
+ if (!knod_map || !knod_map->mem || !knod_map->mem->kaddr)
+ return -ENODEV;
+ knod_map_obj = knod_map->knod_map_obj;
+ priv = knod_map->priv;
+
+ if (knod_map_obj->map_type == BPF_MAP_TYPE_ARRAY ||
+ knod_map_obj->map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
+ return 0;
+ else if (knod_map_obj->map_type == BPF_MAP_TYPE_HASH) {
+ knod_bpf_map_op_begin(priv);
+ ret = knod_bpf_map_hash_delete_elem(knod_map, knod_map_obj,
+ key);
+ knod_bpf_map_op_end(priv);
+ return ret;
+ }
+
+ return -ENOENT;
+}
+
+static void knod_bpf_map_gc_process(struct knod_bpf_map *knod_map)
+{
+ struct knod_bpf_map_obj *knod_map_obj = knod_map->knod_map_obj;
+ unsigned int *gc_list = knod_map->gc_mem->kaddr;
+ unsigned int *queue = knod_map->queue_mem->kaddr;
+ void *elems = knod_map->hash_elems_mem->kaddr;
+ unsigned int *bucket = (unsigned int *)&knod_map_obj->bucket[0];
+ unsigned int elem_size = knod_map_obj->meta.hmeta.elem_size;
+ unsigned int gc_count, cur, i;
+
+ gc_count = READ_ONCE(knod_map_obj->meta.hmeta.gc_count);
+ if (!gc_count)
+ return;
+
+ for (i = 0; i < gc_count; i++) {
+ unsigned int del_id = gc_list[i];
+ struct knod_bpf_hash_elem_obj *del_elem =
+ elems + (del_id * elem_size);
+ unsigned int hash, eid;
+ struct knod_bpf_hash_elem_obj *e, *pe;
+
+ hash = jhash(&del_elem->kv[0], knod_map_obj->key_size,
+ knod_map_obj->meta.hmeta.hashrnd);
+ hash = hash & (knod_map_obj->meta.hmeta.n_buckets - 1);
+
+ eid = bucket[hash];
+ pe = NULL;
+ while (eid != KNOD_BPF_HASH_NEXT_END) {
+ e = elems + (eid * elem_size);
+ if (e == del_elem) {
+ unsigned int next = e->next &
+ KNOD_BPF_HASH_NEXT_MASK;
+ if (pe)
+ pe->next =
+ (pe->next &
+ KNOD_BPF_HASH_NEXT_DELETED) |
+ next;
+ else
+ bucket[hash] = next;
+
+ e->next = KNOD_BPF_HASH_NEXT_END;
+
+ cur = knod_map_obj->meta.hmeta.cur;
+ queue[cur] = del_id;
+ knod_map_obj->meta.hmeta.cur = cur + 1;
+ break;
+ }
+ pe = e;
+ eid = e->next & KNOD_BPF_HASH_NEXT_MASK;
+ }
+ }
+
+ WRITE_ONCE(knod_map_obj->meta.hmeta.gc_count, 0);
+}
+
+/*
+ * Per-loop map maintenance, run from the worker loop head (outside any
+ * rcu_read_lock_bh, since knod_free_mem() may sleep). All bound_maps access
+ * is serialized under knodev->lock -- the same lock map_alloc/map_free use:
+ * GC live HASH maps, then reap maps that detach moved onto dead_maps. The
+ * worker only reaches here after completing the previous dispatch, so the
+ * clean atomic flip guarantees the GPU no longer reads a reaped map's BOs.
+ */
+#define KNOD_BPF_MAPS_TICK_INTERVAL 65536
+
+static void knod_bpf_maps_tick(struct knod_bpf_priv *priv)
+{
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_map *knod_map, *tmp;
+ LIST_HEAD(reap);
+
+ if (list_empty(&knodev->accel->xdp.bound_maps) &&
+ list_empty(&priv->dead_maps))
+ return;
+
+ if (list_empty(&priv->dead_maps) &&
+ (++priv->maps_tick_skip & (KNOD_BPF_MAPS_TICK_INTERVAL - 1)))
+ return;
+
+ mutex_lock(&knodev->lock);
+ list_for_each_entry(knod_map, &knodev->accel->xdp.bound_maps, list) {
+ if (knod_map->knod_map_obj->map_type == BPF_MAP_TYPE_HASH)
+ knod_bpf_map_gc_process(knod_map);
+ }
+ list_splice_init(&priv->dead_maps, &reap);
+ mutex_unlock(&knodev->lock);
+
+ list_for_each_entry_safe(knod_map, tmp, &reap, list) {
+ if (knod_map->gc_mem)
+ knod_free_mem(priv->knod, knod_map->gc_mem);
+ if (knod_map->queue_mem)
+ knod_free_mem(priv->knod, knod_map->queue_mem);
+ if (knod_map->hash_elems_mem)
+ knod_free_mem(priv->knod, knod_map->hash_elems_mem);
+ if (knod_map->mem)
+ knod_free_mem(priv->knod, knod_map->mem);
+ kfree(knod_map);
+ }
+}
+
+/* Completion mode: 0 = event (default, sleep on the AQL signal interrupt),
+ * 1 = poll (busy-spin the signal value). Selectable via debugfs.
+ */
+static bool knod_bpf_poll_mode;
+
+/* Max spacing (microseconds) between dispatch-ahead submissions. Once a
+ * dispatch is in flight the worker waits up to this long before submitting
+ * the next so it batches the packets arriving meanwhile, letting inflight
+ * grow >= 2 without degenerating into one-packet dispatches. This is a
+ * ceiling only: an empty pipe submits at once to keep the GPU fed, and a
+ * completed dispatch is always retired without waiting. To actually build
+ * depth the value must be below the GPU execution time of a dispatch.
+ * 0 disables spacing (submit as soon as the ring has anything).
+ */
+static u32 knod_bpf_dispatch_delay_us = 20;
+
+static void knod_bpf_wait_event(struct knod_bpf_priv *priv)
+{
+ struct kfd_event_data events = {
+ .event_id = priv->knod->aql_event[0].id,
+ };
+ u32 timeout_ms = knod_bpf_expire;
+ u32 wait_result;
+
+ knod_wait_on_events(priv->knod->process, 1, &events, true,
+ &timeout_ms, &wait_result);
+}
+
+static bool knod_bpf_submit_work(struct knod_bpf_priv *priv)
+{
+ struct knod_bpf_work_sq *sqw;
+ struct knod_bpf_stats *stats = &priv->stats;
+ ktime_t dispatch_start;
+
+ if (priv->inflight_cnt >= KNOD_BPF_INFLIGHT)
+ return false;
+
+ /* Pace dispatch-ahead so the next dispatch batches the packets that
+ * arrive during this window instead of firing one-packet dispatches.
+ * An empty pipe skips the wait so the GPU is never left idle.
+ */
+ if (priv->inflight_cnt &&
+ ktime_before(ktime_get(), priv->next_dispatch_time))
+ return false;
+
+ if (static_branch_unlikely(&knod_stats_key))
+ dispatch_start = ktime_get();
+
+ sqw = knod_prepare_bpf(priv);
+ if (!sqw)
+ return false;
+
+ if (static_branch_unlikely(&knod_stats_key)) {
+ u64 dns = ktime_to_ns(ktime_sub(ktime_get(),
+ dispatch_start));
+
+ stats->dispatch_total_ns += dns;
+ stats->dispatch_count++;
+ if (dns > stats->dispatch_max_ns)
+ stats->dispatch_max_ns = dns;
+ }
+
+ knod_submit_bpf(priv, sqw);
+ priv->inflight[priv->inflight_cnt++] = sqw;
+ priv->next_dispatch_time =
+ ktime_add_us(ktime_get(),
+ READ_ONCE(knod_bpf_dispatch_delay_us));
+ return true;
+}
+
+static void knod_bpf_record_completion(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct knod_bpf_stats *stats = &priv->stats;
+ u64 ns;
+ int bucket;
+
+ if (!static_branch_unlikely(&knod_stats_key))
+ return;
+
+ ns = ktime_to_ns(ktime_sub(ktime_get(), sqw->dispatch_time));
+ stats->completion_total_ns += ns;
+ stats->completion_count++;
+
+ if (ns > stats->completion_max_ns)
+ stats->completion_max_ns = ns;
+
+ if (ns < 1000)
+ bucket = 0;
+ else
+ bucket = min(ilog2(ns / 1000) + 1,
+ KNOD_LAT_BUCKETS - 1);
+ stats->completion_hist[bucket]++;
+}
+
+static bool knod_bpf_poll_complete(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ struct amd_signal *signal;
+
+ if (!sqw)
+ return false;
+
+ signal = (struct amd_signal *)
+ priv->knod->kaql[0].queue_signal->kaddr;
+
+ if (sqw->sigval > READ_ONCE(signal->value)) {
+ knod_bpf_record_completion(priv, sqw);
+ return true;
+ }
+
+ if (time_after(jiffies, sqw->expire)) {
+ pr_warn_ratelimited("knod_bpf: poll expire (sigval=%lld signal=%lld expire_ms=%u)\n",
+ sqw->sigval, READ_ONCE(signal->value), knod_bpf_expire);
+ knod_bpf_record_completion(priv, sqw);
+ return true;
+ }
+
+ return false;
+}
+
+static void knod_bpf_schedule_pending_napi(struct knod_bpf_priv *priv)
+{
+ struct knod_dev *knodev = priv->knodev;
+ int qi;
+
+ for (qi = 0; qi < priv->nr_works; qi++) {
+ if (spsc_pending(&knodev->wpriv[qi].spsc_bds) &&
+ knodev->wpriv[qi].napi)
+ napi_schedule(knodev->wpriv[qi].napi);
+ }
+}
+
+static int knod_bpf_worker(void *arg)
+{
+ struct knod_bpf_priv *priv = arg;
+ struct knod_bpf_work_sq *sqw;
+ bool progressed;
+
+ while (!kthread_should_stop()) {
+ if (kthread_should_park()) {
+ knod_bpf_drain_worker(priv);
+ kthread_parkme();
+ continue;
+ }
+
+ knod_bpf_maps_tick(priv);
+
+ progressed = false;
+
+ rcu_read_lock_bh();
+ /* Retire completed dispatches oldest-first: the signal is
+ * monotonic so inflight[0] finishes before inflight[1..].
+ */
+ while (priv->inflight_cnt &&
+ knod_bpf_poll_complete(priv, priv->inflight[0])) {
+ sqw = priv->inflight[0];
+ if (--priv->inflight_cnt)
+ memmove(priv->inflight, priv->inflight + 1,
+ priv->inflight_cnt *
+ sizeof(priv->inflight[0]));
+ priv->inflight[priv->inflight_cnt] = NULL;
+ knod_complete_acquire(priv, sqw);
+ knod_complete_napi(priv, sqw);
+ progressed = true;
+ }
+
+ /* Keep the pipe full: dispatch ahead up to KNOD_BPF_INFLIGHT.
+ * Staging self-limits, so this stops once the ring is drained.
+ */
+ while (knod_bpf_submit_work(priv))
+ progressed = true;
+ rcu_read_unlock_bh();
+
+ if (!priv->inflight_cnt) {
+ knod_bpf_schedule_pending_napi(priv);
+ usleep_range(100, 200);
+ } else if (!progressed) {
+ /* Room to dispatch ahead but the pacing window has not
+ * opened yet: spin so the next submit fires on time and
+ * a completion is retired the instant it lands. Block
+ * on the event only when the pipe is full (nothing to
+ * submit) or spacing is disabled.
+ */
+ if (priv->inflight_cnt < KNOD_BPF_INFLIGHT &&
+ ktime_before(ktime_get(), priv->next_dispatch_time))
+ cpu_relax();
+ else if (knod_bpf_poll_mode)
+ cpu_relax();
+ else
+ knod_bpf_wait_event(priv);
+ }
+ }
+
+ return 0;
+}
+
+static void knod_bpf_sq_init(struct knod_bpf_priv *priv)
+{
+ struct knod_bpf_work_sq *sqw;
+ int i;
+
+ priv->worker_task = NULL;
+ priv->inflight_cnt = 0;
+ INIT_LIST_HEAD(&priv->free_list_sqw);
+
+ for (i = 0; i < 32; i++) {
+ sqw = kvzalloc_obj(struct knod_bpf_work_sq, GFP_KERNEL);
+ if (!sqw)
+ continue;
+
+ sqw->param = knod_alloc_mem(priv->knod,
+ sizeof(struct knod_bpf_param),
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT);
+ if (!sqw->param) {
+ kvfree(sqw);
+ continue;
+ }
+ memset(sqw->param->kaddr, 0, sizeof(struct knod_bpf_param));
+ INIT_LIST_HEAD(&sqw->list);
+ list_add(&sqw->list, &priv->free_list_sqw);
+ sqw->backlogs = 0;
+ }
+}
+
+static void knod_bpf_free_sqw(struct knod_bpf_priv *priv,
+ struct knod_bpf_work_sq *sqw)
+{
+ if (!sqw)
+ return;
+
+ knod_free_mem(priv->knod, sqw->param);
+ kfree(sqw);
+}
+
+static void knod_bpf_free_sqw_list(struct knod_bpf_priv *priv,
+ struct list_head *head)
+{
+ struct knod_bpf_work_sq *sqw, *tmp;
+
+ list_for_each_entry_safe(sqw, tmp, head, list) {
+ list_del(&sqw->list);
+ knod_bpf_free_sqw(priv, sqw);
+ }
+}
+
+static void knod_bpf_sq_exit(struct knod_bpf_priv *priv)
+{
+ if (!priv->knod)
+ return;
+
+ knod_bpf_stop_worker(priv);
+ knod_bpf_drain(priv);
+
+ knod_bpf_free_sqw_list(priv, &priv->free_list_sqw);
+ priv->inflight_cnt = 0;
+}
+
+static void knod_priv_exit(struct knod_bpf_priv *priv)
+{
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_map *knod_map, *tmp;
+ LIST_HEAD(reap);
+
+ knod_bpf_sq_exit(priv);
+
+ /*
+ * The dispatch worker is not stopped until the next feature registers
+ * its own worker, so it may still be running knod_bpf_maps_tick() here.
+ * Serialize under knodev->lock and splice both lists to a local one:
+ * whichever side splices first frees them, the other sees them empty.
+ * Free outside the lock since knod_free_mem() may sleep.
+ */
+ mutex_lock(&knodev->lock);
+ list_splice_init(&knodev->accel->xdp.bound_maps, &reap);
+ list_splice_init(&priv->dead_maps, &reap);
+ mutex_unlock(&knodev->lock);
+
+ list_for_each_entry_safe(knod_map, tmp, &reap, list) {
+ if (knod_map->gc_mem)
+ knod_free_mem(priv->knod, knod_map->gc_mem);
+ if (knod_map->queue_mem)
+ knod_free_mem(priv->knod, knod_map->queue_mem);
+ if (knod_map->hash_elems_mem)
+ knod_free_mem(priv->knod, knod_map->hash_elems_mem);
+ if (knod_map->mem)
+ knod_free_mem(priv->knod, knod_map->mem);
+ kfree(knod_map);
+ }
+
+ kfree(priv->prog_buf);
+ kfree(priv->pass_prog_buf);
+ if (priv->pass_knod_prog) {
+ knod_prog_free(priv->pass_knod_prog);
+ priv->pass_knod_prog = NULL;
+ }
+ /* kernels[] are owned by knod (freed in knod_release_ctx), not here */
+ if (priv->pass_meta_buf)
+ knod_free_mem(priv->knod, priv->pass_meta_buf);
+}
+
+static int knod_priv_init(struct knod_bpf_priv *priv)
+{
+ struct knod_dev *knodev = priv->knodev;
+ int pass_meta_buf_size;
+ int index;
+
+ priv->prog = NULL;
+ mutex_init(&priv->map_op_lock);
+ INIT_LIST_HEAD(&priv->dead_maps);
+ priv->maps_tick_skip = 0;
+
+ priv->nr_works = knod_bpf_active_rxq_count(knodev->netdev);
+ if (!priv->nr_works) {
+ pr_warn("knod_bpf: no active RX queues for %s\n",
+ knodev->netdev ? knodev->netdev->name : "<null>");
+ return -EINVAL;
+ }
+
+ priv->prog_buf = kzalloc(KNOD_BPF_PROG_BUF_SIZE, GFP_KERNEL);
+ if (!priv->prog_buf)
+ return -ENOMEM;
+
+ for (index = 0; index < priv->nr_works; index++)
+ priv->queue_base_gaddr[index] = priv->knod->buf[index]->gaddr;
+
+ /* Per-queue PASS slot count; sizes the shader pass_meta_buf below.
+ * At most one PASS packet per dispatched slot, i.e. batch_size.
+ */
+ priv->pass_pkts_per_queue = knod_bpf_batch_size(priv);
+
+ /* Allocate GTT buffer for per-queue shader PASS copy */
+ pass_meta_buf_size = priv->nr_works * priv->pass_pkts_per_queue *
+ KNOD_PASS_SLOT_SIZE;
+ priv->pass_meta_buf = knod_alloc_mem(priv->knod, pass_meta_buf_size,
+ KFD_IOC_ALLOC_MEM_FLAGS_GTT |
+ KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE |
+ KFD_IOC_ALLOC_MEM_FLAGS_COHERENT);
+ if (IS_ERR(priv->pass_meta_buf)) {
+ pr_warn("KNOD: failed to allocate pass_meta_buf\n");
+ priv->pass_meta_buf = NULL;
+ knod_priv_exit(priv);
+ return -ENOMEM;
+ }
+ pr_debug("KNOD: pass_meta_buf gaddr=0x%llx..0x%llx size=%d nr_q=%d pass_pkts_per_queue=%u\n",
+ priv->pass_meta_buf->gaddr,
+ priv->pass_meta_buf->gaddr + pass_meta_buf_size,
+ priv->pass_meta_buf->size, priv->nr_works,
+ priv->pass_pkts_per_queue);
+
+ /* GPU->host delivery pages come from the framework per-queue page_pool
+ * (knodev->wpriv[q].pass_pool): the producer allocs from it and the
+ * NAPI drain recycles, so no per-feature delivery BO is allocated here.
+ */
+
+ knod_bpf_sq_init(priv);
+
+ return 0;
+}
+
+static struct knod_bpf_priv *__knod_accel_xdp_init(struct knod_accel *accel,
+ struct knod_dev *knodev)
+{
+ struct knod *knod = (struct knod *)knodev->accel->priv;
+ struct knod_bpf_priv *priv;
+
+ priv = kzalloc_obj(struct knod_bpf_priv, GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&priv->list);
+ if (knod_bpf_workgroups % 64) {
+ knod_bpf_workgroups /= 64;
+ knod_bpf_workgroups++;
+ knod_bpf_workgroups *= 64;
+ }
+
+ if (knod_bpf_workgroups < KNOD_BPF_WORKGROUPS_MIN ||
+ knod_bpf_workgroups > KNOD_BPF_WORKGROUPS_MAX)
+ knod_bpf_workgroups = KNOD_BPF_WORKGROUPS_DEFAULT;
+
+ if (knod_bpf_expire < KNOD_BPF_EXPIRE_MIN ||
+ knod_bpf_expire > KNOD_BPF_EXPIRE_MAX)
+ knod_bpf_expire = KNOD_BPF_EXPIRE_DEFAULT;
+ pr_debug("workgroup size %d\n", knod_bpf_workgroups);
+ pr_debug("expire time = %dms\n", knod_bpf_expire);
+ pr_debug("packet cache = %d", knod_bpf_pkt_cache);
+
+ INIT_LIST_HEAD(&accel->xdp.bound_maps);
+ accel->flags |= KNOD_FLAGS_XDP;
+ accel->xdp.priv = priv;
+ list_add(&priv->list, &priv_list);
+
+ priv->knod = knod;
+ priv->accel = accel;
+ priv->knodev = knodev;
+ priv->dev = knodev->netdev;
+
+ priv->isa_version = knod->isa_version;
+
+ /*
+ * Only permanent per-attach state is set up here; the GPU compute
+ * buffers (knod_priv_init/kfd_kernel_init) are allocated by
+ * ->activate() when the BPF feature is selected.
+ */
+
+ return priv;
+}
+
+/* Feature select: allocate the BPF GPU compute resources. */
+static int knod_bpf_activate(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv = accel->xdp.priv;
+ struct knod *knod = accel->priv;
+
+ /*
+ * Pin the module while BPF is the selected feature: the core calls
+ * into these ops, so it must not be unloaded until feature->none.
+ * (No-op when built in - THIS_MODULE is NULL.)
+ */
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ if (knod_priv_init(priv)) {
+ WARN_ON_ONCE(1);
+ module_put(THIS_MODULE);
+ return -EINVAL;
+ }
+ if (kfd_kernel_init(knod, priv)) {
+ knod_priv_exit(priv);
+ module_put(THIS_MODULE);
+ return -ENOMEM;
+ }
+
+ priv->start = 0;
+ return 0;
+}
+
+/* Feature deselect: free the BPF GPU compute resources. */
+static void knod_bpf_deactivate(struct knod_dev *knodev)
+{
+ struct knod_bpf_priv *priv = knodev->accel->xdp.priv;
+
+ knod_priv_exit(priv);
+ module_put(THIS_MODULE);
+}
+
+/* True while a user XDP prog or offloaded map is still bound to this accel. */
+static bool knod_bpf_busy(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv = accel->xdp.priv;
+
+ if (!priv)
+ return false;
+ return READ_ONCE(priv->prog) || !list_empty(&accel->xdp.bound_maps);
+}
+
+static void __knod_accel_xdp_exit(struct knod_accel *accel,
+ struct knod_bpf_priv *priv)
+{
+ /* GPU compute buffers are freed by ->deactivate(); free the rest. */
+ memset(&accel->xdp, 0, sizeof(struct knod_accel_xdp));
+ accel->flags &= ~KNOD_FLAGS_XDP;
+ list_del(&priv->list);
+ kfree(priv);
+}
+
+static struct knod_insn_meta *knod_bpf_goto_meta(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ unsigned int insn_idx)
+{
+ unsigned int forward, backward, i;
+
+ backward = meta->bpf_insn_idx - insn_idx;
+ forward = insn_idx - meta->bpf_insn_idx;
+
+ if (min(forward, backward) > knod_prog->n_insns - insn_idx - 1) {
+ backward = knod_prog->n_insns - insn_idx - 1;
+ meta = knod_prog_last_meta(knod_prog);
+ }
+ if (min(forward, backward) > insn_idx && backward > insn_idx) {
+ forward = insn_idx;
+ meta = knod_prog_first_meta(knod_prog);
+ }
+
+ if (forward < backward)
+ for (i = 0; i < forward; i++)
+ meta = knod_meta_next(meta);
+ else
+ for (i = 0; i < backward; i++)
+ meta = knod_meta_prev(meta);
+
+ return meta;
+}
+
+static int knod_bpf_check_stack_access(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ const struct bpf_reg_state *reg,
+ struct bpf_verifier_env *env)
+{
+ s32 old_off, new_off;
+
+ if (reg->frameno != env->cur_state->curframe)
+ meta->flags |= FLAG_INSN_PTR_CALLER_STACK_FRAME;
+
+ if (!tnum_is_const(reg->var_off)) {
+ knod_jit_dbg(" variable ptr stack access\n");
+ return -EINVAL;
+ }
+
+ if (meta->ptr.type == NOT_INIT)
+ return 0;
+
+ old_off = meta->ptr.var_off.value;
+ new_off = reg->var_off.value;
+
+ meta->ptr_not_const |= old_off != new_off;
+
+ if (!meta->ptr_not_const)
+ return 0;
+
+ if (old_off % 4 == new_off % 4)
+ return 0;
+
+ knod_jit_dbg(" stack access changed location was:%d is:%d\n",
+ old_off, new_off);
+ return -EINVAL;
+}
+
+static struct knod_insn_meta *
+knod_bpf_lookup_prev_meta_by_dreg(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ int dreg_id)
+{
+ list_for_each_entry_continue_reverse(meta, &knod_prog->insns, l) {
+ if (!is_mbpf_alu(meta) &&
+ !is_mbpf_load(meta) &&
+ !is_mbpf_store(meta))
+ continue;
+ if (meta->insn.dst_reg == dreg_id)
+ return meta;
+ }
+
+ return NULL;
+}
+
+static int knod_bpf_check_ptr(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ struct bpf_verifier_env *env, u8 reg_no)
+{
+ const struct bpf_reg_state *reg = cur_regs(env) + reg_no;
+ int err;
+
+ if (reg->type != PTR_TO_CTX &&
+ reg->type != PTR_TO_STACK &&
+ reg->type != PTR_TO_MAP_VALUE &&
+ reg->type != PTR_TO_PACKET) {
+ knod_jit_dbg(" unsupported ptr type: %d\n", reg->type);
+ return -EINVAL;
+ }
+
+ if (reg->type == PTR_TO_STACK) {
+ err = knod_bpf_check_stack_access(knod_prog, meta, reg, env);
+ if (err)
+ return err;
+ }
+
+ if (meta->ptr.type != NOT_INIT && meta->ptr.type != reg->type) {
+ knod_jit_dbg(" ptr type changed for instruction %d -> %d\n",
+ meta->ptr.type,
+ reg->type);
+ return -EINVAL;
+ }
+
+ meta->ptr = *reg;
+
+ return 0;
+}
+
+static int knod_bpf_update_ptr_off(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ struct bpf_verifier_env *env)
+{
+ struct knod_bpf_reg_state *sreg = &meta->sreg;
+ struct knod_bpf_reg_state *dreg = &meta->dreg;
+ struct knod_insn_meta *prev_meta;
+
+ if (is_mbpf_load(meta)) {
+ if (sreg->reg.type == PTR_TO_PACKET ||
+ sreg->reg.type == PTR_TO_STACK) {
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.src_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ if (sreg->reg.type == PTR_TO_PACKET)
+ sreg->packet_off = prev_meta->dreg.packet_off;
+ else
+ sreg->stack_off = prev_meta->dreg.stack_off;
+ }
+ } else if (is_mbpf_store(meta)) {
+ if (dreg->reg.type == PTR_TO_PACKET ||
+ dreg->reg.type == PTR_TO_PACKET) {
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ if (dreg->reg.type == PTR_TO_PACKET)
+ dreg->packet_off = prev_meta->dreg.packet_off;
+ else
+ dreg->stack_off = prev_meta->dreg.stack_off;
+ }
+ }
+
+ return 0;
+}
+
+static int knod_bpf_check_store(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ struct bpf_verifier_env *env)
+{
+ const struct bpf_reg_state *reg = cur_regs(env) + meta->insn.dst_reg;
+
+ if (reg->type == PTR_TO_CTX) {
+ if (knod_prog->type == BPF_PROG_TYPE_XDP) {
+ /* XDP ctx accesses must be 4B in size */
+ switch (meta->insn.off) {
+ case offsetof(struct xdp_md, rx_queue_index):
+ knod_jit_dbg(" queue selection not supported by FW\n");
+ return -EOPNOTSUPP;
+ }
+ }
+ knod_jit_dbg(" unsupported store to context field\n");
+ return -EOPNOTSUPP;
+ }
+
+ return knod_bpf_check_ptr(knod_prog, meta, env, meta->insn.dst_reg);
+}
+
+/* NOTE:
+ * knod_bpf_lookup_prev_meta_by_dreg(), src_reg vs dst_reg ???????/
+ */
+static int knod_bpf_check_alu(struct knod_prog *knod_prog,
+ struct knod_insn_meta *meta,
+ struct bpf_verifier_env *env)
+{
+ const struct bpf_reg_state *sreg = cur_regs(env) + meta->insn.src_reg;
+ const struct bpf_reg_state *dreg = cur_regs(env) + meta->insn.dst_reg;
+ struct knod_bpf_reg_state *ksreg = &meta->sreg;
+ struct knod_bpf_reg_state *kdreg = &meta->dreg;
+ struct knod_insn_meta *prev_meta;
+ int imm;
+
+ meta->umin_src = min(meta->umin_src, reg_umin(sreg));
+ meta->umax_src = max(meta->umax_src, reg_umax(sreg));
+ meta->umin_dst = min(meta->umin_dst, reg_umin(dreg));
+ meta->umax_dst = max(meta->umax_dst, reg_umax(dreg));
+
+ /* AMDGPU doesn't have divide instructions, we support divide by
+ * constant through reciprocal multiplication. Given NFP support
+ * multiplication no bigger than u32, we'd require divisor and dividend
+ * no bigger than that as well.
+ *
+ * Also eBPF doesn't support signed divide and has enforced this on C
+ * language level by failing compilation. However LLVM assembler hasn't
+ * enforced this, so it is possible for negative constant to leak in as
+ * a BPF_K operand through assembly code, we reject such cases as well.
+ */
+ if (is_mbpf_div(meta)) {
+ if (meta->umax_dst > U32_MAX) {
+ knod_jit_dbg(" dividend is not within u32 value range\n");
+ return -EINVAL;
+ }
+ if (mbpf_src(meta) == BPF_X) {
+ if (meta->umin_src != meta->umax_src) {
+ knod_jit_dbg(" divisor is not constant\n");
+ return -EINVAL;
+ }
+ if (meta->umax_src > U32_MAX) {
+ knod_jit_dbg(" divisor is not within u32 value range\n");
+ return -EINVAL;
+ }
+ }
+ if (mbpf_src(meta) == BPF_K && meta->insn.imm < 0) {
+ knod_jit_dbg(" divide by negative constant is not supported\n");
+ return -EINVAL;
+ }
+ }
+
+ if (dreg->type == PTR_TO_STACK) {
+ imm = meta->insn.imm;
+
+ switch (meta->insn.code) {
+ /* ALU
+ * If a destination register contains a pointer of STACK,
+ * offset should not be minus.
+ */
+ case BPF_ALU | BPF_MOV | BPF_X:
+ case BPF_ALU64 | BPF_MOV | BPF_X:
+ //r[d] = r[s];
+ kdreg->stack_off = ksreg->stack_off;
+ break;
+ case BPF_ALU | BPF_MOV | BPF_K:
+ case BPF_ALU64 | BPF_MOV | BPF_K:
+ //r[d] = imm;
+ kdreg->stack_off = ksreg->stack_off;
+ break;
+ case BPF_ALU | BPF_XOR | BPF_X:
+ case BPF_ALU64 | BPF_XOR | BPF_X:
+ //r[d] ^= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_XOR | BPF_K:
+ case BPF_ALU64 | BPF_XOR | BPF_K:
+ //r[d] ^= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off ^ imm;
+ break;
+ case BPF_ALU | BPF_MOD | BPF_X:
+ case BPF_ALU64 | BPF_MOD | BPF_X:
+ //r[d] %= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_MOD | BPF_K:
+ case BPF_ALU64 | BPF_MOD | BPF_K:
+ //r[d] %= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off % imm;
+ break;
+ case BPF_ALU | BPF_AND | BPF_X:
+ case BPF_ALU64 | BPF_AND | BPF_X:
+ //r[d] &= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_AND | BPF_K:
+ case BPF_ALU64 | BPF_AND | BPF_K:
+ //r[d] &= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off & imm;
+ break;
+ case BPF_ALU | BPF_OR | BPF_X:
+ case BPF_ALU64 | BPF_OR | BPF_X:
+ //r[d] |= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_OR | BPF_K:
+ case BPF_ALU64 | BPF_OR | BPF_K:
+ //r[d] |= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off | imm;
+ break;
+ case BPF_ALU | BPF_ADD | BPF_X:
+ case BPF_ALU64 | BPF_ADD | BPF_X:
+ //r[d] += r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_ADD | BPF_K:
+ case BPF_ALU64 | BPF_ADD | BPF_K:
+ //r[d] += imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off + imm;
+ break;
+ case BPF_ALU | BPF_SUB | BPF_X:
+ case BPF_ALU64 | BPF_SUB | BPF_X:
+ //r[d] -= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_SUB | BPF_K:
+ case BPF_ALU64 | BPF_SUB | BPF_K:
+ //r[d] -= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off - imm;
+ break;
+ case BPF_ALU | BPF_MUL | BPF_X:
+ case BPF_ALU64 | BPF_MUL | BPF_X:
+ //r[d] *= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_MUL | BPF_K:
+ case BPF_ALU64 | BPF_MUL | BPF_K:
+ //r[d] *= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off * imm;
+ break;
+ case BPF_ALU | BPF_DIV | BPF_X:
+ case BPF_ALU64 | BPF_DIV | BPF_X:
+ //r[d] /= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_DIV | BPF_K:
+ case BPF_ALU64 | BPF_DIV | BPF_K:
+ //r[d] /= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off / imm;
+ break;
+ case BPF_ALU | BPF_NEG:
+ case BPF_ALU64 | BPF_NEG:
+ //r[d] = -r[d];
+ break;
+ case BPF_ALU | BPF_LSH | BPF_X:
+ case BPF_ALU64 | BPF_LSH | BPF_X:
+ //r[d] <<= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_LSH | BPF_K:
+ case BPF_ALU64 | BPF_LSH | BPF_K:
+ //r[d] <<= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off << imm;
+ break;
+ case BPF_ALU | BPF_RSH | BPF_X:
+ case BPF_ALU64 | BPF_RSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_RSH | BPF_K:
+ case BPF_ALU64 | BPF_RSH | BPF_K:
+ //r[d] >>= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off >> imm;
+ break;
+ case BPF_ALU | BPF_ARSH | BPF_X:
+ case BPF_ALU64 | BPF_ARSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_ARSH | BPF_K:
+ case BPF_ALU64 | BPF_ARSH | BPF_K:
+ //r[d] >>= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->stack_off = prev_meta->dreg.stack_off >> imm;
+ break;
+ }
+ knod_jit_dbg(" %d: dreg->stack_off = %d\n", meta->bpf_insn_idx,
+ kdreg->stack_off);
+ }
+
+ if (dreg->type == PTR_TO_PACKET) {
+ imm = meta->insn.imm;
+
+ switch (meta->insn.code) {
+ /* ALU
+ * If a destination register contains a pointer of STACK,
+ * offset should not be minus.
+ */
+ case BPF_ALU | BPF_MOV | BPF_X:
+ case BPF_ALU64 | BPF_MOV | BPF_X:
+ //r[d] = r[s];
+ kdreg->packet_off = ksreg->packet_off;
+ break;
+ case BPF_ALU | BPF_MOV | BPF_K:
+ case BPF_ALU64 | BPF_MOV | BPF_K:
+ //r[d] = imm;
+ kdreg->packet_off = ksreg->packet_off;
+ break;
+ case BPF_ALU | BPF_XOR | BPF_X:
+ case BPF_ALU64 | BPF_XOR | BPF_X:
+ //r[d] ^= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_XOR | BPF_K:
+ case BPF_ALU64 | BPF_XOR | BPF_K:
+ //r[d] ^= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off ^ imm;
+ break;
+ case BPF_ALU | BPF_MOD | BPF_X:
+ case BPF_ALU64 | BPF_MOD | BPF_X:
+ //r[d] %= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_MOD | BPF_K:
+ case BPF_ALU64 | BPF_MOD | BPF_K:
+ //r[d] %= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off % imm;
+ break;
+ case BPF_ALU | BPF_AND | BPF_X:
+ case BPF_ALU64 | BPF_AND | BPF_X:
+ //r[d] &= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_AND | BPF_K:
+ case BPF_ALU64 | BPF_AND | BPF_K:
+ //r[d] &= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off & imm;
+ break;
+ case BPF_ALU | BPF_OR | BPF_X:
+ case BPF_ALU64 | BPF_OR | BPF_X:
+ //r[d] |= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_OR | BPF_K:
+ case BPF_ALU64 | BPF_OR | BPF_K:
+ //r[d] |= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off | imm;
+ break;
+ case BPF_ALU | BPF_ADD | BPF_X:
+ case BPF_ALU64 | BPF_ADD | BPF_X:
+ //r[d] += r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_ADD | BPF_K:
+ case BPF_ALU64 | BPF_ADD | BPF_K:
+ //r[d] += imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off + imm;
+ break;
+ case BPF_ALU | BPF_SUB | BPF_X:
+ case BPF_ALU64 | BPF_SUB | BPF_X:
+ //r[d] -= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_SUB | BPF_K:
+ case BPF_ALU64 | BPF_SUB | BPF_K:
+ //r[d] -= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off - imm;
+ break;
+ case BPF_ALU | BPF_MUL | BPF_X:
+ case BPF_ALU64 | BPF_MUL | BPF_X:
+ //r[d] *= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_MUL | BPF_K:
+ case BPF_ALU64 | BPF_MUL | BPF_K:
+ //r[d] *= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off * imm;
+ break;
+ case BPF_ALU | BPF_DIV | BPF_X:
+ case BPF_ALU64 | BPF_DIV | BPF_X:
+ //r[d] /= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_DIV | BPF_K:
+ case BPF_ALU64 | BPF_DIV | BPF_K:
+ //r[d] /= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off / imm;
+ break;
+ case BPF_ALU | BPF_NEG:
+ case BPF_ALU64 | BPF_NEG:
+ //r[d] = -r[d];
+ break;
+ case BPF_ALU | BPF_LSH | BPF_X:
+ case BPF_ALU64 | BPF_LSH | BPF_X:
+ //r[d] <<= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_LSH | BPF_K:
+ case BPF_ALU64 | BPF_LSH | BPF_K:
+ //r[d] <<= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off << imm;
+ break;
+ case BPF_ALU | BPF_RSH | BPF_X:
+ case BPF_ALU64 | BPF_RSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_RSH | BPF_K:
+ case BPF_ALU64 | BPF_RSH | BPF_K:
+ //r[d] >>= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off >> imm;
+ break;
+ case BPF_ALU | BPF_ARSH | BPF_X:
+ case BPF_ALU64 | BPF_ARSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_jit_dbg(" PTR_TO_STACK with BPF_X is not supported\n");
+ return -EINVAL;
+ case BPF_ALU | BPF_ARSH | BPF_K:
+ case BPF_ALU64 | BPF_ARSH | BPF_K:
+ //r[d] >>= imm;
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(
+ knod_prog, meta, meta->insn.dst_reg);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ return -EINVAL;
+ }
+ kdreg->packet_off = prev_meta->dreg.packet_off >> imm;
+ break;
+ }
+ knod_jit_dbg(" %d: dreg->packet_off = %d\n", meta->bpf_insn_idx,
+ kdreg->packet_off);
+ }
+ return 0;
+}
+
+static int knod_bpf_verify_insn(struct bpf_verifier_env *env,
+ int insn_idx, int prev_insn)
+{
+ struct knod_prog *knod_prog = env->prog->aux->offload->dev_priv;
+ const struct bpf_reg_state *sreg, *dreg, *kreg, *vreg;
+ struct knod_insn_meta *meta = knod_prog->meta;
+ struct knod_insn_meta *prev_meta;
+ int err = 0;
+
+ meta = knod_bpf_goto_meta(knod_prog, meta, insn_idx);
+ sreg = cur_regs(env) + meta->insn.src_reg;
+ dreg = cur_regs(env) + meta->insn.dst_reg;
+ knod_prog->meta = meta;
+ meta->sreg.reg = *sreg;
+ meta->dreg.reg = *dreg;
+
+ knod_bpf_update_ptr_off(knod_prog, meta, env);
+
+ if (meta->insn.src_reg >= MAX_BPF_REG ||
+ meta->insn.dst_reg >= MAX_BPF_REG) {
+ knod_jit_dbg(" program uses extended registers - jit hardening?\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (is_mbpf_load(meta)) {
+ err = knod_bpf_check_ptr(knod_prog, meta, env,
+ meta->insn.src_reg);
+ goto out;
+ }
+ if (is_mbpf_store(meta)) {
+ err = knod_bpf_check_store(knod_prog, meta, env);
+ goto out;
+ }
+
+ if (is_mbpf_map_call(meta)) {
+ kreg = cur_regs(env) + 2;
+ meta->kreg.reg = *kreg;
+
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(knod_prog,
+ meta,
+ 2);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid\n");
+ err = -EINVAL;
+ goto out;
+ }
+ if (kreg->type == PTR_TO_PACKET)
+ meta->kreg.packet_off = prev_meta->dreg.packet_off;
+ else
+ meta->kreg.stack_off = prev_meta->dreg.stack_off;
+ if (knod_prog->max_stack_off > meta->kreg.stack_off)
+ knod_prog->max_stack_off = meta->kreg.stack_off;
+ if (knod_prog->max_packet_off < meta->kreg.packet_off)
+ knod_prog->max_packet_off = meta->kreg.packet_off;
+
+ /* bpf_map_update_elem: track r3 (value pointer) */
+ if (meta->insn.imm == 2) {
+ vreg = cur_regs(env) + 3;
+ meta->vreg.reg = *vreg;
+
+ prev_meta = knod_bpf_lookup_prev_meta_by_dreg(knod_prog,
+ meta,
+ 3);
+ if (!prev_meta) {
+ knod_jit_dbg(" Invalid vreg\n");
+ err = -EINVAL;
+ goto out;
+ }
+ if (vreg->type == PTR_TO_PACKET)
+ meta->vreg.packet_off =
+ prev_meta->dreg.packet_off;
+ else
+ meta->vreg.stack_off =
+ prev_meta->dreg.stack_off;
+ if (knod_prog->max_stack_off > meta->vreg.stack_off)
+ knod_prog->max_stack_off = meta->vreg.stack_off;
+ if (knod_prog->max_packet_off < meta->vreg.packet_off)
+ knod_prog->max_packet_off =
+ meta->vreg.packet_off;
+ }
+ }
+
+ if (is_mbpf_alu(meta))
+ err = knod_bpf_check_alu(knod_prog, meta, env);
+
+ /* less stack offset is bigger */
+ if (knod_prog->max_stack_off > meta->sreg.stack_off)
+ knod_prog->max_stack_off = meta->sreg.stack_off;
+ if (knod_prog->max_stack_off > meta->dreg.stack_off)
+ knod_prog->max_stack_off = meta->dreg.stack_off;
+ if (knod_prog->max_packet_off < meta->sreg.packet_off)
+ knod_prog->max_packet_off = meta->sreg.packet_off;
+ if (knod_prog->max_packet_off < meta->dreg.packet_off)
+ knod_prog->max_packet_off = meta->dreg.packet_off;
+
+out:
+ if (err)
+ pr_warn("knod_bpf: verifier rejected bpf insn %d (code 0x%02x off %d imm %d): %d\n",
+ insn_idx, meta->insn.code, meta->insn.off,
+ meta->insn.imm, err);
+ return err;
+}
+
+static int knod_bpf_finalize(struct bpf_verifier_env *env)
+{
+ return 0;
+}
+
+static int knod_bpf_offload(struct knod_dev *knodev,
+ struct bpf_prog *prog, bool oldprog)
+{
+ struct knod_bpf_priv *priv = knodev->accel->xdp.priv;
+
+ WARN(!!knod_dev_offloaded(knodev) != oldprog,
+ "bad offload state, expected offload %sto be active",
+ oldprog ? "" : "not ");
+
+ WRITE_ONCE(priv->prog, prog);
+ knod_dev_offload(knodev, prog);
+
+ /*
+ * Uninstalling the prog: reload the pass kernel now, while the prog's
+ * maps are still valid, so the worker stops dispatching prog code that
+ * is about to reference freed maps.
+ */
+ if (!prog)
+ knod_bpf_reload_pass(knodev);
+
+ return 0;
+}
+
+static int knod_bpf_xdp_offload_prog(struct knod_dev *knodev,
+ struct netdev_bpf *bpf)
+{
+ if (!knod_dev_active(knodev) && !bpf->prog)
+ return 0;
+
+ if (!knod_dev_active(knodev) && bpf->prog &&
+ knodev->accel->xdp.bpf_offloaded) {
+ return -EBUSY;
+ }
+
+ return knod_bpf_offload(knodev, bpf->prog, knod_dev_active(knodev));
+}
+
+static int knod_bpf_xdp_set_prog(struct knod_dev *knodev,
+ struct netdev_bpf *bpf)
+{
+ int err;
+
+ if (bpf->command == XDP_SETUP_PROG_HW) {
+ err = knod_bpf_xdp_offload_prog(knodev, bpf);
+ if (err)
+ return err;
+ }
+
+ xdp_attachment_setup(&knodev->accel->xdp.xdp_hw, bpf);
+
+ return 0;
+}
+
+static void knod_wait_vmcnt(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ knod_emit(priv, meta, s_waitcnt_vmcnt);
+}
+
+static void knod_global_load_size_cache(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 *d,
+ struct amdgcn_param32 s,
+ int dst_idx, int start_off, int length)
+{
+ int off = start_off;
+
+ /* length is 4B aligned */
+ while (length) {
+ if (length >= 16) {
+ knod_emit(priv, meta, global_load_dwordx4, d[dst_idx],
+ s, off);
+ length -= 16;
+ off += 16;
+ dst_idx += 4;
+ } else if (length >= 8) {
+ knod_emit(priv, meta, global_load_dwordx2, d[dst_idx],
+ s, off);
+ length -= 8;
+ off += 8;
+ dst_idx += 2;
+ } else if (length >= 4) {
+ knod_emit(priv, meta, global_load_dword, d[dst_idx],
+ s, off);
+ length -= 4;
+ off += 4;
+ dst_idx += 1;
+ }
+ }
+
+ knod_wait_vmcnt(priv, meta);
+}
+
+static void knod_global_store_size_cache(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 *d,
+ struct amdgcn_param32 s,
+ int dst_idx, int start_off, int length)
+{
+ int off = start_off;
+
+ /* length is 4B aligned */
+ while (length) {
+ if (length >= 16) {
+ knod_emit(priv, meta, global_store_dwordx4, d[dst_idx],
+ s, off);
+ length -= 16;
+ off += 16;
+ dst_idx += 4;
+ } else if (length >= 8) {
+ knod_emit(priv, meta, global_store_dwordx2, d[dst_idx],
+ s, off);
+ length -= 8;
+ off += 8;
+ dst_idx += 2;
+ } else if (length >= 4) {
+ knod_emit(priv, meta, global_store_dword, d[dst_idx],
+ s, off);
+ length -= 4;
+ off += 4;
+ dst_idx += 1;
+ }
+ }
+
+ knod_wait_vmcnt(priv, meta);
+}
+
+static int knod_prog_prepare_insns(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog)
+{
+ struct amdgcn_param64 param64[3];
+ struct amdgcn_param32 param[10];
+ struct knod_insn_meta *meta;
+ int bs_shift;
+
+ meta = kzalloc_obj(*meta, GFP_KERNEL);
+ if (!meta)
+ return -ENOMEM;
+
+ meta->amdgpu_insn_idx = 0;
+
+ /* Invalidate SQC instruction cache so that a re-uploaded shader
+ * at the same VRAM address is fetched from memory, not from the
+ * stale I-cache. Must be the very first instruction at the entry
+ * point so that every shader version has s_icache_inv at the same
+ * offset - the cached old version executes s_icache_inv too,
+ * which flushes the cache before divergent code is reached.
+ */
+ knod_emit(priv, meta, s_icache_inv);
+ knod_emit(priv, meta, s_waitcnt_vmcnt_lgkmcnt);
+
+ knod_sset32(¶m[0], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_sset32(¶m[1], KNOD_AMDGPU_ARG_SREG);
+ /* param = (__global struct _knod_bpf_param *)pkt.kernarg_address; */
+ knod_emit(priv, meta, s_load_dwordx2, param[0], param[1],
+ offsetof(struct hsa_kernel_dispatch_packet, kernarg_address));
+
+ knod_vset32(¶m[0], KNOD_AMDGPU_IDX_VREG);
+ knod_vset32(¶m[1], KNOD_AMDGPU_VREG0_LO);
+ knod_iset32(¶m[2], 0);
+ /* 10bits, lidx can up to 1024, Do not edit */
+ knod_iset32(¶m[3], 10);
+ /* extract workitem ID to reserved vgpr register.
+ * In the 2D-dispatch layout, IDX_VREG holds the per-workgroup tid
+ * (0..workgroup_size_x-1). queue_id = workgroup_id_y (s15). The
+ * flat index (queue_id * batch_size + local_idx) is computed later,
+ * after queue_desc has been loaded and the v_cmpx bounds check has
+ * narrowed EXEC to lanes with local_idx < count.
+ */
+ knod_emit(priv, meta, v_bfe_i32, param[0], param[1], param[2],
+ param[3]);
+ if (priv->batch_size > knod_bpf_workgroups) {
+ /* local_idx = workgroup_id_x * workgroup_size_x
+ * + workitem_id.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_sset32(¶m[1], KNOD_AMDGPU_WORKGROUP_ID_X_SREG);
+ knod_iset32(¶m[2], knod_bpf_workgroups);
+ knod_emit(priv, meta,
+ v_mul_lo_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_IDX_VREG);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_vset32(¶m[2], KNOD_AMDGPU_IDX_VREG);
+ knod_emit(priv, meta, v_add_u32, param[0], param[1], param[2]);
+ }
+ /* set frame pointer to 0 */
+ knod_sset32(¶m[0], KNOD_AMDGPU_FRAME_POINTER_SREG);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, s_mov_b32, param[0], param[1]);
+ /* wait for s_load_dwordx2 (kernarg_address) */
+ knod_emit(priv, meta, s_waitcnt_vmcnt_lgkmcnt);
+
+ /* load {nr_backlogs, _pad} from param (offset 0, 8-byte aligned) */
+ knod_sset32(¶m[0], KNOD_AMDGPU_TMP_SREG1_LO);
+ knod_sset32(¶m[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, s_load_dwordx2, param[0], param[1], 0);
+ knod_emit(priv, meta, s_waitcnt_vmcnt_lgkmcnt);
+
+ /* NOTE: the bounds check `EXEC &= (tid < count)` is deferred until
+ * after the queue descriptor load (queue<->workgroup binding).
+ * nr_backlogs is no longer the right upper bound because lanes with
+ * tid > this queue's count must be masked, not just the ones past
+ * the aggregate backlog total.
+ */
+
+ /* Initialize done_mask to 0 for structurized CFG */
+ knod_emit(priv, meta, s_mov_b64, knod_prog->done_mask_sreg,
+ AMDGCN_SREG_INTEGER_0);
+
+ /* =========================================================
+ * Queue-descriptor prep (2D dispatch: queue_id = workgroup_id_y)
+ *
+ * Loads this WG's queue descriptor from VRAM, performs the
+ * per-lane bounds check (local_idx < queues[queue_id].count), and
+ * converts IDX_VREG from local_idx to flat_IDX (queue_id *
+ * batch_size + local_idx) which the rest of the prologue/BPF body
+ * expects. TMP_VREG9_LO is repurposed to hold the saved local
+ * tid for use as local_idx in the slot-address step.
+ * =========================================================
+ */
+ bs_shift = ilog2(priv->batch_size);
+
+ /* a. queue_id = workgroup_id_y (broadcast scalar to VGPR LO). */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_sset32(¶m[1], KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ /* b. Copy queue_id into TMP_VREG5_LO - separate scratch used as
+ * the v_mad src-multiplicand. Avoids dst/src1 overlap on the
+ * following v_mad_u64_u32 (dst=TMP_VREG0_LO:HI).
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ /* c. TMP_VREG0 = PARAM + queue_id * sizeof(queue_desc). */
+ knod_vset64(¶m64[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_sset32(¶m[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_iset32(¶m[1],
+ sizeof(struct knod_bpf_queue_desc));
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_sset64(¶m64[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, v_mad_u64_u32, param64[0], param[0],
+ param[1], param[2], param64[1]);
+
+ /* d. TMP_VREG0 += offsetof(queues). */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1],
+ offsetof(struct knod_bpf_param, queues));
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[0]);
+
+ /* e. Load pool_gaddr + base_gaddr (offset 0, 16 bytes) into
+ * TMP_VREG1_LO..TMP_VREG2_HI (v24..v27 - must be consecutive).
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, global_load_dwordx4, param[0], param[1], 0);
+
+ /* f. Load count + _pad + ring_start + ring_mask (offset 16, 16
+ * bytes) into TMP_VREG3_LO..TMP_VREG4_HI.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG3_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, global_load_dwordx4, param[0], param[1],
+ offsetof(struct knod_bpf_queue_desc, count));
+ knod_emit(priv, meta, s_waitcnt_vmcnt);
+
+ /* g. Bounds check: EXEC &= (workitem_id < count). */
+ knod_vset32(¶m[0], KNOD_AMDGPU_IDX_VREG);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG3_LO);
+ knod_emit(priv, meta, v_cmpx_lt_u32, param[0], param[1]);
+
+ /* Snapshot the in-bounds lane mask. The unified epilogue uses this
+ * to publish one verdict for every lane the dispatch claimed, even if
+ * a malformed or newly added CFG path fails to join done_mask.
+ */
+ knod_emit(priv, meta, s_mov_b64, knod_prog->initial_exec_sreg,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* h. Save per-queue local_idx to TMP_VREG9_LO.
+ * The slot-address step consumes this value; keeping it in a
+ * dedicated VGPR lets us overwrite IDX_VREG with flat_IDX for
+ * the CTX address computation that immediately follows.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_IDX_VREG);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ /* i. IDX_VREG = (queue_id << ilog2(batch_size)) + local_idx
+ * -> flat_IDX into the sub[] / sqw->bds[] arrays, matching the
+ * CPU-side layout `sqw->bds[queue_id * batch_size + local_idx]`.
+ * batch_size is rounded down to a power of two at start so the
+ * shift is exact.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_IDX_VREG);
+ knod_sset32(¶m[1], KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_iset32(¶m[2], bs_shift);
+ knod_vset32(¶m[3], KNOD_AMDGPU_IDX_VREG);
+ knod_emit(priv, meta, v_lshl_add_u32, param[0], param[1],
+ param[2], param[3]);
+
+ /* ctx = ¶m->sub[flat_IDX].ctx;
+ * v_mad: VREG1 = sizeof(sub_obj) * flat_IDX + PARAM_SREG
+ * then add offsetof(sub) = 8 to account for nr_backlogs/_pad
+ */
+ knod_vset64(¶m64[0], KNOD_AMDGPU_VREG1_LO);
+ knod_sset32(¶m[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_iset32(¶m[1], sizeof(struct knod_bpf_subparam_obj));
+ knod_vset32(¶m[2], KNOD_AMDGPU_IDX_VREG);
+ knod_sset64(¶m64[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, v_mad_u64_u32, param64[0], param[0],
+ param[1], param[2], param64[1]);
+ /* + offsetof(struct knod_bpf_param, sub) */
+ knod_vset32(¶m[0], KNOD_AMDGPU_VREG1_LO);
+ knod_iset32(¶m[1], offsetof(struct knod_bpf_param, sub));
+ knod_vset32(¶m[2], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_VREG1_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[0]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_CTX_VREG_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_CTX_VREG_HI);
+ knod_vset32(¶m[1], KNOD_AMDGPU_VREG1_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_FRAME_POINTER_VREG_LO);
+ knod_iset32(¶m[1], 0x200);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_FRAME_POINTER_VREG_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ /* 5. slot = (ring_start + local_idx) & ring_mask.
+ * local_idx = saved per-queue local_idx in TMP_VREG9_LO.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG4_LO);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, meta, v_add_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG4_HI);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_emit(priv, meta, v_and_b32_e32, param[0], param[1], param[2]);
+
+ /* Save backlog index before step 6 overwrites IDX_VREG -> SLOT_VREG.
+ * v_mov_b32 BACKLOG_IDX_VREG(v58), IDX_VREG(v62)
+ * Used in epilogue for XDP_PASS pass_indices[] write.
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_BACKLOG_IDX_VREG);
+ knod_vset32(¶m[1], KNOD_AMDGPU_IDX_VREG);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ /* 6. slot_addr = pool_gaddr + slot * spsc_stride, where
+ * spsc_stride = ALIGN(sizeof(spsc_bd), SMP_CACHE_BYTES)
+ * Compute directly into SLOT_VREG (v62:v63).
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_SLOT_VREG_LO);
+ knod_iset32(¶m[1],
+ ilog2(ALIGN(sizeof(struct spsc_bd), SMP_CACHE_BYTES)));
+ knod_emit(priv, meta, v_lshlrev_b32, param[0], param[1], param[2]);
+ /* slot_addr = pool_gaddr + slot_offset */
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_SLOT_VREG_HI);
+ knod_iset32(¶m[1], 0);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG1_HI);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[2]);
+
+ /* 7. Load spsc_bd: {off(u16)|len(u16), page_idx} via single dwordx2
+ * TMP_VREG6_LO (v34) = off|len, TMP_VREG6_HI (v35) = page_idx
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG6_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_SLOT_VREG_LO);
+ knod_emit(priv, meta, global_load_dwordx2, param[0], param[1],
+ offsetof(struct spsc_bd, off));
+ knod_emit(priv, meta, s_waitcnt_vmcnt);
+
+ /* 8. data = base_gaddr + (page_idx << PAGE_SHIFT) + off
+ * Compute directly into DATA_VREG (v64:v65).
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_iset32(¶m[1], PAGE_SHIFT);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG6_HI);
+ knod_emit(priv, meta, v_lshlrev_b32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_iset32(¶m[1], 32 - PAGE_SHIFT);
+ knod_emit(priv, meta, v_lshrrev_b32, param[0], param[1], param[2]);
+
+ /* data = base_gaddr + page_gaddr */
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG2_LO);
+ knod_vset32(¶m[2], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG2_HI);
+ knod_vset32(¶m[2], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[2]);
+
+ if (knod_prog->uses_adjust) {
+ /* Save page_base to PAGE_BASE_VREG before adding off */
+ knod_vset32(¶m[0], KNOD_AMDGPU_PAGE_BASE_VREG_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_PAGE_BASE_VREG_HI);
+ knod_vset32(¶m[1], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ }
+
+ /* extract off (lower 16 bits of TMP_VREG6_LO) */
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG8_LO);
+ knod_iset32(¶m[1], 0xffff);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG6_LO);
+ knod_emit(priv, meta, v_and_b32_e32, param[0], param[1], param[2]);
+
+ /* data += off */
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG8_LO);
+ knod_vset32(¶m[2], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[0]);
+
+ /* 9. data_end = data + len (upper 16 bits of TMP_VREG6_LO)
+ * Compute directly into DATA_END_VREG (v66:v67).
+ */
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_iset32(¶m[1], 16);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG6_LO);
+ knod_emit(priv, meta, v_lshrrev_b32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_vset32(¶m[1], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(¶m[2], KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_emit(priv, meta, v_add_co_u32, param[0], param[1], param[2]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_DATA_END_VREG_HI);
+ knod_iset32(¶m[1], 0);
+ knod_vset32(¶m[2], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, param[0], param[1],
+ param[2]);
+
+ /* Step 10 eliminated: data->DATA_VREG, data_end->DATA_END_VREG,
+ * slot_addr->SLOT_VREG computed directly in steps 6/8/9 above.
+ */
+
+ pr_debug("knod_bpf DEBUG: prologue emitted idx=%u (KNOD_META_INSNS=%d)\n",
+ meta->amdgpu_insns, KNOD_META_INSNS);
+ if (WARN_ON(meta->amdgpu_insns > KNOD_META_INSNS))
+ return -ENOSPC;
+ list_add_tail(&meta->l, &knod_prog->pre_insns);
+
+ return 0;
+}
+
+static int knod_prog_prepare(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog,
+ const struct bpf_insn *prog,
+ unsigned int cnt)
+{
+ struct knod_insn_meta *meta;
+ unsigned int i;
+
+ /* Pre-scan: detect helper 44/65 to set uses_adjust early */
+ for (i = 0; i < cnt; i++) {
+ if (prog[i].code == (BPF_JMP | BPF_CALL) &&
+ (prog[i].imm == 44 || prog[i].imm == 65)) {
+ knod_prog->uses_adjust = true;
+ break;
+ }
+ }
+
+ knod_vset64(&r64[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_vset64(&r64[1], KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_vset64(&r64[2], KNOD_AMDGPU_TMP_VREG2_LO);
+ knod_vset64(&r64[3], KNOD_AMDGPU_TMP_VREG3_LO);
+ knod_vset64(&r64[4], KNOD_AMDGPU_TMP_VREG4_LO);
+ knod_vset64(&r64[5], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_vset64(&r64[6], KNOD_AMDGPU_TMP_VREG6_LO);
+ knod_vset64(&r64[7], KNOD_AMDGPU_TMP_VREG7_LO);
+ knod_vset64(&r64[8], KNOD_AMDGPU_TMP_VREG8_LO);
+ knod_vset64(&r64[9], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset64(&r64[10], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_vset64(&r64[11], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_vset64(&r64[12], KNOD_AMDGPU_TMP_VREG12_LO);
+ knod_vset64(&r64[13], KNOD_AMDGPU_TMP_VREG13_LO);
+ knod_vset64(&r64[14], KNOD_AMDGPU_TMP_VREG14_LO);
+ knod_vset64(&r64[15], KNOD_AMDGPU_TMP_VREG15_LO);
+ knod_vset64(&r64[16], KNOD_AMDGPU_TMP_VREG16_LO);
+ knod_vset64(&r64[17], KNOD_AMDGPU_TMP_VREG17_LO);
+ knod_vset64(&r64[18], KNOD_AMDGPU_TMP_VREG18_LO);
+ knod_vset64(&r64[19], KNOD_AMDGPU_CTX_VREG_LO);
+
+ knod_sset64(&sr64[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_sset64(&sr64[1], KNOD_AMDGPU_TMP_SREG1_LO);
+ knod_sset64(&sr64[2], KNOD_AMDGPU_TMP_SREG2_LO);
+ knod_sset64(&sr64[3], KNOD_AMDGPU_TMP_SREG3_LO);
+ knod_sset64(&sr64[4], KNOD_AMDGPU_TMP_SREG4_LO);
+ knod_sset64(&sr64[5], KNOD_AMDGPU_TMP_SREG5_LO);
+
+ knod_vset64(&bpf_reg64[0], KNOD_AMDGPU_VREG0_LO);
+ knod_vset64(&bpf_reg64[1], KNOD_AMDGPU_VREG1_LO);
+ knod_vset64(&bpf_reg64[2], KNOD_AMDGPU_VREG2_LO);
+ knod_vset64(&bpf_reg64[3], KNOD_AMDGPU_VREG3_LO);
+ knod_vset64(&bpf_reg64[4], KNOD_AMDGPU_VREG4_LO);
+ knod_vset64(&bpf_reg64[5], KNOD_AMDGPU_VREG5_LO);
+ knod_vset64(&bpf_reg64[6], KNOD_AMDGPU_VREG6_LO);
+ knod_vset64(&bpf_reg64[7], KNOD_AMDGPU_VREG7_LO);
+ knod_vset64(&bpf_reg64[8], KNOD_AMDGPU_VREG8_LO);
+ knod_vset64(&bpf_reg64[9], KNOD_AMDGPU_VREG9_LO);
+ knod_vset64(&bpf_reg64[10], KNOD_AMDGPU_FRAME_POINTER_VREG_LO);
+
+ knod_vset32(&r32[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ for (i = 1; i < 40; i++)
+ knod_vset32(&r32[i], r32[i - 1].v + 1);
+
+ if (knod_bpf_pkt_cache) {
+ int pkt_cache_start = knod_prog->uses_adjust ?
+ KNOD_AMDGPU_PKT_CACHE_VREG0 :
+ KNOD_AMDGPU_PAGE_BASE_VREG_LO;
+
+ knod_vset32(&pkt_cache[0], pkt_cache_start);
+ for (i = 1; i < 64; i++)
+ knod_vset32(&pkt_cache[i],
+ pkt_cache[i - 1].v + 1);
+ }
+
+ knod_vset32(&stack[0], KNOD_AMDGPU_STACK_VREG0);
+ for (i = 1; i < 128; i++)
+ knod_vset32(&stack[i], stack[i - 1].v + 1);
+
+ for (i = 0; i < cnt; i++) {
+ meta = kzalloc_obj(*meta, GFP_KERNEL);
+ if (!meta)
+ return -ENOMEM;
+
+ meta->insn = prog[i];
+ meta->bpf_insn_idx = i;
+
+ list_add_tail(&meta->l, &knod_prog->insns);
+ }
+ knod_prog->n_insns = cnt;
+
+ return 0;
+}
+
+static void knod_prog_free(struct knod_prog *knod_prog)
+{
+ struct knod_insn_meta *meta, *tmp;
+
+ //kfree(knod_prog->subprog);
+
+ list_for_each_entry_safe(meta, tmp, &knod_prog->pre_insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ list_for_each_entry_safe(meta, tmp, &knod_prog->insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ list_for_each_entry_safe(meta, tmp, &knod_prog->post_insns, l) {
+ list_del(&meta->l);
+ kfree(meta);
+ }
+ kfree(knod_prog);
+}
+
+static int knod_bpf_verifier_prep(struct bpf_prog *prog)
+{
+ struct knod_prog *knod_prog;
+ struct knod_bpf_priv *priv;
+ int err;
+
+ knod_prog = kzalloc_obj(struct knod_prog, GFP_KERNEL);
+ if (!knod_prog)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&knod_prog->insns);
+ INIT_LIST_HEAD(&knod_prog->pre_insns);
+ INIT_LIST_HEAD(&knod_prog->post_insns);
+ prog->aux->offload->dev_priv = knod_prog;
+ priv = bpf_offload_dev_priv(prog->aux->offload->offdev);
+ knod_prog->knodev = priv->knodev;
+ WRITE_ONCE(priv->knod_prog, knod_prog);
+ knod_prog->knod = priv->knod;
+ knod_prog->insn_idx = 0;
+
+ if (priv->isa_version == 10) {
+ knod_prog->done_mask_sreg = 32;
+ knod_prog->exec_save_base = 34;
+ knod_prog->initial_exec_sreg =
+ KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX10;
+ } else {
+ knod_prog->done_mask_sreg = KNOD_AMDGPU_DONE_MASK_SREG;
+ knod_prog->exec_save_base = KNOD_AMDGPU_EXEC_SAVE_SREG_BASE;
+ knod_prog->initial_exec_sreg =
+ KNOD_AMDGPU_INITIAL_EXEC_SREG_GFX9;
+ }
+
+ err = knod_prog_prepare(priv, knod_prog, prog->insnsi, prog->len);
+ if (err)
+ goto err_free;
+
+ knod_prog->meta = knod_prog_first_meta(knod_prog);
+
+ return 0;
+
+err_free:
+ knod_prog_free(knod_prog);
+
+ return err;
+}
+
+static struct knod_insn_meta *knod_bpf_lookup_meta(struct knod_prog *knod_prog,
+ short idx)
+{
+ struct knod_insn_meta *meta;
+
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ if (meta->amdgpu_insn_idx == AMDGPU_INSN_SKIP)
+ continue;
+ if (meta->bpf_insn_idx == idx)
+ return meta;
+ }
+
+ return NULL;
+}
+
+static void knod_mov64_imm(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int d, u64 imm64)
+{
+ struct amdgcn_param32 param[2];
+
+ knod_vset32(¶m[0], d);
+ knod_iset32(¶m[1], imm64 & ~0U);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], d + 1);
+ knod_iset32(¶m[1], imm64 >> 32);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+}
+
+static void knod_mov32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src)
+{
+ knod_emit(priv, meta, v_mov_b32_e32, dst, src);
+}
+
+static void knod_mov64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src)
+{
+ knod_mov32(priv, meta, dst.lo, src.lo);
+ knod_mov32(priv, meta, dst.hi, src.hi);
+}
+
+static void knod_add64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_add_co_u32, dst.lo, src0.lo, src1.lo);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, dst.hi, src0.hi,
+ src1.hi);
+}
+
+/* No carry out/in */
+static void knod_add32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_add_u32, dst, src0, src1);
+}
+
+static void knod_xor32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_xor_b32_e32, dst, src0, src1);
+}
+
+static void knod_alignbit32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1,
+ struct amdgcn_param32 src2)
+{
+ knod_emit(priv, meta, v_alignbit_b32, dst, src0, src1, src2);
+}
+
+static void knod_bfe32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1,
+ struct amdgcn_param32 src2)
+{
+ knod_emit(priv, meta, v_bfe_u32, dst, src0, src1, src2);
+}
+
+static void knod_bfi32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1,
+ struct amdgcn_param32 src2)
+{
+ knod_emit(priv, meta, v_bfi_b32, dst, src0, src1, src2);
+}
+
+static void knod_lshrrev32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_lshrrev_b32, dst, src0, src1);
+}
+
+static void knod_lshrrev64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_lshrrev_b64, dst, src0, src1);
+}
+
+static void knod_ashrrev32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_ashrrev_i32, dst, src0, src1);
+}
+
+static void knod_ashrrev64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_ashrrev_i64, dst, src0, src1);
+}
+
+static void knod_lshlrev32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_lshlrev_b32, dst, src0, src1);
+}
+
+static void knod_lshlrev64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_lshlrev_b64, dst, src0, src1);
+}
+
+/* No carry out/in */
+static void knod_sub32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_sub_u32, dst, src0, src1);
+}
+
+static void knod_and32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_and_b32_e32, dst, src0, src1);
+}
+
+static void knod_and64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_and32(priv, meta, dst.lo, src0.lo, src1.lo);
+ knod_and32(priv, meta, dst.hi, src0.hi, src1.hi);
+}
+
+static void knod_or32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src0,
+ struct amdgcn_param32 src1)
+{
+ knod_emit(priv, meta, v_or_b32_e32, dst, src0, src1);
+}
+
+static void knod_sub64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_sub_co_u32, dst.lo, src0.lo, src1.lo);
+ knod_emit(priv, meta, v_sub_co_ci_u32_e32, dst.hi, src0.hi,
+ src1.hi);
+}
+
+static void knod_subrev64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src0,
+ struct amdgcn_param64 src1)
+{
+ knod_emit(priv, meta, v_subrev_co_u32, dst.lo, src0.lo, src1.lo);
+ knod_emit(priv, meta, v_subrev_co_ci_u32_e32, dst.hi, src0.hi,
+ src1.hi);
+}
+
+static void knod_mul_lo32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src1,
+ struct amdgcn_param32 src2)
+{
+ knod_emit(priv, meta, v_mul_lo_u32, dst, src1, src2);
+}
+
+static void knod_mul_hi32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param32 dst,
+ struct amdgcn_param32 src1,
+ struct amdgcn_param32 src2)
+{
+ knod_emit(priv, meta, v_mul_hi_u32, dst, src1, src2);
+}
+
+static void knod_mul64(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 src1,
+ struct amdgcn_param64 src2,
+ struct amdgcn_param64 tmp)
+{
+ /*
+ * v_mul_lo_u32 v1, v2, v1
+ * v_mul_hi_u32 v5, v2, v0
+ * v_mul_lo_u32 v3, v3, v0
+ * v_mul_lo_u32 v0, v2, v0
+ * v_add_u32_e32 v1, v5, v1
+ * v_add_u32_e32 v1, v1, v3
+ *
+ * v[0:1] = src1, dst
+ * v[2:3] = src2
+ * v5 = tmp
+ */
+
+ /* v_mul_lo_u32 v1, v2, v1 */
+ knod_mul_lo32(priv, meta, src2.hi, src1.lo, src2.hi);
+ /* v_mul_hi_u32 v5, v2, v0 */
+ knod_mul_hi32(priv, meta, tmp.lo, src1.lo, src2.lo);
+ /* v_mul_lo_u32 v3, v3, v0 */
+ knod_mul_lo32(priv, meta, src1.hi, src1.hi, src2.lo);
+ /* v_mul_lo_u32 v0, v2, v0 */
+ knod_mul_lo32(priv, meta, src1.lo, src1.lo, src2.lo);
+ /* v_add_u32_e32 v1, v5, v1 */
+ knod_add32(priv, meta, src2.lo, tmp.lo, src2.hi);
+ /* v_add_u32_e32 v1, v1, v3 */
+ knod_add32(priv, meta, src1.hi, src2.lo, src1.hi);
+ knod_mov32(priv, meta, dst.lo, src1.lo);
+ knod_mov32(priv, meta, dst.hi, src1.hi);
+}
+
+static void knod_div(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 imm,
+ struct amdgcn_param64 tmp_reg0,
+ struct amdgcn_param64 tmp_reg1,
+ struct amdgcn_param64 tmp_reg2,
+ struct amdgcn_param64 tmp_reg3)
+{
+ struct reciprocal_value_adv rvalue;
+ struct amdgcn_param64 p64[4];
+ u8 pre_shift, exp;
+
+ WARN_ON((imm.lo.type != AMDGCN_PARAM_TYPE_INTEGER_0) &&
+ (imm.lo.type != AMDGCN_PARAM_TYPE_LITERAL_CONST));
+ WARN_ON((imm.hi.type != AMDGCN_PARAM_TYPE_INTEGER_0) &&
+ (imm.hi.type != AMDGCN_PARAM_TYPE_LITERAL_CONST));
+ knod_iset64(&p64[0], 0);
+ knod_iset64(&p64[1], 0);
+ knod_iset64(&p64[2], 0);
+ knod_iset64(&p64[3], 0);
+ /*
+ * dst := imm
+ * n := dst_reg
+ */
+ if (imm.imm > U32_MAX) {
+ knod_mov64(priv, meta, dst, p64[0]);
+ return;
+ }
+
+ if (imm.imm >= 1U << 31) {
+ /* result = n >= dst; */
+ knod_mov64(priv, meta, tmp_reg0, imm);
+ knod_emit(priv, meta, v_cmp_ge_u64, dst, tmp_reg0);
+ return;
+ }
+
+ rvalue = reciprocal_value_adv(imm.lo.v, 32);
+ exp = rvalue.exp;
+ if (rvalue.is_wide_m && !(imm.lo.v & 1)) {
+ pre_shift = fls(imm.lo.v & -imm.lo.v) - 1;
+ rvalue = reciprocal_value_adv(imm.lo.v >> pre_shift,
+ 32 - pre_shift);
+ } else {
+ pre_shift = 0;
+ }
+
+ if (imm.lo.v == 1U << exp) {
+ knod_iset64(&p64[0], exp);
+ /* n = n >> exp */
+ knod_lshrrev64(priv, meta, dst, p64[0], dst);
+ return;
+ } else if (rvalue.is_wide_m) {
+ /*
+ * pre_shift must be zero when reached here.
+ * t = (n * rvalue.m) >> 32;
+ * result = n - t;
+ * result >>= 1;
+ * result += t;
+ * result >>= rvalue.sh - 1;
+ */
+
+ /*
+ * n := VREG0
+ * t := VREG1
+ * rvalue.m := VREG2
+ * tmp := VREG3
+ */
+
+ /* n := TMP_VREG0 */
+ knod_mov64(priv, meta, tmp_reg0, dst);
+
+ knod_iset64(&p64[0], rvalue.m);
+ /* rvalue.m := TMP_VREG2 */
+ knod_mov64(priv, meta, tmp_reg2, p64[0]);
+
+ /* t = n * rvalue.m; */
+ knod_mul64(priv, meta,
+ tmp_reg1, /* t */
+ tmp_reg0, /* n */
+ tmp_reg2, /* rvalue.m */
+ tmp_reg3); /* tmp */
+
+ /* t >>= 32; */
+ knod_iset64(&p64[0], 0);
+ knod_mov32(priv, meta, tmp_reg1.lo, tmp_reg1.hi);
+ knod_mov32(priv, meta, tmp_reg1.hi, p64[0].lo);
+
+ /* result = n - t */
+ knod_sub64(priv, meta, dst, dst, tmp_reg1);
+
+ /* result >>= 1 */
+ knod_iset64(&p64[0], 1);
+ knod_lshrrev64(priv, meta, dst, p64[0], dst);
+
+ /* result += t; */
+ knod_add64(priv, meta,
+ dst,
+ dst, /* result */
+ tmp_reg1); /* t */
+
+ /* result >>= rvalue.sh - 1; */
+ knod_iset64(&p64[0], rvalue.sh - 1);
+ WARN_ON(rvalue.sh - 1 > 31);
+ knod_lshrrev64(priv, meta, dst, p64[0], dst);
+ return;
+ }
+
+ /*
+ * if (pre_shift)
+ * result = n >> pre_shift;
+ * result = ((u64)result * rvalue.m) >> 32;
+ * result >>= rvalue.sh;
+ */
+
+ /*
+ * n := VREG0
+ * <NONE> := VREG1
+ * rvalue.m := VREG2
+ * tmp := VREG3
+ * result := dst * 2
+ */
+
+ /* n := TMP_VREG0 */
+ knod_mov64(priv, meta, tmp_reg0, dst);
+
+ /* rvalue.m := TMP_VREG2 */
+ knod_iset64(&p64[0], rvalue.m);
+ knod_mov64(priv, meta, tmp_reg2, p64[0]);
+
+ if (pre_shift) {
+ /* result = n >> pre_shift; */
+ knod_iset64(&p64[0], pre_shift);
+ knod_lshrrev64(priv, meta, dst, p64[0],
+ tmp_reg0); /* n */
+ } else {
+ /* tmp = 0 */
+ knod_iset64(&p64[0], 0);
+ knod_mov64(priv, meta, tmp_reg0, p64[0]);
+ }
+
+ /* result = result * rvalue.m; */
+ knod_mul64(priv, meta,
+ dst, /* result */
+ dst, /* result */
+ tmp_reg2, /* rvalue.m */
+ tmp_reg3); /* tmp */
+
+ /* result >>= (32 + rvalue.sh); */
+ knod_iset64(&p64[0], 32 + rvalue.sh);
+ knod_lshrrev64(priv, meta, dst, p64[0], dst);
+}
+
+static void knod_mod(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param64 imm,
+ struct amdgcn_param64 tmp_reg0,
+ struct amdgcn_param64 tmp_reg1,
+ struct amdgcn_param64 tmp_reg2,
+ struct amdgcn_param64 tmp_reg3,
+ struct amdgcn_param64 tmp_reg4)
+{
+ WARN_ON((imm.lo.type != AMDGCN_PARAM_TYPE_INTEGER_0) &&
+ (imm.lo.type != AMDGCN_PARAM_TYPE_LITERAL_CONST));
+ WARN_ON((imm.hi.type != AMDGCN_PARAM_TYPE_INTEGER_0) &&
+ (imm.hi.type != AMDGCN_PARAM_TYPE_LITERAL_CONST));
+ /* q := tmp_reg0 */
+ knod_mov64(priv, meta, tmp_reg0, dst);
+ /* q = n / imm */
+ knod_div(priv, meta, tmp_reg0, imm,
+ tmp_reg1, tmp_reg2, tmp_reg3, tmp_reg4);
+
+ /* tmp_reg1 := imm_reg */
+ knod_mov64(priv, meta, tmp_reg1, imm);
+
+ /* imm * q := tmp_reg3 */
+ knod_mul64(priv, meta,
+ tmp_reg3, /* imm * q */
+ tmp_reg0, /* q */
+ tmp_reg1, /* imm_reg */
+ tmp_reg2); /* tmp */
+
+ knod_sub64(priv, meta, dst, dst, tmp_reg3);
+}
+
+/*
+ * Fast constant modulo on the 32-bit value in @dst.lo for divisors of a
+ * special form, avoiding knod_mod's reciprocal divide + 64-bit multiply:
+ * 2^k -> dst & (2^k-1) (mask)
+ * 2^k + 1 -> lo - hi (+C if lo<hi) (Fermat: 2^k = -1 mod C)
+ * 2^k - 1 -> lo + hi (-C while >=C) (Mersenne: 2^k = 1 mod C)
+ * lo/hi are the low/high k-bit halves. One fold is exact for a 32-bit
+ * dividend when 2^k covers the high half (true for e.g. 65537 = 2^16+1,
+ * kondor's per-packet `hash % RING_SIZE`). Returns false for other
+ * divisors (caller falls back to knod_mod). Scratch: r64[0], r64[1].
+ */
+static bool knod_mod_k32(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 dst, u32 imm)
+{
+ struct amdgcn_param32 p;
+ int i;
+
+ if (is_power_of_2(imm)) {
+ knod_iset32(&p, imm - 1);
+ knod_and32(priv, meta, dst.lo, p, dst.lo);
+ } else if (is_power_of_2(imm - 1) && (imm - 1) >= (1u << 16)) {
+ knod_iset32(&p, imm - 2); /* mask 2^k-1 */
+ knod_and32(priv, meta, r64[0].lo, p, dst.lo); /* lo */
+ knod_iset32(&p, ilog2(imm - 1)); /* k */
+ knod_emit(priv, meta, v_lshrrev_b32, r64[1].lo, p, dst.lo);
+ /* lo-hi */
+ knod_sub32(priv, meta, dst.lo, r64[0].lo, r64[1].lo);
+ knod_emit(priv, meta, v_cmp_lt_u32, r64[0].lo, r64[1].lo);
+ knod_iset32(&p, imm);
+ knod_add32(priv, meta, r64[1].lo, p, dst.lo); /* +C */
+ knod_emit(priv, meta, v_cndmask_b32_e32, dst.lo, dst.lo,
+ r64[1].lo);
+ } else if (is_power_of_2(imm + 1) && (imm + 1) >= (1u << 16)) {
+ knod_iset32(&p, imm); /* mask 2^k-1 */
+ knod_and32(priv, meta, r64[0].lo, p, dst.lo); /* lo */
+ knod_iset32(&p, ilog2(imm + 1)); /* k */
+ knod_emit(priv, meta, v_lshrrev_b32, r64[1].lo, p, dst.lo);
+ /* lo+hi */
+ knod_add32(priv, meta, dst.lo, r64[0].lo, r64[1].lo);
+ knod_iset32(&p, imm);
+ knod_mov32(priv, meta, r64[0].lo, p); /* C in VGPR */
+ for (i = 0; i < 2; i++) { /* r < 2C */
+ knod_emit(priv, meta, v_cmp_le_u32, r64[0].lo, dst.lo);
+ knod_sub32(priv, meta, r64[1].lo, dst.lo, r64[0].lo);
+ knod_emit(priv, meta, v_cndmask_b32_e32, dst.lo,
+ dst.lo, r64[1].lo);
+ }
+ } else {
+ return false;
+ }
+
+ knod_iset32(&p, 0);
+ knod_mov32(priv, meta, dst.hi, p);
+ return true;
+}
+
+/* Considered to be able to use all temporary vregisters
+ * Also, key is stack pointer, not global
+ */
+static void knod_jhash(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ u32 dst_idx, u32 length, u32 initval)
+{
+ u32 a_reg = TREG32_MAX - 3, b_reg = TREG32_MAX - 2;
+ u32 c_reg = TREG32_MAX - 1, d_reg = TREG32_MAX;
+ u32 key_in_pkt = KEY_IN_PKT_32;
+ struct amdgcn_param32 p32;
+
+ knod_iset32(&p32, JHASH_INITVAL + length + initval);
+ knod_mov32(priv, meta, r32[a_reg], p32);
+ knod_mov32(priv, meta, r32[b_reg], p32);
+ knod_mov32(priv, meta, r32[c_reg], p32);
+ knod_iset32(&p32, 0);
+
+ while (length > 12) {
+ /* a += *key; */
+ knod_add32(priv, meta, r32[a_reg], r32[a_reg],
+ r32[key_in_pkt]);
+ /* b += *(key + 4); */
+ knod_add32(priv, meta, r32[b_reg], r32[b_reg],
+ r32[key_in_pkt + 1]);
+ /* c += *(key + 8); */
+ knod_add32(priv, meta, r32[c_reg], r32[c_reg],
+ r32[key_in_pkt + 2]);
+ /* a -= c; */
+ knod_sub32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* a ^= rol32(c, 4); */
+ knod_iset32(&p32, 32 - 4);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[c_reg],
+ r32[c_reg], p32);
+ knod_xor32(priv, meta,
+ r32[a_reg], r32[a_reg], r32[d_reg]);
+ /* c += b; */
+ knod_add32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* b -= a; */
+ knod_sub32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ /* b ^= rol32(a, 6); */
+ knod_iset32(&p32, 32 - 6);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[a_reg],
+ r32[a_reg], p32);
+ knod_xor32(priv, meta,
+ r32[b_reg], r32[b_reg], r32[d_reg]);
+ /*a += c; */
+ knod_add32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* c -= b; */
+ knod_sub32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* c ^= rol32(b, 8); */
+ knod_iset32(&p32, 32 - 8);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[b_reg],
+ r32[b_reg], p32);
+ knod_xor32(priv, meta,
+ r32[c_reg], r32[c_reg], r32[d_reg]);
+ /* b += a; */
+ knod_add32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ /* a -= c; */
+ knod_sub32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* a ^= rol32(c, 16); */
+ knod_iset32(&p32, 32 - 16);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[c_reg],
+ r32[c_reg], p32);
+ knod_xor32(priv, meta, r32[a_reg], r32[a_reg], r32[d_reg]);
+ /* c += b; */
+ knod_add32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* b -= a; */
+ knod_sub32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ /* b ^= rol32(a, 19); */
+ knod_iset32(&p32, 32 - 19);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[a_reg],
+ r32[a_reg], p32);
+ knod_xor32(priv, meta, r32[b_reg], r32[b_reg], r32[d_reg]);
+ /* a += c; */
+ knod_add32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* c -= b; */
+ knod_sub32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* c ^= rol32(b, 4); */
+ knod_iset32(&p32, 32 - 4);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[b_reg],
+ r32[b_reg], p32);
+ knod_xor32(priv, meta, r32[c_reg], r32[c_reg], r32[d_reg]);
+ /* b += a; */
+ knod_add32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ length -= 12;
+ key_in_pkt += 3;
+ }
+
+ switch (length) {
+ case 12:
+ /* c += (unsigned int)k[11]<<24; */
+ fallthrough;
+ case 11:
+ /* c += (unsigned int)k[10]<<16; */
+ fallthrough;
+ case 10:
+ /* c += (unsigned int)k[9]<<8; */
+ fallthrough;
+ case 9:
+ /* c += k[8]; */
+ knod_add32(priv, meta, r32[c_reg], r32[c_reg],
+ r32[key_in_pkt + 2]);
+ fallthrough;
+ case 8:
+ /* b += (unsigned int)k[7]<<24; */
+ fallthrough;
+ case 7:
+ /* b += (unsigned int)k[6]<<16; */
+ fallthrough;
+ case 6:
+ /* b += (unsigned int)k[5]<<8; */
+ fallthrough;
+ case 5:
+ /* b += k[4]; */
+ knod_add32(priv, meta, r32[b_reg], r32[b_reg],
+ r32[key_in_pkt + 1]);
+ fallthrough;
+ case 4:
+ /* a += (unsigned int)k[3]<<24; */
+ fallthrough;
+ case 3:
+ /* a += (unsigned int)k[2]<<16; */
+ fallthrough;
+ case 2:
+ /* a += (unsigned int)k[1]<<8; */
+ fallthrough;
+ case 1:
+ /* a += k[0]; */
+ knod_add32(priv, meta, r32[a_reg], r32[a_reg],
+ r32[key_in_pkt]);
+ /* c ^= b; */
+ knod_xor32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* c -= rol32(b, 14); */
+ knod_iset32(&p32, 32 - 14);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[b_reg],
+ r32[b_reg], p32);
+ knod_sub32(priv, meta, r32[c_reg], r32[c_reg], r32[d_reg]);
+ /* a ^= c; */
+ knod_xor32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* a -= rol32(c, 11); */
+ knod_iset32(&p32, 32 - 11);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[c_reg],
+ r32[c_reg], p32);
+ knod_sub32(priv, meta, r32[a_reg], r32[a_reg], r32[d_reg]);
+ /* b ^= a; */
+ knod_xor32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ /* b -= rol32(a, 25); */
+ knod_iset32(&p32, 32 - 25);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[a_reg],
+ r32[a_reg], p32);
+ knod_sub32(priv, meta, r32[b_reg], r32[b_reg], r32[d_reg]);
+ /* c ^= b; */
+ knod_xor32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* c -= rol32(b, 16); */
+ knod_iset32(&p32, 32 - 16);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[b_reg],
+ r32[b_reg], p32);
+ knod_sub32(priv, meta, r32[c_reg], r32[c_reg], r32[d_reg]);
+ /* a ^= c; */
+ knod_xor32(priv, meta, r32[a_reg], r32[a_reg], r32[c_reg]);
+ /* a -= rol32(c, 4); */
+ knod_iset32(&p32, 32 - 4);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[c_reg],
+ r32[c_reg], p32);
+ knod_sub32(priv, meta, r32[a_reg], r32[a_reg], r32[d_reg]);
+ /* b ^= a; */
+ knod_xor32(priv, meta, r32[b_reg], r32[b_reg], r32[a_reg]);
+ /* b -= rol32(a, 14); */
+ knod_iset32(&p32, 32 - 14);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[a_reg],
+ r32[a_reg], p32);
+ knod_sub32(priv, meta, r32[b_reg], r32[b_reg], r32[d_reg]);
+ /* c ^= b; */
+ knod_xor32(priv, meta, r32[c_reg], r32[c_reg], r32[b_reg]);
+ /* c -= rol32(b, 24); */
+ knod_iset32(&p32, 32 - 24);
+ knod_alignbit32(priv, meta, r32[d_reg], r32[b_reg],
+ r32[b_reg], p32);
+ knod_sub32(priv, meta, r32[c_reg], r32[c_reg], r32[d_reg]);
+ break;
+ case 0: /* Nothing left to add */
+ break;
+ }
+
+ knod_mov32(priv, meta, r64[dst_idx].lo, r32[c_reg]);
+}
+
+static u64 knod_bpf_map_gaddr(struct knod_bpf_priv *priv, int id)
+{
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_map *knod_map;
+ struct knod_mem *mem;
+
+ mutex_lock(&knodev->lock);
+ list_for_each_entry(knod_map, &knodev->accel->xdp.bound_maps, list) {
+ if (knod_map->offmap->map.id == id) {
+ mem = knod_map->mem;
+ mutex_unlock(&knodev->lock);
+ return (u64)mem->gaddr;
+ }
+ }
+ mutex_unlock(&knodev->lock);
+
+ return 0;
+}
+
+static void *knod_bpf_map_kaddr(struct knod_bpf_priv *priv, int id)
+{
+ struct knod_dev *knodev = priv->knodev;
+ struct knod_bpf_map *knod_map;
+ struct knod_mem *mem;
+
+ mutex_lock(&knodev->lock);
+ list_for_each_entry(knod_map, &knodev->accel->xdp.bound_maps, list) {
+ if (knod_map->offmap->map.id == id) {
+ mem = knod_map->mem;
+
+ /* GPUVM */
+ mutex_unlock(&knodev->lock);
+ return (void *)mem->kaddr;
+ }
+ }
+ mutex_unlock(&knodev->lock);
+
+ return NULL;
+}
+
+static u64 knod_bpf_get_map_gaddr(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta1,
+ struct knod_insn_meta *meta2)
+{
+ struct bpf_map *map;
+
+ map = (void *)(unsigned long)((u32)meta1->insn.imm |
+ (u64)meta2->insn.imm << 32);
+
+ return knod_bpf_map_gaddr(priv, map->id);
+}
+
+static int knod_bpf_get_map_id(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta1,
+ struct knod_insn_meta *meta2)
+{
+ struct bpf_map *map;
+
+ map = (void *)(unsigned long)((u32)meta1->insn.imm |
+ (u64)meta2->insn.imm << 32);
+
+ return map->id;
+}
+
+static int knod_bpf_get_amdgpu_insn_idx(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int t)
+{
+ int i, insn_idx = meta->amdgpu_insn_idx;
+
+ for (i = 0; i < t; i++)
+ insn_idx += meta->amdgpu_insn[i].size / 4;
+
+ return insn_idx;
+}
+
+static void knod_bpf_fixup_branch(struct knod_bpf_priv *priv,
+ struct amdgcn_branch_fixup *fixup)
+{
+ int target_off = knod_bpf_get_amdgpu_insn_idx(priv,
+ fixup->target_label->meta,
+ fixup->target_label->insn_idx);
+ int cur_off = knod_bpf_get_amdgpu_insn_idx(priv,
+ fixup->meta,
+ fixup->insn_idx);
+ cur_off++;
+
+ target_off -= cur_off;
+ emit_branch_fixup(priv->isa_version,
+ &fixup->meta->amdgpu_insn[fixup->insn_idx],
+ target_off);
+ knod_jit_dbg(" target_off was updated to %d\n", target_off);
+}
+
+static void knod_bpf_set_fixup(struct knod_insn_meta *meta,
+ struct amdgcn_branch_fixup *fixup,
+ struct amdgcn_label *target_label,
+ int insn_idx)
+{
+ fixup->meta = meta;
+ fixup->insn_idx = insn_idx;
+ fixup->target_label = target_label;
+}
+
+static void knod_bpf_set_label(struct knod_insn_meta *meta,
+ struct amdgcn_label *label,
+ int insn_idx)
+{
+ label->meta = meta;
+ label->insn_idx = insn_idx;
+}
+
+/*
+ * knod_bpf_emit_offlen_writeback - Write updated off/len to spsc_bd.
+ *
+ * Uses PAGE_BASE_VREG (set once in prologue), computes
+ * new_off = DATA_VREG_LO - PAGE_BASE_VREG_LO and
+ * new_len = DATA_END_VREG_LO - DATA_VREG_LO, packs them as (len<<16)|off,
+ * and stores the result at spsc_bd.off via SLOT_VREG.
+ *
+ * Clobbers: TMP_VREG2 (v26:v27).
+ */
+static void knod_bpf_emit_offlen_writeback(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ struct amdgcn_param32 s0, s1, data_lo, data_end_lo, pbase_lo, slot_lo;
+ struct amdgcn_param32 imm;
+
+ knod_vset32(&s0, KNOD_AMDGPU_TMP_VREG2_LO);
+ knod_vset32(&s1, KNOD_AMDGPU_TMP_VREG2_HI);
+ knod_vset32(&data_lo, KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(&data_end_lo, KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_vset32(&pbase_lo, KNOD_AMDGPU_PAGE_BASE_VREG_LO);
+ knod_vset32(&slot_lo, KNOD_AMDGPU_SLOT_VREG_LO);
+
+ /* s0 = len = DATA_END_LO - DATA_LO */
+ knod_sub32(priv, meta, s0, data_end_lo, data_lo);
+
+ /* s0 = len << 16 */
+ knod_iset32(&imm, 16);
+ knod_lshlrev32(priv, meta, s0, imm, s0);
+
+ /* s1 = off = DATA_LO - page_base_lo */
+ knod_sub32(priv, meta, s1, data_lo, pbase_lo);
+
+ /* s0 = (len << 16) | off */
+ knod_or32(priv, meta, s0, s0, s1);
+
+ /* Store packed {off, len} to spsc_bd */
+ knod_emit(priv, meta, global_store_dword, s0, slot_lo,
+ offsetof(struct spsc_bd, off));
+}
+
+/*
+ * knod_bpf_xdp_adjust_head - JIT bpf_xdp_adjust_head (helper 44).
+ *
+ * R2 = delta (signed 32-bit). Adjusts DATA_VREG by delta.
+ * Bounds: page_base <= DATA_VREG <= DATA_END_VREG - ETH_HLEN.
+ * Each bound is checked with its own VOPC, but VCC is captured into
+ * VGPRs via v_cndmask (VALU) rather than SGPRs via s_mov_b64 (SALU).
+ * VALU reads VCC correctly after VOPC; only SALU suffers the GFX10
+ * dual-VOPC stale-read hazard.
+ * page_base is reloaded on demand from param + spsc_bd.
+ * On failure, DATA_VREG is restored and R0 = -EINVAL.
+ * On success, R0 = 0.
+ *
+ * Clobbers: TMP_VREG0 (v22:v23), TMP_VREG1 (v24:v25), TMP_VREG2 (v26:v27),
+ * TMP_SREG0 (s16), TMP_SREG2 (s20:s21).
+ */
+static void knod_bpf_xdp_adjust_head(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ struct amdgcn_param32 ub_lo, ub_hi, dend_lo, dend_hi, sext_dst;
+ struct amdgcn_param32 shift_amt;
+ struct amdgcn_param32 tmp0_lo, tmp0_hi, data_lo, data_hi, fail_lo;
+ struct amdgcn_param32 fail_hi;
+ struct amdgcn_param64 data_vreg, pbase_vreg, ub;
+ struct amdgcn_param32 r0_lo, r0_hi, imm, delta;
+
+ knod_vset64(&data_vreg, KNOD_AMDGPU_DATA_VREG_LO);
+
+ knod_vset32(&tmp0_lo, KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_vset32(&tmp0_hi, KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_vset32(&data_lo, KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(&data_hi, KNOD_AMDGPU_DATA_VREG_HI);
+ knod_vset32(&r0_lo, KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&r0_hi, KNOD_AMDGPU_VREG0_HI);
+ knod_vset32(&delta, bpf_reg64[2].lo.v);
+ knod_vset32(&fail_lo, KNOD_AMDGPU_TMP_VREG2_LO);
+ knod_vset32(&fail_hi, KNOD_AMDGPU_TMP_VREG2_HI);
+
+ /* 1. Save original DATA_VREG -> TMP_VREG0 */
+ knod_mov32(priv, meta, tmp0_lo, data_lo);
+ knod_mov32(priv, meta, tmp0_hi, data_hi);
+
+ /* 2. DATA_VREG += delta (R2.lo, sign-extended to 64-bit) */
+ knod_emit(priv, meta, v_add_co_u32, data_lo, delta, data_lo);
+
+ knod_vset32(&sext_dst, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_iset32(&shift_amt, 31);
+ knod_emit(priv, meta, v_ashrrev_i32, sext_dst, shift_amt, delta);
+
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, data_hi, sext_dst,
+ data_hi);
+
+ /* 3. Lower bound: DATA_VREG < page_base -> VCC = fail */
+ knod_vset64(&pbase_vreg, KNOD_AMDGPU_PAGE_BASE_VREG_LO);
+ knod_emit(priv, meta, v_cmp_lt_u64, data_vreg, pbase_vreg);
+
+ /*
+ * Capture VCC -> VGPR via v_cndmask (VALU reads VCC correctly,
+ * unlike SALU which suffers the dual-VOPC stale-read hazard).
+ */
+ knod_iset32(&imm, 1);
+ knod_mov32(priv, meta, fail_hi, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, fail_lo, imm, fail_hi);
+
+ /* 5. Upper bound: DATA_VREG > DATA_END_VREG - ETH_HLEN */
+ knod_vset32(&ub_lo, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_vset32(&ub_hi, KNOD_AMDGPU_TMP_VREG1_HI);
+ knod_vset32(&dend_lo, KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_vset32(&dend_hi, KNOD_AMDGPU_DATA_END_VREG_HI);
+
+ knod_iset32(&imm, ETH_HLEN);
+ /*
+ * v_sub_co_u32 is VOP2 on GFX9, whose vsrc1 must be a VGPR (a literal
+ * there reads v0). Subtraction is not commutative, so materialise
+ * ETH_HLEN into a scratch VGPR (ub_hi, overwritten by the high half
+ * below) and use it as src1 instead of an immediate.
+ */
+ knod_mov32(priv, meta, ub_hi, imm);
+ knod_emit(priv, meta, v_sub_co_u32, ub_lo, dend_lo, ub_hi);
+ knod_iset32(&imm, 0);
+ knod_mov32(priv, meta, delta, imm);
+ knod_emit(priv, meta, v_sub_co_ci_u32_e32, ub_hi, dend_hi, delta);
+
+ knod_vset64(&ub, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_emit(priv, meta, v_cmp_gt_u64, data_vreg, ub);
+
+ /* Capture upper_fail via v_cndmask, combine, convert to VCC */
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, fail_hi, imm, fail_hi);
+
+ knod_emit(priv, meta, v_or_b32_e32, fail_lo, fail_lo, fail_hi);
+
+ knod_emit(priv, meta, v_cmp_lt_u32, imm, fail_lo);
+
+ /* 6. Conditional restore: VCC=1(fail) -> original,
+ * VCC=0(pass) -> adjusted
+ */
+ knod_emit(priv, meta, v_cndmask_b32_e32, data_lo, data_lo,
+ tmp0_lo);
+ knod_emit(priv, meta, v_cndmask_b32_e32, data_hi, data_hi,
+ tmp0_hi);
+
+ /* 7. R0 = VCC ? -EINVAL : 0 */
+ knod_iset32(&imm, -EINVAL);
+ knod_mov32(priv, meta, tmp0_lo, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, r0_lo, imm, tmp0_lo);
+
+ knod_iset32(&imm, -1);
+ knod_mov32(priv, meta, tmp0_hi, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, r0_hi, imm, tmp0_hi);
+}
+
+/*
+ * knod_bpf_xdp_adjust_tail - JIT bpf_xdp_adjust_tail (helper 65).
+ *
+ * R2 = delta (signed 32-bit). Adjusts DATA_END_VREG by delta.
+ * Bounds: DATA_VREG + ETH_HLEN <= DATA_END_VREG <= page_base + PAGE_SIZE.
+ * Each bound is checked with its own VOPC, but VCC is captured into
+ * VGPRs via v_cndmask (VALU) rather than SGPRs via s_mov_b64 (SALU).
+ * VALU reads VCC correctly after VOPC; only SALU suffers the GFX10
+ * dual-VOPC stale-read hazard.
+ * page_base is reloaded on demand from param + spsc_bd.
+ * On failure, DATA_END_VREG is restored and R0 = -EINVAL.
+ * On success, R0 = 0.
+ *
+ * Clobbers: TMP_VREG0 (v22:v23), TMP_VREG1 (v24:v25), TMP_VREG2 (v26:v27),
+ * TMP_SREG0 (s16), TMP_SREG2 (s20:s21).
+ */
+static void knod_bpf_xdp_adjust_tail(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ struct amdgcn_param32 tmp0_lo, tmp0_hi, dend_lo, dend_hi, fail_lo;
+ struct amdgcn_param32 fail_hi;
+ struct amdgcn_param32 lb_lo, lb_hi, d_lo, d_hi, sext_dst, shift_amt;
+ struct amdgcn_param32 pb_src_lo, pb_src_hi;
+ struct amdgcn_param32 r0_lo, r0_hi;
+ struct amdgcn_param32 imm, delta;
+ struct amdgcn_param64 dend_vreg, lb;
+
+ knod_vset32(&tmp0_lo, KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_vset32(&tmp0_hi, KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_vset32(&dend_lo, KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_vset32(&dend_hi, KNOD_AMDGPU_DATA_END_VREG_HI);
+ knod_vset32(&r0_lo, KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&r0_hi, KNOD_AMDGPU_VREG0_HI);
+ knod_vset32(&delta, bpf_reg64[2].lo.v);
+ knod_vset32(&fail_lo, KNOD_AMDGPU_TMP_VREG2_LO);
+ knod_vset32(&fail_hi, KNOD_AMDGPU_TMP_VREG2_HI);
+ knod_vset64(&dend_vreg, KNOD_AMDGPU_DATA_END_VREG_LO);
+
+ /* 1. Save original DATA_END_VREG -> TMP_VREG0 */
+ knod_mov32(priv, meta, tmp0_lo, dend_lo);
+ knod_mov32(priv, meta, tmp0_hi, dend_hi);
+
+ /* 2. DATA_END_VREG += delta (R2.lo, sign-extended) */
+ knod_emit(priv, meta, v_add_co_u32, dend_lo, delta, dend_lo);
+
+ knod_vset32(&sext_dst, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_iset32(&shift_amt, 31);
+ knod_emit(priv, meta, v_ashrrev_i32, sext_dst, shift_amt, delta);
+
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, dend_hi, sext_dst,
+ dend_hi);
+
+ /* 3. Lower bound: lb = DATA + ETH_HLEN -> TMP_VREG1 */
+ knod_vset32(&lb_lo, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_vset32(&lb_hi, KNOD_AMDGPU_TMP_VREG1_HI);
+ knod_vset32(&d_lo, KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(&d_hi, KNOD_AMDGPU_DATA_VREG_HI);
+
+ knod_iset32(&imm, ETH_HLEN);
+ knod_emit(priv, meta, v_add_co_u32, lb_lo, imm, d_lo);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, lb_hi, imm, d_hi);
+
+ /* VOPC#1: DATA_END < lb -> VCC = lower_fail */
+ knod_vset64(&lb, KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_emit(priv, meta, v_cmp_lt_u64, dend_vreg, lb);
+
+ /*
+ * Capture VCC -> VGPR via v_cndmask (VALU reads VCC correctly,
+ * unlike SALU which suffers the GFX10 dual-VOPC stale-read hazard).
+ */
+ knod_iset32(&imm, 1);
+ knod_mov32(priv, meta, fail_hi, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, fail_lo, imm, fail_hi);
+
+ /* 4. Upper bound: ub = page_base + PAGE_SIZE -> TMP_VREG1 */
+ knod_vset32(&pb_src_lo, KNOD_AMDGPU_PAGE_BASE_VREG_LO);
+ knod_vset32(&pb_src_hi, KNOD_AMDGPU_PAGE_BASE_VREG_HI);
+ knod_mov32(priv, meta, lb_lo, pb_src_lo);
+ knod_mov32(priv, meta, lb_hi, pb_src_hi);
+
+ knod_iset32(&imm, PAGE_SIZE);
+ knod_emit(priv, meta, v_add_co_u32, lb_lo, imm, lb_lo);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_add_co_ci_u32_e32, lb_hi, imm, lb_hi);
+
+ /* VOPC#2: DATA_END > ub -> VCC = upper_fail */
+ knod_emit(priv, meta, v_cmp_gt_u64, dend_vreg, lb);
+
+ /* Capture upper_fail via v_cndmask, combine, convert to VCC */
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, fail_hi, imm, fail_hi);
+
+ knod_emit(priv, meta, v_or_b32_e32, fail_lo, fail_lo, fail_hi);
+
+ knod_emit(priv, meta, v_cmp_lt_u32, imm, fail_lo);
+
+ /* 5. Conditional restore: VCC=1(fail) -> original,
+ * VCC=0(pass) -> adjusted
+ */
+ knod_emit(priv, meta, v_cndmask_b32_e32, dend_lo, dend_lo,
+ tmp0_lo);
+ knod_emit(priv, meta, v_cndmask_b32_e32, dend_hi, dend_hi,
+ tmp0_hi);
+
+ /* 6. R0 = VCC ? -EINVAL : 0 */
+ knod_iset32(&imm, -EINVAL);
+ knod_mov32(priv, meta, tmp0_lo, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, r0_lo, imm, tmp0_lo);
+
+ knod_iset32(&imm, -1);
+ knod_mov32(priv, meta, tmp0_hi, imm);
+ knod_iset32(&imm, 0);
+ knod_emit(priv, meta, v_cndmask_b32_e32, r0_hi, imm, tmp0_hi);
+}
+
+static void knod_bpf_load_size(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 *dst,
+ /* packet or stack */
+ struct amdgcn_param32 *cache,
+ int size, int off)
+{
+ struct amdgcn_param32 p32[2];
+
+ knod_jit_dbg(" %d: off = %d off_4 = %d size = %d\n", meta->bpf_insn_idx,
+ off, off%4, size);
+ switch (size) {
+ case sizeof(unsigned long):
+ if ((off % 4) == 0) {
+ knod_mov32(priv, meta, dst->lo, cache[off / 4]);
+ knod_mov32(priv, meta, dst->hi,
+ cache[(off / 4) + 1]);
+ } else if ((off % 4) == 1) {
+ WARN_ON_ONCE(1);
+ } else if ((off % 4) == 2) {
+ WARN_ON_ONCE(1);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case sizeof(unsigned int):
+ if ((off % 4) == 0) {
+ knod_mov32(priv, meta, dst->lo, cache[off / 4]);
+ } else if ((off % 4) == 1) {
+ knod_iset32(&p32[0], 8);
+ knod_lshrrev32(priv, meta, r32[0], p32[0],
+ cache[off / 4]);
+ knod_iset32(&p32[0], 24);
+ knod_lshlrev32(priv, meta, dst->lo, p32[0],
+ cache[(off / 4) + 1]);
+ knod_or32(priv, meta, dst->lo, dst->lo, r32[0]);
+ } else if ((off % 4) == 2) {
+ knod_iset32(&p32[0], 16);
+ knod_lshrrev32(priv, meta, r32[0], p32[0],
+ cache[off / 4]);
+ knod_lshlrev32(priv, meta, dst->lo, p32[0],
+ cache[(off / 4) + 1]);
+ knod_or32(priv, meta, dst->lo, dst->lo, r32[0]);
+ } else {
+ knod_iset32(&p32[0], 24);
+ knod_lshrrev32(priv, meta, r32[0], p32[0],
+ cache[off / 4]);
+ knod_iset32(&p32[0], 8);
+ knod_lshlrev32(priv, meta, dst->lo, p32[0],
+ cache[(off / 4) + 1]);
+ knod_or32(priv, meta, dst->lo, dst->lo, r32[0]);
+ }
+ break;
+ case sizeof(unsigned short):
+ if ((off % 4) == 3) {
+ knod_iset32(&p32[0], 24);
+ knod_iset32(&p32[1], 8);
+ knod_bfe32(priv, meta, r64[0].lo, cache[off / 4],
+ p32[0], p32[1]);
+ knod_iset32(&p32[0], 0);
+ knod_bfe32(priv, meta, r64[0].hi,
+ cache[(off / 4) + 1], p32[0], p32[1]);
+ /* bpf_reg64[d].lo = (r64[0].hi << 8) | r64[0].lo. */
+ knod_emit(priv, meta, v_lshl_or_b32, dst->lo,
+ r64[0].hi, p32[1], r64[0].lo);
+ } else {
+ if (!(off % 4))
+ knod_iset32(&p32[0], 0);
+ else if ((off % 4) == 1)
+ knod_iset32(&p32[0], 8);
+ else if ((off % 4) == 2)
+ knod_iset32(&p32[0], 16);
+ knod_iset32(&p32[1], 16);
+ knod_bfe32(priv, meta, dst->lo, cache[off / 4],
+ p32[0], p32[1]);
+ }
+ break;
+ case sizeof(unsigned char):
+ if ((off % 4) == 0)
+ knod_iset32(&p32[0], 0);
+ else if ((off % 4) == 1)
+ knod_iset32(&p32[0], 8);
+ else if ((off % 4) == 2)
+ knod_iset32(&p32[0], 16);
+ else
+ knod_iset32(&p32[0], 24);
+ knod_iset32(&p32[1], 8);
+ knod_bfe32(priv, meta, dst->lo, cache[off / 4], p32[0],
+ p32[1]);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+
+ if (size != sizeof(unsigned long)) {
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, dst->hi, p32[0]);
+ }
+}
+
+/*
+ * GFX10 (RDNA2) quirk: global_load_{dword,dwordx2,dwordx4} silently
+ * clear the low 2 bits of the effective address, forcing Dword
+ * alignment. For PTR_TO_PACKET loads at a byte offset that is not
+ * Dword-aligned, round the offset down to the nearest 4-byte boundary,
+ * load enough contiguous dwords to cover the requested range, then use
+ * v_alignbit_b32 to extract the byte-aligned result. For size < 4 a
+ * final v_and_b32 masks the result to the correct width.
+ *
+ * Caller is responsible for zeroing dst.hi for size < 8; this helper
+ * only writes dst.lo (and dst.hi when size == 8).
+ *
+ * Scratch: up to 4 contiguous VGPRs at v32..v35
+ * (TMP_VREG5_LO..TMP_VREG6_HI).
+ */
+static void knod_bpf_emit_gfx10_unaligned_load(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int size,
+ struct amdgcn_param64 dst,
+ struct amdgcn_param32 src_lo,
+ int off)
+{
+ int off_a = off & ~3;
+ int shift_bits = (off - off_a) * 8;
+ int needed = DIV_ROUND_UP((off & 3) + size, 4);
+ struct amdgcn_param32 tmp[4];
+ struct amdgcn_param32 shift_imm, mask_imm;
+
+ knod_vset32(&tmp[0], KNOD_AMDGPU_TMP_VREG5_LO);
+ knod_vset32(&tmp[1], KNOD_AMDGPU_TMP_VREG5_HI);
+ knod_vset32(&tmp[2], KNOD_AMDGPU_TMP_VREG6_LO);
+ knod_vset32(&tmp[3], KNOD_AMDGPU_TMP_VREG6_HI);
+ knod_iset32(&shift_imm, shift_bits);
+
+ if (needed <= 1) {
+ knod_emit(priv, meta, global_load_dword, tmp[0], src_lo,
+ off_a);
+ } else if (needed == 2) {
+ knod_emit(priv, meta, global_load_dwordx2, tmp[0], src_lo,
+ off_a);
+ } else {
+ /* needed == 3: no dwordx3, widen to dwordx4. */
+ knod_emit(priv, meta, global_load_dwordx4, tmp[0], src_lo,
+ off_a);
+ }
+ knod_wait_vmcnt(priv, meta);
+
+ if (size <= 4) {
+ /* v_alignbit_b32 D, S0, S1, S2:
+ * D = ({S0, S1} >> S2)[31:0]
+ * S0 is HIGH, S1 is LOW. tmp[0] holds
+ * bytes[off_a..+4) (memory-low) and tmp[1] holds
+ * bytes[off_a+4..+8) (memory-high), so
+ * src0=tmp[1], src1=tmp[0].
+ */
+ if (shift_bits == 0)
+ knod_mov32(priv, meta, dst.lo, tmp[0]);
+ else
+ knod_alignbit32(priv, meta, dst.lo,
+ tmp[1], tmp[0], shift_imm);
+
+ if (size == 1) {
+ knod_iset32(&mask_imm, 0xff);
+ knod_and32(priv, meta, dst.lo, dst.lo,
+ mask_imm);
+ } else if (size == 2) {
+ knod_iset32(&mask_imm, 0xffff);
+ knod_and32(priv, meta, dst.lo, dst.lo,
+ mask_imm);
+ }
+ } else {
+ /* size == 8: two alignbits for low / high output dwords. */
+ if (shift_bits == 0) {
+ knod_mov32(priv, meta, dst.lo, tmp[0]);
+ knod_mov32(priv, meta, dst.hi, tmp[1]);
+ } else {
+ knod_alignbit32(priv, meta, dst.lo,
+ tmp[1], tmp[0], shift_imm);
+ knod_alignbit32(priv, meta, dst.hi,
+ tmp[2], tmp[1], shift_imm);
+ }
+ }
+}
+
+#define LABEL_NEXT 8
+#define LABEL_OUT 9
+static void knod_bpf_ktime_get_ns(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ struct amdgcn_param32 p[2];
+
+ knod_sset32(&p[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, s_load_dwordx2, p[0], p[1],
+ offsetof(struct knod_bpf_param, ktime_ns));
+
+ knod_emit(priv, meta, s_waitcnt_lgkmcnt);
+
+ knod_sset32(&p[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_mov32(priv, meta, bpf_reg64[0].lo, p[0]);
+ knod_sset32(&p[0], KNOD_AMDGPU_TMP_SREG0_HI);
+ knod_mov32(priv, meta, bpf_reg64[0].hi, p[0]);
+}
+
+static void knod_bpf_map_lookup(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int map_id)
+{
+ struct knod_bpf_map_obj *knod_map_obj_k, *knod_map_obj_g;
+ int off, len, _len, idx, key_in_pkt, key_in_map;
+ bool first_cmp;
+ struct amdgcn_branch_fixup fixups[12] = {0,};
+ struct amdgcn_label labels[10] = {0,};
+ u32 stack_off = meta->kreg.stack_off;
+ unsigned long bucket_gaddr;
+ struct amdgcn_param32 p32;
+ int fixup_idx = 0;
+
+ knod_map_obj_k =
+ (struct knod_bpf_map_obj *)knod_bpf_map_kaddr(priv, map_id);
+ knod_map_obj_g =
+ (struct knod_bpf_map_obj *)knod_bpf_map_gaddr(priv, map_id);
+ bucket_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, bucket);
+
+ knod_jit_dbg(" stack_off = %d map_id = %d\n", stack_off, map_id);
+ if (!knod_map_obj_g || !knod_map_obj_k)
+ WARN_ON_ONCE(1);
+
+ knod_bpf_load_size(priv, meta,
+ &r64[2],
+ &stack[0],
+ sizeof(unsigned int),
+ 512 + stack_off);
+ /* reg1 := bucket
+ * NOTE: bucket_gaddr is greater than X
+ */
+ knod_iset64(&p64[0], bucket_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[1], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[1].lo);
+
+ knod_iset64(&p64[1], 0);
+ knod_mov64(priv, meta, bpf_reg64[0], p64[1]);
+
+ /* BPF_REG0 = 0
+ * TMP_REG1 = bucket_gaddr
+ * TMP_REG2 = key
+ */
+
+ if (knod_map_obj_k->map_type == BPF_MAP_TYPE_ARRAY ||
+ knod_map_obj_k->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ /* if (key > knod_map_obj_k.max_entries)
+ * NOTE: integer
+ */
+ knod_iset64(&p64[1], knod_map_obj_k->max_entries);
+ knod_mov64(priv, meta, r64[3], p64[1]);
+ knod_emit(priv, meta, v_cmp_ge_u64, r64[2], r64[3]);
+ /* structurized CFG: save OOB lanes, narrow exec */
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0); /* update required */
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_OUT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+ /* PERCPU_ARRAY: bucket += workgroup_id_y * per_instance_size so
+ * each RX queue addresses its own instance and the atomic
+ * update after the lookup has no cross-CU contention. Auto
+ * xgroups keeps PERCPU programs at one workgroup per queue, so
+ * the queue id (workgroup_id_y) is the instance index.
+ */
+ if (knod_map_obj_k->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ /* r64[3] is scratch after the bounds check: .lo =
+ * workgroup_id_y, .hi = per_instance_size (too large
+ * for an inline constant, so stage both in VGPRs
+ * first).
+ */
+ knod_sset32(&p32, KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[3].lo, p32);
+ knod_iset64(&p64[1],
+ knod_map_obj_k->meta.ameta
+ .per_instance_size);
+ knod_mov32(priv, meta, r64[3].hi, p64[1].lo);
+ emit_v_mad_u64_u32(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ r64[1],
+ sr64[0].lo,
+ r64[3].hi, /* per_instance_size */
+ r64[3].lo, /* workgroup_id_y */
+ r64[1]); /* bucket */
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ }
+ /* elem_id = &bucket[key]; */
+ knod_iset64(&p64[1], knod_map_obj_k->value_size);
+ emit_v_mad_u64_u32(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ bpf_reg64[0],
+ sr64[0].lo,
+ p64[1].lo, /* value_size */
+ r64[2].lo, /* key */
+ r64[1]); /* bucket */
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ /* structurized CFG: restore OOB lanes */
+ knod_bpf_set_label(meta, &labels[LABEL_OUT],
+ meta->amdgpu_insns);
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+ } else if (knod_map_obj_k->map_type == BPF_MAP_TYPE_HASH) {
+ key_in_pkt = KEY_IN_PKT_64;
+ len = knod_map_obj_k->key_size;
+ off = stack_off;
+
+ /* TMP_VREGs(vgpr-pair)
+ * |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|
+ * | | | |K|K|K|K|K|K|K|K |K |K |K |K |K |K |K |K |
+ */
+ while (len) {
+ if (len >= sizeof(unsigned long))
+ _len = sizeof(unsigned long);
+ else
+ _len = len;
+ knod_bpf_load_size(priv, meta,
+ &r64[key_in_pkt],
+ &stack[0],
+ _len,
+ 512 + off);
+ key_in_pkt++;
+ len -= _len;
+ off += _len;
+ }
+
+ knod_jhash(priv, meta,
+ 2,
+ knod_map_obj_k->key_size,
+ knod_map_obj_k->meta.hmeta.hashrnd);
+ /* clear hi register of r64[2] because hash is 32bit */
+ knod_iset64(&p64[0], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[0].lo);
+
+ /* hash = hash & (n_buckets - 1) before indexing the bucket
+ * array -- jhash returns the full 32-bit hash and the update
+ * and delete emitters mask it too; without this
+ * bucket_gaddr[hash] runs off the end of the bucket array.
+ */
+ knod_iset32(&p64[0].lo,
+ knod_map_obj_k->meta.hmeta.n_buckets - 1);
+ knod_and32(priv, meta, r64[2].lo, p64[0].lo, r64[2].lo);
+
+ /* elem_id = bucket_gaddr[hash]; */
+ knod_iset64(&p64[1], sizeof(int));
+ emit_v_mad_u64_u32(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ r64[2],
+ sr64[0].lo,
+ p64[1].lo, /* sizeof(int) */
+ r64[2].lo, /* hash */
+ r64[1]); /* bucket */
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ /* bpf_reg64[0] = 0 (default return for not-found lanes) */
+ knod_iset64(&p64[0], 0);
+ knod_mov64(priv, meta, bpf_reg64[0], p64[0]);
+
+ /* structurized CFG: save initial exec for restoring at end */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ knod_bpf_set_label(meta, &labels[LABEL_NEXT],
+ meta->amdgpu_insns);
+ /* load elem_id from elem structure */
+ knod_emit(priv, meta, global_load_dword, r64[2].lo,
+ r64[2].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* mask out DELETED bit from next field */
+ knod_iset32(&p32, KNOD_BPF_HASH_NEXT_MASK);
+ knod_emit(priv, meta, v_and_b32_e32, r64[2].lo, p32,
+ r64[2].lo);
+
+ /* if (r64[2].lo == KNOD_BPF_HASH_NEXT_END)
+ * goto out;
+ * VOPC cannot encode literal constants - move to VGPR first.
+ * NEXT_MASK == NEXT_END (0x7FFFFFFF), reuse p32 from v_and
+ * above.
+ */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[0].hi, p32);
+ knod_emit(priv, meta, v_cmp_eq_u32, r64[0].hi, r64[2].lo);
+ /* structurized CFG: remove end-of-chain lanes */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0); /* update required */
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_OUT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ knod_iset64(&p64[0],
+ (unsigned long)knod_map_obj_k->meta.hmeta.elems);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[0], knod_map_obj_k->meta.hmeta.elem_size);
+ knod_mov64(priv, meta, r64[0], p64[0]);
+
+ key_in_map = KEY_IN_MAP_32;
+ len = knod_map_obj_k->key_size;
+ off = offsetof(struct knod_bpf_hash_elem_obj, kv);
+
+ /* elem = &elems[elem_id]; */
+ emit_v_mad_u64_u32(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ r64[2], /* elem */
+ sr64[0].lo,
+ r64[0].lo, /* elem_size */
+ r64[2].lo, /* elem_id */
+ r64[1]); /* elem_gaddr */
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ /* load elem.next for DELETED check (parallel with key loads) */
+ knod_emit(priv, meta, global_load_dword, r64[0].hi,
+ r64[2].lo, 0);
+ while (len >= 16) {
+ knod_emit(priv, meta, global_load_dwordx4,
+ r32[key_in_map],
+ r64[2].lo, /* elem ptr */ off);
+ off += 16;
+ len -= 16;
+ key_in_map += 4;
+ }
+
+ if (len >= 8) {
+ knod_emit(priv, meta, global_load_dwordx2,
+ r32[key_in_map],
+ /* elem_id */ r64[2].lo, /* elem ptr */ off);
+ off += 8;
+ len -= 8;
+ key_in_map += 2;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, global_load_dword,
+ r32[key_in_map],
+ /* elem_id */ r64[2].lo, /* elem ptr */ off);
+ off += 4;
+ len -= 4;
+ key_in_map += 1;
+ }
+
+ /* map key padding was inited to zero, no AND is required */
+ if (len) {
+ knod_emit(priv, meta, global_load_dword,
+ r32[key_in_map],
+ /* elem_id */ r64[2].lo, /* elem ptr */ off);
+ }
+
+ knod_wait_vmcnt(priv, meta);
+
+ /* structurized CFG: accumulate key match into TMP_SREG4
+ * instead of early-exit branching per key part
+ */
+ key_in_map = KEY_IN_MAP_32;
+ key_in_pkt = KEY_IN_PKT_32;
+ len = knod_map_obj_k->key_size;
+ first_cmp = true;
+
+ while (len >= 8) {
+ knod_emit(priv, meta, v_cmp_eq_u64, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+
+ key_in_map += 2;
+ key_in_pkt += 2;
+ len -= 8;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+
+ key_in_map += 1;
+ key_in_pkt += 1;
+ len -= 4;
+ }
+
+ if (len) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ }
+
+ /* DELETED check: remove deleted lanes from match result.
+ * r64[0].hi = elem.next (loaded in parallel with key).
+ * Deleted elems have bit 31 set - exclude them from SREG4.
+ */
+ knod_iset32(&p32, KNOD_BPF_HASH_NEXT_DELETED);
+ knod_emit(priv, meta, v_and_b32_e32, r64[0].hi, p32,
+ r64[0].hi);
+ knod_iset32(&p32, 0);
+ knod_emit(priv, meta, v_cmp_eq_u32, p32, r64[0].hi);
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG4_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO, AMDGCN_SREG_VCC_LO);
+
+ /* TMP_SREG4 = lanes where key matched AND not deleted.
+ * Save current exec, narrow to matched lanes for value
+ * computation.
+ */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG5_LO,
+ AMDGCN_SREG_EXEC_LO);
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG4_LO);
+
+ /* bpf_reg64[0] = value address (only for matched lanes) */
+ knod_iset64(&p64[1],
+ offsetof(struct knod_bpf_hash_elem_obj, kv) +
+ knod_map_obj_k->key_size);
+ knod_add64(priv, meta, bpf_reg64[0], p64[1], r64[2]);
+
+ /* set exec to unmatched lanes for next loop iteration */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG5_LO, KNOD_AMDGPU_TMP_SREG4_LO);
+ /* loop back if any unmatched lanes remain */
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0); /* update required */
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_NEXT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+ /* structurized CFG: restore all original lanes */
+ knod_bpf_set_label(meta, &labels[LABEL_OUT],
+ meta->amdgpu_insns);
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+
+ } else {
+ WARN_ON_ONCE(1);
+ }
+}
+
+static void knod_bpf_map_update_array(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int map_id)
+{
+ struct knod_bpf_map_obj *knod_map_obj_k, *knod_map_obj_g;
+ struct amdgcn_branch_fixup fixups[4] = {0,};
+ u32 key_stack_off = meta->kreg.stack_off;
+ u32 val_stack_off = meta->vreg.stack_off;
+ struct amdgcn_label labels[10] = {0,};
+ int idx, val_off, val_len;
+ unsigned long bucket_gaddr;
+ int fixup_idx = 0;
+
+ knod_map_obj_k =
+ (struct knod_bpf_map_obj *)knod_bpf_map_kaddr(priv, map_id);
+ knod_map_obj_g =
+ (struct knod_bpf_map_obj *)knod_bpf_map_gaddr(priv, map_id);
+ bucket_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, bucket);
+
+ if (!knod_map_obj_g || !knod_map_obj_k)
+ WARN_ON_ONCE(1);
+
+ /* load key from stack -> r64[2] */
+ knod_bpf_load_size(priv, meta,
+ &r64[2],
+ &stack[0],
+ sizeof(unsigned int),
+ 512 + key_stack_off);
+
+ /* r64[1] = bucket_gaddr */
+ knod_iset64(&p64[0], bucket_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ /* clear r64[2].hi (key is 32-bit) */
+ knod_iset64(&p64[1], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[1].lo);
+
+ /* bpf_reg64[0] = 0 (return value) */
+ knod_mov64(priv, meta, bpf_reg64[0], p64[1]);
+
+ /* bounds check: if (key >= max_entries) -> skip */
+ knod_iset64(&p64[1], knod_map_obj_k->max_entries);
+ knod_mov64(priv, meta, r64[3], p64[1]);
+ knod_emit(priv, meta, v_cmp_ge_u64, r64[2], r64[3]);
+
+ /* structurized CFG: save OOB lanes, narrow exec */
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_OUT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* dest = bucket_gaddr + key * value_size -> r64[0] */
+ knod_iset64(&p64[1], knod_map_obj_k->value_size);
+ knod_emit(priv, meta, v_mad_u64_u32, r64[0], sr64[0].lo,
+ p64[1].lo, r64[2].lo, r64[1]);
+
+ /* load value from stack and store to dest */
+ val_off = 0;
+ val_len = knod_map_obj_k->value_size;
+
+ while (val_len >= 16) {
+ knod_bpf_load_size(priv, meta,
+ &r64[3],
+ &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_bpf_load_size(priv, meta,
+ &r64[4],
+ &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off + 8);
+ knod_emit(priv, meta, global_store_dwordx4, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 16;
+ val_len -= 16;
+ }
+
+ if (val_len >= 8) {
+ knod_bpf_load_size(priv, meta,
+ &r64[3],
+ &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dwordx2, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 8;
+ val_len -= 8;
+ }
+
+ if (val_len >= 4) {
+ knod_bpf_load_size(priv, meta,
+ &r64[3],
+ &stack[0],
+ sizeof(unsigned int),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dword, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 4;
+ val_len -= 4;
+ }
+
+ if (val_len >= 2) {
+ knod_bpf_load_size(priv, meta,
+ &r64[3],
+ &stack[0],
+ sizeof(unsigned short),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_short, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 2;
+ val_len -= 2;
+ }
+
+ if (val_len >= 1) {
+ knod_bpf_load_size(priv, meta,
+ &r64[3],
+ &stack[0],
+ sizeof(unsigned char),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_byte, r64[3].lo,
+ r64[0].lo, val_off);
+ }
+
+ /* structurized CFG: restore OOB lanes */
+ knod_bpf_set_label(meta, &labels[LABEL_OUT], meta->amdgpu_insns);
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+}
+
+static void knod_bpf_map_update_hash(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int map_id)
+{
+#define LABEL_BUCKET_LOOP 0
+#define LABEL_LOCK_RETRY 1
+#define LABEL_INSERT_LANE 2
+#define LABEL_CHAIN_NEXT 3
+#define LABEL_ALLOC_INSERT 4
+#define LABEL_LANE_DONE 5
+#define LABEL_UNLOCK 6
+ struct knod_bpf_map_obj *knod_map_obj_k, *knod_map_obj_g;
+ struct amdgcn_param32 s_bucket_lo, v_tmp, v_zero, v_one;
+ int off, len, _len, idx, key_in_pkt, key_in_map;
+ struct amdgcn_param32 s_exec_lo, s_exec_hi, s_elem_id;
+ struct amdgcn_branch_fixup fixups[12] = {0,};
+ u32 key_stack_off = meta->kreg.stack_off;
+ u32 val_stack_off = meta->vreg.stack_off;
+ unsigned long queue_gaddr, elems_gaddr;
+ unsigned long bucket_gaddr, cur_gaddr;
+ struct amdgcn_label labels[12] = {0,};
+ struct amdgcn_param32 v_minus_one;
+ struct amdgcn_param64 sr64_carry;
+ struct amdgcn_param32 p32;
+ unsigned long lock_offset;
+ unsigned int elem_size;
+ int val_off, val_len;
+ int fixup_idx = 0;
+ bool first_cmp;
+ int koff, voff;
+
+ knod_map_obj_k =
+ (struct knod_bpf_map_obj *)knod_bpf_map_kaddr(priv, map_id);
+ knod_map_obj_g =
+ (struct knod_bpf_map_obj *)knod_bpf_map_gaddr(priv, map_id);
+ bucket_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, bucket);
+ cur_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, meta.hmeta.cur);
+ queue_gaddr = (unsigned long)knod_map_obj_k->meta.hmeta.q;
+ elems_gaddr = (unsigned long)knod_map_obj_k->meta.hmeta.elems;
+ elem_size = knod_map_obj_k->meta.hmeta.elem_size;
+
+ if (!knod_map_obj_g || !knod_map_obj_k)
+ WARN_ON_ONCE(1);
+
+ /* ======== Phase 1: Setup ======== */
+
+ /* Load key from stack -> r64[3..9] (KEY_IN_PKT) */
+ key_in_pkt = KEY_IN_PKT_64;
+ len = knod_map_obj_k->key_size;
+ off = key_stack_off;
+ while (len) {
+ if (len >= sizeof(unsigned long))
+ _len = sizeof(unsigned long);
+ else
+ _len = len;
+ knod_bpf_load_size(priv, meta,
+ &r64[key_in_pkt],
+ &stack[0],
+ _len,
+ 512 + off);
+ key_in_pkt++;
+ len -= _len;
+ off += _len;
+ }
+
+ /* jhash -> r64[2].lo = hash */
+ knod_jhash(priv, meta,
+ 2,
+ knod_map_obj_k->key_size,
+ knod_map_obj_k->meta.hmeta.hashrnd);
+ knod_iset64(&p64[0], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[0].lo);
+
+ /* hash = hash & (n_buckets - 1) */
+ knod_iset32(&p64[0].lo,
+ knod_map_obj_k->meta.hmeta.n_buckets - 1);
+ knod_and32(priv, meta, r64[2].lo, p64[0].lo, r64[2].lo);
+
+ /* r64[1] = bucket_gaddr */
+ knod_iset64(&p64[0], bucket_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ /* bucket_addr = bucket_gaddr + hash * sizeof(int) -> r64[2] */
+ knod_iset64(&p64[1], sizeof(int));
+ knod_emit(priv, meta, v_mad_u64_u32, r64[2], sr64[0].lo,
+ p64[1].lo, r64[2].lo, r64[1]);
+
+ /* Save bucket_addr to r64[15] for CAS insert */
+ knod_mov64(priv, meta, r64[15], r64[2]);
+
+ /* SREG3 = initial exec */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ======== Phase 2: Sequential per-lane processing ======== */
+
+ /* SREG5 = exec (all lanes to process, for BUCKET_LOOP) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG5_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ---- BUCKET_LOOP: process one unique bucket per iteration ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_BUCKET_LOOP],
+ meta->amdgpu_insns);
+
+ lock_offset =
+ (unsigned long)knod_map_obj_k->meta.hmeta.n_buckets *
+ sizeof(unsigned int);
+
+ knod_sset32(&s_bucket_lo,
+ KNOD_AMDGPU_TMP_SREG1_HI);
+ knod_vset32(&v_tmp, KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(&v_zero, 0);
+ knod_iset32(&v_one, 1);
+ knod_sset32(&s_exec_lo, AMDGCN_SREG_EXEC_LO);
+ knod_sset32(&s_exec_hi,
+ AMDGCN_SREG_EXEC_LO + 1);
+ knod_sset32(&s_elem_id,
+ KNOD_AMDGPU_TMP_SREG1_LO);
+ knod_sset64(&sr64_carry,
+ KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* Pick first active lane's bucket addr */
+ knod_emit(priv, meta, v_readfirstlane_b32, KNOD_AMDGPU_TMP_SREG1_HI,
+ r64[15].lo.v);
+
+ /* vcc = lanes with same bucket */
+ knod_emit(priv, meta, v_cmp_eq_u32, s_bucket_lo, r64[15].lo);
+
+ /* SREG5 = remaining lanes; exec = same-bucket lanes */
+ knod_emit(priv, meta, s_and_saveexec_b64, KNOD_AMDGPU_TMP_SREG5_LO,
+ AMDGCN_SREG_VCC_LO);
+
+ /* SREG0 = same-bucket lanes */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ---- Lock acquire ---- */
+ /* r64[10] = r64[15] + lock_offset */
+ knod_iset64(&p64[0], lock_offset);
+ knod_add64(priv, meta, r64[10], p64[0], r64[15]);
+
+ /* r64[11].lo = 1 (swap data) */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[11].lo, v_one);
+
+ /* First-lane isolation via mbcnt */
+ knod_emit(priv, meta, v_mbcnt_lo_u32_b32, v_tmp, s_exec_lo,
+ v_zero);
+ knod_emit(priv, meta, v_mbcnt_hi_u32_b32, v_tmp, s_exec_hi, v_tmp);
+ knod_emit(priv, meta, v_cmp_eq_u32, v_zero, v_tmp);
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* LOCK_RETRY: spin until lock acquired */
+ knod_bpf_set_label(meta, &labels[LABEL_LOCK_RETRY], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, global_atomic_swap, r64[11].hi, r64[10].lo,
+ r64[11].lo, 0, 1);
+ knod_wait_vmcnt(priv, meta);
+
+ meta->amdgpu_insn[meta->amdgpu_insns].size =
+ emit_gfx10_v_cmp_ne_u32(
+ &meta->amdgpu_insn[meta->amdgpu_insns].gfx10,
+ v_zero, r64[11].hi);
+ meta->amdgpu_insn[meta->amdgpu_insns].type = AMDGCN_INSN_TYPE_VOPC;
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ emit_s_cbranch_vccnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LOCK_RETRY], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* Lock acquired - restore same-bucket lanes */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+
+ /* ---- INSERT_LANE: process one lane at a time ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_INSERT_LANE],
+ meta->amdgpu_insns);
+
+ /* SREG4 = exec (remaining same-bucket lanes) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* Pick first active lane via mbcnt */
+ knod_emit(priv, meta, v_mbcnt_lo_u32_b32, v_tmp, s_exec_lo,
+ v_zero);
+ knod_emit(priv, meta, v_mbcnt_hi_u32_b32, v_tmp, s_exec_hi, v_tmp);
+ knod_emit(priv, meta, v_cmp_eq_u32, v_zero, v_tmp);
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* SREG2 = exec (single-lane mask) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG2_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* r64[2] = r64[15] (bucket_addr for chain walk start) */
+ knod_mov64(priv, meta, r64[2], r64[15]);
+
+ /* ---- CHAIN_NEXT: walk chain ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_CHAIN_NEXT], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, global_load_dword, r64[0].lo, r64[2].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* Mask out DELETED bit */
+ knod_iset32(&p32, KNOD_BPF_HASH_NEXT_MASK);
+ knod_emit(priv, meta, v_and_b32_e32, r64[0].lo, p32, r64[0].lo);
+
+ /* End-of-chain check (VOPC literal workaround) */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[0].hi, p32);
+ knod_emit(priv, meta, v_cmp_eq_u32, r64[0].hi, r64[0].lo);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_ALLOC_INSERT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* elem_addr = elems + elem_id * elem_size -> r64[2] */
+ knod_iset64(&p64[0], elems_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[0], elem_size);
+ knod_mov32(priv, meta, r64[10].lo, p64[0].lo);
+ knod_emit(priv, meta, v_mad_u64_u32, r64[2], sr64_carry.lo,
+ r64[10].lo, r64[0].lo, r64[1]);
+
+ /* Load elem.next for DELETED check */
+ knod_emit(priv, meta, global_load_dword, r64[0].hi, r64[2].lo, 0);
+
+ /* Load key from map element -> KEY_IN_MAP */
+ key_in_map = KEY_IN_MAP_32;
+ len = knod_map_obj_k->key_size;
+ off = offsetof(struct knod_bpf_hash_elem_obj, kv);
+
+ while (len >= 16) {
+ knod_emit(priv, meta, global_load_dwordx4, r32[key_in_map],
+ r64[2].lo, off);
+ off += 16;
+ len -= 16;
+ key_in_map += 4;
+ }
+
+ if (len >= 8) {
+ knod_emit(priv, meta, global_load_dwordx2, r32[key_in_map],
+ r64[2].lo, off);
+ off += 8;
+ len -= 8;
+ key_in_map += 2;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, global_load_dword, r32[key_in_map],
+ r64[2].lo, off);
+ off += 4;
+ len -= 4;
+ key_in_map += 1;
+ }
+
+ if (len) {
+ knod_emit(priv, meta, global_load_dword, r32[key_in_map],
+ r64[2].lo, off);
+ }
+
+ knod_wait_vmcnt(priv, meta);
+
+ /* Key comparison -> SREG1 */
+ key_in_map = KEY_IN_MAP_32;
+ key_in_pkt = KEY_IN_PKT_32;
+ len = knod_map_obj_k->key_size;
+ first_cmp = true;
+
+ while (len >= 8) {
+ knod_emit(priv, meta, v_cmp_eq_u64, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ key_in_map += 2;
+ key_in_pkt += 2;
+ len -= 8;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ key_in_map += 1;
+ key_in_pkt += 1;
+ len -= 4;
+ }
+
+ if (len) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ }
+
+ /* DELETED check: SREG1 &= not_deleted */
+ knod_iset32(&p32,
+ KNOD_BPF_HASH_NEXT_DELETED);
+ knod_emit(priv, meta, v_and_b32_e32, r64[0].hi, p32, r64[0].hi);
+ knod_iset32(&p32, 0);
+ knod_emit(priv, meta, v_cmp_eq_u32, p32, r64[0].hi);
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO, AMDGCN_SREG_VCC_LO);
+
+ /* Narrow exec to matched lane */
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* Value overwrite for matched lane (exec-masked, skipped if no
+ * match)
+ */
+ knod_iset64(&p64[1],
+ offsetof(struct knod_bpf_hash_elem_obj,
+ kv) +
+ knod_map_obj_k->key_size);
+ knod_add64(priv, meta, r64[0], p64[1], r64[2]);
+
+ val_off = 0;
+ val_len = knod_map_obj_k->value_size;
+
+ while (val_len >= 16) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_bpf_load_size(priv, meta,
+ &r64[11], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off + 8);
+ knod_emit(priv, meta, global_store_dwordx4, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 16;
+ val_len -= 16;
+ }
+
+ if (val_len >= 8) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dwordx2, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 8;
+ val_len -= 8;
+ }
+
+ if (val_len >= 4) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned int),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dword, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 4;
+ val_len -= 4;
+ }
+
+ if (val_len >= 2) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned short),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_short, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 2;
+ val_len -= 2;
+ }
+
+ if (val_len >= 1) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned char),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_byte, r64[10].lo,
+ r64[0].lo, val_off);
+ }
+
+ /* If matched, done with this lane */
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* No match: restore lane, continue chain walk */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG2_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_CHAIN_NEXT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ---- ALLOC_INSERT: key not found, insert new elem ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_ALLOC_INSERT],
+ meta->amdgpu_insns);
+
+ /* Restore single-lane exec */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG2_LO);
+
+ /* Alloc from free pool: atomic_add(cur, -1) */
+ knod_iset32(&v_minus_one, -1);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[11].lo, v_minus_one);
+
+ knod_iset64(&p64[0], cur_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ knod_emit(priv, meta, global_atomic_add, r64[11].lo, r64[1].lo,
+ r64[11].lo, 0, 1);
+ knod_wait_vmcnt(priv, meta);
+
+ /* my_cur = old_cur - 1 -> r64[0].lo */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[0].lo, v_one);
+ knod_emit(priv, meta, v_sub_u32, r64[0].lo, r64[11].lo, r64[0].lo);
+
+ /* OOM check: if (my_cur < 0) -> skip insert */
+ knod_emit(priv, meta, v_cmp_gt_i32, v_zero, r64[0].lo);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* queue_addr = queue_gaddr + my_cur * 4 -> r64[1] */
+ knod_iset64(&p64[0], queue_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[1], sizeof(unsigned int));
+ knod_emit(priv, meta, v_mad_u64_u32, r64[1], sr64_carry.lo,
+ p64[1].lo, r64[0].lo, r64[1]);
+
+ /* elem_id = queue[my_cur] -> r64[0].lo */
+ knod_emit(priv, meta, global_load_dword, r64[0].lo, r64[1].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* new_elem_addr = elems + elem_id * elem_size -> r64[2] */
+ knod_iset64(&p64[0], elems_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[0], elem_size);
+ knod_mov32(priv, meta, r64[10].lo, p64[0].lo);
+ knod_emit(priv, meta, v_mad_u64_u32, r64[2], sr64_carry.lo,
+ r64[10].lo, r64[0].lo, r64[1]);
+
+ /* Save elem_id to SGPR (v_mad carry already done) */
+ knod_emit(priv, meta, v_readfirstlane_b32, KNOD_AMDGPU_TMP_SREG1_LO,
+ r64[0].lo.v);
+
+ /* Load current bucket head -> r64[1].lo */
+ knod_emit(priv, meta, global_load_dword, r64[1].lo, r64[15].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* new_elem.next = old_head */
+ knod_emit(priv, meta, global_store_dword, r64[1].lo, r64[2].lo, 0);
+
+ /* Write key to new element */
+ koff = offsetof(struct knod_bpf_hash_elem_obj, kv);
+
+ key_in_pkt = KEY_IN_PKT_32;
+ len = knod_map_obj_k->key_size;
+
+ while (len >= 8) {
+ knod_emit(priv, meta, global_store_dwordx2, r32[key_in_pkt],
+ r64[2].lo, koff);
+ koff += 8;
+ len -= 8;
+ key_in_pkt += 2;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, global_store_dword, r32[key_in_pkt],
+ r64[2].lo, koff);
+ koff += 4;
+ len -= 4;
+ key_in_pkt += 1;
+ }
+
+ if (len >= 2) {
+ knod_emit(priv, meta, global_store_short, r32[key_in_pkt],
+ r64[2].lo, koff);
+ koff += 2;
+ len -= 2;
+ }
+
+ if (len >= 1) {
+ knod_emit(priv, meta, global_store_byte, r32[key_in_pkt],
+ r64[2].lo, koff);
+ }
+
+ /* Write value to new element */
+ voff = offsetof(struct knod_bpf_hash_elem_obj, kv) +
+ knod_map_obj_k->key_size;
+
+ val_off = 0;
+ val_len = knod_map_obj_k->value_size;
+
+ knod_iset64(&p64[1], voff);
+ knod_add64(priv, meta, r64[0], p64[1], r64[2]);
+
+ while (val_len >= 16) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_bpf_load_size(priv, meta,
+ &r64[11], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off + 8);
+ knod_emit(priv, meta, global_store_dwordx4, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 16;
+ val_len -= 16;
+ }
+
+ if (val_len >= 8) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned long),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dwordx2, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 8;
+ val_len -= 8;
+ }
+
+ if (val_len >= 4) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned int),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_dword, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 4;
+ val_len -= 4;
+ }
+
+ if (val_len >= 2) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned short),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_short, r64[10].lo,
+ r64[0].lo, val_off);
+ val_off += 2;
+ val_len -= 2;
+ }
+
+ if (val_len >= 1) {
+ knod_bpf_load_size(priv, meta,
+ &r64[10], &stack[0],
+ sizeof(unsigned char),
+ 512 + val_stack_off + val_off);
+ knod_emit(priv, meta, global_store_byte, r64[10].lo,
+ r64[0].lo, val_off);
+ }
+
+ knod_wait_vmcnt(priv, meta);
+
+ /* Update bucket[hash] = new elem_id */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[1].lo, s_elem_id);
+ knod_emit(priv, meta, global_store_dword, r64[1].lo, r64[15].lo,
+ 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* ---- LANE_DONE: remove this lane, next lane ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO, KNOD_AMDGPU_TMP_SREG2_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_INSERT_LANE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ---- UNLOCK: release lock + next bucket ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_UNLOCK], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+
+ /* Recompute lock_addr (r64[10] clobbered) */
+ knod_iset64(&p64[0], lock_offset);
+ knod_add64(priv, meta, r64[10], p64[0], r64[15]);
+
+ knod_emit(priv, meta, v_mov_b32_e32, r64[1].lo, v_zero);
+ knod_emit(priv, meta, global_store_dword, r64[1].lo, r64[10].lo,
+ 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* Next bucket */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG5_LO, KNOD_AMDGPU_TMP_SREG0_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_BUCKET_LOOP], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ======== Phase 5: Restore ======== */
+
+ knod_bpf_set_label(meta, &labels[LABEL_OUT], meta->amdgpu_insns);
+ /* exec = SREG3 (restore all original lanes) */
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+
+ /* bpf_reg64[0] = 0 (return value for all lanes) */
+ knod_iset64(&p64[0], 0);
+ knod_mov64(priv, meta, bpf_reg64[0], p64[0]);
+
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+
+ return;
+#undef LABEL_BUCKET_LOOP
+#undef LABEL_LOCK_RETRY
+#undef LABEL_INSERT_LANE
+#undef LABEL_CHAIN_NEXT
+#undef LABEL_ALLOC_INSERT
+#undef LABEL_LANE_DONE
+#undef LABEL_UNLOCK
+}
+
+static void knod_bpf_map_delete_hash(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int map_id)
+{
+#define LABEL_BUCKET_LOOP 0
+#define LABEL_LOCK_RETRY 1
+#define LABEL_DELETE_LANE 2
+#define LABEL_CHAIN_NEXT 3
+#define LABEL_LANE_DONE 4
+#define LABEL_UNLOCK 5
+ struct knod_bpf_map_obj *knod_map_obj_k, *knod_map_obj_g;
+ struct amdgcn_param32 s_bucket_lo, v_tmp, v_zero, v_one;
+ int off, len, _len, idx, key_in_pkt, key_in_map;
+ struct amdgcn_branch_fixup fixups[12] = {0,};
+ unsigned long bucket_gaddr, gc_count_gaddr;
+ struct amdgcn_param32 s_exec_lo, s_exec_hi;
+ unsigned long gc_list_gaddr, elems_gaddr;
+ u32 key_stack_off = meta->kreg.stack_off;
+ struct amdgcn_label labels[12] = {0,};
+ struct amdgcn_param64 sr64_carry;
+ struct amdgcn_param32 v_del;
+ struct amdgcn_param32 p32;
+ unsigned long lock_offset;
+ unsigned int elem_size;
+ int fixup_idx = 0;
+ bool first_cmp;
+
+ knod_map_obj_k =
+ (struct knod_bpf_map_obj *)knod_bpf_map_kaddr(priv, map_id);
+ knod_map_obj_g =
+ (struct knod_bpf_map_obj *)knod_bpf_map_gaddr(priv, map_id);
+ bucket_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, bucket);
+ gc_count_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, meta.hmeta.gc_count);
+ gc_list_gaddr = (unsigned long)knod_map_obj_k->meta.hmeta.gc_list;
+ elems_gaddr = (unsigned long)knod_map_obj_k->meta.hmeta.elems;
+ elem_size = knod_map_obj_k->meta.hmeta.elem_size;
+
+ if (!knod_map_obj_g || !knod_map_obj_k)
+ WARN_ON_ONCE(1);
+
+ /* ======== Phase 1: Setup ======== */
+
+ /* Load key from stack -> r64[3..9] (KEY_IN_PKT) */
+ key_in_pkt = KEY_IN_PKT_64;
+ len = knod_map_obj_k->key_size;
+ off = key_stack_off;
+ while (len) {
+ if (len >= sizeof(unsigned long))
+ _len = sizeof(unsigned long);
+ else
+ _len = len;
+ knod_bpf_load_size(priv, meta,
+ &r64[key_in_pkt],
+ &stack[0],
+ _len,
+ 512 + off);
+ key_in_pkt++;
+ len -= _len;
+ off += _len;
+ }
+
+ /* jhash -> r64[2].lo = hash */
+ knod_jhash(priv, meta,
+ 2,
+ knod_map_obj_k->key_size,
+ knod_map_obj_k->meta.hmeta.hashrnd);
+ knod_iset64(&p64[0], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[0].lo);
+
+ /* hash = hash & (n_buckets - 1) */
+ knod_iset32(&p64[0].lo,
+ knod_map_obj_k->meta.hmeta.n_buckets - 1);
+ knod_and32(priv, meta, r64[2].lo, p64[0].lo, r64[2].lo);
+
+ /* r64[1] = bucket_gaddr */
+ knod_iset64(&p64[0], bucket_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ /* bucket_addr = bucket_gaddr + hash * sizeof(int) -> r64[2] */
+ knod_iset64(&p64[1], sizeof(int));
+ knod_emit(priv, meta, v_mad_u64_u32, r64[2], sr64[0].lo,
+ p64[1].lo, r64[2].lo, r64[1]);
+
+ /* Save bucket_addr to r64[15] */
+ knod_mov64(priv, meta, r64[15], r64[2]);
+
+ /* SREG3 = initial exec */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ======== Phase 2: Sequential per-lane processing ======== */
+
+ /* SREG5 = exec (all lanes to process, for BUCKET_LOOP) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG5_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ---- BUCKET_LOOP: process one unique bucket per iteration ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_BUCKET_LOOP],
+ meta->amdgpu_insns);
+
+ lock_offset =
+ (unsigned long)knod_map_obj_k->meta.hmeta.n_buckets *
+ sizeof(unsigned int);
+
+ knod_sset32(&s_bucket_lo,
+ KNOD_AMDGPU_TMP_SREG1_HI);
+ knod_vset32(&v_tmp, KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(&v_zero, 0);
+ knod_iset32(&v_one, 1);
+ knod_sset32(&s_exec_lo, AMDGCN_SREG_EXEC_LO);
+ knod_sset32(&s_exec_hi,
+ AMDGCN_SREG_EXEC_LO + 1);
+ knod_sset64(&sr64_carry,
+ KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* Pick first active lane's bucket addr */
+ knod_emit(priv, meta, v_readfirstlane_b32, KNOD_AMDGPU_TMP_SREG1_HI,
+ r64[15].lo.v);
+
+ /* vcc = lanes with same bucket */
+ knod_emit(priv, meta, v_cmp_eq_u32, s_bucket_lo, r64[15].lo);
+
+ /* SREG5 = remaining lanes; exec = same-bucket lanes */
+ knod_emit(priv, meta, s_and_saveexec_b64, KNOD_AMDGPU_TMP_SREG5_LO,
+ AMDGCN_SREG_VCC_LO);
+
+ /* SREG0 = same-bucket lanes */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* ---- Lock acquire ---- */
+ /* r64[10] = r64[15] + lock_offset */
+ knod_iset64(&p64[0], lock_offset);
+ knod_add64(priv, meta, r64[10], p64[0], r64[15]);
+
+ /* r64[11].lo = 1 (swap data) */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[11].lo, v_one);
+
+ /* First-lane isolation via mbcnt */
+ knod_emit(priv, meta, v_mbcnt_lo_u32_b32, v_tmp, s_exec_lo,
+ v_zero);
+ knod_emit(priv, meta, v_mbcnt_hi_u32_b32, v_tmp, s_exec_hi, v_tmp);
+ knod_emit(priv, meta, v_cmp_eq_u32, v_zero, v_tmp);
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* LOCK_RETRY: spin until lock acquired */
+ knod_bpf_set_label(meta, &labels[LABEL_LOCK_RETRY], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, global_atomic_swap, r64[11].hi, r64[10].lo,
+ r64[11].lo, 0, 1);
+ knod_wait_vmcnt(priv, meta);
+
+ meta->amdgpu_insn[meta->amdgpu_insns].size =
+ emit_gfx10_v_cmp_ne_u32(
+ &meta->amdgpu_insn[meta->amdgpu_insns].gfx10,
+ v_zero, r64[11].hi);
+ meta->amdgpu_insn[meta->amdgpu_insns].type = AMDGCN_INSN_TYPE_VOPC;
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ emit_s_cbranch_vccnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LOCK_RETRY], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* Lock acquired - restore same-bucket lanes */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+
+ /* ---- DELETE_LANE: process one lane at a time ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_DELETE_LANE],
+ meta->amdgpu_insns);
+
+ /* SREG4 = exec (remaining same-bucket lanes) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG4_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* Pick first active lane via mbcnt */
+ knod_emit(priv, meta, v_mbcnt_lo_u32_b32, v_tmp, s_exec_lo,
+ v_zero);
+ knod_emit(priv, meta, v_mbcnt_hi_u32_b32, v_tmp, s_exec_hi, v_tmp);
+ knod_emit(priv, meta, v_cmp_eq_u32, v_zero, v_tmp);
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* SREG2 = exec (single-lane mask) */
+ knod_emit(priv, meta, s_mov_b64, KNOD_AMDGPU_TMP_SREG2_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* r64[2] = r64[15] (bucket_addr for chain walk start) */
+ knod_mov64(priv, meta, r64[2], r64[15]);
+
+ /* ---- CHAIN_NEXT: walk chain ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_CHAIN_NEXT], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, global_load_dword, r64[0].lo, r64[2].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* Mask out DELETED bit */
+ knod_iset32(&p32, KNOD_BPF_HASH_NEXT_MASK);
+ knod_emit(priv, meta, v_and_b32_e32, r64[0].lo, p32, r64[0].lo);
+
+ /* End-of-chain check (VOPC literal workaround) */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[0].hi, p32);
+ knod_emit(priv, meta, v_cmp_eq_u32, r64[0].hi, r64[0].lo);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* elem_addr = elems + elem_id * elem_size -> r64[2] */
+ knod_iset64(&p64[0], elems_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_iset64(&p64[0], elem_size);
+ knod_mov32(priv, meta, r64[10].lo, p64[0].lo);
+ knod_emit(priv, meta, v_mad_u64_u32, r64[2], sr64_carry.lo,
+ r64[10].lo, r64[0].lo, r64[1]);
+
+ /* Load elem.next for DELETED check */
+ knod_emit(priv, meta, global_load_dword, r64[0].hi, r64[2].lo, 0);
+
+ /* Load key from map element -> KEY_IN_MAP */
+ key_in_map = KEY_IN_MAP_32;
+ len = knod_map_obj_k->key_size;
+ off = offsetof(struct knod_bpf_hash_elem_obj, kv);
+
+ while (len >= 16) {
+ knod_emit(priv, meta, global_load_dwordx4, r32[key_in_map],
+ r64[2].lo, off);
+ off += 16;
+ len -= 16;
+ key_in_map += 4;
+ }
+
+ if (len >= 8) {
+ knod_emit(priv, meta, global_load_dwordx2, r32[key_in_map],
+ r64[2].lo, off);
+ off += 8;
+ len -= 8;
+ key_in_map += 2;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, global_load_dword, r32[key_in_map],
+ r64[2].lo, off);
+ off += 4;
+ len -= 4;
+ key_in_map += 1;
+ }
+
+ if (len) {
+ knod_emit(priv, meta, global_load_dword, r32[key_in_map],
+ r64[2].lo, off);
+ }
+
+ knod_wait_vmcnt(priv, meta);
+
+ /* Key comparison -> SREG1 */
+ key_in_map = KEY_IN_MAP_32;
+ key_in_pkt = KEY_IN_PKT_32;
+ len = knod_map_obj_k->key_size;
+ first_cmp = true;
+
+ while (len >= 8) {
+ knod_emit(priv, meta, v_cmp_eq_u64, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ key_in_map += 2;
+ key_in_pkt += 2;
+ len -= 8;
+ }
+
+ if (len >= 4) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+
+ key_in_map += 1;
+ key_in_pkt += 1;
+ len -= 4;
+ }
+
+ if (len) {
+ knod_emit(priv, meta, v_cmp_eq_u32, r32[key_in_map],
+ r32[key_in_pkt]);
+
+ if (first_cmp) {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ first_cmp = false;
+ } else {
+ emit_s_and_b64(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ }
+
+ /* DELETED check: SREG1 &= not_deleted */
+ knod_iset32(&p32,
+ KNOD_BPF_HASH_NEXT_DELETED);
+ knod_emit(priv, meta, v_and_b32_e32, r64[0].hi, p32, r64[0].hi);
+ knod_iset32(&p32, 0);
+ knod_emit(priv, meta, v_cmp_eq_u32, p32, r64[0].hi);
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG1_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO, AMDGCN_SREG_VCC_LO);
+
+ /* Narrow exec to matched lane */
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* ---- Match path: set DELETED + append to gc_list ---- */
+
+ /* atomic_or(elem.next, DELETED_BIT) - mark deleted */
+ knod_lset32(&v_del,
+ KNOD_BPF_HASH_NEXT_DELETED);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[11].lo, v_del);
+ knod_emit(priv, meta, global_atomic_or, r64[11].hi, r64[2].lo,
+ r64[11].lo, 0, 0);
+
+ /* atomic_add(gc_count, 1) -> old_count in r64[11].lo */
+ knod_emit(priv, meta, v_mov_b32_e32, r64[11].lo, v_one);
+
+ knod_iset64(&p64[0], gc_count_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ knod_emit(priv, meta, global_atomic_add, r64[11].lo, r64[1].lo,
+ r64[11].lo, 0, 1);
+ knod_wait_vmcnt(priv, meta);
+
+ /* Store elem_id to gc_list[old_count] */
+ knod_iset64(&p64[0], gc_list_gaddr);
+ knod_mov64(priv, meta, r64[10], p64[0]);
+
+ knod_iset64(&p64[0], sizeof(unsigned int));
+ knod_emit(priv, meta, v_mad_u64_u32, r64[1], sr64_carry.lo,
+ p64[0].lo, r64[11].lo, r64[10]);
+
+ knod_emit(priv, meta, global_store_dword, r64[0].lo, r64[1].lo, 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* If matched, done with this lane */
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* No match: restore lane, continue chain walk */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG2_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_CHAIN_NEXT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ---- LANE_DONE: remove this lane, next lane ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_LANE_DONE], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG4_LO, KNOD_AMDGPU_TMP_SREG2_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_DELETE_LANE], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ---- UNLOCK: release lock + next bucket ---- */
+ knod_bpf_set_label(meta, &labels[LABEL_UNLOCK], meta->amdgpu_insns);
+
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+
+ /* Recompute lock_addr (r64[10] clobbered) */
+ knod_iset64(&p64[0], lock_offset);
+ knod_add64(priv, meta, r64[10], p64[0], r64[15]);
+
+ knod_emit(priv, meta, v_mov_b32_e32, r64[1].lo, v_zero);
+ knod_emit(priv, meta, global_store_dword, r64[1].lo, r64[10].lo,
+ 0);
+ knod_wait_vmcnt(priv, meta);
+
+ /* Next bucket */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG5_LO, KNOD_AMDGPU_TMP_SREG0_LO);
+
+ emit_s_cbranch_execnz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_BUCKET_LOOP], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* ======== Phase 3: Restore ======== */
+
+ /* exec = SREG3 (restore all original lanes) */
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+
+ /* bpf_reg64[0] = 0 (return value) */
+ knod_iset64(&p64[0], 0);
+ knod_mov64(priv, meta, bpf_reg64[0], p64[0]);
+
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+
+ return;
+#undef LABEL_BUCKET_LOOP
+#undef LABEL_LOCK_RETRY
+#undef LABEL_DELETE_LANE
+#undef LABEL_CHAIN_NEXT
+#undef LABEL_LANE_DONE
+#undef LABEL_UNLOCK
+}
+
+static void knod_bpf_map_delete_array(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ int map_id)
+{
+ struct knod_bpf_map_obj *knod_map_obj_k, *knod_map_obj_g;
+ struct amdgcn_branch_fixup fixups[4] = {0,};
+ u32 key_stack_off = meta->kreg.stack_off;
+ struct amdgcn_label labels[10] = {0,};
+ int idx, val_off, val_len;
+ struct amdgcn_param32 v_zero;
+ unsigned long bucket_gaddr;
+ int fixup_idx = 0;
+
+ knod_map_obj_k =
+ (struct knod_bpf_map_obj *)knod_bpf_map_kaddr(priv, map_id);
+ knod_map_obj_g =
+ (struct knod_bpf_map_obj *)knod_bpf_map_gaddr(priv, map_id);
+ bucket_gaddr = (unsigned long)knod_map_obj_g +
+ offsetof(struct knod_bpf_map_obj, bucket);
+
+ if (!knod_map_obj_g || !knod_map_obj_k)
+ WARN_ON_ONCE(1);
+
+ /* load key from stack -> r64[2] */
+ knod_bpf_load_size(priv, meta,
+ &r64[2],
+ &stack[0],
+ sizeof(unsigned int),
+ 512 + key_stack_off);
+
+ /* r64[1] = bucket_gaddr */
+ knod_iset64(&p64[0], bucket_gaddr);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+
+ /* clear r64[2].hi (key is 32-bit) */
+ knod_iset64(&p64[1], 0);
+ knod_mov32(priv, meta, r64[2].hi, p64[1].lo);
+
+ /* bpf_reg64[0] = 0 (return value) */
+ knod_mov64(priv, meta, bpf_reg64[0], p64[1]);
+
+ /* bounds check: if (key >= max_entries) -> skip */
+ knod_iset64(&p64[1], knod_map_obj_k->max_entries);
+ knod_mov64(priv, meta, r64[3], p64[1]);
+ knod_emit(priv, meta, v_cmp_ge_u64, r64[2], r64[3]);
+
+ /* structurized CFG: save OOB lanes, narrow exec */
+ knod_emit(priv, meta, s_and_b64, KNOD_AMDGPU_TMP_SREG3_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+ emit_s_cbranch_execz(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ 0);
+ knod_bpf_set_fixup(meta, &fixups[fixup_idx],
+ &labels[LABEL_OUT], meta->amdgpu_insns);
+ debug_insn(priv->isa_version, &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ fixup_idx++;
+
+ /* dest = bucket_gaddr + key * value_size -> r64[0] */
+ knod_iset64(&p64[1], knod_map_obj_k->value_size);
+ knod_emit(priv, meta, v_mad_u64_u32, r64[0], sr64[0].lo,
+ p64[1].lo, r64[2].lo, r64[1]);
+
+ /* Zero out value at dest */
+ knod_iset32(&v_zero, 0);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[3].lo, v_zero);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[3].hi, v_zero);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[4].lo, v_zero);
+ knod_emit(priv, meta, v_mov_b32_e32, r64[4].hi, v_zero);
+
+ val_off = 0;
+ val_len = knod_map_obj_k->value_size;
+
+ while (val_len >= 16) {
+ knod_emit(priv, meta, global_store_dwordx4, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 16;
+ val_len -= 16;
+ }
+
+ if (val_len >= 8) {
+ knod_emit(priv, meta, global_store_dwordx2, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 8;
+ val_len -= 8;
+ }
+
+ if (val_len >= 4) {
+ knod_emit(priv, meta, global_store_dword, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 4;
+ val_len -= 4;
+ }
+
+ if (val_len >= 2) {
+ knod_emit(priv, meta, global_store_short, r64[3].lo,
+ r64[0].lo, val_off);
+ val_off += 2;
+ val_len -= 2;
+ }
+
+ if (val_len >= 1) {
+ knod_emit(priv, meta, global_store_byte, r64[3].lo,
+ r64[0].lo, val_off);
+ }
+
+ knod_wait_vmcnt(priv, meta);
+
+ /* structurized CFG: restore OOB lanes */
+ knod_bpf_set_label(meta, &labels[LABEL_OUT], meta->amdgpu_insns);
+ knod_emit(priv, meta, s_or_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, KNOD_AMDGPU_TMP_SREG3_LO);
+
+ for (idx = 0; idx < fixup_idx; idx++)
+ knod_bpf_fixup_branch(priv, &fixups[idx]);
+}
+
+static void knod_bpf_store_cache_size(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct amdgcn_param64 *src,
+ /* packet or stack */
+ struct amdgcn_param32 *cache,
+ int size, int off)
+{
+ struct amdgcn_param32 p32[2];
+
+ knod_jit_dbg(" %d: off = %d off_4 = %d size = %d\n", meta->bpf_insn_idx,
+ off, off%4, size);
+ WARN_ON(knod_param_is_literal(src->lo) ||
+ knod_param_is_literal(src->hi));
+ switch (size) {
+ case sizeof(unsigned long):
+ if ((off % 4) == 0) {
+ knod_mov32(priv, meta,
+ cache[off / 4],
+ src->lo);
+ knod_mov32(priv, meta,
+ cache[(off / 4) + 1],
+ src->hi);
+ } else if ((off % 4) == 1) {
+ WARN_ON_ONCE(1);
+ } else if ((off % 4) == 2) {
+ WARN_ON_ONCE(1);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case sizeof(unsigned int):
+ if ((off % 4) == 0) {
+ knod_mov32(priv, meta,
+ cache[off / 4],
+ src->lo);
+ } else if ((off % 4) == 1) {
+ knod_iset64(&p64[0], 8);
+ knod_lshlrev64(priv, meta, r64[0], p64[0], *src);
+
+ knod_iset32(&p32[0], 0xffffff00);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4],
+ r32[2], r64[0].lo, cache[off / 4]);
+ knod_iset32(&p32[0], 0x000000ff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[(off / 4) + 1], r32[2],
+ r64[0].hi, cache[(off / 4) + 1]);
+ } else if ((off % 4) == 2) {
+ knod_iset64(&p64[0], 16);
+ knod_lshlrev64(priv, meta, r64[0], p64[0], *src);
+
+ knod_iset32(&p32[0], 0xffff0000);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ r64[0].lo, cache[off / 4]);
+ knod_iset32(&p32[0], 0x0000ffff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[(off / 4) + 1], r32[2],
+ r64[0].hi, cache[(off / 4) + 1]);
+ } else {
+ knod_iset64(&p64[0], 24);
+ knod_lshlrev64(priv, meta, r64[0], p64[0], *src);
+
+ knod_iset32(&p32[0], 0xffff0000);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ r64[0].lo, cache[off / 4]);
+ knod_iset32(&p32[0], 0x00ffffff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[(off / 4) + 1], r32[2],
+ r64[0].hi, cache[(off / 4) + 1]);
+ }
+ break;
+ case sizeof(unsigned short):
+ if ((off % 4) == 0) {
+ knod_iset32(&p32[0], 0x0000ffff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ src->lo, cache[off / 4]);
+ } else if ((off % 4) == 1) {
+ knod_iset32(&p32[0], 8);
+ knod_lshlrev32(priv, meta, r32[0], p32[0],
+ src->lo);
+ knod_iset32(&p32[0], 0x00ffff00);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ r32[0], cache[off / 4]);
+ } else if ((off % 4) == 2) {
+ knod_iset32(&p32[0], 16);
+ knod_lshlrev32(priv, meta, r32[0], p32[0],
+ src->lo);
+ knod_iset32(&p32[0], 0xffff0000);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ r32[0], cache[off / 4]);
+ } else {
+ knod_iset64(&p64[0], 24);
+ knod_lshlrev64(priv, meta, r64[0], p64[0], *src);
+
+ knod_iset32(&p32[0], 0xff000000);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ r64[0].lo, cache[off / 4]);
+ knod_iset32(&p32[0], 0x000000ff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[(off / 4) + 1], r32[2],
+ r64[0].hi, cache[(off / 4) + 1]);
+ }
+ break;
+ case sizeof(unsigned char):
+ if ((off % 4) == 0) {
+ knod_iset32(&p32[0], 0x000000ff);
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2],
+ src->lo, cache[off / 4]);
+ return;
+ } else if ((off % 4) == 1) {
+ knod_iset32(&p32[0], 8);
+ knod_lshlrev32(priv, meta, r32[0], p32[0],
+ src->lo);
+
+ knod_iset32(&p32[0], 0x0000ff00);
+ } else if ((off % 4) == 2) {
+ knod_iset32(&p32[0], 16);
+ knod_lshlrev32(priv, meta, r32[0], p32[0],
+ src->lo);
+
+ knod_iset32(&p32[0], 0x00ff0000);
+ } else {
+ knod_iset32(&p32[0], 24);
+ knod_lshlrev32(priv, meta, r32[0], p32[0],
+ src->lo);
+ knod_iset32(&p32[0], 0xff000000);
+ }
+
+ knod_mov32(priv, meta, r32[2], p32[0]);
+ knod_bfi32(priv, meta, cache[off / 4], r32[2], r32[0],
+ cache[off / 4]);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ }
+}
+
+static bool knod_meta_is_exit(const struct knod_insn_meta *meta);
+static bool knod_bpf_is_retval_move_to_r0(const struct knod_insn_meta *meta);
+
+/*
+ * knod_bpf_emit_branch_tail - Emit EXEC mask manipulation after v_cmp for
+ * structurized per-lane branching. Replaces the old s_cbranch_vccnz/vccz.
+ *
+ * For FORWARD_SKIP:
+ * Save jumping lanes -> narrow EXEC -> s_cbranch_execz
+ * (skip if no active lanes)
+ *
+ * For DIRECT_EXIT:
+ * Compute exit lanes -> update done_mask -> remove from EXEC (no branch)
+ *
+ * Emits the required EXEC mask manipulation in-place.
+ */
+static void knod_bpf_emit_direct_exit_retval(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *emit_meta,
+ struct knod_insn_meta *target)
+{
+ struct amdgcn_param64 dst, src;
+ s64 imm;
+
+ if (!target || knod_meta_is_exit(target))
+ return;
+
+ if (WARN_ON_ONCE(!knod_bpf_is_retval_move_to_r0(target)))
+ return;
+
+ knod_vset64(&dst, KNOD_AMDGPU_VREG0_LO);
+
+ switch (target->insn.code) {
+ case BPF_ALU | BPF_MOV | BPF_X:
+ case BPF_ALU64 | BPF_MOV | BPF_X:
+ knod_vset64(&src, target->insn.src_reg * 2);
+ knod_mov64(priv, emit_meta, dst, src);
+ break;
+ case BPF_ALU | BPF_MOV | BPF_K:
+ imm = (u32)target->insn.imm;
+ knod_iset64(&src, imm);
+ knod_mov64(priv, emit_meta, dst, src);
+ break;
+ case BPF_ALU64 | BPF_MOV | BPF_K:
+ imm = (s64)(s32)target->insn.imm;
+ knod_iset64(&src, imm);
+ knod_mov64(priv, emit_meta, dst, src);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+}
+
+static void knod_bpf_emit_branch_tail(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct knod_prog *knod_prog,
+ short off)
+{
+ switch (meta->branch_type) {
+ case KNOD_BR_FORWARD_SKIP:
+ if (meta->jump_neg_op) {
+ /* JNE: VCC=0 -> jump, VCC=1 -> fall-through.
+ * Save jump lanes (VCC=0): s[n] = exec & ~vcc
+ */
+ knod_emit(priv, meta, s_andn2_b64,
+ meta->exec_save_sreg,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+
+ /* Keep fall-through (VCC=1): exec = exec & vcc */
+ knod_emit(priv, meta, s_and_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ } else {
+ /* Normal: VCC=1 -> jump, VCC=0 -> fall-through.
+ * Save jump lanes (VCC=1): s[n] = exec & vcc
+ */
+ knod_emit(priv, meta, s_and_b64,
+ meta->exec_save_sreg,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+
+ /* Keep fall-through (VCC=0): exec = exec & ~vcc */
+ knod_emit(priv, meta, s_andn2_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+
+ /*
+ * No GPU branch. After the RPO reorder, branch scopes
+ * interleave, so the jumping lanes must flow through every
+ * following block under the EXEC mask and rejoin at their merge
+ * point. An s_cbranch_execz skipping ahead to the merge would
+ * jump over other scopes' merge points and strand their saved
+ * lanes (EXEC never restored -> act=0).
+ */
+ break;
+
+ case KNOD_BR_DIRECT_EXIT:
+ if (meta->jump_neg_op) {
+ /* JNE: VCC=0 -> exit. exit_lanes = exec & ~vcc */
+ knod_emit(priv, meta, s_andn2_b64,
+ KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ } else {
+ /* Normal: VCC=1 -> exit. exit_lanes = exec & vcc */
+ knod_emit(priv, meta, s_and_b64,
+ KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_VCC_LO);
+ }
+
+ /* Keep the lanes that did not take the exit path. */
+ knod_emit(priv, meta, s_andn2_b64,
+ KNOD_AMDGPU_TMP_SREG1_LO,
+ AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+
+ /* Replay a shared "r0 = action; exit" target under the
+ * exiting lanes before marking them done. Otherwise a direct
+ * branch to the common exit can publish stale r0 scratch state.
+ */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_bpf_emit_direct_exit_retval(priv, meta, meta->merge_point);
+
+ /* done_mask |= exit_lanes */
+ knod_emit(priv, meta, s_or_b64,
+ knod_prog->done_mask_sreg,
+ knod_prog->done_mask_sreg,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* Continue with the non-exit lanes. */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* No branch - fall through with reduced EXEC.
+ * No fixup needed.
+ */
+ meta->jmp_dst = NULL;
+ break;
+
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+}
+
+/*
+ * --- Basic-block CFG analysis (foundation for block reordering) ---
+ *
+ * The emitter is a linear SIMT machine: instructions run in list order under
+ * an EXEC mask. A *forward* jump is realized by masking off the jumping
+ * lanes and restoring them at the merge point. A *backward* jump has no such
+ * realization unless it is a loop (real GPU branch + EXEC convergence, not yet
+ * implemented).
+ *
+ * LLVM tail-sharing and block placement routinely emit jumps that are
+ * backward in BPF byte order but are NOT loops - e.g. a UDP bounds check that
+ * jumps back to a shared XDP_PASS tail. Classifying those as "exit" (the
+ * jmp_off < 0 heuristic in knod_bpf_analyze_cfg) silently miscompiles them:
+ * the jumping lanes exit carrying whatever R0 happened to hold instead of
+ * flowing to the real target.
+ *
+ * The fix is to classify by control-flow, not byte order:
+ * 1. partition the instruction stream into basic blocks,
+ * 2. build the control-flow graph (successor edges),
+ * 3. DFS for a reverse-postorder (RPO) and detect back-edges,
+ * 4. no back-edges (a DAG) -> reorder blocks into RPO so every edge points
+ * forward, then classify by linear position,
+ * 5. a real loop is present -> bail (-EOPNOTSUPP) until loop emission lands.
+ *
+ * Loop emission (step 5) is not implemented yet, so programs containing a
+ * loop are rejected with -EOPNOTSUPP.
+ */
+struct knod_bb {
+ struct knod_insn_meta *leader; /* first instruction of the block */
+ struct knod_insn_meta *last; /* last instruction of the block */
+ /* successors: [0] not-taken, [1] taken */
+ struct knod_bb *succ[2];
+ int n_succ;
+ /* reverse-postorder rank, -1 if unreachable */
+ int rpo;
+ /* DFS color: 0 white, 1 gray, 2 black */
+ int dfs;
+ bool loop_header; /* target of a back-edge */
+ /* scratch: member of the loop being walked */
+ bool in_loop;
+ /* immediate dominator (self for entry) */
+ struct knod_bb *idom;
+};
+
+static bool knod_meta_is_exit(const struct knod_insn_meta *meta)
+{
+ u8 code = meta->insn.code;
+
+ return code == (BPF_JMP | BPF_EXIT) || code == (BPF_JMP32 | BPF_EXIT);
+}
+
+static struct knod_insn_meta *
+knod_bpf_next_meta(struct knod_prog *knod_prog, struct knod_insn_meta *meta)
+{
+ if (!meta || list_is_last(&meta->l, &knod_prog->insns))
+ return NULL;
+
+ return list_next_entry(meta, l);
+}
+
+static bool knod_bpf_is_retval_move_to_r0(const struct knod_insn_meta *meta)
+{
+ u8 code;
+
+ if (!meta || meta->insn.dst_reg != BPF_REG_0)
+ return false;
+
+ code = meta->insn.code;
+ return code == (BPF_ALU | BPF_MOV | BPF_X) ||
+ code == (BPF_ALU64 | BPF_MOV | BPF_X) ||
+ code == (BPF_ALU | BPF_MOV | BPF_K) ||
+ code == (BPF_ALU64 | BPF_MOV | BPF_K);
+}
+
+static bool knod_bpf_is_direct_exit_target(struct knod_prog *knod_prog,
+ struct knod_insn_meta *target)
+{
+ if (knod_meta_is_exit(target))
+ return true;
+
+ if (!knod_bpf_is_retval_move_to_r0(target))
+ return false;
+
+ return knod_meta_is_exit(knod_bpf_next_meta(knod_prog, target));
+}
+
+static bool knod_meta_is_ja(const struct knod_insn_meta *meta)
+{
+ u8 code = meta->insn.code;
+
+ return code == (BPF_JMP | BPF_JA | BPF_K) ||
+ code == (BPF_JMP32 | BPF_JA | BPF_K);
+}
+
+/* A block ends after a terminator; the next instruction starts a new block. */
+static bool knod_meta_is_terminator(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_cond_jump(meta) || knod_meta_is_ja(meta) ||
+ knod_meta_is_exit(meta);
+}
+
+/* Target instruction index of a conditional jump or BPF_JA. */
+static short knod_meta_jump_target_idx(const struct knod_insn_meta *meta)
+{
+ if (meta->insn.code == (BPF_JMP32 | BPF_JA | BPF_K))
+ return meta->bpf_insn_idx + meta->insn.imm + 1;
+ return meta->bpf_insn_idx + meta->insn.off + 1;
+}
+
+static struct knod_bb *knod_bb_of_leader(struct knod_bb *bbs, int n_bbs,
+ const struct knod_insn_meta *meta)
+{
+ int i;
+
+ for (i = 0; i < n_bbs; i++)
+ if (bbs[i].leader == meta)
+ return &bbs[i];
+ return NULL;
+}
+
+/* Resolve the block a conditional jump / BPF_JA at @jmp transfers to. */
+static struct knod_bb *knod_bb_jump_target(struct knod_prog *knod_prog,
+ struct knod_bb *bbs, int n_bbs,
+ const struct knod_insn_meta *jmp)
+{
+ struct knod_insn_meta *tgt;
+
+ tgt = knod_bpf_lookup_meta(knod_prog, knod_meta_jump_target_idx(jmp));
+ return tgt ? knod_bb_of_leader(bbs, n_bbs, tgt) : NULL;
+}
+
+/*
+ * Partition knod_prog->insns into basic blocks. A leader is the first
+ * instruction, any jump target, or the instruction after a terminator.
+ * Returns the block count or a negative errno; @bbs holds >= n_insns blocks.
+ */
+static int knod_bpf_build_bbs(struct knod_prog *knod_prog, struct knod_bb *bbs)
+{
+ struct knod_insn_meta *meta, *tgt;
+ struct knod_bb *cur = NULL;
+ int n_bbs = 0;
+ short tgt_idx;
+
+ /* Pass A: mark every jump target as a leader. */
+ list_for_each_entry(meta, &knod_prog->insns, l)
+ meta->flags &= ~FLAG_INSN_IS_JUMP_DST;
+
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ if (!is_mbpf_cond_jump(meta) && !knod_meta_is_ja(meta))
+ continue;
+ tgt_idx = knod_meta_jump_target_idx(meta);
+ tgt = knod_bpf_lookup_meta(knod_prog, tgt_idx);
+ if (!tgt) {
+ pr_warn("knod_cfg: bpf#%d jump target %d unresolved\n",
+ meta->bpf_insn_idx, tgt_idx);
+ return -EINVAL;
+ }
+ tgt->flags |= FLAG_INSN_IS_JUMP_DST;
+ }
+
+ /* Pass B: cut the list into blocks. */
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ if (!cur || (meta->flags & FLAG_INSN_IS_JUMP_DST)) {
+ cur = &bbs[n_bbs++];
+ cur->leader = meta;
+ cur->n_succ = 0;
+ }
+ cur->last = meta;
+
+ if (knod_meta_is_terminator(meta))
+ /* next instruction starts a new block */
+ cur = NULL;
+ }
+
+ return n_bbs;
+}
+
+/* Build successor edges for every block from its terminator. */
+static int knod_bpf_build_edges(struct knod_prog *knod_prog,
+ struct knod_bb *bbs, int n_bbs)
+{
+ struct knod_bb *bb, *fall, *tgt_bb;
+ struct knod_insn_meta *last;
+ int i;
+
+ for (i = 0; i < n_bbs; i++) {
+ bb = &bbs[i];
+ last = bb->last;
+ bb->n_succ = 0;
+
+ if (knod_meta_is_exit(last))
+ continue; /* no successors */
+
+ /* Successor in list order: block led by the next
+ * instruction.
+ */
+ fall = NULL;
+ if (!list_is_last(&last->l, &knod_prog->insns))
+ fall = knod_bb_of_leader(bbs, n_bbs,
+ list_next_entry(last, l));
+
+ if (is_mbpf_cond_jump(last)) {
+ tgt_bb = knod_bb_jump_target(knod_prog, bbs, n_bbs,
+ last);
+ if (!fall || !tgt_bb)
+ return -EINVAL;
+ bb->succ[bb->n_succ++] = fall; /* not taken */
+ bb->succ[bb->n_succ++] = tgt_bb; /* taken */
+ } else if (knod_meta_is_ja(last)) {
+ tgt_bb = knod_bb_jump_target(knod_prog, bbs, n_bbs,
+ last);
+ if (!tgt_bb)
+ return -EINVAL;
+ bb->succ[bb->n_succ++] = tgt_bb;
+ } else {
+ if (!fall) /* fell off the end */
+ return -EINVAL;
+ bb->succ[bb->n_succ++] = fall;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Iterative DFS from the entry block. Computes a reverse-postorder rank for
+ * every reachable block and flags back-edge targets as loop headers. Returns
+ * the number of back-edges in *n_back, or a negative errno.
+ */
+static int knod_bpf_compute_rpo(struct knod_bb *bbs, int n_bbs,
+ struct knod_bb *entry, int *n_back)
+{
+ struct knod_bb **stack;
+ int *cursor;
+ int top = 0, post = 0, nb = 0, i;
+
+ for (i = 0; i < n_bbs; i++) {
+ bbs[i].dfs = 0;
+ bbs[i].rpo = -1;
+ bbs[i].loop_header = false;
+ }
+
+ stack = kcalloc(n_bbs, sizeof(*stack), GFP_KERNEL);
+ cursor = kcalloc(n_bbs, sizeof(*cursor), GFP_KERNEL);
+ if (!stack || !cursor) {
+ kfree(stack);
+ kfree(cursor);
+ return -ENOMEM;
+ }
+
+ entry->dfs = 1;
+ stack[top] = entry;
+ cursor[top] = 0;
+ top++;
+
+ while (top > 0) {
+ struct knod_bb *bb = stack[top - 1];
+
+ if (cursor[top - 1] < bb->n_succ) {
+ struct knod_bb *s = bb->succ[cursor[top - 1]++];
+
+ if (s->dfs == 0) { /* tree edge */
+ s->dfs = 1;
+ stack[top] = s;
+ cursor[top] = 0;
+ top++;
+ } else if (s->dfs == 1) { /* gray -> back-edge */
+ s->loop_header = true;
+ nb++;
+ }
+ /* s->dfs == 2 -> forward/cross edge, nothing to do */
+ } else {
+ /* finished: postorder */
+ bb->dfs = 2;
+ bb->rpo = post++;
+ top--;
+ }
+ }
+
+ /* postorder -> reverse-postorder rank */
+ for (i = 0; i < n_bbs; i++)
+ if (bbs[i].rpo >= 0)
+ bbs[i].rpo = post - 1 - bbs[i].rpo;
+
+ kfree(stack);
+ kfree(cursor);
+ *n_back = nb;
+ return 0;
+}
+
+/*
+ * Cooper-Harvey-Kennedy dominator intersect: walk the two fingers up the idom
+ * chain (toward the entry, which has the lowest RPO) until they meet.
+ */
+static struct knod_bb *knod_dom_intersect(struct knod_bb *a, struct knod_bb *b)
+{
+ while (a != b) {
+ while (a->rpo > b->rpo)
+ a = a->idom;
+ while (b->rpo > a->rpo)
+ b = b->idom;
+ }
+ return a;
+}
+
+/*
+ * Compute the immediate dominator of every reachable block (Cooper, Harvey,
+ * Kennedy, "A Simple, Fast Dominance Algorithm"). Iterates over RPO to a
+ * fixpoint; bb->idom is the block's immediate dominator, the entry dominating
+ * itself. Requires bb->rpo from knod_bpf_compute_rpo.
+ */
+static int knod_bpf_compute_dom(struct knod_bb *bbs, int n_bbs,
+ struct knod_bb *entry)
+{
+ struct knod_bb **order;
+ int i, k, n_order = 0;
+ bool changed;
+
+ order = kcalloc(n_bbs, sizeof(*order), GFP_KERNEL);
+ if (!order)
+ return -ENOMEM;
+
+ for (i = 0; i < n_bbs; i++) {
+ bbs[i].idom = NULL;
+ if (bbs[i].rpo >= 0) {
+ order[bbs[i].rpo] = &bbs[i];
+ n_order++;
+ }
+ }
+ entry->idom = entry;
+
+ do {
+ changed = false;
+
+ /* process every reachable block but the entry, in RPO order */
+ for (k = 1; k < n_order; k++) {
+ struct knod_bb *n = order[k];
+ struct knod_bb *new_idom = NULL;
+ int b, s;
+
+ /* intersect over already-processed predecessors */
+ for (b = 0; b < n_bbs; b++) {
+ for (s = 0; s < bbs[b].n_succ; s++) {
+ if (bbs[b].succ[s] != n || !bbs[b].idom)
+ continue;
+ new_idom = new_idom ?
+ knod_dom_intersect(&bbs[b],
+ new_idom) :
+ &bbs[b];
+ }
+ }
+
+ if (new_idom && n->idom != new_idom) {
+ n->idom = new_idom;
+ changed = true;
+ }
+ }
+ } while (changed);
+
+ kfree(order);
+ return 0;
+}
+
+/* Does block @a dominate block @b? Walk @b up the idom chain to the entry. */
+static bool knod_dom_dominates(struct knod_bb *a, struct knod_bb *b)
+{
+ for (;;) {
+ if (b == a)
+ return true;
+ if (b->idom == b) /* reached the entry */
+ return false;
+ b = b->idom;
+ }
+}
+
+/*
+ * Mark the natural loop body of back-edge @latch->@hdr in bb->in_loop: the
+ * header plus every block that reaches the latch without passing through the
+ * header, found by walking predecessors back from the latch. @stack is
+ * caller-provided scratch of at least @n_bbs entries.
+ */
+static void knod_loop_mark_body(struct knod_bb *bbs, int n_bbs,
+ struct knod_bb *latch, struct knod_bb *hdr,
+ struct knod_bb **stack)
+{
+ int b, sp, k, top = 0;
+
+ for (k = 0; k < n_bbs; k++)
+ bbs[k].in_loop = false;
+
+ hdr->in_loop = true;
+ if (latch != hdr) {
+ latch->in_loop = true;
+ stack[top++] = latch;
+ }
+
+ while (top > 0) {
+ struct knod_bb *d = stack[--top];
+
+ for (b = 0; b < n_bbs; b++) {
+ if (bbs[b].in_loop)
+ continue;
+ for (sp = 0; sp < bbs[b].n_succ; sp++) {
+ if (bbs[b].succ[sp] != d)
+ continue;
+ bbs[b].in_loop = true;
+ stack[top++] = &bbs[b];
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Detect natural loops from the dominator tree and report their structure.
+ *
+ * A back-edge is an edge u->v whose target v dominates its source u - v is
+ * the loop header, u the latch. Its natural loop body is the header plus the
+ * blocks that reach the latch without passing through the header; an exit edge
+ * leaves a body block for a non-body block.
+ *
+ * Loops are still rejected by the reorder (-EOPNOTSUPP); this only reports what
+ * was found (to dmesg, since a rejected program never attaches so /bpf/cfg is
+ * unavailable) so the detection can be verified before emission is built.
+ */
+static int knod_bpf_detect_loops(struct knod_bb *bbs, int n_bbs)
+{
+ struct knod_bb **stack;
+ int u, s, k, n_be = 0;
+
+ stack = kcalloc(n_bbs, sizeof(*stack), GFP_KERNEL);
+ if (!stack)
+ return -ENOMEM;
+
+ for (u = 0; u < n_bbs; u++) {
+ for (s = 0; s < bbs[u].n_succ; s++) {
+ struct knod_bb *hdr = bbs[u].succ[s];
+ int body = 0, exits = 0, sp;
+
+ if (!knod_dom_dominates(hdr, &bbs[u]))
+ continue; /* not a back-edge */
+ n_be++;
+
+ knod_loop_mark_body(bbs, n_bbs, &bbs[u], hdr, stack);
+
+ for (k = 0; k < n_bbs; k++) {
+ if (!bbs[k].in_loop)
+ continue;
+ body++;
+ for (sp = 0; sp < bbs[k].n_succ; sp++)
+ if (!bbs[k].succ[sp]->in_loop)
+ exits++;
+ }
+
+ pr_info("knod_loop: back-edge bpf#%d -> bpf#%d (latch->header) body=%d exits=%d\n",
+ bbs[u].leader->bpf_insn_idx,
+ hdr->leader->bpf_insn_idx, body, exits);
+ }
+ }
+
+ kfree(stack);
+
+ if (n_be)
+ pr_info("knod_loop: %d back-edge(s) - %s\n", n_be,
+ n_be == 1 ? "single loop (simple-shape candidate)" :
+ "nested/multiple loops (complex)");
+ return 0;
+}
+
+/*
+ * Block that lanes fall into in list order when the terminator is not taken:
+ * the not-taken successor of a conditional jump, or the sole successor of a
+ * block that ended only because the next instruction was a leader. BPF_JA and
+ * EXIT have no such successor (control leaves explicitly).
+ */
+static struct knod_bb *knod_bb_fall_succ(struct knod_bb *bb)
+{
+ if (knod_meta_is_exit(bb->last) || knod_meta_is_ja(bb->last))
+ return NULL;
+ return bb->n_succ ? bb->succ[0] : NULL;
+}
+
+/*
+ * Reorder the instruction list into reverse-postorder so every control-flow
+ * edge points forward, and splice in a synthetic BPF_JA wherever a block's
+ * not-taken successor no longer follows it in list order. After this the
+ * emitter's forward-only machinery (FORWARD_SKIP / FORWARD_GOTO) handles the
+ * whole program - including the backward-in-byte-order, non-loop jumps that
+ * the old jmp_off < 0 heuristic miscompiled.
+ *
+ * Loops (back-edges) are rejected with -EOPNOTSUPP until loop emission lands.
+ */
+static int knod_bpf_reorder_rpo(struct knod_prog *knod_prog,
+ struct knod_bb *bbs, int n_bbs, int n_back)
+{
+ struct knod_insn_meta *m, *nx, *sj;
+ struct knod_bb **order;
+ int n_order = 0, r, i, k, idx = 0;
+ LIST_HEAD(new_list);
+
+ if (n_back) {
+ pr_warn("knod_cfg: %d loop back-edge(s) - block reorder cannot lower loops yet (-EOPNOTSUPP)\n",
+ n_back);
+ return -EOPNOTSUPP;
+ }
+
+ order = kcalloc(n_bbs, sizeof(*order), GFP_KERNEL);
+ if (!order)
+ return -ENOMEM;
+
+ /* Reachable blocks in RPO, then any unreachable ones so no instruction
+ * is dropped from the list.
+ */
+ for (r = 0; r < n_bbs; r++)
+ for (i = 0; i < n_bbs; i++)
+ if (bbs[i].rpo == r) {
+ order[n_order++] = &bbs[i];
+ break;
+ }
+ for (i = 0; i < n_bbs; i++)
+ if (bbs[i].rpo < 0)
+ order[n_order++] = &bbs[i];
+
+ for (k = 0; k < n_order; k++) {
+ struct knod_bb *bb = order[k];
+ struct knod_bb *next = (k + 1 < n_order) ? order[k + 1] : NULL;
+ struct knod_bb *fall;
+
+ m = bb->leader;
+ while (true) {
+ nx = (m == bb->last) ? NULL : knod_meta_next(m);
+ list_move_tail(&m->l, &new_list);
+ if (m == bb->last)
+ break;
+ m = nx;
+ }
+
+ fall = knod_bb_fall_succ(bb);
+ if (!fall || (next && next->leader == fall->leader))
+ continue;
+
+ /* Not-taken successor no longer adjacent: route it
+ * explicitly.
+ */
+ sj = kzalloc_obj(*sj, GFP_KERNEL);
+ if (!sj) {
+ list_splice(&new_list, &knod_prog->insns);
+ kfree(order);
+ return -ENOMEM;
+ }
+ sj->insn.code = BPF_JMP | BPF_JA | BPF_K;
+ /* synthetic, never a jump target */
+ sj->bpf_insn_idx = -1;
+ /* consumed by classify_linear */
+ sj->jmp_dst = fall->leader;
+ INIT_LIST_HEAD(&sj->l);
+ list_add_tail(&sj->l, &new_list);
+ }
+
+ list_splice(&new_list, &knod_prog->insns);
+
+ list_for_each_entry(m, &knod_prog->insns, l)
+ m->linear_idx = idx++;
+
+ kfree(order);
+ return 0;
+}
+
+/*
+ * Classify branches by linear position after the RPO reorder. Every edge is
+ * now forward, so a conditional jump is FORWARD_SKIP (or DIRECT_EXIT when it
+ * targets the exit), and every BPF_JA - real or synthetic - is FORWARD_GOTO
+ * (or DIRECT_EXIT).
+ */
+static int knod_bpf_classify_linear(struct knod_prog *knod_prog)
+{
+ struct knod_insn_meta *meta, *target;
+ short ti;
+
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ if (is_mbpf_cond_jump(meta)) {
+ meta->jump_neg_op = (mbpf_op(meta) == BPF_JNE);
+ ti = knod_meta_jump_target_idx(meta);
+ target = knod_bpf_lookup_meta(knod_prog, ti);
+ } else if (knod_meta_is_ja(meta)) {
+ /* synthetic JA carries its destination in jmp_dst;
+ * a real BPF_JA is resolved from its offset.
+ */
+ if (meta->jmp_dst) {
+ target = meta->jmp_dst;
+ } else {
+ ti = knod_meta_jump_target_idx(meta);
+ target = knod_bpf_lookup_meta(knod_prog, ti);
+ }
+ } else {
+ continue;
+ }
+
+ if (!target) {
+ pr_err("knod_cfg: bpf#%d unresolved branch target\n",
+ meta->bpf_insn_idx);
+ return -EINVAL;
+ }
+
+ if (target->linear_idx <= meta->linear_idx)
+ pr_warn("knod_cfg: bpf#%d -> #%d still backward after reorder (linear %d -> %d)\n",
+ meta->bpf_insn_idx, target->bpf_insn_idx,
+ meta->linear_idx, target->linear_idx);
+
+ if (knod_bpf_is_direct_exit_target(knod_prog, target)) {
+ meta->branch_type = KNOD_BR_DIRECT_EXIT;
+ meta->merge_point = target;
+ continue;
+ }
+
+ meta->branch_type = is_mbpf_cond_jump(meta) ?
+ KNOD_BR_FORWARD_SKIP : KNOD_BR_FORWARD_GOTO;
+ meta->merge_point = target;
+ target->is_merge_point = true;
+ }
+
+ return 0;
+}
+
+/*
+ * Build the basic-block CFG, compute RPO, reorder the instruction list into
+ * RPO and insert synthetic jumps. Returns 0, or a negative errno (a loop
+ * yields -EOPNOTSUPP).
+ */
+static int knod_bpf_build_cfg(struct knod_prog *knod_prog)
+{
+ struct knod_insn_meta *meta;
+ int n_insns = 0, n_bbs, n_back = 0, ret;
+ struct knod_bb *bbs;
+
+ list_for_each_entry(meta, &knod_prog->insns, l)
+ n_insns++;
+ if (!n_insns)
+ return 0;
+
+ bbs = kcalloc(n_insns, sizeof(*bbs), GFP_KERNEL);
+ if (!bbs)
+ return -ENOMEM;
+
+ n_bbs = knod_bpf_build_bbs(knod_prog, bbs);
+ if (n_bbs < 0) {
+ ret = n_bbs;
+ goto out_free;
+ }
+
+ ret = knod_bpf_build_edges(knod_prog, bbs, n_bbs);
+ if (ret)
+ goto out_free;
+
+ ret = knod_bpf_compute_rpo(bbs, n_bbs, &bbs[0], &n_back);
+ if (ret)
+ goto out_free;
+
+ ret = knod_bpf_compute_dom(bbs, n_bbs, &bbs[0]);
+ if (ret)
+ goto out_free;
+
+ if (n_back) {
+ ret = knod_bpf_detect_loops(bbs, n_bbs);
+ if (ret)
+ goto out_free;
+ }
+
+ /* Hand the block array to the prog for the /bpf/cfg view (freed at
+ * teardown); kept even if the reorder below rejects a loop, so the
+ * rejection can be inspected.
+ */
+ kfree(knod_prog->bbs);
+ knod_prog->bbs = bbs;
+ knod_prog->n_bbs = n_bbs;
+ knod_prog->n_back = n_back;
+
+ return knod_bpf_reorder_rpo(knod_prog, bbs, n_bbs, n_back);
+
+out_free:
+ kfree(bbs);
+ return ret;
+}
+
+/*
+ * Assign exec_save SGPR pairs to the forward branches, recycling a pair once
+ * its merge point has been passed. The peak concurrent live count is the
+ * actual SGPR requirement - usually far less than the total branch count.
+ */
+static int knod_bpf_alloc_exec_sregs(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog)
+{
+ struct {
+ u8 sreg;
+ struct knod_insn_meta *merge;
+ } live[72];
+ int exec_save_max, max_pairs, n_live, peak, j;
+ struct knod_insn_meta *meta;
+ u8 free_stack[72];
+ int free_top;
+
+ exec_save_max = (priv->isa_version == 10) ?
+ KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX10 :
+ KNOD_AMDGPU_EXEC_SAVE_SREG_MAX_GFX9;
+ max_pairs = (exec_save_max - knod_prog->exec_save_base + 1) / 2;
+
+ for (free_top = 0; free_top < max_pairs; free_top++)
+ free_stack[free_top] = knod_prog->exec_save_base +
+ (max_pairs - 1 - free_top) * 2;
+
+ n_live = 0;
+ peak = 0;
+
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ /* Reclaim pairs from scopes that merge at this insn */
+ for (j = n_live - 1; j >= 0; j--) {
+ if (live[j].merge == meta) {
+ free_stack[free_top++] = live[j].sreg;
+ live[j] = live[--n_live];
+ }
+ }
+
+ if (meta->branch_type != KNOD_BR_FORWARD_SKIP &&
+ meta->branch_type != KNOD_BR_FORWARD_GOTO)
+ continue;
+
+ if (free_top == 0) {
+ pr_err("knod_cfg: exec_save exhausted, peak %d concurrent scopes (max %d)\n",
+ peak, max_pairs);
+ return -ENOSPC;
+ }
+
+ meta->exec_save_sreg = free_stack[--free_top];
+ live[n_live].sreg = meta->exec_save_sreg;
+ live[n_live].merge = meta->merge_point;
+ n_live++;
+
+ if (n_live > peak)
+ peak = n_live;
+ }
+
+ knod_prog->exec_save_pairs_used = peak;
+ pr_debug("knod_cfg: done, peak %d concurrent scopes (total fwd jumps: %d+%d)\n",
+ peak, peak, n_live);
+ return 0;
+}
+
+/*
+ * knod_bpf_analyze_cfg - Classify branches and allocate SGPRs for
+ * structurized CFG.
+ *
+ * Runs before instruction emission. For each conditional branch:
+ * - Backward jump or jump to EXIT -> DIRECT_EXIT (no SGPR needed)
+ * - Forward jump to non-EXIT -> FORWARD_SKIP, allocate SGPR pair
+ *
+ * The "save jumping lanes" pattern handles crossing scopes correctly:
+ * branch: s_and_b64 s[n], exec, vcc; s_andn2_b64 exec, exec, vcc
+ * merge: s_or_b64 exec, exec, s[n]
+ *
+ * For JNE (jump_neg_op): VCC=0 -> jump, so lanes are swapped.
+ */
+static int knod_bpf_analyze_cfg(struct knod_bpf_priv *priv,
+ struct knod_prog *knod_prog)
+{
+ int ret;
+
+ /* Build the basic-block CFG, reorder the instruction list into RPO so
+ * every branch is forward (inserting synthetic jumps where a not-taken
+ * successor would no longer be adjacent), then classify each branch by
+ * linear position. A loop in the program is rejected (-EOPNOTSUPP).
+ */
+ ret = knod_bpf_build_cfg(knod_prog);
+ if (ret)
+ return ret;
+ ret = knod_bpf_classify_linear(knod_prog);
+ if (ret)
+ return ret;
+
+ return knod_bpf_alloc_exec_sregs(priv, knod_prog);
+}
+
+/*
+ * Shader stores packet address and length into pass_meta_buf slot header.
+ * Host-side SDMA engine does the actual copy to the delivery page.
+ *
+ * At entry:
+ * TMP_VREG10_LO (v42) = old_val * 2 (from pass_indices addressing)
+ * DATA_VREG (v64:v65) = packet source VRAM address
+ * DATA_END_VREG (v66:v67) = packet end address
+ * PARAM_SREG (s28:s29) = param GTT address
+ *
+ * Stores at slot header:
+ * +0: u32 len (DATA_END_LO - DATA_LO)
+ * +8: u64 src_addr (DATA_VREG)
+ */
+static void knod_emit_pass_addr_store(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta)
+{
+ struct amdgcn_param32 p[3];
+
+ /* s_lshl_b32 s18, s15, 3 - queue_idx * 8 for pass_meta_buf_gaddr
+ * stride
+ */
+ knod_sset32(&p[0], KNOD_AMDGPU_TMP_SREG1_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_iset32(&p[2], 3);
+ knod_emit(priv, meta, s_lshl_b32, p[0], p[1], p[2]);
+
+ /* s_load_dwordx2 s[16:17], s[28:29], offsetof(pass_meta_buf_gaddr)
+ * soffset=s18
+ */
+ knod_sset32(&p[0], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, s_load_dwordx2_soff, p[0], p[1],
+ offsetof(struct knod_bpf_param, pass_meta_buf_gaddr),
+ KNOD_AMDGPU_TMP_SREG1_LO);
+
+ /* s_waitcnt lgkmcnt(0) */
+ knod_emit(priv, meta, s_waitcnt_lgkmcnt);
+
+ /* Compute slot offset: old_val << 12 = (old_val*2) << 11
+ * v_lshlrev_b32 v44, 11, v42
+ */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_iset32(&p[1], 11);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_emit(priv, meta, v_lshlrev_b32, p[0], p[1], p[2]);
+
+ /* v_mov_b32 v45, 0 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG11_HI);
+ knod_iset32(&p[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* v_add_co_u32 v44, s16, v44 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_emit(priv, meta, v_add_co_u32, p[0], p[1], p[2]);
+
+ /*
+ * slot_hi = base_hi (NOT base_hi + carry). The slot offset is at
+ * most (pass_pkts_per_queue-1)*KNOD_PASS_SLOT_SIZE and the whole
+ * pass_meta_buf is a single contiguous allocation that never straddles
+ * a 4GiB boundary, so base_lo + offset never wraps and the carry is
+ * always 0. Avoid the v_add_co/v_addc carry chain entirely: on GFX9
+ * the v_addc here was picking up a stale VCC (from the preceding
+ * XDP_PASS v_cmp) instead of the v_add_co carry-out, setting slot_hi=1
+ * and faulting at 0x1_xxxx.
+ * v_mov_b32 v45, s17
+ */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG11_HI);
+ knod_sset32(&p[1], KNOD_AMDGPU_TMP_SREG0_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* v44:v45 = slot_addr in pass_meta_buf */
+
+ /* Store len: v_sub_u32 v0, DATA_END_LO, DATA_LO */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_DATA_END_VREG_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_sub_u32, p[0], p[1], p[2]);
+
+ /* global_store_dword [slot+0], len */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_emit(priv, meta, global_store_dword, p[0], p[1],
+ offsetof(struct knod_pass_slot_hdr, len));
+
+ /* global_store_dwordx2 [slot+8], DATA_VREG (src_addr) */
+ knod_vset32(&p[0], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG11_LO);
+ knod_emit(priv, meta, global_store_dwordx2, p[0], p[1],
+ offsetof(struct knod_pass_slot_hdr, src_addr));
+}
+
+static int knod_bpf_jit(struct knod_dev *knodev,
+ struct knod_prog *knod_prog)
+{
+ struct knod_bpf_priv *priv =
+ (struct knod_bpf_priv *)knodev->accel->xdp.priv;
+ short off, packet_off, stack_off;
+ struct knod_insn_meta *meta, *meta2;
+ struct amdgcn_param64 param64[2];
+ u32 insn_idx = 0, i;
+ struct amdgcn_param32 param[3];
+ struct amdgcn_param32 p32[2];
+ struct amdgcn_param32 p[3];
+ int s, d, imm, imm2;
+ int pass_branch_idx;
+ bool is_dw, fetch;
+ bool skip = false;
+ int pass_dwords;
+ int atomic_op;
+ int j;
+ int map_id;
+ u64 imm64;
+ int ret;
+
+ /* Analyze CFG before instruction emission */
+ ret = knod_bpf_analyze_cfg(priv, knod_prog);
+
+ if (ret)
+ return ret;
+
+ knod_bpf_layout_sregs(priv, knod_prog);
+ ret = knod_prog_prepare_insns(priv, knod_prog);
+ if (ret)
+ return ret;
+
+ knod_prog->max_stack_off = -knod_prog->max_stack_off;
+ knod_prog->max_stack_off = ALIGN(knod_prog->max_stack_off, 4);
+ knod_prog->max_packet_off = ALIGN(knod_prog->max_packet_off, 4);
+ /* NOTE:
+ * packet is accessed with packet_off + size
+ * largest size of it is unsigned long
+ */
+ knod_prog->max_packet_off += sizeof(unsigned long);
+ if (knod_prog->max_packet_off > MAX_PACKET_CACHE) {
+ WARN_ON_ONCE(1);
+ knod_bpf_pkt_cache = 0;
+ }
+
+ /* Initialize all exec_save SGPRs to 0.
+ * Without this, merge points that restore from exec_save SGPRs
+ * of branches that were skipped (by an outer s_cbranch_execz)
+ * would OR garbage into EXEC, enabling invalid lanes.
+ * In the old code, BPF_EXIT used s_endpgm so execution never
+ * reached those merge points; now it does.
+ */
+ if (knod_prog->exec_save_pairs_used > 0) {
+ u8 sreg;
+
+ meta = knod_prog_pre_last_meta(knod_prog);
+
+ for (sreg = knod_prog->exec_save_base;
+ sreg < knod_prog->exec_save_base +
+ knod_prog->exec_save_pairs_used * 2;
+ sreg += 2)
+ knod_emit(priv, meta, s_mov_b64, sreg,
+ AMDGCN_SREG_INTEGER_0);
+ }
+
+ if (knod_bpf_pkt_cache) {
+ meta = knod_prog_pre_last_meta(knod_prog);
+
+ /* ctx->data is in DATA_VREG -> copy to r32[0] via v_mov */
+ knod_vset32(¶m[0], r32[0].v);
+ knod_vset32(¶m[1], KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+ knod_vset32(¶m[0], r32[0].v + 1);
+ knod_vset32(¶m[1], KNOD_AMDGPU_DATA_VREG_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0], param[1]);
+
+ knod_global_load_size_cache(priv, meta,
+ &pkt_cache[0],
+ r32[0],
+ 0, /* dst index */
+ 0, /* start offset */
+ knod_prog->max_packet_off);
+ }
+
+ insn_idx = 0;
+ list_for_each_entry(meta, &knod_prog->pre_insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ insn_idx += (meta->amdgpu_insn[i].size / 4);
+ }
+
+ list_for_each_entry(meta, &knod_prog->insns, l) {
+ if (skip) {
+ skip = false;
+ meta->amdgpu_insn_idx = AMDGPU_INSN_SKIP;
+ continue;
+ }
+ s = meta->insn.src_reg;
+ d = meta->insn.dst_reg;
+ imm = meta->insn.imm;
+ off = meta->insn.off;
+
+ meta->amdgpu_insn_idx = insn_idx;
+ meta->amdgpu_insns = 0;
+
+ /* Structurized CFG: restore EXEC at merge points */
+ if (meta->is_merge_point) {
+ struct knod_insn_meta *br;
+
+ list_for_each_entry(br, &knod_prog->insns, l) {
+ if ((br->branch_type == KNOD_BR_FORWARD_SKIP ||
+ br->branch_type == KNOD_BR_FORWARD_GOTO) &&
+ br->merge_point == meta) {
+ knod_emit(priv, meta, s_or_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO,
+ br->exec_save_sreg);
+ }
+ }
+ /* Remove done lanes from restored EXEC */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO,
+ knod_prog->done_mask_sreg);
+ }
+
+ switch (meta->insn.code) {
+ /* ALU
+ * If a destination register contains a pointer of STACK,
+ * offset should not be minus.
+ */
+ case BPF_ALU | BPF_MOV | BPF_X:
+ case BPF_ALU64 | BPF_MOV | BPF_X:
+ //r[d] = r[s];
+ knod_mov64(priv, meta, bpf_reg64[d], bpf_reg64[s]);
+ break;
+ case BPF_ALU | BPF_MOV | BPF_K:
+ case BPF_ALU64 | BPF_MOV | BPF_K:
+ //r[d] = imm;
+ knod_iset64(&p64[0], imm);
+ knod_mov64(priv, meta, bpf_reg64[d], p64[0]);
+ break;
+ case BPF_ALU | BPF_XOR | BPF_X:
+ knod_xor32(priv, meta,
+ bpf_reg64[d].lo, bpf_reg64[d].lo,
+ bpf_reg64[s].lo);
+ knod_iset64(&p64[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p64[0].lo);
+ break;
+ case BPF_ALU64 | BPF_XOR | BPF_X:
+ //r[d] ^= r[s];
+ knod_xor32(priv, meta,
+ bpf_reg64[d].lo, bpf_reg64[d].lo,
+ bpf_reg64[s].lo);
+ knod_xor32(priv, meta,
+ bpf_reg64[d].hi, bpf_reg64[d].hi,
+ bpf_reg64[s].hi);
+ break;
+ case BPF_ALU | BPF_XOR | BPF_K:
+ case BPF_ALU64 | BPF_XOR | BPF_K:
+ knod_iset64(&p64[0], imm);
+ knod_mov64(priv, meta, bpf_reg64[d], p64[0]);
+ knod_xor32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, r64[0].lo);
+ break;
+ //r[d] ^= imm;
+ break;
+ case BPF_ALU | BPF_MOD | BPF_X:
+ case BPF_ALU64 | BPF_MOD | BPF_X:
+ //r[d] %= r[s];
+ knod_iset64(&p64[0], meta->umin_src);
+ knod_mod(priv, meta, bpf_reg64[d], p64[0],
+ r64[0], r64[1], r64[2], r64[3], r64[4]);
+ break;
+ case BPF_ALU | BPF_MOD | BPF_K:
+ case BPF_ALU64 | BPF_MOD | BPF_K:
+ //r[d] %= imm;
+ /* The dividend fits 32 bits (verifier rejects wider
+ * div/mod), so the 32-bit fold is valid even when
+ * clang emitted this as a 64-bit ALU op (e.g. u32
+ * hash % 65537 -> `r2 %= 65537`).
+ */
+ if (meta->umax_dst <= U32_MAX && imm &&
+ knod_mod_k32(priv, meta, bpf_reg64[d], imm))
+ break;
+ knod_iset64(&p64[0], imm);
+ knod_mod(priv, meta, bpf_reg64[d], p64[0],
+ r64[0], r64[1], r64[2], r64[3], r64[4]);
+ break;
+ case BPF_ALU | BPF_AND | BPF_X:
+ knod_and32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_AND | BPF_X:
+ //r[d] &= r[s];
+ knod_and64(priv, meta, bpf_reg64[d],
+ bpf_reg64[d], bpf_reg64[s]);
+ break;
+ case BPF_ALU | BPF_AND | BPF_K:
+ case BPF_ALU64 | BPF_AND | BPF_K:
+ //r[d] &= imm;
+ knod_iset32(&p32[0], imm);
+ knod_and32(priv, meta, bpf_reg64[d].lo, p32[0],
+ bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU | BPF_OR | BPF_X:
+ knod_or32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_OR | BPF_X:
+ //r[d] |= r[s];
+ knod_or32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ knod_or32(priv, meta, bpf_reg64[d].hi,
+ bpf_reg64[d].hi, bpf_reg64[s].hi);
+ break;
+ case BPF_ALU | BPF_OR | BPF_K:
+ case BPF_ALU64 | BPF_OR | BPF_K:
+ //r[d] |= imm;
+ knod_iset32(&p32[0], imm);
+ knod_or32(priv, meta,
+ bpf_reg64[d].lo, p32[0], bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU | BPF_ADD | BPF_X:
+ knod_add32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_ADD | BPF_X:
+ knod_add64(priv, meta, bpf_reg64[d],
+ bpf_reg64[d],
+ bpf_reg64[s]);
+
+ //r[d] += r[s];
+ break;
+ case BPF_ALU | BPF_ADD | BPF_K:
+ case BPF_ALU64 | BPF_ADD | BPF_K:
+ //r[d] += imm;
+ knod_iset32(&p32[0], imm);
+ knod_add32(priv, meta, bpf_reg64[d].lo,
+ p32[0], bpf_reg64[d].lo);
+ /* NOTE:
+ * imm is 24bit.
+ * But should we set hi to 0?
+ */
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU | BPF_SUB | BPF_X:
+ //r[d] -= r[s];
+ knod_sub32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_SUB | BPF_X:
+ //r[d] -= r[s];
+
+ knod_sub64(priv, meta, bpf_reg64[d], bpf_reg64[d],
+ bpf_reg64[s]);
+ break;
+ case BPF_ALU | BPF_SUB | BPF_K:
+ case BPF_ALU64 | BPF_SUB | BPF_K:
+ //r[d] -= imm;
+ knod_iset64(&p64[0], imm);
+ knod_subrev64(priv, meta, bpf_reg64[d], p64[0],
+ bpf_reg64[s]);
+ break;
+ case BPF_ALU | BPF_MUL | BPF_X:
+ knod_mul_lo32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[d].lo, bpf_reg64[s].lo);
+ break;
+ case BPF_ALU64 | BPF_MUL | BPF_X:
+ //r[d] *= r[s];
+ knod_mov64(priv, meta, r64[0], bpf_reg64[d]);
+ knod_mov64(priv, meta, r64[1], bpf_reg64[s]);
+ knod_mul64(priv, meta,
+ bpf_reg64[d],
+ r64[0],
+ r64[1],
+ r64[2]);
+ break;
+ case BPF_ALU | BPF_MUL | BPF_K:
+ knod_iset32(&p32[0], imm);
+ knod_mul_lo32(priv, meta, bpf_reg64[d].lo,
+ p32[0], bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_MUL | BPF_K:
+ //r[d] *= imm;
+ knod_iset64(&p64[0], imm);
+ knod_mov64(priv, meta, r64[0], bpf_reg64[d]);
+ knod_mov64(priv, meta, r64[1], p64[0]);
+ knod_mul64(priv, meta,
+ bpf_reg64[d],
+ r64[0],
+ r64[1],
+ r64[2]);
+ break;
+ case BPF_ALU | BPF_DIV | BPF_X:
+ case BPF_ALU64 | BPF_DIV | BPF_X:
+ //r[d] /= r[s];
+ knod_iset64(&p64[0], meta->umin_src);
+ knod_div(priv, meta, bpf_reg64[d], p64[0],
+ r64[0], r64[1], r64[2], r64[3]);
+ break;
+ case BPF_ALU | BPF_DIV | BPF_K:
+ case BPF_ALU64 | BPF_DIV | BPF_K:
+ //r[d] /= imm;
+ knod_iset64(&p64[0], imm);
+ knod_div(priv, meta, bpf_reg64[d], p64[0],
+ r64[0], r64[1], r64[2], r64[3]);
+ break;
+ case BPF_ALU | BPF_NEG:
+ knod_iset32(&p32[0], 0);
+ knod_sub32(priv, meta, bpf_reg64[d].lo, p32[0],
+ bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_NEG:
+ //r[d] = -r[d];
+ WARN_ON_ONCE(1);
+ break;
+ case BPF_ALU | BPF_LSH | BPF_X:
+ knod_lshlrev32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[s].lo, bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_LSH | BPF_X:
+ //r[d] <<= r[s];
+ knod_lshlrev64(priv, meta, bpf_reg64[d],
+ bpf_reg64[s], bpf_reg64[d]);
+ break;
+ case BPF_ALU | BPF_LSH | BPF_K:
+ knod_iset32(&p32[0], imm);
+ knod_lshlrev32(priv, meta, bpf_reg64[d].lo, p32[0],
+ bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_LSH | BPF_K:
+ //r[d] <<= imm;
+ knod_iset64(&p64[0], imm);
+ knod_lshlrev64(priv, meta, bpf_reg64[d], p64[0],
+ bpf_reg64[d]);
+ break;
+ case BPF_ALU | BPF_RSH | BPF_X:
+ knod_lshrrev32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_RSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_lshrrev64(priv, meta, bpf_reg64[d],
+ bpf_reg64[s], bpf_reg64[d]);
+ break;
+ case BPF_ALU | BPF_RSH | BPF_K:
+ knod_iset32(&p32[0], imm);
+ knod_lshrrev32(priv, meta, bpf_reg64[d].lo, p32[0],
+ bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_RSH | BPF_K:
+ //r[d] >>= imm;
+ knod_iset64(&p64[0], imm);
+ knod_lshrrev64(priv, meta, bpf_reg64[d],
+ p64[0], bpf_reg64[d]);
+ break;
+ case BPF_ALU | BPF_ARSH | BPF_X:
+ knod_ashrrev32(priv, meta, bpf_reg64[d].lo,
+ bpf_reg64[s].lo, bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_ARSH | BPF_X:
+ //r[d] >>= r[s];
+ knod_ashrrev64(priv, meta, bpf_reg64[d],
+ bpf_reg64[s], bpf_reg64[d]);
+ break;
+ case BPF_ALU | BPF_ARSH | BPF_K:
+ knod_iset32(&p32[0], imm);
+ knod_ashrrev32(priv, meta, bpf_reg64[d].lo,
+ p32[0], bpf_reg64[d].lo);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ break;
+ case BPF_ALU64 | BPF_ARSH | BPF_K:
+ //r[d] >>= imm;
+ knod_iset64(&p64[0], imm);
+ knod_ashrrev64(priv, meta, bpf_reg64[d],
+ p64[0], bpf_reg64[d]);
+ break;
+ case BPF_LD | BPF_IMM | BPF_DW:
+ meta2 = list_next_entry(meta, l);
+ if (WARN_ON_ONCE(!meta2))
+ return -EINVAL;
+ imm2 = meta2->insn.imm;
+ skip = true;
+ imm64 = (u64)imm2 << 32 | (u32)imm;
+ switch (s) {
+ case 0x00:
+ //r[d] = imm64;
+ knod_mov64_imm(priv, meta, d * 2,
+ imm64);
+
+ break;
+ case 0x01:
+ /* r[d] = param->maps[imm]; */
+ imm64 = knod_bpf_get_map_gaddr(priv,
+ meta,
+ meta2);
+ map_id = knod_bpf_get_map_id(priv,
+ meta,
+ meta2);
+ knod_mov64_imm(priv, meta, d * 2,
+ imm64);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+ break;
+ /* Legacy BPF packet access, not needed */
+ case BPF_LD | BPF_ABS | BPF_B:
+ case BPF_LD | BPF_ABS | BPF_H:
+ case BPF_LD | BPF_ABS | BPF_W:
+ case BPF_LD | BPF_IND | BPF_B:
+ case BPF_LD | BPF_IND | BPF_H:
+ case BPF_LD | BPF_IND | BPF_W:
+ //err = pc | 0x0700;
+ //exit = true;
+ WARN_ON_ONCE(1);
+ break;
+ case BPF_LDX | BPF_MEM | BPF_B:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->sreg.stack_off + off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &stack[0],
+ sizeof(unsigned char),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_load_ubyte,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_load_ubyte,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_load_ubyte,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->sreg.packet_off +
+ off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &pkt_cache[0],
+ sizeof(unsigned char),
+ packet_off);
+ } else {
+ knod_emit(priv, meta, global_load_ubyte,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ knod_wait_vmcnt(priv, meta);
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ //ptr = (__global void *)r[s] + off;
+ //r[d] = *(__global unsigned char *)ptr;
+ break;
+ case BPF_LDX | BPF_MEM | BPF_H:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->sreg.stack_off + off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &stack[0],
+ sizeof(unsigned short),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_load_ushort,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_load_ushort,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_load_ushort,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == SCALAR_VALUE) {
+ knod_emit(priv, meta, global_load_ushort,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->sreg.packet_off +
+ off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &pkt_cache[0],
+ sizeof(unsigned short),
+ packet_off);
+ } else if (priv->isa_version == 10 &&
+ (off & 1)) {
+ knod_bpf_emit_gfx10_unaligned_load(
+ priv, meta,
+ sizeof(unsigned short),
+ bpf_reg64[d],
+ bpf_reg64[s].lo, off);
+ } else {
+ knod_emit(priv, meta,
+ global_load_ushort,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ }
+ } else {
+ knod_jit_err(" type = %d\n", meta->ptr.type);
+ WARN_ON_ONCE(1);
+ }
+ //ptr = (__global void *)r[s] + off;
+ //r[d] = *(__global unsigned short *)ptr;
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi, p32[0]);
+ knod_wait_vmcnt(priv, meta);
+ break;
+ case BPF_LDX | BPF_MEM | BPF_W:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->sreg.stack_off + off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &stack[0],
+ sizeof(unsigned int),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ if (off == offsetof(struct xdp_md, data)) {
+ knod_mov32(priv, meta,
+ bpf_reg64[d].lo,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_VREG_LO,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ knod_mov32(priv, meta,
+ bpf_reg64[d].hi,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_VREG_HI,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ } else if (off == offsetof(struct xdp_md,
+ data_end)) {
+ knod_mov32(priv, meta,
+ bpf_reg64[d].lo,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_END_VREG_LO,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ knod_mov32(priv, meta,
+ bpf_reg64[d].hi,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_END_VREG_HI,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ } else {
+ emit_global_load_dwordx2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo,
+ off * 2);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ }
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_load_dword,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_load_dword,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->sreg.packet_off +
+ off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &pkt_cache[0],
+ sizeof(unsigned int),
+ packet_off);
+ } else if (priv->isa_version == 10 &&
+ (off & 3)) {
+ knod_bpf_emit_gfx10_unaligned_load(
+ priv, meta,
+ sizeof(unsigned int),
+ bpf_reg64[d],
+ bpf_reg64[s].lo, off);
+ } else {
+ knod_emit(priv, meta, global_load_dword,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ //ptr = (__global void *)r[s] + off;
+ //r[d] = *(__global unsigned int *)ptr;
+ if (meta->ptr.type != PTR_TO_CTX) {
+ knod_iset32(&p32[0], 0);
+ knod_mov32(priv, meta, bpf_reg64[d].hi,
+ p32[0]);
+ }
+ knod_wait_vmcnt(priv, meta);
+ break;
+ case BPF_LDX | BPF_MEM | BPF_DW:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->sreg.stack_off + off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &stack[0],
+ sizeof(unsigned long),
+ 512+stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ if (off == offsetof(struct xdp_md, data)) {
+ knod_mov32(priv, meta,
+ bpf_reg64[d].lo,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_VREG_LO,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ knod_mov32(priv, meta,
+ bpf_reg64[d].hi,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_VREG_HI,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ } else if (off == offsetof(struct xdp_md,
+ data_end)) {
+ knod_mov32(priv, meta,
+ bpf_reg64[d].lo,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_END_VREG_LO,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ knod_mov32(priv, meta,
+ bpf_reg64[d].hi,
+ (struct amdgcn_param32){
+ .v = KNOD_AMDGPU_DATA_END_VREG_HI,
+ .type = AMDGCN_PARAM_TYPE_VGPR});
+ } else {
+ knod_emit(priv, meta,
+ global_load_dwordx2,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off * 2);
+ }
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_load_dwordx2,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_load_dwordx2,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->sreg.packet_off +
+ off;
+ knod_bpf_load_size(priv, meta,
+ &bpf_reg64[d],
+ &pkt_cache[0],
+ sizeof(unsigned long),
+ packet_off);
+ } else if (priv->isa_version == 10 &&
+ (off & 3)) {
+ knod_bpf_emit_gfx10_unaligned_load(
+ priv, meta,
+ sizeof(unsigned long),
+ bpf_reg64[d],
+ bpf_reg64[s].lo, off);
+ } else {
+ knod_emit(priv, meta,
+ global_load_dwordx2,
+ bpf_reg64[d].lo,
+ bpf_reg64[s].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ //ptr = (__global void *)r[s] + off;
+ //r[d] = *(__global unsigned long *)ptr;
+ knod_wait_vmcnt(priv, meta);
+ break;
+ case BPF_STX | BPF_MEM | BPF_B:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_bpf_store_cache_size(priv, meta,
+ &bpf_reg64[s],
+ &stack[0],
+ sizeof(u8),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_byte,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_byte,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_byte,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &bpf_reg64[s],
+ &pkt_cache[0],
+ sizeof(u8),
+ packet_off);
+ } else {
+ knod_emit(priv, meta, global_store_byte,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_STX | BPF_MEM | BPF_H:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_bpf_store_cache_size(priv, meta,
+ &bpf_reg64[s],
+ &stack[0],
+ sizeof(u16),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_short,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_short,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_short,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &bpf_reg64[s],
+ &pkt_cache[0],
+ sizeof(u16),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_short,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_STX | BPF_MEM | BPF_W:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_bpf_store_cache_size(priv, meta,
+ &bpf_reg64[s],
+ &stack[0],
+ sizeof(u32),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_dword,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_dword,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_dword,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &bpf_reg64[s],
+ &pkt_cache[0],
+ sizeof(u32),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_dword,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_STX | BPF_MEM | BPF_DW:
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_bpf_store_cache_size(priv, meta,
+ &bpf_reg64[s],
+ &stack[0],
+ sizeof(u64),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv, meta,
+ &bpf_reg64[s],
+ &pkt_cache[0],
+ sizeof(u64),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_dwordx2,
+ bpf_reg64[s].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_STX | BPF_ATOMIC | BPF_W:
+ case BPF_STX | BPF_ATOMIC | BPF_DW:
+ is_dw = BPF_SIZE(meta->insn.code) == BPF_DW;
+ atomic_op = imm & ~BPF_FETCH;
+ fetch = imm & BPF_FETCH;
+
+ /*
+ * BPF atomic: *(dst_reg + off) op= src_reg
+ * If BPF_FETCH: src_reg = old value
+ * BPF_CMPXCHG: expect in r0, new in src_reg,
+ * old value returned in r0.
+ *
+ * global_atomic_* with glc=1 returns old value in vdst.
+ * For non-FETCH ops use glc=0 (fire-and-forget).
+ *
+ * 64-bit atomics (global_atomic_*_x2) hang on GFX9
+ * VRAM. GFX10+ supports them.
+ */
+ if (is_dw && priv->isa_version == 9) {
+ pr_err("knod: 64-bit atomic not supported on GFX9\n");
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * For CMPXCHG/FETCH: drain pending loads so addr/data
+ * VGPRs are ready. For non-fetch ADD wave reduction,
+ * addr was already waited for at map_lookup, and data
+ * is from ALU - no waitcnt needed.
+ */
+ if (imm == BPF_CMPXCHG || fetch)
+ knod_wait_vmcnt(priv, meta);
+
+ if (imm == BPF_CMPXCHG) {
+ /* cmpswap: data = {expect(r0), new(src)}.
+ * AMD cmpswap data reg pair must be
+ * consecutive:
+ * 32-bit: {cmp, new} = 2 consecutive VGPRs
+ * 64-bit: {cmp_lo, cmp_hi, new_lo, new_hi}
+ * Copy r0 and src into TMP consecutive pair.
+ */
+ struct amdgcn_param32 tmp0_lo, tmp0_hi,
+ tmp1_lo, tmp1_hi;
+
+ knod_vset32(&tmp0_lo,
+ KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_vset32(&tmp0_hi,
+ KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_vset32(&tmp1_lo,
+ KNOD_AMDGPU_TMP_VREG1_LO);
+ knod_vset32(&tmp1_hi,
+ KNOD_AMDGPU_TMP_VREG1_HI);
+
+ if (!is_dw) {
+ /* TMP0_LO = r0 (expect),
+ * TMP0_HI = src (new)
+ */
+ knod_mov32(priv, meta,
+ tmp0_lo,
+ bpf_reg64[0].lo);
+ knod_mov32(priv, meta,
+ tmp0_hi,
+ bpf_reg64[s].lo);
+
+ emit_global_atomic_cmpswap(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ tmp0_lo, bpf_reg64[d].lo,
+ tmp0_lo, off, 1);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ knod_wait_vmcnt(priv, meta);
+ /* Return old value in r0 */
+ knod_mov32(priv, meta,
+ bpf_reg64[0].lo,
+ tmp0_lo);
+ } else {
+ /* 64-bit:
+ * {r0_lo, r0_hi, src_lo, src_hi}
+ */
+ knod_mov32(priv, meta,
+ tmp0_lo,
+ bpf_reg64[0].lo);
+ knod_mov32(priv, meta,
+ tmp0_hi,
+ bpf_reg64[0].hi);
+ knod_mov32(priv, meta,
+ tmp1_lo,
+ bpf_reg64[s].lo);
+ knod_mov32(priv, meta,
+ tmp1_hi,
+ bpf_reg64[s].hi);
+
+ emit_global_atomic_cmpswap_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ tmp0_lo, bpf_reg64[d].lo,
+ tmp0_lo, off, 1);
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ knod_wait_vmcnt(priv, meta);
+ knod_mov32(priv, meta,
+ bpf_reg64[0].lo,
+ tmp0_lo);
+ knod_mov32(priv, meta,
+ bpf_reg64[0].hi,
+ tmp0_hi);
+ }
+ } else if (!fetch && atomic_op == BPF_ADD) {
+ /*
+ * Wave reduction for BPF_ADD (non-fetch):
+ * Instead of all lanes doing atomic_add(val),
+ * count active lanes, multiply by val, and
+ * have a single lane do atomic_add(count*val).
+ *
+ * Assumes src_reg is uniform across all active
+ * lanes (true for constant increments like
+ * +=1).
+ *
+ * s_bcnt1_i32_b64 s_tmp, exec
+ * v_mul_lo_u32 v_tmp, s_tmp, v_src
+ * v_mbcnt_lo v_tmp2, exec_lo, 0
+ * v_mbcnt_hi v_tmp2, exec_hi, v_tmp2
+ * v_cmp_eq_u32 vcc, v_tmp2, 0
+ * s_and_saveexec s_save, vcc
+ * global_atomic_add addr, v_tmp, off
+ * s_waitcnt vmcnt(0)
+ * s_mov_b64 exec, s_save
+ */
+ struct amdgcn_param32 v_tmp, v_tmp2,
+ s_count, s_exec_lo,
+ s_exec_hi, v_zero;
+
+ knod_vset32(&v_tmp,
+ KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_vset32(&v_tmp2,
+ KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_sset32(&s_count,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+ knod_sset32(&s_exec_lo,
+ AMDGCN_SREG_EXEC_LO);
+ knod_sset32(&s_exec_hi,
+ AMDGCN_SREG_EXEC_LO + 1);
+ knod_iset32(&v_zero, 0);
+
+ /* s_bcnt1_i32_b64 s_count, exec */
+ knod_emit(priv, meta, s_bcnt1_i32_b64,
+ KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_EXEC_LO);
+
+ /* v_mul_lo_u32 v_tmp, s_count, v_src */
+ knod_emit(priv, meta, v_mul_lo_u32, v_tmp,
+ s_count, bpf_reg64[s].lo);
+
+ /* v_mbcnt_lo_u32_b32 v_tmp2, exec_lo, 0 */
+ knod_emit(priv, meta, v_mbcnt_lo_u32_b32,
+ v_tmp2, s_exec_lo, v_zero);
+
+ /* v_mbcnt_hi_u32_b32 v_tmp2, exec_hi, v_tmp2 */
+ knod_emit(priv, meta, v_mbcnt_hi_u32_b32,
+ v_tmp2, s_exec_hi, v_tmp2);
+
+ /* v_cmp_eq_u32 vcc, 0, v_tmp2 ->
+ * first active lane
+ */
+ knod_emit(priv, meta, v_cmp_eq_u32, v_zero,
+ v_tmp2);
+
+ /* s_and_saveexec_b64 s_save, vcc */
+ knod_emit(priv, meta, s_and_saveexec_b64,
+ KNOD_AMDGPU_TMP_SREG0_LO,
+ AMDGCN_SREG_VCC_LO);
+
+ if (is_dw) {
+ struct amdgcn_param32 v_tmp_hi;
+
+ knod_vset32(&v_tmp_hi,
+ KNOD_AMDGPU_TMP_VREG0_HI);
+ /*
+ * x2 atomics consume a consecutive
+ * VGPR pair, and the 32-bit addend
+ * lands in the host-visible low dword
+ * when it is placed in the second
+ * register.
+ */
+ knod_emit(priv, meta, v_mov_b32_e32,
+ v_tmp_hi, v_tmp);
+ knod_emit(priv, meta, v_mov_b32_e32,
+ v_tmp, v_zero);
+ /* global_atomic_add_x2 addr,
+ * {0, v_tmp_hi}, off
+ */
+ knod_emit(priv, meta,
+ global_atomic_add_x2, v_tmp,
+ bpf_reg64[d].lo, v_tmp, off,
+ 0);
+ } else {
+ /* global_atomic_add addr, v_tmp, off
+ * (single lane)
+ */
+ knod_emit(priv, meta, global_atomic_add,
+ v_tmp,
+ bpf_reg64[d].lo, v_tmp, off,
+ 0);
+ }
+
+ /*
+ * No s_waitcnt needed: glc=0 atomic doesn't
+ * increment vmcnt. The GPU guarantees all
+ * pending ops complete before wave exit.
+ */
+
+ /* s_mov_b64 exec, s_save */
+ knod_emit(priv, meta, s_mov_b64,
+ AMDGCN_SREG_EXEC_LO,
+ KNOD_AMDGPU_TMP_SREG0_LO);
+ } else if (!is_dw) {
+ /* 32-bit: AND, OR, XOR, XCHG, or fetch ops */
+ struct amdgcn_param32 vdst, data_p;
+
+ if (fetch) {
+ vdst = bpf_reg64[s].lo;
+ } else {
+ knod_vset32(&vdst,
+ KNOD_AMDGPU_TMP_VREG0_LO);
+ }
+ data_p = bpf_reg64[s].lo;
+
+ switch (atomic_op) {
+ case BPF_ADD:
+ emit_global_atomic_add(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_AND:
+ emit_global_atomic_and(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_OR:
+ emit_global_atomic_or(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_XOR:
+ emit_global_atomic_xor(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ default: /* BPF_XCHG */
+ emit_global_atomic_swap(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ }
+ } else {
+ /* 64-bit: AND, OR, XOR, XCHG, or fetch ops */
+ struct amdgcn_param32 vdst, data_p;
+
+ if (fetch) {
+ vdst = bpf_reg64[s].lo;
+ } else {
+ knod_vset32(&vdst,
+ KNOD_AMDGPU_TMP_VREG0_LO);
+ }
+ data_p = bpf_reg64[s].lo;
+
+ switch (atomic_op) {
+ case BPF_ADD:
+ emit_global_atomic_add_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_AND:
+ emit_global_atomic_and_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_OR:
+ emit_global_atomic_or_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ case BPF_XOR:
+ emit_global_atomic_xor_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ default: /* BPF_XCHG */
+ emit_global_atomic_swap_x2(
+ priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns],
+ vdst, bpf_reg64[d].lo,
+ data_p, off, fetch);
+ break;
+ }
+ debug_insn(priv->isa_version,
+ &meta->amdgpu_insn[meta->amdgpu_insns]);
+ meta->amdgpu_insns++;
+ /* Always wait for atomic completion */
+ knod_wait_vmcnt(priv, meta);
+ }
+ break;
+ case BPF_ST | BPF_MEM | BPF_B:
+ knod_iset32(&p32[0], imm);
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_iset64(&p64[0], imm);
+ knod_bpf_store_cache_size(priv, meta,
+ &p64[0],
+ &stack[0],
+ sizeof(u8),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_byte, p32[0],
+ bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_byte, p32[0],
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_byte, p32[0],
+ bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ knod_iset64(&p64[0], imm);
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &p64[0],
+ &pkt_cache[0],
+ sizeof(u8),
+ packet_off);
+ } else {
+ knod_emit(priv, meta, global_store_byte,
+ p64[0].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_ST | BPF_MEM | BPF_H:
+ knod_iset32(&p32[0], imm);
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_iset64(&p64[0], imm);
+ knod_bpf_store_cache_size(priv, meta,
+ &p64[0],
+ &stack[0],
+ sizeof(u16),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_short,
+ p32[0], bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_short,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_short,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ knod_iset64(&p64[0], imm);
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &p64[0],
+ &pkt_cache[0],
+ sizeof(u16),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_short, p64[0].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_ST | BPF_MEM | BPF_W:
+ knod_iset32(&p32[0], imm);
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_iset64(&p64[0], imm);
+ knod_bpf_store_cache_size(priv, meta,
+ &p64[0],
+ &stack[0],
+ sizeof(u32),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_dword,
+ p32[0], bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_dword,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_dword,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ knod_iset64(&p64[0], imm);
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv,
+ meta,
+ &p64[0],
+ &pkt_cache[0],
+ sizeof(u32),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_dword, p64[0].lo,
+ bpf_reg64[d].lo, off);
+ }
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_ST | BPF_MEM | BPF_DW:
+ knod_iset32(&p32[0], imm);
+ if (meta->ptr.type == PTR_TO_STACK) {
+ stack_off = meta->dreg.stack_off + off;
+ knod_iset64(&p64[0], imm);
+ knod_bpf_store_cache_size(priv, meta,
+ &p64[0],
+ &stack[0],
+ sizeof(u64),
+ 512 + stack_off);
+ } else if (meta->ptr.type == PTR_TO_CTX) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ p32[0], bpf_reg64[d].lo, off * 2);
+ } else if (meta->ptr.type == PTR_TO_MAP_VALUE) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_MAP_KEY) {
+ knod_emit(priv, meta, global_store_dwordx2,
+ p32[0], bpf_reg64[d].lo, off);
+ } else if (meta->ptr.type == PTR_TO_PACKET) {
+ knod_iset64(&p64[0], imm);
+ if (knod_bpf_pkt_cache) {
+ packet_off = meta->dreg.packet_off +
+ off;
+ knod_bpf_store_cache_size(priv, meta,
+ &p64[0],
+ &pkt_cache[0],
+ sizeof(u64),
+ packet_off);
+ } else {
+ knod_emit(priv, meta,
+ global_store_dwordx2,
+ p64[0].lo,
+ bpf_reg64[d].lo, off);
+ }
+ knod_iset32(&p32[0], imm);
+ } else {
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_JMP32 | BPF_JA | BPF_K:
+ if (meta->branch_type == KNOD_BR_DIRECT_EXIT) {
+ knod_bpf_emit_direct_exit_retval(priv, meta,
+ meta->merge_point);
+
+ /* Unconditional goto exit:
+ * all active lanes done
+ */
+ knod_emit(priv, meta, s_or_b64,
+ knod_prog->done_mask_sreg,
+ knod_prog->done_mask_sreg,
+ AMDGCN_SREG_EXEC_LO);
+ knod_emit(priv, meta, s_mov_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_INTEGER_0);
+ } else if (meta->branch_type == KNOD_BR_FORWARD_GOTO) {
+ /* Structurized: save all active lanes, clear
+ * EXEC. Lanes resume at merge_point (target).
+ */
+ knod_emit(priv, meta, s_mov_b64,
+ meta->exec_save_sreg,
+ AMDGCN_SREG_EXEC_LO);
+ knod_emit(priv, meta, s_mov_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_INTEGER_0);
+ } else {
+ /* Reorder classifies every JA as FORWARD_GOTO
+ * or DIRECT_EXIT; reaching here is a bug.
+ */
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_JMP | BPF_JA | BPF_K:
+ if (meta->branch_type == KNOD_BR_DIRECT_EXIT) {
+ knod_bpf_emit_direct_exit_retval(priv, meta,
+ meta->merge_point);
+
+ /* Unconditional goto exit:
+ * all active lanes done
+ */
+ knod_emit(priv, meta, s_or_b64,
+ knod_prog->done_mask_sreg,
+ knod_prog->done_mask_sreg,
+ AMDGCN_SREG_EXEC_LO);
+ knod_emit(priv, meta, s_mov_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_INTEGER_0);
+ } else if (meta->branch_type == KNOD_BR_FORWARD_GOTO) {
+ /* Structurized: save all active lanes, clear
+ * EXEC. Lanes resume at merge_point (target).
+ */
+ knod_emit(priv, meta, s_mov_b64,
+ meta->exec_save_sreg,
+ AMDGCN_SREG_EXEC_LO);
+ knod_emit(priv, meta, s_mov_b64,
+ AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_INTEGER_0);
+ } else {
+ /* Reorder classifies every JA as FORWARD_GOTO
+ * or DIRECT_EXIT; reaching here is a bug.
+ */
+ WARN_ON_ONCE(1);
+ }
+ break;
+ case BPF_JMP32 | BPF_JEQ | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JEQ | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u64, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JEQ | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_eq_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JEQ | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_eq_u64, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JGT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_gt_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JGT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_gt_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JGT | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_gt_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JGT | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_gt_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JGE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_ge_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JGE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_ge_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JGE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_ge_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JGE | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_ge_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JLT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_lt_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JLT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_lt_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JLT | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_lt_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JLT | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_lt_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JLE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_le_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JLE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_le_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JLE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_le_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JLE | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], 2 * 2);
+ knod_emit(priv, meta, v_cmp_le_u64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSGT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_gt_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSGT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_gt_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSGT | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_gt_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSGT | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_gt_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSGE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_ge_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSGE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_ge_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSGE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_ge_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSGE | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_ge_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSLT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_lt_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSLT | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_lt_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSLT | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_lt_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSLT | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_lt_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSLE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_le_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSLE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_le_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSLE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_le_i32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSLE | BPF_X:
+ knod_vset64(¶m64[0], d * 2);
+ knod_vset64(¶m64[1], s * 2);
+ knod_emit(priv, meta, v_cmp_le_i64, param64[0],
+ param64[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSET | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], d * 2);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSET | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], d * 2);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_vset32(¶m[0], (d * 2) + 1);
+ knod_vset32(¶m[1], (d * 2) + 1);
+ knod_vset32(¶m[2], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u64, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JSET | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], d * 2);
+ knod_vset32(¶m[2], s * 2);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JSET | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], d * 2);
+ knod_vset32(¶m[2], s * 2);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_vset32(¶m[0], (d * 2) + 1);
+ knod_vset32(¶m[1], (d * 2) + 1);
+ knod_vset32(¶m[2], (s * 2) + 1);
+ knod_emit(priv, meta, v_and_b32_e32, param[0],
+ param[1], param[2]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JNE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JNE | BPF_K:
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_iset32(¶m[1], imm);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], KNOD_AMDGPU_TMP_VREG0_HI);
+ knod_iset32(¶m[1], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, param[0],
+ param[1]);
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u64, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_JNE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_eq_u32, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP | BPF_JNE | BPF_X:
+ knod_vset32(¶m[0], d * 2);
+ knod_vset32(¶m[1], s * 2);
+ knod_emit(priv, meta, v_cmp_eq_u64, param[0],
+ param[1]);
+ knod_bpf_emit_branch_tail(priv, meta, knod_prog, off);
+ break;
+ case BPF_JMP32 | BPF_CALL:
+ case BPF_JMP | BPF_CALL:
+ switch (imm) {
+ case 1:
+ if (map_id == -1) {
+ WARN_ON_ONCE(1);
+ break;
+ }
+ knod_bpf_map_lookup(priv, meta, map_id);
+ map_id = -1;
+ break;
+ case 2: {
+ struct knod_bpf_map_obj *_map_obj;
+
+ if (map_id == -1) {
+ WARN_ON_ONCE(1);
+ break;
+ }
+ _map_obj = knod_bpf_map_kaddr(priv, map_id);
+ if (!_map_obj) {
+ WARN_ON_ONCE(1);
+ break;
+ }
+ if (_map_obj->map_type == BPF_MAP_TYPE_ARRAY)
+ knod_bpf_map_update_array(priv, meta,
+ map_id);
+ else if (_map_obj->map_type ==
+ BPF_MAP_TYPE_HASH)
+ knod_bpf_map_update_hash(priv, meta,
+ map_id);
+ else
+ WARN_ON_ONCE(1);
+ map_id = -1;
+ break;
+ }
+ case 3: {
+ struct knod_bpf_map_obj *_map_obj;
+
+ if (map_id == -1) {
+ WARN_ON_ONCE(1);
+ break;
+ }
+ _map_obj = knod_bpf_map_kaddr(priv, map_id);
+ if (!_map_obj) {
+ WARN_ON_ONCE(1);
+ break;
+ }
+ if (_map_obj->map_type == BPF_MAP_TYPE_ARRAY)
+ knod_bpf_map_delete_array(priv, meta,
+ map_id);
+ else if (_map_obj->map_type ==
+ BPF_MAP_TYPE_HASH)
+ knod_bpf_map_delete_hash(priv, meta,
+ map_id);
+ else
+ WARN_ON_ONCE(1);
+ map_id = -1;
+ break;
+ }
+ case 5:
+ knod_bpf_ktime_get_ns(priv, meta);
+ break;
+ case 44:
+ knod_bpf_xdp_adjust_head(priv, meta);
+ break;
+ case 65:
+ knod_bpf_xdp_adjust_tail(priv, meta);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+ break;
+ case BPF_JMP32 | BPF_EXIT:
+ case BPF_JMP | BPF_EXIT:
+ /* Structurized CFG: BPF_EXIT is NOT a terminator.
+ * Mark all active lanes as done and clear EXEC.
+ * Actual exit handling (retval store, pkt_cache,
+ * PASS block, s_endpgm) is in the unified
+ * fallthrough EXIT at the end of the stream.
+ * This follows the LLVM StructurizeCFG model where
+ * all lanes must reach the single exit point.
+ */
+ knod_emit(priv, meta, s_or_b64,
+ knod_prog->done_mask_sreg,
+ knod_prog->done_mask_sreg,
+ AMDGCN_SREG_EXEC_LO);
+
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_INTEGER_0);
+ break;
+ case BPF_ALU | BPF_END | BPF_TO_BE: {
+ struct amdgcn_param32 v_dst_lo, v_dst_hi, v_tmp, s_sel;
+
+ knod_vset32(&v_dst_lo, d * 2);
+ knod_vset32(&v_dst_hi, d * 2 + 1);
+ knod_vset32(&v_tmp, KNOD_AMDGPU_TMP_VREG0_LO);
+ knod_sset32(&s_sel, KNOD_AMDGPU_TMP_SREG0_LO);
+
+ switch (imm) {
+ case 16:
+ /* bswap16+zext: {0,0,byte0,byte1} */
+ knod_iset32(¶m[0], 0x0C0C0001);
+ knod_emit(priv, meta, s_mov_b32, s_sel,
+ param[0]);
+
+ knod_emit(priv, meta, v_perm_b32, v_dst_lo,
+ v_dst_lo, v_dst_lo, s_sel);
+
+ knod_iset32(¶m[0], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, v_dst_hi,
+ param[0]);
+ break;
+ case 32:
+ /* bswap32+zext */
+ knod_iset32(¶m[0], 0x00010203);
+ knod_emit(priv, meta, s_mov_b32, s_sel,
+ param[0]);
+
+ knod_emit(priv, meta, v_perm_b32, v_dst_lo,
+ v_dst_lo, v_dst_lo, s_sel);
+
+ knod_iset32(¶m[0], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, v_dst_hi,
+ param[0]);
+ break;
+ case 64: {
+ struct amdgcn_param32 v_src_hi;
+
+ knod_vset32(&v_src_hi, d * 2 + 1);
+
+ /* bswap32 selector */
+ knod_iset32(¶m[0], 0x00010203);
+ knod_emit(priv, meta, s_mov_b32, s_sel,
+ param[0]);
+
+ /* tmp = bswap32(lo) */
+ knod_emit(priv, meta, v_perm_b32, v_tmp,
+ v_dst_lo, v_dst_lo, s_sel);
+
+ /* new_lo = bswap32(hi) */
+ knod_emit(priv, meta, v_perm_b32, v_dst_lo,
+ v_src_hi, v_src_hi, s_sel);
+
+ /* new_hi = tmp (bswap32(old_lo)) */
+ knod_emit(priv, meta, v_mov_b32_e32, v_dst_hi,
+ v_tmp);
+ break;
+ }
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+ break;
+ }
+ case BPF_ALU | BPF_END | BPF_TO_LE: {
+ struct amdgcn_param32 v_dst_lo, v_dst_hi;
+
+ knod_vset32(&v_dst_lo, d * 2);
+ knod_vset32(&v_dst_hi, d * 2 + 1);
+
+ switch (imm) {
+ case 16:
+ knod_iset32(¶m[0], 0xFFFF);
+ knod_emit(priv, meta, v_and_b32_e32, v_dst_lo,
+ param[0], v_dst_lo);
+
+ knod_iset32(¶m[0], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, v_dst_hi,
+ param[0]);
+ break;
+ case 32:
+ knod_iset32(¶m[0], 0);
+ knod_emit(priv, meta, v_mov_b32_e32, v_dst_hi,
+ param[0]);
+ break;
+ case 64:
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+ break;
+ }
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+
+ WARN_ON(meta->amdgpu_insns >= KNOD_META_INSNS);
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ insn_idx += (meta->amdgpu_insn[i].size / 4);
+ }
+
+ /* Fallthrough EXIT: publish a verdict for every in-bounds lane.
+ * Lanes that did not reach BPF_EXIT are forced to XDP_DROP below.
+ */
+ meta = kzalloc(sizeof(*meta), GFP_KERNEL);
+ if (!meta)
+ return -ENOMEM;
+ list_add_tail(&meta->l, &knod_prog->post_insns);
+
+ /* Any in-bounds lane outside done_mask gets a conservative DROP
+ * verdict instead of publishing stale VGPR state or leaving the
+ * recycle-time poison in bd->act.
+ */
+ knod_emit(priv, meta, s_andn2_b64, AMDGCN_SREG_EXEC_LO,
+ knod_prog->initial_exec_sreg, knod_prog->done_mask_sreg);
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_iset32(&p[1], XDP_DROP);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* Store bd->act for every lane that participated in this dispatch.
+ * Done lanes retain their low32 action in v0; unfinished lanes publish
+ * the fallback DROP written above.
+ */
+ knod_emit(priv, meta, s_mov_b64, AMDGCN_SREG_EXEC_LO,
+ knod_prog->initial_exec_sreg);
+
+ /* BPF/XDP verdicts are low32; do not spend a second GTT dword per
+ * packet.
+ */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG0_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_SLOT_VREG_LO);
+ knod_emit(priv, meta, global_store_dword, p[0], p[1],
+ offsetof(struct spsc_bd, act));
+
+ if (knod_prog->uses_adjust)
+ knod_bpf_emit_offlen_writeback(priv, meta);
+
+ /* pkt_cache writeback: flush modified packet data back to VRAM */
+ if (knod_bpf_pkt_cache) {
+ knod_vset32(&p[0], r32[0].v);
+ knod_vset32(&p[1],
+ KNOD_AMDGPU_DATA_VREG_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+ knod_vset32(&p[0], r32[0].v + 1);
+ knod_vset32(&p[1],
+ KNOD_AMDGPU_DATA_VREG_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ knod_global_store_size_cache(priv, meta,
+ &pkt_cache[0],
+ r32[0],
+ 0, /* dst index */
+ 0, /* start offset */
+ knod_prog->max_packet_off);
+ }
+
+ /* XDP_PASS detection */
+ knod_iset32(&p[0], XDP_PASS);
+ knod_vset32(&p[1], KNOD_AMDGPU_VREG0_LO);
+ knod_emit(priv, meta, v_cmp_eq_u32, p[0], p[1]);
+
+ pass_branch_idx = meta->amdgpu_insns;
+ knod_emit(priv, meta, s_cbranch_vccz, 0);
+
+ /* EXEC &= VCC (only PASS lanes) */
+ knod_emit(priv, meta, s_and_b64, AMDGCN_SREG_EXEC_LO,
+ AMDGCN_SREG_EXEC_LO, AMDGCN_SREG_VCC_LO);
+
+ /* v_mov param addr to VGPR pair */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_LO);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_HI);
+ knod_sset32(&p[1], KNOD_AMDGPU_PARAM_SREG_HI);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* Per-queue pass_count: offset TMP_VREG9 by queue_idx * 4 */
+ /* v_mov_b32 v2, s15 (queue_idx -> VGPR) */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG1_LO);
+ knod_sset32(&p[1],
+ KNOD_AMDGPU_WORKGROUP_ID_Y_SREG);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* v_lshlrev_b32 v2, 2, v2 (queue_idx * 4) */
+ knod_vset32(&p[0], KNOD_AMDGPU_VREG1_LO);
+ knod_iset32(&p[1], 2);
+ knod_vset32(&p[2], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, meta, v_lshlrev_b32, p[0], p[1], p[2]);
+
+ /* v_add_u32 v40, v2, v40 (param_addr_lo += queue_idx * 4) */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_VREG1_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, meta, v_add_u32, p[0], p[1], p[2]);
+
+ /* v_mov TMP10_LO, 1 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_iset32(&p[1], 1);
+ knod_emit(priv, meta, v_mov_b32_e32, p[0], p[1]);
+
+ /* global_atomic_add TMP10_LO, TMP9, TMP10_LO,
+ * offsetof(pass_count)
+ * GLC=1 to receive old_val in vdst (needed for per-lane slot
+ * index). With GLC=0 vdst is NOT written, leaving TMP10_LO
+ * as the addend (1) -- every PASS lane then computes slot=1
+ * and races on the same pass_meta_buf entry, leaving slot 0 empty.
+ */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_emit(priv, meta, global_atomic_add, p[0], p[1], p[2],
+ offsetof(struct knod_bpf_param, pass_count), 1);
+
+ /* s_waitcnt vmcnt(0) */
+ knod_emit(priv, meta, s_waitcnt_vmcnt);
+
+ /* v_sub_u32 v40, v40, v2 (restore param_addr_lo) */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_VREG1_LO);
+ knod_emit(priv, meta, v_sub_u32, p[0], p[1], p[2]);
+
+ /* old_val * 2 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_iset32(&p[1], 1);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_emit(priv, meta, v_lshlrev_b32, p[0], p[1], p[2]);
+
+ /* addr_lo += old_val * 2 */
+ knod_vset32(&p[0], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG10_LO);
+ knod_vset32(&p[2], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, meta, v_add_u32, p[0], p[1], p[2]);
+
+ /* global_store_short pass_indices[old_val],
+ * BACKLOG_IDX_VREG
+ * dst=data, src=addr in wrapper convention
+ */
+ knod_vset32(&p[0],
+ KNOD_AMDGPU_BACKLOG_IDX_VREG);
+ knod_vset32(&p[1], KNOD_AMDGPU_TMP_VREG9_LO);
+ knod_emit(priv, meta, global_store_short, p[0], p[1],
+ offsetof(struct knod_bpf_param, pass_indices));
+
+ /* Copy PASS packet data (shader mode) or store src addr (SDMA mode) */
+ knod_emit_pass_addr_store(priv, meta);
+
+ /* Patch branch offset */
+ pass_dwords = 0;
+
+ for (j = pass_branch_idx + 1; j < meta->amdgpu_insns; j++)
+ pass_dwords += meta->amdgpu_insn[j].size / 4;
+ emit_s_cbranch_vccz(priv->isa_version,
+ &meta->amdgpu_insn[pass_branch_idx],
+ pass_dwords);
+
+ knod_emit(priv, meta, s_endpgm);
+
+ for (j = 0; j < meta->amdgpu_insns; j++)
+ insn_idx += meta->amdgpu_insn[j].size / 4;
+
+ if (priv->isa_version == 10) {
+ if (insn_idx % 256) {
+ meta = kzalloc_obj(*meta, GFP_KERNEL);
+ if (!meta)
+ return -ENOMEM;
+ list_add_tail(&meta->l, &knod_prog->post_insns);
+ }
+
+ while (insn_idx % 256) {
+ if (meta->amdgpu_insns >= KNOD_META_INSNS) {
+ meta = kzalloc_obj(*meta, GFP_KERNEL);
+ if (!meta)
+ return -ENOMEM;
+ list_add_tail(&meta->l, &knod_prog->post_insns);
+ }
+ knod_emit(priv, meta, s_code_end);
+ insn_idx +=
+ meta->amdgpu_insn[meta->amdgpu_insns - 1].size /
+ 4;
+ }
+ }
+
+ return 0;
+}
+
+static int knod_bpf_translate(struct bpf_prog *prog)
+{
+ struct knod_prog *knod_prog = prog->aux->offload->dev_priv;
+ struct knod_dev *knodev = knod_prog->knodev;
+ int ret;
+
+ knod_bpf_map_setup(prog);
+ ret = knod_bpf_jit(knodev, knod_prog);
+ if (ret < 0) {
+ pr_err("knod: failed to JIT: %d\n", ret);
+ return ret;
+ }
+
+ knod_setup_bpf_prog(prog);
+
+ return 0;
+}
+
+static void knod_bpf_destroy_prog(struct bpf_prog *prog)
+{
+ struct knod_prog *knod_prog = prog->aux->offload->dev_priv;
+ struct knod_dev *knodev = knod_prog->knodev;
+ struct knod_bpf_priv *priv = knodev->accel->xdp.priv;
+
+ /*
+ * Normally the prog was already uninstalled (offload with a NULL prog
+ * flipped back to pass). Guard the abnormal path where the prog is
+ * freed while still tracked: flip to pass first so the worker stops
+ * dispatching this code. The compiled code lives in a kernel slot and
+ * is no longer read once we flip away; knod_prog is CPU-only IR the GPU
+ * never touches, so it is safe to free synchronously.
+ */
+ if (priv && READ_ONCE(priv->prog) == prog) {
+ WRITE_ONCE(priv->prog, NULL);
+ knod_bpf_reload_pass(knodev);
+ }
+ knod_prog_free(knod_prog);
+}
+
+static const struct bpf_prog_offload_ops knod_bpf_dev_ops = {
+ .insn_hook = knod_bpf_verify_insn,
+ .finalize = knod_bpf_finalize,
+ .prepare = knod_bpf_verifier_prep,
+ .translate = knod_bpf_translate,
+ .destroy = knod_bpf_destroy_prog,
+};
+
+static int knod_bpf_setup_prog_hw_checks(struct knod_dev *knodev,
+ struct netdev_bpf *bpf)
+{
+ if (!bpf->prog)
+ return 0;
+
+ return 0;
+}
+
+static int knod_bpf_map_get_next_key(struct bpf_offloaded_map *offmap,
+ void *key, void *next_key)
+{
+ unsigned int *nkey = (unsigned int *)next_key;
+ unsigned int *_key = (unsigned int *)key;
+
+ if (offmap->map.map_type == BPF_MAP_TYPE_ARRAY ||
+ offmap->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+ if (key == NULL)
+ *nkey = 0;
+ else
+ *nkey = (*_key) + 1;
+
+ if (*nkey >= offmap->map.max_entries)
+ return -ENOENT;
+ } else if (offmap->map.map_type == BPF_MAP_TYPE_HASH) {
+ if (key == NULL)
+ return knod_bpf_map_hash_get_first_key(offmap,
+ next_key);
+ else
+ return knod_bpf_map_hash_get_next_key(offmap, key,
+ nkey);
+ }
+
+ return 0;
+}
+
+static int knod_bpf_map_lookup_elem(struct bpf_offloaded_map *offmap,
+ void *key, void *value)
+{
+ return __knod_bpf_map_lookup_elem(offmap, key, value);
+}
+
+static int knod_bpf_map_update_elem(struct bpf_offloaded_map *offmap,
+ void *key, void *value, u64 flags)
+{
+ return __knod_bpf_map_update_elem(offmap, key, value, flags);
+}
+
+static int knod_bpf_map_delete_elem(struct bpf_offloaded_map *offmap, void *key)
+{
+ return __knod_bpf_map_delete_elem(offmap, key);
+}
+
+static const struct bpf_map_dev_ops knod_bpf_map_ops = {
+ .map_get_next_key = knod_bpf_map_get_next_key,
+ .map_lookup_elem = knod_bpf_map_lookup_elem,
+ .map_update_elem = knod_bpf_map_update_elem,
+ .map_delete_elem = knod_bpf_map_delete_elem,
+};
+
+static int knod_bpf_map_alloc(struct knod_dev *knodev,
+ struct bpf_offloaded_map *offmap)
+{
+ int err;
+
+ if (offmap->map.map_type != BPF_MAP_TYPE_ARRAY &&
+ offmap->map.map_type != BPF_MAP_TYPE_HASH &&
+ offmap->map.map_type != BPF_MAP_TYPE_PERCPU_ARRAY) {
+ knod_jit_dbg(" unsupported map type: %d\n",
+ offmap->map.map_type);
+ return -EOPNOTSUPP;
+ }
+
+ err = __knod_bpf_map_alloc(knodev, offmap);
+ if (err) {
+ knod_jit_dbg(" err = %d\n", err);
+ return err;
+ }
+
+ offmap->dev_ops = &knod_bpf_map_ops;
+ return 0;
+}
+
+static int knod_bpf_xdp_install(struct knod_dev *knodev,
+ struct netdev_bpf *bpf)
+{
+ int err = 0;
+
+ ASSERT_RTNL();
+
+ switch (bpf->command) {
+ case XDP_SETUP_PROG:
+ WARN_ON_ONCE(1);
+ break;
+ case XDP_SETUP_PROG_HW:
+ err = knod_bpf_setup_prog_hw_checks(knodev, bpf);
+ if (err)
+ return err;
+
+ err = knod_bpf_xdp_set_prog(knodev, bpf);
+ break;
+ case BPF_OFFLOAD_MAP_ALLOC:
+ err = knod_bpf_map_alloc(knodev, bpf->offmap);
+ break;
+ case BPF_OFFLOAD_MAP_FREE:
+ knod_bpf_map_free(knodev, bpf->offmap);
+ break;
+ default:
+ knod_jit_dbg(" bpf->command = %d\n", bpf->command);
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static inline int bpf_debugfs_insn(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct seq_file *m,
+ int insn_idx)
+{
+ struct amdgcn_insn *insn = &meta->amdgpu_insn[insn_idx];
+
+ if (priv->isa_version == 10)
+ gfx10_debugfs_insn(insn, m);
+ else if (priv->isa_version == 9)
+ gfx9_debugfs_insn(insn, m);
+ else
+ WARN_ON_ONCE(1);
+
+ return insn->size;
+}
+
+/*
+ * Print one disassembled GPU instruction at @offset, then drop the disasm's
+ * trailing newline and append @tag as a right-hand comment aligned to a fixed
+ * column (tabs expand to 8) so the origin lines up regardless of mnemonic
+ * width. Returns the instruction size in dwords.
+ */
+static int bpf_debugfs_insn_tagged(struct knod_bpf_priv *priv,
+ struct knod_insn_meta *meta,
+ struct seq_file *m, int j,
+ int offset, const char *tag)
+{
+ size_t col, p, line_start = m->count;
+ int sz;
+
+ seq_printf(m, "%d:\t", offset);
+ sz = bpf_debugfs_insn(priv, meta, m, j);
+ if (seq_has_overflowed(m))
+ return sz;
+
+ if (m->count > line_start && m->buf[m->count - 1] == '\n')
+ m->count--;
+ col = 0;
+ for (p = line_start; p < m->count; p++)
+ col = m->buf[p] == '\t' ? (col + 8) & ~(size_t)7 : col + 1;
+ while (col < 96) {
+ seq_putc(m, ' ');
+ col++;
+ }
+ seq_printf(m, " ; %s\n", tag);
+
+ return sz;
+}
+
+/*
+ * Print the instructions a second time, re-sorted into BPF source order so the
+ * dump reads like the program. The offsets are the real (reordered) GPU
+ * offsets, so they appear out of sequence - that shows where the reorder
+ * placed each block. Synthetic jumps have no BPF source insn and are last.
+ */
+static void bpf_insn_show_bpf_order(struct knod_bpf_priv *priv,
+ struct seq_file *m)
+{
+ struct knod_insn_meta *meta;
+ int idx, max_idx = -1, off2, i;
+ bool synth_hdr = false;
+ char tag[24];
+
+ seq_puts(m, "===[INSTRUCTIONS (bpf order)]===\n");
+
+ list_for_each_entry(meta, &priv->knod_prog->insns, l)
+ if (meta->bpf_insn_idx > max_idx)
+ max_idx = meta->bpf_insn_idx;
+
+ for (idx = 0; idx <= max_idx; idx++) {
+ list_for_each_entry(meta, &priv->knod_prog->insns, l) {
+ if (meta->bpf_insn_idx != idx || !meta->amdgpu_insns)
+ continue;
+ scnprintf(tag, sizeof(tag), "bpf#%d", idx);
+ off2 = meta->amdgpu_insn_idx;
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ off2 += bpf_debugfs_insn_tagged(priv, meta, m,
+ i, off2, tag);
+ }
+ }
+
+ list_for_each_entry(meta, &priv->knod_prog->insns, l) {
+ if (meta->bpf_insn_idx >= 0 || !meta->amdgpu_insns)
+ continue;
+ if (!synth_hdr) {
+ seq_puts(m, " [synthetic jumps]\n");
+ synth_hdr = true;
+ }
+ scnprintf(tag, sizeof(tag), "synth JA->#%d",
+ meta->jmp_dst ? meta->jmp_dst->bpf_insn_idx : -1);
+ off2 = meta->amdgpu_insn_idx;
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ off2 += bpf_debugfs_insn_tagged(priv, meta, m,
+ i, off2, tag);
+ }
+}
+
+static int bpf_insn_show(struct seq_file *m, void *v)
+{
+ struct knod_bpf_priv *priv = (struct knod_bpf_priv *)m->private;
+ struct knod_insn_meta *meta;
+ struct knod_prog *kp;
+ int i, insn_idx = 0;
+ bool have_prog;
+
+ if (!priv)
+ return 0;
+
+ /*
+ * Show the kernel the GPU actually dispatches: the XDP prog when one is
+ * attached, otherwise the retained pass-through kernel.
+ */
+ have_prog = READ_ONCE(priv->prog);
+ if (have_prog) {
+ kp = priv->knod_prog;
+ } else {
+ kp = priv->pass_knod_prog;
+ seq_puts(m, "no XDP prog attached -- pass-through kernel:\n");
+ }
+ if (!kp)
+ return 0;
+
+ seq_puts(m, "===[PROLOGUE]===\n");
+ list_for_each_entry(meta, &kp->pre_insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ seq_printf(m, "%d:\t", insn_idx);
+ insn_idx += bpf_debugfs_insn(priv, meta, m, i);
+ }
+ }
+
+ /* Emission (RPO) order - the actual GPU layout. Each line is tagged
+ * with its origin BPF insn since the reorder makes this differ from the
+ * BPF byte order; synthetic jumps inserted by the reorder have none.
+ */
+ seq_puts(m, "===[INSTRUCTIONS]===\n");
+ list_for_each_entry(meta, &kp->insns, l) {
+ char tag[24];
+
+ if (meta->bpf_insn_idx < 0)
+ scnprintf(tag, sizeof(tag), "synth JA->#%d",
+ meta->jmp_dst ?
+ meta->jmp_dst->bpf_insn_idx : -1);
+ else
+ scnprintf(tag, sizeof(tag), "bpf#%d",
+ meta->bpf_insn_idx);
+
+ for (i = 0; i < meta->amdgpu_insns; i++)
+ insn_idx += bpf_debugfs_insn_tagged(priv, meta, m, i,
+ insn_idx, tag);
+ }
+
+ seq_puts(m, "===[EPILOG]===\n");
+ list_for_each_entry(meta, &kp->post_insns, l) {
+ for (i = 0; i < meta->amdgpu_insns; i++) {
+ seq_printf(m, "%d:\t", insn_idx);
+ insn_idx += bpf_debugfs_insn(priv, meta, m, i);
+ }
+ }
+
+ if (have_prog)
+ bpf_insn_show_bpf_order(priv, m);
+
+ return 0;
+}
+
+static int bpf_insn_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bpf_insn_show, inode->i_private);
+}
+
+static const struct file_operations bpf_insn_fops = {
+ .owner = THIS_MODULE,
+ .open = bpf_insn_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const char *knod_branch_type_str(enum knod_branch_type type)
+{
+ switch (type) {
+ case KNOD_BR_NONE: return "NONE";
+ case KNOD_BR_DIRECT_EXIT: return "DIRECT_EXIT";
+ case KNOD_BR_FORWARD_SKIP: return "FORWARD_SKIP";
+ case KNOD_BR_FORWARD_GOTO: return "FORWARD_GOTO";
+ default: return "UNKNOWN";
+ }
+}
+
+static int bpf_cfg_show(struct seq_file *m, void *v)
+{
+ struct knod_bpf_priv *priv = (struct knod_bpf_priv *)m->private;
+ struct knod_insn_meta *meta;
+
+ if (!priv || !priv->knod_prog)
+ return 0;
+
+ seq_puts(m, "===[STRUCTURIZED CFG]===\n");
+ seq_printf(m, "exec_save_pairs_used: %u\n",
+ priv->knod_prog->exec_save_pairs_used);
+ seq_printf(m, "done_mask: s[%d:%d]\n",
+ priv->knod_prog->done_mask_sreg,
+ priv->knod_prog->done_mask_sreg + 1);
+ seq_printf(m, "initial_exec: s[%d:%d]\n",
+ priv->knod_prog->initial_exec_sreg,
+ priv->knod_prog->initial_exec_sreg + 1);
+ seq_puts(m, "\n");
+
+ seq_printf(m, "%-6s %-8s %-14s %-10s %-10s %-8s\n",
+ "bpf#", "opcode", "branch_type", "exec_save", "merge_pt",
+ "is_merge");
+
+ list_for_each_entry(meta, &priv->knod_prog->insns, l) {
+ bool is_jmp = is_mbpf_jmp(meta);
+
+ if (!is_jmp && !meta->is_merge_point)
+ continue;
+
+ seq_printf(m, "%-6d 0x%02x ",
+ meta->bpf_insn_idx, meta->insn.code);
+
+ if (meta->branch_type != KNOD_BR_NONE) {
+ seq_printf(m, "%-14s s[%d:%d] ",
+ knod_branch_type_str(meta->branch_type),
+ meta->exec_save_sreg,
+ meta->exec_save_sreg + 1);
+ if (meta->merge_point)
+ seq_printf(m, "%-10d ",
+ meta->merge_point->bpf_insn_idx);
+ else
+ seq_printf(m, "%-10s ", "-");
+ } else if (is_jmp) {
+ seq_printf(m, "%-14s %-10s %-10s ",
+ knod_branch_type_str(KNOD_BR_NONE),
+ "-", "-");
+ } else {
+ seq_printf(m, "%-14s %-10s %-10s ",
+ "", "", "");
+ }
+
+ if (meta->is_merge_point) {
+ struct knod_insn_meta *br;
+
+ seq_puts(m, "YES restore:");
+ list_for_each_entry(br, &priv->knod_prog->insns, l) {
+ if ((br->branch_type == KNOD_BR_FORWARD_SKIP ||
+ br->branch_type == KNOD_BR_FORWARD_GOTO) &&
+ br->merge_point == meta)
+ seq_printf(m, " s[%d:%d](from bpf#%d)",
+ br->exec_save_sreg,
+ br->exec_save_sreg + 1,
+ br->bpf_insn_idx);
+ }
+ seq_puts(m, "\n");
+ } else {
+ seq_puts(m, "\n");
+ }
+ }
+
+ /* Basic-block CFG from the reorder analysis (origin BPF order). */
+ if (priv->knod_prog->bbs) {
+ struct knod_bb *bbs = priv->knod_prog->bbs;
+ int nb = priv->knod_prog->n_bbs;
+ int k, s;
+
+ seq_printf(m, "\n[BASIC BLOCKS] %d blocks, %d back-edge(s) -> %s\n",
+ nb, priv->knod_prog->n_back,
+ priv->knod_prog->n_back ? "HAS LOOP" : "DAG");
+
+ for (k = 0; k < nb; k++) {
+ struct knod_bb *bb = &bbs[k];
+
+ seq_printf(m, "BB%-3d bpf#%d..#%d rpo=%d idom=#%d succ={",
+ k, bb->leader->bpf_insn_idx,
+ bb->last->bpf_insn_idx, bb->rpo,
+ bb->idom ?
+ bb->idom->leader->bpf_insn_idx : -1);
+ for (s = 0; s < bb->n_succ; s++)
+ seq_printf(m, "%s#%d", s ? "," : "",
+ bb->succ[s]->leader->bpf_insn_idx);
+ seq_printf(m, "}%s\n",
+ bb->loop_header ? " LOOP_HDR" : "");
+ }
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(bpf_cfg);
+
+static int knod_stats_show(struct seq_file *s, void *unused)
+{
+ struct knod_bpf_priv *priv = s->private;
+ u64 p50 = 0, p99 = 0, p999 = 0, acc;
+ struct knod_bpf_stats *stats;
+ u64 ccnt, dcnt;
+ int i;
+
+ stats = &priv->stats;
+ ccnt = stats->completion_count;
+ dcnt = stats->dispatch_count;
+ seq_printf(s, "enabled: %s\n",
+ static_branch_unlikely(&knod_stats_key) ? "yes" : "no");
+
+ seq_puts(s, "\n--- dispatch ---\n");
+ seq_printf(s, "count: %llu\n", dcnt);
+ seq_printf(s, "avg_ns: %llu\n",
+ dcnt ? stats->dispatch_total_ns / dcnt : 0);
+ seq_printf(s, "max_ns: %llu\n", stats->dispatch_max_ns);
+ seq_printf(s, "backlogs_avg: %llu\n",
+ dcnt ? stats->backlogs_total / dcnt : 0);
+
+ seq_puts(s, "\nbacklogs histogram:\n");
+ for (i = 0; i < KNOD_BL_BUCKETS; i++)
+ seq_printf(s, " %-10s %llu\n",
+ bl_labels[i], stats->backlogs_hist[i]);
+
+ seq_puts(s, "\n--- completion ---\n");
+ seq_printf(s, "count: %llu\n", ccnt);
+ seq_printf(s, "avg_ns: %llu\n",
+ ccnt ? stats->completion_total_ns / ccnt : 0);
+ seq_printf(s, "max_ns: %llu\n",
+ stats->completion_max_ns);
+
+ seq_puts(s, "\nlatency histogram:\n");
+ for (i = 0; i < KNOD_LAT_BUCKETS; i++)
+ seq_printf(s, " %-10s %llu\n",
+ lat_labels[i], stats->completion_hist[i]);
+
+ if (ccnt) {
+ acc = 0;
+ for (i = 0; i < KNOD_LAT_BUCKETS; i++) {
+ acc += stats->completion_hist[i];
+ if (!p50 && acc * 1000 >= ccnt * 500)
+ p50 = i;
+ if (!p99 && acc * 1000 >= ccnt * 990)
+ p99 = i;
+ if (!p999 && acc * 1000 >= ccnt * 999)
+ p999 = i;
+ }
+ seq_printf(s, "\np50: %s\n", lat_labels[p50]);
+ seq_printf(s, "p99: %s\n", lat_labels[p99]);
+ seq_printf(s, "p999: %s\n", lat_labels[p999]);
+ }
+
+ seq_puts(s, "\n--- decode_act ---\n");
+ seq_printf(s, "count: %llu\n", stats->decode_act_count);
+ seq_printf(s, "avg_ns: %llu\n",
+ stats->decode_act_count ?
+ stats->decode_act_total_ns / stats->decode_act_count : 0);
+ seq_printf(s, "max_ns: %llu\n", stats->decode_act_max_ns);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(knod_stats);
+
+static ssize_t knod_stats_enable_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ bool val;
+
+ if (kstrtobool_from_user(buf, count, &val))
+ return -EINVAL;
+
+ if (val)
+ static_branch_enable(&knod_stats_key);
+ else
+ static_branch_disable(&knod_stats_key);
+
+ return count;
+}
+
+static ssize_t knod_stats_enable_read(struct file *file,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char tmp[4];
+ int len;
+
+ len = scnprintf(tmp, sizeof(tmp), "%d\n",
+ static_branch_unlikely(&knod_stats_key) ? 1 : 0);
+
+ return simple_read_from_buffer(buf, count, ppos, tmp, len);
+}
+
+static const struct file_operations knod_stats_enable_fops = {
+ .owner = THIS_MODULE,
+ .read = knod_stats_enable_read,
+ .write = knod_stats_enable_write,
+};
+
+static ssize_t knod_stats_reset_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct knod_bpf_priv *priv = file->private_data;
+
+ memset(&priv->stats, 0, sizeof(priv->stats));
+ return count;
+}
+
+static const struct file_operations knod_stats_reset_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = knod_stats_reset_write,
+};
+
+static int knod_debugfs_init(struct knod_bpf_priv *priv)
+{
+ struct dentry *dir = priv->knod->debug_dir;
+ struct dentry *bpf_dir;
+
+ if (!dir)
+ return -ENOENT;
+
+ bpf_dir = debugfs_create_dir("bpf", dir);
+ if (IS_ERR(bpf_dir))
+ return PTR_ERR(bpf_dir);
+
+ priv->debug_dir = bpf_dir;
+
+ debugfs_create_file("insn", 0644,
+ bpf_dir, priv, &bpf_insn_fops);
+ debugfs_create_file("cfg", 0444, bpf_dir, priv,
+ &bpf_cfg_fops);
+ debugfs_create_file("stats", 0444, bpf_dir, priv,
+ &knod_stats_fops);
+ debugfs_create_file("stats_enable", 0644, bpf_dir, priv,
+ &knod_stats_enable_fops);
+ debugfs_create_file("stats_reset", 0200, bpf_dir, priv,
+ &knod_stats_reset_fops);
+ debugfs_create_bool("poll_mode", 0644, bpf_dir, &knod_bpf_poll_mode);
+ debugfs_create_u32("dispatch_delay_us", 0644, bpf_dir,
+ &knod_bpf_dispatch_delay_us);
+
+ return 0;
+}
+
+static void knod_debugfs_cleanup(struct knod_bpf_priv *priv)
+{
+ if (!priv->debug_dir)
+ return;
+
+ debugfs_remove_recursive(priv->debug_dir);
+ priv->debug_dir = NULL;
+}
+
+/* Called when attached or module loading time */
+/* attach: allocate the permanent per-attach priv struct. */
+static int knod_accel_xdp_init(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv;
+
+ priv = __knod_accel_xdp_init(accel, knodev);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+ return 0;
+}
+
+/* detach: free the permanent priv struct. */
+static void knod_accel_xdp_exit(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv = accel->xdp.priv;
+
+ __knod_accel_xdp_exit(accel, priv);
+}
+
+/*
+ * Feature select, phase B: register the BPF offload device so user XDP
+ * progs/maps can bind to it. Called after ->activate() set up the GPU
+ * buffers, while xdp_ops already points at the BPF ops.
+ */
+static int knod_bpf_offload_init(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv = accel->xdp.priv;
+ struct bpf_offload_dev *bpf_dev;
+ int err;
+
+ bpf_dev = bpf_offload_dev_create(&knod_bpf_dev_ops, priv);
+ err = PTR_ERR_OR_ZERO(bpf_dev);
+ if (err)
+ return err;
+ err = bpf_offload_dev_netdev_register(bpf_dev, knodev->netdev);
+ if (err) {
+ bpf_offload_dev_destroy(bpf_dev);
+ return err;
+ }
+ knod_debugfs_init(priv);
+ accel->xdp.bpf_dev = bpf_dev;
+ return 0;
+}
+
+/*
+ * Feature deselect, phase 1: unregister the BPF offload device. This
+ * force-frees any user XDP progs/maps still bound; the map-free ndo is
+ * routed back through accel_ops.xdp_ops->xdp_install, so the caller keeps
+ * xdp_ops pointed at the BPF ops until this returns.
+ */
+static void knod_bpf_offload_uninit(struct knod_dev *knodev)
+{
+ struct knod_accel *accel = knodev->accel;
+ struct knod_bpf_priv *priv = accel->xdp.priv;
+
+ knod_debugfs_cleanup(priv);
+ bpf_offload_dev_netdev_unregister(accel->xdp.bpf_dev, knodev->netdev);
+ bpf_offload_dev_destroy(accel->xdp.bpf_dev);
+ accel->xdp.bpf_dev = NULL;
+}
+
+struct knod_accel_xdp_ops accel_xdp_ops = {
+ /* attach/detach: permanent priv struct */
+ .init = &knod_accel_xdp_init,
+ .exit = &knod_accel_xdp_exit,
+ /* feature select: GPU compute buffers (A) + offload dev (B) */
+ .activate = &knod_bpf_activate,
+ .deactivate = &knod_bpf_deactivate,
+ .busy = &knod_bpf_busy,
+ .xdp_offload_init = &knod_bpf_offload_init,
+ .xdp_offload_uninit = &knod_bpf_offload_uninit,
+ /* interface up/down (or feature switch): worker + GPU drain */
+ .start = &knod_bpf_start,
+ .stop = &knod_bpf_stop,
+ .xdp_install = &knod_bpf_xdp_install,
+};
+
+static int __init knod_bpf_init_module(void)
+{
+ pr_info("knod-bpf module load\n");
+
+ /* knod_accel_xdp_register() already calls xdp_ops->init() on every
+ * registered accel, so a second per-accel init loop here would just
+ * re-create the "bpf" debugfs dir ("already exists" warning) and leak
+ * a duplicate offload dev.
+ */
+ knod_dev_lock();
+ knod_accel_xdp_register(&accel_xdp_ops);
+ knod_dev_unlock();
+
+ return 0;
+}
+late_initcall(knod_bpf_init_module);
+
+static void __exit knod_bpf_cleanup_module(void)
+{
+ struct knod_bpf_priv *priv, *tmp;
+ struct knod_accel *accel;
+
+ rtnl_lock();
+ knod_dev_lock();
+ list_for_each_entry_safe(priv, tmp, &priv_list, list) {
+ accel = priv->accel;
+ if (accel->knodev)
+ accel_xdp_ops.exit(accel->knodev);
+ }
+ knod_accel_xdp_unregister();
+ knod_dev_unlock();
+ rtnl_unlock();
+ pr_info("knod-bpf module unload\n");
+}
+module_exit(knod_bpf_cleanup_module);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Taehee Yoo <ap420073@gmail.com>");
+MODULE_DESCRIPTION("AMDGPU BPF offload backend");
+MODULE_VERSION("multi-aql");
diff --git a/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.h b/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.h
new file mode 100644
index 000000000000..de6df06c4f2f
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdkfd/knod/knod_bpf.h
@@ -0,0 +1,597 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
+ * Copyright (c) 2021 Hoyeon Lee <hoyeon.rhee@gmail.com>
+ */
+
+#ifndef KFD_BPF_H_INCLUDED
+#define KFD_BPF_H_INCLUDED
+
+#include <uapi/linux/bpf.h>
+#include <net/xdp.h>
+#include <net/netmem.h>
+#include <net/netlink.h>
+#include <net/page_pool/helpers.h>
+#include <net/ip.h>
+#include <net/net_namespace.h>
+#include <net/gro_cells.h>
+#include <net/rtnetlink.h>
+#include <net/protocol.h>
+#include <net/netns/generic.h>
+#include <net/xdp.h>
+#include <net/netdev_lock.h>
+#include <net/spsc_ring.h>
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/hash.h>
+#include <linux/netdevice.h>
+#include <linux/types.h>
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/rtnetlink.h>
+#include <linux/workqueue.h>
+#include <linux/ktime.h>
+#include <linux/static_key.h>
+#include "knod_amdgpu_insn.h"
+#include "../../../../../../net/core/devmem.h"
+#include "../amdgpu/amdgpu_vm.h"
+#include "knod_bpf.h"
+#include "kfd_knod.h"
+
+#define KNOD_BPF_BACKLOGS_MAX 65536
+#define KNOD_BPF_INFLIGHT 3 /* triple-buffered dispatches */
+#define KNOD_BPF_WORKGROUPS_DEFAULT 256
+#define KNOD_BPF_WORKGROUPS_MIN 64
+#define KNOD_BPF_WORKGROUPS_MAX 256
+#define KNOD_BPF_EXPIRE_DEFAULT 10
+#define KNOD_BPF_EXPIRE_MIN 1
+#define KNOD_BPF_EXPIRE_MAX 1000
+#define QUEUE_SIZE_DGPU 8192
+#define QUEUE_SIZE_IGPU 2048
+#define KNOD_MAX_BDS (KNOD_BPF_BACKLOGS_MAX / KNOD_SPSC_MAX)
+
+#define MAX_KEY_SIZE 64 /* 64Bytes */
+#define MAX_PACKET_CACHE 256 /* 256Bytes */
+
+#define knod_prog_first_meta(knod_prog) \
+ list_first_entry(&(knod_prog)->insns, struct knod_insn_meta, l)
+#define knod_prog_last_meta(knod_prog) \
+ list_last_entry(&(knod_prog)->insns, struct knod_insn_meta, l)
+#define knod_prog_pre_last_meta(knod_prog) \
+ list_last_entry(&(knod_prog)->pre_insns, struct knod_insn_meta, l)
+#define knod_meta_next(meta) list_next_entry(meta, l)
+#define knod_meta_prev(meta) list_prev_entry(meta, l)
+
+#define knod_for_each_insn_walk2(knod_prog, pos, next) \
+ for (pos = list_first_entry(&(knod_prog)->insns, typeof(*pos), l),\
+ next = list_next_entry(pos, l); \
+ &(knod_prog)->insns != &pos->l && \
+ &(knod_prog)->insns != &next->l; \
+ pos = knod_meta_next(pos), \
+ next = knod_meta_next(pos))
+
+#define knod_for_each_insn_walk3(knod_prog, pos, next, next2) \
+ for (pos = list_first_entry(&(knod_prog)->insns, typeof(*pos), l),\
+ next = list_next_entry(pos, l), \
+ next2 = list_next_entry(next, l); \
+ &(knod_prog)->insns != &pos->l && \
+ &(knod_prog)->insns != &next->l && \
+ &(knod_prog)->insns != &next2->l; \
+ pos = knod_meta_next(pos), \
+ next = knod_meta_next(pos), \
+ next2 = knod_meta_next(next))
+
+struct xdp_md_obj {
+ u64 data;
+ u64 data_end;
+ u64 data_meta;
+ /* Below access go through struct xdp_rxq_info */
+ u64 ingress_ifindex; /* rxq->dev->ifindex */
+ u64 rx_queue_index; /* rxq->queue_index */
+
+ u64 egress_ifindex; /* txq->dev->ifindex */
+ u64 retval;
+};
+
+struct knod_bpf_subparam_obj {
+ struct xdp_md_obj ctx;
+};
+
+#define KNOD_BPF_HASH_NEXT_END 0x7FFFFFFFU
+#define KNOD_BPF_HASH_NEXT_DELETED 0x80000000U
+#define KNOD_BPF_HASH_NEXT_MASK 0x7FFFFFFFU
+
+struct knod_bpf_hash_elem_obj {
+ unsigned int next;
+ unsigned char kv[];
+};
+
+struct knod_bpf_map_hash_meta_obj {
+ unsigned int n_buckets;
+ unsigned int hashrnd;
+ unsigned int cur;
+ unsigned int elem_size;
+ void *q;
+ void *elems;
+ unsigned int gc_count;
+ void *gc_list;
+};
+
+struct knod_bpf_map_array_meta_obj {
+ u32 per_instance_size; /* value_size * max_entries (one instance) */
+ u32 n_instances; /* 1 for ARRAY, num_possible_cpus for PERCPU */
+};
+
+union knod_bpf_map_meta_obj {
+ struct knod_bpf_map_hash_meta_obj hmeta;
+ struct knod_bpf_map_array_meta_obj ameta;
+};
+
+struct knod_bpf_map_obj {
+ enum bpf_map_type map_type;
+ unsigned int key_size;
+ unsigned int value_size;
+ unsigned int max_entries;
+ unsigned int id;
+ unsigned long map_extra; /* any per-map-type extra fields */
+ unsigned int map_flags;
+ union knod_bpf_map_meta_obj meta;
+ int mutex;
+ unsigned char bucket[];
+};
+
+struct knod_bpf_map {
+ struct list_head list;
+ struct knod_mem *mem, *queue_mem, *hash_elems_mem, *gc_mem;
+ /* ptr to mem_k->kaddr */
+ struct knod_bpf_map_obj *knod_map_obj;
+ struct bpf_offloaded_map *offmap;
+ struct knod_bpf_priv *priv;
+};
+
+struct knod_bpf_queue_desc {
+ u64 pool_gaddr; /* SPSC pool GTT address for this queue */
+ u64 base_gaddr; /* dma-buf base address for this queue */
+ u32 count; /* number of packets from this queue */
+ /* was start_idx; kept for global_load_dwordx4 layout */
+ u32 _pad;
+ u32 ring_start; /* acquired cursor at peek time */
+ u32 ring_mask; /* capacity - 1 */
+};
+
+struct knod_bpf_param {
+ u32 nr_backlogs;
+ u32 nr_queues;
+ u32 spsc_stride;
+ u32 _pad0;
+ u64 ktime_ns; /* snapshot of ktime_get_ns() at dispatch */
+ u32 pass_count[KNOD_SPSC_MAX]; /* per-queue atomic XDP_PASS counter */
+ /* per-queue GTT pass_meta_buf GPU addr */
+ u64 pass_meta_buf_gaddr[KNOD_SPSC_MAX];
+ struct knod_bpf_queue_desc queues[KNOD_SPSC_MAX];
+ /* backlog indices of PASS packets */
+ u16 pass_indices[KNOD_BPF_BACKLOGS_MAX];
+ struct knod_bpf_subparam_obj sub[KNOD_BPF_BACKLOGS_MAX];
+};
+
+struct knod_packet {
+ union {
+ netmem_ref netmem;
+ void *kaddr;
+ };
+ u16 len;
+ u16 off;
+};
+
+/* Single Queue Worok */
+struct knod_bpf_work_sq {
+ struct list_head list;
+ struct knod_mem *param;
+ int queue_idx[KNOD_SPSC_MAX];
+ struct spsc_bd *bds[KNOD_BPF_BACKLOGS_MAX];
+ ktime_t dispatch_time;
+ s64 sigval;
+ unsigned long expire;
+ int backlogs;
+};
+
+struct knod_bpf_reg_state {
+ struct bpf_reg_state reg;
+ int stack_off;
+ int packet_off;
+ bool var_off;
+};
+
+/* Structurized CFG branch types */
+enum knod_branch_type {
+ KNOD_BR_NONE = 0, /* not a branch */
+ /* backward jump to exit: inline retval + done_mask update */
+ KNOD_BR_DIRECT_EXIT,
+ KNOD_BR_FORWARD_SKIP, /* forward jump: skip region via EXEC mask */
+ KNOD_BR_FORWARD_GOTO, /* forward jump crossing other branch scopes */
+};
+
+#define KNOD_META_INSNS 1024
+#define AMDGPU_INSN_SKIP -1
+struct knod_insn_meta {
+ struct bpf_insn insn;
+ short bpf_insn_idx;
+
+ struct amdgcn_insn amdgpu_insn[KNOD_META_INSNS];
+ u32 amdgpu_insn_idx;
+ u32 amdgpu_insns;
+
+ union {
+ /* pointer ops (ld/st/xadd) */
+ struct {
+ struct bpf_reg_state ptr;
+ struct bpf_insn *paired_st;
+ s16 ldst_gather_len;
+ bool ptr_not_const;
+ struct {
+ s16 range_start;
+ s16 range_end;
+ bool do_init;
+ } pkt_cache;
+ bool xadd_over_16bit;
+ bool xadd_maybe_16bit;
+ };
+ /* jump */
+ struct {
+ struct knod_insn_meta *jmp_dst;
+ bool jump_neg_op;
+ u32 num_insns_after_br; /* only for BPF-to-BPF calls */
+ /* structurized CFG */
+ enum knod_branch_type branch_type;
+ /* SGPR index for s_and_saveexec_b64 */
+ u8 exec_save_sreg;
+ /* where EXEC is restored */
+ struct knod_insn_meta *merge_point;
+ };
+ /* function calls */
+ struct {
+ u32 func_id;
+ struct bpf_reg_state arg1;
+ struct knod_bpf_reg_state arg2;
+ };
+ /* We are interested in range info for operands of ALU
+ * operations. For example, shift amount, multiplicand and
+ * multiplier etc.
+ */
+ struct {
+ u64 umin_src;
+ u64 umax_src;
+ u64 umin_dst;
+ u64 umax_dst;
+ };
+ };
+
+ struct knod_bpf_reg_state sreg;
+ struct knod_bpf_reg_state dreg;
+ struct knod_bpf_reg_state kreg;
+ struct knod_bpf_reg_state vreg;
+ unsigned int off;
+ unsigned short flags;
+ unsigned short subprog_idx;
+ bool is_merge_point; /* EXEC restore target */
+ u8 restore_sreg; /* SGPR to restore EXEC from at merge point */
+ int linear_idx; /* position in the (reordered) emission list */
+ struct list_head l;
+};
+
+/* Encode one GPU instruction at @meta's running slot and advance it.
+ * @meta->amdgpu_insns is both the cursor during emission and the final
+ * instruction count afterwards. @fn names a knod_amdgpu_insn.h encoder
+ * without its emit_ prefix (e.g. v_add32 for emit_v_add32); the macro
+ * pastes it back, so call sites read knod_emit(priv, meta, v_add32, ...).
+ */
+#define knod_emit(priv, meta, fn, ...) \
+ do { \
+ struct knod_insn_meta *__m = (meta); \
+ \
+ emit_##fn((priv)->isa_version, \
+ &__m->amdgpu_insn[__m->amdgpu_insns], \
+ ##__VA_ARGS__); \
+ debug_insn((priv)->isa_version, \
+ &__m->amdgpu_insn[__m->amdgpu_insns]); \
+ __m->amdgpu_insns++; \
+ } while (0)
+
+/* JIT debug/error trace: auto-prefix with "knod_jit <func>:<line>".
+ * knod_jit_dbg() is a pr_debug(), so it is off by default and toggled
+ * with dynamic debug; knod_jit_err() always fires.
+ */
+#define knod_jit_dbg(fmt, ...) \
+ pr_debug("knod_jit %s:%d" fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define knod_jit_err(fmt, ...) \
+ pr_err("knod_jit %s:%d" fmt, __func__, __LINE__, ##__VA_ARGS__)
+
+#define BPF_SIZE_MASK 0x18
+
+struct knod_bb; /* basic-block CFG analysis (knod_bpf.c) */
+
+struct knod_prog {
+ struct knod *knod;
+ struct knod_dev *knodev;
+
+ u64 *prog;
+ unsigned int prog_len;
+ unsigned int __prog_alloc_len;
+ int max_stack_off;
+ int max_packet_off;
+
+ struct knod_insn_meta *meta;
+ enum bpf_prog_type type;
+ struct list_head pre_insns;
+ struct list_head post_insns;
+ struct list_head insns;
+ unsigned int n_insns;
+ unsigned int pre_n_insns;
+ int insn_idx;
+
+ /* Structurized CFG state */
+ /* GFX9: 34, GFX10: 32 (s[32:33] safe on RDNA) */
+ u8 done_mask_sreg;
+ u8 exec_save_base; /* GFX9: 36, GFX10: 34 */
+ /* in-bounds EXEC snapshot for verdict publish */
+ u8 initial_exec_sreg;
+ /* number of SGPR pairs allocated for EXEC saves */
+ u8 exec_save_pairs_used;
+ bool uses_adjust;
+
+ /* Basic-block CFG analysis, retained for the /bpf/cfg view. */
+ struct knod_bb *bbs;
+ int n_bbs;
+ int n_back;
+};
+
+#define KNOD_XDP_MEMCPY 0
+#define KNOD_XDP_PT 1
+#define KNOD_XDP_NETMEM 2
+#define KNOD_XDP_NONE 3
+#define KNOD_XDP_DEFAULT KNOD_XDP_PT
+
+#define KNOD_LAT_BUCKETS 10
+#define KNOD_BL_BUCKETS 8
+
+struct knod_bpf_stats {
+ u64 dispatch_total_ns;
+ u64 dispatch_count;
+ u64 dispatch_max_ns;
+
+ u64 completion_total_ns;
+ u64 completion_count;
+ u64 completion_max_ns;
+ u64 completion_hist[KNOD_LAT_BUCKETS];
+
+ u64 backlogs_total;
+ u64 backlogs_hist[KNOD_BL_BUCKETS];
+
+ u64 decode_act_total_ns;
+ u64 decode_act_count;
+ u64 decode_act_max_ns;
+};
+
+#define KNOD_PASS_SLOT_SIZE PAGE_SIZE
+
+/* pass_meta_buf slot header, written by the shader (offsetof used by the
+ * codegen). The host read path is gone now that PASS delivery goes via the
+ * NIC act handler + knod_d2h_copy; the shader still stores {len, src_addr}
+ * here pending removal of that store.
+ */
+struct knod_pass_slot_hdr {
+ u32 len; /* packet length */
+ u32 _pad;
+ u64 src_addr; /* VRAM source address (SDMA mode only) */
+};
+
+struct knod_bpf_priv {
+ struct list_head list;
+ struct knod *knod;
+ struct knod_accel *accel;
+ struct knod_dev *knodev;
+ struct net_device *dev;
+ struct knod_prog *knod_prog;
+ /* retained pass IR for debugfs insn dump */
+ struct knod_prog *pass_knod_prog;
+ struct bpf_prog *prog;
+ struct amdgpu_vm *vm;
+ u64 queue_base_gaddr[KNOD_SPSC_MAX];
+ struct knod_bpf_work_sq *inflight[KNOD_BPF_INFLIGHT];
+ unsigned int inflight_cnt;
+ ktime_t next_dispatch_time;
+ struct task_struct *worker_task;
+ struct list_head free_list_sqw;
+ struct mutex map_op_lock;
+ /* maps awaiting deferred free by the worker */
+ struct list_head dead_maps;
+ u32 maps_tick_skip;
+ struct dentry *debug_dir;
+ struct knod_bpf_stats stats;
+ void *prog_buf;
+ void *pass_prog_buf;
+ u32 pass_prog_size;
+ /* descriptor + live shader bytes per kernel slot */
+ u32 kernel_image_len[2];
+ /* knod->kernels[] slot the GPU dispatches */
+ int active_idx;
+ /* knod->kernels[] slot holding the pass kernel */
+ int pass_idx;
+ /*
+ * XDP_PASS shader-to-GTT metadata (shader-written; host read path
+ * removed). GTT metadata: shader-written headers.
+ */
+ struct knod_mem *pass_meta_buf;
+ u32 pass_pkts_per_queue; /* backlogs / nr_works */
+ /* batch size per queue */
+ int batch_size;
+ int nr_works;
+ int isa_version;
+ bool installing_kernel;
+ int start;
+};
+
+static inline u8 mbpf_class(const struct knod_insn_meta *meta)
+{
+ return BPF_CLASS(meta->insn.code);
+}
+
+static inline u8 mbpf_src(const struct knod_insn_meta *meta)
+{
+ return BPF_SRC(meta->insn.code);
+}
+
+static inline u8 mbpf_op(const struct knod_insn_meta *meta)
+{
+ return BPF_OP(meta->insn.code);
+}
+
+static inline u8 mbpf_mode(const struct knod_insn_meta *meta)
+{
+ return BPF_MODE(meta->insn.code);
+}
+
+static inline bool is_mbpf_alu(const struct knod_insn_meta *meta)
+{
+ return mbpf_class(meta) == BPF_ALU64 || mbpf_class(meta) == BPF_ALU;
+}
+
+static inline bool is_mbpf_load(const struct knod_insn_meta *meta)
+{
+ return (meta->insn.code & ~BPF_SIZE_MASK) == (BPF_LDX | BPF_MEM);
+}
+
+static inline bool is_mbpf_jmp32(const struct knod_insn_meta *meta)
+{
+ return mbpf_class(meta) == BPF_JMP32;
+}
+
+static inline bool is_mbpf_jmp64(const struct knod_insn_meta *meta)
+{
+ return mbpf_class(meta) == BPF_JMP;
+}
+
+static inline bool is_mbpf_jmp(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_jmp32(meta) || is_mbpf_jmp64(meta);
+}
+
+static inline bool is_mbpf_store(const struct knod_insn_meta *meta)
+{
+ return (meta->insn.code & ~BPF_SIZE_MASK) == (BPF_STX | BPF_MEM);
+}
+
+static inline bool is_mbpf_load_pkt(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_load(meta) && meta->ptr.type == PTR_TO_PACKET;
+}
+
+static inline bool is_mbpf_store_pkt(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_store(meta) && meta->ptr.type == PTR_TO_PACKET;
+}
+
+static inline bool is_mbpf_classic_load(const struct knod_insn_meta *meta)
+{
+ u8 code = meta->insn.code;
+
+ return BPF_CLASS(code) == BPF_LD &&
+ (BPF_MODE(code) == BPF_ABS || BPF_MODE(code) == BPF_IND);
+}
+
+static inline bool is_mbpf_classic_store(const struct knod_insn_meta *meta)
+{
+ u8 code = meta->insn.code;
+
+ return BPF_CLASS(code) == BPF_ST && BPF_MODE(code) == BPF_MEM;
+}
+
+static inline bool is_mbpf_classic_store_pkt(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_classic_store(meta) && meta->ptr.type == PTR_TO_PACKET;
+}
+
+static inline bool is_mbpf_atomic(const struct knod_insn_meta *meta)
+{
+ return (meta->insn.code & ~BPF_SIZE_MASK) == (BPF_STX | BPF_ATOMIC);
+}
+
+static inline bool is_mbpf_mul(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_alu(meta) && mbpf_op(meta) == BPF_MUL;
+}
+
+static inline bool is_mbpf_div(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_alu(meta) && mbpf_op(meta) == BPF_DIV;
+}
+
+static inline bool is_mbpf_mod(const struct knod_insn_meta *meta)
+{
+ return is_mbpf_alu(meta) && mbpf_op(meta) == BPF_MOD;
+}
+
+static inline bool is_mbpf_cond_jump(const struct knod_insn_meta *meta)
+{
+ u8 op;
+
+ if (is_mbpf_jmp32(meta))
+ return true;
+
+ if (!is_mbpf_jmp64(meta))
+ return false;
+
+ op = mbpf_op(meta);
+ return op != BPF_JA && op != BPF_EXIT && op != BPF_CALL;
+}
+
+static inline bool is_mbpf_helper_call(const struct knod_insn_meta *meta)
+{
+ struct bpf_insn insn = meta->insn;
+
+ return insn.code == (BPF_JMP | BPF_CALL) &&
+ insn.src_reg != BPF_PSEUDO_CALL;
+}
+
+static inline bool is_mbpf_pseudo_call(const struct knod_insn_meta *meta)
+{
+ struct bpf_insn insn = meta->insn;
+
+ return insn.code == (BPF_JMP | BPF_CALL) &&
+ insn.src_reg == BPF_PSEUDO_CALL;
+}
+
+static inline bool is_mbpf_map_call(const struct knod_insn_meta *meta)
+{
+ struct bpf_insn insn = meta->insn;
+
+ return insn.code == (BPF_JMP | BPF_CALL) && insn.imm <= 3;
+}
+
+#define STACK_FRAME_ALIGN 64
+
+#define FLAG_INSN_IS_JUMP_DST BIT(0)
+#define FLAG_INSN_IS_SUBPROG_START BIT(1)
+#define FLAG_INSN_PTR_CALLER_STACK_FRAME BIT(2)
+/* Instruction is pointless, noop even on its own */
+#define FLAG_INSN_SKIP_NOOP BIT(3)
+/* Instruction is optimized out based on preceding instructions */
+#define FLAG_INSN_SKIP_PREC_DEPENDENT BIT(4)
+/* Instruction is optimized by the verifier */
+#define FLAG_INSN_SKIP_VERIFIER_OPT BIT(5)
+/* Instruction needs to zero extend to high 32-bit */
+#define FLAG_INSN_DO_ZEXT BIT(6)
+
+#define FLAG_INSN_SKIP_MASK (FLAG_INSN_SKIP_NOOP | \
+ FLAG_INSN_SKIP_PREC_DEPENDENT | \
+ FLAG_INSN_SKIP_VERIFIER_OPT)
+#endif
--
2.43.0
next prev parent reply other threads:[~2026-07-19 18:01 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-19 17:58 [RFC PATCH net-next 00/13] net: knod: in-kernel network offload device Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 01/13] net: knod: add uapi and core headers Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 02/13] net: devmem: extend memory provider for knod Taehee Yoo
2026-07-20 19:43 ` Mina Almasry
2026-07-19 17:58 ` [RFC PATCH net-next 03/13] net: core: add XDP_MODE_HW offload hook " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 04/13] net: knod: add offload device core and control plane Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 05/13] bpf: offload: allow PERCPU_ARRAY maps for offloaded programs Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 06/13] drm/amdkfd: prepare kfd core for the knod provider Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 07/13] drm/amdkfd: add knod provider core Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 08/13] drm/amdkfd: add GPU instruction emitter and disassembler Taehee Yoo
2026-07-20 20:05 ` Natalie Vock
2026-07-20 20:53 ` Andrew Lunn
2026-07-19 17:58 ` Taehee Yoo [this message]
2026-07-19 17:58 ` [RFC PATCH net-next 10/13] net/mlx5e: add knod XDP offload support Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 11/13] bnxt_en: " Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 12/13] selftests: drivers/net: add knod tests Taehee Yoo
2026-07-19 17:58 ` [RFC PATCH net-next 13/13] drm/amdkfd: add IPsec full-packet offload Taehee Yoo
2026-07-20 19:18 ` [RFC PATCH net-next 00/13] net: knod: in-kernel network offload device Mina Almasry
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260719175857.4071636-10-ap420073@gmail.com \
--to=ap420073@gmail.com \
--cc=Felix.Kuehling@amd.com \
--cc=airlied@gmail.com \
--cc=alexander.deucher@amd.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=andrew+netdev@lunn.ch \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=christian.koenig@amd.com \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=emil@etsalapatis.com \
--cc=hawk@kernel.org \
--cc=horms@kernel.org \
--cc=hoyeon.rhee@gmail.com \
--cc=ilias.apalodimas@linaro.org \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=justinstitt@google.com \
--cc=kees@kernel.org \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linaro-mm-sig@lists.linaro.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=martin.lau@linux.dev \
--cc=mbloch@nvidia.com \
--cc=memxor@gmail.com \
--cc=michael.chan@broadcom.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pavan.chebbi@broadcom.com \
--cc=saeedm@nvidia.com \
--cc=sdf@fomichev.me \
--cc=shuah@kernel.org \
--cc=simona@ffwll.ch \
--cc=song@kernel.org \
--cc=sumit.semwal@linaro.org \
--cc=tariqt@nvidia.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.