Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 00/12] nova-core: Complete GSP boot and begin RPC communication
Hello! These patches a refresh of the series adding support for final stages of the GSP boot process where a sequencer which inteprets firmware instructions needs to run to boot the GSP processor, followed by waiting for an INIT_DONE message from the GSP. The patches are based on Alex's github branch which have several prerequisites: Repo: https://github.com/Gnurou/linux.git Branch: b4/gsp_boot I also dropped several patches (mainly from John that have already been applied). Tested on Ampere GA102. We also need the "gpu: nova-core: Add get_gsp_info() command" patch which I dropped since it needs to be reworked, and it is not needed for GSP boot on Ampere (but John mentioned it is needed for Blackwell so we could include it in the Blackwell series or I can try to include it in this series if I'm respinning). Previous series: [1] https://lore.kernel.org/all/20250829173254.2068763-1-joelagnelf at nvidia.com/ Alistair Popple (1): gpu: nova-core: gsp: Wait for gsp initialisation to complete Joel Fernandes (11): nova-core: falcon: Move waiting until halted to a helper nova-core: falcon: Move start functionality into separate helper nova-core: falcon: Move mbox functionalities into helper nova-core: falcon: Move dma_reset functionality into helper nova-core: gsp: Add support for checking if GSP reloaded nova-core: Add bindings required by GSP sequencer nova-core: Implement the GSP sequencer nova-core: sequencer: Add register opcodes nova-core: sequencer: Add delay opcode support nova-core: sequencer: Implement basic core operations nova-core: sequencer: Implement core resume operation drivers/gpu/nova-core/falcon.rs | 101 +++-- drivers/gpu/nova-core/falcon/gsp.rs | 17 + drivers/gpu/nova-core/gsp.rs | 1 + drivers/gpu/nova-core/gsp/boot.rs | 27 +- drivers/gpu/nova-core/gsp/cmdq.rs | 1 - drivers/gpu/nova-core/gsp/commands.rs | 39 +- drivers/gpu/nova-core/gsp/fw.rs | 44 ++ .../gpu/nova-core/gsp/fw/r570_144/bindings.rs | 85 ++++ drivers/gpu/nova-core/gsp/sequencer.rs | 413 ++++++++++++++++++ drivers/gpu/nova-core/regs.rs | 6 + drivers/gpu/nova-core/sbuffer.rs | 1 - 11 files changed, 698 insertions(+), 37 deletions(-) create mode 100644 drivers/gpu/nova-core/gsp/sequencer.rs -- 2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 01/12] nova-core: falcon: Move waiting until halted to a helper
Move the "waiting until halted" functionality into a helper so that we
can use it in the sequencer, which is a separate sequencer operation.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index dc8c2179935e..dc883ce5f28b 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -540,6 +540,19 @@ pub(crate) fn dma_load<F: FalconFirmware<Target =
E>>(&self, bar: &Bar0, fw: &F)
Ok(())
}
+ /// Wait until the falcon CPU is halted.
+ pub(crate) fn wait_till_halted(&self, bar: &Bar0) ->
Result<()> {
+ // TIMEOUT: arbitrarily large value, firmwares should complete in less
than 2 seconds.
+ read_poll_timeout(
+ || Ok(regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID)),
+ |r| r.halted(),
+ Delta::ZERO,
+ Delta::from_secs(2),
+ )?;
+
+ Ok(())
+ }
+
/// Runs the loaded firmware and waits for its completion.
///
/// `mbox0` and `mbox1` are optional parameters to write into the `MBOX0`
and `MBOX1` registers
@@ -574,13 +587,7 @@ pub(crate) fn boot(
.write(bar, &E::ID),
}
- // TIMEOUT: arbitrarily large value, firmwares should complete in less
than 2 seconds.
- read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID)),
- |r| r.halted(),
- Delta::ZERO,
- Delta::from_secs(2),
- )?;
+ self.wait_till_halted(bar)?;
let (mbox0, mbox1) = (
regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value(),
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 02/12] nova-core: falcon: Move start functionality into separate helper
Move start functionality into a separate helper so we can use it from
the sequencer.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index dc883ce5f28b..1bcee06fdec2 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -553,7 +553,21 @@ pub(crate) fn wait_till_halted(&self, bar: &Bar0)
-> Result<()> {
Ok(())
}
- /// Runs the loaded firmware and waits for its completion.
+ /// Start the falcon CPU.
+ pub(crate) fn start(&self, bar: &Bar0) -> Result<()> {
+ match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en()
{
+ true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default()
+ .set_startcpu(true)
+ .write(bar, &E::ID),
+ false => regs::NV_PFALCON_FALCON_CPUCTL::default()
+ .set_startcpu(true)
+ .write(bar, &E::ID),
+ }
+
+ Ok(())
+ }
+
+ /// Start running the loaded firmware.
///
/// `mbox0` and `mbox1` are optional parameters to write into the `MBOX0`
and `MBOX1` registers
/// prior to running.
@@ -578,15 +592,7 @@ pub(crate) fn boot(
.write(bar, &E::ID);
}
- match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en()
{
- true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default()
- .set_startcpu(true)
- .write(bar, &E::ID),
- false => regs::NV_PFALCON_FALCON_CPUCTL::default()
- .set_startcpu(true)
- .write(bar, &E::ID),
- }
-
+ self.start(bar)?;
self.wait_till_halted(bar)?;
let (mbox0, mbox1) = (
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 03/12] nova-core: falcon: Move mbox functionalities into helper
Move falcon reading/writing to mbox functionality into helper so we can
use it from the sequencer resume flow.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 51 +++++++++++++++++++++++----------
1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 1bcee06fdec2..181347feb3ca 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -567,19 +567,13 @@ pub(crate) fn start(&self, bar: &Bar0) ->
Result<()> {
Ok(())
}
- /// Start running the loaded firmware.
- ///
- /// `mbox0` and `mbox1` are optional parameters to write into the `MBOX0`
and `MBOX1` registers
- /// prior to running.
- ///
- /// Wait up to two seconds for the firmware to complete, and return its
exit status read from
- /// the `MBOX0` and `MBOX1` registers.
- pub(crate) fn boot(
+ /// Writes values to the mailbox registers if provided.
+ pub(crate) fn write_mailboxes(
&self,
bar: &Bar0,
mbox0: Option<u32>,
mbox1: Option<u32>,
- ) -> Result<(u32, u32)> {
+ ) -> Result<()> {
if let Some(mbox0) = mbox0 {
regs::NV_PFALCON_FALCON_MAILBOX0::default()
.set_value(mbox0)
@@ -591,18 +585,45 @@ pub(crate) fn boot(
.set_value(mbox1)
.write(bar, &E::ID);
}
+ Ok(())
+ }
- self.start(bar)?;
- self.wait_till_halted(bar)?;
+ /// Reads the value from mbox0 register.
+ pub(crate) fn read_mailbox0(&self, bar: &Bar0) ->
Result<u32> {
+ Ok(regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value())
+ }
- let (mbox0, mbox1) = (
- regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value(),
- regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value(),
- );
+ /// Reads the value from mbox1 register.
+ pub(crate) fn read_mailbox1(&self, bar: &Bar0) ->
Result<u32> {
+ Ok(regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value())
+ }
+ /// Reads values from both mailbox registers.
+ pub(crate) fn read_mailboxes(&self, bar: &Bar0) ->
Result<(u32, u32)> {
+ let mbox0 = self.read_mailbox0(bar)?;
+ let mbox1 = self.read_mailbox1(bar)?;
Ok((mbox0, mbox1))
}
+ /// Start running the loaded firmware.
+ ///
+ /// `mbox0` and `mbox1` are optional parameters to write into the `MBOX0`
and `MBOX1` registers
+ /// prior to running.
+ ///
+ /// Wait up to two seconds for the firmware to complete, and return its
exit status read from
+ /// the `MBOX0` and `MBOX1` registers.
+ pub(crate) fn boot(
+ &self,
+ bar: &Bar0,
+ mbox0: Option<u32>,
+ mbox1: Option<u32>,
+ ) -> Result<(u32, u32)> {
+ self.write_mailboxes(bar, mbox0, mbox1)?;
+ self.start(bar)?;
+ self.wait_till_halted(bar)?;
+ self.read_mailboxes(bar)
+ }
+
/// Returns the fused version of the signature to use in order to run a HS
firmware on this
/// falcon instance. `engine_id_mask` and `ucode_id` are obtained from the
firmware header.
pub(crate) fn signature_reg_fuse_version(
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 04/12] nova-core: falcon: Move dma_reset functionality into helper
Move dma_reset so we can use it for the upcoming sequencer
functionality.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 181347feb3ca..964033ded3f2 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -371,6 +371,12 @@ pub(crate) fn new(dev: &device::Device, chipset:
Chipset) -> Result<Self> {
})
}
+ /// Resets DMA-related registers.
+ pub(crate) fn dma_reset(&self, bar: &Bar0) {
+ regs::NV_PFALCON_FBIF_CTL::update(bar, &E::ID, |v|
v.set_allow_phys_no_ctx(true));
+ regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
+ }
+
/// Wait for memory scrubbing to complete.
fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result {
// TIMEOUT: memory scrubbing should complete in less than 20ms.
@@ -520,8 +526,7 @@ fn dma_wr<F: FalconFirmware<Target = E>>(
/// Perform a DMA load into `IMEM` and `DMEM` of `fw`, and prepare the
falcon to run it.
pub(crate) fn dma_load<F: FalconFirmware<Target =
E>>(&self, bar: &Bar0, fw: &F) -> Result {
- regs::NV_PFALCON_FBIF_CTL::update(bar, &E::ID, |v|
v.set_allow_phys_no_ctx(true));
- regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
+ self.dma_reset(bar);
regs::NV_PFALCON_FBIF_TRANSCFG::update(bar, &E::ID, 0, |v| {
v.set_target(FalconFbifTarget::CoherentSysmem)
.set_mem_type(FalconFbifMemType::Physical)
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 05/12] nova-core: gsp: Add support for checking if GSP reloaded
During the sequencer process, we need to check if GSP was successfully
reloaded. Add functionality to check for the same.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon/gsp.rs | 18 ++++++++++++++++++
drivers/gpu/nova-core/regs.rs | 6 ++++++
2 files changed, 24 insertions(+)
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs
b/drivers/gpu/nova-core/falcon/gsp.rs
index f17599cb49fa..e0c0b18ec5bf 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -1,5 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
+use kernel::{
+ io::poll::read_poll_timeout,
+ prelude::*,
+ time::Delta, //
+};
+
use crate::{
driver::Bar0,
falcon::{Falcon, FalconEngine, PFalcon2Base, PFalconBase},
@@ -29,4 +35,16 @@ pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) {
.set_swgen0(true)
.write(bar, &Gsp::ID);
}
+
+ /// Checks if GSP reload/resume has completed during the boot process.
+ #[expect(dead_code)]
+ pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout:
Delta) -> Result<bool> {
+ read_poll_timeout(
+ || Ok(regs::NV_PGC6_BSI_SECURE_SCRATCH_14::read(bar)),
+ |val| val.boot_stage_3_handoff(),
+ Delta::ZERO,
+ timeout,
+ )
+ .map(|_| true)
+ }
}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index c945adf63b9e..cb7f60a6b911 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -124,6 +124,12 @@ pub(crate) fn higher_bound(self) -> u64 {
// These scratch registers remain powered on even in a low-power state and have
a designated group
// number.
+// Boot Sequence Interface (BSI) register used to determine
+// if GSP reload/resume has completed during the boot process.
+register!(NV_PGC6_BSI_SECURE_SCRATCH_14 @ 0x001180f8 {
+ 26:26 boot_stage_3_handoff as bool;
+});
+
// Privilege level mask register. It dictates whether the host CPU has
privilege to access the
// `PGC6_AON_SECURE_SCRATCH_GROUP_05` register (which it needs to read
GFW_BOOT).
register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128,
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 06/12] nova-core: Add bindings required by GSP sequencer
Add several firmware bindings required by GSP sequencer code.
Co-developed-by: Alistair Popple <apopple at nvidia.com>
Signed-off-by: Alistair Popple <apopple at nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp/fw.rs | 45 ++++++++++
.../gpu/nova-core/gsp/fw/r570_144/bindings.rs | 85 +++++++++++++++++++
2 files changed, 130 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 687749bdbb45..53e28458cd7d 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -543,6 +543,51 @@ pub(crate) fn element_count(&self) -> u32 {
}
}
+#[expect(unused)]
+pub(crate) use r570_144::{
+ // GSP sequencer run structure with information on how to run the
sequencer.
+ rpc_run_cpu_sequencer_v17_00,
+
+ // GSP sequencer structures.
+ GSP_SEQUENCER_BUFFER_CMD,
+ GSP_SEQ_BUF_OPCODE,
+
+ // GSP sequencer core operation opcodes.
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT,
+
+ // GSP sequencer delay opcode and payload.
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US,
+
+ // GSP sequencer register opcodes.
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE,
+ GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE,
+
+ // GSP sequencer delay payload structure.
+ GSP_SEQ_BUF_PAYLOAD_DELAY_US,
+
+ // GSP sequencer register payload structures.
+ GSP_SEQ_BUF_PAYLOAD_REG_MODIFY,
+ GSP_SEQ_BUF_PAYLOAD_REG_POLL,
+ GSP_SEQ_BUF_PAYLOAD_REG_STORE,
+ GSP_SEQ_BUF_PAYLOAD_REG_WRITE, //
+};
+
+// SAFETY: This struct only contains integer types for which all bit patterns
+// are valid.
+unsafe impl FromBytes for GSP_SEQUENCER_BUFFER_CMD {}
+
+// SAFETY: Padding is explicit and will not contain uninitialized data.
+unsafe impl AsBytes for rpc_run_cpu_sequencer_v17_00 {}
+
+// SAFETY: This struct only contains integer types for which all bit patterns
+// are valid.
+unsafe impl FromBytes for rpc_run_cpu_sequencer_v17_00 {}
+
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for GspMsgElement {}
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 32933874ff97..c5c589c1e2ac 100644
--- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
+++ b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
@@ -664,6 +664,7 @@ pub struct PACKED_REGISTRY_TABLE {
pub numEntries: u32_,
pub entries: __IncompleteArrayField<PACKED_REGISTRY_ENTRY>,
}
+
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Zeroable)]
pub struct msgqTxHeader {
@@ -702,3 +703,87 @@ fn default() -> Self {
}
}
}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct rpc_run_cpu_sequencer_v17_00 {
+ pub bufferSizeDWord: u32_,
+ pub cmdIndex: u32_,
+ pub regSaveArea: [u32_; 8usize],
+ pub commandBuffer: __IncompleteArrayField<u32_>,
+}
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE: GSP_SEQ_BUF_OPCODE =
0;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY: GSP_SEQ_BUF_OPCODE
= 1;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL: GSP_SEQ_BUF_OPCODE =
2;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US: GSP_SEQ_BUF_OPCODE =
3;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE: GSP_SEQ_BUF_OPCODE =
4;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET: GSP_SEQ_BUF_OPCODE
= 5;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START: GSP_SEQ_BUF_OPCODE
= 6;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT:
GSP_SEQ_BUF_OPCODE = 7;
+pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME: GSP_SEQ_BUF_OPCODE
= 8;
+pub type GSP_SEQ_BUF_OPCODE = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct GSP_SEQ_BUF_PAYLOAD_REG_WRITE {
+ pub addr: u32_,
+ pub val: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct GSP_SEQ_BUF_PAYLOAD_REG_MODIFY {
+ pub addr: u32_,
+ pub mask: u32_,
+ pub val: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct GSP_SEQ_BUF_PAYLOAD_REG_POLL {
+ pub addr: u32_,
+ pub mask: u32_,
+ pub val: u32_,
+ pub timeout: u32_,
+ pub error: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct GSP_SEQ_BUF_PAYLOAD_DELAY_US {
+ pub val: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct GSP_SEQ_BUF_PAYLOAD_REG_STORE {
+ pub addr: u32_,
+ pub index: u32_,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct GSP_SEQUENCER_BUFFER_CMD {
+ pub opCode: GSP_SEQ_BUF_OPCODE,
+ pub payload: GSP_SEQUENCER_BUFFER_CMD__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union GSP_SEQUENCER_BUFFER_CMD__bindgen_ty_1 {
+ pub regWrite: GSP_SEQ_BUF_PAYLOAD_REG_WRITE,
+ pub regModify: GSP_SEQ_BUF_PAYLOAD_REG_MODIFY,
+ pub regPoll: GSP_SEQ_BUF_PAYLOAD_REG_POLL,
+ pub delayUs: GSP_SEQ_BUF_PAYLOAD_DELAY_US,
+ pub regStore: GSP_SEQ_BUF_PAYLOAD_REG_STORE,
+}
+impl Default for GSP_SEQUENCER_BUFFER_CMD__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for GSP_SEQUENCER_BUFFER_CMD {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 07/12] nova-core: Implement the GSP sequencer
Implement the GSP sequencer which culminates in INIT_DONE message being
received from the GSP indicating that the GSP has successfully booted.
This is just initial sequencer support, the actual commands will be
added in the next patches.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 1 +
drivers/gpu/nova-core/gsp/boot.rs | 19 ++-
drivers/gpu/nova-core/gsp/cmdq.rs | 1 -
drivers/gpu/nova-core/gsp/sequencer.rs | 208 +++++++++++++++++++++++++
drivers/gpu/nova-core/sbuffer.rs | 1 -
5 files changed, 227 insertions(+), 3 deletions(-)
create mode 100644 drivers/gpu/nova-core/gsp/sequencer.rs
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 36175eafaf2e..9d62aea3c782 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -16,6 +16,7 @@
pub(crate) mod cmdq;
pub(crate) mod commands;
mod fw;
+mod sequencer;
use fw::GspArgumentsCached;
use fw::LibosMemoryRegionInitArgument;
diff --git a/drivers/gpu/nova-core/gsp/boot.rs
b/drivers/gpu/nova-core/gsp/boot.rs
index 649c758eda70..761020a11153 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -19,7 +19,13 @@
};
use crate::gpu::Chipset;
use crate::gsp::commands::{build_registry, set_system_info};
-use crate::gsp::GspFwWprMeta;
+use crate::gsp::{
+ sequencer::{
+ GspSequencer,
+ GspSequencerParams, //
+ },
+ GspFwWprMeta, //
+};
use crate::regs;
use crate::vbios::Vbios;
@@ -204,6 +210,17 @@ pub(crate) fn boot(
gsp_falcon.is_riscv_active(bar),
);
+ // Create and run the GSP sequencer.
+ let seq_params = GspSequencerParams {
+ gsp_fw: &gsp_fw,
+ libos_dma_handle: libos_handle,
+ gsp_falcon,
+ sec2_falcon,
+ dev: pdev.as_ref(),
+ bar,
+ };
+ GspSequencer::run(&mut self.cmdq, seq_params,
Delta::from_secs(10))?;
+
Ok(())
}
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs
b/drivers/gpu/nova-core/gsp/cmdq.rs
index 0fb8ff26ba2f..0185629a3b5c 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -418,7 +418,6 @@ struct FullCommand<M> {
Ok(())
}
- #[expect(unused)]
pub(crate) fn receive_msg_from_gsp<M: MessageFromGsp, R>(
&mut self,
timeout: Delta,
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs
b/drivers/gpu/nova-core/gsp/sequencer.rs
new file mode 100644
index 000000000000..48c40140876b
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! GSP Sequencer implementation for Pre-hopper GSP boot sequence.
+
+use core::mem::size_of;
+use kernel::alloc::flags::GFP_KERNEL;
+use kernel::device;
+use kernel::prelude::*;
+use kernel::time::Delta;
+use kernel::transmute::FromBytes;
+
+use crate::driver::Bar0;
+use crate::falcon::{
+ gsp::Gsp,
+ sec2::Sec2,
+ Falcon, //
+};
+use crate::firmware::gsp::GspFirmware;
+use crate::gsp::cmdq::{
+ Cmdq,
+ MessageFromGsp, //
+};
+use crate::gsp::fw;
+
+use kernel::{
+ dev_dbg,
+ dev_err, //
+};
+
+impl MessageFromGsp for fw::rpc_run_cpu_sequencer_v17_00 {
+ const FUNCTION: fw::MsgFunction = fw::MsgFunction::GspRunCpuSequencer;
+}
+
+const CMD_SIZE: usize = size_of::<fw::GSP_SEQUENCER_BUFFER_CMD>();
+
+struct GspSequencerInfo<'a> {
+ info: &'a fw::rpc_run_cpu_sequencer_v17_00,
+ cmd_data: KVec<u8>,
+}
+
+/// GSP Sequencer Command types with payload data.
+/// Commands have an opcode and a opcode-dependent struct.
+#[allow(dead_code)]
+pub(crate) enum GspSeqCmd {}
+
+impl GspSeqCmd {
+ /// Creates a new GspSeqCmd from a firmware GSP_SEQUENCER_BUFFER_CMD.
+ pub(crate) fn from_fw_cmd(_cmd: &fw::GSP_SEQUENCER_BUFFER_CMD) ->
Result<Self> {
+ Err(EINVAL)
+ }
+
+ pub(crate) fn new(data: &[u8], dev:
&device::Device<device::Bound>) -> Result<Self> {
+ let fw_cmd =
fw::GSP_SEQUENCER_BUFFER_CMD::from_bytes(data).ok_or(EINVAL)?;
+ let cmd = Self::from_fw_cmd(fw_cmd)?;
+
+ if data.len() < cmd.size_bytes() {
+ dev_err!(dev, "data is not enough for command.\n");
+ return Err(EINVAL);
+ }
+
+ Ok(cmd)
+ }
+
+ /// Get the size of this command in bytes, the command consists of
+ /// a 4-byte opcode, and a variable-sized payload.
+ pub(crate) fn size_bytes(&self) -> usize {
+ 0
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct GspSequencer<'a> {
+ seq_info: GspSequencerInfo<'a>,
+ bar: &'a Bar0,
+ sec2_falcon: &'a Falcon<Sec2>,
+ gsp_falcon: &'a Falcon<Gsp>,
+ libos_dma_handle: u64,
+ gsp_fw: &'a GspFirmware,
+ dev: &'a device::Device<device::Bound>,
+}
+
+pub(crate) trait GspSeqCmdRunner {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result;
+}
+
+impl GspSeqCmdRunner for GspSeqCmd {
+ fn run(&self, _seq: &GspSequencer<'_>) -> Result {
+ Ok(())
+ }
+}
+
+pub(crate) struct GspSeqIter<'a> {
+ cmd_data: &'a [u8],
+ current_offset: usize, // Tracking the current position.
+ total_cmds: u32,
+ cmds_processed: u32,
+ dev: &'a device::Device<device::Bound>,
+}
+
+impl<'a> Iterator for GspSeqIter<'a> {
+ type Item = Result<GspSeqCmd>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // Stop if we've processed all commands or reached the end of data.
+ if self.cmds_processed >= self.total_cmds || self.current_offset
>= self.cmd_data.len() {
+ return None;
+ }
+
+ // Check if we have enough data for opcode.
+ let opcode_size = size_of::<fw::GSP_SEQ_BUF_OPCODE>();
+ if self.current_offset + opcode_size > self.cmd_data.len() {
+ return Some(Err(EINVAL));
+ }
+
+ let offset = self.current_offset;
+
+ // Handle command creation based on available data,
+ // zero-pad if necessary (since last command may not be full size).
+ let mut buffer = [0u8; CMD_SIZE];
+ let copy_len = if offset + CMD_SIZE <= self.cmd_data.len() {
+ CMD_SIZE
+ } else {
+ self.cmd_data.len() - offset
+ };
+ buffer[..copy_len].copy_from_slice(&self.cmd_data[offset..offset +
copy_len]);
+ let cmd_result = GspSeqCmd::new(&buffer, self.dev);
+
+ cmd_result.map_or_else(
+ |_err| {
+ dev_err!(self.dev, "Error parsing command at offset
{}", offset);
+ None
+ },
+ |cmd| {
+ self.current_offset += cmd.size_bytes();
+ self.cmds_processed += 1;
+ Some(Ok(cmd))
+ },
+ )
+ }
+}
+
+impl<'a, 'b> IntoIterator for &'b
GspSequencer<'a> {
+ type Item = Result<GspSeqCmd>;
+ type IntoIter = GspSeqIter<'b>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ let cmd_data = &self.seq_info.cmd_data[..];
+
+ GspSeqIter {
+ cmd_data,
+ current_offset: 0,
+ total_cmds: self.seq_info.info.cmdIndex,
+ cmds_processed: 0,
+ dev: self.dev,
+ }
+ }
+}
+
+/// Parameters for running the GSP sequencer.
+pub(crate) struct GspSequencerParams<'a> {
+ pub(crate) gsp_fw: &'a GspFirmware,
+ pub(crate) libos_dma_handle: u64,
+ pub(crate) gsp_falcon: &'a Falcon<Gsp>,
+ pub(crate) sec2_falcon: &'a Falcon<Sec2>,
+ pub(crate) dev: &'a device::Device<device::Bound>,
+ pub(crate) bar: &'a Bar0,
+}
+
+impl<'a> GspSequencer<'a> {
+ pub(crate) fn run(cmdq: &mut Cmdq, params:
GspSequencerParams<'a>, timeout: Delta) -> Result {
+ cmdq.receive_msg_from_gsp(timeout, |info, mut sbuf| {
+ let cmd_data = sbuf.flush_into_kvec(GFP_KERNEL)?;
+ let seq_info = GspSequencerInfo { info, cmd_data };
+
+ let sequencer = GspSequencer {
+ seq_info,
+ bar: params.bar,
+ sec2_falcon: params.sec2_falcon,
+ gsp_falcon: params.gsp_falcon,
+ libos_dma_handle: params.libos_dma_handle,
+ gsp_fw: params.gsp_fw,
+ dev: params.dev,
+ };
+
+ dev_dbg!(params.dev, "Running CPU Sequencer commands\n");
+
+ for cmd_result in &sequencer {
+ match cmd_result {
+ Ok(cmd) => cmd.run(&sequencer)?,
+ Err(e) => {
+ dev_err!(
+ params.dev,
+ "Error running command at index {}\n",
+ sequencer.seq_info.info.cmdIndex
+ );
+ return Err(e);
+ }
+ }
+ }
+
+ dev_dbg!(
+ params.dev,
+ "CPU Sequencer commands completed successfully\n"
+ );
+ Ok(())
+ })
+ }
+}
diff --git a/drivers/gpu/nova-core/sbuffer.rs b/drivers/gpu/nova-core/sbuffer.rs
index 4d7cbc4bd060..36890c8610c2 100644
--- a/drivers/gpu/nova-core/sbuffer.rs
+++ b/drivers/gpu/nova-core/sbuffer.rs
@@ -162,7 +162,6 @@ pub(crate) fn read_exact(&mut self, mut dst: &mut
[u8]) -> Result {
/// Read all the remaining data into a [`KVec`].
///
/// `self` will be empty after this operation.
- #[expect(unused)]
pub(crate) fn flush_into_kvec(&mut self, flags: kernel::alloc::Flags)
-> Result<KVec<u8>> {
let mut buf = KVec::<u8>::new();
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 08/12] nova-core: sequencer: Add register opcodes
These opcodes are used for register write, modify, poll and store (save)
sequencer operations.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp/sequencer.rs | 138 +++++++++++++++++++++++--
1 file changed, 131 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs
b/drivers/gpu/nova-core/gsp/sequencer.rs
index 48c40140876b..f429fee01f86 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -5,6 +5,7 @@
use core::mem::size_of;
use kernel::alloc::flags::GFP_KERNEL;
use kernel::device;
+use kernel::io::poll::read_poll_timeout;
use kernel::prelude::*;
use kernel::time::Delta;
use kernel::transmute::FromBytes;
@@ -40,13 +41,36 @@ struct GspSequencerInfo<'a> {
/// GSP Sequencer Command types with payload data.
/// Commands have an opcode and a opcode-dependent struct.
-#[allow(dead_code)]
-pub(crate) enum GspSeqCmd {}
+#[allow(clippy::enum_variant_names)]
+pub(crate) enum GspSeqCmd {
+ RegWrite(fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE),
+ RegModify(fw::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY),
+ RegPoll(fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL),
+ RegStore(fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE),
+}
impl GspSeqCmd {
/// Creates a new GspSeqCmd from a firmware GSP_SEQUENCER_BUFFER_CMD.
- pub(crate) fn from_fw_cmd(_cmd: &fw::GSP_SEQUENCER_BUFFER_CMD) ->
Result<Self> {
- Err(EINVAL)
+ pub(crate) fn from_fw_cmd(cmd: &fw::GSP_SEQUENCER_BUFFER_CMD) ->
Result<Self> {
+ match cmd.opCode {
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE => {
+ // SAFETY: We're using the union field that corresponds to
the opCode.
+ Ok(GspSeqCmd::RegWrite(unsafe { cmd.payload.regWrite }))
+ }
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY => {
+ // SAFETY: We're using the union field that corresponds to
the opCode.
+ Ok(GspSeqCmd::RegModify(unsafe { cmd.payload.regModify }))
+ }
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL => {
+ // SAFETY: We're using the union field that corresponds to
the opCode.
+ Ok(GspSeqCmd::RegPoll(unsafe { cmd.payload.regPoll }))
+ }
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE => {
+ // SAFETY: We're using the union field that corresponds to
the opCode.
+ Ok(GspSeqCmd::RegStore(unsafe { cmd.payload.regStore }))
+ }
+ _ => Err(EINVAL),
+ }
}
pub(crate) fn new(data: &[u8], dev:
&device::Device<device::Bound>) -> Result<Self> {
@@ -64,7 +88,16 @@ pub(crate) fn new(data: &[u8], dev:
&device::Device<device::Bound>) -> Result<Se
/// Get the size of this command in bytes, the command consists of
/// a 4-byte opcode, and a variable-sized payload.
pub(crate) fn size_bytes(&self) -> usize {
- 0
+ let opcode_size = size_of::<fw::GSP_SEQ_BUF_OPCODE>();
+ match self {
+ // For commands with payloads, add the payload size in bytes.
+ GspSeqCmd::RegWrite(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE>(),
+ GspSeqCmd::RegModify(_) => {
+ opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY>()
+ }
+ GspSeqCmd::RegPoll(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL>(),
+ GspSeqCmd::RegStore(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE>(),
+ }
}
}
@@ -83,12 +116,103 @@ pub(crate) trait GspSeqCmdRunner {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result;
}
-impl GspSeqCmdRunner for GspSeqCmd {
- fn run(&self, _seq: &GspSequencer<'_>) -> Result {
+impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
+ dev_dbg!(
+ sequencer.dev,
+ "RegWrite: addr=0x{:x}, val=0x{:x}\n",
+ self.addr,
+ self.val
+ );
+ let addr = self.addr as usize;
+ let val = self.val;
+ let _ = sequencer.bar.try_write32(val, addr);
+ Ok(())
+ }
+}
+
+impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
+ dev_dbg!(
+ sequencer.dev,
+ "RegModify: addr=0x{:x}, mask=0x{:x}, val=0x{:x}\n",
+ self.addr,
+ self.mask,
+ self.val
+ );
+
+ let addr = self.addr as usize;
+ if let Ok(temp) = sequencer.bar.try_read32(addr) {
+ let _ = sequencer
+ .bar
+ .try_write32((temp & !self.mask) | self.val, addr);
+ }
Ok(())
}
}
+impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
+ dev_dbg!(
+ sequencer.dev,
+ "RegPoll: addr=0x{:x}, mask=0x{:x}, val=0x{:x},
timeout=0x{:x}, error=0x{:x}\n",
+ self.addr,
+ self.mask,
+ self.val,
+ self.timeout,
+ self.error
+ );
+
+ let addr = self.addr as usize;
+ let mut timeout_us = i64::from(self.timeout);
+
+ // Default timeout to 4 seconds.
+ timeout_us = if timeout_us == 0 { 4000000 } else { timeout_us };
+
+ // First read.
+ sequencer.bar.try_read32(addr)?;
+
+ // Poll the requested register with requested timeout.
+ read_poll_timeout(
+ || sequencer.bar.try_read32(addr),
+ |current| (current & self.mask) == self.val,
+ Delta::ZERO,
+ Delta::from_micros(timeout_us),
+ )
+ .map(|_| ())
+ }
+}
+
+impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
+ let addr = self.addr as usize;
+ let _index = self.index;
+
+ let val = sequencer.bar.try_read32(addr)?;
+
+ dev_dbg!(
+ sequencer.dev,
+ "RegStore: addr=0x{:x}, index=0x{:x}, value={:?}\n",
+ self.addr,
+ self.index,
+ val
+ );
+
+ Ok(())
+ }
+}
+
+impl GspSeqCmdRunner for GspSeqCmd {
+ fn run(&self, seq: &GspSequencer<'_>) -> Result {
+ match self {
+ GspSeqCmd::RegWrite(cmd) => cmd.run(seq),
+ GspSeqCmd::RegModify(cmd) => cmd.run(seq),
+ GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
+ GspSeqCmd::RegStore(cmd) => cmd.run(seq),
+ }
+ }
+}
+
pub(crate) struct GspSeqIter<'a> {
cmd_data: &'a [u8],
current_offset: usize, // Tracking the current position.
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 09/12] nova-core: sequencer: Add delay opcode support
Implement a sequencer opcode for delay operations.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp/sequencer.rs | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs
b/drivers/gpu/nova-core/gsp/sequencer.rs
index f429fee01f86..d08664044506 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -4,6 +4,7 @@
use core::mem::size_of;
use kernel::alloc::flags::GFP_KERNEL;
+use kernel::bindings;
use kernel::device;
use kernel::io::poll::read_poll_timeout;
use kernel::prelude::*;
@@ -46,6 +47,7 @@ pub(crate) enum GspSeqCmd {
RegWrite(fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE),
RegModify(fw::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY),
RegPoll(fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL),
+ DelayUs(fw::GSP_SEQ_BUF_PAYLOAD_DELAY_US),
RegStore(fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE),
}
@@ -65,6 +67,10 @@ pub(crate) fn from_fw_cmd(cmd:
&fw::GSP_SEQUENCER_BUFFER_CMD) -> Result<Self> {
// SAFETY: We're using the union field that corresponds to
the opCode.
Ok(GspSeqCmd::RegPoll(unsafe { cmd.payload.regPoll }))
}
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US => {
+ // SAFETY: We're using the union field that corresponds to
the opCode.
+ Ok(GspSeqCmd::DelayUs(unsafe { cmd.payload.delayUs }))
+ }
fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE => {
// SAFETY: We're using the union field that corresponds to
the opCode.
Ok(GspSeqCmd::RegStore(unsafe { cmd.payload.regStore }))
@@ -96,6 +102,7 @@ pub(crate) fn size_bytes(&self) -> usize {
opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY>()
}
GspSeqCmd::RegPoll(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL>(),
+ GspSeqCmd::DelayUs(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_DELAY_US>(),
GspSeqCmd::RegStore(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE>(),
}
}
@@ -183,6 +190,21 @@ fn run(&self, sequencer:
&GspSequencer<'_>) -> Result {
}
}
+impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_DELAY_US {
+ fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
+ dev_dbg!(sequencer.dev, "DelayUs: val=0x{:x}\n", self.val);
+ // SAFETY: `usleep_range_state` is safe to call with any parameter.
+ unsafe {
+ bindings::usleep_range_state(
+ self.val as usize,
+ self.val as usize,
+ bindings::TASK_UNINTERRUPTIBLE,
+ )
+ };
+ Ok(())
+ }
+}
+
impl GspSeqCmdRunner for fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result
{
let addr = self.addr as usize;
@@ -208,6 +230,7 @@ fn run(&self, seq: &GspSequencer<'_>)
-> Result {
GspSeqCmd::RegWrite(cmd) => cmd.run(seq),
GspSeqCmd::RegModify(cmd) => cmd.run(seq),
GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
+ GspSeqCmd::DelayUs(cmd) => cmd.run(seq),
GspSeqCmd::RegStore(cmd) => cmd.run(seq),
}
}
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 10/12] nova-core: sequencer: Implement basic core operations
These opcodes implement various falcon-related boot operations: reset,
start, wait-for-halt.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp/sequencer.rs | 27 ++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs
b/drivers/gpu/nova-core/gsp/sequencer.rs
index d08664044506..127b293a161c 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -49,6 +49,9 @@ pub(crate) enum GspSeqCmd {
RegPoll(fw::GSP_SEQ_BUF_PAYLOAD_REG_POLL),
DelayUs(fw::GSP_SEQ_BUF_PAYLOAD_DELAY_US),
RegStore(fw::GSP_SEQ_BUF_PAYLOAD_REG_STORE),
+ CoreReset,
+ CoreStart,
+ CoreWaitForHalt,
}
impl GspSeqCmd {
@@ -75,6 +78,11 @@ pub(crate) fn from_fw_cmd(cmd:
&fw::GSP_SEQUENCER_BUFFER_CMD) -> Result<Self> {
// SAFETY: We're using the union field that corresponds to
the opCode.
Ok(GspSeqCmd::RegStore(unsafe { cmd.payload.regStore }))
}
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET =>
Ok(GspSeqCmd::CoreReset),
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START =>
Ok(GspSeqCmd::CoreStart),
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT =>
{
+ Ok(GspSeqCmd::CoreWaitForHalt)
+ }
_ => Err(EINVAL),
}
}
@@ -96,6 +104,9 @@ pub(crate) fn new(data: &[u8], dev:
&device::Device<device::Bound>) -> Result<Se
pub(crate) fn size_bytes(&self) -> usize {
let opcode_size = size_of::<fw::GSP_SEQ_BUF_OPCODE>();
match self {
+ // Each simple command type just adds 4 bytes (opcode_size) for the
header.
+ GspSeqCmd::CoreReset | GspSeqCmd::CoreStart |
GspSeqCmd::CoreWaitForHalt => opcode_size,
+
// For commands with payloads, add the payload size in bytes.
GspSeqCmd::RegWrite(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE>(),
GspSeqCmd::RegModify(_) => {
@@ -232,6 +243,22 @@ fn run(&self, seq: &GspSequencer<'_>)
-> Result {
GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
GspSeqCmd::DelayUs(cmd) => cmd.run(seq),
GspSeqCmd::RegStore(cmd) => cmd.run(seq),
+ GspSeqCmd::CoreReset => {
+ dev_dbg!(seq.dev, "CoreReset\n");
+ seq.gsp_falcon.reset(seq.bar)?;
+ seq.gsp_falcon.dma_reset(seq.bar);
+ Ok(())
+ }
+ GspSeqCmd::CoreStart => {
+ dev_dbg!(seq.dev, "CoreStart\n");
+ seq.gsp_falcon.start(seq.bar)?;
+ Ok(())
+ }
+ GspSeqCmd::CoreWaitForHalt => {
+ dev_dbg!(seq.dev, "CoreWaitForHalt\n");
+ seq.gsp_falcon.wait_till_halted(seq.bar)?;
+ Ok(())
+ }
}
}
}
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 11/12] nova-core: sequencer: Implement core resume operation
Implement core resume operation. This is the last step of the sequencer
resulting in resume of the GSP and proceeding to INIT_DONE stage of GSP
boot.
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/falcon/gsp.rs | 1 -
drivers/gpu/nova-core/gsp/fw.rs | 1 -
drivers/gpu/nova-core/gsp/sequencer.rs | 49 ++++++++++++++++++++++++--
3 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs
b/drivers/gpu/nova-core/falcon/gsp.rs
index e0c0b18ec5bf..391699dc3a8c 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -37,7 +37,6 @@ pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) {
}
/// Checks if GSP reload/resume has completed during the boot process.
- #[expect(dead_code)]
pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout:
Delta) -> Result<bool> {
read_poll_timeout(
|| Ok(regs::NV_PGC6_BSI_SECURE_SCRATCH_14::read(bar)),
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 53e28458cd7d..bb79f92432aa 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -543,7 +543,6 @@ pub(crate) fn element_count(&self) -> u32 {
}
}
-#[expect(unused)]
pub(crate) use r570_144::{
// GSP sequencer run structure with information on how to run the
sequencer.
rpc_run_cpu_sequencer_v17_00,
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs
b/drivers/gpu/nova-core/gsp/sequencer.rs
index 127b293a161c..ecc80f668dc8 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -52,6 +52,7 @@ pub(crate) enum GspSeqCmd {
CoreReset,
CoreStart,
CoreWaitForHalt,
+ CoreResume,
}
impl GspSeqCmd {
@@ -83,6 +84,7 @@ pub(crate) fn from_fw_cmd(cmd:
&fw::GSP_SEQUENCER_BUFFER_CMD) -> Result<Self> {
fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT =>
{
Ok(GspSeqCmd::CoreWaitForHalt)
}
+ fw::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME =>
Ok(GspSeqCmd::CoreResume),
_ => Err(EINVAL),
}
}
@@ -105,7 +107,10 @@ pub(crate) fn size_bytes(&self) -> usize {
let opcode_size = size_of::<fw::GSP_SEQ_BUF_OPCODE>();
match self {
// Each simple command type just adds 4 bytes (opcode_size) for the
header.
- GspSeqCmd::CoreReset | GspSeqCmd::CoreStart |
GspSeqCmd::CoreWaitForHalt => opcode_size,
+ GspSeqCmd::CoreReset
+ | GspSeqCmd::CoreStart
+ | GspSeqCmd::CoreWaitForHalt
+ | GspSeqCmd::CoreResume => opcode_size,
// For commands with payloads, add the payload size in bytes.
GspSeqCmd::RegWrite(_) => opcode_size +
size_of::<fw::GSP_SEQ_BUF_PAYLOAD_REG_WRITE>(),
@@ -119,7 +124,6 @@ pub(crate) fn size_bytes(&self) -> usize {
}
}
-#[expect(dead_code)]
pub(crate) struct GspSequencer<'a> {
seq_info: GspSequencerInfo<'a>,
bar: &'a Bar0,
@@ -259,6 +263,47 @@ fn run(&self, seq: &GspSequencer<'_>)
-> Result {
seq.gsp_falcon.wait_till_halted(seq.bar)?;
Ok(())
}
+ GspSeqCmd::CoreResume => {
+ dev_dbg!(seq.dev, "CoreResume\n");
+ // At this point, 'SEC2-RTOS' has been loaded into SEC2
by the sequencer
+ // but neither SEC2-RTOS nor GSP-RM is running yet. This part
of the
+ // sequencer will start both.
+
+ // Reset the GSP to prepare it for resuming.
+ seq.gsp_falcon.reset(seq.bar)?;
+
+ // Write the libOS DMA handle to GSP mailboxes.
+ seq.gsp_falcon.write_mailboxes(
+ seq.bar,
+ Some(seq.libos_dma_handle as u32),
+ Some((seq.libos_dma_handle >> 32) as u32),
+ )?;
+
+ // Start the SEC2 falcon which will trigger GSP-RM to resume on
the GSP.
+ seq.sec2_falcon.start(seq.bar)?;
+
+ // Poll until GSP-RM reload/resume has completed (up to 2
seconds).
+ seq.gsp_falcon
+ .check_reload_completed(seq.bar, Delta::from_secs(2))?;
+
+ // Verify SEC2 completed successfully by checking its mailbox
for errors.
+ let mbox0 = seq.sec2_falcon.read_mailbox0(seq.bar)?;
+ if mbox0 != 0 {
+ dev_err!(seq.dev, "Sequencer: sec2 errors:
{:?}\n", mbox0);
+ return Err(EIO);
+ }
+
+ // Configure GSP with the bootloader version.
+ seq.gsp_falcon
+ .write_os_version(seq.bar,
seq.gsp_fw.bootloader.app_version);
+
+ // Verify the GSP's RISC-V core is active indicating
successful GSP boot.
+ if !seq.gsp_falcon.is_riscv_active(seq.bar) {
+ dev_err!(seq.dev, "Sequencer: RISC-V core is not
active\n");
+ return Err(EIO);
+ }
+ Ok(())
+ }
}
}
}
--
2.34.1
Joel Fernandes
2025-Nov-02 23:59 UTC
[PATCH v2 12/12] gpu: nova-core: gsp: Wait for gsp initialization to complete
From: Alistair Popple <apopple at nvidia.com>
This adds the GSP init done command to wait for GSP initialization
to complete. Once this command has been received the GSP is fully
operational and will respond properly to normal RPC commands.
Signed-off-by: Alistair Popple <apopple at nvidia.com>
Co-developed-by: Joel Fernandes <joelagnelf at nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf at nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 8 +++++-
drivers/gpu/nova-core/gsp/commands.rs | 39 +++++++++++++++++++++++++--
2 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs
b/drivers/gpu/nova-core/gsp/boot.rs
index 761020a11153..0dd8099f5f8c 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -18,7 +18,11 @@
FIRMWARE_VERSION,
};
use crate::gpu::Chipset;
-use crate::gsp::commands::{build_registry, set_system_info};
+use crate::gsp::commands::{
+ build_registry,
+ gsp_init_done,
+ set_system_info, //
+};
use crate::gsp::{
sequencer::{
GspSequencer,
@@ -221,6 +225,8 @@ pub(crate) fn boot(
};
GspSequencer::run(&mut self.cmdq, seq_params,
Delta::from_secs(10))?;
+ gsp_init_done(&mut self.cmdq, Delta::from_secs(10))?;
+
Ok(())
}
}
diff --git a/drivers/gpu/nova-core/gsp/commands.rs
b/drivers/gpu/nova-core/gsp/commands.rs
index 338d1695027f..475c6d9410c5 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -4,16 +4,51 @@
use kernel::device;
use kernel::pci;
use kernel::prelude::*;
-use kernel::transmute::AsBytes;
+use kernel::time::Delta;
+use kernel::transmute::{
+ AsBytes,
+ FromBytes, //
+};
use super::fw::commands::*;
use super::fw::MsgFunction;
use crate::driver::Bar0;
use crate::gsp::cmdq::Cmdq;
-use crate::gsp::cmdq::{CommandToGsp, CommandToGspBase,
CommandToGspWithPayload};
+use crate::gsp::cmdq::{
+ CommandToGsp,
+ CommandToGspBase,
+ CommandToGspWithPayload,
+ MessageFromGsp, //
+};
use crate::gsp::GSP_PAGE_SIZE;
use crate::sbuffer::SBufferIter;
+/// Message type for GSP initialization done notification.
+struct GspInitDone {}
+
+// SAFETY: GspInitDone is a zero-sized type with no bytes, therefore it
+// trivially has no uninitialized bytes.
+unsafe impl AsBytes for GspInitDone {}
+
+// SAFETY: GspInitDone is a zero-sized type with no bytes, therefore it
+// trivially has no uninitialized bytes.
+unsafe impl FromBytes for GspInitDone {}
+
+impl MessageFromGsp for GspInitDone {
+ const FUNCTION: MsgFunction = MsgFunction::GspInitDone;
+}
+
+/// Waits for GSP initialization to complete.
+pub(crate) fn gsp_init_done(cmdq: &mut Cmdq, timeout: Delta) -> Result {
+ loop {
+ match cmdq.receive_msg_from_gsp::<GspInitDone, ()>(timeout, |_,
_| Ok(())) {
+ Ok(_) => break Ok(()),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+}
+
// For now we hard-code the registry entries. Future work will allow others to
// be added as module parameters.
const GSP_REGISTRY_NUM_ENTRIES: usize = 3;
--
2.34.1
Timur Tabi
2025-Nov-03 19:12 UTC
[PATCH v2 00/12] nova-core: Complete GSP boot and begin RPC communication
On Sun, 2025-11-02 at 18:59 -0500, Joel Fernandes wrote:> Hello! > These patches a refresh of the series adding support for final stages of the > GSP boot process where a sequencer which inteprets firmware instructions needs > to run to boot the GSP processor, followed by waiting for an INIT_DONE message > from the GSP. > > The patches are based on Alex's github branch which have several prerequisites: > Repo: https://github.com/Gnurou/linux.git?Branch: b4/gsp_boot > > I also dropped several patches (mainly from John that have already been > applied).? Tested on Ampere GA102. We also need the "gpu: nova-core: Add > get_gsp_info() command" patch which I dropped since it needs to be reworked, > and it is not needed for GSP boot on Ampere (but John mentioned it is needed > for Blackwell so we could include it in the Blackwell series or I can try to > include it in this series if I'm respinning).I applied your patches on top of Alex's tree, and when I boot on a GA102 I get this: [ 376.316679] NovaCore 0000:65:00.0: NVIDIA (Chipset: GA102, Architecture: Ampere, Revision: a.1) [ 377.188060] NovaCore 0000:65:00.0: GSP RPC: send: seq# 0, function=Ok(GspSetSystemInfo), length=0x3f0 [ 377.188070] NovaCore 0000:65:00.0: GSP RPC: send: seq# 1, function=Ok(SetRegistry), length=0xc5 [ 378.315960] NovaCore 0000:65:00.0: GSP RPC: receive: seq# 0, function=NOCAT, length=0x50c [ 378.319875] NovaCore 0000:65:00.0: probe with driver NovaCore failed with error -34 Are you sure there are no other patches? The RPC patches can't depend on INIT_DONE being the first response. Getting a NOCAT RPC first is not uncommon.