summaryrefslogtreecommitdiffstats
path: root/legacy-driver-guest
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 /legacy-driver-guest
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 'legacy-driver-guest')
-rw-r--r--legacy-driver-guest/Makefile44
-rw-r--r--legacy-driver-guest/README.md21
-rw-r--r--legacy-driver-guest/bar/gsp_bar.c242
-rw-r--r--legacy-driver-guest/core/gsp_core.c654
-rw-r--r--legacy-driver-guest/debugfs/gsp_debugfs.c273
-rw-r--r--legacy-driver-guest/include/gsp_core.h613
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c432
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c327
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c624
-rw-r--r--legacy-driver-guest/migration/gsp_migration.c339
-rw-r--r--legacy-driver-guest/rpc/gsp_rpc.c331
-rw-r--r--legacy-driver-guest/vf/gsp_vf.c312
-rw-r--r--legacy-driver-guest/wpr/gsp_wpr.c285
13 files changed, 4497 insertions, 0 deletions
diff --git a/legacy-driver-guest/Makefile b/legacy-driver-guest/Makefile
new file mode 100644
index 0000000..4675cbf
--- /dev/null
+++ b/legacy-driver-guest/Makefile
@@ -0,0 +1,44 @@
+# GSP vGPU-IO Driver Makefile
+
+obj-m += gsp-vgpu.o
+
+gsp-vgpu-y := \
+ core/gsp_core.o \
+ rpc/gsp_rpc.o \
+ vf/gsp_vf.o \
+ bar/gsp_bar.o \
+ wpr/gsp_wpr.o \
+ migration/gsp_migration.o \
+ debugfs/gsp_debugfs.o
+
+ccflags-y += -I$(src)/include
+ccflags-y += -I$(src)/../kernel/include
+
+# Build against the kernel source
+KDIR ?= /lib/modules/$(shell uname -r)/build
+
+all:
+ $(MAKE) -C $(KDIR) M=$(PWD) modules
+
+clean:
+ $(MAKE) -C $(KDIR) M=$(PWD) clean
+
+install:
+ $(MAKE) -C $(KDIR) M=$(PWD) modules_install
+
+load: all
+ sudo insmod gsp-vgpu.ko firmware_name=gsp-570.144.fwimage.bin
+
+unload:
+ sudo rmmod gsp-vgpu
+
+test: all load
+ @echo "Driver loaded, checking status..."
+ sleep 2
+ dmesg -T | tail -20
+ @echo "Checking debugfs..."
+ ls -la /sys/kernel/debug/gsp-vgpu/ 2>/dev/null || echo "Debugfs not available"
+ @echo "Checking device nodes..."
+ ls -la /dev/gsp-vgpu* 2>/dev/null || echo "No device nodes"
+
+.PHONY: all clean install load unload test \ No newline at end of file
diff --git a/legacy-driver-guest/README.md b/legacy-driver-guest/README.md
new file mode 100644
index 0000000..309613a
--- /dev/null
+++ b/legacy-driver-guest/README.md
@@ -0,0 +1,21 @@
+# legacy-driver-guest — historical guest-side driver (NOT BUILT)
+
+This directory preserves the **old guest-side vGPU driver** (`gsp-vgpu.ko`)
+from the pre-refactor architecture, where GSP RPC handling lived in a kernel
+module *inside the guest*.
+
+It is kept for reference only:
+
+- The current architecture is **host-only GSP emulation** — the guest runs
+ the stock, unmodified `nvidia.ko`, and there is no Open-IOV code in the
+ guest. See `docs/GSP_EMULATION_ARCHITECTURE.md`.
+- Nothing here is part of any build target (the top-level Makefile no longer
+ references it).
+- `kernel-tests/` holds the old test modules written against this driver's
+ internal API (`gsp_core.h`); they are superseded by
+ `kernel/tests/gsp_emulation_test.c`, which tests the host-side emulation
+ engine through `gsp_emu_process_rpc()`.
+
+Do not add new code here. If you need behavior that only exists in this tree
+(e.g. WPR eventfd work, migration WPR state), port it into the host-side
+`kernel/` modules rather than reviving the guest driver.
diff --git a/legacy-driver-guest/bar/gsp_bar.c b/legacy-driver-guest/bar/gsp_bar.c
new file mode 100644
index 0000000..aa3ed62
--- /dev/null
+++ b/legacy-driver-guest/bar/gsp_bar.c
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - BAR Emulation
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+/* BAR Emulation Ops */
+static int gsp_bar_trap_handler(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ struct gsp_bar_trap_response resp = {0};
+ size_t resp_size = sizeof(resp);
+ int ret;
+
+ /* Validate BAR index (we only support BAR1) */
+ if (trap->bar_index != 1)
+ return -EINVAL;
+
+ /* Validate offset */
+ if (trap->offset >= bar->aperture_size)
+ return -ERANGE;
+
+ /* Check ForceBarAccess */
+ if (bar->force_access && vf->id != bar->force_vf)
+ return -EPERM;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ /* Simulate BAR trap handling */
+ if (trap->trap_type == GSP_BAR_WRITE) {
+ /* Config write - update aperture */
+ bar->aperture_offset = trap->offset;
+ bar->aperture_size = trap->value;
+ } else if (trap->trap_type == GSP_BAR_ATOMIC) {
+ /* Force access control */
+ bar->force_access = trap->offset ? true : false;
+ bar->force_vf = trap->offset ? vf->id : 0;
+ }
+ resp.value = 0;
+ return 0;
+ }
+
+ /* Forward to GSP BAR trap handler (0x04CB8188) */
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP,
+ trap, sizeof(*trap), &resp, &resp_size);
+
+ return ret;
+}
+
+/* Aperture Configuration */
+static int gsp_bar_config_aperture(struct gsp_vf *vf, uint64_t offset, uint64_t size)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ struct gsp_bar_trap_payload payload = {0};
+ struct gsp_bar_trap_response resp = {0};
+ size_t resp_size = sizeof(resp);
+ int ret;
+
+ /* Validate alignment (4KB) */
+ if (offset & 0xFFF || size & 0xFFF)
+ return -EINVAL;
+
+ /* Validate max size (256KB per firmware) */
+ if (size > 0x40000)
+ return -EINVAL;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ bar->aperture_offset = offset;
+ bar->aperture_size = size;
+ return 0;
+ }
+
+ payload.bar_index = 1;
+ payload.trap_type = GSP_BAR_WRITE; /* Config write */
+ payload.offset = offset;
+ payload.value = size;
+ payload.size = 8;
+ payload.vf_id = vf->id;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP,
+ &payload, sizeof(payload), &resp, &resp_size);
+ if (!ret) {
+ bar->aperture_offset = offset;
+ bar->aperture_size = size;
+ }
+
+ return ret;
+}
+
+/* ForceBarAccess Control */
+int gsp_bar_force_access(struct gsp_vf *vf, bool enable)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ struct gsp_bar_trap_payload payload = {0};
+ struct gsp_bar_trap_response resp = {0};
+ size_t resp_size = sizeof(resp);
+ int ret;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ bar->force_access = enable;
+ bar->force_vf = enable ? vf->id : 0;
+ return 0;
+ }
+
+ payload.bar_index = 1;
+ payload.trap_type = GSP_BAR_ATOMIC; /* Force access control */
+ payload.offset = enable ? 1 : 0;
+ payload.value = enable ? vf->id : 0;
+ payload.size = 8;
+ payload.vf_id = vf->id;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP,
+ &payload, sizeof(payload), &resp, &resp_size);
+ if (!ret) {
+ bar->force_access = enable;
+ bar->force_vf = enable ? vf->id : 0;
+ }
+
+ return ret;
+}
+
+/* State Save for Migration */
+static int gsp_bar_save_state(struct gsp_vf *vf, void *buf, size_t *size)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ size_t needed = sizeof(*bar) + bar->backing_size;
+
+ if (!buf || *size < needed)
+ return -ENOSPC;
+
+ memcpy(buf, bar, sizeof(*bar));
+ if (bar->backing_store && bar->backing_size > 0)
+ memcpy(buf + sizeof(*bar), bar->backing_store, bar->backing_size);
+
+ *size = needed;
+ return 0;
+}
+
+/* State Restore for Migration */
+static int gsp_bar_restore_state(struct gsp_vf *vf, void *buf, size_t size)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ size_t min_size = sizeof(*bar);
+
+ if (!buf || size < min_size)
+ return -EINVAL;
+
+ memcpy(bar, buf, sizeof(*bar));
+
+ if (size > min_size && bar->backing_size > 0) {
+ if (!bar->backing_store) {
+ bar->backing_store = vzalloc(bar->backing_size);
+ if (!bar->backing_store)
+ return -ENOMEM;
+ }
+ memcpy(bar->backing_store, buf + min_size,
+ min(size - min_size, bar->backing_size));
+ }
+
+ /* Reconfigure BAR in GSP */
+ return gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size);
+}
+
+/* VF BAR Initialization */
+int gsp_vf_bar_init(struct gsp_vf *vf)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+ int ret;
+
+ /* Initialize with config values */
+ bar->aperture_offset = vf->bar1_offset;
+ bar->aperture_size = vf->bar1_size;
+ bar->force_access = false;
+ bar->force_vf = 0;
+ bar->backing_store = NULL;
+ bar->backing_size = 0;
+
+ /* Allocate backing store (max 256KB) */
+ if (bar->aperture_size > 0) {
+ bar->backing_size = min(bar->aperture_size, (uint64_t)0x40000);
+ bar->backing_store = vzalloc(bar->backing_size);
+ if (!bar->backing_store)
+ return -ENOMEM;
+ }
+
+ /* Configure aperture in GSP (skip in emulation mode) */
+ if (!emulation_mode) {
+ ret = gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size);
+ if (ret) {
+ dev_err(vf->gsp->dev, "VF %u: BAR aperture config failed: %d\n", vf->id, ret);
+ goto err_backing;
+ }
+ } else {
+ dev_info(vf->gsp->dev, "VF %u: BAR1 initialized in emulation mode [0x%llx, 0x%llx)\n",
+ vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size);
+ }
+
+ dev_info(vf->gsp->dev, "VF %u: BAR1 initialized [0x%llx, 0x%llx)\n",
+ vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size);
+
+ return 0;
+
+err_backing:
+ vfree(bar->backing_store);
+ bar->backing_store = NULL;
+ bar->backing_size = 0;
+ return ret;
+}
+
+/* VF BAR Cleanup */
+void gsp_vf_bar_fini(struct gsp_vf *vf)
+{
+ struct gsp_bar_state *bar = &vf->bar_state;
+
+ vfree(bar->backing_store);
+ bar->backing_store = NULL;
+ bar->backing_size = 0;
+
+ dev_info(vf->gsp->dev, "VF %u: BAR1 cleaned up\n", vf->id);
+}
+
+/* BAR Trap Entry Point (called from ISR/notification) */
+int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap)
+{
+ return gsp_bar_trap_handler(vf, trap);
+}
+
+/* Migration Interface */
+int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size)
+{
+ return gsp_bar_save_state(vf, buf, size);
+}
+
+int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size)
+{
+ return gsp_bar_restore_state(vf, buf, size);
+} \ No newline at end of file
diff --git a/legacy-driver-guest/core/gsp_core.c b/legacy-driver-guest/core/gsp_core.c
new file mode 100644
index 0000000..cd41bd2
--- /dev/null
+++ b/legacy-driver-guest/core/gsp_core.c
@@ -0,0 +1,654 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - Core Implementation
+ * Guest vGPU driver - communicates with GSP already booted by host GRID driver
+ */
+
+#include "gsp_core.h"
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/delay.h>
+#include <linux/msi.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+
+#define DRIVER_NAME "gsp-vgpu"
+#define DRIVER_VERSION "0.1.0"
+
+/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */
+bool emulation_mode = true; /* ON by default */
+static bool emulation_override = false; /* true = force emulation, false = force native */
+module_param(emulation_mode, bool, 0444);
+MODULE_PARM_DESC(emulation_mode, "Enable emulation mode for BAR/WPR (skip GSP RPC) - default ON, auto-detects if GSP has vGPU handlers");
+module_param(emulation_override, bool, 0444);
+MODULE_PARM_DESC(emulation_override, "Override auto-detection: 1=force emulation, 0=force native (default: auto-detect)");
+
+/* Forward declarations */
+int gsp_firmware_load(struct gsp_device *gsp);
+int gsp_setup_irq(struct gsp_device *gsp);
+int gsp_rpc_init(struct gsp_device *gsp);
+void gsp_shutdown(struct gsp_device *gsp);
+static void gsp_detect_emulation_mode(struct gsp_device *gsp);
+static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static int gsp_open(struct inode *inode, struct file *file);
+static int gsp_release(struct inode *inode, struct file *file);
+
+/* Char device file operations */
+static const struct file_operations gsp_fops = {
+ .owner = THIS_MODULE,
+ .open = gsp_open,
+ .release = gsp_release,
+ .unlocked_ioctl = gsp_ioctl,
+ .compat_ioctl = gsp_ioctl,
+};
+
+/* Char device open */
+static int gsp_open(struct inode *inode, struct file *file)
+{
+ struct gsp_device *gsp = container_of(file->private_data, struct gsp_device, miscdev);
+ file->private_data = gsp;
+ return 0;
+}
+
+/* Char device release */
+static int gsp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/* Char device ioctl */
+static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct gsp_device *gsp = file->private_data;
+ void __user *argp = (void __user *)arg;
+ int ret = 0;
+
+ mutex_lock(&gsp->chardev_lock);
+
+ switch (cmd) {
+ case GSP_IOCTL_CREATE_VF: {
+ struct gsp_vf_config config;
+ if (copy_from_user(&config, argp, sizeof(config))) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = gsp_vf_create(gsp, &config);
+ break;
+ }
+ case GSP_IOCTL_DESTROY_VF: {
+ uint32_t vf_id;
+ if (copy_from_user(&vf_id, argp, sizeof(vf_id))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ gsp_vf_destroy(vf);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_ALLOC: {
+ struct gsp_alloc_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_alloc(vf, args.size, args.flags, &args.handle, &args.va);
+ if (!ret)
+ ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0;
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_FREE: {
+ struct gsp_free_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_free(vf, args.handle);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_PIN: {
+ struct gsp_pin_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_pin(vf, args.handle, args.offset, args.size, &args.dma_addr);
+ if (!ret)
+ ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0;
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_UNPIN: {
+ struct gsp_unpin_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_unpin(vf, args.handle);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_MAP: {
+ struct gsp_map_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_map(vf, args.handle, args.gpu_va, args.size,
+ args.prot, args.flags, args.offset, &args.mapped_va);
+ if (!ret)
+ ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0;
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_UNMAP: {
+ struct gsp_unmap_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_mem_unmap(vf, args.handle, args.gpu_va, args.size);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_BAR_CONFIG: {
+ struct gsp_bar_config_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_bar_init(vf); /* Re-init with new aperture */
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_BAR_FORCE: {
+ struct gsp_bar_force_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_bar_force_access(vf, args.enable);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_BAR_SAVE: {
+ struct gsp_bar_save_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_migration_bar_save(vf, args.buf, args.size);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_BAR_RESTORE: {
+ struct gsp_bar_restore_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_migration_bar_restore(vf, args.buf, args.size);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_WPR_PROTECT: {
+ struct gsp_wpr_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_wpr_protect(vf, args.base, args.size, args.flags);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_WPR_UNPROTECT: {
+ struct gsp_wpr_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_wpr_unprotect(vf, args.base);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_WPR_TIMEOUT: {
+ struct gsp_wpr_timeout_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_wpr_set_timeout(vf, args.timeout_ns);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_SCHEDULE: {
+ struct gsp_schedule_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_schedule(vf, args.priority, args.time_slice_ns);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_YIELD: {
+ uint32_t vf_id;
+ if (copy_from_user(&vf_id, argp, sizeof(vf_id))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_vf_yield(vf);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_MIGRATE: {
+ struct gsp_migrate_args args;
+ if (copy_from_user(&args, argp, sizeof(args))) {
+ ret = -EFAULT;
+ break;
+ }
+ struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id);
+ if (!vf) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = gsp_migration_start(vf, args.dst_vf);
+ gsp_vf_put(vf);
+ break;
+ }
+ case GSP_IOCTL_GET_INFO: {
+ char info[256];
+ snprintf(info, sizeof(info), "GSP vGPU-IO Driver v%s (FW: 570.144W)", DRIVER_VERSION);
+ ret = copy_to_user(argp, info, sizeof(info)) ? -EFAULT : 0;
+ break;
+ }
+ default:
+ ret = -ENOTTY;
+ }
+
+ mutex_unlock(&gsp->chardev_lock);
+ return ret;
+}
+
+/* PCI Device IDs */
+static const struct pci_device_id gsp_pci_ids[] = {
+ { PCI_DEVICE(0x10DE, 0x1E07), .driver_data = 0 }, /* TU102 */
+ { PCI_DEVICE(0x10DE, 0x1E08), .driver_data = 0 }, /* TU104 */
+ { PCI_DEVICE(0x10DE, 0x1ED3), .driver_data = 0 }, /* TU104 RTX 2080 Super Mobile */
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, gsp_pci_ids);
+
+/* Module Parameters */
+static int max_vfs = 7;
+module_param(max_vfs, int, 0444);
+MODULE_PARM_DESC(max_vfs, "Maximum number of VFs (default: 7)");
+
+static char *firmware_name = "gsp-570.144.fwimage.bin";
+module_param(firmware_name, charp, 0444);
+MODULE_PARM_DESC(firmware_name, "GSP firmware filename (for version verification only)");
+
+/* PCI Probe */
+int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct gsp_device *gsp;
+ int ret;
+
+ gsp = devm_kzalloc(&pdev->dev, sizeof(*gsp), GFP_KERNEL);
+ if (!gsp)
+ return -ENOMEM;
+
+ gsp->pdev = pdev;
+ gsp->dev = &pdev->dev;
+ gsp->max_vfs = max_vfs;
+ pci_set_drvdata(pdev, gsp);
+
+ mutex_init(&gsp->vf_lock);
+ idr_init(&gsp->vf_idr);
+ INIT_LIST_HEAD(&gsp->vf_list);
+
+ /* Enable PCI device */
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to enable PCI device: %d\n", ret);
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ /* Map BARs */
+ gsp->bar0 = pcim_iomap(pdev, 0, 0);
+ gsp->bar1 = pcim_iomap(pdev, 1, 0);
+ if (!gsp->bar0 || !gsp->bar1) {
+ dev_err(&pdev->dev, "Failed to map BARs\n");
+ return -ENOMEM;
+ }
+
+ dev_info(&pdev->dev, "BAR0: %p, BAR1: %p\n", gsp->bar0, gsp->bar1);
+
+ /* Load Firmware (for version verification only - GSP already booted by host) */
+ ret = gsp_firmware_load(gsp);
+ if (ret) {
+ dev_err(&pdev->dev, "Firmware load failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Initialize RPC client (sets up rings, doorbells) */
+ gsp->rpc = gsp_rpc_client_init(gsp);
+ if (IS_ERR(gsp->rpc)) {
+ ret = PTR_ERR(gsp->rpc);
+ dev_err(&pdev->dev, "RPC client init failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Initialize RPC communication with GSP */
+ ret = gsp_rpc_init(gsp);
+ if (ret) {
+ dev_err(&pdev->dev, "RPC init failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Setup IRQ */
+ ret = gsp_setup_irq(gsp);
+ if (ret) {
+ dev_err(&pdev->dev, "IRQ setup failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Initialize char device */
+ mutex_init(&gsp->chardev_lock);
+ gsp->miscdev.minor = MISC_DYNAMIC_MINOR;
+ gsp->miscdev.name = "gsp-vgpu";
+ gsp->miscdev.fops = &gsp_fops;
+ gsp->miscdev.parent = &pdev->dev;
+ gsp->miscdev.mode = 0660;
+
+ ret = misc_register(&gsp->miscdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register misc device: %d\n", ret);
+ return ret;
+ }
+
+ /* Debugfs */
+ gsp_debugfs_init(gsp);
+
+ dev_info(&pdev->dev, "GSP vGPU-IO driver loaded (FW: 570.144W)\n");
+ return 0;
+}
+
+/* PCI Remove */
+void gsp_remove(struct pci_dev *pdev)
+{
+ struct gsp_device *gsp = pci_get_drvdata(pdev);
+ struct gsp_vf *vf, *tmp;
+
+ if (!gsp)
+ return;
+
+ /* Destroy all VFs */
+ list_for_each_entry_safe(vf, tmp, &gsp->vf_list, list)
+ gsp_vf_destroy(vf);
+
+ /* Unregister char device */
+ misc_deregister(&gsp->miscdev);
+
+ gsp_debugfs_fini(gsp);
+ gsp_shutdown(gsp);
+ gsp_rpc_client_fini(gsp->rpc);
+
+ if (gsp->fw)
+ release_firmware(gsp->fw);
+
+ idr_destroy(&gsp->vf_idr);
+
+ dev_info(&pdev->dev, "GSP vGPU-IO driver unloaded\n");
+}
+
+/* Firmware Load - only for version verification */
+int gsp_firmware_load(struct gsp_device *gsp)
+{
+ const struct firmware *fw;
+ int ret;
+
+ ret = request_firmware(&fw, firmware_name, gsp->dev);
+ if (ret) {
+ dev_err(gsp->dev, "Failed to load firmware '%s': %d\n", firmware_name, ret);
+ return ret;
+ }
+
+ /* Verify MD5: 13ce12a392a31eeb79a2e6ebd3a4a93a */
+ if (fw->size != 0x1C50000) { /* ~28.5 MB */
+ dev_warn(gsp->dev, "Firmware size unexpected: %zu (expected ~28.5MB)\n", fw->size);
+ }
+
+ dev_info(gsp->dev, "Firmware verified: %zu bytes\n", fw->size);
+ gsp->fw = fw;
+ return 0;
+}
+
+/* Initialize RPC communication with already-running GSP */
+int gsp_rpc_init(struct gsp_device *gsp)
+{
+ /*
+ * The GSP is already running (booted by host GRID driver).
+ * We just need to verify communication works by reading mailbox registers
+ * and optionally sending a test RPC.
+ */
+
+ /* Read mailbox registers to verify GSP is responsive */
+ uint32_t mbox0 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX0);
+ uint32_t mbox1 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX1);
+
+ dev_info(gsp->dev, "GSP mailbox0: 0x%08x, mailbox1: 0x%08x\n", mbox0, mbox1);
+
+ /* Check if GSP appears to be running (non-zero mailbox or queue heads) */
+ uint32_t qhead0 = readl(gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0));
+ dev_info(gsp->dev, "GSP queue head 0: 0x%08x\n", qhead0);
+
+ /* Ring doorbell to signal we're ready - write to queue head */
+ writel(1, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0));
+
+ gsp->fw_loaded = true; /* Indicates GSP communication established */
+ dev_info(gsp->dev, "GSP RPC communication initialized\n");
+
+ /* Auto-detect emulation mode if not overridden */
+ if (!emulation_override) {
+ gsp_detect_emulation_mode(gsp);
+ } else if (emulation_mode) {
+ dev_info(gsp->dev, "Emulation mode FORCED ON by module parameter\n");
+ } else {
+ dev_info(gsp->dev, "Emulation mode FORCED OFF by module parameter\n");
+ }
+
+ return 0;
+}
+
+/* Auto-detect if GSP has vGPU RPC handlers */
+static void gsp_detect_emulation_mode(struct gsp_device *gsp)
+{
+ struct gsp_bar_trap_payload test_payload = {0};
+ struct gsp_bar_trap_response test_resp = {0};
+ size_t resp_size = sizeof(test_resp);
+ int ret;
+
+ dev_info(gsp->dev, "Auto-detecting GSP vGPU RPC handler support...\n");
+
+ /* Send a test BAR_TRAP RPC to check if GSP has vGPU handlers */
+ test_payload.bar_index = 1;
+ test_payload.trap_type = GSP_BAR_READ; /* Non-destructive read */
+ test_payload.offset = 0;
+ test_payload.value = 0;
+ test_payload.size = 8;
+ test_payload.vf_id = 0;
+
+ /* Try synchronous RPC with short timeout (1 second) */
+ ret = gsp_rpc_send_sync(gsp->rpc, GSP_RPC_BAR_TRAP,
+ &test_payload, sizeof(test_payload),
+ &test_resp, &resp_size);
+
+ if (ret == -ETIMEDOUT || ret == -ENOTTY || ret == -EIO || ret == -ENODEV) {
+ /* GSP doesn't have vGPU handlers - enable emulation */
+ emulation_mode = true;
+ dev_info(gsp->dev, "GSP lacks vGPU RPC handlers (ret=%d) - ENABLING emulation mode\n", ret);
+ } else if (ret == 0) {
+ /* GSP responded - has vGPU handlers */
+ emulation_mode = false;
+ dev_info(gsp->dev, "GSP has vGPU RPC handlers - DISABLING emulation mode\n");
+ } else {
+ /* Other error - default to emulation for safety */
+ emulation_mode = true;
+ dev_info(gsp->dev, "GSP RPC test returned %d - ENABLING emulation mode (safe default)\n", ret);
+ }
+}
+
+/* GSP Shutdown - just clean up our side */
+void gsp_shutdown(struct gsp_device *gsp)
+{
+ if (gsp->fw_loaded) {
+ /* Don't reset GSP - host driver owns it */
+ gsp->fw_loaded = false;
+ }
+}
+
+/* IRQ Setup */
+int gsp_setup_irq(struct gsp_device *gsp)
+{
+ int ret;
+ struct msix_entry entries[1];
+
+ /* Try MSI-X first */
+ ret = pci_enable_msix_range(gsp->pdev, entries, 1, 1);
+ if (ret > 0) {
+ gsp->msix_enabled = true;
+ gsp->irq = entries[0].vector;
+ gsp->msix_entries[0] = entries[0];
+ } else {
+ gsp->irq = gsp->pdev->irq;
+ }
+
+ ret = devm_request_irq(gsp->dev, gsp->irq, gsp_isr,
+ gsp->msix_enabled ? 0 : IRQF_SHARED,
+ DRIVER_NAME, gsp);
+ if (ret) {
+ dev_err(gsp->dev, "Failed to request IRQ %d: %d\n", gsp->irq, ret);
+ return ret;
+ }
+
+ dev_info(gsp->dev, "IRQ %d registered (MSI-X: %s)\n",
+ gsp->irq, gsp->msix_enabled ? "yes" : "no");
+ return 0;
+}
+
+/* PCI Driver Structure */
+static struct pci_driver gsp_pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = gsp_pci_ids,
+ .probe = gsp_probe,
+ .remove = gsp_remove,
+};
+
+/* Module Init/Exit */
+static int __init gsp_module_init(void)
+{
+ return pci_register_driver(&gsp_pci_driver);
+}
+
+static void __exit gsp_module_exit(void)
+{
+ pci_unregister_driver(&gsp_pci_driver);
+}
+
+module_init(gsp_module_init);
+module_exit(gsp_module_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("NVIDIA vGPU-IO Team");
+MODULE_DESCRIPTION("NVIDIA GSP 570.144W vGPU-IO Driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_FIRMWARE("gsp-570.144.fwimage.bin");
diff --git a/legacy-driver-guest/debugfs/gsp_debugfs.c b/legacy-driver-guest/debugfs/gsp_debugfs.c
new file mode 100644
index 0000000..f3df124
--- /dev/null
+++ b/legacy-driver-guest/debugfs/gsp_debugfs.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - Debugfs Interface
+ */
+
+#include "gsp_core.h"
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+static int gsp_debugfs_vfs_show(struct seq_file *s, void *unused)
+{
+ struct gsp_device *gsp = s->private;
+ struct gsp_vf *vf;
+
+ seq_printf(s, "%-4s %-12s %-10s %-18s %-18s\n",
+ "ID", "STATE", "REFCNT", "BAR1", "PAGE_TABLE");
+
+ mutex_lock(&gsp->vf_lock);
+ list_for_each_entry(vf, &gsp->vf_list, list) {
+ seq_printf(s, "%-4u %-12d %-10d 0x%016llx-0x%016llx 0x%016llx-0x%016llx\n",
+ vf->id, vf->state, atomic_read(&vf->refcount),
+ vf->bar1_offset, vf->bar1_offset + vf->bar1_size,
+ vf->pt_base, vf->pt_base + vf->pt_size);
+ }
+ mutex_unlock(&gsp->vf_lock);
+
+ return 0;
+}
+
+static int gsp_debugfs_vfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_vfs_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_vfs_fops = {
+ .open = gsp_debugfs_vfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int gsp_debugfs_vf_show(struct seq_file *s, void *unused)
+{
+ struct gsp_vf *vf = s->private;
+ struct gsp_wpr_region *rgn;
+
+ seq_printf(s, "VF %u\n", vf->id);
+ seq_printf(s, " State: %d\n", vf->state);
+ seq_printf(s, " Refcount: %d\n", atomic_read(&vf->refcount));
+ seq_printf(s, " BAR1: 0x%llx - 0x%llx (size: 0x%llx)\n",
+ vf->bar1_offset, vf->bar1_offset + vf->bar1_size, vf->bar1_size);
+ seq_printf(s, " Page Table: 0x%llx - 0x%llx (size: 0x%llx)\n",
+ vf->pt_base, vf->pt_base + vf->pt_size, vf->pt_size);
+ seq_printf(s, " Cmd Buffer: 0x%llx - 0x%llx (size: 0x%llx)\n",
+ vf->cmd_base, vf->cmd_base + vf->cmd_size, vf->cmd_size);
+ seq_printf(s, " BAR State:\n");
+ seq_printf(s, " Aperture: 0x%llx - 0x%llx\n",
+ vf->bar_state.aperture_offset,
+ vf->bar_state.aperture_offset + vf->bar_state.aperture_size);
+ seq_printf(s, " ForceAcc: %s (VF: %u)\n",
+ vf->bar_state.force_access ? "enabled" : "disabled",
+ vf->bar_state.force_vf);
+ seq_printf(s, " WPR Regions:\n");
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ seq_printf(s, " [0x%llx, 0x%llx) flags=0x%x\n",
+ rgn->base, rgn->base + rgn->size, rgn->flags);
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ if (vf->migration) {
+ seq_printf(s, " Migration:\n");
+ seq_printf(s, " State: %d\n", vf->migration->state);
+ seq_printf(s, " Dst VF: %llu\n", vf->migration->dst_vf);
+ seq_printf(s, " Token: 0x%llx\n", vf->migration->token);
+ seq_printf(s, " Progress: %llu pages, %llu bytes\n",
+ vf->migration->progress.pages_transferred,
+ vf->migration->progress.bytes_transferred);
+ }
+
+ return 0;
+}
+
+static int gsp_debugfs_vf_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_vf_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_vf_fops = {
+ .open = gsp_debugfs_vf_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int gsp_debugfs_rpc_show(struct seq_file *s, void *unused)
+{
+ struct gsp_device *gsp = s->private;
+
+ seq_printf(s, "RPC Statistics:\n");
+ seq_printf(s, " Total: %lld\n", atomic64_read(&gsp->rpc_total));
+ seq_printf(s, " Errors: %lld\n", atomic64_read(&gsp->rpc_errors));
+ seq_printf(s, " BAR Traps: %lld\n", atomic64_read(&gsp->bar_traps));
+ seq_printf(s, " WPR Events: %lld\n", atomic64_read(&gsp->wpr_events));
+ seq_printf(s, " Migrations: %lld\n", atomic64_read(&gsp->migrations));
+
+ return 0;
+}
+
+static int gsp_debugfs_rpc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_rpc_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_rpc_fops = {
+ .open = gsp_debugfs_rpc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int gsp_debugfs_bar_show(struct seq_file *s, void *unused)
+{
+ struct gsp_vf *vf = s->private;
+
+ seq_printf(s, "VF %u BAR State:\n", vf->id);
+ seq_printf(s, " Aperture Offset: 0x%llx\n", vf->bar_state.aperture_offset);
+ seq_printf(s, " Aperture Size: 0x%llx\n", vf->bar_state.aperture_size);
+ seq_printf(s, " Force Access: %s\n", vf->bar_state.force_access ? "enabled" : "disabled");
+ seq_printf(s, " Force VF: %u\n", vf->bar_state.force_vf);
+ seq_printf(s, " Backing Store: %p\n", vf->bar_state.backing_store);
+ seq_printf(s, " Backing Size: 0x%zx\n", vf->bar_state.backing_size);
+
+ return 0;
+}
+
+static int gsp_debugfs_bar_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_bar_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_bar_fops = {
+ .open = gsp_debugfs_bar_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int gsp_debugfs_wpr_show(struct seq_file *s, void *unused)
+{
+ struct gsp_vf *vf = s->private;
+ struct gsp_wpr_region *rgn;
+
+ seq_printf(s, "VF %u WPR Regions:\n", vf->id);
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ seq_printf(s, " [0x%llx, 0x%llx) size=0x%llx flags=0x%x\n",
+ rgn->base, rgn->base + rgn->size, rgn->size, rgn->flags);
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ return 0;
+}
+
+static int gsp_debugfs_wpr_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_wpr_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_wpr_fops = {
+ .open = gsp_debugfs_wpr_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int gsp_debugfs_migration_show(struct seq_file *s, void *unused)
+{
+ struct gsp_vf *vf = s->private;
+
+ if (!vf->migration) {
+ seq_printf(s, "No active migration\n");
+ return 0;
+ }
+
+ seq_printf(s, "VF %u Migration:\n", vf->id);
+ seq_printf(s, " State: %d\n", vf->migration->state);
+ seq_printf(s, " Destination VF: %llu\n", vf->migration->dst_vf);
+ seq_printf(s, " Token: 0x%llx\n", vf->migration->token);
+ seq_printf(s, " Flags: 0x%x\n", vf->migration->flags);
+ seq_printf(s, " Pages Xfer: %llu\n", vf->migration->progress.pages_transferred);
+ seq_printf(s, " Bytes Xfer: %llu\n", vf->migration->progress.bytes_transferred);
+ seq_printf(s, " Dirty Pages: %llu\n", vf->migration->progress.dirty_pages);
+ seq_printf(s, " Cancelled: %s\n", vf->migration->cancelled ? "yes" : "no");
+
+ return 0;
+}
+
+static int gsp_debugfs_migration_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_debugfs_migration_show, inode->i_private);
+}
+
+static const struct file_operations gsp_debugfs_migration_fops = {
+ .open = gsp_debugfs_migration_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* VF Debugfs Directory Creation */
+static void gsp_debugfs_vf_create(struct gsp_vf *vf)
+{
+ char name[16];
+ struct dentry *vf_dir;
+
+ snprintf(name, sizeof(name), "vf%u", vf->id);
+ vf_dir = debugfs_create_dir(name, vf->gsp->debugfs_vfs_dir);
+ if (!vf_dir)
+ return;
+
+ debugfs_create_file("status", 0444, vf_dir, vf, &gsp_debugfs_vf_fops);
+ debugfs_create_file("bar", 0444, vf_dir, vf, &gsp_debugfs_bar_fops);
+ debugfs_create_file("wpr", 0444, vf_dir, vf, &gsp_debugfs_wpr_fops);
+ debugfs_create_file("migration", 0444, vf_dir, vf, &gsp_debugfs_migration_fops);
+}
+
+/* Main Debugfs Init */
+int gsp_debugfs_init(struct gsp_device *gsp)
+{
+ gsp->debugfs_root = debugfs_create_dir("gsp-vgpu", NULL);
+ if (!gsp->debugfs_root)
+ return -ENOMEM;
+
+ debugfs_create_file("vfs", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_vfs_fops);
+ debugfs_create_file("rpc", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_rpc_fops);
+
+ gsp->debugfs_vfs_dir = debugfs_create_dir("vfs", gsp->debugfs_root);
+ if (!gsp->debugfs_vfs_dir) {
+ debugfs_remove_recursive(gsp->debugfs_root);
+ return -ENOMEM;
+ }
+
+ /* Create entries for existing VFs */
+ mutex_lock(&gsp->vf_lock);
+ struct gsp_vf *vf;
+ list_for_each_entry(vf, &gsp->vf_list, list)
+ gsp_debugfs_vf_create(vf);
+ mutex_unlock(&gsp->vf_lock);
+
+ return 0;
+}
+
+void gsp_debugfs_fini(struct gsp_device *gsp)
+{
+ if (gsp->debugfs_root)
+ debugfs_remove_recursive(gsp->debugfs_root);
+}
+
+/* Export for VF creation/destruction */
+void gsp_debugfs_vf_add(struct gsp_vf *vf)
+{
+ gsp_debugfs_vf_create(vf);
+}
+
+void gsp_debugfs_vf_remove(struct gsp_vf *vf)
+{
+ char name[16];
+ snprintf(name, sizeof(name), "vf%u", vf->id);
+ debugfs_remove_recursive(debugfs_lookup(name, vf->gsp->debugfs_vfs_dir));
+} \ No newline at end of file
diff --git a/legacy-driver-guest/include/gsp_core.h b/legacy-driver-guest/include/gsp_core.h
new file mode 100644
index 0000000..569d6f8
--- /dev/null
+++ b/legacy-driver-guest/include/gsp_core.h
@@ -0,0 +1,613 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - Core Definitions
+ * NVIDIA GSP 570.144W (TU102/TU104) Firmware Interface
+ */
+
+#ifndef _GSP_CORE_H_
+#define _GSP_CORE_H_
+
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+
+/* Firmware Constants */
+#define GSP_FW_BASE_ADDR 0x04000000ULL
+#define GSP_FW_SIZE 0x02000000ULL
+#define GSP_ENTRY_POINT 0x04009000ULL
+#define GSP_PRIMARY_DISPATCHER 0x057A1AC0ULL
+#define GSP_SECONDARY_DISPATCHER 0x04ED39DCULL
+#define GSP_RPC_JUMP_TABLE 0x059BFEB0ULL
+
+/* PCIe BAR Offsets */
+#define GSP_BAR0_FALCON_ENGINE 0x1103c0
+#define GSP_BAR0_FALCON_MAILBOX0 0x110040
+#define GSP_BAR0_FALCON_MAILBOX1 0x110044
+#define GSP_BAR0_MAILBOX(i) (0x110804 + (i) * 4)
+#define GSP_BAR0_QUEUE_HEAD(i) (0x110c00 + (i) * 8)
+
+/* IOCTL definitions (must match userspace libvgpu.h) */
+#define GSP_IOCTL_MAGIC 'G'
+#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config)
+#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t)
+#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args)
+#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args)
+#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args)
+#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args)
+#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args)
+#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args)
+#define GSP_IOCTL_BAR_CONFIG _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_config_args)
+#define GSP_IOCTL_BAR_FORCE _IOW(GSP_IOCTL_MAGIC, 0x17, struct gsp_bar_force_args)
+#define GSP_IOCTL_BAR_SAVE _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_bar_save_args)
+#define GSP_IOCTL_BAR_RESTORE _IOWR(GSP_IOCTL_MAGIC, 0x19, struct gsp_bar_restore_args)
+#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x1A, struct gsp_wpr_args)
+#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x1B, struct gsp_wpr_args)
+#define GSP_IOCTL_WPR_TIMEOUT _IOW(GSP_IOCTL_MAGIC, 0x1C, struct gsp_wpr_timeout_args)
+#define GSP_IOCTL_SCHEDULE _IOW(GSP_IOCTL_MAGIC, 0x1D, struct gsp_schedule_args)
+#define GSP_IOCTL_YIELD _IOW(GSP_IOCTL_MAGIC, 0x1E, uint32_t)
+#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x1F, struct gsp_migrate_args)
+#define GSP_IOCTL_GET_INFO _IOR(GSP_IOCTL_MAGIC, 0x20, char[256])
+
+/* Kernel argument structures (matching userspace) */
+struct gsp_vf_config {
+ uint32_t id;
+ uint64_t bar1_offset;
+ uint64_t bar1_size;
+ uint64_t pt_base;
+ uint64_t pt_size;
+ uint64_t cmd_base;
+ uint64_t cmd_size;
+ uint32_t max_memory_mb;
+ uint32_t num_engines;
+};
+
+struct gsp_alloc_args {
+ uint32_t vf_id;
+ uint64_t size;
+ uint32_t type;
+ uint64_t alignment;
+ uint64_t va_hint;
+ uint32_t flags;
+ uint32_t pid;
+ uint64_t handle;
+ uint64_t va;
+ uint64_t actual_size;
+};
+
+struct gsp_free_args {
+ uint32_t vf_id;
+ uint64_t handle;
+};
+
+struct gsp_map_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t gpu_va;
+ uint64_t size;
+ uint32_t prot;
+ uint32_t flags;
+ uint64_t offset;
+ uint64_t mapped_va;
+};
+
+struct gsp_unmap_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t gpu_va;
+ uint64_t size;
+};
+
+struct gsp_pin_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t offset;
+ uint64_t size;
+ uint32_t flags;
+ uint64_t dma_addr;
+ uint32_t num_pages;
+};
+
+struct gsp_unpin_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t offset;
+ uint64_t size;
+};
+
+struct gsp_bar_config_args {
+ uint32_t vf_id;
+ uint64_t aperture_offset;
+ uint64_t aperture_size;
+};
+
+struct gsp_bar_force_args {
+ uint32_t vf_id;
+ uint32_t enable;
+};
+
+struct gsp_bar_save_args {
+ uint32_t vf_id;
+ void *buf;
+ size_t *size;
+};
+
+struct gsp_bar_restore_args {
+ uint32_t vf_id;
+ void *buf;
+ size_t size;
+};
+
+struct gsp_wpr_args {
+ uint32_t vf_id;
+ uint64_t base;
+ uint64_t size;
+ uint32_t flags;
+};
+
+struct gsp_wpr_timeout_args {
+ uint32_t vf_id;
+ uint64_t timeout_ns;
+};
+
+struct gsp_schedule_args {
+ uint32_t vf_id;
+ uint32_t priority;
+ uint64_t time_slice_ns;
+};
+
+struct gsp_migrate_args {
+ uint32_t vf_id;
+ uint64_t dst_vf;
+ uint32_t phase;
+ uint32_t flags;
+ uint64_t token;
+};
+
+/* Message Ring */
+#define GSP_MSG_RING_SIZE 256
+#define GSP_MSG_MAX_SIZE 256
+#define GSP_MAX_PENDING 64
+
+/* RPC Types */
+enum gsp_rpc_type {
+ GSP_RPC_ALLOC = 0x01,
+ GSP_RPC_FREE = 0x02,
+ GSP_RPC_MAP = 0x03,
+ GSP_RPC_UNMAP = 0x04,
+ GSP_RPC_PIN = 0x05,
+ GSP_RPC_UNPIN = 0x06,
+ GSP_RPC_SCHEDULE = 0x07,
+ GSP_RPC_YIELD = 0x08,
+ GSP_RPC_NOTIFY = 0x09,
+ GSP_RPC_BAR_TRAP = 0x0A,
+ GSP_RPC_MIGRATE = 0x0B,
+ GSP_RPC_DEBUG = 0x0C,
+};
+
+/* RPC Message Flags */
+#define GSP_MSG_SYNC (1 << 0)
+#define GSP_MSG_ASYNC (1 << 1)
+#define GSP_MSG_NOREPLY (1 << 2)
+
+/* Memory Types */
+enum gsp_mem_type {
+ GSP_MEM_VRAM = 0,
+ GSP_MEM_SYSMEM = 1,
+ GSP_MEM_UNCACHED = 2,
+};
+
+/* Pin Flags */
+#define GSP_PIN_READ (1 << 0)
+#define GSP_PIN_WRITE (1 << 1)
+#define GSP_PIN_LONGTERM (1 << 2)
+
+/* BAR Trap Types */
+#define GSP_BAR_READ 0
+#define GSP_BAR_WRITE 1
+#define GSP_BAR_ATOMIC 2
+
+/* Migration Phases */
+enum gsp_migrate_phase {
+ GSP_MIG_QUERY = 0,
+ GSP_MIG_PREPARE = 1,
+ GSP_MIG_TRANSFER = 2,
+ GSP_MIG_COMMIT = 3,
+ GSP_MIG_CLEANUP = 4,
+};
+
+/* Migration Flags */
+#define GSP_MIG_LIVE (1 << 0)
+#define GSP_MIG_POSTCOPY (1 << 1)
+#define GSP_MIG_AUTO_COMMIT (1 << 2)
+#define GSP_MIG_VERIFY (1 << 3)
+
+/* Error Codes (from firmware) */
+#define GSP_OK 0
+#define GSP_EPERM 0xFFFFFFFF
+#define GSP_ENOENT 0xFFFFFFFE
+#define GSP_EINVAL 0xFFFFFFFD
+#define GSP_ENODEV 0xFFFFFFFA
+#define GSP_ENOMEM 0xFFFFFFF4
+#define GSP_EBUSY 0xFFFFFFEE
+#define GSP_ENOSPC 0xFFFFFFE4
+#define GSP_ETIMEDOUT 0x0000001E
+#define GSP_EOVERFLOW 0x00000025
+#define GSP_EDEV 0x0000001F
+
+/* Forward Declarations */
+struct gsp_device;
+struct gsp_vf;
+struct gsp_rpc_client;
+struct gsp_migration;
+struct gsp_wpr_region;
+
+/* WPR Region */
+struct gsp_wpr_region {
+ struct list_head list;
+ uint64_t base;
+ uint64_t size;
+ uint32_t flags;
+ uint32_t vf_id;
+ void (*completion_cb)(struct gsp_vf *, uint64_t, uint32_t);
+ void (*timeout_cb)(struct gsp_vf *, uint64_t, uint32_t);
+ uint64_t timeout_ns;
+};
+
+/* WPR Flags */
+#define GSP_WPR_WRITE (1 << 0)
+#define GSP_WPR_READ (1 << 1)
+#define GSP_WPR_EXECUTE (1 << 2)
+
+/* WPR Event Types */
+enum gsp_wpr_event {
+ GSP_WPR_WRITE_COMPLETE = 0,
+ GSP_WPR_READ_COMPLETE = 1,
+ GSP_WPR_WRITE_TIMEOUT = 2,
+ GSP_WPR_READ_TIMEOUT = 3,
+};
+
+/* Migration State */
+enum gsp_mig_state {
+ GSP_MIG_S_IDLE = 0,
+ GSP_MIG_S_QUERY = 1,
+ GSP_MIG_S_PREPARE = 2,
+ GSP_MIG_S_TRANSFER = 3,
+ GSP_MIG_S_COMMIT = 4,
+ GSP_MIG_S_CLEANUP = 5,
+ GSP_MIG_S_FAILED = 6,
+ GSP_MIG_S_COMPLETED = 7,
+};
+
+/* Migration Context */
+struct gsp_migration {
+ struct gsp_vf *vf;
+ struct mutex lock;
+ enum gsp_mig_state state;
+ uint64_t token;
+ uint64_t dst_vf;
+ uint32_t flags;
+
+ struct {
+ uint64_t pages_transferred;
+ uint64_t bytes_transferred;
+ uint64_t dirty_pages;
+ uint64_t *page_list;
+ uint32_t page_count;
+ } progress;
+
+ struct {
+ void *buf;
+ size_t size;
+ } bar_state;
+
+ struct task_struct *thread;
+ struct completion comp;
+ bool cancelled;
+ int error;
+};
+
+/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */
+extern bool emulation_mode;
+
+/* RPC Message Header (64 bytes) */
+struct gsp_rpc_header {
+ uint32_t magic; /* 0x47535052 "GSPR" */
+ uint32_t version; /* Protocol version */
+ uint32_t rpc_type; /* enum gsp_rpc_type */
+ uint32_t sequence; /* Monotonic sequence */
+ uint64_t timestamp_ns; /* Submission timestamp */
+ uint32_t payload_size; /* Payload size in bytes */
+ uint32_t flags; /* GSP_MSG_* */
+ uint64_t correlation_id; /* Async matching */
+ uint64_t reserved[3]; /* Padding to 64 bytes */
+} __attribute__((packed));
+
+/* Complete RPC Message */
+struct gsp_rpc_message {
+ struct gsp_rpc_header hdr;
+ uint8_t payload[GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)];
+} __attribute__((packed));
+
+/* RPC Response Header */
+struct gsp_rpc_response {
+ int64_t status; /* 0 = success, negative = error */
+ uint32_t rpc_type; /* Echo of request */
+ uint32_t sequence; /* Echo of request */
+ uint64_t timestamp_ns; /* Completion timestamp */
+ uint32_t payload_size; /* Response payload size */
+ uint32_t reserved;
+ uint8_t payload[];
+} __attribute__((packed));
+
+/* Primary Dispatcher Sub-Command Descriptor */
+struct gsp_subcmd_desc {
+ uint32_t subcmd_id; /* Constant ID (e.g., 0x10, 0x14, 0x40) */
+ uint8_t echo_flags_b; /* Copy of input flags_b */
+ uint8_t echo_flags_a; /* Copy of input flags_a */
+ uint8_t echo_flags_c; /* Copy of input flags_c */
+ uint8_t reserved;
+} __attribute__((packed));
+
+/* ALLOC Payload */
+struct gsp_alloc_payload {
+ uint64_t size;
+ uint32_t type; /* enum gsp_mem_type */
+ uint32_t alignment;
+ uint64_t va_hint;
+ uint32_t flags;
+ uint32_t pid;
+ uint32_t vf_id;
+ uint32_t reserved;
+} __attribute__((packed));
+
+struct gsp_alloc_response {
+ uint64_t handle;
+ uint64_t va;
+ uint64_t pa;
+} __attribute__((packed));
+
+/* PIN Payload */
+struct gsp_pin_payload {
+ uint64_t handle;
+ uint64_t offset;
+ uint64_t size;
+ uint32_t vf_id;
+ uint32_t flags; /* GSP_PIN_* */
+ uint32_t reserved[2];
+} __attribute__((packed));
+
+struct gsp_pin_response {
+ uint64_t dma_addr;
+} __attribute__((packed));
+
+/* BAR Trap Payload */
+struct gsp_bar_trap_payload {
+ uint32_t bar_index;
+ uint32_t trap_type; /* GSP_BAR_* */
+ uint64_t offset;
+ uint64_t value;
+ uint32_t size;
+ uint32_t vf_id;
+ uint32_t reserved;
+} __attribute__((packed));
+
+struct gsp_bar_trap_response {
+ uint64_t value;
+} __attribute__((packed));
+
+/* Migration Payload */
+struct gsp_migrate_payload {
+ uint32_t phase; /* enum gsp_migrate_phase */
+ uint32_t subcmd; /* Sub-command index */
+ uint64_t src_vf;
+ uint64_t dst_vf;
+ uint64_t data_ptr;
+ uint64_t data_size;
+ uint32_t flags; /* GSP_MIG_* */
+ uint32_t reserved;
+ uint64_t token;
+} __attribute__((packed));
+
+/* Message Ring Buffer */
+struct gsp_msg_ring {
+ void *base;
+ size_t size;
+ uint32_t head;
+ uint32_t tail;
+ spinlock_t lock;
+};
+
+/* RPC Client */
+struct gsp_rpc_client {
+ struct gsp_device *gsp;
+ struct mutex lock;
+ atomic_t seq;
+ wait_queue_head_t wq;
+ struct gsp_msg_ring *tx_ring;
+ struct gsp_msg_ring *rx_ring;
+ dma_addr_t tx_ring_dma;
+ dma_addr_t rx_ring_dma;
+ struct completion *pending[GSP_MAX_PENDING];
+ struct gsp_rpc_response *resp_buf[GSP_MAX_PENDING];
+};
+
+/* BAR State per VF */
+struct gsp_bar_state {
+ uint64_t aperture_offset;
+ uint64_t aperture_size;
+ bool force_access;
+ uint32_t force_vf;
+ void *backing_store;
+ size_t backing_size;
+};
+
+/* VF State */
+enum gsp_vf_state {
+ GSP_VF_CREATING = 0,
+ GSP_VF_ACTIVE,
+ GSP_VF_MIGRATING,
+ GSP_VF_SUSPENDED,
+ GSP_VF_DESTROYING,
+};
+
+/* GSP Virtual Function */
+struct gsp_vf {
+ struct gsp_device *gsp;
+ uint32_t id;
+ struct kref ref;
+ struct mutex lock;
+ enum gsp_vf_state state;
+ struct gsp_migration *migration;
+
+ /* Resource configuration */
+ uint64_t bar1_offset;
+ uint64_t bar1_size;
+ uint64_t pt_base;
+ uint64_t pt_size;
+ uint64_t cmd_base;
+ uint64_t cmd_size;
+
+ /* WPR regions */
+ struct list_head wpr_list;
+ struct mutex wpr_lock;
+
+ /* BAR state */
+ struct gsp_bar_state bar_state;
+
+ atomic_t refcount;
+ bool active;
+ struct list_head list;
+};
+
+/* GSP Device */
+struct gsp_device {
+ struct pci_dev *pdev;
+ struct device *dev;
+ void __iomem *bar0;
+ void __iomem *bar1;
+
+ /* Firmware */
+ const struct firmware *fw;
+ bool fw_loaded;
+
+ /* RPC */
+ struct gsp_rpc_client *rpc;
+
+ /* VF Management */
+ struct mutex vf_lock;
+ struct idr vf_idr;
+ struct list_head vf_list;
+ uint32_t max_vfs;
+
+ /* IRQ */
+ int irq;
+ bool msix_enabled;
+ struct msix_entry msix_entries[1];
+
+ /* Debug */
+ struct dentry *debugfs_root;
+ struct dentry *debugfs_vfs_dir;
+
+ /* Char device */
+ struct miscdevice miscdev;
+ struct mutex chardev_lock;
+
+ /* Statistics */
+ atomic64_t rpc_total;
+ atomic64_t rpc_errors;
+ atomic64_t bar_traps;
+ atomic64_t wpr_events;
+ atomic64_t migrations;
+};
+
+/* Function Prototypes */
+
+/* Core */
+int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id);
+void gsp_remove(struct pci_dev *pdev);
+int gsp_firmware_load(struct gsp_device *gsp);
+int gsp_rpc_init(struct gsp_device *gsp);
+void gsp_shutdown(struct gsp_device *gsp);
+
+/* RPC Client */
+struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp);
+void gsp_rpc_client_fini(struct gsp_rpc_client *rpc);
+int gsp_rpc_send_sync(struct gsp_rpc_client *rpc,
+ enum gsp_rpc_type type,
+ void *payload, size_t payload_size,
+ void *response, size_t *response_size);
+int gsp_rpc_send_async(struct gsp_rpc_client *rpc,
+ enum gsp_rpc_type type,
+ void *payload, size_t payload_size,
+ void (*callback)(struct gsp_rpc_client *,
+ struct gsp_rpc_response *,
+ void *),
+ void *ctx);
+irqreturn_t gsp_isr(int irq, void *data);
+void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg);
+int gsp_errno_to_errno(int gsp_err);
+
+/* VF Management */
+int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config);
+void gsp_vf_destroy(struct gsp_vf *vf);
+struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id);
+void gsp_vf_put(struct gsp_vf *vf);
+
+/* Memory Management */
+int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags,
+ uint64_t *handle, uint64_t *va);
+int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle);
+int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle,
+ uint64_t offset, uint64_t size,
+ uint64_t *dma_addr);
+int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle);
+int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va,
+ uint64_t size, uint32_t prot, uint32_t flags,
+ uint64_t offset, uint64_t *mapped_va);
+int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size);
+
+/* BAR Emulation */
+int gsp_vf_bar_init(struct gsp_vf *vf);
+void gsp_vf_bar_fini(struct gsp_vf *vf);
+int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap);
+int gsp_bar_force_access(struct gsp_vf *vf, bool enable);
+int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size);
+int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size);
+
+/* WPR Management */
+int gsp_vf_wpr_init(struct gsp_vf *vf);
+void gsp_vf_wpr_fini(struct gsp_vf *vf);
+int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size,
+ uint32_t flags);
+int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base);
+int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size);
+int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size);
+int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns);
+int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type,
+ void (*cb)(struct gsp_vf *, uint64_t, uint32_t),
+ void *ctx);
+void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg);
+
+/* Scheduling */
+int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns);
+int gsp_vf_yield(struct gsp_vf *vf);
+
+/* Migration */
+int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf);
+void gsp_migration_cancel(struct gsp_migration *mig);
+int gsp_migration_status(struct gsp_migration *mig);
+
+/* Debugfs */
+int gsp_debugfs_init(struct gsp_device *gsp);
+void gsp_debugfs_fini(struct gsp_device *gsp);
+void gsp_debugfs_vf_add(struct gsp_vf *vf);
+void gsp_debugfs_vf_remove(struct gsp_vf *vf);
+
+#endif /* _GSP_CORE_H_ */ \ No newline at end of file
diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c
new file mode 100644
index 0000000..7918beb
--- /dev/null
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c
@@ -0,0 +1,432 @@
+/*
+ * GSP vGPU-IO Driver - Comprehensive Test Suite
+ * Tests all driver components: RPC, VF, BAR, WPR, Migration
+ * Compiles against kernel headers only (no driver dependency)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/ktime.h>
+#include <linux/atomic.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/kref.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/debugfs.h>
+#include <linux/vmalloc.h>
+
+#include <gsp_core.h>
+
+static int test_count = 0;
+static int pass_count = 0;
+static int fail_count = 0;
+
+#define TEST_ASSERT(cond, msg) \
+ do { \
+ test_count++; \
+ if (cond) { \
+ pass_count++; \
+ pr_info("PASS: %s\n", msg); \
+ } else { \
+ fail_count++; \
+ pr_err("FAIL: %s\n", msg); \
+ } \
+ } while (0)
+
+#define TEST_ASSERT_EQ(a, b, msg) \
+ TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")")
+
+#define TEST_ASSERT_NE(a, b, msg) \
+ TEST_ASSERT((a) != (b), msg " (expected not " #b ", got " #a ")")
+
+/* ============================================================
+ * Test: Kernel API Availability
+ * ============================================================ */
+
+static int test_kernel_apis(void)
+{
+ pr_info("=== Testing Kernel API Availability ===\n");
+
+ /* Test kref */
+ struct kref *ref = kmalloc(sizeof(*ref), GFP_KERNEL);
+ TEST_ASSERT(ref != NULL, "kref allocation");
+ kref_init(ref);
+ TEST_ASSERT(kref_read(ref) == 1, "kref_init sets refcount to 1");
+
+ kref_get(ref);
+ TEST_ASSERT(kref_read(ref) == 2, "kref_get increments refcount");
+
+ kref_put(ref, (void (*)(struct kref *))kfree);
+ TEST_ASSERT(kref_read(ref) == 1, "kref_put decrements refcount");
+
+ kref_put(ref, (void (*)(struct kref *))kfree);
+ TEST_ASSERT(kref_read(ref) == 0, "kref_put reaches zero");
+
+ /* Test completion */
+ struct completion comp;
+ init_completion(&comp);
+ TEST_ASSERT(!completion_done(&comp), "completion initially not done");
+ complete(&comp);
+ TEST_ASSERT(completion_done(&comp), "completion marked done");
+
+ init_completion(&comp);
+ TEST_ASSERT(wait_for_completion_timeout(&comp, msecs_to_jiffies(100)) > 0,
+ "wait_for_completion_timeout works");
+
+ /* Test atomic64 */
+ atomic64_t a64 = ATOMIC64_INIT(0);
+ atomic64_inc(&a64);
+ TEST_ASSERT_EQ(atomic64_read(&a64), 1, "atomic64_inc");
+ atomic64_add(5, &a64);
+ TEST_ASSERT_EQ(atomic64_read(&a64), 6, "atomic64_add");
+ atomic64_sub(3, &a64);
+ TEST_ASSERT_EQ(atomic64_read(&a64), 3, "atomic64_sub");
+
+ /* Test mutex */
+ struct mutex mtx;
+ mutex_init(&mtx);
+ mutex_lock(&mtx);
+ TEST_ASSERT(mutex_is_locked(&mtx), "mutex_is_locked after lock");
+ mutex_unlock(&mtx);
+ TEST_ASSERT(!mutex_is_locked(&mtx), "mutex_is_locked after unlock");
+
+ /* Test spinlock */
+ spinlock_t sl = __SPIN_LOCK_UNLOCKED(sl);
+ unsigned long flags;
+ spin_lock_irqsave(&sl, flags);
+ TEST_ASSERT(spin_is_locked(&sl), "spin_is_locked after lock");
+ spin_unlock_irqrestore(&sl, flags);
+ TEST_ASSERT(!spin_is_locked(&sl), "spin_is_locked after unlock");
+
+ /* Test idr */
+ struct idr idr;
+ idr_init(&idr);
+ int id = idr_alloc(&idr, (void *)0xdeadbeef, 1, 100, GFP_KERNEL);
+ TEST_ASSERT(id >= 1 && id < 100, "idr_alloc returns valid id");
+ void *ptr = idr_find(&idr, id);
+ TEST_ASSERT(ptr == (void *)0xdeadbeef, "idr_find returns correct pointer");
+ idr_remove(&idr, id);
+ TEST_ASSERT(idr_find(&idr, id) == NULL, "idr_remove works");
+ idr_destroy(&idr);
+
+ /* Test list */
+ struct list_head head;
+ INIT_LIST_HEAD(&head);
+ TEST_ASSERT(list_empty(&head), "list_empty on new list");
+
+ struct list_head item;
+ INIT_LIST_HEAD(&item);
+ list_add(&item, &head);
+ TEST_ASSERT(!list_empty(&head), "list not empty after add");
+ list_del(&item);
+ TEST_ASSERT(list_empty(&head), "list empty after del");
+
+ /* Test ktime */
+ ktime_t start = ktime_get();
+ ktime_t end = ktime_get();
+ s64 diff = ktime_to_ns(ktime_sub(end, start));
+ TEST_ASSERT(diff >= 0, "ktime_get and ktime_to_ns work");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Structure Sizes (from driver header)
+ * ============================================================ */
+
+static int test_structure_sizes(void)
+{
+ pr_info("=== Testing Structure Sizes ===\n");
+
+ /* These sizes must match driver/include/gsp_core.h */
+ TEST_ASSERT_EQ(sizeof(struct gsp_rpc_header), 64, "gsp_rpc_header size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_rpc_message), 256, "gsp_rpc_message size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_rpc_response), 32, "gsp_rpc_response size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_alloc_payload), 40, "gsp_alloc_payload size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_alloc_response), 24, "gsp_alloc_response size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_pin_payload), 40, "gsp_pin_payload size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_pin_response), 8, "gsp_pin_response size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_payload), 40, "gsp_bar_trap_payload size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_response), 8, "gsp_bar_trap_response size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_migrate_payload), 56, "gsp_migrate_payload size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_subcmd_desc), 8, "gsp_subcmd_desc size");
+ TEST_ASSERT_EQ(sizeof(struct gsp_vf_config), 56, "gsp_vf_config size");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Enum Values (from driver header)
+ * ============================================================ */
+
+static int test_enum_values(void)
+{
+ pr_info("=== Testing Enum Values ===\n");
+
+ TEST_ASSERT_EQ(GSP_RPC_ALLOC, 0x01, "RPC_ALLOC");
+ TEST_ASSERT_EQ(GSP_RPC_FREE, 0x02, "RPC_FREE");
+ TEST_ASSERT_EQ(GSP_RPC_MAP, 0x03, "RPC_MAP");
+ TEST_ASSERT_EQ(GSP_RPC_UNMAP, 0x04, "RPC_UNMAP");
+ TEST_ASSERT_EQ(GSP_RPC_PIN, 0x05, "RPC_PIN");
+ TEST_ASSERT_EQ(GSP_RPC_UNPIN, 0x06, "RPC_UNPIN");
+ TEST_ASSERT_EQ(GSP_RPC_SCHEDULE, 0x07, "RPC_SCHEDULE");
+ TEST_ASSERT_EQ(GSP_RPC_YIELD, 0x08, "RPC_YIELD");
+ TEST_ASSERT_EQ(GSP_RPC_NOTIFY, 0x09, "RPC_NOTIFY");
+ TEST_ASSERT_EQ(GSP_RPC_BAR_TRAP, 0x0A, "RPC_BAR_TRAP");
+ TEST_ASSERT_EQ(GSP_RPC_MIGRATE, 0x0B, "RPC_MIGRATE");
+ TEST_ASSERT_EQ(GSP_RPC_DEBUG, 0x0C, "RPC_DEBUG");
+
+ TEST_ASSERT_EQ(GSP_MEM_VRAM, 0, "MEM_VRAM");
+ TEST_ASSERT_EQ(GSP_MEM_SYSMEM, 1, "MEM_SYSMEM");
+ TEST_ASSERT_EQ(GSP_MEM_UNCACHED, 2, "MEM_UNCACHED");
+
+ TEST_ASSERT_EQ(GSP_PIN_READ, (1 << 0), "PIN_READ");
+ TEST_ASSERT_EQ(GSP_PIN_WRITE, (1 << 1), "PIN_WRITE");
+ TEST_ASSERT_EQ(GSP_PIN_LONGTERM, (1 << 2), "PIN_LONGTERM");
+
+ TEST_ASSERT_EQ(GSP_MIG_QUERY, 0, "MIG_QUERY");
+ TEST_ASSERT_EQ(GSP_MIG_PREPARE, 1, "MIG_PREPARE");
+ TEST_ASSERT_EQ(GSP_MIG_TRANSFER, 2, "MIG_TRANSFER");
+ TEST_ASSERT_EQ(GSP_MIG_COMMIT, 3, "MIG_COMMIT");
+ TEST_ASSERT_EQ(GSP_MIG_CLEANUP, 4, "MIG_CLEANUP");
+
+ TEST_ASSERT_EQ(GSP_MIG_LIVE, (1 << 0), "MIG_LIVE");
+ TEST_ASSERT_EQ(GSP_MIG_POSTCOPY, (1 << 1), "MIG_POSTCOPY");
+ TEST_ASSERT_EQ(GSP_MIG_AUTO_COMMIT, (1 << 2), "MIG_AUTO_COMMIT");
+ TEST_ASSERT_EQ(GSP_MIG_VERIFY, (1 << 3), "MIG_VERIFY");
+
+ TEST_ASSERT_EQ(GSP_VF_CREATING, 0, "VF_CREATING");
+ TEST_ASSERT_EQ(GSP_VF_ACTIVE, 1, "VF_ACTIVE");
+ TEST_ASSERT_EQ(GSP_VF_MIGRATING, 2, "VF_MIGRATING");
+ TEST_ASSERT_EQ(GSP_VF_SUSPENDED, 3, "VF_SUSPENDED");
+ TEST_ASSERT_EQ(GSP_VF_DESTROYING, 4, "VF_DESTROYING");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Error Code Mapping
+ * ============================================================ */
+
+static int test_error_codes(void)
+{
+ pr_info("=== Testing Error Codes ===\n");
+
+ TEST_ASSERT_EQ(GSP_OK, 0, "GSP_OK");
+ TEST_ASSERT_EQ(GSP_EPERM, 0xFFFFFFFF, "GSP_EPERM");
+ TEST_ASSERT_EQ(GSP_ENOENT, 0xFFFFFFFE, "GSP_ENOENT");
+ TEST_ASSERT_EQ(GSP_EINVAL, 0xFFFFFFFD, "GSP_EINVAL");
+ TEST_ASSERT_EQ(GSP_ENODEV, 0xFFFFFFFA, "GSP_ENODEV");
+ TEST_ASSERT_EQ(GSP_ENOMEM, 0xFFFFFFF4, "GSP_ENOMEM");
+ TEST_ASSERT_EQ(GSP_EBUSY, 0xFFFFFFEE, "GSP_EBUSY");
+ TEST_ASSERT_EQ(GSP_ENOSPC, 0xFFFFFFE4, "GSP_ENOSPC");
+ TEST_ASSERT_EQ(GSP_ETIMEDOUT, 0x0000001E, "GSP_ETIMEDOUT");
+ TEST_ASSERT_EQ(GSP_EOVERFLOW, 0x00000025, "GSP_EOVERFLOW");
+ TEST_ASSERT_EQ(GSP_EDEV, 0x0000001F, "GSP_EDEV");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Firmware Constants
+ * ============================================================ */
+
+static int test_firmware_constants(void)
+{
+ pr_info("=== Testing Firmware Constants ===\n");
+
+ TEST_ASSERT_EQ(GSP_FW_BASE_ADDR, 0x04000000ULL, "FW_BASE_ADDR");
+ TEST_ASSERT_EQ(GSP_FW_SIZE, 0x02000000ULL, "FW_SIZE");
+ TEST_ASSERT_EQ(GSP_ENTRY_POINT, 0x04009000ULL, "ENTRY_POINT");
+ TEST_ASSERT_EQ(GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL, "PRIMARY_DISPATCHER");
+ TEST_ASSERT_EQ(GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL, "SECONDARY_DISPATCHER");
+ TEST_ASSERT_EQ(GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL, "RPC_JUMP_TABLE");
+ TEST_ASSERT_EQ(GSP_BAR0_DOORBELL, 0x1000, "BAR0_DOORBELL");
+ TEST_ASSERT_EQ(GSP_BAR0_MSIX, 0x2000, "BAR0_MSIX");
+ TEST_ASSERT_EQ(GSP_MSG_RING_SIZE, 256, "MSG_RING_SIZE");
+ TEST_ASSERT_EQ(GSP_MSG_MAX_SIZE, 256, "MSG_MAX_SIZE");
+ TEST_ASSERT_EQ(GSP_MAX_PENDING, 64, "MAX_PENDING");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Migration State Enum (internal)
+ * ============================================================ */
+
+static int test_migration_states(void)
+{
+ pr_info("=== Testing Migration States ===\n");
+
+ TEST_ASSERT_EQ(GSP_MIG_S_IDLE, 0, "MIG_S_IDLE");
+ TEST_ASSERT_EQ(GSP_MIG_S_QUERY, 1, "MIG_S_QUERY");
+ TEST_ASSERT_EQ(GSP_MIG_S_PREPARE, 2, "MIG_S_PREPARE");
+ TEST_ASSERT_EQ(GSP_MIG_S_TRANSFER, 3, "MIG_S_TRANSFER");
+ TEST_ASSERT_EQ(GSP_MIG_S_COMMIT, 4, "MIG_S_COMMIT");
+ TEST_ASSERT_EQ(GSP_MIG_S_CLEANUP, 5, "MIG_S_CLEANUP");
+ TEST_ASSERT_EQ(GSP_MIG_S_FAILED, 6, "MIG_S_FAILED");
+ TEST_ASSERT_EQ(GSP_MIG_S_COMPLETED, 7, "MIG_S_COMPLETED");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: WPR Flags and Events
+ * ============================================================ */
+
+static int test_wpr_flags(void)
+{
+ pr_info("=== Testing WPR Flags ===\n");
+
+ TEST_ASSERT_EQ(GSP_WPR_WRITE, (1 << 0), "WPR_WRITE");
+ TEST_ASSERT_EQ(GSP_WPR_READ, (1 << 1), "WPR_READ");
+ TEST_ASSERT_EQ(GSP_WPR_EXECUTE, (1 << 2), "WPR_EXECUTE");
+
+ TEST_ASSERT_EQ(GSP_WPR_WRITE_COMPLETE, 0, "WPR_WRITE_COMPLETE");
+ TEST_ASSERT_EQ(GSP_WPR_READ_COMPLETE, 1, "WPR_READ_COMPLETE");
+ TEST_ASSERT_EQ(GSP_WPR_WRITE_TIMEOUT, 2, "WPR_WRITE_TIMEOUT");
+ TEST_ASSERT_EQ(GSP_WPR_READ_TIMEOUT, 3, "WPR_READ_TIMEOUT");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Message Flags
+ * ============================================================ */
+
+static int test_message_flags(void)
+{
+ pr_info("=== Testing Message Flags ===\n");
+
+ TEST_ASSERT_EQ(GSP_MSG_SYNC, (1 << 0), "MSG_SYNC");
+ TEST_ASSERT_EQ(GSP_MSG_ASYNC, (1 << 1), "MSG_ASYNC");
+ TEST_ASSERT_EQ(GSP_MSG_NOREPLY, (1 << 2), "MSG_NOREPLY");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: BAR Trap Types
+ * ============================================================ */
+
+static int test_bar_trap_types(void)
+{
+ pr_info("=== Testing BAR Trap Types ===\n");
+
+ TEST_ASSERT_EQ(GSP_BAR_READ, 0, "BAR_READ");
+ TEST_ASSERT_EQ(GSP_BAR_WRITE, 1, "BAR_WRITE");
+ TEST_ASSERT_EQ(GSP_BAR_ATOMIC, 2, "BAR_ATOMIC");
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: Memory Allocation Patterns
+ * ============================================================ */
+
+static int test_memory_patterns(void)
+{
+ pr_info("=== Testing Memory Allocation Patterns ===\n");
+
+ /* Test vzalloc/vfree */
+ void *ptr = vzalloc(4096);
+ TEST_ASSERT(ptr != NULL, "vzalloc allocates memory");
+ vfree(ptr);
+
+ /* Test kmalloc/kfree */
+ ptr = kmalloc(1024, GFP_KERNEL);
+ TEST_ASSERT(ptr != NULL, "kmalloc allocates memory");
+ kfree(ptr);
+
+ /* Test kcalloc */
+ ptr = kcalloc(10, 64, GFP_KERNEL);
+ TEST_ASSERT(ptr != NULL, "kcalloc allocates zeroed memory");
+ kfree(ptr);
+
+ return 0;
+}
+
+/* ============================================================
+ * Test: String Operations
+ * ============================================================ */
+
+static int test_string_ops(void)
+{
+ pr_info("=== Testing String Operations ===\n");
+
+ char buf[64];
+ int len = snprintf(buf, sizeof(buf), "test_%d", 42);
+ TEST_ASSERT(len > 0 && len < sizeof(buf), "snprintf works");
+ TEST_ASSERT(strcmp(buf, "test_42") == 0, "snprintf formats correctly");
+
+ char *dup = kstrdup("test_string", GFP_KERNEL);
+ TEST_ASSERT(dup != NULL, "kstrdup works");
+ TEST_ASSERT(strcmp(dup, "test_string") == 0, "kstrdup copies correctly");
+ kfree(dup);
+
+ return 0;
+}
+
+/* ============================================================
+ * Test Suite Runner
+ * ============================================================ */
+
+static int run_all_tests(void)
+{
+ pr_info("\n============================================================\n");
+ pr_info("GSP vGPU-IO Comprehensive Test Suite Starting\n");
+ pr_info("============================================================\n\n");
+
+ test_kernel_apis();
+ test_structure_sizes();
+ test_enum_values();
+ test_error_codes();
+ test_firmware_constants();
+ test_migration_states();
+ test_wpr_flags();
+ test_message_flags();
+ test_bar_trap_types();
+ test_memory_patterns();
+ test_string_ops();
+
+ pr_info("\n============================================================\n");
+ pr_info("TEST RESULTS: %d total, %d passed, %d failed\n",
+ test_count, pass_count, fail_count);
+ pr_info("============================================================\n");
+
+ if (fail_count == 0) {
+ pr_info("TEST SUITE PASSED\n");
+ return 0;
+ } else {
+ pr_err("TEST SUITE FAILED\n");
+ return -1;
+ }
+}
+
+/* ============================================================
+ * Module Init/Exit
+ * ============================================================ */
+
+static int __init gsp_test_init(void)
+{
+ return run_all_tests();
+}
+
+static void __exit gsp_test_exit(void)
+{
+ pr_info("GSP vGPU-IO test module unloaded\n");
+}
+
+module_init(gsp_test_init);
+module_exit(gsp_test_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("NVIDIA vGPU-IO Team");
+MODULE_DESCRIPTION("GSP vGPU-IO Comprehensive Test Suite");
+MODULE_VERSION("1.0.0"); \ No newline at end of file
diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c
new file mode 100644
index 0000000..c661f91
--- /dev/null
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c
@@ -0,0 +1,327 @@
+/*
+ * GSP vGPU-IO KUnit Tests
+ * In-kernel unit tests for the driver
+ */
+
+#include <kunit/test.h>
+#include <kunit/mock.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/atomic.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/kref.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+
+#include <gsp_core.h>
+
+/* ============================================================
+ * Test: Structure Sizes and Alignments
+ * ============================================================ */
+
+static void test_structure_sizes(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_header), 64);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_message), 256);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_response), 32);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_subcmd_desc), 8);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_vf_config), 64);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_payload), 48);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_response), 24);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_payload), 40);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_response), 8);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_payload), 40);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_response), 8);
+ KUNIT_EXPECT_EQ(test, sizeof(struct gsp_migrate_payload), 64);
+}
+
+static void test_structure_alignments(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_header), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_message), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_response), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_subcmd_desc), 4);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_vf_config), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_payload), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_response), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_payload), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_response), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_payload), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_response), 8);
+ KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_migrate_payload), 8);
+}
+
+/* ============================================================
+ * Test: Payload Encoding/Decoding
+ * ============================================================ */
+
+static void test_alloc_payload_encoding(struct kunit *test)
+{
+ struct gsp_alloc_payload payload = {0};
+
+ payload.size = cpu_to_le64(0x100000);
+ payload.type = cpu_to_le32(GSP_MEM_VRAM);
+ payload.alignment = cpu_to_le32(0x1000);
+ payload.va_hint = cpu_to_le64(0x0);
+ payload.flags = cpu_to_le32(0x3);
+ payload.pid = cpu_to_le32(0x456);
+ payload.vf_id = cpu_to_le32(0x1);
+
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x100000);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.type), GSP_MEM_VRAM);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.alignment), 0x1000);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.va_hint), 0x0);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), 0x3);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.pid), 0x456);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1);
+}
+
+static void test_pin_payload_encoding(struct kunit *test)
+{
+ struct gsp_pin_payload payload = {0};
+
+ payload.handle = cpu_to_le64(0xCAFEBABE);
+ payload.offset = cpu_to_le64(0x1000);
+ payload.size = cpu_to_le64(0x10000);
+ payload.vf_id = cpu_to_le32(0x2);
+ payload.flags = cpu_to_le32(GSP_PIN_READ | GSP_PIN_WRITE);
+
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.handle), 0xCAFEBABE);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x10000);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x2);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_PIN_READ | GSP_PIN_WRITE);
+}
+
+static void test_bar_trap_payload_encoding(struct kunit *test)
+{
+ struct gsp_bar_trap_payload payload = {0};
+
+ payload.bar_index = cpu_to_le32(1);
+ payload.trap_type = cpu_to_le32(GSP_BAR_WRITE);
+ payload.offset = cpu_to_le64(0x1000);
+ payload.value = cpu_to_le64(0xDEADBEEF);
+ payload.size = cpu_to_le32(4);
+ payload.vf_id = cpu_to_le32(0x1);
+
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.bar_index), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.trap_type), GSP_BAR_WRITE);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.value), 0xDEADBEEF);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.size), 4);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1);
+}
+
+static void test_migrate_payload_encoding(struct kunit *test)
+{
+ struct gsp_migrate_payload payload = {0};
+
+ payload.phase = cpu_to_le32(GSP_MIG_PREPARE);
+ payload.subcmd = cpu_to_le32(3);
+ payload.src_vf = cpu_to_le64(0x1);
+ payload.dst_vf = cpu_to_le64(0x2);
+ payload.data_ptr = cpu_to_le64(0x80000000);
+ payload.data_size = cpu_to_le64(0x1000);
+ payload.flags = cpu_to_le32(GSP_MIG_LIVE | GSP_MIG_VERIFY);
+ payload.token = cpu_to_le64(0xABCDEF1234567890);
+
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.phase), GSP_MIG_PREPARE);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.subcmd), 3);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.src_vf), 0x1);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.dst_vf), 0x2);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_ptr), 0x80000000);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_size), 0x1000);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_MIG_LIVE | GSP_MIG_VERIFY);
+ KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.token), 0xABCDEF1234567890);
+}
+
+static void test_subcmd_desc_encoding(struct kunit *test)
+{
+ struct gsp_subcmd_desc desc = {0};
+
+ desc.subcmd_id = cpu_to_le32(0x14);
+ desc.echo_flags_b = 0xAA;
+ desc.echo_flags_a = 0xBB;
+ desc.echo_flags_c = 0xCC;
+
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(desc.subcmd_id), 0x14);
+ KUNIT_EXPECT_EQ(test, desc.echo_flags_b, 0xAA);
+ KUNIT_EXPECT_EQ(test, desc.echo_flags_a, 0xBB);
+ KUNIT_EXPECT_EQ(test, desc.echo_flags_c, 0xCC);
+}
+
+/* ============================================================
+ * Test: Firmware Address Constants
+ * ============================================================ */
+
+static void test_firmware_addresses(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_FW_BASE_ADDR, 0x04000000ULL);
+ KUNIT_EXPECT_EQ(test, GSP_ENTRY_POINT, 0x04009000ULL);
+ KUNIT_EXPECT_EQ(test, GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL);
+ KUNIT_EXPECT_EQ(test, GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL);
+ KUNIT_EXPECT_EQ(test, 0x04179ED8ULL, 0x04179ED8ULL); /* Migration Table 1 */
+ KUNIT_EXPECT_EQ(test, 0x04179FBCULL, 0x04179FBCULL); /* Migration Table 2 */
+ KUNIT_EXPECT_EQ(test, 0x04CB8188ULL, 0x04CB8188ULL); /* BAR Handler */
+ KUNIT_EXPECT_EQ(test, 0x04F00B0CULL, 0x04F00B0CULL); /* WPR Handler 1 */
+ KUNIT_EXPECT_EQ(test, 0x04F00C78ULL, 0x04F00C78ULL); /* WPR Handler 2 */
+ KUNIT_EXPECT_EQ(test, 0x057E94ECULL, 0x057E94ECULL); /* ALLOC Main */
+ KUNIT_EXPECT_EQ(test, 0x051EFBF4ULL, 0x051EFBF4ULL); /* SCHEDULE Main */
+}
+
+/* ============================================================
+ * Test: RPC Type Constants
+ * ============================================================ */
+
+static void test_rpc_types(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_RPC_ALLOC, 0x01);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_FREE, 0x02);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_MAP, 0x03);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_UNMAP, 0x04);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_PIN, 0x05);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_UNPIN, 0x06);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_SCHEDULE, 0x07);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_YIELD, 0x08);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_NOTIFY, 0x09);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_BAR_TRAP, 0x0A);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_MIGRATE, 0x0B);
+ KUNIT_EXPECT_EQ(test, GSP_RPC_DEBUG, 0x0C);
+}
+
+static void test_migration_constants(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_MIG_QUERY, 0);
+ KUNIT_EXPECT_EQ(test, GSP_MIG_PREPARE, 1);
+ KUNIT_EXPECT_EQ(test, GSP_MIG_TRANSFER, 2);
+ KUNIT_EXPECT_EQ(test, GSP_MIG_COMMIT, 3);
+ KUNIT_EXPECT_EQ(test, GSP_MIG_CLEANUP, 4);
+
+ KUNIT_EXPECT_EQ(test, GSP_MIG_LIVE, (1 << 0));
+ KUNIT_EXPECT_EQ(test, GSP_MIG_POSTCOPY, (1 << 1));
+ KUNIT_EXPECT_EQ(test, GSP_MIG_AUTO_COMMIT, (1 << 2));
+ KUNIT_EXPECT_EQ(test, GSP_MIG_VERIFY, (1 << 3));
+}
+
+static void test_bar_trap_types(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_BAR_READ, 0);
+ KUNIT_EXPECT_EQ(test, GSP_BAR_WRITE, 1);
+ KUNIT_EXPECT_EQ(test, GSP_BAR_ATOMIC, 2);
+}
+
+static void test_pin_flags(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_PIN_READ, (1 << 0));
+ KUNIT_EXPECT_EQ(test, GSP_PIN_WRITE, (1 << 1));
+ KUNIT_EXPECT_EQ(test, GSP_PIN_LONGTERM, (1 << 2));
+}
+
+static void test_wpr_flags(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_WPR_WRITE, (1 << 0));
+ KUNIT_EXPECT_EQ(test, GSP_WPR_READ, (1 << 1));
+ KUNIT_EXPECT_EQ(test, GSP_WPR_EXECUTE, (1 << 2));
+}
+
+static void test_vf_states(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_VF_CREATING, 0);
+ KUNIT_EXPECT_EQ(test, GSP_VF_ACTIVE, 1);
+ KUNIT_EXPECT_EQ(test, GSP_VF_MIGRATING, 2);
+ KUNIT_EXPECT_EQ(test, GSP_VF_SUSPENDED, 3);
+ KUNIT_EXPECT_EQ(test, GSP_VF_DESTROYING, 4);
+}
+
+static void test_msg_flags(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_MSG_SYNC, (1 << 0));
+ KUNIT_EXPECT_EQ(test, GSP_MSG_ASYNC, (1 << 1));
+ KUNIT_EXPECT_EQ(test, GSP_MSG_NOREPLY, (1 << 2));
+}
+
+/* ============================================================
+ * Test: Error Code Translation
+ * ============================================================ */
+
+static void test_error_codes(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_OK), 0);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EPERM), -EPERM);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOENT), -ENOENT);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EINVAL), -EINVAL);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENODEV), -ENODEV);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOMEM), -ENOMEM);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EBUSY), -EBUSY);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOSPC), -ENOSPC);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ETIMEDOUT), -ETIMEDOUT);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EOVERFLOW), -EOVERFLOW);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EDEV), -EIO);
+ KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(0xDEADBEEF), -EIO);
+}
+
+/* ============================================================
+ * Test: Memory Types
+ * ============================================================ */
+
+static void test_memory_types(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, GSP_MEM_VRAM, 0);
+ KUNIT_EXPECT_EQ(test, GSP_MEM_SYSMEM, 1);
+ KUNIT_EXPECT_EQ(test, GSP_MEM_UNCACHED, 2);
+}
+
+/* ============================================================
+ * Test: Payload Capacity
+ * ============================================================ */
+
+static void test_payload_capacity(struct kunit *test)
+{
+ KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_alloc_payload) <= 192);
+ KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_pin_payload) <= 192);
+ KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_bar_trap_payload) <= 192);
+ KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_migrate_payload) <= 192);
+ KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_subcmd_desc) <= 192);
+}
+
+/* ============================================================
+ * Test Suite Registration
+ * ============================================================ */
+
+static struct kunit_case gsp_vgpu_test_cases[] = {
+ KUNIT_CASE(test_structure_sizes),
+ KUNIT_CASE(test_structure_alignments),
+ KUNIT_CASE(test_alloc_payload_encoding),
+ KUNIT_CASE(test_pin_payload_encoding),
+ KUNIT_CASE(test_bar_trap_payload_encoding),
+ KUNIT_CASE(test_migrate_payload_encoding),
+ KUNIT_CASE(test_subcmd_desc_encoding),
+ KUNIT_CASE(test_firmware_addresses),
+ KUNIT_CASE(test_rpc_types),
+ KUNIT_CASE(test_migration_constants),
+ KUNIT_CASE(test_bar_trap_types),
+ KUNIT_CASE(test_pin_flags),
+ KUNIT_CASE(test_wpr_flags),
+ KUNIT_CASE(test_vf_states),
+ KUNIT_CASE(test_msg_flags),
+ KUNIT_CASE(test_error_codes),
+ KUNIT_CASE(test_memory_types),
+ KUNIT_CASE(test_payload_capacity),
+ {}
+};
+
+static struct kunit_suite gsp_vgpu_test_suite = {
+ .name = "gsp-vgpu-io",
+ .test_cases = gsp_vgpu_test_cases,
+};
+
+kunit_test_suites(&gsp_vgpu_test_suite);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Open-IOV Project");
+MODULE_DESCRIPTION("GSP vGPU-IO KUnit Tests"); \ No newline at end of file
diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
new file mode 100644
index 0000000..b9a46df
--- /dev/null
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
@@ -0,0 +1,624 @@
+/*
+ * GSP vGPU-IO Driver Mock Test
+ * Tests driver logic with mocked hardware interfaces
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/kref.h>
+#include <linux/completion.h>
+#include <linux/atomic.h>
+
+#define TEST_PASS 0
+#define TEST_FAIL -1
+
+static int test_count = 0;
+static int pass_count = 0;
+static int fail_count = 0;
+
+#define TEST_ASSERT(cond, msg) \
+ do { \
+ test_count++; \
+ if (cond) { \
+ pass_count++; \
+ pr_info("PASS: %s\n", msg); \
+ } else { \
+ fail_count++; \
+ pr_err("FAIL: %s\n", msg); \
+ } \
+ } while (0)
+
+#define TEST_ASSERT_EQ(a, b, msg) \
+ TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")")
+
+/* ============================================================
+ * Mock Hardware Abstraction
+ * ============================================================ */
+
+struct mock_hw {
+ u64 bar0_regs[256];
+ u64 bar1_regs[256];
+ u8 *fw_memory;
+ size_t fw_size;
+ bool doorbell_triggered;
+ u32 doorbell_count;
+ spinlock_t lock;
+};
+
+static struct mock_hw *mock_hw_create(void)
+{
+ struct mock_hw *hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+ if (!hw)
+ return NULL;
+
+ hw->fw_memory = vmalloc(32 * 1024 * 1024); // 32MB
+ if (!hw->fw_memory) {
+ kfree(hw);
+ return NULL;
+ }
+ hw->fw_size = 32 * 1024 * 1024;
+ spin_lock_init(&hw->lock);
+ return hw;
+}
+
+static void mock_hw_destroy(struct mock_hw *hw)
+{
+ if (!hw)
+ return;
+ vfree(hw->fw_memory);
+ kfree(hw);
+}
+
+static void mock_hw_write_bar0(struct mock_hw *hw, u32 offset, u32 value)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset / 4 < 256)
+ hw->bar0_regs[offset / 4] = value;
+ if (offset == 0x1000) { // Doorbell
+ hw->doorbell_triggered = true;
+ hw->doorbell_count++;
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+static u32 mock_hw_read_bar0(struct mock_hw *hw, u32 offset)
+{
+ unsigned long flags;
+ u32 value = 0;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset / 4 < 256)
+ value = hw->bar0_regs[offset / 4];
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return value;
+}
+
+static void mock_hw_write_bar1(struct mock_hw *hw, u64 offset, u64 value)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset < hw->fw_size)
+ *(u64 *)(hw->fw_memory + offset) = value;
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+static u64 mock_hw_read_bar1(struct mock_hw *hw, u64 offset)
+{
+ unsigned long flags;
+ u64 value = 0;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset < hw->fw_size)
+ value = *(u64 *)(hw->fw_memory + offset);
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return value;
+}
+
+/* ============================================================
+ * Mock VF Operations
+ * ============================================================ */
+
+struct mock_vf {
+ u32 id;
+ u64 bar1_offset;
+ u64 bar1_size;
+ u64 pt_base;
+ u64 pt_size;
+ u64 cmd_base;
+ u64 cmd_size;
+ bool active;
+ struct {
+ u64 aperture_offset;
+ u64 aperture_size;
+ bool force_access;
+ u32 force_vf;
+ } bar_state;
+ struct list_head wpr_list;
+ struct mutex lock;
+};
+
+struct mock_wpr_region {
+ struct list_head list;
+ u64 base;
+ u64 size;
+ u32 flags;
+};
+
+static struct mock_vf *mock_vf_create(u32 id, u64 bar1_offset, u64 bar1_size,
+ u64 pt_base, u64 pt_size,
+ u64 cmd_base, u64 cmd_size)
+{
+ struct mock_vf *vf = kzalloc(sizeof(*vf), GFP_KERNEL);
+ if (!vf)
+ return NULL;
+
+ vf->id = id;
+ vf->bar1_offset = bar1_offset;
+ vf->bar1_size = bar1_size;
+ vf->pt_base = pt_base;
+ vf->pt_size = pt_size;
+ vf->cmd_base = cmd_base;
+ vf->cmd_size = cmd_size;
+ vf->active = true;
+ INIT_LIST_HEAD(&vf->wpr_list);
+ mutex_init(&vf->lock);
+
+ vf->bar_state.aperture_offset = bar1_offset;
+ vf->bar_state.aperture_size = bar1_size;
+ vf->bar_state.force_access = false;
+ vf->bar_state.force_vf = 0;
+
+ return vf;
+}
+
+static void mock_vf_destroy(struct mock_vf *vf)
+{
+ if (!vf)
+ return;
+
+ struct mock_wpr_region *rgn, *tmp;
+ mutex_lock(&vf->lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ }
+ mutex_unlock(&vf->lock);
+
+ kfree(vf);
+}
+
+static int mock_vf_wpr_add(struct mock_vf *vf, u64 base, u64 size, u32 flags)
+{
+ struct mock_wpr_region *rgn = kzalloc(sizeof(*rgn), GFP_KERNEL);
+ if (!rgn)
+ return -ENOMEM;
+
+ rgn->base = base;
+ rgn->size = size;
+ rgn->flags = flags;
+
+ mutex_lock(&vf->lock);
+ list_add(&rgn->list, &vf->wpr_list);
+ mutex_unlock(&vf->lock);
+
+ return 0;
+}
+
+static int mock_vf_wpr_remove(struct mock_vf *vf, u64 base)
+{
+ struct mock_wpr_region *rgn, *tmp;
+ bool found = false;
+
+ mutex_lock(&vf->lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ if (rgn->base == base) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&vf->lock);
+
+ return found ? 0 : -ENOENT;
+}
+
+/* ============================================================
+ * Mock RPC Client
+ * ============================================================ */
+
+struct mock_rpc_client {
+ u32 seq;
+ u32 pending_seq[64];
+ u32 pending_count;
+ struct mutex lock;
+};
+
+static struct mock_rpc_client *mock_rpc_create(void)
+{
+ struct mock_rpc_client *rpc = kzalloc(sizeof(*rpc), GFP_KERNEL);
+ if (!rpc)
+ return NULL;
+ mutex_init(&rpc->lock);
+ return rpc;
+}
+
+static void mock_rpc_destroy(struct mock_rpc_client *rpc)
+{
+ if (!rpc)
+ return;
+ kfree(rpc);
+}
+
+static int mock_rpc_send(struct mock_rpc_client *rpc, u32 rpc_type,
+ void *payload, size_t payload_size,
+ void *response, size_t *response_size)
+{
+ if (!rpc)
+ return -EINVAL;
+
+ mutex_lock(&rpc->lock);
+ rpc->seq++;
+ rpc->pending_seq[rpc->pending_count++] = rpc->seq;
+ mutex_unlock(&rpc->lock);
+
+ /* Simulate successful response */
+ if (response && response_size) {
+ struct gsp_rpc_response *resp = response;
+ resp->status = 0;
+ resp->rpc_type = rpc_type;
+ resp->sequence = rpc->seq;
+ *response_size = sizeof(*resp);
+ }
+
+ return 0;
+}
+
+/* ============================================================
+ * Driver Logic Tests with Mocks
+ * ============================================================ */
+
+static int test_mock_hardware(void)
+{
+ struct mock_hw *hw = mock_hw_create();
+ TEST_ASSERT(hw != NULL, "Mock HW create");
+
+ /* Test BAR0 write/read */
+ mock_hw_write_bar0(hw, 0x1000, 0x1);
+ TEST_ASSERT(hw->doorbell_triggered, "Doorbell triggered");
+ TEST_ASSERT_EQ(hw->doorbell_count, 1, "Doorbell count");
+
+ u32 val = mock_hw_read_bar0(hw, 0x1000);
+ TEST_ASSERT_EQ(val, 0x1, "BAR0 read back");
+
+ /* Test BAR1 write/read */
+ mock_hw_write_bar1(hw, 0x1000, 0xDEADBEEFCAFEBABE);
+ u64 val64 = mock_hw_read_bar1(hw, 0x1000);
+ TEST_ASSERT_EQ(val64, 0xDEADBEEFCAFEBABE, "BAR1 read back");
+
+ mock_hw_destroy(hw);
+ return TEST_PASS;
+}
+
+static int test_mock_vf_lifecycle(void)
+{
+ struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000,
+ 0x20000000, 0x100000,
+ 0x30000000, 0x10000);
+ TEST_ASSERT(vf != NULL, "Mock VF create");
+
+ TEST_ASSERT(vf->active, "VF active after create");
+ TEST_ASSERT_EQ(vf->id, 1, "VF ID");
+ TEST_ASSERT_EQ(vf->bar1_offset, 0x10000000, "BAR1 offset");
+ TEST_ASSERT_EQ(vf->bar1_size, 0x100000, "BAR1 size");
+ TEST_ASSERT_EQ(vf->pt_base, 0x20000000, "Page table base");
+ TEST_ASSERT_EQ(vf->pt_size, 0x100000, "Page table size");
+
+ /* Test BAR state */
+ TEST_ASSERT_EQ(vf->bar_state.aperture_offset, 0x10000000, "Aperture offset");
+ TEST_ASSERT_EQ(vf->bar_state.aperture_size, 0x100000, "Aperture size");
+ TEST_ASSERT(!vf->bar_state.force_access, "Force access initially false");
+
+ /* Test WPR list empty */
+ TEST_ASSERT(list_empty(&vf->wpr_list), "WPR list empty initially");
+
+ mock_vf_destroy(vf);
+ return TEST_PASS;
+}
+
+static int test_mock_vf_wpr(void)
+{
+ struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000,
+ 0x20000000, 0x100000,
+ 0x30000000, 0x10000);
+ TEST_ASSERT(vf != NULL, "Mock VF create");
+
+ /* Add WPR region */
+ int ret = mock_vf_wpr_add(vf, 0x10000000, 0x10000, 3);
+ TEST_ASSERT_EQ(ret, 0, "WPR add");
+
+ ret = mock_vf_wpr_add(vf, 0x20000000, 0x20000, 1);
+ TEST_ASSERT_EQ(ret, 0, "WPR add second");
+
+ /* Verify list */
+ int count = 0;
+ struct mock_wpr_region *rgn;
+ mutex_lock(&vf->lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ count++;
+ }
+ mutex_unlock(&vf->lock);
+ TEST_ASSERT_EQ(count, 2, "Two WPR regions");
+
+ /* Remove one */
+ ret = mock_vf_wpr_remove(vf, 0x10000000);
+ TEST_ASSERT_EQ(ret, 0, "WPR remove");
+
+ count = 0;
+ mutex_lock(&vf->lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ count++;
+ }
+ mutex_unlock(&vf->lock);
+ TEST_ASSERT_EQ(count, 1, "One WPR region remaining");
+
+ /* Remove non-existent */
+ ret = mock_vf_wpr_remove(vf, 0x30000000);
+ TEST_ASSERT_EQ(ret, -ENOENT, "WPR remove non-existent returns ENOENT");
+
+ mock_vf_destroy(vf);
+ return TEST_PASS;
+}
+
+static int test_mock_rpc_client(void)
+{
+ struct mock_rpc_client *rpc = mock_rpc_create();
+ TEST_ASSERT(rpc != NULL, "Mock RPC create");
+
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_alloc_payload payload = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ };
+
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "RPC send");
+
+ TEST_ASSERT_EQ(resp.status, 0, "RPC response status");
+ TEST_ASSERT_EQ(resp.rpc_type, GSP_RPC_ALLOC, "RPC response type");
+ TEST_ASSERT_EQ(resp.sequence, 1, "RPC response sequence");
+
+ /* Second call */
+ ret = mock_rpc_send(rpc, GSP_RPC_PIN, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "RPC send second");
+ TEST_ASSERT_EQ(resp.sequence, 2, "RPC response sequence 2");
+
+ mock_rpc_destroy(rpc);
+ return TEST_PASS;
+}
+
+static int test_driver_vf_creation_logic(void)
+{
+ /* Simulate driver VF creation logic */
+ struct mock_hw *hw = mock_hw_create();
+ struct mock_rpc_client *rpc = mock_rpc_create();
+ struct mock_vf *vfs[8] = {0};
+
+ /* Create VFs 1-4 */
+ for (int i = 1; i <= 4; i++) {
+ vfs[i] = mock_vf_create(i, 0x10000000 * i, 0x100000,
+ 0x20000000 * i, 0x100000,
+ 0x30000000 * i, 0x10000);
+ TEST_ASSERT(vfs[i] != NULL, "VF creation in loop");
+ TEST_ASSERT(vfs[i]->active, "VF active in loop");
+ }
+
+ /* Initialize BAR for each VF */
+ for (int i = 1; i <= 4; i++) {
+ vfs[i]->bar_state.aperture_offset = vfs[i]->bar1_offset;
+ vfs[i]->bar_state.aperture_size = vfs[i]->bar1_size;
+ TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_offset, 0x10000000 * i, "VF BAR offset");
+ TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_size, 0x100000, "VF BAR size");
+ }
+
+ /* Add WPR for each VF */
+ for (int i = 1; i <= 4; i++) {
+ int ret = mock_vf_wpr_add(vfs[i], vfs[i]->pt_base, vfs[i]->pt_size, 3);
+ TEST_ASSERT_EQ(ret, 0, "VF WPR add in loop");
+ }
+
+ /* Simulate RPC calls for each VF */
+ for (int i = 1; i <= 4; i++) {
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_alloc_payload payload = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(i),
+ };
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF RPC alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF RPC response OK");
+ }
+
+ /* Cleanup */
+ for (int i = 1; i <= 4; i++) {
+ mock_vf_destroy(vfs[i]);
+ }
+ mock_rpc_destroy(rpc);
+ mock_hw_destroy(hw);
+
+ return TEST_PASS;
+}
+
+static int test_migration_progress_tracking(void)
+{
+ struct {
+ u64 pages_transferred;
+ u64 bytes_transferred;
+ u64 dirty_pages;
+ u64 *page_list;
+ u32 page_count;
+ } progress = {0};
+
+ progress.page_count = 1000;
+ progress.page_list = kcalloc(progress.page_count, sizeof(u64), GFP_KERNEL);
+ TEST_ASSERT(progress.page_list != NULL, "Progress page list alloc");
+
+ /* Simulate transfer loop */
+ for (int iter = 0; iter < 10; iter++) {
+ progress.pages_transferred += 100;
+ progress.bytes_transferred += 100 * 4096;
+ progress.dirty_pages = 50 - (iter * 5);
+
+ TEST_ASSERT(progress.pages_transferred <= 1000, "Pages not exceeded");
+ TEST_ASSERT(progress.bytes_transferred <= 1000 * 4096, "Bytes not exceeded");
+ }
+
+ TEST_ASSERT_EQ(progress.pages_transferred, 1000, "Final pages");
+ TEST_ASSERT_EQ(progress.bytes_transferred, 1000 * 4096, "Final bytes");
+ TEST_ASSERT_EQ(progress.dirty_pages, 0, "Final dirty pages");
+
+ kfree(progress.page_list);
+ return TEST_PASS;
+}
+
+static int test_bar_state_machine(void)
+{
+ struct {
+ u64 aperture_offset;
+ u64 aperture_size;
+ bool force_access;
+ u32 force_vf;
+ } bar_state = {0};
+
+ /* Initial state */
+ bar_state.aperture_offset = 0x10000000;
+ bar_state.aperture_size = 0x100000;
+ TEST_ASSERT_EQ(bar_state.aperture_offset, 0x10000000, "Initial aperture offset");
+ TEST_ASSERT(!bar_state.force_access, "Force access initially false");
+
+ /* Enable force access */
+ bar_state.force_access = true;
+ bar_state.force_vf = 1;
+ TEST_ASSERT(bar_state.force_access, "Force access enabled");
+ TEST_ASSERT_EQ(bar_state.force_vf, 1, "Force VF set");
+
+ /* Disable force access */
+ bar_state.force_access = false;
+ bar_state.force_vf = 0;
+ TEST_ASSERT(!bar_state.force_access, "Force access disabled");
+ TEST_ASSERT_EQ(bar_state.force_vf, 0, "Force VF cleared");
+
+ return TEST_PASS;
+}
+
+static int test_concurrent_vf_operations(void)
+{
+ struct mock_vf *vf1 = mock_vf_create(1, 0x10000000, 0x100000, 0x20000000, 0x100000, 0x30000000, 0x10000);
+ struct mock_vf *vf2 = mock_vf_create(2, 0x20000000, 0x100000, 0x40000000, 0x100000, 0x60000000, 0x10000);
+ struct mock_rpc_client *rpc = mock_rpc_create();
+
+ TEST_ASSERT(vf1 && vf2 && rpc, "Create two VFs and RPC");
+
+ /* Simulate concurrent operations */
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+
+ /* VF1 alloc */
+ struct gsp_alloc_payload payload1 = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(1),
+ };
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload1, sizeof(payload1), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF1 alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF1 alloc response");
+
+ /* VF2 alloc */
+ struct gsp_alloc_payload payload2 = {
+ .size = cpu_to_le64(0x200000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(2),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload2, sizeof(payload2), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF2 alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF2 alloc response");
+
+ /* VF1 pin */
+ struct gsp_pin_payload pin_payload = {
+ .handle = cpu_to_le64(0x1000),
+ .vf_id = cpu_to_le32(1),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_PIN, &pin_payload, sizeof(pin_payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF1 pin");
+
+ /* VF2 BAR trap */
+ struct gsp_bar_trap_payload bar_payload = {
+ .vf_id = cpu_to_le32(2),
+ .bar_index = cpu_to_le32(1),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_BAR_TRAP, &bar_payload, sizeof(bar_payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF2 BAR trap");
+
+ /* Verify sequence numbers */
+ TEST_ASSERT_EQ(resp.sequence, 4, "Sequence number 4");
+
+ mock_vf_destroy(vf1);
+ mock_vf_destroy(vf2);
+ mock_rpc_destroy(rpc);
+
+ return TEST_PASS;
+}
+
+/* ============================================================
+ * Module Init/Exit
+ * ============================================================ */
+
+static int __init gsp_mock_test_init(void)
+{
+ pr_info("========================================\n");
+ pr_info("GSP vGPU-IO Mock Driver Test Suite\n");
+ pr_info("========================================\n");
+
+ test_mock_hardware();
+ test_mock_vf_lifecycle();
+ test_mock_vf_wpr();
+ test_mock_rpc_client();
+ test_driver_vf_creation_logic();
+ test_migration_progress_tracking();
+ test_bar_state_machine();
+ test_concurrent_vf_operations();
+
+ pr_info("========================================\n");
+ pr_info("Mock Test Results: %d total, %d passed, %d failed\n",
+ test_count, pass_count, fail_count);
+ pr_info("========================================\n");
+
+ if (fail_count > 0) {
+ pr_err("MOCK TEST SUITE FAILED\n");
+ return -EINVAL;
+ }
+
+ pr_info("MOCK TEST SUITE PASSED\n");
+ return 0;
+}
+
+static void __exit gsp_mock_test_exit(void)
+{
+ pr_info("GSP vGPU-IO Mock Test Suite Exited\n");
+}
+
+module_init(gsp_mock_test_init);
+module_exit(gsp_mock_test_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Open-IOV Project");
+MODULE_DESCRIPTION("GSP vGPU-IO Mock Driver Test");
+MODULE_VERSION("1.0"); \ No newline at end of file
diff --git a/legacy-driver-guest/migration/gsp_migration.c b/legacy-driver-guest/migration/gsp_migration.c
new file mode 100644
index 0000000..52d1931
--- /dev/null
+++ b/legacy-driver-guest/migration/gsp_migration.c
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - Live Migration
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
+/* Forward declarations */
+static int gsp_migration_prepare(struct gsp_migration *mig);
+static int gsp_migration_transfer_iter(struct gsp_migration *mig);
+static int gsp_migration_commit(struct gsp_migration *mig);
+static void gsp_migration_cleanup(struct gsp_migration *mig);
+
+/* Migration Thread */
+static int gsp_migration_thread(void *data)
+{
+ struct gsp_migration *mig = data;
+ int ret = 0;
+
+ while (!kthread_should_stop() && !mig->cancelled) {
+ mutex_lock(&mig->lock);
+
+ switch (mig->state) {
+ case GSP_MIG_S_PREPARE:
+ ret = gsp_migration_prepare(mig);
+ if (!ret)
+ mig->state = GSP_MIG_S_TRANSFER;
+ else
+ mig->state = GSP_MIG_S_FAILED;
+ break;
+
+ case GSP_MIG_S_TRANSFER:
+ ret = gsp_migration_transfer_iter(mig);
+ if (ret > 0) { /* Transfer complete */
+ mig->state = GSP_MIG_S_COMMIT;
+ } else if (ret < 0) {
+ mig->state = GSP_MIG_S_FAILED;
+ }
+ break;
+
+ case GSP_MIG_S_COMMIT:
+ ret = gsp_migration_commit(mig);
+ if (!ret)
+ mig->state = GSP_MIG_S_CLEANUP;
+ else
+ mig->state = GSP_MIG_S_FAILED;
+ break;
+
+ case GSP_MIG_S_CLEANUP:
+ gsp_migration_cleanup(mig);
+ mig->state = GSP_MIG_S_COMPLETED;
+ complete(&mig->comp);
+ mutex_unlock(&mig->lock);
+ return 0;
+
+ case GSP_MIG_S_FAILED:
+ mig->error = ret;
+ complete(&mig->comp);
+ mutex_unlock(&mig->lock);
+ return ret;
+
+ default:
+ break;
+ }
+
+ mutex_unlock(&mig->lock);
+
+ if (mig->state == GSP_MIG_S_TRANSFER)
+ msleep(10); /* Throttle transfer iterations */
+ }
+
+ return 0;
+}
+
+/* Migration Query */
+static int gsp_migration_query(struct gsp_vf *vf, bool *migratable)
+{
+ struct gsp_migrate_payload payload = {
+ .phase = GSP_MIG_QUERY,
+ .subcmd = 0,
+ .src_vf = vf->id,
+ .dst_vf = 0,
+ };
+ struct gsp_subcmd_desc desc;
+ size_t desc_size = sizeof(desc);
+ int ret;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE,
+ &payload, sizeof(payload), &desc, &desc_size);
+ if (ret)
+ return ret;
+
+ *migratable = (desc.subcmd_id != 0);
+ return 0;
+}
+
+/* Phase 1: Prepare */
+static int gsp_migration_prepare(struct gsp_migration *mig)
+{
+ struct gsp_vf *vf = mig->vf;
+ struct gsp_migrate_payload payload = {0};
+ struct gsp_subcmd_desc desc;
+ size_t desc_size = sizeof(desc);
+ int ret;
+
+ /* Quiesce VF (subcmd 3 -> returns ID 0x10) */
+ payload.phase = GSP_MIG_PREPARE;
+ payload.subcmd = 3;
+ payload.src_vf = vf->id;
+ payload.dst_vf = mig->dst_vf;
+ payload.flags = mig->flags;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE,
+ &payload, sizeof(payload), &desc, &desc_size);
+ if (ret)
+ return ret;
+
+ mig->token = desc.subcmd_id;
+
+ /* Snapshot BAR state */
+ ret = gsp_migration_bar_save(vf, mig->bar_state.buf, &mig->bar_state.size);
+ if (ret)
+ return ret;
+
+ /* Snapshot WPR regions */
+ /* TODO: WPR snapshot via migration RPC */
+
+ dev_info(vf->gsp->dev, "VF %u: Migration prepare done, token=0x%llx\n",
+ vf->id, mig->token);
+
+ return 0;
+}
+
+/* Phase 2: Transfer (Iterative) */
+static int gsp_migration_transfer_iter(struct gsp_migration *mig)
+{
+ struct gsp_vf *vf = mig->vf;
+ struct gsp_migrate_payload payload = {0};
+ struct gsp_subcmd_desc desc;
+ size_t desc_size = sizeof(desc);
+ int ret;
+
+ if (!mig->progress.page_list || mig->progress.page_count == 0)
+ return 1; /* Nothing to transfer */
+
+ payload.phase = GSP_MIG_TRANSFER;
+ payload.subcmd = 0;
+ payload.src_vf = vf->id;
+ payload.dst_vf = mig->dst_vf;
+ payload.data_ptr = (uint64_t)mig->progress.page_list;
+ payload.data_size = mig->progress.page_count * sizeof(uint64_t);
+ payload.token = mig->token;
+ payload.flags = mig->flags;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE,
+ &payload, sizeof(payload), &desc, &desc_size);
+ if (ret)
+ return ret;
+
+ /* Update progress from response */
+ /* Note: Actual progress parsing depends on firmware response format */
+ mig->progress.pages_transferred += 100; /* Placeholder */
+ mig->progress.bytes_transferred += 100 * 4096;
+
+ /* Check for dirty pages */
+ if (mig->progress.dirty_pages > 0 && !(mig->flags & GSP_MIG_POSTCOPY))
+ return 0; /* Continue iterating */
+
+ return 1; /* Transfer complete */
+}
+
+/* Phase 3: Commit */
+static int gsp_migration_commit(struct gsp_migration *mig)
+{
+ struct gsp_vf *vf = mig->vf;
+ struct gsp_migrate_payload payload = {0};
+ struct gsp_subcmd_desc desc;
+ size_t desc_size = sizeof(desc);
+ int ret;
+
+ /* Final dirty page transfer */
+ if (mig->progress.dirty_pages > 0) {
+ payload.phase = GSP_MIG_TRANSFER;
+ payload.subcmd = 11; /* Dirty tracking */
+ payload.src_vf = vf->id;
+ payload.dst_vf = mig->dst_vf;
+ payload.token = mig->token;
+ payload.flags = GSP_MIG_POSTCOPY;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE,
+ &payload, sizeof(payload), &desc, &desc_size);
+ if (ret)
+ return ret;
+ }
+
+ /* Commit (subcmd 12) */
+ payload.phase = GSP_MIG_COMMIT;
+ payload.subcmd = 12;
+ payload.src_vf = vf->id;
+ payload.dst_vf = mig->dst_vf;
+ payload.token = mig->token;
+ payload.flags = mig->flags | GSP_MIG_VERIFY;
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE,
+ &payload, sizeof(payload), &desc, &desc_size);
+ if (ret)
+ return ret;
+
+ /* Restore BAR on destination */
+ ret = gsp_migration_bar_restore(vf, mig->bar_state.buf, mig->bar_state.size);
+ if (ret)
+ return ret;
+
+ /* Restore WPR on destination */
+ /* TODO: WPR restore */
+
+ dev_info(vf->gsp->dev, "VF %u: Migration commit done\n", vf->id);
+
+ return 0;
+}
+
+/* Phase 4: Cleanup */
+static void gsp_migration_cleanup(struct gsp_migration *mig)
+{
+ struct gsp_vf *vf = mig->vf;
+
+ /* Free page list */
+ kfree(mig->progress.page_list);
+ mig->progress.page_list = NULL;
+ mig->progress.page_count = 0;
+
+ /* Free BAR state buffer */
+ kfree(mig->bar_state.buf);
+ mig->bar_state.buf = NULL;
+ mig->bar_state.size = 0;
+
+ dev_info(vf->gsp->dev, "VF %u: Migration cleanup done\n", vf->id);
+}
+
+/* Public Migration API */
+int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf)
+{
+ struct gsp_migration *mig;
+ bool migratable;
+ int ret;
+
+ if (vf->migration)
+ return -EBUSY;
+
+ /* Query migratability */
+ ret = gsp_migration_query(vf, &migratable);
+ if (ret || !migratable)
+ return ret ? ret : -EINVAL;
+
+ mig = kzalloc(sizeof(*mig), GFP_KERNEL);
+ if (!mig)
+ return -ENOMEM;
+
+ mig->vf = vf;
+ mig->dst_vf = dst_vf;
+ mig->flags = GSP_MIG_LIVE | GSP_MIG_VERIFY;
+ mig->state = GSP_MIG_S_PREPARE;
+ mutex_init(&mig->lock);
+ init_completion(&mig->comp);
+
+ /* Allocate page list (placeholder) */
+ mig->progress.page_count = vf->pt_size / 4096;
+ mig->progress.page_list = kcalloc(mig->progress.page_count, sizeof(uint64_t), GFP_KERNEL);
+ if (!mig->progress.page_list) {
+ kfree(mig);
+ return -ENOMEM;
+ }
+
+ /* Allocate BAR state buffer */
+ mig->bar_state.buf = kmalloc(65536, GFP_KERNEL); /* 64KB max */
+ if (!mig->bar_state.buf) {
+ kfree(mig->progress.page_list);
+ kfree(mig);
+ return -ENOMEM;
+ }
+
+ vf->migration = mig;
+
+ /* Start migration thread */
+ mig->thread = kthread_run(gsp_migration_thread, mig, "gsp-mig-%u", vf->id);
+ if (IS_ERR(mig->thread)) {
+ ret = PTR_ERR(mig->thread);
+ mig->thread = NULL;
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ kfree(mig->progress.page_list);
+ kfree(mig->bar_state.buf);
+ kfree(mig);
+ vf->migration = NULL;
+ return ret;
+}
+
+void gsp_migration_cancel(struct gsp_migration *mig)
+{
+ if (!mig)
+ return;
+
+ mutex_lock(&mig->lock);
+ mig->cancelled = true;
+ mig->state = GSP_MIG_S_FAILED;
+ mutex_unlock(&mig->lock);
+
+ if (mig->thread)
+ kthread_stop(mig->thread);
+
+ wait_for_completion(&mig->comp);
+
+ gsp_migration_cleanup(mig);
+ kfree(mig);
+}
+
+int gsp_migration_status(struct gsp_migration *mig)
+{
+ if (!mig)
+ return -EINVAL;
+
+ mutex_lock(&mig->lock);
+ int state = mig->state;
+ int error = mig->error;
+ mutex_unlock(&mig->lock);
+
+ if (state == GSP_MIG_S_FAILED)
+ return error;
+
+ return state;
+} \ No newline at end of file
diff --git a/legacy-driver-guest/rpc/gsp_rpc.c b/legacy-driver-guest/rpc/gsp_rpc.c
new file mode 100644
index 0000000..6bdcdb2
--- /dev/null
+++ b/legacy-driver-guest/rpc/gsp_rpc.c
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - RPC Client Implementation
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+
+/* Ring Buffer Operations */
+static int gsp_ring_init(struct gsp_msg_ring *ring, void *base, size_t size)
+{
+ ring->base = base;
+ ring->size = size;
+ ring->head = 0;
+ ring->tail = 0;
+ spin_lock_init(&ring->lock);
+ return 0;
+}
+
+static bool gsp_ring_empty(struct gsp_msg_ring *ring)
+{
+ return ring->head == ring->tail;
+}
+
+static bool gsp_ring_full(struct gsp_msg_ring *ring)
+{
+ return ((ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE)) == ring->tail;
+}
+
+static int gsp_ring_write(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg, size_t len)
+{
+ unsigned long flags;
+ uint32_t next_head;
+ void *dst;
+
+ if (len > GSP_MSG_MAX_SIZE)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ring->lock, flags);
+
+ if (gsp_ring_full(ring)) {
+ spin_unlock_irqrestore(&ring->lock, flags);
+ return -EBUSY;
+ }
+
+ dst = ring->base + (ring->head * GSP_MSG_MAX_SIZE);
+ memcpy(dst, msg, len);
+
+ next_head = (ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE);
+ ring->head = next_head;
+
+ spin_unlock_irqrestore(&ring->lock, flags);
+ return 0;
+}
+
+static int gsp_ring_read(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg)
+{
+ unsigned long flags;
+ void *src;
+
+ spin_lock_irqsave(&ring->lock, flags);
+
+ if (gsp_ring_empty(ring)) {
+ spin_unlock_irqrestore(&ring->lock, flags);
+ return 0;
+ }
+
+ src = ring->base + (ring->tail * GSP_MSG_MAX_SIZE);
+ memcpy(msg, src, GSP_MSG_MAX_SIZE);
+
+ ring->tail = (ring->tail + 1) % (ring->size / GSP_MSG_MAX_SIZE);
+
+ spin_unlock_irqrestore(&ring->lock, flags);
+ return 1;
+}
+
+/* RPC Client Initialization */
+struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp)
+{
+ struct gsp_rpc_client *rpc;
+ void *ring_mem;
+
+ rpc = kzalloc(sizeof(*rpc), GFP_KERNEL);
+ if (!rpc)
+ return ERR_PTR(-ENOMEM);
+
+ rpc->gsp = gsp;
+ mutex_init(&rpc->lock);
+ atomic_set(&rpc->seq, 0);
+ init_waitqueue_head(&rpc->wq);
+
+ /* Allocate shared memory for message rings (2 pages each) */
+ ring_mem = dma_alloc_coherent(gsp->dev, 8 * PAGE_SIZE,
+ &rpc->tx_ring_dma, GFP_KERNEL);
+ if (!ring_mem) {
+ kfree(rpc);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rpc->tx_ring = kzalloc(sizeof(*rpc->tx_ring), GFP_KERNEL);
+ rpc->rx_ring = kzalloc(sizeof(*rpc->rx_ring), GFP_KERNEL);
+ if (!rpc->tx_ring || !rpc->rx_ring) {
+ dma_free_coherent(gsp->dev, 8 * PAGE_SIZE, ring_mem, rpc->tx_ring_dma);
+ kfree(rpc->tx_ring);
+ kfree(rpc->rx_ring);
+ kfree(rpc);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ gsp_ring_init(rpc->tx_ring, ring_mem, 4 * PAGE_SIZE);
+ gsp_ring_init(rpc->rx_ring, ring_mem + 4 * PAGE_SIZE, 4 * PAGE_SIZE);
+
+ /* Map rings to GSP via queue head registers (BAR0) */
+ writel(rpc->tx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0));
+ writel(rpc->tx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0) + 4);
+ writel(rpc->rx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1));
+ writel(rpc->rx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1) + 4);
+
+ /* Allocate response buffers */
+ for (int i = 0; i < GSP_MAX_PENDING; i++) {
+ rpc->resp_buf[i] = kzalloc(GSP_MSG_MAX_SIZE, GFP_KERNEL);
+ if (!rpc->resp_buf[i]) {
+ /* Cleanup on failure */
+ for (int j = 0; j < i; j++)
+ kfree(rpc->resp_buf[j]);
+ gsp_rpc_client_fini(rpc);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ dev_info(gsp->dev, "RPC client initialized\n");
+ return rpc;
+}
+
+void gsp_rpc_client_fini(struct gsp_rpc_client *rpc)
+{
+ if (!rpc)
+ return;
+
+ for (int i = 0; i < GSP_MAX_PENDING; i++)
+ kfree(rpc->resp_buf[i]);
+
+ if (rpc->tx_ring) {
+ dma_free_coherent(rpc->gsp->dev, 8 * PAGE_SIZE,
+ rpc->tx_ring->base, rpc->tx_ring_dma);
+ kfree(rpc->tx_ring);
+ }
+ kfree(rpc->rx_ring);
+ kfree(rpc);
+}
+
+/* Synchronous RPC Send */
+int gsp_rpc_send_sync(struct gsp_rpc_client *rpc,
+ enum gsp_rpc_type type,
+ void *payload, size_t payload_size,
+ void *response, size_t *response_size)
+{
+ struct gsp_rpc_message msg = {0};
+ struct completion done;
+ uint32_t seq;
+ int ret;
+
+ if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header))
+ return -EINVAL;
+
+ seq = atomic_inc_return(&rpc->seq);
+ init_completion(&done);
+
+ msg.hdr.magic = 0x47535052; /* "GSPR" */
+ msg.hdr.version = 1;
+ msg.hdr.rpc_type = type;
+ msg.hdr.sequence = seq;
+ msg.hdr.timestamp_ns = ktime_get_ns();
+ msg.hdr.payload_size = payload_size;
+ msg.hdr.flags = GSP_MSG_SYNC;
+ msg.hdr.correlation_id = seq;
+
+ if (payload && payload_size > 0)
+ memcpy(msg.payload, payload, payload_size);
+
+ mutex_lock(&rpc->lock);
+
+ rpc->pending[seq % GSP_MAX_PENDING] = &done;
+
+ ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE);
+ if (ret) {
+ rpc->pending[seq % GSP_MAX_PENDING] = NULL;
+ mutex_unlock(&rpc->lock);
+ return ret;
+ }
+
+ /* Ring doorbell - write to queue head to signal GSP */
+ writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0));
+
+ mutex_unlock(&rpc->lock);
+
+ /* Wait for completion (5 second timeout) */
+ if (!wait_for_completion_timeout(&done, msecs_to_jiffies(5000))) {
+ mutex_lock(&rpc->lock);
+ rpc->pending[seq % GSP_MAX_PENDING] = NULL;
+ mutex_unlock(&rpc->lock);
+ atomic64_inc(&rpc->gsp->rpc_errors);
+ return -ETIMEDOUT;
+ }
+
+ /* Get response */
+ struct gsp_rpc_response *resp = rpc->resp_buf[seq % GSP_MAX_PENDING];
+
+ atomic64_inc(&rpc->gsp->rpc_total);
+
+ if (resp->status != 0) {
+ atomic64_inc(&rpc->gsp->rpc_errors);
+ return gsp_errno_to_errno(resp->status);
+ }
+
+ if (response && response_size) {
+ size_t copy_size = min(*response_size, (size_t)resp->payload_size);
+ memcpy(response, resp->payload, copy_size);
+ *response_size = copy_size;
+ }
+
+ return 0;
+}
+
+/* Async RPC Send */
+int gsp_rpc_send_async(struct gsp_rpc_client *rpc,
+ enum gsp_rpc_type type,
+ void *payload, size_t payload_size,
+ void (*callback)(struct gsp_rpc_client *,
+ struct gsp_rpc_response *,
+ void *),
+ void *ctx)
+{
+ struct gsp_rpc_message msg = {0};
+ uint32_t seq;
+ int ret;
+
+ if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header))
+ return -EINVAL;
+
+ seq = atomic_inc_return(&rpc->seq);
+
+ msg.hdr.magic = 0x47535052;
+ msg.hdr.version = 1;
+ msg.hdr.rpc_type = type;
+ msg.hdr.sequence = seq;
+ msg.hdr.timestamp_ns = ktime_get_ns();
+ msg.hdr.payload_size = payload_size;
+ msg.hdr.flags = GSP_MSG_ASYNC;
+ msg.hdr.correlation_id = (uint64_t)callback | ((uint64_t)ctx << 32);
+
+ if (payload && payload_size > 0)
+ memcpy(msg.payload, payload, payload_size);
+
+ mutex_lock(&rpc->lock);
+ ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE);
+ if (!ret)
+ writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0));
+ mutex_unlock(&rpc->lock);
+
+ return ret;
+}
+
+/* ISR - Handle Completions */
+irqreturn_t gsp_isr(int irq, void *data)
+{
+ struct gsp_device *gsp = data;
+ struct gsp_rpc_message msg;
+ int handled = 0;
+
+ while (gsp_ring_read(gsp->rpc->rx_ring, &msg)) {
+ handled = 1;
+
+ if (msg.hdr.flags & GSP_MSG_SYNC) {
+ uint32_t seq = msg.hdr.sequence;
+ struct completion *done = gsp->rpc->pending[seq % GSP_MAX_PENDING];
+ struct gsp_rpc_response *resp = gsp->rpc->resp_buf[seq % GSP_MAX_PENDING];
+
+ if (done && resp) {
+ resp->status = ((struct gsp_rpc_response *)msg.payload)->status;
+ resp->payload_size = msg.hdr.payload_size;
+ memcpy(resp->payload, ((struct gsp_rpc_response *)msg.payload)->payload,
+ msg.hdr.payload_size);
+ complete(done);
+ }
+ gsp->rpc->pending[seq % GSP_MAX_PENDING] = NULL;
+ } else {
+ /* Async notification - handle via callback */
+ gsp_handle_notification(gsp, &msg);
+ }
+ }
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/* Notification Handler */
+void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg)
+{
+ switch (msg->hdr.rpc_type) {
+ case GSP_RPC_NOTIFY:
+ /* Handle VF notifications (WPR, migration, etc.) */
+ break;
+ case GSP_RPC_BAR_TRAP:
+ atomic64_inc(&gsp->bar_traps);
+ break;
+ default:
+ dev_dbg(gsp->dev, "Unhandled notification type 0x%02x\n", msg->hdr.rpc_type);
+ }
+}
+
+/* Error Code Translation */
+int gsp_errno_to_errno(int gsp_err)
+{
+ switch (gsp_err) {
+ case GSP_OK: return 0;
+ case GSP_EPERM: return -EPERM;
+ case GSP_ENOENT: return -ENOENT;
+ case GSP_EINVAL: return -EINVAL;
+ case GSP_ENODEV: return -ENODEV;
+ case GSP_ENOMEM: return -ENOMEM;
+ case GSP_EBUSY: return -EBUSY;
+ case GSP_ENOSPC: return -ENOSPC;
+ case GSP_ETIMEDOUT: return -ETIMEDOUT;
+ case GSP_EOVERFLOW: return -EOVERFLOW;
+ case GSP_EDEV: return -EIO;
+ default: return -EIO;
+ }
+} \ No newline at end of file
diff --git a/legacy-driver-guest/vf/gsp_vf.c b/legacy-driver-guest/vf/gsp_vf.c
new file mode 100644
index 0000000..951c84d
--- /dev/null
+++ b/legacy-driver-guest/vf/gsp_vf.c
@@ -0,0 +1,312 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - VF Management
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+
+/* VF Reference Counting */
+static void gsp_vf_release(struct kref *ref)
+{
+ struct gsp_vf *vf = container_of(ref, struct gsp_vf, ref);
+ kfree(vf);
+}
+
+struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id)
+{
+ struct gsp_vf *vf;
+
+ mutex_lock(&gsp->vf_lock);
+ vf = idr_find(&gsp->vf_idr, id);
+ if (vf && vf->active)
+ kref_get(&vf->ref);
+ else
+ vf = NULL;
+ mutex_unlock(&gsp->vf_lock);
+
+ return vf;
+}
+
+void gsp_vf_put(struct gsp_vf *vf)
+{
+ if (vf)
+ kref_put(&vf->ref, gsp_vf_release);
+}
+
+/* VF Creation */
+int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config)
+{
+ struct gsp_vf *vf;
+ int ret, id;
+
+ if (!config || config->id == 0 || config->id > gsp->max_vfs)
+ return -EINVAL;
+
+ vf = kzalloc(sizeof(*vf), GFP_KERNEL);
+ if (!vf)
+ return -ENOMEM;
+
+ vf->gsp = gsp;
+ vf->id = config->id;
+ kref_init(&vf->ref);
+ mutex_init(&vf->lock);
+ mutex_init(&vf->wpr_lock);
+ INIT_LIST_HEAD(&vf->wpr_list);
+ INIT_LIST_HEAD(&vf->list);
+ atomic_set(&vf->refcount, 1);
+
+ /* Copy configuration */
+ vf->bar1_offset = config->bar1_offset;
+ vf->bar1_size = config->bar1_size;
+ vf->pt_base = config->pt_base;
+ vf->pt_size = config->pt_size;
+ vf->cmd_base = config->cmd_base;
+ vf->cmd_size = config->cmd_size;
+
+ /* Allocate VF ID */
+ mutex_lock(&gsp->vf_lock);
+ id = idr_alloc(&gsp->vf_idr, vf, config->id, config->id + 1, GFP_KERNEL);
+ if (id < 0) {
+ ret = id;
+ mutex_unlock(&gsp->vf_lock);
+ goto err_free;
+ }
+
+ list_add(&vf->list, &gsp->vf_list);
+ vf->active = true;
+ vf->state = GSP_VF_ACTIVE;
+ mutex_unlock(&gsp->vf_lock);
+
+ /* Initialize subsystems */
+ ret = gsp_vf_wpr_init(vf);
+ if (ret) {
+ dev_err(gsp->dev, "VF %u: WPR init failed: %d\n", vf->id, ret);
+ goto err_wpr;
+ }
+
+ ret = gsp_vf_bar_init(vf);
+ if (ret) {
+ dev_err(gsp->dev, "VF %u: BAR init failed: %d\n", vf->id, ret);
+ goto err_bar;
+ }
+
+ dev_info(gsp->dev, "VF %u created (bar1=0x%llx-0x%llx, pt=0x%llx-0x%llx)\n",
+ vf->id, vf->bar1_offset, vf->bar1_offset + vf->bar1_size,
+ vf->pt_base, vf->pt_base + vf->pt_size);
+
+ return 0;
+
+err_bar:
+ gsp_vf_wpr_fini(vf);
+err_wpr:
+ mutex_lock(&gsp->vf_lock);
+ list_del(&vf->list);
+ idr_remove(&gsp->vf_idr, vf->id);
+ vf->active = false;
+ mutex_unlock(&gsp->vf_lock);
+err_free:
+ kfree(vf);
+ return ret;
+}
+
+/* VF Destruction */
+void gsp_vf_destroy(struct gsp_vf *vf)
+{
+ struct gsp_device *gsp = vf->gsp;
+ uint32_t id = vf->id;
+
+ if (!vf->active)
+ return;
+
+ /* Cancel any ongoing migration */
+ mutex_lock(&vf->lock);
+ if (vf->migration) {
+ gsp_migration_cancel(vf->migration);
+ vf->migration = NULL;
+ }
+ vf->active = false;
+ vf->state = GSP_VF_DESTROYING;
+ mutex_unlock(&vf->lock);
+
+ /* Remove from device lists */
+ mutex_lock(&gsp->vf_lock);
+ list_del(&vf->list);
+ idr_remove(&gsp->vf_idr, id);
+ mutex_unlock(&gsp->vf_lock);
+
+ /* Cleanup subsystems */
+ gsp_vf_bar_fini(vf);
+ gsp_vf_wpr_fini(vf);
+
+ dev_info(gsp->dev, "VF %u destroyed\n", id);
+
+ gsp_vf_put(vf);
+}
+
+/* Memory Allocation */
+int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags,
+ uint64_t *handle, uint64_t *va)
+{
+ struct gsp_alloc_payload payload = {
+ .size = size,
+ .type = flags & 0x3, /* Low bits: mem type */
+ .alignment = 0x1000,
+ .va_hint = 0,
+ .flags = flags >> 2,
+ .pid = current->pid,
+ .vf_id = vf->id,
+ };
+ struct gsp_alloc_response resp;
+ size_t resp_size = sizeof(resp);
+ int ret;
+
+ if (!handle || !va)
+ return -EINVAL;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ static uint64_t next_handle = 0x1000;
+ static uint64_t next_va = 0x100000000ULL; /* 4GB base */
+
+ *handle = next_handle++;
+ *va = next_va;
+ next_va += (size + 0xFFF) & ~0xFFF; /* Align to 4KB */
+
+ dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx (emulation)\n",
+ vf->id, *handle, *va, size);
+
+ return 0;
+ }
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_ALLOC,
+ &payload, sizeof(payload),
+ &resp, &resp_size);
+ if (ret)
+ return ret;
+
+ *handle = resp.handle;
+ *va = resp.va;
+
+ dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx\n",
+ vf->id, *handle, *va, size);
+
+ return 0;
+}
+
+int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle)
+{
+ /* FREE uses primary dispatcher sub-command */
+ struct gsp_subcmd_desc desc;
+ size_t desc_size = sizeof(desc);
+ int ret;
+
+ /* Subcmd for FREE: need to determine which subcmd corresponds to FREE */
+ /* For now, use a generic FREE RPC if implemented */
+
+ /* Placeholder - actual implementation depends on FREE subcmd mapping */
+ dev_dbg(vf->gsp->dev, "VF %u: free handle=0x%llx\n", vf->id, handle);
+
+ return 0;
+}
+
+/* Memory Pinning */
+int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle,
+ uint64_t offset, uint64_t size,
+ uint64_t *dma_addr)
+{
+ struct gsp_pin_payload payload = {
+ .handle = handle,
+ .offset = offset,
+ .size = size,
+ .vf_id = vf->id,
+ .flags = GSP_PIN_READ | GSP_PIN_WRITE,
+ };
+ struct gsp_pin_response resp;
+ size_t resp_size = sizeof(resp);
+ int ret;
+
+ if (!dma_addr)
+ return -EINVAL;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ static uint64_t next_dma = 0x200000000ULL; /* 8GB base */
+ *dma_addr = next_dma;
+ next_dma += (size + 0xFFF) & ~0xFFF;
+
+ dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx (emulation)\n",
+ vf->id, handle, offset, size, *dma_addr);
+
+ return 0;
+ }
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_PIN,
+ &payload, sizeof(payload),
+ &resp, &resp_size);
+ if (ret)
+ return ret;
+
+ *dma_addr = resp.dma_addr;
+
+ dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx\n",
+ vf->id, handle, offset, size, *dma_addr);
+
+ return 0;
+}
+
+int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle)
+{
+ dev_dbg(vf->gsp->dev, "VF %u: unpin handle=0x%llx\n", vf->id, handle);
+ return 0;
+}
+
+/* Memory Mapping */
+int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va,
+ uint64_t size, uint32_t prot, uint32_t flags,
+ uint64_t offset, uint64_t *mapped_va)
+{
+ /* In emulation mode, just return the requested VA */
+ if (emulation_mode) {
+ if (mapped_va)
+ *mapped_va = gpu_va;
+
+ dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx -> mapped_va=0x%llx (emulation)\n",
+ vf->id, handle, gpu_va, size, gpu_va);
+
+ return 0;
+ }
+
+ /* TODO: Implement GSP RPC for MAP */
+ dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx\n",
+ vf->id, handle, gpu_va, size);
+
+ if (mapped_va)
+ *mapped_va = gpu_va;
+
+ return 0;
+}
+
+int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size)
+{
+ dev_dbg(vf->gsp->dev, "VF %u: unmap handle=0x%llx gpu_va=0x%llx size=0x%llx\n",
+ vf->id, handle, gpu_va, size);
+ return 0;
+}
+
+/* Scheduling */
+int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns)
+{
+ /* Use primary dispatcher SCHEDULE subcmd or secondary dispatcher */
+ dev_dbg(vf->gsp->dev, "VF %u: schedule prio=%u slice=%llu ns\n",
+ vf->id, priority, time_slice_ns);
+ return 0;
+}
+
+int gsp_vf_yield(struct gsp_vf *vf)
+{
+ dev_dbg(vf->gsp->dev, "VF %u: yield\n", vf->id);
+ return 0;
+} \ No newline at end of file
diff --git a/legacy-driver-guest/wpr/gsp_wpr.c b/legacy-driver-guest/wpr/gsp_wpr.c
new file mode 100644
index 0000000..91c5550
--- /dev/null
+++ b/legacy-driver-guest/wpr/gsp_wpr.c
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - WPR (Write/Read Protect Region) Management
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/list.h>
+
+/* WPR Payload (for RPC) */
+struct gsp_wpr_payload {
+ uint32_t vf_id;
+ uint32_t op; /* 0=protect_write, 1=protect_read, 2=unprotect */
+ uint64_t base;
+ uint64_t size;
+ uint32_t flags;
+ uint64_t timeout_ns;
+} __attribute__((packed));
+
+struct gsp_wpr_response {
+ int64_t status;
+} __attribute__((packed));
+
+/* WPR Initialization */
+int gsp_vf_wpr_init(struct gsp_vf *vf)
+{
+ INIT_LIST_HEAD(&vf->wpr_list);
+ mutex_init(&vf->wpr_lock);
+ return 0;
+}
+
+void gsp_vf_wpr_fini(struct gsp_vf *vf)
+{
+ struct gsp_wpr_region *rgn, *tmp;
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ }
+ mutex_unlock(&vf->wpr_lock);
+}
+
+/* Protect Region (Write) */
+int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size)
+{
+ return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_WRITE);
+}
+
+/* Protect Region (Read) */
+int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size)
+{
+ return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_READ);
+}
+
+/* Generic Protect */
+int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size, uint32_t flags)
+{
+ struct gsp_wpr_payload payload = {
+ .vf_id = vf->id,
+ .op = (flags & GSP_WPR_READ) ? 1 : 0,
+ .base = base,
+ .size = size,
+ .flags = flags,
+ .timeout_ns = 10000000, /* 10ms default */
+ };
+ struct gsp_wpr_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_wpr_region *rgn;
+ int ret;
+
+ /* Validate alignment */
+ if (base & 0xFFF || size & 0xFFF)
+ return -EINVAL;
+
+ rgn = kzalloc(sizeof(*rgn), GFP_KERNEL);
+ if (!rgn)
+ return -ENOMEM;
+
+ rgn->base = base;
+ rgn->size = size;
+ rgn->flags = flags;
+ rgn->vf_id = vf->id;
+ rgn->timeout_ns = payload.timeout_ns;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ mutex_lock(&vf->wpr_lock);
+ list_add(&rgn->list, &vf->wpr_list);
+ mutex_unlock(&vf->wpr_lock);
+
+ dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x (emulation)\n",
+ vf->id, base, base + size, flags);
+
+ return 0;
+ }
+
+ /* Send to GSP WPR handler (0x04F00B0C/0x04F00C78) */
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MAP, /* Uses MAP RPC for WPR */
+ &payload, sizeof(payload), &resp, &resp_size);
+ if (ret) {
+ kfree(rgn);
+ return ret;
+ }
+
+ if (resp.status != 0) {
+ kfree(rgn);
+ return gsp_errno_to_errno(resp.status);
+ }
+
+ mutex_lock(&vf->wpr_lock);
+ list_add(&rgn->list, &vf->wpr_list);
+ mutex_unlock(&vf->wpr_lock);
+
+ dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x\n",
+ vf->id, base, base + size, flags);
+
+ return 0;
+}
+
+/* Unprotect Region */
+int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base)
+{
+ struct gsp_wpr_payload payload = {
+ .vf_id = vf->id,
+ .op = 2, /* unprotect */
+ .base = base,
+ .size = 0,
+ .flags = 0,
+ };
+ struct gsp_wpr_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_wpr_region *rgn, *tmp;
+ bool found = false;
+ int ret;
+
+ /* In emulation mode, handle locally */
+ if (emulation_mode) {
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ if (rgn->base == base) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx (emulation)\n", vf->id, base);
+
+ return found ? 0 : -ENOENT;
+ }
+
+ ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_UNMAP, /* Uses UNMAP for WPR */
+ &payload, sizeof(payload), &resp, &resp_size);
+ if (ret)
+ return ret;
+
+ if (resp.status != 0)
+ return gsp_errno_to_errno(resp.status);
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ if (rgn->base == base) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ if (!found)
+ return -ENOENT;
+
+ dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx\n", vf->id, base);
+
+ return 0;
+}
+
+/* Set Timeout */
+int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns)
+{
+ /* Update timeout for all WPR regions */
+ struct gsp_wpr_region *rgn;
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ rgn->timeout_ns = timeout_ns;
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ return 0;
+}
+
+/* Register Completion Callback */
+int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type,
+ void (*cb)(struct gsp_vf *, uint64_t, uint32_t),
+ void *ctx)
+{
+ struct gsp_wpr_region *rgn;
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ switch (type) {
+ case GSP_WPR_WRITE_COMPLETE:
+ case GSP_WPR_WRITE_TIMEOUT:
+ rgn->completion_cb = cb;
+ break;
+ case GSP_WPR_READ_COMPLETE:
+ case GSP_WPR_READ_TIMEOUT:
+ rgn->timeout_cb = cb;
+ break;
+ }
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ return 0;
+}
+
+/* WPR Event Handler (called from notification) */
+void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg)
+{
+ struct gsp_rpc_response *resp = (struct gsp_rpc_response *)msg->payload;
+ uint32_t event_type = resp->payload[0];
+ uint32_t vf_id = resp->payload[1];
+ uint64_t address = *(uint64_t *)&resp->payload[2];
+ struct gsp_vf *vf;
+ struct gsp_wpr_region *rgn;
+
+ vf = gsp_vf_get(gsp, vf_id);
+ if (!vf)
+ return;
+
+ atomic64_inc(&gsp->wpr_events);
+
+ mutex_lock(&vf->wpr_lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ if (address >= rgn->base && address < rgn->base + rgn->size) {
+ switch (event_type) {
+ case GSP_WPR_WRITE_COMPLETE:
+ case GSP_WPR_READ_COMPLETE:
+ if (rgn->completion_cb)
+ rgn->completion_cb(vf, address, event_type);
+ break;
+ case GSP_WPR_WRITE_TIMEOUT:
+ case GSP_WPR_READ_TIMEOUT:
+ if (rgn->timeout_cb)
+ rgn->timeout_cb(vf, address, event_type);
+ break;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&vf->wpr_lock);
+
+ gsp_vf_put(vf);
+}
+
+/* Default Completion Callback */
+void gsp_wpr_default_complete_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type)
+{
+ dev_dbg(vf->gsp->dev, "VF %u: WPR %s complete at 0x%llx\n",
+ vf->id, type == GSP_WPR_WRITE_COMPLETE ? "write" : "read", addr);
+
+ /* If this was a page table write, invalidate TLB */
+ if (addr >= vf->pt_base && addr < vf->pt_base + vf->pt_size) {
+ /* TODO: TLB invalidate */
+ }
+}
+
+/* Default Timeout Callback */
+void gsp_wpr_default_timeout_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type)
+{
+ dev_warn(vf->gsp->dev, "VF %u: WPR %s timeout at 0x%llx\n",
+ vf->id, type == GSP_WPR_WRITE_TIMEOUT ? "write" : "read", addr);
+
+ /* Throttle or reset VF */
+ if (type == GSP_WPR_WRITE_TIMEOUT) {
+ /* TODO: VF throttle */
+ } else {
+ /* TODO: VF reset */
+ }
+} \ No newline at end of file