]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
drm/nouveau/bios: skip the IFR header if present
authorTimur Tabi <ttabi@nvidia.com>
Thu, 30 Apr 2026 22:38:36 +0000 (17:38 -0500)
committerDanilo Krummrich <dakr@kernel.org>
Thu, 28 May 2026 17:30:15 +0000 (19:30 +0200)
The GPU's ROM may begin with an Init-from-ROM (IFR) header that precedes
the PCI Expansion ROM images (VBIOS).  When present, the PROM shadow
method must parse this header to determine the offset where the PCI ROM
images actually begin, and adjust all subsequent reads accordingly.

On most GPUs this is not needed because either the PRAMIN shadow method
(which reads from VRAM via the display engine) succeeds first, or the IFR
microcode has already applied the ROM offset so that PROM reads
transparently skip the header.  However, on GA100 neither of these
applies: GA100 has no display engine (so PRAMIN is unavailable), and the
IFR offset is not applied to PROM reads on this GPU.

Signed-off-by: Timur Tabi <ttabi@nvidia.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Link: https://patch.msgid.link/20260430223838.2530778-9-ttabi@nvidia.com
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
drivers/gpu/drm/nouveau/nvkm/subdev/bios/shadowrom.c

index 39144ceb117b4b42116c420635f29da3960bdee7..9e171b1bad73202b145e71b9386ba5a378ea4bdf 100644 (file)
 
 #include <subdev/pci.h>
 
+#define NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE 0x4947564E      /* "NVGI" */
+#define NV_ROM_DIRECTORY_IDENTIFIER 0x44524652                 /* "RFRD" */
+
+struct priv {
+       struct nvkm_device *device;
+       u32 pci_rom_offset;
+};
+
 static u32
 nvbios_prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
 {
-       struct nvkm_device *device = data;
+       struct priv *priv = data;
+       struct nvkm_device *device = priv->device;
        u32 i;
-       if (offset + length <= 0x00100000) {
-               for (i = offset; i < offset + length; i += 4)
-                       *(u32 *)&bios->data[i] = nvkm_rd32(device, 0x300000 + i);
-               return length;
-       }
-       return 0;
+
+       /* Make sure we don't try to read past the end of data[] */
+       if (offset + length > bios->size)
+               return 0;
+
+       /* Make sure the read falls within the 1MB PROM window */
+       if (offset + priv->pci_rom_offset + length > 0x00100000)
+               return 0;
+
+       for (i = offset; i < offset + length; i += 4)
+               *(u32 *)&bios->data[i] = nvkm_rd32(device, 0x300000 + priv->pci_rom_offset + i);
+       return length;
 }
 
 static void
 nvbios_prom_fini(void *data)
 {
-       struct nvkm_device *device = data;
+       struct priv *priv = data;
+       struct nvkm_device *device = priv->device;
+
        nvkm_pci_rom_shadow(device->pci, true);
+
+       kfree(data);
 }
 
 static void *
 nvbios_prom_init(struct nvkm_bios *bios, const char *name)
 {
        struct nvkm_device *device = bios->subdev.device;
+       struct priv *priv;
+       u32 fixed0;
+
+       /* There is no PROM on NV4x iGPUs */
        if (device->card_type == NV_40 && device->chipset >= 0x4c)
                return ERR_PTR(-ENODEV);
+
+       priv = kzalloc_obj(*priv);
+       if (!priv)
+               return ERR_PTR(-ENOMEM);
+
+       /* Disable the PCI ROM shadow so that we can read PROM. */
        nvkm_pci_rom_shadow(device->pci, false);
-       return device;
+
+       /*
+        * Check for an IFR header. If present, parse it to find the actual PCI ROM header.
+        *
+        * The IFR header is documented in Documentation/gpu/nova/core/vbios.rst
+        */
+       fixed0 = nvkm_rd32(device, 0x300000);
+       if (fixed0 == NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE) {
+               u32 fixed1 = nvkm_rd32(device, 0x300004);
+               u8 version = (fixed1 >> 8) & 0xff;
+               u32 fixed2, data_size, offset, signature;
+
+               switch (version) {
+               case 1:
+               case 2:
+                       data_size = (fixed1 >> 16) & 0x7fff;
+                       priv->pci_rom_offset = nvkm_rd32(device, 0x300000 + data_size + 4);
+                       break;
+               case 3:
+                       fixed2 = nvkm_rd32(device, 0x300008);
+                       data_size = fixed2 & 0x000fffff;
+
+                       /* ROM directory offset */
+                       offset = nvkm_rd32(device, 0x300000 + data_size) + 4096;
+
+                       signature = nvkm_rd32(device, 0x300000 + offset);
+                       if (signature != NV_ROM_DIRECTORY_IDENTIFIER) {
+                               nvkm_error(&bios->subdev, "could not find IFR ROM directory\n");
+                               goto fail;
+                       }
+
+                       priv->pci_rom_offset = nvkm_rd32(device, 0x300000 + offset + 8);
+
+                       break;
+               default:
+                       nvkm_error(&bios->subdev, "unsupported IFR header version %u\n",
+                                  version);
+                       goto fail;
+               }
+
+               /* Double-check that the offset is valid */
+               if (priv->pci_rom_offset >= 0x00100000) {
+                       nvkm_error(&bios->subdev,
+                                  "PCI ROM offset of 0x%x is too large\n", priv->pci_rom_offset);
+                       goto fail;
+               }
+
+               /* If there is an IFR header, there must also be a PCI ROM header. */
+               signature = nvkm_rd32(device, 0x300000 + priv->pci_rom_offset) & 0xffff;
+               if (signature != 0xaa55) {
+                       nvkm_error(&bios->subdev,
+                                  "could not find PCI ROM signature at offset 0x%x\n",
+                                  priv->pci_rom_offset);
+                       goto fail;
+               }
+       }
+
+       priv->device = device;
+       return priv;
+
+fail:
+       nvkm_pci_rom_shadow(device->pci, true);
+       kfree(priv);
+       return ERR_PTR(-ENODEV);
 }
 
 const struct nvbios_source