summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_phys_vbios.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/gsp_phys_vbios.c')
-rw-r--r--kernel/gsp_phys_vbios.c771
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;
+}