Skip to contentSkip to conversation

gpu: nova-core: run unload sequence upon unbinding

PATCH Superseded Alexandre Courbot 4 participants 15 messages Apr 27 – Apr 28

Subsystems (5)

v4 of 7 Superseded Latest is v7 →

lore
Share & tools
lore

Alexandre Courbot

author maintainer
#0 lore raw

v4 cover gpu: nova-core: run unload sequence upon unbinding

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/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://github.com/Gnurou/linux/tree/b4/nova-unload

---
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/[email protected]

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/[email protected]

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/[email protected]

---
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,

Signature · 1 line
Alexandre Courbot <[email protected]>
author maintainer
#1 lore raw

PATCH v4 1/8 gpu: nova-core: remove unneeded get_gsp_info proxy function

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.

---
 drivers/gpu/nova-core/gsp/boot.rs     | 2 +-
 drivers/gpu/nova-core/gsp/commands.rs | 8 +-------
 2 files changed, 2 insertions(+), 8 deletions(-)
drivers/gpu/nova-core/gsp/boot.rs +1 -1
index df105ef4b371..e838d61bef50 100644
@@ -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),
drivers/gpu/nova-core/gsp/commands.rs +1 -7
index c89c7b57a751..e81a865050e0 100644
@@ -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)
-}
Signature · 1 line
2.54.0
reviewer Reviewed-by on v4 1/8
#2 lore raw

Re: [PATCH v4 1/8] gpu: nova-core: remove unneeded get_gsp_info proxy function

Quoted 6 lines · 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.

Quoted 4 lines
---
 drivers/gpu/nova-core/gsp/boot.rs     | 2 +-
 drivers/gpu/nova-core/gsp/commands.rs | 8 +-------
 2 files changed, 2 insertions(+), 8 deletions(-)
author maintainer
#3 lore raw

PATCH v4 2/8 gpu: nova-core: do not import firmware commands into GSP command module

In reply to 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.

---
 drivers/gpu/nova-core/gsp/commands.rs | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
drivers/gpu/nova-core/gsp/commands.rs +12 -12
index e81a865050e0..484aeb7dfd39 100644
@@ -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(
Signature · 1 line
2.54.0
author maintainer
#4 lore raw

PATCH v4 3/8 gpu: nova-core: split BAR acquisition in unbind()

In reply to 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.

---
 drivers/gpu/nova-core/gpu.rs | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gpu.rs +6 -5
index 233a4bcec9fc..920783362251 100644
@@ -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);
     }
 }
Signature · 1 line
2.54.0
Reviewed-by on v4 3/8
#5 lore raw

Re: [PATCH v4 3/8] gpu: nova-core: split BAR acquisition in unbind()

Quoted 20 lines · 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.

---
 drivers/gpu/nova-core/gpu.rs | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gpu.rs +0 -3
index 233a4bcec9fc..920783362251 100644
@@ -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)

drivers/gpu/nova-core/gpu.rs pub(crate) fn new<'a>( · line 329

Quoted 10 lines
drivers/gpu/nova-core/gpu.rs +6 -2
-            .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);
     }
 }

I expect you saw but with the higher-rank lifetime changes from Danilo + Gary we probably won't need this soon.

author maintainer
#6 lore raw

PATCH v4 4/8 gpu: nova-core: send UNLOADING_GUEST_DRIVER GSP command upon unloading

In reply to 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.

---
 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(+)
drivers/gpu/nova-core/gpu.rs +10 -0
index 920783362251..1dd25c93a67e 100644
@@ -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);
     }
 }
drivers/gpu/nova-core/gsp/boot.rs +44 -0

60 lines not on this page · raw message

drivers/gpu/nova-core/gsp/commands.rs +43 -0

46 lines not on this page · raw message

drivers/gpu/nova-core/gsp/fw.rs +4 -0
index 3245793bbe42..33c9f5860771 100644
@@ -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),
drivers/gpu/nova-core/gsp/fw/commands.rs +44 -0

47 lines not on this page · raw message

drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs +11 -0
index 334e8be5fde8..f82ed097b283 100644
@@ -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_,
Signature · 1 line
2.54.0
author maintainer
#7 lore raw

PATCH v4 5/8 gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run()

In reply to Alexandre Courbot

From: John Hubbard <[email protected]>

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.

---
 drivers/gpu/nova-core/firmware/booter.rs | 30 ++++++++++++++++++++++
 drivers/gpu/nova-core/gsp/boot.rs        | 43 +++++++++++++++-----------------
 2 files changed, 50 insertions(+), 23 deletions(-)
drivers/gpu/nova-core/firmware/booter.rs +30 -0
index de2a4536b532..6a41690e72c6 100644
@@ -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 {
drivers/gpu/nova-core/gsp/boot.rs +20 -23

61 lines not on this page · raw message

Signature · 1 line
2.54.0
Reviewed-by on v4 5/8
#8 lore raw

Re: [PATCH v4 5/8] gpu: nova-core: refactor SEC2 booter loading into BooterFirmware::run()

Quoted 6 lines · Alexandre Courbot

From: John Hubbard <[email protected]>

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.

nit: this isn't no behaviour change because it moves a failure in BooterFirmware::new to after a bunch of messages to GSP and GSP boot. But assuming that is fine,

Quoted 35 lines
---
 drivers/gpu/nova-core/firmware/booter.rs | 30 ++++++++++++++++++++++
 drivers/gpu/nova-core/gsp/boot.rs        | 43 +++++++++++++++-----------------
 2 files changed, 50 insertions(+), 23 deletions(-)
drivers/gpu/nova-core/firmware/booter.rs +10 -0
index de2a4536b532..6a41690e72c6 100644
@@ -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,

drivers/gpu/nova-core/firmware/booter.rs pub(crate) fn new( · line 418

Quoted 10 lines
drivers/gpu/nova-core/firmware/booter.rs +10 -0
+        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),

nit: should we have // CAST comments here? fine to ignore this since this this patch is just moving code though.

Quoted 81 lines
drivers/gpu/nova-core/firmware/booter.rs +10 -0
+        )?;
+        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 {
drivers/gpu/nova-core/gsp/boot.rs +20 -23
index d39da38c8918..e75c4b235aab 100644
@@ -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);
 
author maintainer
#9 lore raw

PATCH v4 6/8 gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close

In reply to 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.

---
 drivers/gpu/nova-core/gsp/boot.rs | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/gpu/nova-core/gsp/boot.rs +7 -7
index e75c4b235aab..2e1401cd6171 100644
@@ -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,
Signature · 1 line
2.54.0
Reviewed-by on v4 6/8
#10 lore raw

Re: [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close

Quoted 11 lines · 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.

---

maintainer
#11 lore raw

Re: [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close

In reply to Alexandre Courbot
Quoted 9 lines · 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.

So this is something that has changed in open-gpu over firmwares.

Older firmwares pre-595 always queued up the async messages before hitting the boot sequence,

on 595 and later you send the async message later in the boot process once GSP is booted.

I can't read enough context in this patch to know if that matters, but I just thought I'd point it out.

Dave.

Quoted 47 lines
---
 drivers/gpu/nova-core/gsp/boot.rs | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/gpu/nova-core/gsp/boot.rs +7 -8
index e75c4b235aab..2e1401cd6171 100644
@@ -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

author maintainer
#12 lore raw

Re: [PATCH v4 6/8] gpu: nova-core: gsp: shuffle boot code a bit to keep chipset-specific parts close

Quoted 13 lines · Dave Airlie
Quoted 9 lines · 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.

So this is something that has changed in open-gpu over firmwares.

Quoted 8 lines

Older firmwares pre-595 always queued up the async messages before hitting the boot sequence,

on 595 and later you send the async message later in the boot process once GSP is booted.

I can't read enough context in this patch to know if that matters, but I just thought I'd point it out.

Indeed. I started this after being a bit confused that the code for the Blackwell series had a conditional to queue the messages *after* the GSP was booted on chips using the FSP path, but *before* for chips using SEC2. Looking at OpenRM it indeed seems to be linked to the firmware version rather than the architecture, meaning that John probably used a more recent OpenRM for reference when writing the Blackwell code.

In practice, unless GSP-RM does something really funny, it shouldn't matter whether there already are messages on its message queue when it boots or whether they arrive a bit later. I tested both options on both SEC2 and FSP hardware, and they both booted successfully for both cases.

So I opted for the latter since this is what OpenRM currently does, and the first firmware we will actually support long-term will be post-595.

author maintainer
#13 lore raw

PATCH v4 7/8 gpu: nova-core: gsp: move chipset-specific parts of the boot process into a HAL

In reply to 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.

---
 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(-)
drivers/gpu/nova-core/gsp.rs +1 -0
index ba5b7f990031..38378f104068 100644
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 mod boot;
+mod hal;
 
 use kernel::{
     debugfs,
drivers/gpu/nova-core/gsp/boot.rs +14 -165

232 lines not on this page · raw message

drivers/gpu/nova-core/gsp/hal.rs new file +73 -0

73 lines not on this page · raw message

drivers/gpu/nova-core/gsp/hal/gh100.rs new file +49 -0

49 lines not on this page · raw message

drivers/gpu/nova-core/gsp/hal/tu102.rs new file +216 -0

216 lines not on this page · raw message

Signature · 1 line
2.54.0
author maintainer
#14 lore raw

PATCH v4 8/8 gpu: nova-core: run Booter Unloader and FWSEC-SB upon unbinding

In reply to 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.

---
 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(-)
drivers/gpu/nova-core/firmware/booter.rs +0 -1
index 6a41690e72c6..6f421906c272 100644
@@ -281,7 +281,6 @@ fn new_booter(data: &[u8]) -> Result<Self> {
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub(crate) enum BooterKind {
     Loader,
-    #[expect(unused)]
     Unloader,
 }
 
drivers/gpu/nova-core/firmware/fwsec.rs +0 -1
index 8810cb49db67..4108f28cd338 100644
@@ -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,
 }
 
drivers/gpu/nova-core/gpu.rs +1 -1
index 1dd25c93a67e..7cf994ba2fb0 100644
@@ -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.
drivers/gpu/nova-core/gsp.rs +3 -0
index 38378f104068..58b917172efb 100644
@@ -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,
             }))
         })
     }
drivers/gpu/nova-core/gsp/boot.rs +16 -2
index 59c1b4d030ae..c8555d76b3a2 100644
@@ -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(())
     }
 }
drivers/gpu/nova-core/gsp/hal.rs +20 -1
index 4d8c1998f4cf..d9c324226dda 100644
@@ -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.
     ///
drivers/gpu/nova-core/gsp/hal/gh100.rs +5 -2
index 9d7e9f4454b1..51e1099cda0d 100644
@@ -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)
     }
 }
drivers/gpu/nova-core/gsp/hal/tu102.rs +138 -3

165 lines not on this page · raw message

drivers/gpu/nova-core/regs.rs +5 -0
index 6faeed73901d..356fbf364ea5 100644
@@ -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
Signature · 1 line
2.54.0