* [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding
@ 2026-04-27 6:56 Alexandre Courbot
2026-04-27 6:56 ` [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function Alexandre Courbot
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:56 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
Currently the GSP is left running and the WPR2 memory region untouched
when the driver is unbound. This is obviously not ideal for at least two
reasons:
- Probing requires setting up the WPR2 region, which cannot be done if
there is already one in place. Hence the current requirement to reset
the GPU (using e.g. `echo 1 >/sys/bus/pci/devices/.../reset`) before
the driver can be probed again after removal.
- The running GSP may still attempt to access shared memory regions
which the kernel might recycle.
On top of that, there is a nasty bug in the Blackwell VBIOS that
sometimes borks the GPU upon PCI reset, requiring a reboot. So relying
on the PCI reset to unload/reload Nova is really not practical here.
This series does what is needed to leave the GPU in a clean state after
unbind, for all currently supported GPUs. Blackwell support is trivial
and will be added alongside the Blackwell series [1] if this can be
merged first.
On top of addressing the feedback received on v3, this revision is a
considerable rework. The `warn_on_err` convenience macro looks like it
will require a bit more work, so it has been dropped as it brought a
very small improvement anyway.
Since this series touches the GSP boot code, which will also be changed
further by the Blackwell series [1], I have decided to proactively split
said boot code into a HAL. This will be helpful to avoid having the two
boot methods in the same Rust module and discriminated only by
conditionals; but this is also useful for this series in order to
abstract the unload bundle.
Indeed, as Sachiko correctly pointed out we cannot guarantee filesystem
access when the driver detaches. So this revision now prepares all the
firmware needed for unloading at load time and saves it into an unload
bundle that is executed when the driver unbinds.
The GSP HAL work might look a bit premature, but it will be fully
exploited by the Blackwell series and doing it now spares us the need to
move things around in it by putting everything in the right place from
the get-go.
This series is based on `drm-rust-next` with the GA100 support series
[2] and first patches of the Blackwell series [1] applied. A branch with
the series and its required dependencies is available at [3].
[1] https://lore.kernel.org/all/20260411024953.473149-1-jhubbard@nvidia.com/
[2] https://lore.kernel.org/all/20260417191359.1307434-1-ttabi@nvidia.com/
[3] https://github.com/Gnurou/linux/tree/b4/nova-unload
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Changes in v4:
- Remove `warn_on_err` macro as it isn't performing as expected and
distracts from the goal of the series.
- Add John's patch from the Blackwell series refactoring the Booter
Loader runner code.
- Add a GSP HAL and move the existing TU102/SEC2 boot sequence into it
in preparation for the Hopper/Blackwell FSP boot path.
- Prepare the firmware required for unloading at probe time and save it
into an unload bundle, as we cannot guarantee filesystem access at
unload time.
- Constrain `UNLOADING_GUEST_DRIVER`'s visibility to the parent module.
- Also write the sentinel value `0xff` into `mbox1` when running Booter
Unloader to align with OpenRM.
- Link to v3: https://patch.msgid.link/20260422-nova-unload-v3-0-1d2c81bd3ced@nvidia.com
Changes in v3:
- Disambiguate doccomment for `warn_on_err`.
- Test the correct bit instead of the whole register value to determine
that the GSP has stopped.
- Use an enum instead of a boolean to encode the power level when
shutting down the GSP.
- Add missing newline to `dev_err`.
- Add missing doccomments for new types.
- Use values from bindings instead of magic numbers.
- Remove the redundant `get_gsp_info` function.
- Better document Booter Unloader mailbox sentinel value, and check the
value of mbox0 upon return.
- Link to v2: https://patch.msgid.link/20260421-nova-unload-v2-0-2fe54963af8b@nvidia.com
Changes in v2:
- Rebase on top of `master` and remove unneeded/obsolete preparatory patches.
- Tidy up the imports of commands from the `fw` module in the `gsp` module.
- Link to v1: https://patch.msgid.link/20251216-nova-unload-v1-0-6a5d823be19d@nvidia.com
---
Alexandre Courbot (7):
gpu: nova-core: remove unneeded get_gsp_info proxy function
gpu: nova-core: do not import firmware commands into GSP command module
gpu: nova-core: split BAR acquisition in unbind()
gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading
gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close
gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL
gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding
John Hubbard (1):
gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run()
drivers/gpu/nova-core/firmware/booter.rs | 31 +-
drivers/gpu/nova-core/firmware/fwsec.rs | 1 -
drivers/gpu/nova-core/gpu.rs | 21 +-
drivers/gpu/nova-core/gsp.rs | 4 +
drivers/gpu/nova-core/gsp/boot.rs | 252 +++++-----------
drivers/gpu/nova-core/gsp/commands.rs | 71 +++--
drivers/gpu/nova-core/gsp/fw.rs | 4 +
drivers/gpu/nova-core/gsp/fw/commands.rs | 44 +++
drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs | 11 +
drivers/gpu/nova-core/gsp/hal.rs | 92 ++++++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 52 ++++
drivers/gpu/nova-core/gsp/hal/tu102.rs | 351 ++++++++++++++++++++++
drivers/gpu/nova-core/regs.rs | 5 +
13 files changed, 741 insertions(+), 198 deletions(-)
---
base-commit: 1affa624811b93a2df098c1d141a33c7c134d43f
change-id: 20251216-nova-unload-4029b3b76950
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
@ 2026-04-27 6:56 ` Alexandre Courbot
2026-04-27 13:16 ` Gary Guo
2026-04-27 6:56 ` [PATCH v4 2/8] gpu: nova-core: do not import firmware commands into GSP command module Alexandre Courbot
` (6 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:56 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
This function was useful before the generic command-queue send methods
got merged, but it is just boilerplate now. Replace it with the correct
sequence to queue the `GetGspStaticInfo` command directly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 2 +-
drivers/gpu/nova-core/gsp/commands.rs | 8 +-------
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index df105ef4b371..e838d61bef50 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -243,7 +243,7 @@ pub(crate) fn boot(
commands::wait_gsp_init_done(&self.cmdq)?;
// Obtain and display basic GPU information.
- let info = commands::get_gsp_info(&self.cmdq, bar)?;
+ let info = self.cmdq.send_command(bar, commands::GetGspStaticInfo)?;
match info.gpu_name() {
Ok(name) => dev_info!(pdev, "GPU name: {}\n", name),
Err(e) => dev_warn!(pdev, "GPU name unavailable: {:?}\n", e),
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index c89c7b57a751..e81a865050e0 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -18,7 +18,6 @@
};
use crate::{
- driver::Bar0,
gsp::{
cmdq::{
Cmdq,
@@ -176,7 +175,7 @@ pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
}
/// The `GetGspStaticInfo` command.
-struct GetGspStaticInfo;
+pub(crate) struct GetGspStaticInfo;
impl CommandToGsp for GetGspStaticInfo {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
@@ -232,8 +231,3 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
.map_err(GpuNameError::InvalidUtf8)
}
}
-
-/// Send the [`GetGspInfo`] command and awaits for its reply.
-pub(crate) fn get_gsp_info(cmdq: &Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
- cmdq.send_command(bar, GetGspStaticInfo)
-}
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 2/8] gpu: nova-core: do not import firmware commands into GSP command module
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
2026-04-27 6:56 ` [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function Alexandre Courbot
@ 2026-04-27 6:56 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 3/8] gpu: nova-core: split BAR acquisition in unbind() Alexandre Courbot
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:56 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
Importing all the firmware commands like we did is a bit confusing, as
the layer of a command type (fw or GSP) cannot be inferred from looking
at its name alone. Furthermore it makes it impossible to create commands
that have the same name as their firmware command.
Thus, stop importing all commands and refer to them from the `fw` module
instead.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/commands.rs | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index e81a865050e0..484aeb7dfd39 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -26,7 +26,7 @@
NoReply, //
},
fw::{
- commands::*,
+ self,
MsgFunction, //
},
},
@@ -47,12 +47,12 @@ pub(crate) fn new(pdev: &'a pci::Device<device::Bound>) -> Self {
impl<'a> CommandToGsp for SetSystemInfo<'a> {
const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
- type Command = GspSetSystemInfo;
+ type Command = fw::commands::GspSetSystemInfo;
type Reply = NoReply;
type InitError = Error;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- GspSetSystemInfo::init(self.pdev)
+ Self::Command::init(self.pdev)
}
}
@@ -99,12 +99,12 @@ pub(crate) fn new() -> Self {
impl CommandToGsp for SetRegistry {
const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
- type Command = PackedRegistryTable;
+ type Command = fw::commands::PackedRegistryTable;
type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- PackedRegistryTable::init(Self::NUM_ENTRIES as u32, self.variable_payload_len() as u32)
+ Self::Command::init(Self::NUM_ENTRIES as u32, self.variable_payload_len() as u32)
}
fn variable_payload_len(&self) -> usize {
@@ -112,22 +112,22 @@ fn variable_payload_len(&self) -> usize {
for i in 0..Self::NUM_ENTRIES {
key_size += self.entries[i].key.len() + 1; // +1 for NULL terminator
}
- Self::NUM_ENTRIES * size_of::<PackedRegistryEntry>() + key_size
+ Self::NUM_ENTRIES * size_of::<fw::commands::PackedRegistryEntry>() + key_size
}
fn init_variable_payload(
&self,
dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
- let string_data_start_offset =
- size_of::<PackedRegistryTable>() + Self::NUM_ENTRIES * size_of::<PackedRegistryEntry>();
+ let string_data_start_offset = size_of::<Self::Command>()
+ + Self::NUM_ENTRIES * size_of::<fw::commands::PackedRegistryEntry>();
// Array for string data.
let mut string_data = KVec::new();
for entry in self.entries.iter().take(Self::NUM_ENTRIES) {
dst.write_all(
- PackedRegistryEntry::new(
+ fw::commands::PackedRegistryEntry::new(
(string_data_start_offset + string_data.len()) as u32,
entry.value,
)
@@ -179,12 +179,12 @@ pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
impl CommandToGsp for GetGspStaticInfo {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Command = GspStaticConfigInfo;
+ type Command = fw::commands::GspStaticConfigInfo;
type Reply = GetGspStaticInfoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- GspStaticConfigInfo::init_zeroed()
+ Self::Command::init_zeroed()
}
}
@@ -195,7 +195,7 @@ pub(crate) struct GetGspStaticInfoReply {
impl MessageFromGsp for GetGspStaticInfoReply {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Message = GspStaticConfigInfo;
+ type Message = fw::commands::GspStaticConfigInfo;
type InitError = Infallible;
fn read(
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 3/8] gpu: nova-core: split BAR acquisition in unbind()
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
2026-04-27 6:56 ` [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function Alexandre Courbot
2026-04-27 6:56 ` [PATCH v4 2/8] gpu: nova-core: do not import firmware commands into GSP command module Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 4/8] gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading Alexandre Courbot
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
We are going to use the BAR to perform other teardown tasks, so move its
acquisition into a dedicated code block with a more meaningful error
message.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gpu.rs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 233a4bcec9fc..920783362251 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -319,10 +319,11 @@ pub(crate) fn new<'a>(
///
/// Note: This method must only be called from `Driver::unbind`.
pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) {
- kernel::warn_on!(self
- .bar
- .access(dev)
- .inspect(|bar| self.sysmem_flush.unregister(bar))
- .is_err());
+ let Ok(bar) = self.bar.access(dev) else {
+ dev_err!(dev, "failed to acquire bar for driver unbinding\n");
+ return;
+ };
+
+ self.sysmem_flush.unregister(bar);
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 4/8] gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
` (2 preceding siblings ...)
2026-04-27 6:57 ` [PATCH v4 3/8] gpu: nova-core: split BAR acquisition in unbind() Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 5/8] gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run() Alexandre Courbot
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
Currently, the GSP is left running after the driver is unbound. This is
not great for several reasons, notably that it can still access shared
memory areas that the kernel will now reclaim (especially problematic on
setups without an IOMMU).
Fix this by sending the `UNLOADING_GUEST_DRIVER` GSP command when
unbinding. This stops the GSP and lets us proceed with the rest of the
unbind sequence in a later patch.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gpu.rs | 10 ++++++
drivers/gpu/nova-core/gsp/boot.rs | 44 +++++++++++++++++++++++
drivers/gpu/nova-core/gsp/commands.rs | 43 ++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 4 +++
drivers/gpu/nova-core/gsp/fw/commands.rs | 44 +++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs | 11 ++++++
6 files changed, 156 insertions(+)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 920783362251..1dd25c93a67e 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -317,6 +317,9 @@ pub(crate) fn new<'a>(
/// Called when the corresponding [`Device`](device::Device) is unbound.
///
+ /// Prepares the GPU for unbinding by shutting down the GSP and unregistering the sysmem flush
+ /// memory page.
+ ///
/// Note: This method must only be called from `Driver::unbind`.
pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) {
let Ok(bar) = self.bar.access(dev) else {
@@ -324,6 +327,13 @@ pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) {
return;
};
+ // Do not return upon error as the sysmem flush page must be unregistered in any case.
+ let _ = self
+ .gsp
+ .unload(dev, bar, &self.gsp_falcon)
+ .inspect_err(|e| dev_err!(dev, "failed to unload GSP: {:?}\n", e));
+
+ // Unregister the sysmem flush page before the driver data is dropped.
self.sysmem_flush.unregister(bar);
}
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index e838d61bef50..d39da38c8918 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::{
+ bits,
device,
dma::Coherent,
io::poll::read_poll_timeout,
@@ -36,6 +37,7 @@
Chipset, //
},
gsp::{
+ cmdq::Cmdq,
commands,
sequencer::{
GspSequencer,
@@ -251,4 +253,46 @@ pub(crate) fn boot(
Ok(())
}
+
+ /// Shut down the GSP and wait until it is offline.
+ fn shutdown_gsp(
+ cmdq: &Cmdq,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<Gsp>,
+ mode: commands::PowerStateLevel,
+ ) -> Result<()> {
+ // Command to shut the GSP down.
+ cmdq.send_command(bar, commands::UnloadingGuestDriver::new(mode))?;
+
+ // Wait until GSP signals it is suspended.
+ const LIBOS_INTERRUPT_PROCESSOR_SUSPENDED: u32 = bits::bit_u32(31);
+ read_poll_timeout(
+ || Ok(gsp_falcon.read_mailbox0(bar)),
+ |&mb0| mb0 & LIBOS_INTERRUPT_PROCESSOR_SUSPENDED != 0,
+ Delta::from_millis(10),
+ Delta::from_secs(5),
+ )
+ .map(|_| ())
+ }
+
+ /// Attempts to unload the GSP firmware.
+ ///
+ /// This stops all activity on the GSP.
+ pub(crate) fn unload(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<Gsp>,
+ ) -> Result {
+ // Shut down the GSP.
+ Self::shutdown_gsp(
+ &self.cmdq,
+ bar,
+ gsp_falcon,
+ commands::PowerStateLevel::Level0,
+ )
+ .inspect_err(|e| dev_err!(dev, "Unload guest driver failed: {:?}\n", e))?;
+
+ Ok(())
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 484aeb7dfd39..8cb0ebdc0d06 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -231,3 +231,46 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
.map_err(GpuNameError::InvalidUtf8)
}
}
+
+pub(crate) use fw::commands::PowerStateLevel;
+
+/// The `UnloadingGuestDriver` command, used to shut down the GSP.
+///
+/// Only used within the `gsp` module.
+pub(super) struct UnloadingGuestDriver {
+ level: PowerStateLevel,
+}
+
+impl UnloadingGuestDriver {
+ /// Creates a new `UnloadingGuestDriver` command for the given [`PowerStateLevel`].
+ pub(super) fn new(level: PowerStateLevel) -> Self {
+ Self { level }
+ }
+}
+
+impl CommandToGsp for UnloadingGuestDriver {
+ const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
+ type Command = fw::commands::UnloadingGuestDriver;
+ type Reply = UnloadingGuestDriverReply;
+ type InitError = Infallible;
+
+ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
+ fw::commands::UnloadingGuestDriver::new(self.level)
+ }
+}
+
+/// The reply from the GSP to the [`UnloadingGuestDriver`] command.
+pub(super) struct UnloadingGuestDriverReply;
+
+impl MessageFromGsp for UnloadingGuestDriverReply {
+ const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
+ type InitError = Infallible;
+ type Message = ();
+
+ fn read(
+ _msg: &Self::Message,
+ _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError> {
+ Ok(UnloadingGuestDriverReply)
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 3245793bbe42..33c9f5860771 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -279,6 +279,7 @@ pub(crate) enum MsgFunction {
Nop = bindings::NV_VGPU_MSG_FUNCTION_NOP,
SetGuestSystemInfo = bindings::NV_VGPU_MSG_FUNCTION_SET_GUEST_SYSTEM_INFO,
SetRegistry = bindings::NV_VGPU_MSG_FUNCTION_SET_REGISTRY,
+ UnloadingGuestDriver = bindings::NV_VGPU_MSG_FUNCTION_UNLOADING_GUEST_DRIVER,
// Event codes
GspInitDone = bindings::NV_VGPU_MSG_EVENT_GSP_INIT_DONE,
@@ -323,6 +324,9 @@ fn try_from(value: u32) -> Result<MsgFunction> {
Ok(MsgFunction::SetGuestSystemInfo)
}
bindings::NV_VGPU_MSG_FUNCTION_SET_REGISTRY => Ok(MsgFunction::SetRegistry),
+ bindings::NV_VGPU_MSG_FUNCTION_UNLOADING_GUEST_DRIVER => {
+ Ok(MsgFunction::UnloadingGuestDriver)
+ }
// Event codes
bindings::NV_VGPU_MSG_EVENT_GSP_INIT_DONE => Ok(MsgFunction::GspInitDone),
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index db46276430be..e0f1cd92681c 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -129,3 +129,47 @@ unsafe impl AsBytes for GspStaticConfigInfo {}
// SAFETY: This struct only contains integer types for which all bit patterns
// are valid.
unsafe impl FromBytes for GspStaticConfigInfo {}
+
+/// Power level requested to the [`UnloadingGuestDriver`] command.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(u32)]
+#[expect(unused)]
+pub(crate) enum PowerStateLevel {
+ /// Full unload.
+ Level0 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0,
+ /// S3 (suspend to RAM).
+ Level3 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3,
+ /// Hibernate (suspend to disk).
+ Level7 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_7,
+}
+
+impl PowerStateLevel {
+ /// Returns `true` if this state represents a power management transition, i.e. some GPU state
+ /// must survive it (as opposed to a full unload).
+ pub(crate) fn is_power_transition(self) -> bool {
+ self != PowerStateLevel::Level0
+ }
+}
+
+/// Payload of the `UnloadingGuestDriver` command and message.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, Zeroable)]
+pub(crate) struct UnloadingGuestDriver(bindings::rpc_unloading_guest_driver_v1F_07);
+
+impl UnloadingGuestDriver {
+ pub(crate) fn new(level: PowerStateLevel) -> Self {
+ Self(bindings::rpc_unloading_guest_driver_v1F_07 {
+ bInPMTransition: u8::from(level.is_power_transition()),
+ bGc6Entering: 0,
+ newLevel: level as u32,
+ ..Zeroable::zeroed()
+ })
+ }
+}
+
+// SAFETY: Padding is explicit and will not contain uninitialized data.
+unsafe impl AsBytes for UnloadingGuestDriver {}
+
+// SAFETY: This struct only contains integer types for which all bit patterns
+// are valid.
+unsafe impl FromBytes for UnloadingGuestDriver {}
diff --git a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
index 334e8be5fde8..f82ed097b283 100644
--- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
+++ b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
@@ -30,6 +30,9 @@ fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0: u32 = 0;
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3: u32 = 3;
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_7: u32 = 7;
pub const NV_VGPU_MSG_SIGNATURE_VALID: u32 = 1129337430;
pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS2: u32 = 0;
pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS3_BAREMETAL: u32 = 23068672;
@@ -880,6 +883,14 @@ fn default() -> Self {
}
}
#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct rpc_unloading_guest_driver_v1F_07 {
+ pub bInPMTransition: u8_,
+ pub bGc6Entering: u8_,
+ pub __bindgen_padding_0: [u8; 2usize],
+ pub newLevel: u32_,
+}
+#[repr(C)]
#[derive(Debug, Default, MaybeZeroable)]
pub struct rpc_run_cpu_sequencer_v17_00 {
pub bufferSizeDWord: u32_,
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 5/8] gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run()
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
` (3 preceding siblings ...)
2026-04-27 6:57 ` [PATCH v4 4/8] gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close Alexandre Courbot
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
Move the SEC2 reset/load/boot sequence into a BooterFirmware::run()
method, and call it from a thin run_booter() helper on Gsp. This is a
pure refactoring with no behavior change, done in preparation for adding
an alternative FSP boot path.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/firmware/booter.rs | 30 ++++++++++++++++++++++
drivers/gpu/nova-core/gsp/boot.rs | 43 +++++++++++++++-----------------
2 files changed, 50 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/booter.rs b/drivers/gpu/nova-core/firmware/booter.rs
index de2a4536b532..6a41690e72c6 100644
--- a/drivers/gpu/nova-core/firmware/booter.rs
+++ b/drivers/gpu/nova-core/firmware/booter.rs
@@ -8,6 +8,7 @@
use kernel::{
device,
+ dma::Coherent,
prelude::*,
transmute::FromBytes, //
};
@@ -396,6 +397,35 @@ pub(crate) fn new(
ucode: ucode_signed,
})
}
+
+ /// Load and run the booter firmware on SEC2.
+ ///
+ /// Resets SEC2, loads this firmware image, then boots with the WPR metadata
+ /// address passed via the SEC2 mailboxes.
+ pub(crate) fn run<T>(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ sec2_falcon: &Falcon<Sec2>,
+ wpr_meta: &Coherent<T>,
+ ) -> Result {
+ sec2_falcon.reset(bar)?;
+ sec2_falcon.load(dev, bar, self)?;
+ let wpr_handle = wpr_meta.dma_handle();
+ let (mbox0, mbox1) = sec2_falcon.boot(
+ bar,
+ Some(wpr_handle as u32),
+ Some((wpr_handle >> 32) as u32),
+ )?;
+ dev_dbg!(dev, "SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
+
+ if mbox0 != 0 {
+ dev_err!(dev, "Booter-load failed with error {:#x}\n", mbox0);
+ return Err(ENODEV);
+ }
+
+ Ok(())
+ }
}
impl FalconDmaLoadable for BooterFirmware {
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index d39da38c8918..e75c4b235aab 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -136,6 +136,25 @@ fn run_fwsec_frts(
}
}
+ /// Load and run the booter firmware.
+ fn run_booter(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ sec2_falcon: &Falcon<Sec2>,
+ wpr_meta: &Coherent<GspFwWprMeta>,
+ ) -> Result {
+ BooterFirmware::new(
+ dev,
+ BooterKind::Loader,
+ chipset,
+ FIRMWARE_VERSION,
+ sec2_falcon,
+ bar,
+ )?
+ .run(dev, bar, sec2_falcon, wpr_meta)
+ }
+
/// Attempt to boot the GSP.
///
/// This is a GPU-dependent and complex procedure that involves loading firmware files from
@@ -173,15 +192,6 @@ pub(crate) fn boot(
Self::run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, &fb_layout)?;
}
- let booter_loader = BooterFirmware::new(
- dev,
- BooterKind::Loader,
- chipset,
- FIRMWARE_VERSION,
- sec2_falcon,
- bar,
- )?;
-
let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
self.cmdq
@@ -203,20 +213,7 @@ pub(crate) fn boot(
"Using SEC2 to load and run the booter_load firmware...\n"
);
- sec2_falcon.reset(bar)?;
- sec2_falcon.load(dev, bar, &booter_loader)?;
- let wpr_handle = wpr_meta.dma_handle();
- let (mbox0, mbox1) = sec2_falcon.boot(
- bar,
- Some(wpr_handle as u32),
- Some((wpr_handle >> 32) as u32),
- )?;
- dev_dbg!(pdev, "SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
-
- if mbox0 != 0 {
- dev_err!(pdev, "Booter-load failed with error {:#x}\n", mbox0);
- return Err(ENODEV);
- }
+ Self::run_booter(dev, bar, chipset, sec2_falcon, &wpr_meta)?;
gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
` (4 preceding siblings ...)
2026-04-27 6:57 ` [PATCH v4 5/8] gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run() Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 7/8] gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 8/8] gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding Alexandre Courbot
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
Some parts of the GSP boot process are chip-specific actions, whereas
others (like sending the initial post-boot messages) deal directly with
the working GSP.
Reorganize the boot code a bit so the chipset-specific parts are clumped
together, which will make their extraction into a HAL easier.
This has no effect on the GSP boot process.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index e75c4b235aab..2e1401cd6171 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -187,18 +187,13 @@ pub(crate) fn boot(
let fb_layout = FbLayout::new(chipset, bar, &gsp_fw)?;
dev_dbg!(dev, "{:#x?}\n", fb_layout);
+ let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
+
// FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
if !fb_layout.frts.is_empty() {
Self::run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, &fb_layout)?;
}
- let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
-
- self.cmdq
- .send_command_no_wait(bar, commands::SetSystemInfo::new(pdev))?;
- self.cmdq
- .send_command_no_wait(bar, commands::SetRegistry::new())?;
-
gsp_falcon.reset(bar)?;
let libos_handle = self.libos.dma_handle();
let (mbox0, mbox1) = gsp_falcon.boot(
@@ -227,6 +222,11 @@ pub(crate) fn boot(
dev_dbg!(pdev, "RISC-V active? {}\n", gsp_falcon.is_riscv_active(bar),);
+ self.cmdq
+ .send_command_no_wait(bar, commands::SetSystemInfo::new(pdev))?;
+ self.cmdq
+ .send_command_no_wait(bar, commands::SetRegistry::new())?;
+
// Create and run the GSP sequencer.
let seq_params = GspSequencerParams {
bootloader_app_version: gsp_fw.bootloader.app_version,
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 7/8] gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
` (5 preceding siblings ...)
2026-04-27 6:57 ` [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 8/8] gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding Alexandre Courbot
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
Booting the GSP is done differently depending on the architecture. Move
the parts that are chipset-specific under a HAL.
This does not change much at the moment, since the differences between
Turing and Ampere are rather benign, but will become critical to
properly support the FSP boot process used by Hopper and Blackwell.
The Hopper/Blackwell support is not merged yet, so their HAL is a stub
for now.
This patch is intended to be a mechanical code extraction with no
behavioral changes.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 1 +
drivers/gpu/nova-core/gsp/boot.rs | 179 +++------------------------
drivers/gpu/nova-core/gsp/hal.rs | 73 +++++++++++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 49 ++++++++
drivers/gpu/nova-core/gsp/hal/tu102.rs | 216 +++++++++++++++++++++++++++++++++
5 files changed, 353 insertions(+), 165 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index ba5b7f990031..38378f104068 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
mod boot;
+mod hal;
use kernel::{
debugfs,
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 2e1401cd6171..59c1b4d030ae 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -5,7 +5,6 @@
device,
dma::Coherent,
io::poll::read_poll_timeout,
- io::Io,
pci,
prelude::*,
time::Delta, //
@@ -20,141 +19,18 @@
},
fb::FbLayout,
firmware::{
- booter::{
- BooterFirmware,
- BooterKind, //
- },
- fwsec::{
- bootloader::FwsecFirmwareWithBl,
- FwsecCommand,
- FwsecFirmware, //
- },
gsp::GspFirmware,
FIRMWARE_VERSION, //
},
- gpu::{
- Architecture,
- Chipset, //
- },
+ gpu::Chipset,
gsp::{
cmdq::Cmdq,
commands,
- sequencer::{
- GspSequencer,
- GspSequencerParams, //
- },
GspFwWprMeta, //
},
- regs,
- vbios::Vbios,
};
impl super::Gsp {
- /// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
- /// created the WPR2 region.
- fn run_fwsec_frts(
- dev: &device::Device<device::Bound>,
- chipset: Chipset,
- falcon: &Falcon<Gsp>,
- bar: &Bar0,
- bios: &Vbios,
- fb_layout: &FbLayout,
- ) -> Result<()> {
- // Check that the WPR2 region does not already exists - if it does, we cannot run
- // FWSEC-FRTS until the GPU is reset.
- if bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound() != 0 {
- dev_err!(
- dev,
- "WPR2 region already exists - GPU needs to be reset to proceed\n"
- );
- return Err(EBUSY);
- }
-
- // FWSEC-FRTS will create the WPR2 region.
- let fwsec_frts = FwsecFirmware::new(
- dev,
- falcon,
- bar,
- bios,
- FwsecCommand::Frts {
- frts_addr: fb_layout.frts.start,
- frts_size: fb_layout.frts.len(),
- },
- )?;
-
- if chipset.needs_fwsec_bootloader() {
- let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
- // Load and run the bootloader, which will load FWSEC-FRTS and run it.
- fwsec_frts_bl.run(dev, falcon, bar)?;
- } else {
- // Load and run FWSEC-FRTS directly.
- fwsec_frts.run(dev, falcon, bar)?;
- }
-
- // SCRATCH_E contains the error code for FWSEC-FRTS.
- let frts_status = bar
- .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR)
- .frts_err_code();
- if frts_status != 0 {
- dev_err!(
- dev,
- "FWSEC-FRTS returned with error code {:#x}\n",
- frts_status
- );
-
- return Err(EIO);
- }
-
- // Check that the WPR2 region has been created as we requested.
- let (wpr2_lo, wpr2_hi) = (
- bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO).lower_bound(),
- bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound(),
- );
-
- match (wpr2_lo, wpr2_hi) {
- (_, 0) => {
- dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n");
-
- Err(EIO)
- }
- (wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => {
- dev_err!(
- dev,
- "WPR2 region created at unexpected address {:#x}; expected {:#x}\n",
- wpr2_lo,
- fb_layout.frts.start,
- );
-
- Err(EIO)
- }
- (wpr2_lo, wpr2_hi) => {
- dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_lo, wpr2_hi);
- dev_dbg!(dev, "GPU instance built\n");
-
- Ok(())
- }
- }
- }
-
- /// Load and run the booter firmware.
- fn run_booter(
- dev: &device::Device<device::Bound>,
- bar: &Bar0,
- chipset: Chipset,
- sec2_falcon: &Falcon<Sec2>,
- wpr_meta: &Coherent<GspFwWprMeta>,
- ) -> Result {
- BooterFirmware::new(
- dev,
- BooterKind::Loader,
- chipset,
- FIRMWARE_VERSION,
- sec2_falcon,
- bar,
- )?
- .run(dev, bar, sec2_falcon, wpr_meta)
- }
-
/// Attempt to boot the GSP.
///
/// This is a GPU-dependent and complex procedure that involves loading firmware files from
@@ -163,24 +39,15 @@ fn run_booter(
///
/// Upon return, the GSP is up and running, and its runtime object given as return value.
pub(crate) fn boot(
- self: Pin<&mut Self>,
+ mut self: Pin<&mut Self>,
pdev: &pci::Device<device::Bound>,
bar: &Bar0,
chipset: Chipset,
gsp_falcon: &Falcon<Gsp>,
sec2_falcon: &Falcon<Sec2>,
) -> Result {
- // The FSP boot process of Hopper+ is not supported for now.
- if matches!(
- chipset.arch(),
- Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x
- ) {
- return Err(ENOTSUPP);
- }
-
let dev = pdev.as_ref();
-
- let bios = Vbios::new(dev, bar)?;
+ let hal = super::hal::gsp_hal(chipset);
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, FIRMWARE_VERSION), GFP_KERNEL)?;
@@ -189,30 +56,21 @@ pub(crate) fn boot(
let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
- // FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
- if !fb_layout.frts.is_empty() {
- Self::run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, &fb_layout)?;
- }
-
- gsp_falcon.reset(bar)?;
- let libos_handle = self.libos.dma_handle();
- let (mbox0, mbox1) = gsp_falcon.boot(
+ // Perform the chipset-specific boot sequence.
+ hal.boot(
+ self.as_mut(),
+ dev,
bar,
- Some(libos_handle as u32),
- Some((libos_handle >> 32) as u32),
+ chipset,
+ &fb_layout,
+ &wpr_meta,
+ gsp_falcon,
+ sec2_falcon,
)?;
- dev_dbg!(pdev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
-
- dev_dbg!(
- pdev,
- "Using SEC2 to load and run the booter_load firmware...\n"
- );
-
- Self::run_booter(dev, bar, chipset, sec2_falcon, &wpr_meta)?;
gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);
- // Poll for RISC-V to become active before running sequencer
+ // Poll for RISC-V to become active before continuing.
read_poll_timeout(
|| Ok(gsp_falcon.is_riscv_active(bar)),
|val: &bool| *val,
@@ -227,16 +85,7 @@ pub(crate) fn boot(
self.cmdq
.send_command_no_wait(bar, commands::SetRegistry::new())?;
- // Create and run the GSP sequencer.
- let seq_params = GspSequencerParams {
- bootloader_app_version: gsp_fw.bootloader.app_version,
- libos_dma_handle: libos_handle,
- gsp_falcon,
- sec2_falcon,
- dev: pdev.as_ref().into(),
- bar,
- };
- GspSequencer::run(&self.cmdq, seq_params)?;
+ hal.post_boot(self.as_mut(), dev, bar, &gsp_fw, gsp_falcon, sec2_falcon)?;
// Wait until GSP is fully initialized.
commands::wait_gsp_init_done(&self.cmdq)?;
diff --git a/drivers/gpu/nova-core/gsp/hal.rs b/drivers/gpu/nova-core/gsp/hal.rs
new file mode 100644
index 000000000000..4d8c1998f4cf
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/hal.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+
+mod gh100;
+mod tu102;
+
+use kernel::prelude::*;
+
+use kernel::{
+ device,
+ dma::Coherent, //
+};
+
+use crate::{
+ driver::Bar0,
+ falcon::{
+ gsp::Gsp as GspEngine,
+ sec2::Sec2,
+ Falcon, //
+ },
+ fb::FbLayout,
+ firmware::gsp::GspFirmware,
+ gpu::{
+ Architecture,
+ Chipset, //
+ },
+ gsp::{
+ Gsp,
+ GspFwWprMeta, //
+ },
+};
+
+/// Trait implemented by GSP HALs.
+pub(super) trait GspHal: Send {
+ /// Performs the GSP boot process, loading and running the required firmwares as needed.
+ #[allow(clippy::too_many_arguments)]
+ fn boot(
+ &self,
+ gsp: Pin<&mut Gsp>,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ fb_layout: &FbLayout,
+ wpr_meta: &Coherent<GspFwWprMeta>,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result;
+
+ /// Performs HAL-specific post-GSP boot tasks.
+ ///
+ /// This method is called by the GSP boot code after the GSP is confirmed to be running, and
+ /// after the initialization commands have been pushed onto its queue.
+ fn post_boot(
+ &self,
+ _gsp: Pin<&mut Gsp>,
+ _dev: &device::Device<device::Bound>,
+ _bar: &Bar0,
+ _gsp_fw: &GspFirmware,
+ _gsp_falcon: &Falcon<GspEngine>,
+ _sec2_falcon: &Falcon<Sec2>,
+ ) -> Result {
+ Ok(())
+ }
+}
+
+/// Returns the GSP HAL to be used for `chipset`.
+pub(super) fn gsp_hal(chipset: Chipset) -> &'static dyn GspHal {
+ match chipset.arch() {
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada => tu102::TU102_HAL,
+ Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+ gh100::GH100_HAL
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
new file mode 100644
index 000000000000..9d7e9f4454b1
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::prelude::*;
+
+use kernel::{
+ device,
+ dma::Coherent, //
+};
+
+use crate::{
+ driver::Bar0,
+ falcon::{
+ gsp::Gsp as GspEngine,
+ sec2::Sec2,
+ Falcon, //
+ },
+ fb::FbLayout,
+ gpu::Chipset,
+ gsp::{
+ hal::GspHal,
+ Gsp,
+ GspFwWprMeta, //
+ },
+};
+
+struct Gh100;
+
+impl GspHal for Gh100 {
+ /// Boot GSP via FSP Chain of Trust (Hopper/Blackwell+ path).
+ ///
+ /// This path uses FSP to establish a chain of trust and boot GSP-FMC. FSP handles
+ /// the GSP boot internally - no manual GSP reset/boot is needed.
+ fn boot(
+ &self,
+ _gsp: Pin<&mut Gsp>,
+ _dev: &device::Device<device::Bound>,
+ _bar: &Bar0,
+ _chipset: Chipset,
+ _fb_layout: &FbLayout,
+ _wpr_meta: &Coherent<GspFwWprMeta>,
+ _gsp_falcon: &Falcon<GspEngine>,
+ _sec2_falcon: &Falcon<Sec2>,
+ ) -> Result {
+ Err(ENOTSUPP)
+ }
+}
+
+const GH100: Gh100 = Gh100;
+pub(super) const GH100_HAL: &dyn GspHal = &GH100;
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
new file mode 100644
index 000000000000..d5da23cd8c90
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::prelude::*;
+
+use kernel::{
+ device,
+ dma::Coherent,
+ io::Io, //
+};
+
+use crate::{
+ driver::Bar0,
+ falcon::{
+ gsp::Gsp as GspEngine,
+ sec2::Sec2,
+ Falcon, //
+ },
+ fb::FbLayout,
+ firmware::{
+ booter::{
+ BooterFirmware,
+ BooterKind, //
+ },
+ fwsec::{
+ bootloader::FwsecFirmwareWithBl,
+ FwsecCommand,
+ FwsecFirmware, //
+ },
+ gsp::GspFirmware,
+ FIRMWARE_VERSION, //
+ },
+ gpu::Chipset,
+ gsp::{
+ hal::GspHal,
+ sequencer::{
+ GspSequencer,
+ GspSequencerParams, //
+ },
+ Gsp,
+ GspFwWprMeta, //
+ },
+ regs,
+ vbios::Vbios, //
+};
+
+/// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
+/// created the WPR2 region.
+fn run_fwsec_frts(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ falcon: &Falcon<GspEngine>,
+ bar: &Bar0,
+ bios: &Vbios,
+ fb_layout: &FbLayout,
+) -> Result<()> {
+ // Check that the WPR2 region does not already exist - if it does, we cannot run
+ // FWSEC-FRTS until the GPU is reset.
+ if bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound() != 0 {
+ dev_err!(
+ dev,
+ "WPR2 region already exists - GPU needs to be reset to proceed\n"
+ );
+ return Err(EBUSY);
+ }
+
+ // FWSEC-FRTS will create the WPR2 region.
+ let fwsec_frts = FwsecFirmware::new(
+ dev,
+ falcon,
+ bar,
+ bios,
+ FwsecCommand::Frts {
+ frts_addr: fb_layout.frts.start,
+ frts_size: fb_layout.frts.len(),
+ },
+ )?;
+
+ if chipset.needs_fwsec_bootloader() {
+ let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
+ // Load and run the bootloader, which will load FWSEC-FRTS and run it.
+ fwsec_frts_bl.run(dev, falcon, bar)?;
+ } else {
+ // Load and run FWSEC-FRTS directly.
+ fwsec_frts.run(dev, falcon, bar)?;
+ }
+
+ // SCRATCH_E contains the error code for FWSEC-FRTS.
+ let frts_status = bar
+ .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR)
+ .frts_err_code();
+ if frts_status != 0 {
+ dev_err!(
+ dev,
+ "FWSEC-FRTS returned with error code {:#x}\n",
+ frts_status
+ );
+
+ return Err(EIO);
+ }
+
+ // Check that the WPR2 region has been created as we requested.
+ let (wpr2_lo, wpr2_hi) = (
+ bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO).lower_bound(),
+ bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound(),
+ );
+
+ match (wpr2_lo, wpr2_hi) {
+ (_, 0) => {
+ dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n");
+
+ Err(EIO)
+ }
+ (wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => {
+ dev_err!(
+ dev,
+ "WPR2 region created at unexpected address {:#x}; expected {:#x}\n",
+ wpr2_lo,
+ fb_layout.frts.start,
+ );
+
+ Err(EIO)
+ }
+ (wpr2_lo, wpr2_hi) => {
+ dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_lo, wpr2_hi);
+ dev_dbg!(dev, "GPU instance built\n");
+
+ Ok(())
+ }
+ }
+}
+
+/// Load and run the booter firmware.
+fn run_booter(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ sec2_falcon: &Falcon<Sec2>,
+ wpr_meta: &Coherent<GspFwWprMeta>,
+) -> Result {
+ BooterFirmware::new(
+ dev,
+ BooterKind::Loader,
+ chipset,
+ FIRMWARE_VERSION,
+ sec2_falcon,
+ bar,
+ )?
+ .run(dev, bar, sec2_falcon, wpr_meta)
+}
+
+struct Tu102;
+
+impl GspHal for Tu102 {
+ fn boot(
+ &self,
+ gsp: Pin<&mut Gsp>,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ fb_layout: &FbLayout,
+ wpr_meta: &Coherent<GspFwWprMeta>,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result {
+ let bios = Vbios::new(dev, bar)?;
+
+ // FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
+ if !fb_layout.frts.is_empty() {
+ run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, fb_layout)?;
+ }
+
+ gsp_falcon.reset(bar)?;
+ let libos_handle = gsp.libos.dma_handle();
+ let (mbox0, mbox1) = gsp_falcon.boot(
+ bar,
+ Some(libos_handle as u32),
+ Some((libos_handle >> 32) as u32),
+ )?;
+ dev_dbg!(dev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
+
+ dev_dbg!(
+ dev,
+ "Using SEC2 to load and run the booter_load firmware...\n"
+ );
+
+ run_booter(dev, bar, chipset, sec2_falcon, wpr_meta)?;
+
+ Ok(())
+ }
+
+ fn post_boot(
+ &self,
+ gsp: Pin<&mut Gsp>,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_fw: &GspFirmware,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result {
+ // Create and run the GSP sequencer.
+ let seq_params = GspSequencerParams {
+ bootloader_app_version: gsp_fw.bootloader.app_version,
+ libos_dma_handle: gsp.libos.dma_handle(),
+ gsp_falcon,
+ sec2_falcon,
+ dev: dev.into(),
+ bar,
+ };
+ GspSequencer::run(&gsp.cmdq, seq_params)?;
+
+ Ok(())
+ }
+}
+
+const TU102: Tu102 = Tu102;
+pub(super) const TU102_HAL: &dyn GspHal = &TU102;
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 8/8] gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
` (6 preceding siblings ...)
2026-04-27 6:57 ` [PATCH v4 7/8] gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL Alexandre Courbot
@ 2026-04-27 6:57 ` Alexandre Courbot
7 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-04-27 6:57 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux,
Alexandre Courbot
When probing the driver, the FWSEC-FRTS firmware creates a WPR2 secure
memory region to store the GSP firmware, and the Booter Loader loads and
starts that firmware into the GSP, making it run in RISC-V mode.
These operations need to be reverted upon unloading, particularly the
WPR2 secure region creation, as its presence prevents the driver from
subsequently probing.
Thus, prepare the Booter Unloader and FWSEC-SB firmwares when booting
the GSP, so they can be executed at unbind time to put the GPU into a
state where it can be probed again.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware/booter.rs | 1 -
drivers/gpu/nova-core/firmware/fwsec.rs | 1 -
drivers/gpu/nova-core/gpu.rs | 2 +-
drivers/gpu/nova-core/gsp.rs | 3 +
drivers/gpu/nova-core/gsp/boot.rs | 18 +++-
drivers/gpu/nova-core/gsp/hal.rs | 21 ++++-
drivers/gpu/nova-core/gsp/hal/gh100.rs | 7 +-
drivers/gpu/nova-core/gsp/hal/tu102.rs | 141 ++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/regs.rs | 5 ++
9 files changed, 188 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/booter.rs b/drivers/gpu/nova-core/firmware/booter.rs
index 6a41690e72c6..6f421906c272 100644
--- a/drivers/gpu/nova-core/firmware/booter.rs
+++ b/drivers/gpu/nova-core/firmware/booter.rs
@@ -281,7 +281,6 @@ fn new_booter(data: &[u8]) -> Result<Self> {
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum BooterKind {
Loader,
- #[expect(unused)]
Unloader,
}
diff --git a/drivers/gpu/nova-core/firmware/fwsec.rs b/drivers/gpu/nova-core/firmware/fwsec.rs
index 8810cb49db67..4108f28cd338 100644
--- a/drivers/gpu/nova-core/firmware/fwsec.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec.rs
@@ -144,7 +144,6 @@ pub(crate) enum FwsecCommand {
/// image into it.
Frts { frts_addr: u64, frts_size: u64 },
/// Asks [`FwsecFirmware`] to load pre-OS apps on the PMU.
- #[expect(dead_code)]
Sb,
}
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 1dd25c93a67e..7cf994ba2fb0 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -330,7 +330,7 @@ pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) {
// Do not return upon error as the sysmem flush page must be unregistered in any case.
let _ = self
.gsp
- .unload(dev, bar, &self.gsp_falcon)
+ .unload(dev, bar, &self.gsp_falcon, &self.sec2_falcon)
.inspect_err(|e| dev_err!(dev, "failed to unload GSP: {:?}\n", e));
// Unregister the sysmem flush page before the driver data is dropped.
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 38378f104068..58b917172efb 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -126,6 +126,8 @@ pub(crate) struct Gsp {
pub(crate) cmdq: Cmdq,
/// RM arguments.
rmargs: Coherent<GspArgumentsPadded>,
+ /// Ready-to-run GSP unload bundle, if any.
+ unload: Option<KBox<dyn hal::UnloadBundle>>,
}
impl Gsp {
@@ -181,6 +183,7 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
dir.read_binary_file(c"logrm", &logs.logrm.0);
})
},
+ unload: None,
}))
})
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 59c1b4d030ae..c8555d76b3a2 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -56,8 +56,8 @@ pub(crate) fn boot(
let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
- // Perform the chipset-specific boot sequence.
- hal.boot(
+ // Perform the chipset-specific boot sequence, and retrieve the unload bundle.
+ let unload_bundle = hal.boot(
self.as_mut(),
dev,
bar,
@@ -67,6 +67,7 @@ pub(crate) fn boot(
gsp_falcon,
sec2_falcon,
)?;
+ *self.as_mut().project().unload = unload_bundle;
gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);
@@ -129,6 +130,7 @@ pub(crate) fn unload(
dev: &device::Device<device::Bound>,
bar: &Bar0,
gsp_falcon: &Falcon<Gsp>,
+ sec2_falcon: &Falcon<Sec2>,
) -> Result {
// Shut down the GSP.
Self::shutdown_gsp(
@@ -139,6 +141,18 @@ pub(crate) fn unload(
)
.inspect_err(|e| dev_err!(dev, "Unload guest driver failed: {:?}\n", e))?;
+ // With the GSP shut down, reset the GSP so it can be restarted.
+ if let Some(unload_bundle) = self.unload.as_ref() {
+ unload_bundle.run(dev, bar, gsp_falcon, sec2_falcon)?;
+ } else {
+ dev_warn!(
+ dev,
+ "Unload bundle is missing, GSP won't be properly reset.\n"
+ );
+ }
+
+ dev_info!(dev, "successfully unloaded\n");
+
Ok(())
}
}
diff --git a/drivers/gpu/nova-core/gsp/hal.rs b/drivers/gpu/nova-core/gsp/hal.rs
index 4d8c1998f4cf..d9c324226dda 100644
--- a/drivers/gpu/nova-core/gsp/hal.rs
+++ b/drivers/gpu/nova-core/gsp/hal.rs
@@ -29,9 +29,28 @@
},
};
+/// Trait for types containing the resources and code required to fully reset the GSP.
+///
+/// The GSP unload code might run in a situation where we cannot load firmware dynamically (e.g.
+/// because we are in shutdown and the file system is not accessible anymore). Thus, the firmware
+/// required for unloading is prepared at load time, and stored here until it needs to be run.
+pub(super) trait UnloadBundle: Send {
+ /// Performs the steps required to properly reset the GSP after it has been stopped.
+ fn run(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result;
+}
+
/// Trait implemented by GSP HALs.
pub(super) trait GspHal: Send {
/// Performs the GSP boot process, loading and running the required firmwares as needed.
+ ///
+ /// Upon success, returns the [`UnloadBundle`] to be run (if any) in order to properly reset the
+ /// GSP after it has been stopped.
#[allow(clippy::too_many_arguments)]
fn boot(
&self,
@@ -43,7 +62,7 @@ fn boot(
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
- ) -> Result;
+ ) -> Result<Option<KBox<dyn UnloadBundle>>>;
/// Performs HAL-specific post-GSP boot tasks.
///
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index 9d7e9f4454b1..51e1099cda0d 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -17,7 +17,10 @@
fb::FbLayout,
gpu::Chipset,
gsp::{
- hal::GspHal,
+ hal::{
+ GspHal,
+ UnloadBundle, //
+ },
Gsp,
GspFwWprMeta, //
},
@@ -40,7 +43,7 @@ fn boot(
_wpr_meta: &Coherent<GspFwWprMeta>,
_gsp_falcon: &Falcon<GspEngine>,
_sec2_falcon: &Falcon<Sec2>,
- ) -> Result {
+ ) -> Result<Option<KBox<dyn UnloadBundle>>> {
Err(ENOTSUPP)
}
}
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index d5da23cd8c90..c6c947696ba8 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -31,7 +31,10 @@
},
gpu::Chipset,
gsp::{
- hal::GspHal,
+ hal::{
+ GspHal,
+ UnloadBundle, //
+ },
sequencer::{
GspSequencer,
GspSequencerParams, //
@@ -43,6 +46,124 @@
vbios::Vbios, //
};
+// A ready-to-run FWSEC unload firmware.
+//
+// Since there are two variants of the prepared firmware (with and without a bootloader), this type
+// abstracts the difference.
+enum FwsecUnloadFirmware {
+ WithoutBl(FwsecFirmware),
+ WithBl(FwsecFirmwareWithBl),
+}
+
+impl FwsecUnloadFirmware {
+ /// Loads the FWSEC SB firmware, as well as its bootloader if `chipset` requires it.
+ fn new(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ bios: &Vbios,
+ gsp_falcon: &Falcon<GspEngine>,
+ ) -> Result<Self> {
+ let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bar, bios, FwsecCommand::Sb)?;
+
+ Ok(if chipset.needs_fwsec_bootloader() {
+ Self::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?)
+ } else {
+ Self::WithoutBl(fwsec_sb)
+ })
+ }
+
+ /// Runs the FWSEC SB firmware.
+ fn run(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ ) -> Result<()> {
+ match self {
+ Self::WithoutBl(fw) => fw.run(dev, gsp_falcon, bar),
+ Self::WithBl(fw) => fw.run(dev, gsp_falcon, bar),
+ }
+ }
+}
+
+// Contains the firmware required to fully reset GSP on chipsets where the GSP is started using
+// FWSEC/Booter.
+pub(super) struct Sec2UnloadBundle {
+ fwsec_sb: FwsecUnloadFirmware,
+ booter_unloader: BooterFirmware,
+}
+
+impl Sec2UnloadBundle {
+ /// Load and prepare the resources required to properly reset the GSP after it has been stopped.
+ fn build(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ bios: &Vbios,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result<KBox<dyn UnloadBundle>> {
+ KBox::new(
+ Self {
+ fwsec_sb: FwsecUnloadFirmware::new(dev, bar, chipset, bios, gsp_falcon)?,
+ booter_unloader: BooterFirmware::new(
+ dev,
+ BooterKind::Unloader,
+ chipset,
+ FIRMWARE_VERSION,
+ sec2_falcon,
+ bar,
+ )?,
+ },
+ GFP_KERNEL,
+ )
+ .map(|b| b as KBox<dyn UnloadBundle>)
+ .map_err(Into::into)
+ }
+}
+
+impl UnloadBundle for Sec2UnloadBundle {
+ fn run(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ sec2_falcon: &Falcon<Sec2>,
+ ) -> Result<()> {
+ // Run FWSEC-SB to reset the GSP falcon to its pre-libos state.
+ self.fwsec_sb.run(dev, bar, gsp_falcon)?;
+
+ // Remove WPR2 region if set.
+ let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);
+ if wpr2_hi.is_wpr2_set() {
+ sec2_falcon.reset(bar)?;
+ sec2_falcon.load(dev, bar, &self.booter_unloader)?;
+
+ // Sentinel value to confirm that Booter Unloader has run.
+ const MAILBOX_SENTINEL: u32 = 0xff;
+ let (mbox0, _) =
+ sec2_falcon.boot(bar, Some(MAILBOX_SENTINEL), Some(MAILBOX_SENTINEL))?;
+ if mbox0 != 0 {
+ dev_err!(dev, "Booter Unloader returned error 0x{:x}\n", mbox0);
+ return Err(EINVAL);
+ }
+
+ // Confirm that the WPR2 region has been removed.
+ let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);
+ if wpr2_hi.is_wpr2_set() {
+ dev_err!(
+ dev,
+ "WPR2 region still set after Booter Unloader returned\n"
+ );
+ return Err(EBUSY);
+ }
+ }
+
+ Ok(())
+ }
+}
+
/// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
/// created the WPR2 region.
fn run_fwsec_frts(
@@ -161,7 +282,7 @@ fn boot(
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
- ) -> Result {
+ ) -> Result<Option<KBox<dyn UnloadBundle>>> {
let bios = Vbios::new(dev, bar)?;
// FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
@@ -185,7 +306,21 @@ fn boot(
run_booter(dev, bar, chipset, sec2_falcon, wpr_meta)?;
- Ok(())
+ // Last, try and prepare the unload bundle. If this fails, the GPU will need to be reset
+ // before the driver can be probed again.
+ let unload_bundle =
+ Sec2UnloadBundle::build(dev, bar, chipset, &bios, gsp_falcon, sec2_falcon)
+ .inspect_err(|e| {
+ dev_warn!(dev, "Failed to prepare unload firmware: {:?}\n", e);
+ dev_warn!(dev, "The GSP won't be able to unload properly on unbind.\n");
+ dev_warn!(
+ dev,
+ "The GPU will need to be reset before the driver can bind again.\n"
+ );
+ })
+ .ok();
+
+ Ok(unload_bundle)
}
fn post_boot(
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 6faeed73901d..356fbf364ea5 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -175,6 +175,11 @@ impl NV_PFB_PRI_MMU_WPR2_ADDR_HI {
pub(crate) fn higher_bound(self) -> u64 {
u64::from(self.hi_val()) << 12
}
+
+ /// Returns whether the WPR2 region is currently set.
+ pub(crate) fn is_wpr2_set(self) -> bool {
+ self.hi_val() != 0
+ }
}
// PGSP
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function
2026-04-27 6:56 ` [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function Alexandre Courbot
@ 2026-04-27 13:16 ` Gary Guo
0 siblings, 0 replies; 10+ messages in thread
From: Gary Guo @ 2026-04-27 13:16 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Eliot Courtney, nova-gpu, dri-devel, linux-kernel, rust-for-linux
On Mon Apr 27, 2026 at 7:56 AM BST, Alexandre Courbot wrote:
> This function was useful before the generic command-queue send methods
> got merged, but it is just boilerplate now. Replace it with the correct
> sequence to queue the `GetGspStaticInfo` command directly.
>
> Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
> ---
> drivers/gpu/nova-core/gsp/boot.rs | 2 +-
> drivers/gpu/nova-core/gsp/commands.rs | 8 +-------
> 2 files changed, 2 insertions(+), 8 deletions(-)
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-04-27 13:16 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-27 6:56 [PATCH v4 0/8] gpu: nova-core: run unload sequence upon unbinding Alexandre Courbot
2026-04-27 6:56 ` [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function Alexandre Courbot
2026-04-27 13:16 ` Gary Guo
2026-04-27 6:56 ` [PATCH v4 2/8] gpu: nova-core: do not import firmware commands into GSP command module Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 3/8] gpu: nova-core: split BAR acquisition in unbind() Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 4/8] gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 5/8] gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run() Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 7/8] gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL Alexandre Courbot
2026-04-27 6:57 ` [PATCH v4 8/8] gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding Alexandre Courbot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox