From 5d4d26f8e773f2871c1b5c9266b82495d390970e Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 12:37:46 -0400 Subject: 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. --- kernel/tests/gsp_emulation_test.c | 933 ++++++++++++-------------------------- 1 file changed, 289 insertions(+), 644 deletions(-) (limited to 'kernel/tests/gsp_emulation_test.c') 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 #include #include -#include -#include -#include -#include #include -#include -#include -#include #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); -- cgit v1.2.3