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 | 9a038702b1fc6ab867ebd3d6652ac3c7d04d6fff (patch) | |
| tree | 8c950a14e7c8f0753569fc7d92bbb4f01e526b54 /kernel/gsp_phys_vbios.c | |
| parent | bef0d867a0d666b98123d4e19edd3084ea95c3c0 (diff) | |
| download | open-iov-9a038702b1fc6ab867ebd3d6652ac3c7d04d6fff.tar.gz open-iov-9a038702b1fc6ab867ebd3d6652ac3c7d04d6fff.zip | |
gsp-phys: stage 3c FWSEC via VBIOS BIT image plus failure recovery
Diffstat (limited to 'kernel/gsp_phys_vbios.c')
| -rw-r--r-- | kernel/gsp_phys_vbios.c | 771 |
1 files changed, 771 insertions, 0 deletions
diff --git a/kernel/gsp_phys_vbios.c b/kernel/gsp_phys_vbios.c new file mode 100644 index 0000000..4c35636 --- /dev/null +++ b/kernel/gsp_phys_vbios.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Real-GSP boot stage 3c support: VBIOS access and FWSEC image parsing. + * + * Port of the distribution driver's Turing ROM extraction path, reduced to + * what the FWSEC/FRTS run needs: raw 32-bit reads through the PROM window, + * the legacy PCI ROM header / expansion-ROM walk, the BIT header scan and + * the falcon-ucode table lookup for the FWSEC application. No device writes. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/io.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#include "gsp_phys_vbios.h" + +/* Legacy PCI ROM / PCI data structure constants. */ +#define GSP_PHYS_ROM_SIG_STD 0xaa55 +#define GSP_PHYS_ROM_SIG_NV 0x4e56 +#define GSP_PHYS_ROM_SIG_NV2 0xbb77 +#define GSP_PHYS_PCIR_SIG 0x52494350 +#define GSP_PHYS_PCIR_SIG_NV 0x5344504e +#define GSP_PHYS_PCIR_SIG_NV2 0x53494752 +#define GSP_PHYS_PCIR_LAST_IMAGE BIT(7) +#define GSP_PHYS_ROM_BLOCK_SIZE 512 +#define GSP_PHYS_PCIR_STRUCT_LEN 0x0a +#define GSP_PHYS_PCIR_IMAGE_LEN 0x10 +#define GSP_PHYS_PCIR_CODE_TYPE 0x14 +#define GSP_PHYS_PCIR_LAST_IMAGE_OFF 0x15 +#define GSP_PHYS_PCIR_PTR 0x18 +#define GSP_PHYS_NPDE_SIG 0x4544504e +#define GSP_PHYS_NPDE_REV_10 0x0100 +#define GSP_PHYS_NPDE_REV_11 0x0101 +#define GSP_PHYS_NPDE_LEN 0x06 +#define GSP_PHYS_NPDE_REV 0x04 +#define GSP_PHYS_NPDE_SUBIMAGE_LEN 0x08 +#define GSP_PHYS_NPDE_LAST_IMAGE 0x0a +#define GSP_PHYS_NPDE_CODE_TYPE_VBIOS_BASE 0x00 +#define GSP_PHYS_NPDE_CODE_TYPE_VBIOS_EXT 0xe0 + +/* IFR (image format record) fixed fields at the start of the PROM image. */ +#define GSP_PHYS_IFR_FIXED1_VERSIONSW 0x0000ff00 +#define GSP_PHYS_IFR_FIXED1_VERSIONSW_SHIFT 8 +#define GSP_PHYS_IFR_FIXED1_FIXED_DATA_SIZE 0x7fff0000 +#define GSP_PHYS_IFR_FIXED1_FIXED_DATA_SIZE_SHIFT 16 +#define GSP_PHYS_IFR_FIXED2_TOTAL_DATA_SIZE 0x000fffff +#define GSP_PHYS_IFR_SIGNATURE 0x4947564e +#define GSP_PHYS_ROM_DIRECTORY_IDENTIFIER 0x44524652 +#define GSP_PHYS_ROM_DIRECTORY_SKIP 4096 + +/* BIOS Information Table (BIT) header and tokens. */ +#define GSP_PHYS_BIT_ID 0xb8ff +#define GSP_PHYS_BIT_SIGNATURE 0x00544942 +#define GSP_PHYS_BIT_HEADER_SIZE_OFF 8 +#define GSP_PHYS_BIT_TOKEN_SIZE_OFF 9 +#define GSP_PHYS_BIT_TOKEN_ENTRIES_OFF 10 +#define GSP_PHYS_BIT_TOKEN_BIOSDATA 0x42 +#define GSP_PHYS_BIT_TOKEN_FALCON_DATA 0x70 +#define GSP_PHYS_BIT_TOKEN_SIZE_6 6 +#define GSP_PHYS_BIT_TOKEN_SIZE_8 8 +#define GSP_PHYS_BIT_BIOSDATA_SIZE_MIN 5 +#define GSP_PHYS_BIT_FALCON_DATA_V2_SIZE_MIN 4 +#define GSP_PHYS_FALCON_UCODE_TABLE_VERSION 1 +#define GSP_PHYS_FALCON_UCODE_TABLE_HDR_SIZE 6 +#define GSP_PHYS_FALCON_UCODE_ENTRY_SIZE 6 +#define GSP_PHYS_FALCON_UCODE_DESC_V2_SIZE 60 +#define GSP_PHYS_FALCON_UCODE_DESC_V3_SIZE 44 + +/* + * Read one aligned 32-bit word from the PROM window. The window exposes only + * whole 32-bit registers; smaller/unaligned reads are assembled from them by + * gsp_phys_prom_read(), exactly like the distribution driver does. + */ +static bool gsp_phys_prom_read32(void __iomem *bar0, u32 offset, u32 *value) +{ + if (offset > GSP_PHYS_VBIOS_MAX_SIZE - sizeof(u32)) + return false; + *value = readl(bar0 + GSP_PHYS_VBIOS_PROM_WINDOW + offset); + return true; +} + +/* + * Read 1-4 bytes at an arbitrary PROM offset via 32-bit window reads. An + * unaligned access is assembled from one or two aligned words; the returned + * value is little endian (first byte at the lowest offset is the LSB). + */ +static bool gsp_phys_prom_read(void __iomem *bar0, u32 offset, u32 size, + u32 *value) +{ + union { + u32 word[2]; + u8 byte[8]; + } buf; + u32 byte_index; + bool second; + + if (size == 0 || size > sizeof(u32)) + return false; + if (offset > GSP_PHYS_VBIOS_MAX_SIZE - size) + return false; + + byte_index = offset & (sizeof(u32) - 1); + offset -= byte_index; + byte_index += size; + second = byte_index > sizeof(u32); + + if (!gsp_phys_prom_read32(bar0, offset, &buf.word[0])) + return false; + if (second && !gsp_phys_prom_read32(bar0, offset + sizeof(u32), + &buf.word[1])) + return false; + + *value = 0; + while (size--) + *value = (*value << 8) + buf.byte[--byte_index]; + return true; +} + +static bool gsp_phys_prom_read8(void __iomem *bar0, u32 offset, u32 *value) +{ + return gsp_phys_prom_read(bar0, offset, 1, value); +} + +static bool gsp_phys_prom_read16(void __iomem *bar0, u32 offset, u32 *value) +{ + return gsp_phys_prom_read(bar0, offset, 2, value); +} + +/* Read 1-4 bytes out of the already-copied image, little endian. */ +static bool gsp_phys_img_read(const struct gsp_phys_vbios *img, u32 offset, + u32 size, u32 *value) +{ + u32 v = 0, i; + + if (size == 0 || size > sizeof(u32)) + return false; + if (offset > img->size || size > img->size - offset) + return false; + + for (i = 0; i < size; i++) + v |= (u32)img->data[offset + i] << (8 * i); + *value = v; + return true; +} + +/* + * Locate the legacy PCI ROM image: either the image starts at PROM offset 0 + * (standard/nv signature) or the IFR record points at the image start. IFR + * versions 1/2 use the extended data-size field for the image offset; version + * 3 stores the flash-status offset at the total-data-size offset and the ROM + * directory 4096 bytes after that. + */ +static int gsp_phys_vbios_find_pci_header(void __iomem *bar0, u32 *pci_offset) +{ + u32 fixed0, fixed1, fixed2; + u32 image_offset = 0; + u32 data_size, flash_status, directory_sig, version; + + if (!gsp_phys_prom_read32(bar0, 0, &fixed0) || + !gsp_phys_prom_read32(bar0, 4, &fixed1) || + !gsp_phys_prom_read32(bar0, 8, &fixed2)) + return -EIO; + + if (fixed0 != GSP_PHYS_IFR_SIGNATURE) { + /* No IFR record: the image starts at offset 0. */ + *pci_offset = 0; + return 0; + } + + version = (fixed1 & GSP_PHYS_IFR_FIXED1_VERSIONSW) >> + GSP_PHYS_IFR_FIXED1_VERSIONSW_SHIFT; + + switch (version) { + case 1: + case 2: + data_size = (fixed1 & GSP_PHYS_IFR_FIXED1_FIXED_DATA_SIZE) >> + GSP_PHYS_IFR_FIXED1_FIXED_DATA_SIZE_SHIFT; + if (!gsp_phys_prom_read32(bar0, data_size + 4, &image_offset)) + return -EIO; + break; + case 3: + data_size = fixed2 & GSP_PHYS_IFR_FIXED2_TOTAL_DATA_SIZE; + if (!gsp_phys_prom_read32(bar0, data_size, &flash_status)) + return -EIO; + if (flash_status > + GSP_PHYS_VBIOS_MAX_SIZE - GSP_PHYS_ROM_DIRECTORY_SKIP - 8) + return -EINVAL; + if (!gsp_phys_prom_read32(bar0, + flash_status + + GSP_PHYS_ROM_DIRECTORY_SKIP, + &directory_sig)) + return -EIO; + if (directory_sig != GSP_PHYS_ROM_DIRECTORY_IDENTIFIER) { + pr_err("gsp-phys-vbios: ROM directory not found (0x%08x)\n", + directory_sig); + return -EINVAL; + } + if (!gsp_phys_prom_read32(bar0, + flash_status + + GSP_PHYS_ROM_DIRECTORY_SKIP + 8, + &image_offset)) + return -EIO; + break; + default: + pr_err("gsp-phys-vbios: unsupported IFR version %u\n", version); + return -EINVAL; + } + + if (image_offset > GSP_PHYS_VBIOS_MAX_SIZE - sizeof(u32)) { + pr_err("gsp-phys-vbios: IFR image offset 0x%x out of range\n", + image_offset); + return -EINVAL; + } + + *pci_offset = image_offset; + return 0; +} + +static bool gsp_phys_pci_data_sig_valid(u32 sig) +{ + return sig == GSP_PHYS_PCIR_SIG || sig == GSP_PHYS_PCIR_SIG_NV || + sig == GSP_PHYS_PCIR_SIG_NV2; +} + +/* + * Walk the PCI expansion ROM chain: every block starts with a ROM header + * whose 0x18 pointer gives the PCI data structure; the optional NVIDIA PCI + * data extension can override the sub-image length and the last-image flag. + * Returns the total image size relative to pci_offset and the expansion ROM + * base (the region that carries the BIT and the falcon ucode table). + */ +static int gsp_phys_vbios_locate_expansion_roms(void __iomem *bar0, + u32 pci_offset, + u32 *bios_size, + u32 *expansion_rom_offset) +{ + u32 curr_block = pci_offset; + u32 ext_rom_offset = 0, base_rom_size = 0; + u32 block_offset = 0, block_size = 0; + u32 iterations = GSP_PHYS_VBIOS_MAX_SIZE / GSP_PHYS_ROM_BLOCK_SIZE + 8; + u32 iter; + + for (iter = 0; iter < iterations; iter++) { + u32 pcir_ptr, base, sig, img_len, sub_img_len, code_type; + u32 struct_len, ext_at, ext_sig, ext_rev, ext_len; + bool last_image; + + if (!gsp_phys_prom_read16(bar0, curr_block + GSP_PHYS_PCIR_PTR, + &pcir_ptr)) + return -EIO; + if ((u64)curr_block + pcir_ptr > + GSP_PHYS_VBIOS_MAX_SIZE - 2) + return -EINVAL; + base = curr_block + pcir_ptr; + + if (!gsp_phys_prom_read32(bar0, base, &sig)) + return -EIO; + if (!gsp_phys_pci_data_sig_valid(sig)) { + pr_err("gsp-phys-vbios: invalid PCI data signature 0x%08x at 0x%x\n", + sig, base); + return -EINVAL; + } + + { + u32 v; + + if (!gsp_phys_prom_read8(bar0, + base + + GSP_PHYS_PCIR_LAST_IMAGE_OFF, + &v)) + return -EIO; + last_image = (v & GSP_PHYS_PCIR_LAST_IMAGE) != 0; + } + if (!gsp_phys_prom_read16(bar0, base + GSP_PHYS_PCIR_IMAGE_LEN, + &img_len)) + return -EIO; + sub_img_len = img_len; + + if (!gsp_phys_prom_read16(bar0, base + GSP_PHYS_PCIR_STRUCT_LEN, + &struct_len)) + return -EIO; + ext_at = (base + struct_len + 0xf) & ~0xfu; + + if (gsp_phys_prom_read32(bar0, ext_at, &ext_sig) && + ext_sig == GSP_PHYS_NPDE_SIG && + gsp_phys_prom_read16(bar0, ext_at + GSP_PHYS_NPDE_REV, + &ext_rev) && + (ext_rev == GSP_PHYS_NPDE_REV_10 || + ext_rev == GSP_PHYS_NPDE_REV_11)) { + if (!gsp_phys_prom_read16(bar0, + ext_at + GSP_PHYS_NPDE_LEN, + &ext_len)) + return -EIO; + if (!gsp_phys_prom_read16(bar0, + ext_at + + GSP_PHYS_NPDE_SUBIMAGE_LEN, + &sub_img_len)) + return -EIO; + if (GSP_PHYS_NPDE_LAST_IMAGE + 1 <= ext_len) { + u32 v; + + if (!gsp_phys_prom_read8(bar0, + ext_at + + GSP_PHYS_NPDE_LAST_IMAGE, + &v)) + return -EIO; + last_image = (v & GSP_PHYS_PCIR_LAST_IMAGE) != 0; + } else if (sub_img_len < img_len) { + last_image = false; + } + } + + if (!gsp_phys_prom_read8(bar0, base + GSP_PHYS_PCIR_CODE_TYPE, + &code_type)) + return -EIO; + + block_offset = curr_block - pci_offset; + block_size = sub_img_len * GSP_PHYS_ROM_BLOCK_SIZE; + + if (ext_rom_offset == 0 && + code_type == GSP_PHYS_NPDE_CODE_TYPE_VBIOS_EXT) + ext_rom_offset = block_offset; + else if (base_rom_size == 0 && + code_type == GSP_PHYS_NPDE_CODE_TYPE_VBIOS_BASE) + base_rom_size = block_size; + + if (last_image) + break; + + if (sub_img_len == 0) { + pr_err("gsp-phys-vbios: zero-sized ROM block at 0x%x\n", + curr_block); + return -EINVAL; + } + curr_block += sub_img_len * GSP_PHYS_ROM_BLOCK_SIZE; + if (curr_block > GSP_PHYS_VBIOS_MAX_SIZE) + return -EINVAL; + } + + if (iter == iterations) + return -EINVAL; + + if (bios_size) + *bios_size = block_offset + block_size; + if (expansion_rom_offset) { + if (ext_rom_offset > 0 && base_rom_size > 0) + *expansion_rom_offset = ext_rom_offset - base_rom_size; + else + *expansion_rom_offset = 0; + } + + return 0; +} + +static int gsp_phys_vbios_copy_image(struct gsp_phys_vbios *img, + void __iomem *bar0, u32 pci_offset, + u32 bios_size) +{ + u32 aligned, i; + u8 *buf; + + if (bios_size == 0 || bios_size > GSP_PHYS_VBIOS_MAX_SIZE) + return -EINVAL; + + buf = kvzalloc(bios_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + aligned = bios_size & ~0x3u; + for (i = 0; i < aligned; i += 4) { + u32 word; + + if (!gsp_phys_prom_read32(bar0, pci_offset + i, &word)) { + kvfree(buf); + return -EIO; + } + put_unaligned_le32(word, buf + i); + } + for (; i < bios_size; i++) { + u32 byte; + + if (!gsp_phys_prom_read8(bar0, pci_offset + i, &byte)) { + kvfree(buf); + return -EIO; + } + buf[i] = (u8)byte; + } + + img->data = buf; + img->size = bios_size; + img->pci_offset = pci_offset; + return 0; +} + +int gsp_phys_vbios_read(struct gsp_phys_vbios *img, struct pci_dev *pdev, + void __iomem *bar0) +{ + u32 pci_offset = 0, bios_size = 0, expansion_rom_offset = 0; + u32 rom_sig; + int ret; + + if (!img || !bar0) + return -EINVAL; + + memset(img, 0, sizeof(*img)); + + if (!gsp_phys_prom_read16(bar0, 0, &rom_sig)) + return -EIO; + + if (rom_sig != GSP_PHYS_ROM_SIG_STD && + rom_sig != GSP_PHYS_ROM_SIG_NV && + rom_sig != GSP_PHYS_ROM_SIG_NV2) { + ret = gsp_phys_vbios_find_pci_header(bar0, &pci_offset); + if (ret) + return ret; + + /* Re-read the ROM signature at the IFR image start. */ + if (!gsp_phys_prom_read16(bar0, pci_offset, &rom_sig)) + return -EIO; + } + + if (rom_sig != GSP_PHYS_ROM_SIG_STD && + rom_sig != GSP_PHYS_ROM_SIG_NV && + rom_sig != GSP_PHYS_ROM_SIG_NV2) { + pr_err("gsp-phys-vbios: no valid ROM signature at offset 0x%x (0x%04x)\n", + pci_offset, rom_sig); + return -EINVAL; + } + + ret = gsp_phys_vbios_locate_expansion_roms(bar0, pci_offset, + &bios_size, + &expansion_rom_offset); + if (ret) { + pr_err("gsp-phys-vbios: expansion ROM walk failed: %d\n", ret); + return ret; + } + if (bios_size > GSP_PHYS_VBIOS_MAX_SIZE) { + pr_err("gsp-phys-vbios: expansion ROM size 0x%x exceeds the window\n", + bios_size); + return -EINVAL; + } + + ret = gsp_phys_vbios_copy_image(img, bar0, pci_offset, bios_size); + if (ret) + return ret; + + img->expansion_rom_offset = expansion_rom_offset; + + pr_info("gsp-phys-vbios: ROM on %s: pciOffset=0x%x size=0x%x expansionRomOffset=0x%x\n", + pci_name(pdev), pci_offset, bios_size, expansion_rom_offset); + return 0; +} + +void gsp_phys_vbios_free(struct gsp_phys_vbios *img) +{ + if (!img) + return; + kvfree(img->data); + memset(img, 0, sizeof(*img)); +} + +/* Find the BIT header: id 0xB8FF + "BIT\0" signature, header checksum 0. */ +static int gsp_phys_vbios_find_bit(const struct gsp_phys_vbios *img, + u32 *bit_addr) +{ + u32 addr; + + for (addr = 0; addr + 4 <= img->size; addr++) { + u32 id, sig, header_size, checksum = 0, j; + + if (!gsp_phys_img_read(img, addr, 2, &id) || + id != GSP_PHYS_BIT_ID) + continue; + if (!gsp_phys_img_read(img, addr + 2, 4, &sig) || + sig != GSP_PHYS_BIT_SIGNATURE) + continue; + if (!gsp_phys_img_read(img, addr + GSP_PHYS_BIT_HEADER_SIZE_OFF, + 1, &header_size)) + continue; + if (header_size == 0 || header_size > img->size - addr) + continue; + for (j = 0; j < header_size; j++) { + u32 b; + + if (!gsp_phys_img_read(img, addr + j, 1, &b)) + break; + checksum += b; + } + if (j != header_size) + continue; + if ((checksum & 0xff) == 0) { + *bit_addr = addr; + return 0; + } + } + + return -ENOENT; +} + +static bool gsp_phys_fwsec_appid_wanted(u32 app_id, bool use_debug_app) +{ + if (app_id == GSP_PHYS_FWSEC_APPID_FIRMWARE_SEC_LIC) + return true; + if (use_debug_app) + return app_id == GSP_PHYS_FWSEC_APPID_DEBUG; + return app_id == GSP_PHYS_FWSEC_APPID_PROD; +} + +static int gsp_phys_vbios_parse_desc(const struct gsp_phys_vbios *img, + u32 desc_offset, u32 app_id, u32 target_id, + struct gsp_phys_fwsec_desc *desc) +{ + u32 vdesc, version, size, fields[14]; + u32 i, image_start, image_end; + + if (!gsp_phys_img_read(img, desc_offset, 4, &vdesc)) + return -EIO; + + /* The flags bit0 must announce a version; 0 means unavailable. */ + if ((vdesc & 0x1) == 0) + return 0; + + version = (vdesc >> 8) & 0xff; + size = (vdesc >> 16) & 0xffff; + + if (version == 3 && size >= GSP_PHYS_FALCON_UCODE_DESC_V3_SIZE) { + pr_err("gsp-phys-vbios: FWSEC appId=0x%x is a V3 (boot-from-HS) descriptor; this GPU generation boots FWSEC with the generic SEC2 boot-loader (V2) only - cannot proceed\n", + app_id); + return -EOPNOTSUPP; + } + if (version != 2 || size < GSP_PHYS_FALCON_UCODE_DESC_V2_SIZE) + return 1; /* not a usable descriptor; keep looking */ + + if (desc_offset > img->size || size > img->size - desc_offset) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + if (!gsp_phys_img_read(img, desc_offset + 4 + i * 4, 4, + &fields[i])) + return -EIO; + } + + /* V2 fields: Stored, Uncompressed, VirtualEntry, Interface, IMEMPhys, + * IMEMLoad, IMEMVirt, IMEMSecBase, IMEMSecSize, DMEMOffset, DMEMPhys, + * DMEMLoad, altIMEM, altDMEM. + */ + memset(desc, 0, sizeof(*desc)); + desc->app_id = app_id; + desc->target_id = target_id; + desc->desc_version = version; + desc->desc_size = size; + desc->desc_offset = desc_offset; + desc->virtual_entry = fields[2]; + desc->interface_offset = fields[3]; + desc->imem_phys_base = fields[4]; + desc->imem_load_size = fields[5]; + desc->imem_virt_base = fields[6]; + desc->imem_sec_base = fields[7]; + desc->imem_sec_size = fields[8]; + desc->dmem_offset = fields[9]; + desc->dmem_phys_base = fields[10]; + desc->dmem_load_size = fields[11]; + + if (desc->imem_load_size == 0 || desc->dmem_load_size == 0) { + pr_err("gsp-phys-vbios: FWSEC descriptor has a zero-sized IMEM/DMEM image (imem=0x%x dmem=0x%x)\n", + desc->imem_load_size, desc->dmem_load_size); + return -EINVAL; + } + if (desc->imem_sec_size > desc->imem_load_size) { + pr_err("gsp-phys-vbios: FWSEC secure IMEM size 0x%x exceeds load size 0x%x\n", + desc->imem_sec_size, desc->imem_load_size); + return -EINVAL; + } + + /* Code is stored immediately after the descriptor, data after code. */ + image_start = desc_offset + size; + if (image_start > img->size || + desc->imem_load_size > img->size - image_start) { + pr_err("gsp-phys-vbios: FWSEC IMEM image runs past the ROM copy\n"); + return -EINVAL; + } + if (desc->dmem_offset > img->size - image_start) { + pr_err("gsp-phys-vbios: FWSEC DMEM offset runs past the ROM copy\n"); + return -EINVAL; + } + image_end = image_start + desc->dmem_offset; + if (desc->dmem_load_size > img->size - image_end) { + pr_err("gsp-phys-vbios: FWSEC DMEM image runs past the ROM copy\n"); + return -EINVAL; + } + if (desc->interface_offset >= desc->dmem_load_size) { + pr_err("gsp-phys-vbios: FWSEC interface offset 0x%x outside DMEM image (0x%x)\n", + desc->interface_offset, desc->dmem_load_size); + return -EINVAL; + } + + pr_info("gsp-phys-vbios: FWSEC entry appId=0x%x targetId=%u desc=V%u size=%u at 0x%x\n", + app_id, target_id, version, size, desc_offset); + pr_info("gsp-phys-vbios: FWSEC image: code at 0x%x (0x%x bytes) data at 0x%x (0x%x bytes)\n", + image_start, desc->imem_load_size, + image_start + desc->dmem_offset, desc->dmem_load_size); + pr_info("gsp-phys-vbios: FWSEC IMEM base/load/virt/secBase/secSize=0x%x/0x%x/0x%x/0x%x/0x%x DMEM off/phys/load=0x%x/0x%x/0x%x interfaceOff=0x%x entry=0x%x\n", + desc->imem_phys_base, desc->imem_load_size, + desc->imem_virt_base, desc->imem_sec_base, + desc->imem_sec_size, desc->dmem_offset, + desc->dmem_phys_base, desc->dmem_load_size, + desc->interface_offset, desc->virtual_entry); + return 0; +} + +/* + * Parse the copied VBIOS for the FWSEC falcon-ucode descriptor: find the BIT + * header, walk its tokens to the falcon-ucode table, then walk the table + * entries for the requested FWSEC application. Also reports the combined + * VBIOS version from the BIOSDATA token when present. + */ +int gsp_phys_vbios_parse_fwsec(const struct gsp_phys_vbios *img, + bool use_debug_app, + struct gsp_phys_fwsec_desc *desc) +{ + u32 bit_addr, header_size, token_size, token_entries; + u64 version = 0; + int ret; + + if (!img || !img->data || !desc) + return -EINVAL; + + ret = gsp_phys_vbios_find_bit(img, &bit_addr); + if (ret) { + pr_err("gsp-phys-vbios: BIT header not found in %zu-byte ROM image\n", + img->size); + return ret; + } + + if (!gsp_phys_img_read(img, bit_addr + GSP_PHYS_BIT_HEADER_SIZE_OFF, 1, + &header_size) || + !gsp_phys_img_read(img, bit_addr + GSP_PHYS_BIT_TOKEN_SIZE_OFF, 1, + &token_size) || + !gsp_phys_img_read(img, bit_addr + GSP_PHYS_BIT_TOKEN_ENTRIES_OFF, + 1, &token_entries)) { + pr_err("gsp-phys-vbios: truncated BIT header at 0x%x\n", + bit_addr); + return -EINVAL; + } + pr_info("gsp-phys-vbios: BIT header at 0x%x headerSize=%u tokenSize=%u tokens=%u\n", + bit_addr, header_size, token_size, token_entries); + + if (token_size < GSP_PHYS_BIT_TOKEN_SIZE_6) { + pr_err("gsp-phys-vbios: invalid BIT token size %u\n", + token_size); + return -EINVAL; + } + + { + u32 i; + + for (i = 0; i < token_entries; i++) { + u32 token_offset = bit_addr + header_size + + i * token_size; + u32 token_id, data_version, data_size, data_ptr; + u32 table_ptr; + + if (!gsp_phys_img_read(img, token_offset, 1, + &token_id) || + !gsp_phys_img_read(img, token_offset + 1, 1, + &data_version) || + !gsp_phys_img_read(img, token_offset + 2, 2, + &data_size)) + continue; + + if (token_size >= GSP_PHYS_BIT_TOKEN_SIZE_8) { + if (!gsp_phys_img_read(img, token_offset + 4, 4, + &data_ptr)) + continue; + } else { + if (!gsp_phys_img_read(img, token_offset + 4, 2, + &data_ptr)) + continue; + } + + if (token_id == GSP_PHYS_BIT_TOKEN_BIOSDATA && + (data_version == 1 || data_version == 2) && + data_size > GSP_PHYS_BIT_BIOSDATA_SIZE_MIN) { + u32 binver, oem; + + if (gsp_phys_img_read(img, data_ptr, 4, + &binver) && + gsp_phys_img_read(img, data_ptr + 4, 1, + &oem)) + version = ((u64)binver << 8) | oem; + } + + if (token_id != GSP_PHYS_BIT_TOKEN_FALCON_DATA || + data_version != 2 || + data_size < GSP_PHYS_BIT_FALCON_DATA_V2_SIZE_MIN) + continue; + + if (!gsp_phys_img_read(img, data_ptr, 4, &table_ptr)) + continue; + + { + u32 table_offset = img->expansion_rom_offset + + table_ptr; + u32 table_version, table_hdr_size, entry_size; + u32 entry_count, j; + + if (!gsp_phys_img_read(img, table_offset, 1, + &table_version) || + !gsp_phys_img_read(img, table_offset + 1, 1, + &table_hdr_size) || + !gsp_phys_img_read(img, table_offset + 2, 1, + &entry_size) || + !gsp_phys_img_read(img, table_offset + 3, 1, + &entry_count)) + continue; + + if (table_version != + GSP_PHYS_FALCON_UCODE_TABLE_VERSION || + table_hdr_size < + GSP_PHYS_FALCON_UCODE_TABLE_HDR_SIZE || + entry_size < GSP_PHYS_FALCON_UCODE_ENTRY_SIZE) + continue; + + for (j = 0; j < entry_count; j++) { + u32 entry_offset = table_offset + + table_hdr_size + + j * entry_size; + u32 app_id, target_id, desc_ptr; + + if (!gsp_phys_img_read(img, entry_offset, + 1, &app_id) || + !gsp_phys_img_read(img, + entry_offset + 1, 1, + &target_id) || + !gsp_phys_img_read(img, + entry_offset + 2, 4, + &desc_ptr)) + continue; + + if (!gsp_phys_fwsec_appid_wanted( + app_id, use_debug_app)) + continue; + + pr_info("gsp-phys-vbios: BIT falcon entry %u: appId=0x%x targetId=%u descPtr=0x%x\n", + j, app_id, target_id, desc_ptr); + + ret = gsp_phys_vbios_parse_desc( + img, + img->expansion_rom_offset + + desc_ptr, + app_id, target_id, desc); + if (ret < 0) + return ret; + if (ret == 0) { + pr_info("gsp-phys-vbios: VBIOS version (binver<<8|oem) = 0x%llx\n", + version); + return 0; + } + } + } + } + } + + pr_err("gsp-phys-vbios: no usable FWSEC V2 descriptor in the BIT (debug app %s)\n", + use_debug_app ? "wanted" : "not wanted"); + return -ENOENT; +} |
