diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 01:06:14 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 01:06:14 -0400 |
| commit | b9264c40ad9f27f31176a9f8fe718196abde1800 (patch) | |
| tree | 9739d73b3b4fc6fcb1dd1a8638e1c23f92df3c4e /kernel/gsp_phys_fwsec.c | |
| parent | 8065d67e9a7e2f22490406755e4df429b89136d0 (diff) | |
| download | open-iov-b9264c40ad9f27f31176a9f8fe718196abde1800.tar.gz open-iov-b9264c40ad9f27f31176a9f8fe718196abde1800.zip | |
gsp-phys: stage 3c FWSEC via VBIOS BIT image plus failure recovery
Diffstat (limited to 'kernel/gsp_phys_fwsec.c')
| -rw-r--r-- | kernel/gsp_phys_fwsec.c | 1038 |
1 files changed, 1038 insertions, 0 deletions
diff --git a/kernel/gsp_phys_fwsec.c b/kernel/gsp_phys_fwsec.c new file mode 100644 index 0000000..93a4208 --- /dev/null +++ b/kernel/gsp_phys_fwsec.c @@ -0,0 +1,1038 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Real-GSP boot stage 3c: FWSEC secure-verification run plus failure recovery. + * + * FWSEC is the VBIOS-provided security ucode that verifies the GSP image and + * carves the WPR2/FRTS region in video memory. It is not part of the GSP-RM + * firmware file; this module reads it from the GPU's own VBIOS (stage 3c's + * VBIOS/BIT parser) and runs it on the GSP falcon through the generic SEC2 + * boot-loader that ships as bindata. After a successful run WPR2 is up, so + * the distribution driver must not be rebound until the teardown (FWSEC-SB + + * booter unload) has put WPR2 back down; release refuses the hand-back + * otherwise. + * + * Disabled by default (gsp_phys_fwsec=N). The run happens only when the + * physical GPU is claimed (gsp_phys_gpu=1 + gsp_phys_hazard_ack=1) and the + * boot layout (gsp_phys_layout=1) succeeded. All device writes are BAR0 + * register/DMEM/IMEM programmings plus resets; the host never touches FB or + * BAR1. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <asm/unaligned.h> + +#include "gsp_phys_fwsec.h" +#include "gsp_phys_vbios.h" +#include "gsp_phys_sec2_bl.h" +#include "gsp_phys_booter_unload_img.h" + +static bool gsp_phys_fwsec_param; +module_param_named(gsp_phys_fwsec, gsp_phys_fwsec_param, bool, 0644); +MODULE_PARM_DESC(gsp_phys_fwsec, + "Run the stage-3c FWSEC/FRTS verification on the claimed GPU after the boot layout succeeds (default 0; device writes)"); + +bool gsp_phys_fwsec_enabled(void) +{ + return gsp_phys_fwsec_param; +} + +/* Falcon engine bases (BAR0 absolute). */ +#define GSP_PHYS_GSP_BASE 0x00110000 +#define GSP_PHYS_GSP_FBIF_BASE 0x00110600 +#define GSP_PHYS_SEC2_BASE 0x00840000 +#define GSP_PHYS_SEC2_FBIF_BASE 0x00840600 + +/* Falcon register offsets, relative to the engine base. */ +#define GSP_PHYS_FALCON_MAILBOX0 0x40 +#define GSP_PHYS_FALCON_MAILBOX1 0x44 +#define GSP_PHYS_FALCON_RM 0x84 +#define GSP_PHYS_FALCON_HWCFG2 0xf4 +#define GSP_PHYS_FALCON_CPUCTL 0x100 +#define GSP_PHYS_FALCON_BOOTVEC 0x104 +#define GSP_PHYS_FALCON_HWCFG 0x108 +#define GSP_PHYS_FALCON_DMACTL 0x10c +#define GSP_PHYS_FALCON_CPUCTL_ALIAS 0x130 +#define GSP_PHYS_FALCON_IMEMC 0x180 +#define GSP_PHYS_FALCON_IMEMD 0x184 +#define GSP_PHYS_FALCON_IMEMT 0x188 +#define GSP_PHYS_FALCON_DMEMC 0x1c0 +#define GSP_PHYS_FALCON_DMEMD 0x1c4 +#define GSP_PHYS_FALCON_ENGINE 0x3c0 + +/* FBIF registers, relative to the falcon's FBIF base. */ +#define GSP_PHYS_FBIF_TRANSCFG(i) ((i) * 4) +#define GSP_PHYS_FBIF_CTL 0x24 + +/* Falcon CPUCTL / ENGINE / DMACTL field bits. */ +#define GSP_PHYS_CPUCTL_STARTCPU BIT(1) +#define GSP_PHYS_CPUCTL_HALTED BIT(4) +#define GSP_PHYS_CPUCTL_ALIAS_EN BIT(6) +#define GSP_PHYS_ENGINE_RESET BIT(0) +#define GSP_PHYS_DMACTL_DMEM_SCRUBBING BIT(1) +#define GSP_PHYS_DMACTL_IMEM_SCRUBBING BIT(2) +#define GSP_PHYS_HWCFG_IMEM_SIZE 0x1ff + +/* FBIF CTL / TRANSCFG fields. */ +#define GSP_PHYS_FBIF_CTL_ALLOW_PHYS_NO_CTX BIT(7) +#define GSP_PHYS_FBIF_TRANSCFG_TARGET_MASK 0x3 +#define GSP_PHYS_FBIF_TRANSCFG_TARGET_COHERENT_SYSMEM 0x1 +#define GSP_PHYS_FBIF_TRANSCFG_MEM_TYPE BIT(2) + +/* IMEM/DMEM access port fields. */ +#define GSP_PHYS_FALCON_ADDR_MASK (0x0000fffc) +#define GSP_PHYS_FALCON_AINCW BIT(24) +#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 +#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). */ +#define GSP_PHYS_WPR2_VAL_SHIFT 4 +#define GSP_PHYS_WPR2_VAL_MASK 0x0fffffffu +#define GSP_PHYS_GFW_BOOT 0x118234 +#define GSP_PHYS_GFW_BOOT_PROGRESS_MASK 0xff +#define GSP_PHYS_GFW_BOOT_COMPLETED 0xff +#define GSP_PHYS_GFW_PLM 0x118128 +#define GSP_PHYS_GFW_PLM_LEVEL0 BIT(0) +#define GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS 0x2174c +#define GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS_FIELD BIT(0) + +/* Timeouts (bounded; never an unbounded poll). */ +#define GSP_PHYS_GFW_BOOT_TIMEOUT_MS 2050 /* 50 ms + 2 s, as scaled by the driver */ +#define GSP_PHYS_FALCON_RESET_TIMEOUT_MS 1000 +#define GSP_PHYS_FWSEC_HALT_TIMEOUT_MS 5000 +#define GSP_PHYS_BOOTER_HALT_TIMEOUT_MS 5000 + +/* FWSEC interface command ids (DMEM mapper init_cmd). */ +#define GSP_PHYS_FWSEC_CMD_FRTS 0x15 +#define GSP_PHYS_FWSEC_CMD_SB 0x19 +/* FRTS region size in 4 KB units (1 MB). */ +#define GSP_PHYS_FRTS_REGION_SIZE_4K 0x100 +/* FRTS region media type: frame buffer. */ +#define GSP_PHYS_FRTS_REGION_MEDIA_FB 2 +/* readVbiosDesc flags used for the FWSEC FRTS/SB commands. */ +#define GSP_PHYS_FWSEC_READ_VBIOS_FLAGS 2 +/* DMEM mapper interface entry id. */ +#define GSP_PHYS_FWSEC_IFACE_ID_DMEMMAPPER 4 +/* Generic SEC2 boot-loader placement in IMEM (start tag). */ +#define GSP_PHYS_SEC2_BL_START_TAG 0xfd +/* + * Only the 0x200-byte code section of the 768-byte generic boot-loader image + * is loaded, as the distribution driver does (its data section and the DMEM + * descriptor are supplied separately). + */ +#define GSP_PHYS_SEC2_BL_CODE_SIZE 0x200 +/* Booter unload image layout (direct mode, from its bindata header). */ +#define GSP_PHYS_BOOTER_UNLOAD_NS_CODE_OFF 0x0000 +#define GSP_PHYS_BOOTER_UNLOAD_NS_CODE_SIZE 0x0100 +#define GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_OFF 0x0100 +#define GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_SIZE 0x4b00 +#define GSP_PHYS_BOOTER_UNLOAD_DATA_OFF 0x4c00 +#define GSP_PHYS_BOOTER_UNLOAD_DATA_SIZE 0x4d00 +#define GSP_PHYS_BOOTER_UNLOAD_MBOX 0xff +#define GSP_PHYS_BOOTER_UNLOAD_IMAGE_SIZE 0x9900 + +/* Packed command structures consumed by the FWSEC ucode (little endian). */ +struct gsp_phys_fwsec_read_vbios_desc { + u32 version; + u32 size; + u64 gfw_image_offset; + u32 gfw_image_size; + u32 flags; +}; + +struct gsp_phys_fwsec_frts_region_desc { + u32 version; + u32 size; + u32 frts_region_offset4k; + u32 frts_region_size; + u32 frts_region_media_type; +}; + +struct gsp_phys_fwsec_frts_cmd { + struct gsp_phys_fwsec_read_vbios_desc read_vbios_desc; + struct gsp_phys_fwsec_frts_region_desc frts_region_desc; +}; + +/* + * DMEM descriptor handed to the generic boot-loader: reserved/signature + * (zero), the DMA context, the code/data DMA bases and the IMEM/DMEM + * offsets. 84 bytes exactly, 32-bit aligned fields only. + */ +struct gsp_phys_fwsec_bl_dmem_desc { + u32 reserved[4]; + u32 signature[4]; + u32 ctx_dma; + u32 code_dma_lo; + u32 code_dma_hi; + u32 non_secure_code_off; + u32 non_secure_code_size; + u32 secure_code_off; + u32 secure_code_size; + u32 code_entry_point; + u32 data_dma_lo; + u32 data_dma_hi; + u32 data_size; + u32 argc; + u32 argv; +}; + +/* FWSEC data-interface records (found inside the ucode DMEM image). */ +struct gsp_phys_fwsec_intf_header { + u8 version; + u8 header_size; + u8 entry_size; + u8 entry_count; +}; + +struct gsp_phys_fwsec_intf_entry { + u32 id; + u32 dmem_offset; +}; + +struct gsp_phys_fwsec_dmem_mapper { + u32 signature; + u16 version; + u16 size; + u32 cmd_in_buffer_offset; + u32 cmd_in_buffer_size; + u32 cmd_out_buffer_offset; + u32 cmd_out_buffer_size; + u32 nvf_img_data_buffer_offset; + u32 nvf_img_data_buffer_size; + u32 printf_buffer_hdr; + u32 ucode_build_time_stamp; + u32 ucode_signature; + u32 init_cmd; + u32 ucode_feature; + u32 ucode_cmd_mask0; + u32 ucode_cmd_mask1; + u32 multi_tgt_tbl; +}; + +struct gsp_phys_fwsec_ucode { + struct gsp_phys_fwsec_desc desc; + void *code_va; + dma_addr_t code_dma; + size_t code_size; + void *data_va; + dma_addr_t data_dma; + size_t data_size; +}; + +static struct gsp_phys_fwsec { + bool touched; /* FWSEC device state may be programmed */ + struct gsp_phys_fwsec_ucode ucode; +} fwsec; + +static u32 gsp_phys_fwsec_wpr2_hi_val(void __iomem *bar0) +{ + return (readl(bar0 + GSP_PHYS_WPR2_ADDR_HI) >> + GSP_PHYS_WPR2_VAL_SHIFT) & GSP_PHYS_WPR2_VAL_MASK; +} + +static u32 gsp_phys_fwsec_wpr2_lo_val(void __iomem *bar0) +{ + return (readl(bar0 + GSP_PHYS_WPR2_ADDR_LO) >> + GSP_PHYS_WPR2_VAL_SHIFT) & GSP_PHYS_WPR2_VAL_MASK; +} + +static bool gsp_phys_fwsec_wpr2_up(void __iomem *bar0) +{ + return gsp_phys_fwsec_wpr2_hi_val(bar0) != 0; +} + +static void gsp_phys_fwsec_dump_state(void __iomem *bar0, const char *what) +{ + pr_info("gsp-phys-fwsec: %s: GSP_CPUCTL=0x%08x SEC2_CPUCTL=0x%08x GSP_BOOTVEC=0x%08x WPR2_LO=0x%08x WPR2_HI=0x%08x scratch0E=0x%08x scratch15=0x%08x GFW_BOOT=0x%08x SEC2_M0=0x%08x SEC2_M1=0x%08x\n", + what, + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL), + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_BOOTVEC), + readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_0E), + readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_15), + readl(bar0 + GSP_PHYS_GFW_BOOT), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0), + readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1)); +} + +/* + * Falcon reset: pulse the engine reset bit with the reference's propagation + * read cycles, wait for the IMEM/DMEM scrubbers to report done (bounded), + * then load FALCON_RM with the raw PMC_BOOT_0 value. + */ +static int gsp_phys_falcon_reset(void __iomem *bar0, u32 base) +{ + unsigned long deadline; + u32 i, value; + int ret = 0; + + value = readl(bar0 + base + GSP_PHYS_FALCON_ENGINE); + writel(value | GSP_PHYS_ENGINE_RESET, + bar0 + base + GSP_PHYS_FALCON_ENGINE); + for (i = 0; i < 10; i++) + readl(bar0 + base + GSP_PHYS_FALCON_ENGINE); + + value = readl(bar0 + base + GSP_PHYS_FALCON_ENGINE); + writel(value & ~GSP_PHYS_ENGINE_RESET, + bar0 + base + GSP_PHYS_FALCON_ENGINE); + for (i = 0; i < 10; i++) + readl(bar0 + base + GSP_PHYS_FALCON_ENGINE); + + deadline = jiffies + msecs_to_jiffies(GSP_PHYS_FALCON_RESET_TIMEOUT_MS); + for (;;) { + value = readl(bar0 + base + GSP_PHYS_FALCON_DMACTL); + if (!(value & (GSP_PHYS_DMACTL_DMEM_SCRUBBING | + GSP_PHYS_DMACTL_IMEM_SCRUBBING))) + break; + if (time_after_eq(jiffies, deadline)) { + pr_err("gsp-phys-fwsec: falcon@0x%x reset scrub not done (DMACTL=0x%08x)\n", + base, value); + ret = -ETIMEDOUT; + break; + } + usleep_range(50, 100); + } + + if (ret) + return ret; + + writel(readl(bar0 + GSP_PHYS_PMC_BOOT_0), + bar0 + base + GSP_PHYS_FALCON_RM); + pr_info("gsp-phys-fwsec: falcon@0x%x reset: DMACTL=0x%08x FALCON_RM=0x%08x (PMC_BOOT_0) ENGINE=0x%08x\n", + base, readl(bar0 + base + GSP_PHYS_FALCON_DMACTL), + readl(bar0 + base + GSP_PHYS_FALCON_RM), + readl(bar0 + base + GSP_PHYS_FALCON_ENGINE)); + return ret; +} + +/* + * Allow physical DMA without a bound context: FBIF CTL bit7 and DMACTL=0, + * so the generic boot-loader can fetch its code/data descriptors. + */ +static void gsp_phys_falcon_disable_ctx_req(void __iomem *bar0, u32 base, + u32 fbif) +{ + u32 value; + + value = readl(bar0 + fbif + GSP_PHYS_FBIF_CTL); + writel(value | GSP_PHYS_FBIF_CTL_ALLOW_PHYS_NO_CTX, + bar0 + fbif + GSP_PHYS_FBIF_CTL); + writel(0, bar0 + base + GSP_PHYS_FALCON_DMACTL); + + pr_info("gsp-phys-fwsec: falcon@0x%x FBIF_CTL=0x%08x (ALLOW_PHYS_NO_CTX) DMACTL=0x%08x\n", + base, readl(bar0 + fbif + GSP_PHYS_FBIF_CTL), + readl(bar0 + base + GSP_PHYS_FALCON_DMACTL)); +} + +static void gsp_phys_falcon_dmem_copy(void __iomem *bar0, u32 base, u32 dest, + const u8 *src, u32 size) +{ + u32 words = size / 4; + u32 i; + + writel((dest & GSP_PHYS_FALCON_ADDR_MASK) | GSP_PHYS_FALCON_AINCW, + bar0 + base + GSP_PHYS_FALCON_DMEMC); + for (i = 0; i < words; i++) + writel(get_unaligned_le32(src + i * 4), + bar0 + base + GSP_PHYS_FALCON_DMEMD); +} + +/* + * IMEM block copy with per-256-byte tags starting at tag>>8, incrementing + * once per block. The tag names the virtual IMEM block the block belongs to. + */ +static void gsp_phys_falcon_imem_copy(void __iomem *bar0, u32 base, u32 dest, + const u8 *src, u32 size, bool secure, + u32 tag) +{ + u32 words = size / 4; + u32 i, value; + + value = (dest & GSP_PHYS_FALCON_ADDR_MASK) | GSP_PHYS_FALCON_AINCW; + if (secure) + value |= GSP_PHYS_FALCON_IMEMC_SECURE; + writel(value, bar0 + base + GSP_PHYS_FALCON_IMEMC); + + tag >>= 8; + for (i = 0; i < words; i++) { + if ((i & 63) == 0) { + writel(tag & 0xffff, + bar0 + base + GSP_PHYS_FALCON_IMEMT); + tag++; + } + writel(get_unaligned_le32(src + i * 4), + 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); + + if (cpuctl & GSP_PHYS_CPUCTL_ALIAS_EN) { + pr_info("gsp-phys-fwsec: falcon@0x%x ALIAS_EN set, starting via CPUCTL_ALIAS\n", + base); + writel(GSP_PHYS_CPUCTL_STARTCPU, + bar0 + base + GSP_PHYS_FALCON_CPUCTL_ALIAS); + } else { + writel(GSP_PHYS_CPUCTL_STARTCPU, + bar0 + base + GSP_PHYS_FALCON_CPUCTL); + } +} + +static int gsp_phys_falcon_wait_halt(void __iomem *bar0, u32 base, + u32 timeout_ms) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms); + u32 cpuctl; + + for (;;) { + cpuctl = readl(bar0 + base + GSP_PHYS_FALCON_CPUCTL); + if (cpuctl & GSP_PHYS_CPUCTL_HALTED) + return 0; + if (time_after_eq(jiffies, deadline)) { + pr_err("gsp-phys-fwsec: falcon@0x%x did not halt (CPUCTL=0x%08x, timeout %ums)\n", + base, cpuctl, timeout_ms); + return -ETIMEDOUT; + } + usleep_range(500, 1000); + } +} + +/* Wait for GFW boot completion: falcon halted and progress 0xff. */ +static int gsp_phys_fwsec_wait_gfw_boot(void __iomem *bar0) +{ + unsigned long deadline = jiffies + + msecs_to_jiffies(GSP_PHYS_GFW_BOOT_TIMEOUT_MS); + u32 cpuctl = 0, progress = 0; + + for (;;) { + cpuctl = readl(bar0 + GSP_PHYS_GSP_BASE + + GSP_PHYS_FALCON_CPUCTL); + progress = readl(bar0 + GSP_PHYS_GFW_BOOT) & + GSP_PHYS_GFW_BOOT_PROGRESS_MASK; + if ((cpuctl & GSP_PHYS_CPUCTL_HALTED) && + progress == GSP_PHYS_GFW_BOOT_COMPLETED) { + pr_info("gsp-phys-fwsec: GFW boot complete: CPUCTL=0x%08x (halted) progress=0xff\n", + cpuctl); + return 0; + } + if (time_after_eq(jiffies, deadline)) + break; + msleep(1); + } + + pr_err("gsp-phys-fwsec: GFW boot not complete (CPUCTL=0x%08x progress=0x%x, timeout %ums); not resetting or kicking the falcon\n", + cpuctl, progress, GSP_PHYS_GFW_BOOT_TIMEOUT_MS); + return -ETIMEDOUT; +} + +static void gsp_phys_read_vbios_desc_init(struct gsp_phys_fwsec_read_vbios_desc *d) +{ + memset(d, 0, sizeof(*d)); + d->version = 1; + d->size = sizeof(*d); + d->flags = GSP_PHYS_FWSEC_READ_VBIOS_FLAGS; +} + +/* + * Patch the FWSEC interface in the staged DMEM copy for one command: walk + * the application-interface header to the DMEM mapper entry, program + * init_cmd and copy the command payload into the mapper's input buffer. + */ +static int gsp_phys_fwsec_patch_interface(struct gsp_phys_fwsec_ucode *u, + u32 cmd, const void *cmd_buf, + u32 cmd_size) +{ + struct gsp_phys_fwsec_intf_header *hdr; + struct gsp_phys_fwsec_dmem_mapper *mapper = NULL; + u8 *data = u->data_va; + u32 mapped_size = u->desc.dmem_load_size; + u32 interface_offset = u->desc.interface_offset; + u32 cur, i; + + if (interface_offset > mapped_size || + sizeof(*hdr) > mapped_size - interface_offset) { + pr_err("gsp-phys-fwsec: interface offset 0x%x outside DMEM image 0x%x\n", + interface_offset, mapped_size); + return -EINVAL; + } + + hdr = (struct gsp_phys_fwsec_intf_header *)(data + interface_offset); + if (hdr->entry_count < 2) { + pr_err("gsp-phys-fwsec: interface at 0x%x has only %u entries\n", + interface_offset, hdr->entry_count); + return -EINVAL; + } + + cur = interface_offset + sizeof(*hdr); + for (i = 0; i < hdr->entry_count; i++) { + struct gsp_phys_fwsec_intf_entry *entry; + + if (cur + sizeof(*entry) > mapped_size) + return -EINVAL; + entry = (struct gsp_phys_fwsec_intf_entry *)(data + cur); + cur += sizeof(*entry); + + if (entry->id == GSP_PHYS_FWSEC_IFACE_ID_DMEMMAPPER) { + if (entry->dmem_offset > mapped_size || + sizeof(*mapper) > mapped_size - entry->dmem_offset) + return -EINVAL; + mapper = (struct gsp_phys_fwsec_dmem_mapper *) + (data + entry->dmem_offset); + } + } + + if (!mapper) { + pr_err("gsp-phys-fwsec: no DMEM mapper entry for cmd 0x%x\n", + cmd); + return -EINVAL; + } + + mapper->init_cmd = cmd; + if (mapper->cmd_in_buffer_size < cmd_size) + pr_warn("gsp-phys-fwsec: interface input buffer 0x%x smaller than cmd 0x%x (0x%x bytes)\n", + mapper->cmd_in_buffer_size, cmd, cmd_size); + if (mapper->cmd_in_buffer_offset > mapped_size || + cmd_size > mapped_size - mapper->cmd_in_buffer_offset) { + pr_err("gsp-phys-fwsec: cmd buffer 0x%x+0x%x outside DMEM image 0x%x\n", + mapper->cmd_in_buffer_offset, cmd_size, mapped_size); + return -EINVAL; + } + + memcpy(data + mapper->cmd_in_buffer_offset, cmd_buf, cmd_size); + pr_info("gsp-phys-fwsec: interface patched: mapper@0x%x init_cmd=0x%x cmd@0x%x (0x%x bytes), out buffer 0x%x/0x%x\n", + (u32)((u8 *)mapper - data), cmd, + mapper->cmd_in_buffer_offset, cmd_size, + mapper->cmd_out_buffer_offset, mapper->cmd_out_buffer_size); + return 0; +} + +/* + * 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. + */ +static int gsp_phys_fwsec_exec_with_loader(void __iomem *bar0, + struct gsp_phys_fwsec_ucode *u) +{ + struct gsp_phys_fwsec_bl_dmem_desc bl; + const struct gsp_phys_fwsec_desc *d = &u->desc; + s64 secure_off64; + u32 secure_off, secure_size, block_count, bl_dst; + u32 transcfg, cpuctl; + int ret; + + secure_off64 = (s64)d->imem_sec_base - d->imem_virt_base + + d->imem_phys_base; + if (secure_off64 < 0) { + pr_err("gsp-phys-fwsec: secure IMEM offset underflows (base=0x%x virt=0x%x phys=0x%x)\n", + d->imem_sec_base, d->imem_virt_base, + d->imem_phys_base); + return -EINVAL; + } + secure_off = (u32)secure_off64; + secure_size = ALIGN(d->imem_sec_size, 256); + + if (secure_off + secure_size > u->code_size || + d->imem_phys_base + d->imem_load_size > u->code_size) { + pr_err("gsp-phys-fwsec: code placement invalid (phys=0x%x load=0x%x secOff=0x%x secSize=0x%x alloc=0x%zx)\n", + d->imem_phys_base, d->imem_load_size, secure_off, + secure_size, u->code_size); + return -EINVAL; + } + + gsp_phys_falcon_disable_ctx_req(bar0, GSP_PHYS_GSP_BASE, + GSP_PHYS_GSP_FBIF_BASE); + + transcfg = readl(bar0 + GSP_PHYS_GSP_FBIF_BASE + + GSP_PHYS_FBIF_TRANSCFG(4)); + transcfg &= ~(GSP_PHYS_FBIF_TRANSCFG_TARGET_MASK | + GSP_PHYS_FBIF_TRANSCFG_MEM_TYPE); + transcfg |= GSP_PHYS_FBIF_TRANSCFG_TARGET_COHERENT_SYSMEM | + GSP_PHYS_FBIF_TRANSCFG_MEM_TYPE; + writel(transcfg, bar0 + GSP_PHYS_GSP_FBIF_BASE + + GSP_PHYS_FBIF_TRANSCFG(4)); + pr_info("gsp-phys-fwsec: TRANSCFG[4]@0x%x = 0x%08x (coherent sysmem, physical)\n", + GSP_PHYS_GSP_FBIF_BASE + GSP_PHYS_FBIF_TRANSCFG(4), + readl(bar0 + GSP_PHYS_GSP_FBIF_BASE + + GSP_PHYS_FBIF_TRANSCFG(4))); + + memset(&bl, 0, sizeof(bl)); + bl.ctx_dma = 4; /* physical system memory DMA context */ + bl.code_dma_lo = lower_32_bits(u->code_dma); + bl.code_dma_hi = upper_32_bits(u->code_dma); + bl.non_secure_code_off = d->imem_phys_base; + bl.non_secure_code_size = d->imem_load_size - d->imem_sec_size; + bl.secure_code_off = secure_off; + bl.secure_code_size = secure_size; + bl.code_entry_point = 0; + bl.data_dma_lo = lower_32_bits(u->data_dma); + bl.data_dma_hi = upper_32_bits(u->data_dma); + bl.data_size = d->dmem_load_size; + + gsp_phys_falcon_dmem_copy(bar0, GSP_PHYS_GSP_BASE, 0, (const u8 *)&bl, + sizeof(bl)); + pr_info("gsp-phys-fwsec: BL descriptor at DMEM0: ctxDma=%u codeDma=0x%llx nsOff/Size=0x%x/0x%x secOff/Size=0x%x/0x%x dataDma=0x%llx dataSize=0x%x\n", + bl.ctx_dma, (u64)u->code_dma, bl.non_secure_code_off, + bl.non_secure_code_size, bl.secure_code_off, + bl.secure_code_size, (u64)u->data_dma, bl.data_size); + + block_count = readl(bar0 + GSP_PHYS_GSP_BASE + + GSP_PHYS_FALCON_HWCFG) & GSP_PHYS_HWCFG_IMEM_SIZE; + if (block_count * 256 < GSP_PHYS_SEC2_BL_CODE_SIZE) { + pr_err("gsp-phys-fwsec: GSP IMEM too small for the boot-loader (%u blocks)\n", + block_count); + return -EINVAL; + } + bl_dst = (block_count - GSP_PHYS_SEC2_BL_CODE_SIZE / 256) << 8; + gsp_phys_falcon_imem_copy(bar0, GSP_PHYS_GSP_BASE, bl_dst, + gsp_phys_sec2_bl_image, + GSP_PHYS_SEC2_BL_CODE_SIZE, false, + GSP_PHYS_SEC2_BL_START_TAG << 8); + pr_info("gsp-phys-fwsec: generic SEC2 BL code 0x%x bytes at IMEM 0x%x (IMEM_SIZE=%u blocks, 256-byte blocks, tag 0x%x)\n", + GSP_PHYS_SEC2_BL_CODE_SIZE, bl_dst, block_count, + GSP_PHYS_SEC2_BL_START_TAG); + + writel(GSP_PHYS_SEC2_BL_START_TAG << 8, + bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_BOOTVEC); + cpuctl = readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL); + pr_info("gsp-phys-fwsec: BOOTVEC=0x%08x CPUCTL before start=0x%08x\n", + readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_BOOTVEC), + cpuctl); + + gsp_phys_falcon_start_cpu(bar0, GSP_PHYS_GSP_BASE); + ret = gsp_phys_falcon_wait_halt(bar0, GSP_PHYS_GSP_BASE, + 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)); + return ret; +} + +static int gsp_phys_fwsec_exec_cmd(void __iomem *bar0, + struct gsp_phys_fwsec_ucode *u, u32 cmd, + const void *cmd_buf, u32 cmd_size) +{ + int ret; + + ret = gsp_phys_fwsec_patch_interface(u, cmd, cmd_buf, cmd_size); + if (ret) + return ret; + + ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); + if (ret) + return ret; + + return gsp_phys_fwsec_exec_with_loader(bar0, u); +} + +static void gsp_phys_fwsec_ucode_free(struct pci_dev *pdev); + +/* + * Stage the FWSEC code/data images into device-visible coherent memory. The + * code image is copied at its IMEM physical base, the data at its DMEM + * physical base, both zero-padded to a 256-byte allocation granularity. + */ +static int gsp_phys_fwsec_ucode_stage(struct pci_dev *pdev, + const struct gsp_phys_vbios *vbios, + const struct gsp_phys_fwsec_desc *desc) +{ + struct gsp_phys_fwsec_ucode *u = &fwsec.ucode; + u32 image_start = desc->desc_offset + desc->desc_size; + u32 code_alloc, data_alloc, secure_off, secure_size; + s64 secure_off64; + + code_alloc = ALIGN(desc->imem_load_size, 256); + data_alloc = ALIGN(desc->dmem_load_size, 256); + secure_off64 = (s64)desc->imem_sec_base - desc->imem_virt_base + + desc->imem_phys_base; + if (secure_off64 < 0) + return -EINVAL; + secure_off = (u32)secure_off64; + secure_size = ALIGN(desc->imem_sec_size, 256); + + if (desc->imem_phys_base >= code_alloc || + desc->imem_load_size > code_alloc - desc->imem_phys_base || + secure_off >= code_alloc || + secure_size > code_alloc - secure_off) { + pr_err("gsp-phys-fwsec: FWSEC IMEM does not fit the code allocation (phys=0x%x load=0x%x secOff=0x%x secSize=0x%x alloc=0x%x)\n", + desc->imem_phys_base, desc->imem_load_size, secure_off, + secure_size, code_alloc); + return -EINVAL; + } + if (desc->dmem_phys_base >= data_alloc || + desc->dmem_load_size > data_alloc - desc->dmem_phys_base) { + pr_err("gsp-phys-fwsec: FWSEC DMEM does not fit the data allocation (phys=0x%x load=0x%x alloc=0x%x)\n", + desc->dmem_phys_base, desc->dmem_load_size, data_alloc); + return -EINVAL; + } + if (desc->dmem_phys_base != 0) + pr_warn("gsp-phys-fwsec: FWSEC DMEM physical base is 0x%x (not 0); interface patching uses the image-buffer offsets as the distribution driver does\n", + desc->dmem_phys_base); + + if (image_start + desc->imem_load_size > vbios->size || + image_start + desc->dmem_offset + desc->dmem_load_size > + vbios->size) { + pr_err("gsp-phys-fwsec: FWSEC images outside the VBIOS copy\n"); + return -EINVAL; + } + + u->desc = *desc; + u->code_size = code_alloc; + u->data_size = data_alloc; + u->code_va = dma_alloc_coherent(&pdev->dev, code_alloc, &u->code_dma, + GFP_KERNEL); + if (!u->code_va) { + pr_err("gsp-phys-fwsec: FWSEC code allocation (0x%x) failed\n", + code_alloc); + return -ENOMEM; + } + u->data_va = dma_alloc_coherent(&pdev->dev, data_alloc, &u->data_dma, + GFP_KERNEL); + if (!u->data_va) { + pr_err("gsp-phys-fwsec: FWSEC data allocation (0x%x) failed\n", + data_alloc); + gsp_phys_fwsec_ucode_free(pdev); + return -ENOMEM; + } + + memset(u->code_va, 0, code_alloc); + memcpy(u->code_va + desc->imem_phys_base, + vbios->data + image_start, desc->imem_load_size); + memset(u->data_va, 0, data_alloc); + memcpy(u->data_va + desc->dmem_phys_base, + vbios->data + image_start + desc->dmem_offset, + desc->dmem_load_size); + + pr_info("gsp-phys-fwsec: staged FWSEC: code alloc 0x%x dma=0x%llx (imemLoad=0x%x secOff/Size=0x%x/0x%x), data alloc 0x%x dma=0x%llx (dmemLoad=0x%x)\n", + code_alloc, (u64)u->code_dma, desc->imem_load_size, + secure_off, secure_size, data_alloc, (u64)u->data_dma, + desc->dmem_load_size); + return 0; +} + +static void gsp_phys_fwsec_ucode_free(struct pci_dev *pdev) +{ + struct gsp_phys_fwsec_ucode *u = &fwsec.ucode; + + if (u->code_va) + dma_free_coherent(&pdev->dev, u->code_size, u->code_va, + u->code_dma); + if (u->data_va) + dma_free_coherent(&pdev->dev, u->data_size, u->data_va, + u->data_dma); + memset(u, 0, sizeof(*u)); +} + +/* + * Booter unload on SEC2: reset, load the unload image in direct mode and run + * it with mailbox0/1 = 0xff. Success leaves WPR2 down. + */ +static int gsp_phys_booter_unload(void __iomem *bar0) +{ + const u8 *img = gsp_phys_booter_unload_image; + u32 m0, m1; + int ret; + + pr_info("gsp-phys-fwsec: booter unload: resetting SEC2 falcon\n"); + ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_SEC2_BASE); + if (ret) + return ret; + + gsp_phys_falcon_disable_ctx_req(bar0, GSP_PHYS_SEC2_BASE, + GSP_PHYS_SEC2_FBIF_BASE); + gsp_phys_falcon_imem_copy(bar0, GSP_PHYS_SEC2_BASE, + GSP_PHYS_BOOTER_UNLOAD_NS_CODE_OFF, + img + GSP_PHYS_BOOTER_UNLOAD_NS_CODE_OFF, + GSP_PHYS_BOOTER_UNLOAD_NS_CODE_SIZE, false, + GSP_PHYS_BOOTER_UNLOAD_NS_CODE_OFF); + gsp_phys_falcon_imem_copy(bar0, GSP_PHYS_SEC2_BASE, + GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_OFF, + img + GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_OFF, + GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_SIZE, true, + GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_OFF); + gsp_phys_falcon_dmem_copy(bar0, GSP_PHYS_SEC2_BASE, 0, + img + GSP_PHYS_BOOTER_UNLOAD_DATA_OFF, + GSP_PHYS_BOOTER_UNLOAD_DATA_SIZE); + writel(0, bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_BOOTVEC); + writel(GSP_PHYS_BOOTER_UNLOAD_MBOX, + bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0); + writel(GSP_PHYS_BOOTER_UNLOAD_MBOX, + bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1); + + pr_info("gsp-phys-fwsec: booter unload: image 0x%zx bytes (ns 0x%x, sec 0x%x, data 0x%x), MAILBOX0/1=0xff, BOOTVEC=0\n", + sizeof(gsp_phys_booter_unload_image), + GSP_PHYS_BOOTER_UNLOAD_NS_CODE_SIZE, + GSP_PHYS_BOOTER_UNLOAD_SEC_CODE_SIZE, + GSP_PHYS_BOOTER_UNLOAD_DATA_SIZE); + + gsp_phys_falcon_start_cpu(bar0, GSP_PHYS_SEC2_BASE); + ret = gsp_phys_falcon_wait_halt(bar0, GSP_PHYS_SEC2_BASE, + GSP_PHYS_BOOTER_HALT_TIMEOUT_MS); + if (ret) + return ret; + + m0 = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0); + m1 = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1); + pr_info("gsp-phys-fwsec: booter unload done: SEC2 M0=0x%08x M1=0x%08x WPR2_LO=0x%08x WPR2_HI=0x%08x\n", + m0, m1, readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI)); + + if (m0 != 0) { + pr_err("gsp-phys-fwsec: booter unload reported error 0x%x\n", + m0); + return -EIO; + } + return 0; +} + +/* + * Teardown: FWSEC-SB (puts the pre-OS applications back) followed by the + * booter unload (removes WPR2). The final criterion is WPR2 down, not the + * individual ucode statuses. + */ +static int gsp_phys_fwsec_teardown(void __iomem *bar0) +{ + struct gsp_phys_fwsec_read_vbios_desc sb_cmd; + u32 scratch, sb_err, plm, progress; + int ret; + + if (!gsp_phys_fwsec_wpr2_up(bar0)) { + pr_info("gsp-phys-fwsec: teardown: WPR2 already down; resetting the GSP falcon only\n"); + gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); + return 0; + } + + if (fwsec.ucode.code_va) { + gsp_phys_read_vbios_desc_init(&sb_cmd); + pr_info("gsp-phys-fwsec: FWSEC-SB cmd 0x%x (readVbiosDesc version=%u size=%u flags=%u)\n", + GSP_PHYS_FWSEC_CMD_SB, sb_cmd.version, sb_cmd.size, + sb_cmd.flags); + ret = gsp_phys_fwsec_exec_cmd(bar0, &fwsec.ucode, + GSP_PHYS_FWSEC_CMD_SB, &sb_cmd, + sizeof(sb_cmd)); + if (ret) { + pr_err("gsp-phys-fwsec: FWSEC-SB execution failed: %d; continuing to booter unload\n", + ret); + } else { + plm = readl(bar0 + GSP_PHYS_GFW_PLM); + progress = readl(bar0 + GSP_PHYS_GFW_BOOT) & + GSP_PHYS_GFW_BOOT_PROGRESS_MASK; + scratch = readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_15); + sb_err = scratch & 0xffff; + pr_info("gsp-phys-fwsec: FWSEC-SB done: SB_ERR=0x%x PLM_LEVEL0=%u GFW_progress=0x%x\n", + sb_err, !!(plm & GSP_PHYS_GFW_PLM_LEVEL0), + progress); + if (sb_err != 0) + pr_warn("gsp-phys-fwsec: FWSEC-SB reported error 0x%x\n", + sb_err); + } + } else { + pr_warn("gsp-phys-fwsec: no staged FWSEC ucode for FWSEC-SB; running booter unload only\n"); + } + + ret = gsp_phys_booter_unload(bar0); + if (ret) + pr_err("gsp-phys-fwsec: booter unload failed: %d\n", ret); + + if (gsp_phys_fwsec_wpr2_up(bar0)) { + pr_err("gsp-phys-fwsec: teardown did not clear WPR2 (HI=0x%08x)\n", + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI)); + return -EIO; + } + + pr_info("gsp-phys-fwsec: teardown complete: WPR2 down\n"); + return 0; +} + +int gsp_phys_fwsec_run(struct gsp_phys_boot *boot, struct pci_dev *pdev, + void __iomem *bar0) +{ + struct gsp_phys_vbios vbios = { }; + struct gsp_phys_fwsec_desc desc; + struct gsp_phys_fwsec_frts_cmd frts; + u32 wpr2_hi, wpr2_lo, scratch, frts_err, hwcfg2, fuse; + bool use_debug_app; + int ret, recover_ret; + + if (!boot || !pdev || !bar0) + return -EINVAL; + if (!boot->initialized) { + pr_err("gsp-phys-fwsec: boot layout not initialised; refusing stage 3c\n"); + return -EINVAL; + } + + pr_info("gsp-phys-fwsec: stage 3c FWSEC/FRTS on %s: frtsOffset=0x%llx frtsSize=0x%llx\n", + pci_name(pdev), boot->frts_offset, boot->frts_size); + + /* Precondition 1: WPR2 must be down. */ + wpr2_hi = gsp_phys_fwsec_wpr2_hi_val(bar0); + wpr2_lo = gsp_phys_fwsec_wpr2_lo_val(bar0); + pr_info("gsp-phys-fwsec: precondition WPR2: LO=0x%08x (VAL=0x%x) HI=0x%08x (VAL=0x%x), must be down\n", + readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), wpr2_lo, + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), wpr2_hi); + if (wpr2_hi != 0) { + pr_err("gsp-phys-fwsec: unexpected WPR2 already up; the GPU is in a bad state, refusing FWSEC\n"); + return -EBUSY; + } + + /* Precondition 2: GFW boot complete and GSP falcon halted. */ + ret = gsp_phys_fwsec_wait_gfw_boot(bar0); + if (ret) + return ret; + + /* Precondition 3: the RISC-V core exists (HWCFG2.RISCV). */ + hwcfg2 = readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_HWCFG2); + if (!(hwcfg2 & BIT(10))) { + pr_err("gsp-phys-fwsec: RISC-V core not enabled (HWCFG2=0x%08x); refusing FWSEC\n", + hwcfg2); + return -ENODEV; + } + pr_info("gsp-phys-fwsec: precondition RISC-V enabled (HWCFG2=0x%08x)\n", + hwcfg2); + + /* Read and parse the VBIOS for the FWSEC image. */ + ret = gsp_phys_vbios_read(&vbios, pdev, bar0); + if (ret) + return ret; + + fuse = readl(bar0 + GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS); + use_debug_app = !(fuse & GSP_PHYS_FUSE_SECURE_GSP_DEBUG_DIS_FIELD); + pr_info("gsp-phys-fwsec: fuse SECURE_GSP_DEBUG_DIS=0x%08x -> %s FWSEC image (appId 0x%x)\n", + fuse, use_debug_app ? "debug" : "production", + use_debug_app ? GSP_PHYS_FWSEC_APPID_DEBUG : + GSP_PHYS_FWSEC_APPID_PROD); + + ret = gsp_phys_vbios_parse_fwsec(&vbios, use_debug_app, &desc); + if (ret) { + pr_err("gsp-phys-fwsec: FWSEC parse from VBIOS failed: %d\n", ret); + gsp_phys_vbios_free(&vbios); + return ret; + } + + ret = gsp_phys_fwsec_ucode_stage(pdev, &vbios, &desc); + gsp_phys_vbios_free(&vbios); + if (ret) + return ret; + + /* From here on the device may carry FWSEC state. */ + fwsec.touched = true; + + gsp_phys_read_vbios_desc_init(&frts.read_vbios_desc); + frts.frts_region_desc.version = 1; + frts.frts_region_desc.size = sizeof(frts.frts_region_desc); + frts.frts_region_desc.frts_region_offset4k = + (u32)(boot->frts_offset >> 12); + frts.frts_region_desc.frts_region_size = GSP_PHYS_FRTS_REGION_SIZE_4K; + frts.frts_region_desc.frts_region_media_type = + GSP_PHYS_FRTS_REGION_MEDIA_FB; + pr_info("gsp-phys-fwsec: FRTS cmd: readVbiosDesc{version=%u size=%u flags=%u}, region{offset4K=0x%x size=0x%x(4K) media=FB}\n", + frts.read_vbios_desc.version, frts.read_vbios_desc.size, + frts.read_vbios_desc.flags, + frts.frts_region_desc.frts_region_offset4k, + frts.frts_region_desc.frts_region_size); + + ret = gsp_phys_fwsec_exec_cmd(bar0, &fwsec.ucode, + GSP_PHYS_FWSEC_CMD_FRTS, &frts, + sizeof(frts)); + if (ret) { + pr_err("gsp-phys-fwsec: FWSEC/FRTS execution failed: %d\n", ret); + goto recover; + } + + /* Success checks: FRTS error clear, WPR2 up, WPR2 at frtsOffset. */ + scratch = readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_0E); + frts_err = scratch >> 16; + wpr2_hi = gsp_phys_fwsec_wpr2_hi_val(bar0); + wpr2_lo = gsp_phys_fwsec_wpr2_lo_val(bar0); + gsp_phys_fwsec_dump_state(bar0, "after FWSEC/FRTS"); + pr_info("gsp-phys-fwsec: checks: FRTS_ERR_CODE=0x%x (want 0), WPR2_HI VAL=0x%x (want nonzero), WPR2_LO VAL=0x%x (want 0x%x)\n", + frts_err, wpr2_hi, wpr2_lo, (u32)(boot->frts_offset >> 12)); + + if (frts_err != 0 || wpr2_hi == 0 || + wpr2_lo != (u32)(boot->frts_offset >> 12)) { + pr_err("gsp-phys-fwsec: stage 3c FWSEC/FRTS checks FAILED\n"); + ret = -EIO; + goto recover; + } + + pr_info("gsp-phys-fwsec: stage 3c complete: WPR2 up at frtsOffset=0x%llx; device stays WPR2-owned until release tears it down\n", + boot->frts_offset); + return 0; + +recover: + pr_err("gsp-phys-fwsec: running WPR2 teardown (FWSEC-SB + booter unload)\n"); + recover_ret = gsp_phys_fwsec_teardown(bar0); + if (recover_ret) { + pr_err("gsp-phys-fwsec: teardown FAILED: WPR2 is still up; release will refuse the hand-back (reboot may be required; do not retry in this boot)\n"); + } else { + pr_info("gsp-phys-fwsec: teardown complete after failure: WPR2 down; the GPU can be handed back at release\n"); + } + gsp_phys_fwsec_dump_state(bar0, "after teardown"); + return ret; +} + +int gsp_phys_fwsec_release(struct gsp_phys_boot *boot, struct pci_dev *pdev, + void __iomem *bar0) +{ + int ret; + + if (!fwsec.touched) + return 0; + if (!pdev || !bar0) + return -EIO; + + if (!gsp_phys_fwsec_wpr2_up(bar0)) { + pr_info("gsp-phys-fwsec: release: WPR2 already down; freeing FWSEC staging and handing back\n"); + gsp_phys_fwsec_ucode_free(pdev); + fwsec.touched = false; + return 0; + } + + pr_info("gsp-phys-fwsec: release: WPR2 is up; tearing down before hand-back\n"); + ret = gsp_phys_fwsec_teardown(bar0); + gsp_phys_fwsec_ucode_free(pdev); + + if (ret || gsp_phys_fwsec_wpr2_up(bar0)) { + pr_err("gsp-phys-fwsec: release: WPR2 still up after teardown (HI=0x%08x); refusing the hand-back to the distribution driver\n", + readl(bar0 + GSP_PHYS_WPR2_ADDR_HI)); + return -EIO; + } + + pr_info("gsp-phys-fwsec: release: WPR2 down after teardown; hand-back is safe\n"); + fwsec.touched = false; + return 0; +} + +/* + * ABI shape checks for the FWSEC structures and the embedded bindata. These + * must match the generations the boot path was derived for; a mismatch means + * the structures or the extracted images changed, which must not be ignored. + */ +static void __maybe_unused gsp_phys_fwsec_abi_check(void) +{ + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_read_vbios_desc) != 24); + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_frts_region_desc) != 20); + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_frts_cmd) != 44); + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_bl_dmem_desc) != 84); + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_intf_header) != 4); + BUILD_BUG_ON(sizeof(struct gsp_phys_fwsec_intf_entry) != 8); + BUILD_BUG_ON(sizeof(gsp_phys_sec2_bl_image) != 0x300); + BUILD_BUG_ON(GSP_PHYS_SEC2_BL_CODE_SIZE != 0x200); + BUILD_BUG_ON(sizeof(gsp_phys_booter_unload_image) != + GSP_PHYS_BOOTER_UNLOAD_IMAGE_SIZE); +} |
