From c8249854284c3e8181587b96e3c5b4177d705e0c Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 14:10:43 -0400 Subject: 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=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)-; 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. --- kernel/bar_emulation.c | 89 +++++++++++++++++++++++++ kernel/gsp_emulation.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++++ kernel/gsp_emulation.h | 15 +++++ kernel/main.c | 15 +++++ scripts/create_mdev.sh | 4 +- 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 #include #include +#include #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 @@ -65,6 +65,173 @@ static inline void gsp_emu_copy_response_to_shared(struct gsp_emu_context *ctx, memcpy(ctx->shared_mem_virt + 0x80, rsp, sizeof(*rsp)); } +/* ============================================================ + * 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=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 -- cgit v1.2.3