summaryrefslogtreecommitdiffstats
path: root/kernel/tests/gsp_emulation_test.c
diff options
context:
space:
mode:
authorOpen-IOV <dev@open-iov.org>2026-09-23 05:44:08 -0400
committerOpen-IOV <dev@open-iov.org>2026-09-23 05:46:43 -0400
commit2302fc09080bad51c73f293294e19fd4db464eb0 (patch)
treec722d186347fdef5a34131e9e143b1549b1daf8e /kernel/tests/gsp_emulation_test.c
downloadopen-iov-2302fc09080bad51c73f293294e19fd4db464eb0.tar.gz
open-iov-2302fc09080bad51c73f293294e19fd4db464eb0.zip
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.
Diffstat (limited to 'kernel/tests/gsp_emulation_test.c')
-rw-r--r--kernel/tests/gsp_emulation_test.c350
1 files changed, 350 insertions, 0 deletions
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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#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);