// 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 "gsp_emulation.h" #include "gsp_rpc_client.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; } /* 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; pr_info("GSP_EMU_TEST: Testing seq_id=0 rejection...\n"); memset(test_shmem, 0, SHMEM_SIZE); msg->type = GSP_RPC_YIELD; msg->seq_id = 0; ret = gsp_emu_process_rpc(test_ctx); T_CHECK(ret == -EINVAL, seq_zero, "expected -EINVAL, got %d\n", ret); pr_info("GSP_EMU_TEST: seq_id=0 correctly rejected\n"); return 0; } /* uninitialized context must be rejected */ static int test_uninitialized_ctx(void) { struct gsp_emu_context *bad; int ret; pr_info("GSP_EMU_TEST: Testing uninitialized ctx rejection...\n"); 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; } /* unknown RPC type must be rejected with -ENOTSUPP in ret and status */ static int test_unknown_type(void) { 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(void) { 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_yield(void) { 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_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_debug(void) { 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; } /* hammer the dispatch loop with mixed SCHEDULE/YIELD traffic */ static int test_dispatch_stress(void) { 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 init_test_context(void) { 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 void cleanup_test_context(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_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) { 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(); } static void __exit gsp_emulation_test_exit(void) { pr_info("GSP_EMU_TEST: Module unloaded\n"); } module_init(gsp_emulation_test_init); module_exit(gsp_emulation_test_exit);