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/gsp_emulation.c | 3 + kernel/gsp_rpc_client.c | 12 +- kernel/include/nvidia-vgpu-vfio.h | 40 +- kernel/main.c | 97 ++-- kernel/tests/Makefile | 8 +- kernel/tests/gsp_emulation_test.c | 933 ++++++++++++-------------------------- 6 files changed, 404 insertions(+), 689 deletions(-) (limited to 'kernel') 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 #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