To support the maximum vGPUs on the device that support vGPU, a larger WPR2 heap size is required. By setting the WPR2 heap size larger than 256MB the scrubber ucode image is required to scrub the FB memory before any other ucode image is executed.
If not, the GSP firmware hangs when booting. When the WPR2 heap exceeds 256MB, execute the scrubber ucode image to scrub the FB memory before executing any other ucode images. Cc: Dirk Behme <[email protected]> Cc: Joel Fernandes <[email protected]> Cc: Alexandre Courbot <[email protected]> Signed-off-by: Zhi Wang <[email protected]> --- drivers/gpu/nova-core/firmware.rs | 3 +- drivers/gpu/nova-core/firmware/booter.rs | 2 + drivers/gpu/nova-core/gsp/boot.rs | 53 ++++++++++++++++++++++++ drivers/gpu/nova-core/gsp/fw.rs | 2 +- drivers/gpu/nova-core/regs.rs | 12 ++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs index ab5889fa6a56..d13615864198 100644 --- a/drivers/gpu/nova-core/firmware.rs +++ b/drivers/gpu/nova-core/firmware.rs @@ -437,7 +437,8 @@ const fn make_entry_chipset(self, chipset: gpu::Chipset) -> Self { .make_entry_file(name, "booter_load") .make_entry_file(name, "booter_unload") .make_entry_file(name, "bootloader") - .make_entry_file(name, "gsp"); + .make_entry_file(name, "gsp") + .make_entry_file(name, "scrubber"); if chipset.needs_fwsec_bootloader() { this.make_entry_file(name, "gen_bootloader") diff --git a/drivers/gpu/nova-core/firmware/booter.rs b/drivers/gpu/nova-core/firmware/booter.rs index 04a887bea888..8642957923a2 100644 --- a/drivers/gpu/nova-core/firmware/booter.rs +++ b/drivers/gpu/nova-core/firmware/booter.rs @@ -284,6 +284,7 @@ fn new_booter(data: &[u8]) -> Result<Self> { #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum BooterKind { + Scrubber, Loader, #[expect(unused)] Unloader, @@ -301,6 +302,7 @@ pub(crate) fn new( bar: &Bar0, ) -> Result<Self> { let fw_name = match kind { + BooterKind::Scrubber => "scrubber", BooterKind::Loader => "booter_load", BooterKind::Unloader => "booter_unload", }; diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs index 86b7a7aa8f5a..8f6e2536db1c 100644 --- a/drivers/gpu/nova-core/gsp/boot.rs +++ b/drivers/gpu/nova-core/gsp/boot.rs @@ -6,6 +6,7 @@ io::poll::read_poll_timeout, io_write, prelude::*, + sizes::SZ_256M_U64, time::Delta, // }; @@ -188,6 +189,49 @@ fn run_fwsec_frts( } } + /// Load and execute the scrubber ucode via SEC2 to scrub FB memory. + /// + /// This is required when the WPR2 heap exceeds 256MB (which happens when + /// vGPU is enabled). Without scrubbing, GSP firmware will hang during boot. + fn run_scrubber(ctx: &super::GspBootContext<'_>) -> Result { + let dev = ctx.dev(); + let bar = ctx.bar; + + let scrubber = BooterFirmware::new( + dev, + BooterKind::Scrubber, + ctx.chipset, + FIRMWARE_VERSION, + ctx.sec2_falcon, + bar, + )?; + + ctx.sec2_falcon.reset(bar)?; + ctx.sec2_falcon.load(dev, bar, &scrubber)?; + + let (mbox0, mbox1) = ctx.sec2_falcon.boot(bar, None, None)?; + dev_dbg!( + dev, + "Scrubber SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", + mbox0, + mbox1 + ); + + // Poll for scrubber completion via BSI_SECURE_SCRATCH_15. + read_poll_timeout( + || Ok(regs::NV_PGC6_BSI_SECURE_SCRATCH_15::read(bar)), + |val| val.scrubber_completed(), + Delta::from_millis(10), + Delta::from_secs(2), + ) + .inspect_err(|_| { + dev_err!(dev, "Scrubber did not complete in time\n"); + })?; + + dev_dbg!(dev, "Scrubber completed successfully\n"); + Ok(()) + } + fn run_booter( dev: &device::Device<device::Bound>, bar: &Bar0, @@ -221,11 +265,19 @@ fn boot_via_sec2( fb_layout: &FbLayout, libos: &Coherent<[LibosMemoryRegionInitArgument]>, wpr_meta: &Coherent<GspFwWprMeta>, + ctx: &super::GspBootContext<'_>, ) -> Result { // Run FWSEC-FRTS to set up the WPR2 region let bios = Vbios::new(dev, bar)?; Self::run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, fb_layout)?; + // When the WPR2 heap exceeds 256MB (e.g. when vGPU is enabled), the + // scrubber ucode must scrub FB memory before any other ucode images + // execute — without this, GSP firmware hangs during boot. + if fb_layout.wpr2_heap.len() > SZ_256M_U64 { + Self::run_scrubber(ctx)?; + } + // Reset and boot GSP before SEC2 gsp_falcon.reset(bar)?; let libos_handle = libos.dma_handle(); @@ -375,6 +427,7 @@ pub(crate) fn boot( &fb_layout, &self.libos, &wpr_meta, + ctx, )?; } else { Self::boot_via_fsp( diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs index b46ab6b921e3..30d82501faba 100644 --- a/drivers/gpu/nova-core/gsp/fw.rs +++ b/drivers/gpu/nova-core/gsp/fw.rs @@ -7,7 +7,7 @@ // Alias to avoid repeating the version number with every use. use r570_144 as bindings; -use core::ops::Range; +use core::{fmt, ops::Range}; use kernel::{ device, diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 8d424dd23a5a..5692114c710c 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -216,6 +216,18 @@ pub(crate) fn higher_bound(self) -> u64 { 26:26 boot_stage_3_handoff as bool; }); +// BSI secure scratch 15: scrubber handoff status. +register!(NV_PGC6_BSI_SECURE_SCRATCH_15 @ 0x001180fc { + 31:29 scrubber_handoff as u8; +}); + +impl NV_PGC6_BSI_SECURE_SCRATCH_15 { + /// Returns `true` if scrubber has completed. + pub(crate) fn scrubber_completed(self) -> bool { + self.scrubber_handoff() >= 0x3 + } +} + // Privilege level mask register. It dictates whether the host CPU has privilege to access the // `PGC6_AON_SECURE_SCRATCH_GROUP_05` register (which it needs to read GFW_BOOT). register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128, -- 2.51.0
