diff options
Diffstat (limited to 'legacy-driver-guest')
| -rw-r--r-- | legacy-driver-guest/Makefile | 44 | ||||
| -rw-r--r-- | legacy-driver-guest/README.md | 21 | ||||
| -rw-r--r-- | legacy-driver-guest/bar/gsp_bar.c | 242 | ||||
| -rw-r--r-- | legacy-driver-guest/core/gsp_core.c | 654 | ||||
| -rw-r--r-- | legacy-driver-guest/debugfs/gsp_debugfs.c | 273 | ||||
| -rw-r--r-- | legacy-driver-guest/include/gsp_core.h | 613 | ||||
| -rw-r--r-- | legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c | 432 | ||||
| -rw-r--r-- | legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c | 327 | ||||
| -rw-r--r-- | legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c | 624 | ||||
| -rw-r--r-- | legacy-driver-guest/migration/gsp_migration.c | 339 | ||||
| -rw-r--r-- | legacy-driver-guest/rpc/gsp_rpc.c | 331 | ||||
| -rw-r--r-- | legacy-driver-guest/vf/gsp_vf.c | 312 | ||||
| -rw-r--r-- | legacy-driver-guest/wpr/gsp_wpr.c | 285 |
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 |
