summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 14:10:43 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 14:11:45 -0400
commitc8249854284c3e8181587b96e3c5b4177d705e0c (patch)
treef0f1c16b055c88b059d159db715407d2f7406090
parent85d888e4763837a5b2f5843c771bf6b6d6e6bfe3 (diff)
downloadopen-iov-c8249854284c3e8181587b96e3c5b4177d705e0c.tar.gz
open-iov-c8249854284c3e8181587b96e3c5b4177d705e0c.zip
kernel: Present physical TU104 to guest + emulate GSP boot (FWSEC/Booter) + VBIOS serving
Guest driver probe fixes (unmodified 570.144 open driver on physical path): - kernel/main.c: add show_description to the mdev driver. Userspace mdev management tools parse 'framebuffer=<n>M' from every mdev type's description sysfs file before creating an mdev; the missing file made such tools fail to create the vGPU. Also fix create_mdev.sh for the real type dir name pci-nvidia-vgpu-* (mdev core names dirs dev_driver_string(parent)-<sysfs_name>; the parent registers before nvidia.ko binds so dev_driver_string == 'pci'). - kernel/bar_emulation.c: seed BAR0 shadow so guest sees a REAL, PHYSICAL, non-virtual TU104 (user directive: no PV/VF vGPU paths, unmodified driver): * PMC_BOOT_0/1/42 = physical TU104 values captured from host hardware (0x164000a1 / 0x0 / 0x164a1000). Without these, halmgrGetHalForGpu() fails and probe aborts with 'does not include the required GSP'. * Serve a valid VBIOS at the PROM window (BAR0+0x300000) loaded from /lib/firmware/tu104_vbios.bin (dump of host TU104 ROM). Guest's kgspExtractVbiosFromRom_TU102 needs it for the mandatory FWSEC ucode (kgspGetFrtsSize is hardwired to 1MB on Turing). * Pre-set GSP secure-boot (GFW) completion state: PGC6 PLM (0x118128)=1, GFW_BOOT progress (0x118234)=0xFF, GSP falcon CPUCTL HALTED (0x110100). The driver's post-FLR GFW wait only *waits* (never kicks), so the pre-set state satisfies it immediately. - kernel/gsp_emulation.c/h: physical GSP boot emulation. * Falcon boot-watch: poll GSP falcon/PGC6/WPR2/scratch registers and log changes (guest BAR0 is mmap-based, otherwise invisible to host). * gsp_emu_poll_boot: STARTCPU auto-complete state machine. Guest kicks FWSEC then Booter Load via GSP falcon CPUCTL; detect each kick and complete the stage (HALTED + FRTS err NONE + WPR2 + mailbox + RISCV-active) so the guest believes its ucodes ran. Diagnosis that led here (documented in Handoff-vGPU-IO.md): - Open nvidia.ko can never be an SR-IOV VF vGPU guest: os_is_grid_supported() is a stub returning FALSE in the open build (disassembled). PV path also rejected per user directive. Only the physical path with a fully emulated local GSP boot remains.
-rw-r--r--kernel/bar_emulation.c89
-rw-r--r--kernel/gsp_emulation.c173
-rw-r--r--kernel/gsp_emulation.h15
-rw-r--r--kernel/main.c15
-rwxr-xr-xscripts/create_mdev.sh4
5 files changed, 294 insertions, 2 deletions
diff --git a/kernel/bar_emulation.c b/kernel/bar_emulation.c
index 009f0e2..e120b3c 100644
--- a/kernel/bar_emulation.c
+++ b/kernel/bar_emulation.c
@@ -6,6 +6,7 @@
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/eventfd.h>
+#include <linux/firmware.h>
#include "nvidia-vgpu-vfio.h"
#include "bar_emulation.h"
@@ -14,6 +15,11 @@
#define NV_VGPU_BAR0_TRAP_REGIONS 64
+/* VBIOS (PROM window) emulation - see nv_vgpu_bar_emul_init */
+#define NV_VGPU_VBIOS_FW_NAME "tu104_vbios.bin"
+#define NV_VGPU_PROM_WINDOW_OFFSET 0x300000
+#define NV_VGPU_VBIOS_MAX_SIZE 0x100000 /* 1MB (s_getBaseBiosMaxSize_TU102) */
+
/* MSI-X Table entry size (16 bytes: addr_lo, addr_hi, data, vector_control) */
#define NV_VGPU_MSIX_ENTRY_SIZE 16
#define NV_VGPU_MSIX_TABLE_ENTRIES 32
@@ -202,6 +208,89 @@ int nv_vgpu_bar_emul_init(struct nv_vgpu_device *vgpu)
/* Initialize MSI-X emulation */
nv_vgpu_msix_init(ctx);
+ /*
+ * Seed PMC boot registers so the guest driver sees a REAL,
+ * PHYSICAL, non-virtual TU104 GPU. Values captured from the host's
+ * physical TU104 (RTX 2080 SUPER, PCI 0000:01:00.0):
+ * NV_PMC_BOOT_0 (BAR0+0x000) = 0x164000a1 (arch TU104, rev A1)
+ * NV_PMC_BOOT_1 (BAR0+0x004) = 0x00000000 (VGPU field = REAL,
+ * no PV/VF - the guest
+ * must take the physical
+ * probe path, never a
+ * para-virtual vGPU path)
+ * NV_PMC_BOOT_42 (BAR0+0xa00) = 0x164a1000 (chip id / revision)
+ *
+ * The guest reads these via the VFIO-mmap'd shadow (and via VFIO
+ * region reads, which hit the same shadow). Without them the open
+ * driver's halmgrGetHalForGpu() fails and probe aborts with the
+ * "does not include the required GPU System Processor (GSP)" error.
+ */
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x000) = 0x164000a1;
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x004) = 0x00000000;
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0xa00) = 0x164a1000;
+
+ /*
+ * Pre-set the GSP secure-boot (GFW) completion state.
+ *
+ * During RmInitAdapter the guest driver waits for the GPU's
+ * post-FLR secure boot (kgspWaitForGfwBootOk -> gpuWaitForGfwBootComplete
+ * -> _gpuIsGfwBootCompleted_TU102 + kflcnWaitForHalt). On real hardware
+ * the boot ROM runs this automatically after reset; the driver only
+ * *waits* for it (it never kicks the falcon here). We present the
+ * "already booted" state so the wait succeeds immediately:
+ * 0x118128 NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK
+ * bit0 READ_PROTECTION_LEVEL0 = ENABLE (FWSEC lowered PLM)
+ * 0x118234 NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT
+ * bits7:0 PROGRESS = 0xFF (COMPLETED)
+ * 0x110100 GSP falcon CPUCTL bit4 = HALTED
+ *
+ * Driver-kicked falcon runs later (FWSEC/Booter) clear CPUCTL via
+ * STARTCPU; the GSP emulation thread re-completes those (see
+ * gsp_emu_poll_boot in gsp_emulation.c).
+ */
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x118128) = 0x00000001;
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x118234) = 0x000000ff;
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x110100) = 0x00000010;
+
+ /*
+ * Serve a valid VBIOS at the PROM window (BAR0+0x300000).
+ *
+ * The guest driver's physical GSP boot (kgspInitRm -> TU102 path)
+ * reads the VBIOS via NV_PROM_DATA (BAR0+0x300000) to extract the
+ * FWSEC ucode (mandatory on Turing: kgspGetFrtsSize is hardwired
+ * to 1MB, so FWSEC/FRTS cannot be skipped). Without a valid ROM
+ * signature the probe aborts with:
+ * "kgspExtractVbiosFromRom_TU102: did not find valid ROM signature"
+ *
+ * The image is a dump of the host's physical TU104 VBIOS (read via
+ * its own PROM window), loaded from /lib/firmware/tu104_vbios.bin.
+ * Non-fatal if absent: probe then fails later in FWSEC, not here.
+ */
+ {
+ const struct firmware *vbios_fw;
+ int fw_ret = request_firmware(&vbios_fw, NV_VGPU_VBIOS_FW_NAME,
+ &vgpu->pdev->dev);
+ if (fw_ret == 0) {
+ u32 copy_size = min_t(u32, vbios_fw->size,
+ NV_VGPU_VBIOS_MAX_SIZE);
+
+ if (NV_VGPU_PROM_WINDOW_OFFSET + copy_size <= ctx->bar0_shadow_size) {
+ memcpy((u8 *)ctx->bar0_shadow + NV_VGPU_PROM_WINDOW_OFFSET,
+ vbios_fw->data, copy_size);
+ nv_vgpu_info(vgpu, "VBIOS loaded at BAR0+0x%x (%u bytes)",
+ NV_VGPU_PROM_WINDOW_OFFSET, copy_size);
+ } else {
+ nv_vgpu_warn(vgpu, "VBIOS too large for PROM window (%u bytes)",
+ copy_size);
+ }
+ release_firmware(vbios_fw);
+ } else {
+ nv_vgpu_warn(vgpu, "VBIOS firmware %s not found (%d); "
+ "guest GSP boot will fail at ROM parse",
+ NV_VGPU_VBIOS_FW_NAME, fw_ret);
+ }
+ }
+
vgpu->bar_emul_ctx = ctx;
return 0;
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index bfd8be7..81c319f 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -66,6 +66,173 @@ static inline void gsp_emu_copy_response_to_shared(struct gsp_emu_context *ctx,
}
/* ============================================================
+ * GSP Falcon Boot-Watch (observability for physical GSP boot)
+ * ============================================================ */
+
+/*
+ * The guest's physical GSP boot (FWSEC -> Booter -> RISCV) pokes the GSP
+ * falcon block via the mmap'd shadow, which the host cannot trap. Poll these
+ * BAR0 offsets and log changes so the boot sequence is visible in host dmesg.
+ */
+static const struct {
+ u32 offset;
+ const char *name;
+} gsp_emu_falcon_watch[GSP_EMU_FALCON_WATCH_COUNT] = {
+ { 0x110040, "GSP_MAILBOX0" },
+ { 0x110044, "GSP_MAILBOX1" },
+ { 0x110080, "GSP_FALCON_OS" },
+ { 0x110100, "GSP_CPUCTL" },
+ { 0x110180, "GSP_IMEMC" },
+ { 0x110184, "GSP_IMEMD" },
+ { 0x1101c0, "GSP_DMEMC" },
+ { 0x1101c4, "GSP_DMEMD" },
+ { 0x1103c0, "GSP_ENGINE" },
+ { 0x110c04, "GSP_QUEUE_HEAD1" },
+ { 0x110c08, "GSP_QUEUE_TAIL0" },
+ { 0x110c0c, "GSP_QUEUE_TAIL1" },
+ { 0x118128, "PGC6_PLM_GFW" },
+ { 0x118234, "PGC6_GFW_BOOT" },
+ { 0x1fa824, "WPR2_ADDR_LO" },
+ { 0x1fa828, "WPR2_ADDR_HI" },
+ { 0x001438, "VBIOS_SCRATCH_0E" },
+};
+
+static void gsp_emu_poll_falcon_watch(struct gsp_emu_context *ctx)
+{
+ int i;
+
+ for (i = 0; i < GSP_EMU_FALCON_WATCH_COUNT; i++) {
+ u32 val = readl(ctx->bar0_vaddr + gsp_emu_falcon_watch[i].offset);
+
+ if (!ctx->falcon_watch_valid) {
+ ctx->falcon_watch_last[i] = val;
+ continue;
+ }
+
+ if (val != ctx->falcon_watch_last[i]) {
+ nv_vgpu_info(ctx->vgpu, "FALCON %s (0x%x): 0x%08x -> 0x%08x",
+ gsp_emu_falcon_watch[i].name,
+ gsp_emu_falcon_watch[i].offset,
+ ctx->falcon_watch_last[i], val);
+ ctx->falcon_watch_last[i] = val;
+ }
+ }
+ ctx->falcon_watch_valid = true;
+}
+
+/* ============================================================
+ * Physical GSP Boot Emulation (FWSEC / Booter falcon auto-complete)
+ * ============================================================ */
+
+/* BAR0 shadow offsets used by the guest's physical GSP boot */
+#define GSP_EMU_REG_GSP_CPUCTL 0x110100 /* bit1 STARTCPU, bit4 HALTED */
+#define GSP_EMU_REG_GSP_ENGINE 0x1103c0 /* bit0 RESET */
+#define GSP_EMU_REG_GSP_MAILBOX0 0x110040
+#define GSP_EMU_REG_VBIOS_SCRATCH_0E 0x001438 /* FRTS err code (0=NONE) */
+#define GSP_EMU_REG_WPR2_ADDR_LO 0x1fa824
+#define GSP_EMU_REG_WPR2_ADDR_HI 0x1fa828
+#define GSP_EMU_REG_RISCV_STATUS 0x110240 /* bit0 ACTIVE (NV_PRISCV_RISCV
+ CORE_SWITCH_RISCV_STATUS at
+ GSP riscv base 0x110000) */
+
+#define GSP_EMU_CPUCTL_STARTCPU 0x2
+#define GSP_EMU_CPUCTL_HALTED 0x10
+
+enum gsp_emu_boot_stage {
+ GSP_BOOT_STAGE_IDLE = 0, /* waiting for first kick (FWSEC) */
+ GSP_BOOT_STAGE_FWSEC_DONE, /* FWSEC FRTS completed */
+ GSP_BOOT_STAGE_BOOTER_DONE, /* Booter Load completed */
+};
+
+static inline void gsp_emu_shadow_write32(struct gsp_emu_context *ctx,
+ u32 offset, u32 val)
+{
+ writel(val, ctx->bar0_vaddr + offset);
+}
+
+static inline u32 gsp_emu_shadow_read32(struct gsp_emu_context *ctx, u32 offset)
+{
+ return readl(ctx->bar0_vaddr + offset);
+}
+
+/*
+ * gsp_emu_poll_boot - drive the guest's physical GSP boot to completion.
+ *
+ * The guest driver kicks falcon ucodes (FWSEC for FRTS, then Booter Load for
+ * GSP-RM) by writing STARTCPU to the GSP falcon CPUCTL, then polls for HALTED
+ * plus stage-specific result registers. The auto-boot waits (GFW) are already
+ * satisfied by the state pre-set in nv_vgpu_bar_emul_init. Here we watch for
+ * STARTCPU kicks and complete each stage so the guest believes its falcon
+ * ucodes ran successfully.
+ */
+static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
+{
+ u32 cpuctl = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_CPUCTL);
+ u32 engine = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_ENGINE);
+
+ /* ENGINE reset asserted -> falcon rebooted, restart stage tracking */
+ if ((engine & 0x1) && ctx->boot_stage != GSP_BOOT_STAGE_IDLE) {
+ ctx->boot_stage = GSP_BOOT_STAGE_IDLE;
+ ctx->flcn_kick_count = 0;
+ nv_vgpu_info(ctx->vgpu, "GSP-BOOT: falcon reset, back to IDLE");
+ return;
+ }
+
+ /* Nothing to do unless the guest kicked the falcon */
+ if (!(cpuctl & GSP_EMU_CPUCTL_STARTCPU))
+ return;
+
+ ctx->flcn_kick_count++;
+
+ switch (ctx->boot_stage) {
+ case GSP_BOOT_STAGE_IDLE:
+ /*
+ * First kick after reset: FWSEC (FRTS setup). Complete it:
+ * - CPUCTL: clear STARTCPU, set HALTED
+ * - VBIOS_SCRATCH[0x0E] FRTS_ERR_CODE = NONE (0)
+ * - WPR2_ADDR_HI != 0, WPR2_ADDR_LO = frtsOffset >> 0xc
+ * frtsOffset is learned from the driver's own error message if
+ * our value mismatches (deterministic for fixed FB + fw image).
+ */
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_VBIOS_SCRATCH_0E, 0);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_HI, 0x1);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_LO,
+ ctx->wpr2_addr_lo);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
+ GSP_EMU_CPUCTL_HALTED);
+ ctx->boot_stage = GSP_BOOT_STAGE_FWSEC_DONE;
+ nv_vgpu_info(ctx->vgpu, "GSP-BOOT: FWSEC kick completed "
+ "(WPR2_LO=0x%x)", ctx->wpr2_addr_lo);
+ break;
+
+ case GSP_BOOT_STAGE_FWSEC_DONE:
+ /*
+ * Second kick: Booter Load (loads/authenticates GSP-RM image,
+ * starts the RISCV). Complete it:
+ * - CPUCTL: clear STARTCPU, set HALTED
+ * - MAILBOX0 = 0 (booter success status)
+ * - RISCV_STATUS = ACTIVE (kflcnIsRiscvActive passes)
+ */
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_MAILBOX0, 0);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_RISCV_STATUS, 0x1);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
+ GSP_EMU_CPUCTL_HALTED);
+ ctx->boot_stage = GSP_BOOT_STAGE_BOOTER_DONE;
+ nv_vgpu_info(ctx->vgpu, "GSP-BOOT: Booter kick completed, "
+ "RISCV active");
+ break;
+
+ default:
+ /* Later kicks (e.g. resume/unload): just complete them */
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
+ GSP_EMU_CPUCTL_HALTED);
+ nv_vgpu_info(ctx->vgpu, "GSP-BOOT: extra kick #%u completed",
+ ctx->flcn_kick_count);
+ break;
+ }
+}
+
+/* ============================================================
* RPC Processing
* ============================================================ */
@@ -276,6 +443,12 @@ static int gsp_emu_thread_fn(void *data)
/* Read doorbell */
doorbell_val = gsp_emu_read_doorbell(ctx);
+ /* Watch GSP falcon boot registers (physical GSP boot tracing) */
+ gsp_emu_poll_falcon_watch(ctx);
+
+ /* Drive the guest's physical GSP boot to completion */
+ gsp_emu_poll_boot(ctx);
+
if (doorbell_val != 0) {
/* ACK doorbell immediately */
gsp_emu_write_doorbell(ctx, 0);
diff --git a/kernel/gsp_emulation.h b/kernel/gsp_emulation.h
index 12bce63..53560e9 100644
--- a/kernel/gsp_emulation.h
+++ b/kernel/gsp_emulation.h
@@ -34,6 +34,12 @@
#define GSP_EMU_POLL_SLEEP_MIN_US 50
#define GSP_EMU_POLL_SLEEP_MAX_US 100
+/* GSP falcon boot-watch: registers polled for change logging so the guest's
+ * physical GSP boot sequence is visible on the host (guest BAR0 access is
+ * mmap-based and otherwise invisible). Offsets are BAR0-relative, GSP falcon
+ * block at 0x110000. */
+#define GSP_EMU_FALCON_WATCH_COUNT 17
+
/* ============================================================
* GSP Emulation Context
* ============================================================ */
@@ -63,6 +69,15 @@ struct gsp_emu_context {
/* MSI-X injection */
struct eventfd_ctx *msix_trigger[NV_VGPU_MAX_IRQS];
+
+ /* Falcon boot-watch: last-sampled values for change detection */
+ u32 falcon_watch_last[GSP_EMU_FALCON_WATCH_COUNT];
+ bool falcon_watch_valid;
+
+ /* Physical GSP boot emulation state (gsp_emu_poll_boot) */
+ u32 boot_stage; /* enum gsp_emu_boot_stage */
+ u32 flcn_kick_count; /* STARTCPU kicks seen since last ENGINE reset */
+ u32 wpr2_addr_lo; /* WPR2_ADDR_LO value to report after FWSEC */
};
/* ============================================================
diff --git a/kernel/main.c b/kernel/main.c
index 3da7dc6..a47fe23 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -96,12 +96,27 @@ static unsigned int nv_vgpu_get_available(struct mdev_type *mtype)
return active >= max ? 0 : max - active;
}
+/*
+ * show_description - mdev type description for management tools.
+ *
+ * / parses "framebuffer=<n>M" from this string for EVERY
+ * supported type before creating an mdev, so the field is mandatory
+ * (missing description file -> readFile failure -> no vGPU).
+ * 256M matches the vGPU BAR1 (VRAM aperture) size we expose to guests.
+ */
+static ssize_t nv_vgpu_show_description(struct mdev_type *mtype, char *buf)
+{
+ return sysfs_emit(buf, "num_heads=1, frl_config=60, framebuffer=256M, "
+ "max_resolution=5120x2880, max_instance=1\n");
+}
+
struct mdev_driver nv_vgpu_mdev_driver = {
.device_api = VFIO_DEVICE_API_PCI_STRING,
.max_instances = 8,
.probe = nv_vgpu_mdev_create,
.remove = nv_vgpu_mdev_remove,
.get_available = nv_vgpu_get_available,
+ .show_description = nv_vgpu_show_description,
.driver = {
.name = "nvidia-vgpu-vfio-mdev",
},
diff --git a/scripts/create_mdev.sh b/scripts/create_mdev.sh
index 7bafaae..a6ea91e 100755
--- a/scripts/create_mdev.sh
+++ b/scripts/create_mdev.sh
@@ -5,10 +5,10 @@
set -euo pipefail
PCI_ADDR="${1:-}"
-VGPU_TYPE="${2:-nvidia-nvidia-vgpu-turing}"
+VGPU_TYPE="${2:-pci-nvidia-vgpu-turing}"
# Supported vGPU types (matching the mdev type names)
-VALID_TYPES=("nvidia-nvidia-vgpu-ampere" "nvidia-nvidia-vgpu-hopper" "nvidia-nvidia-vgpu-turing" "nvidia-nvidia-vgpu-pascal")
+VALID_TYPES=("pci-nvidia-vgpu-ampere" "pci-nvidia-vgpu-hopper" "pci-nvidia-vgpu-turing" "pci-nvidia-vgpu-pascal")
# Find NVIDIA GPU if not specified
if [[ -z "$PCI_ADDR" ]]; then