gpu: nova-core: move GFW boot wait into a GPU HAL

Introduce a GpuHal trait and per-family dispatch so GPU boot
behavior can vary by architecture. Move wait_gfw_boot_completion()
from the standalone gfw module into gpu/hal/tu102.rs as the first
GpuHal implementation. All architectures currently dispatch to this
implementation, preserving existing behavior.

Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Link: https://patch.msgid.link/20260411024953.473149-7-jhubbard@nvidia.com
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
This commit is contained in:
John Hubbard 2026-04-10 19:49:31 -07:00 committed by Alexandre Courbot
parent 46d455853d
commit 8ff326fa0a
5 changed files with 118 additions and 79 deletions

View File

@ -1,76 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
//! GPU Firmware (`GFW`) support, a.k.a `devinit`.
//!
//! Upon reset, the GPU runs some firmware code from the BIOS to setup its core parameters. Most of
//! the GPU is considered unusable until this step is completed, so we must wait on it before
//! performing driver initialization.
//!
//! A clarification about devinit terminology: devinit is a sequence of register read/writes after
//! reset that performs tasks such as:
//! 1. Programming VRAM memory controller timings.
//! 2. Power sequencing.
//! 3. Clock and PLL configuration.
//! 4. Thermal management.
//!
//! devinit itself is a 'script' which is interpreted by an interpreter program typically running
//! on the PMU microcontroller.
//!
//! Note that the devinit sequence also needs to run during suspend/resume.
use kernel::{
io::{
poll::read_poll_timeout,
Io, //
},
prelude::*,
time::Delta, //
};
use crate::{
driver::Bar0,
regs, //
};
/// Wait for the `GFW` (GPU firmware) boot completion signal (`GFW_BOOT`), or a 4 seconds timeout.
///
/// Upon GPU reset, several microcontrollers (such as PMU, SEC2, GSP etc) run some firmware code to
/// setup its core parameters. Most of the GPU is considered unusable until this step is completed,
/// so it must be waited on very early during driver initialization.
///
/// The `GFW` code includes several components that need to execute before the driver loads. These
/// components are located in the VBIOS ROM and executed in a sequence on these different
/// microcontrollers. The devinit sequence typically runs on the PMU, and the FWSEC runs on the
/// GSP.
///
/// This function waits for a signal indicating that core initialization is complete. Before this
/// signal is received, little can be done with the GPU. This signal is set by the FWSEC running on
/// the GSP in Heavy-secured mode.
pub(crate) fn wait_gfw_boot_completion(bar: &Bar0) -> Result {
// Before accessing the completion status in `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05`, we must
// first check `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK`. This is because
// `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05` becomes accessible only after the secure firmware
// (FWSEC) lowers the privilege level to allow CPU (LS/Light-secured) access. We can only
// safely read the status register from CPU (LS/Light-secured) once the mask indicates
// that the privilege level has been lowered.
//
// TIMEOUT: arbitrarily large value. GFW starts running immediately after the GPU is put out of
// reset, and should complete in less time than that.
read_poll_timeout(
|| {
Ok(
// Check that FWSEC has lowered its protection level before reading the GFW_BOOT
// status.
bar.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK)
.read_protection_level0()
&& bar
.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT)
.completed(),
)
},
|&gfw_booted| gfw_booted,
Delta::from_millis(1),
Delta::from_secs(4),
)
.map(|_| ())
}

View File

@ -20,11 +20,12 @@
Falcon, //
},
fb::SysmemFlush,
gfw,
gsp::Gsp,
regs,
};
mod hal;
macro_rules! define_chipset {
({ $($variant:ident = $value:expr),* $(,)* }) =>
{
@ -274,7 +275,7 @@ pub(crate) fn new<'a>(
// We must wait for GFW_BOOT completion before doing any significant setup on the GPU.
_: {
gfw::wait_gfw_boot_completion(bar)
hal::gpu_hal(spec.chipset).wait_gfw_boot_completion(bar)
.inspect_err(|_| dev_err!(pdev, "GFW boot did not complete\n"))?;
},

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::prelude::*;
use crate::{
driver::Bar0,
gpu::{
Architecture,
Chipset, //
},
};
mod tu102;
pub(crate) trait GpuHal {
/// Waits for GFW_BOOT completion if required by this hardware family.
fn wait_gfw_boot_completion(&self, bar: &Bar0) -> Result;
}
pub(super) fn gpu_hal(chipset: Chipset) -> &'static dyn GpuHal {
match chipset.arch() {
Architecture::Turing
| Architecture::Ampere
| Architecture::Ada
| Architecture::Hopper
| Architecture::BlackwellGB10x
| Architecture::BlackwellGB20x => tu102::TU102_HAL,
}
}

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0
//! GPU Firmware (`GFW`) support, a.k.a `devinit`.
//!
//! Upon reset, the GPU runs some firmware code from the BIOS to setup its core parameters. Most of
//! the GPU is considered unusable until this step is completed, so we must wait on it before
//! performing driver initialization.
//!
//! A clarification about devinit terminology: devinit is a sequence of register read/writes after
//! reset that performs tasks such as:
//! 1. Programming VRAM memory controller timings.
//! 2. Power sequencing.
//! 3. Clock and PLL configuration.
//! 4. Thermal management.
//!
//! devinit itself is a 'script' which is interpreted by an interpreter program typically running
//! on the PMU microcontroller.
//!
//! Note that the devinit sequence also needs to run during suspend/resume.
use kernel::{
io::{
poll::read_poll_timeout,
Io, //
},
prelude::*,
time::Delta, //
};
use crate::{
driver::Bar0,
regs, //
};
use super::GpuHal;
struct Tu102;
impl GpuHal for Tu102 {
/// Wait for the `GFW` (GPU firmware) boot completion signal (`GFW_BOOT`), or a 4 seconds
/// timeout.
///
/// Upon GPU reset, several microcontrollers (such as PMU, SEC2, GSP etc) run some firmware
/// code to setup its core parameters. Most of the GPU is considered unusable until this step
/// is completed, so it must be waited on very early during driver initialization.
///
/// The `GFW` code includes several components that need to execute before the driver loads.
/// These components are located in the VBIOS ROM and executed in a sequence on these different
/// microcontrollers. The devinit sequence typically runs on the PMU, and the FWSEC runs on the
/// GSP.
///
/// This function waits for a signal indicating that core initialization is complete. Before
/// this signal is received, little can be done with the GPU. This signal is set by the FWSEC
/// running on the GSP in Heavy-secured mode.
fn wait_gfw_boot_completion(&self, bar: &Bar0) -> Result {
// Before accessing the completion status in `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05`, we must
// first check `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK`. This is because
// `NV_PGC6_AON_SECURE_SCRATCH_GROUP_05` becomes accessible only after the secure firmware
// (FWSEC) lowers the privilege level to allow CPU (LS/Light-secured) access. We can only
// safely read the status register from CPU (LS/Light-secured) once the mask indicates
// that the privilege level has been lowered.
//
// TIMEOUT: arbitrarily large value. GFW starts running immediately after the GPU is put
// out of reset, and should complete in less time than that.
read_poll_timeout(
|| {
Ok(
// Check that FWSEC has lowered its protection level before reading the
// GFW_BOOT status.
bar.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK)
.read_protection_level0()
&& bar
.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT)
.completed(),
)
},
|&gfw_booted| gfw_booted,
Delta::from_millis(1),
Delta::from_secs(4),
)
.map(|_| ())
}
}
const TU102: Tu102 = Tu102;
pub(super) const TU102_HAL: &dyn GpuHal = &TU102;

View File

@ -17,7 +17,6 @@
mod falcon;
mod fb;
mod firmware;
mod gfw;
mod gpu;
mod gsp;
#[macro_use]