summaryrefslogtreecommitdiffstats
path: root/kernel/tests
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 12:37:46 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 12:37:46 -0400
commit5d4d26f8e773f2871c1b5c9266b82495d390970e (patch)
tree544214bfabdcd76ee0b75de879e3798c6ab06334 /kernel/tests
parent85084a8fc6a95dd88f81bbec31cffa6b4aba56b7 (diff)
downloadopen-iov-5d4d26f8e773f2871c1b5c9266b82495d390970e.tar.gz
open-iov-5d4d26f8e773f2871c1b5c9266b82495d390970e.zip
kernel: Fix guest-hang root cause (VFIO mmap garbage PFN) + rebuild mdev remove path
VFIO mmap (main.c): virt_to_phys() was used on vmalloc-family addresses (vzalloc'd BAR0 shadow, ioremap'd BAR2/3), producing garbage PFNs. Guest BAR0 access then hit a KVM make_spte reserved-bits WARN and an EPT-violation livelock, hard-wedging the guest on every nvidia driver probe (confirmed via QEMU GDB stub). BAR0 now maps per-page via vmalloc_to_pfn(); BAR2/3/4/5 map bars[N].gpa directly (like BAR1); config space uses new nv_vgpu_mmap_virt_to_pfn() helper. mdev remove path (main.c): old path never list_del'd the device (available_instances leaked), never ran cleanup (leaked gsp_emu kthread), never unregistered VFIO (leaked /dev/vfio/N + iommu group), never put pdev, never freed the struct. New kvmgt-style path: cancel init_work -> wait fds -> vfio_unregister_group_dev -> cleanup work -> list_del -> pci_dev_put -> vfio_put_device. gsp_rpc_client.c: fix dma_map_page dma_addr_t->int truncation in gsp_rpc_alloc_emul (corrupts >=4GB IOVAs); export gsp_client_init_emulation. gsp_emulation.c: export gsp_emu_process_rpc and gsp_emu_get_stats. main.c: remove wrong MODULE_SOFTDEP("pre: nvidia") - module has zero nvidia imports, loads standalone, and must load BEFORE nvidia.ko (it exports nvidia_vgpu_vfio_* for nvidia.ko to import). nvidia-vgpu-vfio.h: NULL-safe nv_vgpu_{dbg,info,warn,err} macros. tests/gsp_emulation_test.c: rewritten against current API - drives gsp_emu_process_rpc() with crafted shared-memory messages (protocol validation, SCHEDULE/YIELD/NOTIFY/DEBUG dispatch, stress). Builds with KBUILD_EXTRA_SYMBOLS; depends: open-iov-vfio. stack_manager.sh: vfio_mdev doesn't exist on >=6.4 (probe per-module); load-nvidia detects foreign nvidia via /sys/module/nvidia/version and errors with guidance instead of silently skipping.
Diffstat (limited to 'kernel/tests')
-rw-r--r--kernel/tests/Makefile8
-rw-r--r--kernel/tests/gsp_emulation_test.c933
2 files changed, 296 insertions, 645 deletions
diff --git a/kernel/tests/Makefile b/kernel/tests/Makefile
index 7374f8b..56c819a 100644
--- a/kernel/tests/Makefile
+++ b/kernel/tests/Makefile
@@ -2,6 +2,11 @@ obj-m += gsp_emulation_test.o
ccflags-y := -I$(PWD)/../ -I$(PWD)/../include
+# Resolve open-iov-vfio.ko exports (gsp_emu_process_rpc,
+# gsp_client_init_emulation, gsp_rpc_emulation_set, ...).
+# Requires 'make kernel' (or 'make -C kernel') to have run first.
+KBUILD_EXTRA_SYMBOLS := $(abspath $(PWD)/../Module.symvers)
+
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
@@ -11,8 +16,9 @@ all:
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
+# Requires open-iov-vfio.ko loaded first (symbol dependency)
install:
sudo insmod gsp_emulation_test.ko
uninstall:
- sudo rmmod gsp_emulation_test \ No newline at end of file
+ sudo rmmod gsp_emulation_test
diff --git a/kernel/tests/gsp_emulation_test.c b/kernel/tests/gsp_emulation_test.c
index 64b3a25..d63fcdb 100644
--- a/kernel/tests/gsp_emulation_test.c
+++ b/kernel/tests/gsp_emulation_test.c
@@ -1,705 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GSP Emulation Unit Tests
+ *
+ * Drives gsp_emu_process_rpc() end-to-end with crafted shared-memory
+ * messages and validates dispatch, response layout, and protocol handling.
+ *
+ * Standalone-testable RPC paths (no physical GPU dependency):
+ * SCHEDULE (emulated workqueue), YIELD (no-op), NOTIFY (log-only),
+ * DEBUG (log-only), protocol validation (seq_id, unknown type).
+ *
+ * NOT covered here: ALLOC/FREE/MAP/UNMAP/PIN/UNPIN emulation is coupled
+ * to a physical device (client->dev->driver_data -> vgpu -> pdev +
+ * dma_map_page), and BAR_TRAP/MIGRATE submit raw RPCs to real firmware.
+ * Those paths are exercised by the live QEMU guest integration test
+ * (scripts/test_vgpu_stack.sh).
+ *
+ * Requires open-iov-vfio.ko loaded first (links against its exports).
+ */
+
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
-#include <linux/kthread.h>
-#include <linux/delay.h>
-#include <linux/mm.h>
-#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
-#include <linux/spinlock.h>
-#include <linux/ktime.h>
-#include <linux/atomic.h>
#include "gsp_emulation.h"
#include "gsp_rpc_client.h"
-#include "memory.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Open-IOV");
MODULE_DESCRIPTION("GSP Emulation Unit Tests");
+MODULE_SOFTDEP("pre: open_iov_vfio");
+
+#define SHMEM_SIZE 4096
+#define RSP_OFFSET 0x80
+#define UNKNOWN_TYPE 0x77 /* not a valid GSP_RPC_* type */
+
+static struct gsp_emu_context *test_ctx;
+static struct gsp_client *test_client;
+static void *test_shmem;
+static u32 test_seq;
+
+static int test_failures;
+
+#define T_CHECK(cond, name, ...) do { \
+ if (!(cond)) { \
+ pr_err("GSP_EMU_TEST: FAIL %s: " #name ": " __VA_ARGS__); \
+ test_failures++; \
+ return -1; \
+ } \
+} while (0)
+
+/*
+ * run_rpc - place a request in fake shared memory, run one dispatch,
+ * and copy back the response. Returns the dispatch return code.
+ */
+static int run_rpc(u32 type, u32 subcmd, const void *payload, size_t plen,
+ struct gsp_rpc_response *rsp)
+{
+ struct gsp_rpc_msg *msg = test_shmem;
+ struct gsp_rpc_response *r = test_shmem + RSP_OFFSET;
+ int ret;
+
+ memset(test_shmem, 0, SHMEM_SIZE);
+ msg->type = type;
+ msg->subcmd = subcmd;
+ if (payload && plen)
+ memcpy(msg->payload, payload, min(plen, sizeof(msg->payload)));
+ msg->seq_id = ++test_seq;
+
+ ret = gsp_emu_process_rpc(test_ctx);
+
+ memcpy(rsp, r, sizeof(*rsp));
+ if (rsp->seq_id != msg->seq_id) {
+ pr_err("GSP_EMU_TEST: response seq mismatch (got %u, want %u)\n",
+ rsp->seq_id, msg->seq_id);
+ return -EBADMSG;
+ }
+ return ret;
+}
-#define TEST_ALLOC_SIZE (4096)
-#define TEST_MAP_SIZE (4096)
-#define TEST_PIN_PAGES (1)
+/* seq_id == 0 must be rejected by gsp_emu_process_rpc itself */
+static int test_seq_id_zero(void)
+{
+ struct gsp_rpc_msg *msg = test_shmem;
+ int ret;
-static struct gsp_emu_context *test_ctx = NULL;
-static struct gsp_client *test_client = NULL;
-static void *test_shared_mem = NULL;
-static dma_addr_t test_shared_dma = 0;
-static struct page **test_pages = NULL;
-static int test_pages_count = 0;
+ pr_info("GSP_EMU_TEST: Testing seq_id=0 rejection...\n");
-static int test_seq_id = 1;
+ memset(test_shmem, 0, SHMEM_SIZE);
+ msg->type = GSP_RPC_YIELD;
+ msg->seq_id = 0;
-static int get_next_seq_id(void)
-{
- return test_seq_id++;
-}
+ ret = gsp_emu_process_rpc(test_ctx);
+ T_CHECK(ret == -EINVAL, seq_zero, "expected -EINVAL, got %d\n", ret);
-static void init_test_context(void)
-{
- test_shared_mem = dma_alloc_coherent(NULL, 4096, &test_shared_dma, GFP_KERNEL);
- if (!test_shared_mem) {
- pr_err("GSP_EMU_TEST: Failed to allocate shared memory\n");
- return;
- }
- memset(test_shared_mem, 0, 4096);
-
- test_pages_count = TEST_ALLOC_SIZE / PAGE_SIZE;
- test_pages = kmalloc_array(test_pages_count, sizeof(struct page *), GFP_KERNEL);
- if (!test_pages) {
- pr_err("GSP_EMU_TEST: Failed to allocate page array\n");
- return;
- }
- for (int i = 0; i < test_pages_count; i++) {
- test_pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
- if (!test_pages[i]) {
- pr_err("GSP_EMU_TEST: Failed to allocate page %d\n", i);
- test_pages_count = i;
- break;
- }
- }
-
- test_client = gsp_client_init(NULL);
- if (!test_client) {
- pr_err("GSP_EMU_TEST: Failed to init gsp_client\n");
- return;
- }
-
- test_ctx = kzalloc(sizeof(struct gsp_emu_context), GFP_KERNEL);
- if (!test_ctx) {
- pr_err("GSP_EMU_TEST: Failed to allocate context\n");
- return;
- }
-
- test_ctx->vgpu = NULL;
- test_ctx->bar0_vaddr = test_shared_mem;
- test_ctx->doorbell_reg = test_shared_mem + 0x110C00;
- test_ctx->shared_mem = test_shared_mem + 0x1000;
- test_ctx->rpc_client = test_client;
- spin_lock_init(&test_ctx->lock);
- test_ctx->running = true;
- atomic_set(&test_ctx->processed_rpcs, 0);
- atomic_set(&test_ctx->failed_rpcs, 0);
- test_ctx->total_latency_ns = 0;
-
- pr_info("GSP_EMU_TEST: Test context initialized\n");
+ pr_info("GSP_EMU_TEST: seq_id=0 correctly rejected\n");
+ return 0;
}
-static void cleanup_test_context(void)
+/* uninitialized context must be rejected */
+static int test_uninitialized_ctx(void)
{
- if (test_ctx) {
- test_ctx->running = false;
- kfree(test_ctx);
- test_ctx = NULL;
- }
- if (test_client) {
- gsp_client_fini(test_client);
- test_client = NULL;
- }
- if (test_pages) {
- for (int i = 0; i < test_pages_count; i++) {
- if (test_pages[i])
- __free_page(test_pages[i]);
- }
- kfree(test_pages);
- test_pages = NULL;
- }
- if (test_shared_mem) {
- dma_free_coherent(NULL, 4096, test_shared_mem, test_shared_dma);
- test_shared_mem = NULL;
- }
- pr_info("GSP_EMU_TEST: Test context cleaned up\n");
-}
+ struct gsp_emu_context *bad;
+ int ret;
-static int test_alloc_free(void)
-{
- struct gsp_rpc_alloc_request req = {0};
- struct gsp_rpc_alloc_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing ALLOC/FREE...\n");
-
- req.header.type = GSP_RPC_ALLOC;
- req.header.seq_id = get_next_seq_id();
- req.header.flags = 0;
- req.size = TEST_ALLOC_SIZE;
- req.alignment = PAGE_SIZE;
- req.flags = GSP_ALLOC_FLAG_VRAM;
-
- ret = gsp_rpc_alloc_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: ALLOC failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: ALLOC returned status=%d\n", resp.status);
- return -1;
- }
- if (resp.handle == 0) {
- pr_err("GSP_EMU_TEST: ALLOC returned zero handle\n");
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: ALLOC succeeded, handle=0x%llx\n", resp.handle);
-
- struct gsp_rpc_free_request free_req = {0};
- struct gsp_rpc_free_response free_resp = {0};
-
- free_req.header.type = GSP_RPC_FREE;
- free_req.header.seq_id = get_next_seq_id();
- free_req.handle = resp.handle;
-
- ret = gsp_rpc_free_emulation(test_client, &free_req, &free_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: FREE failed with ret=%d\n", ret);
- return -1;
- }
- if (free_resp.status != 0) {
- pr_err("GSP_EMU_TEST: FREE returned status=%d\n", free_resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: FREE succeeded\n");
- return 0;
-}
+ pr_info("GSP_EMU_TEST: Testing uninitialized ctx rejection...\n");
-static int test_map_unmap(void)
-{
- struct gsp_rpc_alloc_request alloc_req = {0};
- struct gsp_rpc_alloc_response alloc_resp = {0};
- struct gsp_rpc_map_request map_req = {0};
- struct gsp_rpc_map_response map_resp = {0};
- struct gsp_rpc_unmap_request unmap_req = {0};
- struct gsp_rpc_unmap_response unmap_resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing MAP/UNMAP...\n");
-
- alloc_req.header.type = GSP_RPC_ALLOC;
- alloc_req.header.seq_id = get_next_seq_id();
- alloc_req.size = TEST_MAP_SIZE;
- alloc_req.alignment = PAGE_SIZE;
- alloc_req.flags = GSP_ALLOC_FLAG_VRAM;
-
- ret = gsp_rpc_alloc_emulation(test_client, &alloc_req, &alloc_resp);
- if (ret != 0 || alloc_resp.status != 0) {
- pr_err("GSP_EMU_TEST: Pre-alloc for MAP failed\n");
- return -1;
- }
-
- map_req.header.type = GSP_RPC_MAP;
- map_req.header.seq_id = get_next_seq_id();
- map_req.handle = alloc_resp.handle;
- map_req.offset = 0;
- map_req.size = TEST_MAP_SIZE;
- map_req.gpu_va = 0x100000000ULL;
- map_req.flags = GSP_MAP_FLAG_READ | GSP_MAP_FLAG_WRITE;
-
- ret = gsp_rpc_map_emulation(test_client, &map_req, &map_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MAP failed with ret=%d\n", ret);
- return -1;
- }
- if (map_resp.status != 0) {
- pr_err("GSP_EMU_TEST: MAP returned status=%d\n", map_resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: MAP succeeded, gpu_va=0x%llx\n", map_resp.gpu_va);
-
- unmap_req.header.type = GSP_RPC_UNMAP;
- unmap_req.header.seq_id = get_next_seq_id();
- unmap_req.gpu_va = map_resp.gpu_va;
- unmap_req.size = TEST_MAP_SIZE;
-
- ret = gsp_rpc_unmap_emulation(test_client, &unmap_req, &unmap_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: UNMAP failed with ret=%d\n", ret);
- return -1;
- }
- if (unmap_resp.status != 0) {
- pr_err("GSP_EMU_TEST: UNMAP returned status=%d\n", unmap_resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: UNMAP succeeded\n");
-
- struct gsp_rpc_free_request free_req = {0};
- struct gsp_rpc_free_response free_resp = {0};
- free_req.header.type = GSP_RPC_FREE;
- free_req.header.seq_id = get_next_seq_id();
- free_req.handle = alloc_resp.handle;
- gsp_rpc_free_emulation(test_client, &free_req, &free_resp);
-
- return 0;
+ bad = kzalloc(sizeof(*bad), GFP_KERNEL);
+ T_CHECK(bad != NULL, alloc, "out of memory\n");
+ bad->initialized = false;
+
+ ret = gsp_emu_process_rpc(bad);
+ kfree(bad);
+ T_CHECK(ret == -ENODEV, uninit, "expected -ENODEV, got %d\n", ret);
+
+ pr_info("GSP_EMU_TEST: uninitialized ctx correctly rejected\n");
+ return 0;
}
-static int test_pin_unpin(void)
+/* unknown RPC type must be rejected with -ENOTSUPP in ret and status */
+static int test_unknown_type(void)
{
- struct gsp_rpc_alloc_request alloc_req = {0};
- struct gsp_rpc_alloc_response alloc_resp = {0};
- struct gsp_rpc_map_request map_req = {0};
- struct gsp_rpc_map_response map_resp = {0};
- struct gsp_rpc_pin_request pin_req = {0};
- struct gsp_rpc_pin_response pin_resp = {0};
- struct gsp_rpc_unpin_request unpin_req = {0};
- struct gsp_rpc_unpin_response unpin_resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing PIN/UNPIN...\n");
-
- alloc_req.header.type = GSP_RPC_ALLOC;
- alloc_req.header.seq_id = get_next_seq_id();
- alloc_req.size = TEST_ALLOC_SIZE;
- alloc_req.alignment = PAGE_SIZE;
- alloc_req.flags = GSP_ALLOC_FLAG_VRAM;
-
- ret = gsp_rpc_alloc_emulation(test_client, &alloc_req, &alloc_resp);
- if (ret != 0 || alloc_resp.status != 0) {
- pr_err("GSP_EMU_TEST: Pre-alloc for PIN failed\n");
- return -1;
- }
-
- map_req.header.type = GSP_RPC_MAP;
- map_req.header.seq_id = get_next_seq_id();
- map_req.handle = alloc_resp.handle;
- map_req.offset = 0;
- map_req.size = TEST_ALLOC_SIZE;
- map_req.gpu_va = 0x200000000ULL;
- map_req.flags = GSP_MAP_FLAG_READ | GSP_MAP_FLAG_WRITE;
-
- ret = gsp_rpc_map_emulation(test_client, &map_req, &map_resp);
- if (ret != 0 || map_resp.status != 0) {
- pr_err("GSP_EMU_TEST: Pre-map for PIN failed\n");
- return -1;
- }
-
- pin_req.header.type = GSP_RPC_PIN;
- pin_req.header.seq_id = get_next_seq_id();
- pin_req.gpu_va = map_resp.gpu_va;
- pin_req.size = TEST_ALLOC_SIZE;
- pin_req.flags = GSP_PIN_FLAG_DMA;
-
- ret = gsp_rpc_pin_emulation(test_client, &pin_req, &pin_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: PIN failed with ret=%d\n", ret);
- return -1;
- }
- if (pin_resp.status != 0) {
- pr_err("GSP_EMU_TEST: PIN returned status=%d\n", pin_resp.status);
- return -1;
- }
- if (pin_resp.dma_addr == 0) {
- pr_err("GSP_EMU_TEST: PIN returned zero DMA address\n");
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: PIN succeeded, dma_addr=0x%llx\n", pin_resp.dma_addr);
-
- unpin_req.header.type = GSP_RPC_UNPIN;
- unpin_req.header.seq_id = get_next_seq_id();
- unpin_req.gpu_va = map_resp.gpu_va;
- unpin_req.size = TEST_ALLOC_SIZE;
-
- ret = gsp_rpc_unpin_emulation(test_client, &unpin_req, &unpin_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: UNPIN failed with ret=%d\n", ret);
- return -1;
- }
- if (unpin_resp.status != 0) {
- pr_err("GSP_EMU_TEST: UNPIN returned status=%d\n", unpin_resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: UNPIN succeeded\n");
-
- struct gsp_rpc_unmap_request unmap_req = {0};
- struct gsp_rpc_unmap_response unmap_resp = {0};
- unmap_req.header.type = GSP_RPC_UNMAP;
- unmap_req.header.seq_id = get_next_seq_id();
- unmap_req.gpu_va = map_resp.gpu_va;
- unmap_req.size = TEST_ALLOC_SIZE;
- gsp_rpc_unmap_emulation(test_client, &unmap_req, &unmap_resp);
-
- struct gsp_rpc_free_request free_req = {0};
- struct gsp_rpc_free_response free_resp = {0};
- free_req.header.type = GSP_RPC_FREE;
- free_req.header.seq_id = get_next_seq_id();
- free_req.handle = alloc_resp.handle;
- gsp_rpc_free_emulation(test_client, &free_req, &free_resp);
-
- return 0;
+ struct gsp_rpc_response rsp;
+ int ret;
+
+ pr_info("GSP_EMU_TEST: Testing unknown type rejection...\n");
+
+ ret = run_rpc(UNKNOWN_TYPE, 0, NULL, 0, &rsp);
+ T_CHECK(ret == -ENOTSUPP, unkret, "expected -ENOTSUPP, got %d\n", ret);
+ T_CHECK((s32)rsp.status == -ENOTSUPP, unkstatus,
+ "expected status -ENOTSUPP, got %d\n", (s32)rsp.status);
+ T_CHECK(rsp.type == UNKNOWN_TYPE, unktype,
+ "expected type echo 0x%x, got 0x%x\n", UNKNOWN_TYPE, rsp.type);
+
+ pr_info("GSP_EMU_TEST: unknown type correctly rejected\n");
+ return 0;
}
-static int test_schedule_yield(void)
+static int test_schedule(void)
{
- struct gsp_rpc_schedule_request req = {0};
- struct gsp_rpc_schedule_response resp = {0};
- struct gsp_rpc_yield_request yield_req = {0};
- struct gsp_rpc_yield_response yield_resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing SCHEDULE/YIELD...\n");
-
- req.header.type = GSP_RPC_SCHEDULE;
- req.header.seq_id = get_next_seq_id();
- req.work_id = 0x1234;
- req.priority = 10;
- req.flags = 0;
-
- ret = gsp_rpc_schedule_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: SCHEDULE failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: SCHEDULE returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: SCHEDULE succeeded\n");
-
- yield_req.header.type = GSP_RPC_YIELD;
- yield_req.header.seq_id = get_next_seq_id();
- yield_req.work_id = 0x1234;
-
- ret = gsp_rpc_yield_emulation(test_client, &yield_req, &yield_resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: YIELD failed with ret=%d\n", ret);
- return -1;
- }
- if (yield_resp.status != 0) {
- pr_err("GSP_EMU_TEST: YIELD returned status=%d\n", yield_resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: YIELD succeeded\n");
- return 0;
+ struct gsp_sched_req sreq = {0};
+ struct gsp_rpc_response rsp;
+ int ret;
+
+ pr_info("GSP_EMU_TEST: Testing SCHEDULE...\n");
+
+ sreq.work_handle = 0x1234;
+ sreq.callback = 0; /* emulation worker never invokes callback */
+ sreq.arg = 0;
+ sreq.priority = 10;
+ sreq.flags = 0;
+
+ ret = run_rpc(GSP_RPC_SCHEDULE, 0, &sreq, sizeof(sreq), &rsp);
+ T_CHECK(ret == 0, schedret, "dispatch returned %d\n", ret);
+ T_CHECK((s32)rsp.status == 0, schedstatus,
+ "status %d\n", (s32)rsp.status);
+ T_CHECK(rsp.type == GSP_RPC_SCHEDULE, schedtype,
+ "type echo 0x%x\n", rsp.type);
+
+ pr_info("GSP_EMU_TEST: SCHEDULE succeeded\n");
+ return 0;
}
-static int test_bar_trap(void)
+static int test_yield(void)
{
- struct gsp_rpc_bar_trap_request req = {0};
- struct gsp_rpc_bar_trap_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing BAR_TRAP...\n");
-
- req.header.type = GSP_RPC_BAR_TRAP;
- req.header.seq_id = get_next_seq_id();
- req.bar_index = 0;
- req.offset = 0x1000;
- req.size = 4;
- req.is_write = false;
- req.value = 0;
-
- ret = gsp_rpc_bar_trap_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: BAR_TRAP read failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: BAR_TRAP read returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: BAR_TRAP read succeeded, value=0x%llx\n", resp.value);
-
- req.header.seq_id = get_next_seq_id();
- req.is_write = true;
- req.value = 0xDEADBEEF;
-
- ret = gsp_rpc_bar_trap_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: BAR_TRAP write failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: BAR_TRAP write returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: BAR_TRAP write succeeded\n");
-
- req.header.seq_id = get_next_seq_id();
- req.is_write = false;
- req.value = 0;
-
- ret = gsp_rpc_bar_trap_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: BAR_TRAP read-back failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.value != 0xDEADBEEF) {
- pr_err("GSP_EMU_TEST: BAR_TRAP read-back mismatch: expected 0xDEADBEEF, got 0x%llx\n", resp.value);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: BAR_TRAP read-back verified\n");
- return 0;
+ struct gsp_rpc_response rsp;
+ int ret;
+
+ pr_info("GSP_EMU_TEST: Testing YIELD...\n");
+
+ ret = run_rpc(GSP_RPC_YIELD, 0, NULL, 0, &rsp);
+ T_CHECK(ret == 0, yieldret, "dispatch returned %d\n", ret);
+ T_CHECK((s32)rsp.status == 0, yieldstatus,
+ "status %d\n", (s32)rsp.status);
+
+ pr_info("GSP_EMU_TEST: YIELD succeeded\n");
+ return 0;
}
static int test_notify(void)
{
- struct gsp_rpc_notify_request req = {0};
- struct gsp_rpc_notify_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing NOTIFY...\n");
-
- req.header.type = GSP_RPC_NOTIFY;
- req.header.seq_id = get_next_seq_id();
- req.event_type = GSP_NOTIFY_EVENT_COMPLETION;
- req.flags = GSP_NOTIFY_FLAG_ENABLE;
-
- ret = gsp_rpc_notify_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: NOTIFY register failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: NOTIFY register returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: NOTIFY register succeeded\n");
-
- req.header.seq_id = get_next_seq_id();
- req.flags = GSP_NOTIFY_FLAG_DISABLE;
-
- ret = gsp_rpc_notify_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: NOTIFY unregister failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: NOTIFY unregister returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: NOTIFY unregister succeeded\n");
- return 0;
+ struct gsp_rpc_response rsp;
+ int ret;
+
+ pr_info("GSP_EMU_TEST: Testing NOTIFY...\n");
+
+ /* subcmd carries the event type; dispatch is log-only, must succeed
+ * even with a NULL vgpu (exercises NULL-safe logging macros). */
+ ret = run_rpc(GSP_RPC_NOTIFY, 1, NULL, 0, &rsp);
+ T_CHECK(ret == 0, notifyret, "dispatch returned %d\n", ret);
+ T_CHECK((s32)rsp.status == 0, notifystatus,
+ "status %d\n", (s32)rsp.status);
+
+ pr_info("GSP_EMU_TEST: NOTIFY succeeded\n");
+ return 0;
}
-static int test_migrate(void)
+static int test_debug(void)
{
- struct gsp_rpc_migrate_request req = {0};
- struct gsp_rpc_migrate_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing MIGRATE...\n");
-
- req.header.type = GSP_RPC_MIGRATE;
- req.header.seq_id = get_next_seq_id();
- req.phase = GSP_MIGRATE_QUERY;
- req.flags = 0;
- req.data = 0;
- req.data_size = 0;
-
- ret = gsp_rpc_migrate_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE QUERY failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE QUERY returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: MIGRATE QUERY succeeded\n");
-
- req.header.seq_id = get_next_seq_id();
- req.phase = GSP_MIGRATE_PREPARE;
-
- ret = gsp_rpc_migrate_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE PREPARE failed with ret=%d\n", ret);
- return -1;
- }
-
- req.header.seq_id = get_next_seq_id();
- req.phase = GSP_MIGRATE_TRANSFER;
-
- ret = gsp_rpc_migrate_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE TRANSFER failed with ret=%d\n", ret);
- return -1;
- }
-
- req.header.seq_id = get_next_seq_id();
- req.phase = GSP_MIGRATE_COMMIT;
-
- ret = gsp_rpc_migrate_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE COMMIT failed with ret=%d\n", ret);
- return -1;
- }
-
- req.header.seq_id = get_next_seq_id();
- req.phase = GSP_MIGRATE_CLEANUP;
-
- ret = gsp_rpc_migrate_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: MIGRATE CLEANUP failed with ret=%d\n", ret);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: MIGRATE 4-phase completed\n");
- return 0;
+ struct gsp_rpc_response rsp;
+ int ret;
+
+ pr_info("GSP_EMU_TEST: Testing DEBUG...\n");
+
+ ret = run_rpc(GSP_RPC_DEBUG, 0, NULL, 0, &rsp);
+ T_CHECK(ret == 0, dbgret, "dispatch returned %d\n", ret);
+ T_CHECK((s32)rsp.status == 0, dbgstatus,
+ "status %d\n", (s32)rsp.status);
+
+ pr_info("GSP_EMU_TEST: DEBUG succeeded\n");
+ return 0;
}
-static int test_debug(void)
+/* hammer the dispatch loop with mixed SCHEDULE/YIELD traffic */
+static int test_dispatch_stress(void)
{
- struct gsp_rpc_debug_request req = {0};
- struct gsp_rpc_debug_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing DEBUG...\n");
-
- req.header.type = GSP_RPC_DEBUG;
- req.header.seq_id = get_next_seq_id();
- req.sub_command = GSP_DEBUG_GET_VERSION;
- req.data = 0;
- req.data_size = 0;
-
- ret = gsp_rpc_debug_emulation(test_client, &req, &resp);
- if (ret != 0) {
- pr_err("GSP_EMU_TEST: DEBUG failed with ret=%d\n", ret);
- return -1;
- }
- if (resp.status != 0) {
- pr_err("GSP_EMU_TEST: DEBUG returned status=%d\n", resp.status);
- return -1;
- }
-
- pr_info("GSP_EMU_TEST: DEBUG succeeded, version=0x%llx\n", resp.data);
- return 0;
+ struct gsp_sched_req sreq = {0};
+ struct gsp_rpc_response rsp;
+ int i, ret;
+
+ pr_info("GSP_EMU_TEST: Testing dispatch stress (100 iterations)...\n");
+
+ sreq.work_handle = 0xBEEF;
+ sreq.priority = 5;
+
+ for (i = 0; i < 100; i++) {
+ u32 type = (i & 1) ? GSP_RPC_YIELD : GSP_RPC_SCHEDULE;
+
+ ret = run_rpc(type, 0, &sreq,
+ (type == GSP_RPC_SCHEDULE) ? sizeof(sreq) : 0,
+ &rsp);
+ T_CHECK(ret == 0, stress, "iteration %d returned %d\n", i, ret);
+ T_CHECK((s32)rsp.status == 0, stressstatus,
+ "iteration %d status %d\n", i, (s32)rsp.status);
+ }
+
+ pr_info("GSP_EMU_TEST: dispatch stress passed (100/100)\n");
+ return 0;
}
-static int test_concurrent_rpcs(void)
+static int init_test_context(void)
{
- struct gsp_rpc_alloc_request req = {0};
- struct gsp_rpc_alloc_response resp = {0};
- int ret, passed = 0, failed = 0;
-
- pr_info("GSP_EMU_TEST: Testing concurrent RPCs...\n");
-
- for (int i = 0; i < 10; i++) {
- req.header.type = GSP_RPC_ALLOC;
- req.header.seq_id = get_next_seq_id();
- req.size = 4096;
- req.alignment = PAGE_SIZE;
- req.flags = GSP_ALLOC_FLAG_VRAM;
-
- ret = gsp_rpc_alloc_emulation(test_client, &req, &resp);
- if (ret == 0 && resp.status == 0 && resp.handle != 0) {
- passed++;
- struct gsp_rpc_free_request free_req = {0};
- struct gsp_rpc_free_response free_resp = {0};
- free_req.header.type = GSP_RPC_FREE;
- free_req.header.seq_id = get_next_seq_id();
- free_req.handle = resp.handle;
- gsp_rpc_free_emulation(test_client, &free_req, &free_resp);
- } else {
- failed++;
- }
- }
-
- pr_info("GSP_EMU_TEST: Concurrent test: %d passed, %d failed\n", passed, failed);
- return (failed == 0) ? 0 : -1;
+ int ret;
+
+ /* Route all RPC paths through their emulation implementations */
+ gsp_rpc_emulation_set(true);
+
+ test_shmem = vzalloc(SHMEM_SIZE);
+ if (!test_shmem)
+ return -ENOMEM;
+
+ /* NULL fw/doorbell virt addrs: emulation paths never touch them */
+ test_client = gsp_client_init_emulation(NULL, NULL, NULL);
+ if (IS_ERR(test_client)) {
+ ret = PTR_ERR(test_client);
+ test_client = NULL;
+ goto err_shmem;
+ }
+
+ test_ctx = kzalloc(sizeof(*test_ctx), GFP_KERNEL);
+ if (!test_ctx) {
+ ret = -ENOMEM;
+ goto err_client;
+ }
+
+ test_ctx->vgpu = NULL; /* NULL-safe logging macros handle this */
+ test_ctx->bar0_vaddr = NULL; /* PBA write is guarded */
+ test_ctx->shared_mem_virt = test_shmem;
+ test_ctx->shared_mem_size = SHMEM_SIZE;
+ test_ctx->rpc_client = test_client;
+ spin_lock_init(&test_ctx->lock);
+ test_ctx->running = true;
+ test_ctx->initialized = true;
+ /* msix_trigger[] zeroed by kzalloc; eventfd_signal is guarded */
+
+ pr_info("GSP_EMU_TEST: Test context initialized\n");
+ return 0;
+
+err_client:
+ gsp_client_fini(test_client);
+ test_client = NULL;
+err_shmem:
+ vfree(test_shmem);
+ test_shmem = NULL;
+ gsp_rpc_emulation_set(false);
+ return ret;
}
-static int test_error_handling(void)
+static void cleanup_test_context(void)
{
- struct gsp_rpc_alloc_request req = {0};
- struct gsp_rpc_alloc_response resp = {0};
- int ret;
-
- pr_info("GSP_EMU_TEST: Testing error handling...\n");
-
- req.header.type = GSP_RPC_ALLOC;
- req.header.seq_id = 0;
- req.size = 4096;
- req.alignment = PAGE_SIZE;
- req.flags = GSP_ALLOC_FLAG_VRAM;
-
- ret = gsp_rpc_alloc_emulation(test_client, &req, &resp);
- if (ret == 0 && resp.status != 0) {
- pr_info("GSP_EMU_TEST: Zero seq_id correctly rejected\n");
- } else {
- pr_err("GSP_EMU_TEST: Zero seq_id not handled correctly\n");
- return -1;
- }
-
- req.header.seq_id = get_next_seq_id();
- req.size = 0;
-
- ret = gsp_rpc_alloc_emulation(test_client, &req, &resp);
- if (ret == 0 && resp.status != 0) {
- pr_info("GSP_EMU_TEST: Zero size correctly rejected\n");
- } else {
- pr_err("GSP_EMU_TEST: Zero size not handled correctly\n");
- return -1;
- }
-
- req.header.seq_id = get_next_seq_id();
- req.size = 4096;
- req.alignment = 123;
-
- ret = gsp_rpc_alloc_emulation(test_client, &req, &resp);
- if (ret == 0 && resp.status != 0) {
- pr_info("GSP_EMU_TEST: Invalid alignment correctly rejected\n");
- } else {
- pr_err("GSP_EMU_TEST: Invalid alignment not handled correctly\n");
- return -1;
- }
-
- return 0;
+ if (test_ctx) {
+ test_ctx->running = false;
+ kfree(test_ctx);
+ test_ctx = NULL;
+ }
+ if (test_client) {
+ gsp_client_fini(test_client);
+ test_client = NULL;
+ }
+ if (test_shmem) {
+ vfree(test_shmem);
+ test_shmem = NULL;
+ }
+ gsp_rpc_emulation_set(false);
+ pr_info("GSP_EMU_TEST: Test context cleaned up\n");
}
static int run_all_tests(void)
{
- int failed = 0;
-
- init_test_context();
- if (!test_ctx || !test_client) {
- pr_err("GSP_EMU_TEST: Failed to initialize test context\n");
- return -1;
- }
-
- if (test_alloc_free() != 0) failed++;
- if (test_map_unmap() != 0) failed++;
- if (test_pin_unpin() != 0) failed++;
- if (test_schedule_yield() != 0) failed++;
- if (test_bar_trap() != 0) failed++;
- if (test_notify() != 0) failed++;
- if (test_migrate() != 0) failed++;
- if (test_debug() != 0) failed++;
- if (test_concurrent_rpcs() != 0) failed++;
- if (test_error_handling() != 0) failed++;
-
- cleanup_test_context();
-
- if (failed == 0) {
- pr_info("GSP_EMU_TEST: ALL TESTS PASSED\n");
- return 0;
- } else {
- pr_err("GSP_EMU_TEST: %d TEST(S) FAILED\n", failed);
- return -1;
- }
+ test_failures = 0;
+
+ if (init_test_context()) {
+ pr_err("GSP_EMU_TEST: Failed to initialize test context\n");
+ return -1;
+ }
+
+ test_seq_id_zero();
+ test_uninitialized_ctx();
+ test_unknown_type();
+ test_schedule();
+ test_yield();
+ test_notify();
+ test_debug();
+ test_dispatch_stress();
+
+ cleanup_test_context();
+
+ if (test_failures == 0) {
+ pr_info("GSP_EMU_TEST: ALL TESTS PASSED\n");
+ return 0;
+ }
+
+ pr_err("GSP_EMU_TEST: %d TEST(S) FAILED\n", test_failures);
+ return -1;
}
static int __init gsp_emulation_test_init(void)
{
- pr_info("GSP_EMU_TEST: Starting GSP emulation unit tests...\n");
- return run_all_tests();
+ pr_info("GSP_EMU_TEST: Starting GSP emulation unit tests...\n");
+ return run_all_tests();
}
static void __exit gsp_emulation_test_exit(void)
{
- pr_info("GSP_EMU_TEST: Module unloaded\n");
+ pr_info("GSP_EMU_TEST: Module unloaded\n");
}
module_init(gsp_emulation_test_init);
-module_exit(gsp_emulation_test_exit); \ No newline at end of file
+module_exit(gsp_emulation_test_exit);