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)
{
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)