summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--kernel/gsp_emulation.c3
-rw-r--r--kernel/gsp_rpc_client.c12
-rw-r--r--kernel/include/nvidia-vgpu-vfio.h40
-rw-r--r--kernel/main.c97
-rw-r--r--kernel/tests/Makefile8
-rw-r--r--kernel/tests/gsp_emulation_test.c933
-rwxr-xr-xscripts/stack_manager.sh34
7 files changed, 431 insertions, 696 deletions
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index e41f3bd..60805eb 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -493,6 +493,9 @@ void gsp_emu_get_stats(struct gsp_emu_context *ctx,
*total_latency_ns = ctx->total_latency_ns;
}
+EXPORT_SYMBOL(gsp_emu_process_rpc);
+EXPORT_SYMBOL(gsp_emu_get_stats);
+
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Open-IOV Project");
MODULE_DESCRIPTION("GSP Emulation Engine for Host-Side vGPU"); \ No newline at end of file
diff --git a/kernel/gsp_rpc_client.c b/kernel/gsp_rpc_client.c
index e91d5f6..ae45513 100644
--- a/kernel/gsp_rpc_client.c
+++ b/kernel/gsp_rpc_client.c
@@ -562,7 +562,7 @@ int gsp_rpc_alloc_emul(struct gsp_client *client,
struct nv_vgpu_device *vgpu = client->dev->driver_data;
struct gsp_vram_alloc *alloc;
u64 va = addr_hint ? addr_hint : 0x100000000ULL; /* Default 4GB */
- int ret;
+ dma_addr_t iova;
alloc = kzalloc(sizeof(*alloc), GFP_KERNEL);
if (!alloc)
@@ -612,9 +612,10 @@ int gsp_rpc_alloc_emul(struct gsp_client *client,
}
alloc->mapping->pinned = true;
- /* Map to IOVA for DMA */
- ret = dma_map_page(&vgpu->pdev->dev, alloc->mapping->pages[0], 0, size, DMA_BIDIRECTIONAL);
- if (dma_mapping_error(&vgpu->pdev->dev, ret)) {
+ /* Map to IOVA for DMA (dma_addr_t is 64-bit - do NOT truncate into int;
+ * with an IOMMU the IOVA may exceed 32 bits) */
+ iova = dma_map_page(&vgpu->pdev->dev, alloc->mapping->pages[0], 0, size, DMA_BIDIRECTIONAL);
+ if (dma_mapping_error(&vgpu->pdev->dev, iova)) {
for (int i = 0; i < alloc->mapping->page_count; i++)
__free_page(alloc->mapping->pages[i]);
vfree(alloc->mapping->pages);
@@ -622,7 +623,7 @@ int gsp_rpc_alloc_emul(struct gsp_client *client,
kfree(alloc);
return -ENOMEM;
}
- alloc->mapping->iova = ret;
+ alloc->mapping->iova = iova;
alloc->mapping->mapped = true;
alloc->gpu_va = va;
@@ -1070,6 +1071,7 @@ int gsp_wpr_scratch_mutex_release(struct gsp_client *client, u32 wpr_id)
* ============================================================ */
EXPORT_SYMBOL(gsp_client_init);
+EXPORT_SYMBOL(gsp_client_init_emulation);
EXPORT_SYMBOL(gsp_client_fini);
EXPORT_SYMBOL(gsp_rpc_alloc);
EXPORT_SYMBOL(gsp_rpc_free);
diff --git a/kernel/include/nvidia-vgpu-vfio.h b/kernel/include/nvidia-vgpu-vfio.h
index 3ca5cf6..02c2c13 100644
--- a/kernel/include/nvidia-vgpu-vfio.h
+++ b/kernel/include/nvidia-vgpu-vfio.h
@@ -191,16 +191,34 @@ static inline struct nv_vgpu_device *mdev_to_vgpu(struct mdev_device *mdev)
return dev_get_drvdata(&mdev->dev);
}
-#define nv_vgpu_dbg(vgpu, fmt, ...) \
- dev_dbg(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__)
-
-#define nv_vgpu_info(vgpu, fmt, ...) \
- dev_info(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__)
-
-#define nv_vgpu_warn(vgpu, fmt, ...) \
- dev_warn(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__)
-
-#define nv_vgpu_err(vgpu, fmt, ...) \
- dev_err(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__)
+/* NULL-tolerant logging: emulation paths may run with a NULL vgpu
+ * (e.g. unit tests, early init) - fall back to plain pr_*. */
+#define nv_vgpu_dbg(vgpu, fmt, ...) do { \
+ if (vgpu) \
+ dev_dbg(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__); \
+ else \
+ pr_debug("%s: " fmt, __func__, ##__VA_ARGS__); \
+} while (0)
+
+#define nv_vgpu_info(vgpu, fmt, ...) do { \
+ if (vgpu) \
+ dev_info(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__); \
+ else \
+ pr_info("%s: " fmt, __func__, ##__VA_ARGS__); \
+} while (0)
+
+#define nv_vgpu_warn(vgpu, fmt, ...) do { \
+ if (vgpu) \
+ dev_warn(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__); \
+ else \
+ pr_warn("%s: " fmt, __func__, ##__VA_ARGS__); \
+} while (0)
+
+#define nv_vgpu_err(vgpu, fmt, ...) do { \
+ if (vgpu) \
+ dev_err(&(vgpu)->dev, "%s: " fmt, __func__, ##__VA_ARGS__); \
+ else \
+ pr_err("%s: " fmt, __func__, ##__VA_ARGS__); \
+} while (0)
#endif \ No newline at end of file
diff --git a/kernel/main.c b/kernel/main.c
index c96357b..e0e823b 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -489,7 +489,10 @@ void nv_vgpu_mdev_unregister_parent(struct pci_dev *pdev)
}
EXPORT_SYMBOL(nv_vgpu_mdev_unregister_parent);
-MODULE_SOFTDEP("pre: nvidia");
+/* NOTE: No MODULE_SOFTDEP on nvidia - this module is fully standalone.
+ * It exports the nvidia_vgpu_vfio_* interface (nvidia.ko imports it when
+ * present) and registers the mdev parent on the physical GPU directly.
+ * Load order for the matched stack: open-iov-vfio.ko BEFORE nvidia.ko. */
MODULE_AUTHOR("Open-IOV Project");
MODULE_DESCRIPTION("Open-IOV vGPU VFIO Mediated Device Driver");
@@ -507,7 +510,6 @@ static int nv_vgpu_mdev_create(struct mdev_device *mdev)
struct nv_vgpu_device *vgpu;
struct pci_dev *pdev;
struct mdev_parent *parent;
- struct iommu_group *iommu_group;
int ret;
vgpu = vfio_alloc_device(nv_vgpu_device, vfio_dev, &mdev->dev,
@@ -595,18 +597,17 @@ static void nv_vgpu_mdev_remove(struct mdev_device *mdev)
pr_info("nvidia-vgpu-vfio: mdev remove started for %s\n", dev_name(&mdev->dev));
- /* Cancel init work first */
+ /* Cancel pending/running init work first */
cancel_work_sync(&vgpu->init_work);
- /* Cancel cleanup work to prevent race */
- cancel_work_sync(&vgpu->cleanup_work);
-
/* Wait for any open file descriptors to close before proceeding.
* The VFIO device must not be unregistered while userspace has it open,
- * otherwise vfio_device_group_unregister can crash with NULL pointer deref. */
+ * otherwise vfio_device_group_unregister can crash with NULL pointer deref.
+ * vfio_unregister_group_dev() itself blocks until fds drain, so this
+ * pre-wait exists to surface a clear warning if userspace is stuck. */
pr_info("nvidia-vgpu-vfio: waiting for vfio_dev_open_count to reach 0 (current: %d)\n",
atomic_read(&vgpu->vfio_dev_open_count));
-
+
timeout = wait_event_timeout(vgpu->vfio_dev_close_wait,
atomic_read(&vgpu->vfio_dev_open_count) == 0,
msecs_to_jiffies(5000));
@@ -617,10 +618,31 @@ static void nv_vgpu_mdev_remove(struct mdev_device *mdev)
pr_info("nvidia-vgpu-vfio: all vfio device file descriptors closed\n");
}
- /* Mark device as being removed - actual resource cleanup happens in
- * vfio_device_release callback which is called when refcount reaches 0
- * after mdev framework unregisters the vfio_device. */
- vgpu->vfio_dev_registered = false;
+ /* Unregister the VFIO device. This removes it from its IOMMU group,
+ * deletes the cdev, and waits for any remaining userspace fds to
+ * close. After this returns, no userspace can access the device. */
+ if (vgpu->vfio_dev_registered) {
+ vfio_unregister_group_dev(&vgpu->vfio_dev);
+ vgpu->vfio_dev_registered = false;
+ }
+
+ /* Now that no userspace can touch the device, release all per-device
+ * resources (GSP emulation thread, memory, IRQs, BARs, migration).
+ * cleanup_work is never scheduled elsewhere, so invoke it directly. */
+ nv_vgpu_device_cleanup_work(&vgpu->cleanup_work);
+
+ /* Remove from driver device list (get_available accounts per-type) */
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_del(&vgpu->list);
+ nv_vgpu_driver.device_count--;
+ mutex_unlock(&nv_vgpu_driver.lock);
+
+ pci_dev_put(vgpu->pdev);
+ vgpu->pdev = NULL;
+
+ /* Balance vfio_alloc_device() in create; frees the vgpu struct once
+ * the vfio_device refcount drops to zero (ops->release fires first). */
+ vfio_put_device(&vgpu->vfio_dev);
pr_info("nvidia-vgpu-vfio: mdev remove callback completed for %s\n", dev_name(&mdev->dev));
}
@@ -1001,6 +1023,19 @@ static long nv_vgpu_vfio_dev_ioctl(struct vfio_device *vdev, unsigned int cmd,
return -ENOTTY;
}
+/* Convert any kernel virtual address (lowmem or vmalloc) to a PFN for mmap.
+ * virt_to_phys() is only valid for direct-mapped lowmem; vmalloc/vmap
+ * addresses (vzalloc, ioremap, kvzalloc fallback) must go through
+ * vmalloc_to_pfn(). Using virt_to_phys() on a vmalloc address produces a
+ * garbage PFN, which KVM then refuses to map (EPT reserved-bit WARN) and
+ * the guest faults/hangs touching the BAR. */
+static unsigned long nv_vgpu_mmap_virt_to_pfn(const void *vaddr)
+{
+ if (is_vmalloc_addr(vaddr))
+ return vmalloc_to_pfn(vaddr);
+ return virt_to_phys((void *)vaddr) >> PAGE_SHIFT;
+}
+
static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct *vma)
{
struct nv_vgpu_device *vgpu = container_of(vdev, struct nv_vgpu_device, vfio_dev);
@@ -1021,7 +1056,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
if (offset + size > region_base + region_size)
return -EINVAL;
vaddr = vgpu->pci_config + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = nv_vgpu_mmap_virt_to_pfn(vaddr);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
@@ -1030,14 +1065,24 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR0_REGION_INDEX);
region_size = bar_ctx ? bar_ctx->bar0_shadow_size : (256 * 1024 * 1024);
if (offset >= region_base && offset < region_base + region_size) {
+ unsigned long shadow_off = offset - region_base;
+ unsigned long pos;
+
if (!bar_ctx || !bar_ctx->bar0_shadow)
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = bar_ctx->bar0_shadow + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ /* bar0_shadow is vzalloc'd: physically non-contiguous pages, so
+ * map one page at a time via vmalloc_to_pfn(). */
+ vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
- return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ for (pos = 0; pos < size; pos += PAGE_SIZE) {
+ pfn = vmalloc_to_pfn(bar_ctx->bar0_shadow + shadow_off + pos);
+ if (remap_pfn_range(vma, vma->vm_start + pos, pfn, PAGE_SIZE,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ }
+ return 0;
}
/* BAR1 region - VRAM (use physical address directly) */
@@ -1054,7 +1099,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR2 region */
+ /* BAR2 region - ioremap'd host BAR; map backing physical address */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR2_REGION_INDEX);
region_size = vgpu->bars[2].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1062,13 +1107,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[2].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[2].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR3 region */
+ /* BAR3 region - ioremap'd host BAR; map backing physical address */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR3_REGION_INDEX);
region_size = vgpu->bars[3].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1076,13 +1120,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[3].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[3].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR4 region */
+ /* BAR4 region - map backing physical address if present */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR4_REGION_INDEX);
region_size = vgpu->bars[4].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1090,13 +1133,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[4].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[4].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR5 region */
+ /* BAR5 region - map backing physical address if present */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR5_REGION_INDEX);
region_size = vgpu->bars[5].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1104,8 +1146,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[5].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[5].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
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);
diff --git a/scripts/stack_manager.sh b/scripts/stack_manager.sh
index c4685da..0b62170 100755
--- a/scripts/stack_manager.sh
+++ b/scripts/stack_manager.sh
@@ -81,7 +81,14 @@ cmd_build_all() {
cmd_load_prereqs() {
log_info "Loading kernel prerequisites (vfio, mdev)..."
- sudo modprobe vfio vfio_iommu_type1 vfio_pci mdev vfio_mdev 2>/dev/null || true
+ # vfio_mdev does not exist on kernels >= 6.4 (mdev core is standalone mdev.ko);
+ # modprobe each individually so a missing one never blocks the rest.
+ local m
+ for m in vfio vfio_iommu_type1 vfio_pci mdev vfio_mdev; do
+ if modinfo "$m" &>/dev/null; then
+ sudo modprobe "$m" 2>/dev/null || true
+ fi
+ done
lsmod | grep -E "vfio|mdev"
}
@@ -104,13 +111,26 @@ cmd_load_nvidia() {
log_err "Missing $NVIDIA_KO - run 'build-nvidia' first"
return 1
fi
- if ! lsmod | grep -q "^nvidia "; then
- log_info "Loading nvidia.ko (OpenRM)..."
- sudo insmod "$NVIDIA_KO" || { log_err "Failed to load nvidia.ko"; return 1; }
- log_ok "nvidia.ko loaded"
- else
- log_ok "nvidia.ko already loaded"
+ if lsmod | grep -q "^nvidia "; then
+ # A module named 'nvidia' is already loaded. Verify it is OUR build
+ # by comparing versions (ours: 570.144 OpenRM w/ vGPU VFIO patches).
+ local loaded_ver want_ver
+ loaded_ver=$(cat /sys/module/nvidia/version 2>/dev/null)
+ want_ver=$(modinfo -F version "$NVIDIA_KO" 2>/dev/null)
+ if [[ -n "$loaded_ver" && "$loaded_ver" == "$want_ver" ]]; then
+ log_ok "nvidia.ko (Open-IOV OpenRM $loaded_ver) already loaded"
+ return 0
+ fi
+ log_err "A foreign nvidia module (version '$loaded_ver') is already loaded;"
+ log_err "Open-IOV needs its own OpenRM build ('$want_ver'). Cannot load both."
+ log_err "Unload the stock driver first (requires nothing holding /dev/nvidia*,"
+ log_err "i.e. no GUI session), or reboot - stock nvidia is blacklisted via"
+ log_err "/etc/modprobe.d/blacklist-nvidia-open-iov.conf."
+ return 1
fi
+ log_info "Loading nvidia.ko (OpenRM)..."
+ sudo insmod "$NVIDIA_KO" || { log_err "Failed to load nvidia.ko"; return 1; }
+ log_ok "nvidia.ko loaded"
}
cmd_install_firmware() {