diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 07:40:07 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 07:40:07 -0400 |
| commit | 62fdab77ac5c856e5751f95293dc9a1693d26424 (patch) | |
| tree | 18b6b4def7af3cafe994613d1bc4fe45c36aacfa /kernel | |
| parent | cc7ec3a0c8cd282b12425bfd37763584e93320f4 (diff) | |
| download | open-iov-62fdab77ac5c856e5751f95293dc9a1693d26424.tar.gz open-iov-62fdab77ac5c856e5751f95293dc9a1693d26424.zip | |
gsp-phys: FWSEC failure diagnostics (falcon scratch/mailbox, DMEM/IMEM readback)
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/gsp_phys_fwsec.c | 172 | ||||
| -rw-r--r-- | kernel/gsp_phys_gpu.c | 15 |
2 files changed, 186 insertions, 1 deletions
diff --git a/kernel/gsp_phys_fwsec.c b/kernel/gsp_phys_fwsec.c index c4b0424..dc8c979 100644 --- a/kernel/gsp_phys_fwsec.c +++ b/kernel/gsp_phys_fwsec.c @@ -46,6 +46,17 @@ module_param_named(gsp_phys_fwsec_dryrun, gsp_phys_fwsec_dryrun_param, bool, MODULE_PARM_DESC(gsp_phys_fwsec_dryrun, "Read-only validation of the stage-3c VBIOS/BIT/FWSEC parse and the staging/placement math on the claimed GPU after the boot layout succeeds (default 0; no device writes)"); +/* + * Verbose read-only state dump after a successful FWSEC kick (the failure + * path always dumps). Off by default: the dump reads DMEM/IMEM through the + * falcon ports, which is only for post-mortem/compare purposes. + */ +static bool gsp_phys_fwsec_verbose_param; +module_param_named(gsp_phys_fwsec_verbose, gsp_phys_fwsec_verbose_param, + bool, 0644); +MODULE_PARM_DESC(gsp_phys_fwsec_verbose, + "Also dump the bounded read-only FWSEC kick state (falcon regs, scratch, DMEM/IMEM readback) after a successful halt (default 0)"); + bool gsp_phys_fwsec_enabled(void) { return gsp_phys_fwsec_param; @@ -65,6 +76,7 @@ bool gsp_phys_fwsec_dryrun_enabled(void) /* Falcon register offsets, relative to the engine base. */ #define GSP_PHYS_FALCON_MAILBOX0 0x40 #define GSP_PHYS_FALCON_MAILBOX1 0x44 +#define GSP_PHYS_FALCON_OS 0x80 #define GSP_PHYS_FALCON_RM 0x84 #define GSP_PHYS_FALCON_HWCFG2 0xf4 #define GSP_PHYS_FALCON_CPUCTL 0x100 @@ -108,12 +120,16 @@ bool gsp_phys_fwsec_dryrun_enabled(void) /* IMEM/DMEM access port fields. */ #define GSP_PHYS_FALCON_ADDR_MASK (0x0000fffc) #define GSP_PHYS_FALCON_AINCW BIT(24) +#define GSP_PHYS_FALCON_AINCR BIT(25) #define GSP_PHYS_FALCON_IMEMC_SECURE BIT(28) /* GPU-side boot state registers. */ #define GSP_PHYS_PMC_BOOT_0 0x00000000 #define GSP_PHYS_VBIOS_SCRATCH_0E 0x001438 #define GSP_PHYS_VBIOS_SCRATCH_15 0x001454 +/* PBUS VBIOS scratch window (the FWSEC error codes live inside it). */ +#define GSP_PHYS_VBIOS_SCRATCH_BASE 0x001400 +#define GSP_PHYS_VBIOS_SCRATCH_WORDS 32 #define GSP_PHYS_WPR2_ADDR_LO 0x1fa824 #define GSP_PHYS_WPR2_ADDR_HI 0x1fa828 /* WPR2 VAL field is bits 31:4 (DRF_VAL returns the unshifted field). */ @@ -124,6 +140,7 @@ bool gsp_phys_fwsec_dryrun_enabled(void) #define GSP_PHYS_GFW_BOOT_COMPLETED 0xff #define GSP_PHYS_GFW_PLM 0x118128 #define GSP_PHYS_GFW_PLM_LEVEL0 BIT(0) +#define GSP_PHYS_RISCV_STATUS 0x111240 #define GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS 0x2174c #define GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS_FIELD BIT(0) @@ -259,6 +276,8 @@ struct gsp_phys_fwsec_ucode { static struct gsp_phys_fwsec { bool touched; /* FWSEC device state may be programmed */ + struct pci_dev *pdev; /* claimed device (failure-dump state) */ + u32 cmd_offset; /* last patched interface cmd offset */ struct gsp_phys_fwsec_ucode ucode; } fwsec; @@ -405,6 +424,34 @@ static void gsp_phys_falcon_imem_copy(void __iomem *bar0, u32 base, u32 dest, } } +/* + * Read-only port access used by the failure dump. DMEM has an auto-increment + * read mode (AINCR); IMEM has none, so its address is rewritten per word. + * Callers pass a fixed, small word count (the dump is always bounded). + */ +static void gsp_phys_falcon_dmem_read(void __iomem *bar0, u32 base, u32 dest, + u32 *dst, u32 words) +{ + u32 i; + + writel((dest & GSP_PHYS_FALCON_ADDR_MASK) | GSP_PHYS_FALCON_AINCR, + bar0 + base + GSP_PHYS_FALCON_DMEMC); + for (i = 0; i < words; i++) + dst[i] = readl(bar0 + base + GSP_PHYS_FALCON_DMEMD); +} + +static void gsp_phys_falcon_imem_read(void __iomem *bar0, u32 base, u32 dest, + u32 *dst, u32 words) +{ + u32 i; + + for (i = 0; i < words; i++) { + writel((dest + i * 4) & GSP_PHYS_FALCON_ADDR_MASK, + bar0 + base + GSP_PHYS_FALCON_IMEMC); + dst[i] = readl(bar0 + base + GSP_PHYS_FALCON_IMEMD); + } +} + static void gsp_phys_falcon_start_cpu(void __iomem *bar0, u32 base) { u32 cpuctl = readl(bar0 + base + GSP_PHYS_FALCON_CPUCTL); @@ -606,6 +653,125 @@ static int gsp_phys_fwsec_bl_desc_fill(const struct gsp_phys_fwsec_ucode *u, } /* + * Bounded, read-only post-mortem dump for a FWSEC kick: PCI bus-master + * state, both falcon cores, WPR2/FRTS state, the PBUS scratch window, the + * raw DMEM0 contents and the IMEM code readback. DMEM0 tells us how far + * the generic boot-loader got: the 84-byte BL descriptor intact means its + * host DMA never delivered anything; the FWSEC data image means the loads + * completed and the FWSEC ucode itself never returned. All accesses are + * bounded and use the reference's port method (DMEM via AINCR, IMEM + * address rewritten per word). + */ +static void gsp_phys_fwsec_dump_kick_state(void __iomem *bar0, + const struct gsp_phys_fwsec_bl_dmem_desc *bl, + const struct gsp_phys_fwsec_ucode *u, + u32 bl_dst) +{ + u32 words[GSP_PHYS_VBIOS_SCRATCH_WORDS]; + u32 expected_bl0 = get_unaligned_le32(gsp_phys_sec2_bl_image); + u32 expected_code0 = 0; + u32 i; + u16 pci_cmd = 0; + bool have_expected_code = false; + bool desc_intact, data_image, code_loaded, bl_intact; + const char *verdict; + + if (fwsec.pdev) + pci_read_config_word(fwsec.pdev, PCI_COMMAND, &pci_cmd); + pr_info("gsp-phys-fwsec: FAILDUMP PCI_COMMAND=0x%04x [bus_master=%s mem=%s io=%s]\n", + pci_cmd, pci_cmd & PCI_COMMAND_MASTER ? "on" : "OFF", + pci_cmd & PCI_COMMAND_MEMORY ? "on" : "off", + pci_cmd & PCI_COMMAND_IO ? "on" : "off"); + + pr_info("gsp-phys-fwsec: FAILDUMP GSP CPUCTL=0x%08x HWCFG=0x%08x HWCFG2=0x%08x ENGINE=0x%08x DMACTL=0x%08x BOOTVEC=0x%08x\n", + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_HWCFG), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_HWCFG2), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_ENGINE), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_DMACTL), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_BOOTVEC)); + pr_info("gsp-phys-fwsec: FAILDUMP GSP MBOX0=0x%08x MBOX1=0x%08x FALCON_OS=0x%08x FALCON_RM=0x%08x\n", + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX1), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_RM)); + pr_info("gsp-phys-fwsec: FAILDUMP SEC2 CPUCTL=0x%08x HWCFG=0x%08x ENGINE=0x%08x MBOX0=0x%08x MBOX1=0x%08x FALCON_OS=0x%08x\n", + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_HWCFG), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_ENGINE), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_OS)); + + pr_info("gsp-phys-fwsec: FAILDUMP state WPR2_LO raw=0x%08x VAL=0x%x WPR2_HI raw=0x%08x VAL=0x%x PLM=0x%08x GFW_BOOT=0x%08x SCRATCH_0E=0x%08x FRTS_ERR=0x%x SCRATCH_15=0x%08x SB_ERR=0x%x RISCV_STATUS=0x%08x\n", + readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), + gsp_phys_fwsec_wpr2_lo_val(bar0), + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), + gsp_phys_fwsec_wpr2_hi_val(bar0), + readl(bar0 + GSP_PHYS_GFW_PLM), + readl(bar0 + GSP_PHYS_GFW_BOOT), + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_0E), + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_0E) >> 16, + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_15), + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_15) & 0xffff, + readl(bar0 + GSP_PHYS_RISCV_STATUS)); + + for (i = 0; i < GSP_PHYS_VBIOS_SCRATCH_WORDS; i++) + words[i] = readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_BASE + i * 4); + pr_info("gsp-phys-fwsec: FAILDUMP scratch 0x1400..0x143f: %*ph\n", + 64, (const u8 *)words); + pr_info("gsp-phys-fwsec: FAILDUMP scratch 0x1440..0x147f: %*ph\n", + 64, (const u8 *)words + 64); + + gsp_phys_falcon_dmem_read(bar0, GSP_PHYS_GSP_BASE, 0, words, + sizeof(*bl) / 4); + pr_info("gsp-phys-fwsec: FAILDUMP DMEM0 (84 B): %*ph\n", + (int)sizeof(*bl), (const u8 *)words); + desc_intact = memcmp(words, bl, sizeof(*bl)) == 0; + data_image = u->data_va && + u->data_size >= (size_t)u->desc.dmem_phys_base + + sizeof(*bl) && + memcmp(words, (const u8 *)u->data_va + + u->desc.dmem_phys_base, sizeof(*bl)) == 0; + + gsp_phys_falcon_imem_read(bar0, GSP_PHYS_GSP_BASE, bl_dst, words, 4); + bl_intact = words[0] == expected_bl0; + pr_info("gsp-phys-fwsec: FAILDUMP IMEM BL @0x%x: %08x %08x %08x %08x (word0 want 0x%08x -> %s)\n", + bl_dst, words[0], words[1], words[2], words[3], expected_bl0, + bl_intact ? "intact" : "CHANGED"); + + if (u->code_va && u->code_size >= (size_t)u->desc.imem_phys_base + 4) { + expected_code0 = get_unaligned_le32(u->code_va + + u->desc.imem_phys_base); + have_expected_code = true; + } + gsp_phys_falcon_imem_read(bar0, GSP_PHYS_GSP_BASE, + u->desc.imem_phys_base, words, 4); + code_loaded = have_expected_code && words[0] == expected_code0; + pr_info("gsp-phys-fwsec: FAILDUMP IMEM ns code @0x%x: %08x %08x %08x %08x (word0 want 0x%08x -> %s)\n", + u->desc.imem_phys_base, words[0], words[1], words[2], words[3], + expected_code0, code_loaded ? "loaded" : "NOT loaded"); + + if (fwsec.cmd_offset && + u->data_size >= (size_t)fwsec.cmd_offset + 48) { + gsp_phys_falcon_dmem_read(bar0, GSP_PHYS_GSP_BASE, + fwsec.cmd_offset, words, 12); + pr_info("gsp-phys-fwsec: FAILDUMP interface patch @DMEM 0x%x (48 B): %*ph\n", + fwsec.cmd_offset, 48, (const u8 *)words); + } + + if (data_image) + verdict = "FWSEC data image at DMEM0 -> BL loads completed; the FWSEC ucode never returned"; + else if (desc_intact && code_loaded) + verdict = "BL descriptor intact + FWSEC code loaded -> stalled at the data DMA"; + else if (desc_intact) + verdict = "BL descriptor intact, code not loaded -> stalled at the code DMA (or the BL never entered; check bus_master and TRANSCFG)"; + else + verdict = "DMEM0 matches neither the BL descriptor nor the FWSEC data image"; + pr_info("gsp-phys-fwsec: FAILDUMP verdict: %s\n", verdict); +} + +/* * Prepare and run one FWSEC command on the GSP falcon through the generic * SEC2 boot-loader: patch the interface, reset the falcon, then load the * boot-loader at the top of IMEM with the FWSEC code/data descriptors. @@ -673,6 +839,8 @@ static int gsp_phys_fwsec_exec_with_loader(void __iomem *bar0, GSP_PHYS_FWSEC_HALT_TIMEOUT_MS); pr_info("gsp-phys-fwsec: GSP CPUCTL after FWSEC kick=0x%08x\n", readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL)); + if (ret || gsp_phys_fwsec_verbose_param) + gsp_phys_fwsec_dump_kick_state(bar0, &bl, u, bl_dst); return ret; } @@ -682,7 +850,8 @@ static int gsp_phys_fwsec_exec_cmd(void __iomem *bar0, { int ret; - ret = gsp_phys_fwsec_patch_interface(u, cmd, cmd_buf, cmd_size, NULL); + ret = gsp_phys_fwsec_patch_interface(u, cmd, cmd_buf, cmd_size, + &fwsec.cmd_offset); if (ret) return ret; @@ -991,6 +1160,7 @@ int gsp_phys_fwsec_run(struct gsp_phys_boot *boot, struct pci_dev *pdev, /* From here on the device may carry FWSEC state. */ fwsec.touched = true; + fwsec.pdev = pdev; gsp_phys_read_vbios_desc_init(&frts.read_vbios_desc); frts.frts_region_desc.version = 1; diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c index 9726b84..0936cf4 100644 --- a/kernel/gsp_phys_gpu.c +++ b/kernel/gsp_phys_gpu.c @@ -356,6 +356,7 @@ static void gsp_phys_dump_falcon(void) static int gsp_phys_probe(struct pci_dev *pdev, const struct pci_device_id *id) { u32 boot0, boot1; + u16 pci_cmd = 0; int ret; pr_info("gsp-phys: probe %s\n", pci_name(pdev)); @@ -367,6 +368,20 @@ static int gsp_phys_probe(struct pci_dev *pdev, const struct pci_device_id *id) } phys.enabled = true; + /* + * Read-only observability for the host->GPU DMA path: the previous + * driver's remove clears PCI_COMMAND_MASTER and this probe does not + * set it (no pci_set_master here on purpose yet), so any GPU-initiated + * sysmem DMA (the FWSEC generic-BL code/data fetch) would never + * complete. Log the command register at claim so the state is + * provable from the cycle log. + */ + pci_read_config_word(pdev, PCI_COMMAND, &pci_cmd); + pr_info("gsp-phys: claim PCI_COMMAND=0x%04x [bus_master=%s mem=%s io=%s] dev->is_busmaster=%u\n", + pci_cmd, pci_cmd & PCI_COMMAND_MASTER ? "on" : "OFF", + pci_cmd & PCI_COMMAND_MEMORY ? "on" : "off", + pci_cmd & PCI_COMMAND_IO ? "on" : "off", pdev->is_busmaster); + ret = pci_request_regions(pdev, "open-iov-gsp-phys"); if (ret) { pr_err("gsp-phys: pci_request_regions failed: %d\n", ret); |
