summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-30 23:10:52 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-30 23:10:52 -0400
commitb08989c6ab43892a4f19798bb51c720f0000dc80 (patch)
tree8bb91a99f577e84b8b72805b1deec65fadaa0c4b /kernel
parentb4e2f15b534f550c515f625fdcb06dc9908456ab (diff)
downloadopen-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/Makefile1
-rw-r--r--kernel/gsp_phys_gpu.c373
-rw-r--r--kernel/gsp_phys_gpu.h15
-rw-r--r--kernel/main.c21
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.