[PATCH v4 07/17] gpu: nova-core: wait for GFW boot in probe, not in the Gpu constructor

From: John Hubbard

Date: Sat Sep 12 2026 - 00:45:45 EST


The GPU boots its own firmware, GFW, out of reset, and nothing may
program the GPU until GFW reports completion.

nova-core waited for GFW inside the Gpu constructor, which also boots
the GSP. Code that has to run after GFW and before GSP boot, such as a
probe-time hardware self-test, had nowhere to go.

Move the wait into probe, ahead of the Gpu constructor, and read the
chipset there from a Spec that probe builds itself. Leave the DMA mask
in the constructor, since it programs the host rather than the GPU.

Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/driver.rs | 13 ++++++++++++-
drivers/gpu/nova-core/gpu.rs | 28 ++++++++++++++++++++--------
2 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 0672a0707a71..15a44f9a6441 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -22,7 +22,13 @@
types::CovariantForLt,
};

-use crate::gpu::Gpu;
+use crate::{
+ gpu,
+ gpu::{
+ Gpu,
+ Spec, //
+ }, //
+};

/// Counter for generating unique auxiliary device IDs.
static AUXILIARY_ID_COUNTER: Atomic<u32> = Atomic::new(0);
@@ -109,6 +115,11 @@ fn probe<'bound>(
let bar1_idx = bar1_resource_index(pdev)?;
pdev.iomap_region(bar1_idx, c"nova-core/bar1")?
},
+ _: {
+ let spec = Spec::new(pdev.as_ref(), bar)?;
+
+ gpu::wait_gfw_boot_completion(pdev.as_ref(), bar, spec.chipset)?;
+ },
// TODO: Use self-referential pin-init syntax once available.
gpu <- Gpu::new(
pdev,
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index d763bc8d3827..3d796d6c7013 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -212,12 +212,12 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// Structure holding a basic description of the GPU: `Chipset` and `Revision`.
#[derive(Clone, Copy)]
pub(crate) struct Spec {
- chipset: Chipset,
+ pub(crate) chipset: Chipset,
revision: Revision,
}

impl Spec {
- fn new(dev: &device::Device, bar: Bar0<'_>) -> Result<Spec> {
+ pub(crate) fn new(dev: &device::Device, bar: Bar0<'_>) -> Result<Spec> {
// Some brief notes about boot0 and boot42, in chronological order:
//
// NV04 through NV50:
@@ -362,17 +362,12 @@ pub(crate) fn new<'a>(
dev_info!(dev,"NVIDIA ({})\n", spec);
})?,

- // We must wait for GFW_BOOT completion before doing any significant setup on the GPU.
_: {
- let hal = hal::gpu_hal(spec.chipset);
- let dma_mask = hal.dma_mask();
+ let dma_mask = hal::gpu_hal(spec.chipset).dma_mask();

// SAFETY: `Gpu` owns all DMA allocations for this device, and we are
// still constructing it, so no concurrent DMA allocations can exist.
unsafe { pdev.dma_set_mask_and_coherent(dma_mask)? };
-
- hal.wait_gfw_boot_completion(bar)
- .inspect_err(|_| dev_err!(dev, "GFW boot did not complete\n"))?;
},

// Initialize this early because `gsp_resources` depends on it.
@@ -495,6 +490,23 @@ pub(crate) fn run_selftests(self: Pin<&mut Self>, pdev: &pci::Device<device::Bou
}
}

+/// Waits for GFW, the GPU's boot firmware, to report completion.
+///
+/// Nothing may program the GPU before then.
+///
+/// # Errors
+///
+/// `ETIMEDOUT` if GFW does not report completion in time.
+pub(crate) fn wait_gfw_boot_completion(
+ dev: &device::Device<device::Bound>,
+ bar: Bar0<'_>,
+ chipset: Chipset,
+) -> Result {
+ hal::gpu_hal(chipset)
+ .wait_gfw_boot_completion(bar)
+ .inspect_err(|_| dev_err!(dev, "GFW boot did not complete\n"))
+}
+
/// Reads the boot0 register and returns its raw value.
pub(crate) fn boot_0_raw(bar: Bar0<'_>) -> u32 {
bar.read(regs::NV_PMC_BOOT_0).into_raw()
--
2.55.0