diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-30 23:10:52 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-30 23:10:52 -0400 |
| commit | b08989c6ab43892a4f19798bb51c720f0000dc80 (patch) | |
| tree | 8bb91a99f577e84b8b72805b1deec65fadaa0c4b /kernel | |
| parent | b4e2f15b534f550c515f625fdcb06dc9908456ab (diff) | |
| download | open-iov-b08989c6ab43892a4f19798bb51c720f0000dc80.tar.gz open-iov-b08989c6ab43892a4f19798bb51c720f0000dc80.zip | |
gsp-phys: claim the physical GPU and stage real GSP firmware (backend rungs 1-2)
Rung 1 (ownership): a default-off gsp_phys_gpu module param; on claim the
distribution driver is released from 10de:1ed3, our pci_driver binds it,
BAR0/1/3 are mapped and the falcon state is sampled; release unbinds ours
and rebinds the distribution driver. Verified live: claim reads
BOOT0=0x164000a1 with the falcon halted and mailboxes clear; rmmod rebinds
nvidia and nvidia-smi stays healthy.
Rung 2 (firmware): while the device is owned, gsp_phys_fw (default
nvidia/570.211.01/gsp_tu10x.bin) is fetched with request_firmware, checked
as ELF64 RISC-V, and its sections are logged (.fwimage, .fwversion,
.fwsignature_tu10x). The image is held for the boot rung.
The claim is default-off; the emulation path is unchanged.
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/Makefile | 1 | ||||
| -rw-r--r-- | kernel/gsp_phys_gpu.c | 373 | ||||
| -rw-r--r-- | kernel/gsp_phys_gpu.h | 15 | ||||
| -rw-r--r-- | kernel/main.c | 21 |
4 files changed, 410 insertions, 0 deletions
diff --git a/kernel/Makefile b/kernel/Makefile index 8562430..6080164 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -16,6 +16,7 @@ open-iov-vfio-y := \ ioctl.o \ firmware_analyzer.o \ dynamic_rpc_dispatcher.o \ + gsp_phys_gpu.o \ architecture/architecture.o \ version/version.o diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c new file mode 100644 index 0000000..fddfae1 --- /dev/null +++ b/kernel/gsp_phys_gpu.c @@ -0,0 +1,373 @@ +/* + * Physical GPU ownership for the real-GSP execution backend. + * + * The vGPU work path executes guest pushes on the physical GPU. To talk to + * the real GSP firmware the module must own the device: the distribution + * driver is unbound (reversibly) and this module's pci_driver claims the + * GPU, maps its BARs, and exposes the registers the backend needs. + * + * Disabled by default (gsp_phys_gpu=N). Claim only when no VM holds an mdev. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/elf.h> +#include <linux/crc32.h> +#include <linux/firmware.h> + +#include "gsp_phys_gpu.h" + +#define GSP_PHYS_VENDOR 0x10de +#define GSP_PHYS_DEVICE 0x1ed3 + +/* + * GSP firmware to run on the claimed GPU (backend rung 2). Default matches + * the distribution driver that manages the physical GPU on this host; can be + * overridden with gsp_phys_fw=<name, without /lib/firmware prefix>. + */ +#define GSP_PHYS_FW_DEFAULT "nvidia/570.211.01/gsp_tu10x.bin" + +static char *gsp_phys_fw_name = GSP_PHYS_FW_DEFAULT; +module_param_named(gsp_phys_fw, gsp_phys_fw_name, charp, 0644); +MODULE_PARM_DESC(gsp_phys_fw, "GSP firmware image to load (default " GSP_PHYS_FW_DEFAULT ")"); + +/* BAR0 registers sampled after claim as proof of live access. */ +#define GSP_PHYS_BOOT_0 0x00000000 +#define GSP_PHYS_BOOT_1 0x00000004 +#define GSP_PHYS_FALCON_OS 0x00110080 +#define GSP_PHYS_GSP_MAILBOX0 0x00110040 +#define GSP_PHYS_GSP_MAILBOX1 0x00110044 +#define GSP_PHYS_GSP_CPUCTL 0x00110100 +#define GSP_PHYS_ENGINE 0x001103c0 + +struct gsp_phys_gpu { + struct pci_dev *pdev; + void __iomem *bar0; + void __iomem *bar1; + void __iomem *bar3; + resource_size_t bar0_len; + resource_size_t bar1_len; + resource_size_t bar3_len; + const struct firmware *fw; + u32 fw_crc32; + bool fw_loaded; + bool enabled; + bool regions; + bool drv_registered; + bool claimed; +}; + +static struct gsp_phys_gpu phys; + +bool gsp_phys_gpu_claimed(void) +{ + return phys.claimed; +} +EXPORT_SYMBOL_GPL(gsp_phys_gpu_claimed); + +void __iomem *gsp_phys_gpu_bar0(void) +{ + return phys.bar0; +} +EXPORT_SYMBOL_GPL(gsp_phys_gpu_bar0); + +static void gsp_phys_fw_unload(void) +{ + if (!phys.fw) + return; + + release_firmware(phys.fw); + phys.fw = NULL; + phys.fw_loaded = false; + phys.fw_crc32 = 0; +} + +static const char *gsp_phys_fw_section_name(const struct firmware *fw, + const Elf64_Shdr *shdr) +{ + const Elf64_Ehdr *eh = (const Elf64_Ehdr *)fw->data; + const Elf64_Shdr *shstr; + const char *base; + + if (eh->e_shstrndx >= eh->e_shnum) + return "?"; + shstr = (const Elf64_Shdr *)(fw->data + eh->e_shoff + + (u64)eh->e_shstrndx * eh->e_shentsize); + if (shstr->sh_offset + shstr->sh_size > fw->size || + shdr->sh_name >= shstr->sh_size) + return "?"; + base = (const char *)fw->data + shstr->sh_offset; + return base + shdr->sh_name; +} + +/* + * Rung 2: fetch and validate the real GSP firmware image while the device + * is owned. No GPU state is touched here - the image is held for the boot + * rung. The image is the ELF the distribution driver would load + * (nvidia/<version>/gsp_tu10x.bin). + */ +static int gsp_phys_fw_load(void) +{ + const Elf64_Ehdr *eh; + int ret; + + ret = request_firmware(&phys.fw, gsp_phys_fw_name, + phys.pdev ? &phys.pdev->dev : NULL); + if (ret) { + pr_err("gsp-phys-fw: request_firmware(%s) failed: %d\n", + gsp_phys_fw_name, ret); + return ret; + } + + eh = (const Elf64_Ehdr *)phys.fw->data; + if (phys.fw->size < sizeof(*eh) || + memcmp(eh->e_ident, ELFMAG, SELFMAG)) { + pr_err("gsp-phys-fw: %s is not an ELF image (%zu bytes)\n", + gsp_phys_fw_name, phys.fw->size); + goto err; + } + if (eh->e_ident[EI_CLASS] != ELFCLASS64 || + eh->e_machine != EM_RISCV) { + pr_err("gsp-phys-fw: unexpected ELF (class=%u machine=%u)\n", + eh->e_ident[EI_CLASS], eh->e_machine); + goto err; + } + + phys.fw_crc32 = crc32_le(0, phys.fw->data, phys.fw->size); + phys.fw_loaded = true; + + pr_info("gsp-phys-fw: loaded %s: %zu bytes, ELF64 RISC-V, " + "entry=0x%llx shnum=%u crc32=0x%08x\n", + gsp_phys_fw_name, phys.fw->size, (u64)eh->e_entry, + eh->e_shnum, phys.fw_crc32); + + if (eh->e_shoff && eh->e_shnum && + eh->e_shentsize == sizeof(Elf64_Shdr) && + eh->e_shoff + (u64)eh->e_shnum * eh->e_shentsize <= phys.fw->size) { + unsigned int i; + + for (i = 0; i < eh->e_shnum; i++) { + const Elf64_Shdr *sh = (const Elf64_Shdr *) + (phys.fw->data + eh->e_shoff + + (u64)i * eh->e_shentsize); + + if (sh->sh_offset > phys.fw->size || + sh->sh_size > phys.fw->size - sh->sh_offset) + continue; + pr_info("gsp-phys-fw: [%02u] %-24s type=%u addr=0x%llx " + "off=0x%llx size=0x%llx\n", i, + gsp_phys_fw_section_name(phys.fw, sh), + sh->sh_type, (u64)sh->sh_addr, + (u64)sh->sh_offset, (u64)sh->sh_size); + } + } + + return 0; + +err: + gsp_phys_fw_unload(); + return -EINVAL; +} + +static void gsp_phys_unmap(struct pci_dev *pdev) +{ + if (phys.bar3) { + pci_iounmap(pdev, phys.bar3); + phys.bar3 = NULL; + } + if (phys.bar1) { + pci_iounmap(pdev, phys.bar1); + phys.bar1 = NULL; + } + if (phys.bar0) { + pci_iounmap(pdev, phys.bar0); + phys.bar0 = NULL; + } + if (phys.regions) { + pci_release_regions(pdev); + phys.regions = false; + } + if (phys.enabled) { + pci_disable_device(pdev); + phys.enabled = false; + } +} + +static int gsp_phys_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + u32 boot0, boot1; + int ret; + + pr_info("gsp-phys: probe %s\n", pci_name(pdev)); + + ret = pci_enable_device(pdev); + if (ret) { + pr_err("gsp-phys: pci_enable_device failed: %d\n", ret); + return ret; + } + phys.enabled = true; + + ret = pci_request_regions(pdev, "open-iov-gsp-phys"); + if (ret) { + pr_err("gsp-phys: pci_request_regions failed: %d\n", ret); + goto err_unmap; + } + phys.regions = true; + + phys.bar0 = pci_iomap(pdev, 0, 0); + phys.bar0_len = pci_resource_len(pdev, 0); + if (!phys.bar0) { + pr_err("gsp-phys: BAR0 map failed\n"); + ret = -ENOMEM; + goto err_unmap; + } + phys.bar1 = pci_iomap(pdev, 1, 0); + phys.bar1_len = pci_resource_len(pdev, 1); + if (!phys.bar1) { + pr_err("gsp-phys: BAR1 map failed\n"); + ret = -ENOMEM; + goto err_unmap; + } + phys.bar3 = pci_iomap(pdev, 3, 0); + phys.bar3_len = pci_resource_len(pdev, 3); + if (!phys.bar3) { + pr_err("gsp-phys: BAR3 map failed\n"); + ret = -ENOMEM; + goto err_unmap; + } + + boot0 = readl(phys.bar0 + GSP_PHYS_BOOT_0); + boot1 = readl(phys.bar0 + GSP_PHYS_BOOT_1); + pr_info("gsp-phys: BAR0 len=0x%llx BAR1 len=0x%llx BAR3 len=0x%llx\n", + (u64)phys.bar0_len, (u64)phys.bar1_len, (u64)phys.bar3_len); + pr_info("gsp-phys: BOOT0=0x%08x BOOT1=0x%08x (chip family in BOOT0[15:4])\n", + boot0, boot1); + pr_info("gsp-phys: GSP_CPUCTL=0x%08x MAILBOX0=0x%08x MAILBOX1=0x%08x " + "ENGINE=0x%08x FALCON_OS=0x%08x\n", + readl(phys.bar0 + GSP_PHYS_GSP_CPUCTL), + readl(phys.bar0 + GSP_PHYS_GSP_MAILBOX0), + readl(phys.bar0 + GSP_PHYS_GSP_MAILBOX1), + readl(phys.bar0 + GSP_PHYS_ENGINE), + readl(phys.bar0 + GSP_PHYS_FALCON_OS)); + + phys.pdev = pdev; + phys.claimed = true; + pr_info("gsp-phys: claimed %s\n", pci_name(pdev)); + return 0; + +err_unmap: + gsp_phys_unmap(pdev); + return ret; +} + +static void gsp_phys_remove(struct pci_dev *pdev) +{ + pr_info("gsp-phys: remove %s\n", pci_name(pdev)); + + phys.claimed = false; + gsp_phys_unmap(pdev); + phys.pdev = NULL; +} + +static const struct pci_device_id gsp_phys_ids[] = { + { PCI_DEVICE(GSP_PHYS_VENDOR, GSP_PHYS_DEVICE) }, + { } +}; +MODULE_DEVICE_TABLE(pci, gsp_phys_ids); + +static struct pci_driver gsp_phys_driver = { + .name = "open-iov-gsp-phys", + .id_table = gsp_phys_ids, + .probe = gsp_phys_probe, + .remove = gsp_phys_remove, +}; + +int gsp_phys_gpu_claim(void) +{ + struct pci_dev *pdev; + int ret; + + if (phys.claimed) + return 0; + + pdev = pci_get_device(GSP_PHYS_VENDOR, GSP_PHYS_DEVICE, NULL); + if (!pdev) { + pr_err("gsp-phys: device %04x:%04x not found\n", + GSP_PHYS_VENDOR, GSP_PHYS_DEVICE); + return -ENODEV; + } + + if (pdev->dev.driver) { + pr_info("gsp-phys: unbinding %s from %s\n", + pdev->dev.driver->name, pci_name(pdev)); + device_release_driver(&pdev->dev); + } + if (pdev->dev.driver) { + pr_err("gsp-phys: %s still bound after unbind (usage count?)\n", + pdev->dev.driver->name); + pci_dev_put(pdev); + return -EBUSY; + } + + ret = pci_register_driver(&gsp_phys_driver); + if (ret) { + pr_err("gsp-phys: register driver failed: %d\n", ret); + pci_dev_put(pdev); + return ret; + } + phys.drv_registered = true; + + if (!phys.claimed) { + pr_err("gsp-phys: our driver did not bind %s\n", pci_name(pdev)); + pci_unregister_driver(&gsp_phys_driver); + phys.drv_registered = false; + pci_dev_put(pdev); + return -ENODEV; + } + + pci_dev_put(pdev); + pr_info("gsp-phys: physical GPU claimed (backend rung 1)\n"); + + if (gsp_phys_fw_load()) + pr_warn("gsp-phys: firmware load failed; backend stays at rung 1\n"); + else + pr_info("gsp-phys: real GSP firmware staged (backend rung 2)\n"); + return 0; +} +EXPORT_SYMBOL_GPL(gsp_phys_gpu_claim); + +void gsp_phys_gpu_release(void) +{ + struct pci_dev *pdev; + + if (!phys.drv_registered && !phys.claimed) + return; + + gsp_phys_fw_unload(); + + pdev = pci_get_device(GSP_PHYS_VENDOR, GSP_PHYS_DEVICE, NULL); + + if (phys.drv_registered) { + pci_unregister_driver(&gsp_phys_driver); + phys.drv_registered = false; + } + + if (pdev) { + int ret = device_attach(&pdev->dev); + struct device_driver *drv = pdev->dev.driver; + + if (drv) + pr_info("gsp-phys: released %s; rebound to %s (rc=%d)\n", + pci_name(pdev), drv->name, ret); + else + pr_warn("gsp-phys: released %s; no driver rebound " + "(rc=%d), run `modprobe nvidia`\n", + pci_name(pdev), ret); + pci_dev_put(pdev); + } + phys.claimed = false; +} diff --git a/kernel/gsp_phys_gpu.h b/kernel/gsp_phys_gpu.h new file mode 100644 index 0000000..b8a1955 --- /dev/null +++ b/kernel/gsp_phys_gpu.h @@ -0,0 +1,15 @@ +#ifndef GSP_PHYS_GPU_H +#define GSP_PHYS_GPU_H + +#include <linux/types.h> + +/* + * Physical GPU ownership (real-GSP execution backend, bring-up rung 1). + * Claim is disabled unless the gsp_phys_gpu module parameter is set. + */ +bool gsp_phys_gpu_claimed(void); +int gsp_phys_gpu_claim(void); +void gsp_phys_gpu_release(void); +void __iomem *gsp_phys_gpu_bar0(void); + +#endif /* GSP_PHYS_GPU_H */ diff --git a/kernel/main.c b/kernel/main.c index fa88d19..f86dc98 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -31,6 +31,7 @@ #include "ioctl.h" #include "nv-vgpu-vfio-interface.h" +#include "gsp_phys_gpu.h" #define NV_VERSION_STRING "550.163.01" @@ -155,6 +156,8 @@ static rm_vgpu_vfio_ops_t g_rm_vgpu_vfio_ops; /* mdev parent structure for our physical device */ static struct mdev_parent *g_mdev_parent = NULL; +static bool gsp_phys_gpu_param; + /* VFIO ops implementation */ static NV_STATUS nv_vgpu_vfio_probe(struct pci_dev *pdev, NvU32 vgpu_type_id, NvU32 *vgpu_id); static void nv_vgpu_vfio_remove(struct pci_dev *pdev); @@ -1443,6 +1446,13 @@ static int __init nv_vgpu_init(void) } pr_info("gsp-resman-vfio: VFIO ops set\n"); + if (gsp_phys_gpu_param) { + ret = gsp_phys_gpu_claim(); + if (ret) + pr_warn("gsp-resman-vfio: physical GPU claim failed: %d\n", + ret); + } + pr_info("NVIDIA vGPU VFIO driver loaded (version %s)\n", NVIDIA_VGPU_VFIO_VERSION); /* Start the emulated-GPU progress service: it discovers the guest's * GPU-VA -> guest-PA map and applies copy-engine semaphore releases so @@ -1478,6 +1488,8 @@ static void __exit nv_vgpu_exit(void) pr_info("gsp-resman-vfio: nv_vgpu_exit: clearing VFIO ops to break circular dependency\n"); g_vgpu_vfio_ops = NULL; + gsp_phys_gpu_release(); + if (nv_vgpu_driver.device_count > 0) { struct nv_vgpu_device *vgpu; mutex_lock(&nv_vgpu_driver.lock); @@ -2428,6 +2440,15 @@ module_param_cb(chan_dump, &nv_vgpu_chan_dump_ops, NULL, 0200); MODULE_PARM_DESC(chan_dump, "Dump the sema service channel table (any value)"); /* + * Physical GPU ownership for the real-GSP execution backend (bring-up rung + * 1: claim). Default off. On claim the distribution driver is unbound from + * the physical GPU, this module's pci_driver binds it and the BARs are + * mapped. Only set when no VM holds an mdev; release happens on unload. + */ +module_param_named(gsp_phys_gpu, gsp_phys_gpu_param, bool, 0644); +MODULE_PARM_DESC(gsp_phys_gpu, "Claim the physical GPU for the real-GSP backend (default 0)"); + +/* * GR ring discovery: pin the compute channels' rings/USERDs, which follow the * CE rings in the channel video-memory allocation. Default on; set 0 to leave * GR channels untranslated when debugging push-execution side effects. |
