From 2302fc09080bad51c73f293294e19fd4db464eb0 Mon Sep 17 00:00:00 2001 From: Open-IOV Date: Wed, 23 Sep 2026 05:44:08 -0400 Subject: Open-IOV: initial public release Open-IOV virtualizes NVIDIA GPUs so that unmodified guest drivers run with real GPU functionality - CUDA first, 3D and video to follow - on unmodified hosts, without a proprietary vGPU license and without patching any driver, firmware or hypervisor. Core principle: emulate the interface, pass through the work. kernel/ open-iov-vfio.ko - host VFIO mdev driver: virtual GPU device (PCI config space, BAR shadows, MSI-X), GSP boot emulation, GSP-RM message-queue/RPC emulation, memory model (GPU-VA to guest-PA discovery) and the semaphore-completion service that advances guest UVM progress. userspace/ libiov.so management API and examples. tools/ guest diagnostics, firmware analysis wrappers, RM probes. scripts/ stack manager, VM harness, build/load/test automation. config/ hardware/version profiles (the portability surface). docs/ documentation tree - start at docs/README.md. wiki/ long-form firmware and protocol deep dives. Status: initialization works end to end for a stock 570.144 Linux guest on Turing (TU104): driver probe, nvidia-smi, the full GSP-RM control battery, object creation and the early CUDA/UVM start-up path. GPU work forwarding - the step that makes CUDA kernels execute on real hardware - is the active work item; see docs/04-implementation/01-known-knowns-and-unknowns.md. License: BSD-3-Clause for the project; GPL-2.0 for kernel modules that link GPL-only kernel interfaces. See LICENSE and docs/09-reference/legal-and-licensing.md. --- kernel/tests/gsp_emulation_test.c | 350 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 kernel/tests/gsp_emulation_test.c (limited to 'kernel/tests/gsp_emulation_test.c') diff --git a/kernel/tests/gsp_emulation_test.c b/kernel/tests/gsp_emulation_test.c new file mode 100644 index 0000000..d63fcdb --- /dev/null +++ b/kernel/tests/gsp_emulation_test.c @@ -0,0 +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 "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); -- cgit v1.2.3