]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/nouveau/gsp: read MMU_LOCK to fix WPR placement on GA100
authorTimur Tabi <ttabi@nvidia.com>
Thu, 30 Apr 2026 22:38:32 +0000 (17:38 -0500)
committerDanilo Krummrich <dakr@kernel.org>
Thu, 28 May 2026 17:30:15 +0000 (19:30 +0200)
On GA100, the row remapper hardware reserves a small amount of DRAM at
the end of framebuffer for spare rows used to repair memory errors at
runtime.  When an uncorrectable ECC error is detected in a DRAM row,
the row remapper redirects accesses to a spare row, transparently
repairing the fault.

The LOCAL_MEMORY_RANGE register (0x100ce0) reports the GPU's FB address
range, but its encoding rounds to 1GB boundaries.  On GA100, VBIOS
originally rounded this value down, which could lose up to ~1GB of
usable FB.  As a workaround, newer VBIOS instead rounds up to the next
1GB boundary and programs MMU_LOCK (registers 0x1fa82c/0x1fa830) to
mark the gap between the actual usable FB and the rounded-up range as
reserved.

OpenRM's kgspCalculateFbLayout_TU102() handles this by reading the
MMU_LOCK registers and computing the WPR top boundary as:

    vbiosReservedOffset = min(mmuLockLo, vgaWorkspaceOffset)

Without this, the WPR region is placed at the top of LOCAL_MEMORY_RANGE,
which overlaps the reserved region.  The booter firmware detects this
and rejects the WPR layout.

Add ga100_gsp_mmu_lock_lo() to read the MMU_LOCK range and clamp
gsp->fb.bios.addr accordingly, mirroring OpenRM's behavior.

This is a GA100-only issue.  GA102 and later add the
NV_USABLE_FB_SIZE_IN_MB register which reports the correct usable FB
size directly, eliminating the need for the MMU_LOCK workaround.

Signed-off-by: Timur Tabi <ttabi@nvidia.com>
Link: https://patch.msgid.link/20260430223838.2530778-5-ttabi@nvidia.com
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c

index aa3ac34989b78c3ce8e04041e987d72663d949d9..66f285b60f1e46491037f825ac84d7ff4e90696b 100644 (file)
@@ -291,6 +291,42 @@ tu102_gsp_vga_workspace_addr(struct nvkm_gsp *gsp, u64 fb_size)
        return addr;
 }
 
+/*
+ * Read the MMU_LOCK range programmed by VBIOS.
+ *
+ * The row remapper reserves a small amount of DRAM at the end of FB for
+ * spare rows used to repair memory errors.  The register used to report
+ * FB size rounds to 1GB boundaries, and VBIOS rounds up rather than
+ * down -- reporting a larger size than is actually usable.  To
+ * compensate, VBIOS programs MMU_LOCK to fence off the unusable region
+ * at the top.  We must read this and keep WPR below it.
+ *
+ * Returns the low address of the locked region, or 0 if not set.
+ */
+static u64
+ga100_gsp_mmu_lock_lo(struct nvkm_gsp *gsp)
+{
+       struct nvkm_device *device = gsp->subdev.device;
+       u32 lo, hi;
+       u64 addr_lo, addr_hi;
+
+       /* NV_PFB_PRI_MMU_LOCK_CFG_PRIV_LEVEL_MASK */
+       if (!(nvkm_rd32(device, 0x1fa7c8) & 0x00000001))
+               return 0;
+
+       lo = nvkm_rd32(device, 0x1fa82c); /* NV_PFB_PRI_MMU_LOCK_ADDR_LO */
+       hi = nvkm_rd32(device, 0x1fa830); /* NV_PFB_PRI_MMU_LOCK_ADDR_HI */
+
+       addr_lo = (u64)(lo >> 4) << 12;
+       addr_hi = (u64)(hi >> 4) << 12;
+
+       if (addr_hi < addr_lo)
+               return 0;
+
+       nvkm_debug(&gsp->subdev, "MMU_LOCK range: 0x%llx - 0x%llx\n", addr_lo, addr_hi);
+       return addr_lo;
+}
+
 int
 tu102_gsp_oneinit(struct nvkm_gsp *gsp)
 {
@@ -304,6 +340,19 @@ tu102_gsp_oneinit(struct nvkm_gsp *gsp)
        gsp->fb.bios.addr = gsp->fb.bios.vga_workspace.addr;
        gsp->fb.bios.size = gsp->fb.bios.vga_workspace.size;
 
+       /*
+        * On GA100, VBIOS may lock the top of FB via MMU_LOCK to reserve
+        * space for the row remapper.  Mirror OpenRM's kgspCalculateFbLayout
+        * by clamping bios.addr to min(mmuLockLo, vgaWorkspace) so that the
+        * entire WPR2 layout and gspFwWprEnd stay within usable FB.
+        */
+       if (device->chipset == 0x170) {
+               u64 lock_lo = ga100_gsp_mmu_lock_lo(gsp);
+
+               if (lock_lo)
+                       gsp->fb.bios.addr = min(gsp->fb.bios.addr, lock_lo);
+       }
+
        ret = gsp->func->booter.ctor(gsp, "booter-load", gsp->fws.booter.load,
                                     &device->sec2->falcon, &gsp->booter.load);
        if (ret)