* [PATCH v13 1/9] gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 10:49 ` Eliot Courtney
2026-06-03 7:30 ` [PATCH v13 2/9] gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure Alexandre Courbot
` (8 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
Add external memory (EMEM) read/write operations to the GPU's FSP falcon
engine. These operations use Falcon PIO (Programmed I/O) to communicate
with the FSP through indirect memory access.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/fsp.rs | 80 ++++++++++++++++++++++++++++++++++---
drivers/gpu/nova-core/regs.rs | 18 +++++++++
2 files changed, 93 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs
index d9f87262e8b1..0956a75ef7aa 100644
--- a/drivers/gpu/nova-core/falcon/fsp.rs
+++ b/drivers/gpu/nova-core/falcon/fsp.rs
@@ -6,12 +6,26 @@
//! The FSP falcon handles secure boot and Chain of Trust operations
//! on Hopper and Blackwell architectures, replacing SEC2's role.
-use kernel::io::register::RegisterBase;
+use kernel::{
+ io::{
+ register::{
+ RegisterBase,
+ WithBase, //
+ },
+ Io, //
+ },
+ prelude::*,
+};
-use crate::falcon::{
- FalconEngine,
- PFalcon2Base,
- PFalconBase, //
+use crate::{
+ driver::Bar0,
+ falcon::{
+ Falcon,
+ FalconEngine,
+ PFalcon2Base,
+ PFalconBase, //
+ },
+ regs,
};
/// Type specifying the `Fsp` falcon engine. Cannot be instantiated.
@@ -26,3 +40,59 @@ impl RegisterBase<PFalcon2Base> for Fsp {
}
impl FalconEngine for Fsp {}
+
+impl Falcon<Fsp> {
+ /// Writes `data` to FSP external memory at offset `0`.
+ ///
+ /// `data` is interpreted as little-endian 32-bit words. Returns `EINVAL`
+ /// if the `data` length is not 4-byte aligned.
+ #[expect(dead_code)]
+ fn write_emem(&mut self, bar: &Bar0, data: &[u8]) -> Result {
+ if data.len() % 4 != 0 {
+ return Err(EINVAL);
+ }
+
+ // Begin a write burst at offset `0`, auto-incrementing on each write.
+ bar.write(
+ WithBase::of::<Fsp>(),
+ regs::NV_PFALCON_FALCON_EMEMC::zeroed().with_aincw(true),
+ );
+
+ for chunk in data.chunks_exact(4) {
+ let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
+
+ // Write the next 32-bit `value`; hardware advances the offset.
+ bar.write(
+ WithBase::of::<Fsp>(),
+ regs::NV_PFALCON_FALCON_EMEMD::zeroed().with_data(value),
+ );
+ }
+
+ Ok(())
+ }
+
+ /// Reads FSP external memory from offset `0` into `data`.
+ ///
+ /// `data` is stored as little-endian 32-bit words. Returns `EINVAL` if
+ /// the `data` length is not 4-byte aligned.
+ #[expect(dead_code)]
+ fn read_emem(&mut self, bar: &Bar0, data: &mut [u8]) -> Result {
+ if data.len() % 4 != 0 {
+ return Err(EINVAL);
+ }
+
+ // Begin a read burst at offset `0`, auto-incrementing on each read.
+ bar.write(
+ WithBase::of::<Fsp>(),
+ regs::NV_PFALCON_FALCON_EMEMC::zeroed().with_aincr(true),
+ );
+
+ for chunk in data.chunks_exact_mut(4) {
+ // Read the next 32-bit word; hardware advances the offset.
+ let value = bar.read(regs::NV_PFALCON_FALCON_EMEMD::of::<Fsp>()).data();
+ chunk.copy_from_slice(&value.to_le_bytes());
+ }
+
+ Ok(())
+ }
+}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 2cb1f02f35a4..e602c7860459 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -475,6 +475,24 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) NV_PFALCON_FBIF_CTL(u32) @ PFalconBase + 0x00000624 {
7:7 allow_phys_no_ctx => bool;
}
+
+ // Falcon EMEM PIO registers (used by FSP on Hopper/Blackwell).
+ // These provide the falcon external memory communication interface.
+
+ pub(crate) NV_PFALCON_FALCON_EMEMC(u32) @ PFalconBase + 0x00000ac0 {
+ /// EMEM byte offset (4-byte aligned) within the block.
+ 7:2 offs;
+ /// EMEM block to access.
+ 15:8 blk;
+ /// Auto-increment the offset after each write.
+ 24:24 aincw => bool;
+ /// Auto-increment the offset after each read.
+ 25:25 aincr => bool;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_EMEMD(u32) @ PFalconBase + 0x00000ac4 {
+ 31:0 data => u32;
+ }
}
impl NV_PFALCON_FALCON_DMACTL {
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 1/9] gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations
2026-06-03 7:30 ` [PATCH v13 1/9] gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations Alexandre Courbot
@ 2026-06-03 10:49 ` Eliot Courtney
0 siblings, 0 replies; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 10:49 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> Add external memory (EMEM) read/write operations to the GPU's FSP falcon
> engine. These operations use Falcon PIO (Programmed I/O) to communicate
> with the FSP through indirect memory access.
>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v13 2/9] gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 1/9] gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 10:50 ` Eliot Courtney
2026-06-03 7:30 ` [PATCH v13 3/9] gpu: nova-core: add MCTP/NVDM protocol types for firmware communication Alexandre Courbot
` (7 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
FSP communication uses a pair of non-circular queues in the FSP
falcon's EMEM, one for messages from the driver to FSP and one for
replies, with the driver polling for response data. Add the queue
registers and the low-level helpers used by the higher-level FSP
message layer.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/fsp.rs | 74 +++++++++++++++++++++++++++++++++++--
drivers/gpu/nova-core/regs.rs | 21 +++++++++++
2 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs
index 0956a75ef7aa..a178fc4db1ea 100644
--- a/drivers/gpu/nova-core/falcon/fsp.rs
+++ b/drivers/gpu/nova-core/falcon/fsp.rs
@@ -8,6 +8,7 @@
use kernel::{
io::{
+ poll::read_poll_timeout,
register::{
RegisterBase,
WithBase, //
@@ -15,6 +16,7 @@
Io, //
},
prelude::*,
+ time::Delta,
};
use crate::{
@@ -25,9 +27,13 @@
PFalcon2Base,
PFalconBase, //
},
- regs,
+ num,
+ regs, //
};
+/// FSP message timeout in milliseconds.
+const FSP_MSG_TIMEOUT_MS: i64 = 2000;
+
/// Type specifying the `Fsp` falcon engine. Cannot be instantiated.
pub(crate) struct Fsp(());
@@ -46,7 +52,6 @@ impl Falcon<Fsp> {
///
/// `data` is interpreted as little-endian 32-bit words. Returns `EINVAL`
/// if the `data` length is not 4-byte aligned.
- #[expect(dead_code)]
fn write_emem(&mut self, bar: &Bar0, data: &[u8]) -> Result {
if data.len() % 4 != 0 {
return Err(EINVAL);
@@ -75,7 +80,6 @@ fn write_emem(&mut self, bar: &Bar0, data: &[u8]) -> Result {
///
/// `data` is stored as little-endian 32-bit words. Returns `EINVAL` if
/// the `data` length is not 4-byte aligned.
- #[expect(dead_code)]
fn read_emem(&mut self, bar: &Bar0, data: &mut [u8]) -> Result {
if data.len() % 4 != 0 {
return Err(EINVAL);
@@ -95,4 +99,68 @@ fn read_emem(&mut self, bar: &Bar0, data: &mut [u8]) -> Result {
Ok(())
}
+
+ /// Poll FSP for incoming data.
+ ///
+ /// Returns the size of available data in bytes, or 0 if no data is available.
+ ///
+ /// The FSP message queue is not circular. Pointers are reset to 0 after each
+ /// message exchange, so `tail >= head` is always true when data is present.
+ fn poll_msgq(&self, bar: &Bar0) -> u32 {
+ let head = bar.read(regs::NV_PFSP_MSGQ_HEAD).address();
+ let tail = bar.read(regs::NV_PFSP_MSGQ_TAIL).address();
+
+ if head == tail {
+ return 0;
+ }
+
+ // TAIL points at last DWORD written, so add 4 to get total size.
+ tail.saturating_sub(head).saturating_add(4)
+ }
+
+ /// Writes `packet` to FSP EMEM and updates the queue pointers to notify FSP.
+ ///
+ /// Returns `EINVAL` if `packet` is empty or its length is not 4-byte aligned.
+ #[expect(dead_code)]
+ pub(crate) fn send_msg(&mut self, bar: &Bar0, packet: &[u8]) -> Result {
+ if packet.is_empty() {
+ return Err(EINVAL);
+ }
+
+ self.write_emem(bar, packet)?;
+
+ // Update queue pointers. TAIL points at the last DWORD written.
+ let tail_offset = u32::try_from(packet.len() - 4).map_err(|_| EINVAL)?;
+ bar.write_reg(regs::NV_PFSP_QUEUE_TAIL::zeroed().with_address(tail_offset));
+ bar.write_reg(regs::NV_PFSP_QUEUE_HEAD::zeroed().with_address(0));
+
+ Ok(())
+ }
+
+ /// Reads the next message from FSP EMEM into a newly-allocated buffer and resets the queue
+ /// pointers.
+ ///
+ /// Returns `ETIMEDOUT` if no message was available until timeout, or a regular error code if a
+ /// memory allocation error occurred.
+ #[expect(dead_code)]
+ pub(crate) fn recv_msg(&mut self, bar: &Bar0) -> Result<KVec<u8>> {
+ let msg_size = read_poll_timeout(
+ || Ok(self.poll_msgq(bar)),
+ |&size| size > 0,
+ Delta::from_millis(10),
+ Delta::from_millis(FSP_MSG_TIMEOUT_MS),
+ )
+ .map(num::u32_as_usize)?;
+
+ let mut buffer = KVec::<u8>::new();
+ buffer.resize(msg_size, 0, GFP_KERNEL)?;
+
+ self.read_emem(bar, &mut buffer)?;
+
+ // Reset message queue pointers after reading.
+ bar.write_reg(regs::NV_PFSP_MSGQ_TAIL::zeroed().with_address(0));
+ bar.write_reg(regs::NV_PFSP_MSGQ_HEAD::zeroed().with_address(0));
+
+ Ok(buffer)
+ }
}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index e602c7860459..c3c50b8d8f10 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -579,6 +579,27 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
}
}
+// FSP (Foundation Security Processor) queue registers for Hopper/Blackwell Chain of Trust.
+// These registers manage falcon EMEM communication queues.
+
+register! {
+ pub(crate) NV_PFSP_QUEUE_HEAD(u32) @ 0x008f2c00 {
+ 31:0 address => u32;
+ }
+
+ pub(crate) NV_PFSP_QUEUE_TAIL(u32) @ 0x008f2c04 {
+ 31:0 address => u32;
+ }
+
+ pub(crate) NV_PFSP_MSGQ_HEAD(u32) @ 0x008f2c80 {
+ 31:0 address => u32;
+ }
+
+ pub(crate) NV_PFSP_MSGQ_TAIL(u32) @ 0x008f2c84 {
+ 31:0 address => u32;
+ }
+}
+
// The modules below provide registers that are not identical on all supported chips. They should
// only be used in HAL modules.
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 2/9] gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure
2026-06-03 7:30 ` [PATCH v13 2/9] gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure Alexandre Courbot
@ 2026-06-03 10:50 ` Eliot Courtney
0 siblings, 0 replies; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 10:50 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> FSP communication uses a pair of non-circular queues in the FSP
> falcon's EMEM, one for messages from the driver to FSP and one for
> replies, with the driver polling for response data. Add the queue
> registers and the low-level helpers used by the higher-level FSP
> message layer.
>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v13 3/9] gpu: nova-core: add MCTP/NVDM protocol types for firmware communication
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 1/9] gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 2/9] gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 4/9] gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging Alexandre Courbot
` (6 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
Add the MCTP (Management Component Transport Protocol) and NVDM (NVIDIA
Data Model) wire-format types used for communication between the kernel
driver and GPU firmware processors.
This includes typed MCTP transport headers, NVDM message headers, and
NVDM message type identifiers. Both the FSP boot path and the upcoming
GSP RPC message queue share this protocol layer.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/mctp.rs | 90 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/nova_core.rs | 1 +
2 files changed, 91 insertions(+)
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
new file mode 100644
index 000000000000..90e289d4c3fe
--- /dev/null
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! MCTP/NVDM protocol types for NVIDIA GPU firmware communication.
+//!
+//! MCTP (Management Component Transport Protocol) carries NVDM (NVIDIA
+//! Data Model) messages between the kernel driver and GPU firmware processors
+//! such as FSP and GSP.
+
+#![expect(dead_code)]
+
+use kernel::pci::Vendor;
+
+/// NVDM message type identifiers carried over MCTP.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+#[repr(u8)]
+pub(crate) enum NvdmType {
+ #[default]
+ /// Chain of Trust boot message.
+ Cot = 0x14,
+ /// FSP command response.
+ FspResponse = 0x15,
+}
+
+impl TryFrom<u8> for NvdmType {
+ type Error = u8;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ x if x == u8::from(Self::Cot) => Ok(Self::Cot),
+ x if x == u8::from(Self::FspResponse) => Ok(Self::FspResponse),
+ _ => Err(value),
+ }
+ }
+}
+
+impl From<NvdmType> for u8 {
+ fn from(value: NvdmType) -> Self {
+ value as u8
+ }
+}
+
+bitfield! {
+ pub(crate) struct MctpHeader(u32), "MCTP transport header for NVIDIA firmware messages." {
+ 31:31 som as bool, "Start-of-message bit.";
+ 30:30 eom as bool, "End-of-message bit.";
+ 29:28 seq as u8, "Packet sequence number.";
+ 23:16 seid as u8, "Source endpoint ID.";
+ }
+}
+
+impl MctpHeader {
+ /// Builds a single-packet MCTP header (`SOM=1`, `EOM=1`, `SEQ=0`, `SEID=0`).
+ pub(crate) fn single_packet() -> Self {
+ Self::default().set_som(true).set_eom(true)
+ }
+
+ /// Returns whether this is a complete single-packet message (`SOM=1` and `EOM=1`).
+ pub(crate) fn is_single_packet(self) -> bool {
+ self.som() && self.eom()
+ }
+}
+
+/// MCTP message type for PCI vendor-defined messages.
+const MSG_TYPE_VENDOR_PCI: u8 = 0x7e;
+
+bitfield! {
+ pub(crate) struct NvdmHeader(u32), "NVIDIA Vendor-Defined Message header over MCTP." {
+ 31:24 nvdm_type as u8 ?=> NvdmType, "NVDM message type.";
+ 23:8 vendor_id as u16, "PCI vendor ID.";
+ 6:0 msg_type as u8, "MCTP vendor-defined message type.";
+ }
+}
+
+impl NvdmHeader {
+ /// Builds an NVDM header for the given message type.
+ pub(crate) fn new(nvdm_type: NvdmType) -> Self {
+ Self::default()
+ .set_msg_type(MSG_TYPE_VENDOR_PCI)
+ .set_vendor_id(Vendor::NVIDIA.as_raw())
+ .set_nvdm_type(nvdm_type)
+ }
+
+ /// Validates this header against the expected NVIDIA NVDM format and type.
+ pub(crate) fn validate(self, expected_type: NvdmType) -> bool {
+ self.msg_type() == MSG_TYPE_VENDOR_PCI
+ && self.vendor_id() == Vendor::NVIDIA.as_raw()
+ && matches!(self.nvdm_type(), Ok(nvdm_type) if nvdm_type == expected_type)
+ }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index 7b6c331da10e..9f0199f7b38c 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -20,6 +20,7 @@
mod fsp;
mod gpu;
mod gsp;
+mod mctp;
#[macro_use]
mod num;
mod regs;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v13 4/9] gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (2 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 3/9] gpu: nova-core: add MCTP/NVDM protocol types for firmware communication Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 10:57 ` Eliot Courtney
2026-06-03 7:30 ` [PATCH v13 5/9] gpu: nova-core: Hopper/Blackwell: select FSP Chain of Trust version Alexandre Courbot
` (5 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
FSP exchanges are request/response: the driver sends an MCTP/NVDM
message and must match the reply against the request before acting on
it. Add the synchronous send-and-wait path that validates the response
transport and message headers and confirms the reply corresponds to the
request that was sent.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/fsp.rs | 2 -
drivers/gpu/nova-core/fsp.rs | 105 +++++++++++++++++++++++++++++++++++-
2 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs
index a178fc4db1ea..bcb88f64c9f8 100644
--- a/drivers/gpu/nova-core/falcon/fsp.rs
+++ b/drivers/gpu/nova-core/falcon/fsp.rs
@@ -121,7 +121,6 @@ fn poll_msgq(&self, bar: &Bar0) -> u32 {
/// Writes `packet` to FSP EMEM and updates the queue pointers to notify FSP.
///
/// Returns `EINVAL` if `packet` is empty or its length is not 4-byte aligned.
- #[expect(dead_code)]
pub(crate) fn send_msg(&mut self, bar: &Bar0, packet: &[u8]) -> Result {
if packet.is_empty() {
return Err(EINVAL);
@@ -142,7 +141,6 @@ pub(crate) fn send_msg(&mut self, bar: &Bar0, packet: &[u8]) -> Result {
///
/// Returns `ETIMEDOUT` if no message was available until timeout, or a regular error code if a
/// memory allocation error occurred.
- #[expect(dead_code)]
pub(crate) fn recv_msg(&mut self, bar: &Bar0) -> Result<KVec<u8>> {
let msg_size = read_poll_timeout(
|| Ok(self.poll_msgq(bar)),
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 908dc112aa6f..cedfea173e50 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -11,7 +11,11 @@
device,
io::poll::read_poll_timeout,
prelude::*,
- time::Delta, //
+ time::Delta,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
};
use crate::{
@@ -22,18 +26,52 @@
},
firmware::fsp::FspFirmware,
gpu::Chipset,
+ mctp::{
+ MctpHeader,
+ NvdmHeader,
+ NvdmType, //
+ },
regs, //
};
mod hal;
+/// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`).
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct NvdmPayloadCommandResponse {
+ task_id: u32,
+ command_nvdm_type: u32,
+ error_code: u32,
+}
+
+/// Complete FSP response structure with MCTP and NVDM headers.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct FspResponse {
+ mctp_header: MctpHeader,
+ nvdm_header: NvdmHeader,
+ response: NvdmPayloadCommandResponse,
+}
+
+// SAFETY: FspResponse is a packed C struct with only integral fields.
+unsafe impl FromBytes for FspResponse {}
+
+/// Trait implemented by types representing a message to send to FSP.
+///
+/// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
+/// a given message, following the same pattern as GSP's `CommandToGsp`.
+pub(crate) trait MessageToFsp: AsBytes {
+ /// NVDM type identifying this message to FSP.
+ const NVDM_TYPE: NvdmType;
+}
+
/// FSP interface for Hopper/Blackwell GPUs.
///
/// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
/// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent
/// Chain of Trust boot.
pub(crate) struct Fsp {
- #[expect(dead_code)]
falcon: Falcon<FspEngine>,
#[expect(dead_code)]
fsp_fw: FspFirmware,
@@ -69,4 +107,67 @@ pub(crate) fn wait_secure_boot(
Ok(Fsp { falcon, fsp_fw })
}
+
+ /// Sends a message to FSP and waits for the response.
+ #[expect(dead_code)]
+ fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result
+ where
+ M: MessageToFsp,
+ {
+ self.falcon.send_msg(bar, msg.as_bytes())?;
+
+ let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| {
+ dev_err!(dev, "FSP response error: {:?}\n", e);
+ })?;
+
+ let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
+ dev_err!(dev, "FSP response too small: {}\n", response_buf.len());
+ EIO
+ })?;
+
+ let mctp_header = response.mctp_header;
+ let nvdm_header = response.nvdm_header;
+ let command_nvdm_type = response.response.command_nvdm_type;
+ let error_code = response.response.error_code;
+
+ if !mctp_header.is_single_packet() {
+ dev_err!(
+ dev,
+ "Unexpected MCTP header in FSP reply: {:x?}\n",
+ mctp_header,
+ );
+ return Err(EIO);
+ }
+
+ if !nvdm_header.validate(NvdmType::FspResponse) {
+ dev_err!(
+ dev,
+ "Unexpected NVDM header in FSP reply: {:x?}\n",
+ nvdm_header,
+ );
+ return Err(EIO);
+ }
+
+ if command_nvdm_type != u8::from(M::NVDM_TYPE).into() {
+ dev_err!(
+ dev,
+ "Expected NVDM type {:?} in reply, got {:#x}\n",
+ M::NVDM_TYPE,
+ command_nvdm_type
+ );
+ return Err(EIO);
+ }
+
+ if error_code != 0 {
+ dev_err!(
+ dev,
+ "NVDM command {:?} failed with error {:#x}\n",
+ M::NVDM_TYPE,
+ error_code
+ );
+ return Err(EIO);
+ }
+
+ Ok(())
+ }
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 4/9] gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging
2026-06-03 7:30 ` [PATCH v13 4/9] gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging Alexandre Courbot
@ 2026-06-03 10:57 ` Eliot Courtney
0 siblings, 0 replies; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 10:57 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> FSP exchanges are request/response: the driver sends an MCTP/NVDM
> message and must match the reply against the request before acting on
> it. Add the synchronous send-and-wait path that validates the response
> transport and message headers and confirms the reply corresponds to the
> request that was sent.
>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> +/// Trait implemented by types representing a message to send to FSP.
> +///
> +/// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
> +/// a given message, following the same pattern as GSP's `CommandToGsp`.
> +pub(crate) trait MessageToFsp: AsBytes {
> + /// NVDM type identifying this message to FSP.
> + const NVDM_TYPE: NvdmType;
> +}
> +
nit: this trait can be private (and actually can't meaningfully be used
currently anyway since `send_sync_fsp` is private)
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v13 5/9] gpu: nova-core: Hopper/Blackwell: select FSP Chain of Trust version
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (3 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 4/9] gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot Alexandre Courbot
` (4 subsequent siblings)
9 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
The FSP Chain of Trust handshake is versioned: Hopper speaks version 1
and Blackwell speaks version 2. Provide the version through the FSP HAL
so the boot message carries the value FSP expects, and so chipsets that
do not use FSP need not express a version at all.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/fsp/hal.rs | 8 +++++++-
drivers/gpu/nova-core/fsp/hal/gb100.rs | 23 +++++++++++++++++++++++
drivers/gpu/nova-core/fsp/hal/gb202.rs | 4 ++++
drivers/gpu/nova-core/fsp/hal/gh100.rs | 15 ++++++++++++---
4 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/nova-core/fsp/hal.rs b/drivers/gpu/nova-core/fsp/hal.rs
index fc5ebb749c59..8aebe1800a64 100644
--- a/drivers/gpu/nova-core/fsp/hal.rs
+++ b/drivers/gpu/nova-core/fsp/hal.rs
@@ -9,19 +9,25 @@
},
};
+mod gb100;
mod gb202;
mod gh100;
pub(super) trait FspHal {
/// Returns the secure boot status from the architecture-specific `NV_THERM_I2CS_SCRATCH` register.
fn fsp_boot_status(&self, bar: &Bar0) -> u32;
+
+ /// Returns the FSP Chain of Trust protocol version this chipset advertises.
+ #[expect(dead_code)]
+ fn cot_version(&self) -> u16;
}
/// Returns the FSP HAL, or `None` if the architecture doesn't support FSP.
pub(super) fn fsp_hal(chipset: Chipset) -> Option<&'static dyn FspHal> {
match chipset.arch() {
Architecture::Turing | Architecture::Ampere | Architecture::Ada => None,
- Architecture::Hopper | Architecture::BlackwellGB10x => Some(gh100::GH100_HAL),
+ Architecture::Hopper => Some(gh100::GH100_HAL),
+ Architecture::BlackwellGB10x => Some(gb100::GB100_HAL),
Architecture::BlackwellGB20x => Some(gb202::GB202_HAL),
}
}
diff --git a/drivers/gpu/nova-core/fsp/hal/gb100.rs b/drivers/gpu/nova-core/fsp/hal/gb100.rs
new file mode 100644
index 000000000000..d50aaba0a84f
--- /dev/null
+++ b/drivers/gpu/nova-core/fsp/hal/gb100.rs
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+use crate::{
+ driver::Bar0,
+ fsp::hal::FspHal, //
+};
+
+struct Gb100;
+
+impl FspHal for Gb100 {
+ fn fsp_boot_status(&self, bar: &Bar0) -> u32 {
+ // GB10x shares Hopper's FSP secure boot status register.
+ super::gh100::fsp_boot_status_gh100(bar)
+ }
+
+ fn cot_version(&self) -> u16 {
+ 2
+ }
+}
+
+const GB100: Gb100 = Gb100;
+pub(super) const GB100_HAL: &dyn FspHal = &GB100;
diff --git a/drivers/gpu/nova-core/fsp/hal/gb202.rs b/drivers/gpu/nova-core/fsp/hal/gb202.rs
index 2f08b6c9f308..2bca76c8fd64 100644
--- a/drivers/gpu/nova-core/fsp/hal/gb202.rs
+++ b/drivers/gpu/nova-core/fsp/hal/gb202.rs
@@ -17,6 +17,10 @@ fn fsp_boot_status(&self, bar: &Bar0) -> u32 {
.fsp_boot_complete()
.into()
}
+
+ fn cot_version(&self) -> u16 {
+ 2
+ }
}
const GB202: Gb202 = Gb202;
diff --git a/drivers/gpu/nova-core/fsp/hal/gh100.rs b/drivers/gpu/nova-core/fsp/hal/gh100.rs
index 290fb55a81da..c38a7e96eb60 100644
--- a/drivers/gpu/nova-core/fsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/fsp/hal/gh100.rs
@@ -11,11 +11,20 @@
struct Gh100;
+/// Reads the FSP secure boot status from the Hopper/GB10x thermal scratch register.
+pub(super) fn fsp_boot_status_gh100(bar: &Bar0) -> u32 {
+ bar.read(regs::gh100::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE)
+ .fsp_boot_complete()
+ .into()
+}
+
impl FspHal for Gh100 {
fn fsp_boot_status(&self, bar: &Bar0) -> u32 {
- bar.read(regs::gh100::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE)
- .fsp_boot_complete()
- .into()
+ fsp_boot_status_gh100(bar)
+ }
+
+ fn cot_version(&self) -> u16 {
+ 1
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (4 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 5/9] gpu: nova-core: Hopper/Blackwell: select FSP Chain of Trust version Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 12:47 ` Eliot Courtney
2026-06-03 7:30 ` [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling Alexandre Courbot
` (3 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
Build and send the Chain of Trust message to FSP, bundling the
DMA-coherent boot parameters that FSP reads at boot time.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware/fsp.rs | 2 -
drivers/gpu/nova-core/fsp.rs | 145 +++++++++++++++++++++-
drivers/gpu/nova-core/fsp/hal.rs | 1 -
drivers/gpu/nova-core/gsp.rs | 1 +
drivers/gpu/nova-core/gsp/fw.rs | 65 ++++++++++
drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs | 82 ++++++++++++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 23 +++-
drivers/gpu/nova-core/mctp.rs | 2 -
8 files changed, 308 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs
index 9b211426a75a..6eaf1c684b9d 100644
--- a/drivers/gpu/nova-core/firmware/fsp.rs
+++ b/drivers/gpu/nova-core/firmware/fsp.rs
@@ -39,10 +39,8 @@ pub(crate) struct FmcSignatures {
pub(crate) struct FspFirmware {
/// FMC firmware image data (only the "image" ELF section).
- #[expect(dead_code)]
pub(crate) fmc_image: Coherent<[u8]>,
/// FMC firmware signatures.
- #[expect(dead_code)]
pub(crate) fmc_sigs: KBox<FmcSignatures>,
}
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index cedfea173e50..883ac4f8b811 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -9,8 +9,14 @@
use kernel::{
device,
+ dma::Coherent,
io::poll::read_poll_timeout,
prelude::*,
+ ptr::{
+ Alignable,
+ Alignment, //
+ },
+ sizes::SZ_2M,
time::Delta,
transmute::{
AsBytes,
@@ -24,13 +30,19 @@
fsp::Fsp as FspEngine,
Falcon, //
},
- firmware::fsp::FspFirmware,
+ fb::FbLayout,
+ firmware::fsp::{
+ FmcSignatures,
+ FspFirmware, //
+ },
gpu::Chipset,
+ gsp::GspFmcBootParams,
mctp::{
MctpHeader,
NvdmHeader,
NvdmType, //
},
+ num,
regs, //
};
@@ -66,6 +78,114 @@ pub(crate) trait MessageToFsp: AsBytes {
const NVDM_TYPE: NvdmType;
}
+/// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main
+/// message body sent to FSP for Chain of Trust boot.
+#[repr(C, packed)]
+#[derive(Clone, Copy, Zeroable)]
+struct NvdmPayloadCot {
+ version: u16,
+ size: u16,
+ gsp_fmc_sysmem_offset: u64,
+ frts_sysmem_offset: u64,
+ frts_sysmem_size: u32,
+ frts_vidmem_offset: u64,
+ frts_vidmem_size: u32,
+ sigs: FmcSignatures,
+ gsp_boot_args_sysmem_offset: u64,
+}
+
+/// Complete FSP message structure with MCTP and NVDM headers.
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct FspMessage {
+ mctp_header: MctpHeader,
+ nvdm_header: NvdmHeader,
+ cot: NvdmPayloadCot,
+}
+
+impl FspMessage {
+ /// Returns an in-place initializer for [`FspMessage`].
+ fn new<'a>(
+ fb_layout: &FbLayout,
+ fsp_fw: &'a FspFirmware,
+ args: &'a FmcBootArgs,
+ ) -> Result<impl Init<Self> + 'a> {
+ // frts_offset is relative to FB end: FRTS_location = FB_END - frts_offset
+ let frts_offset = if !args.resume {
+ let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
+
+ frts_reserved_size
+ .align_up(Alignment::new::<SZ_2M>())
+ .ok_or(EINVAL)?
+ } else {
+ 0
+ };
+
+ let frts_size: u32 = if !args.resume {
+ fb_layout.frts.len().try_into()?
+ } else {
+ 0
+ };
+
+ let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version();
+ let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
+
+ Ok(init!(Self {
+ mctp_header: MctpHeader::single_packet(),
+ nvdm_header: NvdmHeader::new(NvdmType::Cot),
+ // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using
+ // `chain`.
+ cot <- pin_init::init_zeroed(),
+ })
+ .chain(move |msg| {
+ msg.cot.version = version;
+ msg.cot.size = size;
+ msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle();
+ msg.cot.frts_vidmem_offset = frts_offset;
+ msg.cot.frts_vidmem_size = frts_size;
+ msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle();
+ msg.cot.sigs = *fsp_fw.fmc_sigs;
+
+ Ok(())
+ }))
+ }
+}
+
+// SAFETY: `FspMessage` is `#[repr(C)]` with no padding, so all of its
+// bytes are initialized.
+unsafe impl AsBytes for FspMessage {}
+
+impl MessageToFsp for FspMessage {
+ const NVDM_TYPE: NvdmType = NvdmType::Cot;
+}
+
+/// Bundled arguments for FMC boot via FSP Chain of Trust.
+pub(crate) struct FmcBootArgs {
+ chipset: Chipset,
+ fmc_boot_params: Coherent<GspFmcBootParams>,
+ resume: bool,
+}
+
+impl FmcBootArgs {
+ /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter
+ /// structure that FSP will read.
+ pub(crate) fn new(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ wpr_meta_addr: u64,
+ libos_addr: u64,
+ resume: bool,
+ ) -> Result<Self> {
+ let init = GspFmcBootParams::new(wpr_meta_addr, libos_addr);
+
+ Ok(Self {
+ chipset,
+ fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?,
+ resume,
+ })
+ }
+}
+
/// FSP interface for Hopper/Blackwell GPUs.
///
/// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
@@ -73,7 +193,6 @@ pub(crate) trait MessageToFsp: AsBytes {
/// Chain of Trust boot.
pub(crate) struct Fsp {
falcon: Falcon<FspEngine>,
- #[expect(dead_code)]
fsp_fw: FspFirmware,
}
@@ -109,7 +228,6 @@ pub(crate) fn wait_secure_boot(
}
/// Sends a message to FSP and waits for the response.
- #[expect(dead_code)]
fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result
where
M: MessageToFsp,
@@ -170,4 +288,25 @@ fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Res
Ok(())
}
+
+ /// Boots GSP FMC via FSP Chain of Trust.
+ ///
+ /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
+ /// to FSP, and waits for the response.
+ pub(crate) fn boot_fmc(
+ &mut self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ fb_layout: &FbLayout,
+ args: &FmcBootArgs,
+ ) -> Result {
+ dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
+
+ let msg = KBox::init(FspMessage::new(fb_layout, &self.fsp_fw, args)?, GFP_KERNEL)?;
+
+ self.send_sync_fsp(dev, bar, &*msg)?;
+
+ dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
+ Ok(())
+ }
}
diff --git a/drivers/gpu/nova-core/fsp/hal.rs b/drivers/gpu/nova-core/fsp/hal.rs
index 8aebe1800a64..86c595d70c8e 100644
--- a/drivers/gpu/nova-core/fsp/hal.rs
+++ b/drivers/gpu/nova-core/fsp/hal.rs
@@ -18,7 +18,6 @@ pub(super) trait FspHal {
fn fsp_boot_status(&self, bar: &Bar0) -> u32;
/// Returns the FSP Chain of Trust protocol version this chipset advertises.
- #[expect(dead_code)]
fn cot_version(&self) -> u16;
}
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 1885cfa5cb38..69175ca3315c 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -25,6 +25,7 @@
mod sequencer;
pub(crate) use fw::{
+ GspFmcBootParams,
GspFwWprMeta,
LibosParams, //
};
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 0c54e8bf4bb3..4db0cfa4dc4d 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -934,3 +934,68 @@ fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
})
}
}
+
+#[repr(u32)]
+pub(crate) enum GspDmaTarget {
+ #[expect(dead_code)]
+ LocalFb = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB,
+ CoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM,
+ NoncoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM,
+}
+
+type GspAcrBootGspRmParams = bindings::GSP_ACR_BOOT_GSP_RM_PARAMS;
+
+impl GspAcrBootGspRmParams {
+ fn new(target: GspDmaTarget, wpr_meta_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let params = init!(Self {
+ target: target as u32,
+ gspRmDescSize: num::usize_into_u32::<{ size_of::<GspFwWprMeta>() }>(),
+ gspRmDescOffset: wpr_meta_addr,
+ bIsGspRmBoot: 1,
+ wprCarveoutOffset: 0,
+ wprCarveoutSize: 0,
+ __bindgen_padding_0: Default::default(),
+ });
+
+ params
+ }
+}
+
+type GspRmParams = bindings::GSP_RM_PARAMS;
+
+impl GspRmParams {
+ fn new(target: GspDmaTarget, libos_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let params = init!(Self {
+ target: target as u32,
+ bootArgsOffset: libos_addr,
+ __bindgen_padding_0: Default::default(),
+ });
+
+ params
+ }
+}
+
+pub(crate) type GspFmcBootParams = bindings::GSP_FMC_BOOT_PARAMS;
+
+// SAFETY: Padding is explicit and will not contain uninitialized data.
+unsafe impl AsBytes for GspFmcBootParams {}
+// SAFETY: This struct only contains integer types for which all bit patterns are valid.
+unsafe impl FromBytes for GspFmcBootParams {}
+
+impl GspFmcBootParams {
+ pub(crate) fn new(wpr_meta_addr: u64, libos_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let init = init!(Self {
+ // Blackwell FSP obtains WPR info from other sources, so
+ // wprCarveoutOffset and wprCarveoutSize are left zero.
+ bootGspRmParams <- GspAcrBootGspRmParams::new(GspDmaTarget::CoherentSystem,
+ wpr_meta_addr),
+ gspRmParams <- GspRmParams::new(GspDmaTarget::NoncoherentSystem, libos_addr),
+ ..Zeroable::init_zeroed()
+ });
+
+ init
+ }
+}
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 1d592bd3f9ed..ea350f9b2cc4 100644
--- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
+++ b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
@@ -883,6 +883,88 @@ fn default() -> Self {
}
}
}
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB: GSP_DMA_TARGET = 0;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM: GSP_DMA_TARGET = 1;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM: GSP_DMA_TARGET = 2;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COUNT: GSP_DMA_TARGET = 3;
+pub type GSP_DMA_TARGET = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_INIT_PARAMS {
+ pub regkeys: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ACR_BOOT_GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub gspRmDescSize: u32_,
+ pub gspRmDescOffset: u64_,
+ pub wprCarveoutOffset: u64_,
+ pub wprCarveoutSize: u32_,
+ pub bIsGspRmBoot: u8_,
+ pub __bindgen_padding_0: [u8; 3usize],
+}
+impl Default for GSP_ACR_BOOT_GSP_RM_PARAMS {
+ 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()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub bootArgsOffset: u64_,
+}
+impl Default for GSP_RM_PARAMS {
+ 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()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_SPDM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub payloadBufferOffset: u64_,
+ pub payloadBufferSize: u32_,
+ pub __bindgen_padding_1: [u8; 4usize],
+}
+impl Default for GSP_SPDM_PARAMS {
+ 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()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_BOOT_PARAMS {
+ pub initParams: GSP_FMC_INIT_PARAMS,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub bootGspRmParams: GSP_ACR_BOOT_GSP_RM_PARAMS,
+ pub gspRmParams: GSP_RM_PARAMS,
+ pub gspSpdmParams: GSP_SPDM_PARAMS,
+}
+impl Default for GSP_FMC_BOOT_PARAMS {
+ 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()
+ }
+ }
+}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
pub struct rpc_unloading_guest_driver_v1F_07 {
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index b25970dd4561..f41f3fea15ff 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -20,7 +20,10 @@
fsp::FspFirmware,
FIRMWARE_VERSION, //
},
- fsp::Fsp,
+ fsp::{
+ FmcBootArgs,
+ Fsp, //
+ },
gpu::Chipset,
gsp::{
boot::BootUnloadGuard,
@@ -39,17 +42,27 @@ impl GspHal for Gh100 {
/// the GSP boot internally - no manual GSP reset/boot is needed.
fn boot<'a>(
&self,
- _gsp: &'a Gsp,
+ gsp: &'a Gsp,
dev: &'a device::Device<device::Bound>,
bar: &'a Bar0,
chipset: Chipset,
- _fb_layout: &FbLayout,
- _wpr_meta: &Coherent<GspFwWprMeta>,
+ fb_layout: &FbLayout,
+ wpr_meta: &Coherent<GspFwWprMeta>,
_gsp_falcon: &'a Falcon<GspEngine>,
_sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
- let _fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
+ let mut fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
+
+ let args = FmcBootArgs::new(
+ dev,
+ chipset,
+ wpr_meta.dma_handle(),
+ gsp.libos.dma_handle(),
+ false,
+ )?;
+
+ fsp.boot_fmc(dev, bar, fb_layout, &args)?;
Err(ENOTSUPP)
}
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index 90e289d4c3fe..482786e07bc7 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -7,8 +7,6 @@
//! Data Model) messages between the kernel driver and GPU firmware processors
//! such as FSP and GSP.
-#![expect(dead_code)]
-
use kernel::pci::Vendor;
/// NVDM message type identifiers carried over MCTP.
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot
2026-06-03 7:30 ` [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot Alexandre Courbot
@ 2026-06-03 12:47 ` Eliot Courtney
2026-06-03 13:35 ` Alexandre Courbot
0 siblings, 1 reply; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 12:47 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> Build and send the Chain of Trust message to FSP, bundling the
> DMA-coherent boot parameters that FSP reads at boot time.
>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> drivers/gpu/nova-core/firmware/fsp.rs | 2 -
> drivers/gpu/nova-core/fsp.rs | 145 +++++++++++++++++++++-
> drivers/gpu/nova-core/fsp/hal.rs | 1 -
> drivers/gpu/nova-core/gsp.rs | 1 +
> drivers/gpu/nova-core/gsp/fw.rs | 65 ++++++++++
> drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs | 82 ++++++++++++
> drivers/gpu/nova-core/gsp/hal/gh100.rs | 23 +++-
> drivers/gpu/nova-core/mctp.rs | 2 -
> 8 files changed, 308 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs
> index 9b211426a75a..6eaf1c684b9d 100644
> --- a/drivers/gpu/nova-core/firmware/fsp.rs
> +++ b/drivers/gpu/nova-core/firmware/fsp.rs
> @@ -39,10 +39,8 @@ pub(crate) struct FmcSignatures {
>
> pub(crate) struct FspFirmware {
> /// FMC firmware image data (only the "image" ELF section).
> - #[expect(dead_code)]
> pub(crate) fmc_image: Coherent<[u8]>,
> /// FMC firmware signatures.
> - #[expect(dead_code)]
> pub(crate) fmc_sigs: KBox<FmcSignatures>,
> }
>
> diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
> index cedfea173e50..883ac4f8b811 100644
> --- a/drivers/gpu/nova-core/fsp.rs
> +++ b/drivers/gpu/nova-core/fsp.rs
> @@ -9,8 +9,14 @@
>
> use kernel::{
> device,
> + dma::Coherent,
> io::poll::read_poll_timeout,
> prelude::*,
> + ptr::{
> + Alignable,
> + Alignment, //
> + },
> + sizes::SZ_2M,
> time::Delta,
> transmute::{
> AsBytes,
> @@ -24,13 +30,19 @@
> fsp::Fsp as FspEngine,
> Falcon, //
> },
> - firmware::fsp::FspFirmware,
> + fb::FbLayout,
> + firmware::fsp::{
> + FmcSignatures,
> + FspFirmware, //
> + },
> gpu::Chipset,
> + gsp::GspFmcBootParams,
> mctp::{
> MctpHeader,
> NvdmHeader,
> NvdmType, //
> },
> + num,
> regs, //
> };
>
> @@ -66,6 +78,114 @@ pub(crate) trait MessageToFsp: AsBytes {
> const NVDM_TYPE: NvdmType;
> }
>
> +/// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main
> +/// message body sent to FSP for Chain of Trust boot.
> +#[repr(C, packed)]
> +#[derive(Clone, Copy, Zeroable)]
> +struct NvdmPayloadCot {
> + version: u16,
> + size: u16,
> + gsp_fmc_sysmem_offset: u64,
> + frts_sysmem_offset: u64,
> + frts_sysmem_size: u32,
> + frts_vidmem_offset: u64,
> + frts_vidmem_size: u32,
> + sigs: FmcSignatures,
> + gsp_boot_args_sysmem_offset: u64,
> +}
> +
> +/// Complete FSP message structure with MCTP and NVDM headers.
> +#[repr(C)]
> +#[derive(Clone, Copy)]
> +struct FspMessage {
> + mctp_header: MctpHeader,
> + nvdm_header: NvdmHeader,
> + cot: NvdmPayloadCot,
> +}
> +
> +impl FspMessage {
> + /// Returns an in-place initializer for [`FspMessage`].
> + fn new<'a>(
> + fb_layout: &FbLayout,
> + fsp_fw: &'a FspFirmware,
> + args: &'a FmcBootArgs,
> + ) -> Result<impl Init<Self> + 'a> {
> + // frts_offset is relative to FB end: FRTS_location = FB_END - frts_offset
> + let frts_offset = if !args.resume {
> + let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
> +
> + frts_reserved_size
> + .align_up(Alignment::new::<SZ_2M>())
> + .ok_or(EINVAL)?
Why is `frts_offset` calculated using heap and pmu_reserved_size rather
than looking at `fb_layout.frts`? If `fb_layout.frts` is the location we
are meant to have the frts in then why use a (potentially) different
location?
> + } else {
> + 0
> + };
> +
> + let frts_size: u32 = if !args.resume {
> + fb_layout.frts.len().try_into()?
> + } else {
> + 0
> + };
> +
> + let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version();
> + let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
> +
> + Ok(init!(Self {
> + mctp_header: MctpHeader::single_packet(),
> + nvdm_header: NvdmHeader::new(NvdmType::Cot),
> + // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using
> + // `chain`.
> + cot <- pin_init::init_zeroed(),
> + })
> + .chain(move |msg| {
> + msg.cot.version = version;
> + msg.cot.size = size;
> + msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle();
> + msg.cot.frts_vidmem_offset = frts_offset;
> + msg.cot.frts_vidmem_size = frts_size;
> + msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle();
> + msg.cot.sigs = *fsp_fw.fmc_sigs;
I guess we are leaving sysmem frts unset for now since we don't use it
for anything hey
> +
> + Ok(())
> + }))
> + }
> +}
> +
> +// SAFETY: `FspMessage` is `#[repr(C)]` with no padding, so all of its
> +// bytes are initialized.
> +unsafe impl AsBytes for FspMessage {}
> +
> +impl MessageToFsp for FspMessage {
> + const NVDM_TYPE: NvdmType = NvdmType::Cot;
> +}
> +
> +/// Bundled arguments for FMC boot via FSP Chain of Trust.
> +pub(crate) struct FmcBootArgs {
> + chipset: Chipset,
> + fmc_boot_params: Coherent<GspFmcBootParams>,
> + resume: bool,
> +}
> +
> +impl FmcBootArgs {
> + /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter
> + /// structure that FSP will read.
> + pub(crate) fn new(
> + dev: &device::Device<device::Bound>,
> + chipset: Chipset,
> + wpr_meta_addr: u64,
> + libos_addr: u64,
> + resume: bool,
> + ) -> Result<Self> {
> + let init = GspFmcBootParams::new(wpr_meta_addr, libos_addr);
> +
> + Ok(Self {
> + chipset,
> + fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?,
> + resume,
> + })
> + }
> +}
> +
> /// FSP interface for Hopper/Blackwell GPUs.
> ///
> /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
> @@ -73,7 +193,6 @@ pub(crate) trait MessageToFsp: AsBytes {
> /// Chain of Trust boot.
> pub(crate) struct Fsp {
> falcon: Falcon<FspEngine>,
> - #[expect(dead_code)]
> fsp_fw: FspFirmware,
> }
>
> @@ -109,7 +228,6 @@ pub(crate) fn wait_secure_boot(
> }
>
> /// Sends a message to FSP and waits for the response.
> - #[expect(dead_code)]
> fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result
> where
> M: MessageToFsp,
> @@ -170,4 +288,25 @@ fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Res
>
> Ok(())
> }
> +
> + /// Boots GSP FMC via FSP Chain of Trust.
> + ///
> + /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
> + /// to FSP, and waits for the response.
> + pub(crate) fn boot_fmc(
> + &mut self,
> + dev: &device::Device<device::Bound>,
> + bar: &Bar0,
> + fb_layout: &FbLayout,
> + args: &FmcBootArgs,
> + ) -> Result {
> + dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
> +
> + let msg = KBox::init(FspMessage::new(fb_layout, &self.fsp_fw, args)?, GFP_KERNEL)?;
> +
> + self.send_sync_fsp(dev, bar, &*msg)?;
> +
> + dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
> + Ok(())
> + }
> }
> diff --git a/drivers/gpu/nova-core/fsp/hal.rs b/drivers/gpu/nova-core/fsp/hal.rs
> index 8aebe1800a64..86c595d70c8e 100644
> --- a/drivers/gpu/nova-core/fsp/hal.rs
> +++ b/drivers/gpu/nova-core/fsp/hal.rs
> @@ -18,7 +18,6 @@ pub(super) trait FspHal {
> fn fsp_boot_status(&self, bar: &Bar0) -> u32;
>
> /// Returns the FSP Chain of Trust protocol version this chipset advertises.
> - #[expect(dead_code)]
> fn cot_version(&self) -> u16;
> }
>
> diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
> index 1885cfa5cb38..69175ca3315c 100644
> --- a/drivers/gpu/nova-core/gsp.rs
> +++ b/drivers/gpu/nova-core/gsp.rs
> @@ -25,6 +25,7 @@
> mod sequencer;
>
> pub(crate) use fw::{
> + GspFmcBootParams,
> GspFwWprMeta,
> LibosParams, //
> };
> diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
> index 0c54e8bf4bb3..4db0cfa4dc4d 100644
> --- a/drivers/gpu/nova-core/gsp/fw.rs
> +++ b/drivers/gpu/nova-core/gsp/fw.rs
> @@ -934,3 +934,68 @@ fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
> })
> }
> }
> +
> +#[repr(u32)]
> +pub(crate) enum GspDmaTarget {
> + #[expect(dead_code)]
> + LocalFb = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB,
> + CoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM,
> + NoncoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM,
> +}
> +
> +type GspAcrBootGspRmParams = bindings::GSP_ACR_BOOT_GSP_RM_PARAMS;
> +
> +impl GspAcrBootGspRmParams {
> + fn new(target: GspDmaTarget, wpr_meta_addr: u64) -> impl Init<Self> {
> + #[allow(non_snake_case)]
> + let params = init!(Self {
> + target: target as u32,
> + gspRmDescSize: num::usize_into_u32::<{ size_of::<GspFwWprMeta>() }>(),
> + gspRmDescOffset: wpr_meta_addr,
> + bIsGspRmBoot: 1,
> + wprCarveoutOffset: 0,
> + wprCarveoutSize: 0,
> + __bindgen_padding_0: Default::default(),
> + });
> +
> + params
> + }
> +}
> +
> +type GspRmParams = bindings::GSP_RM_PARAMS;
> +
> +impl GspRmParams {
> + fn new(target: GspDmaTarget, libos_addr: u64) -> impl Init<Self> {
> + #[allow(non_snake_case)]
> + let params = init!(Self {
> + target: target as u32,
> + bootArgsOffset: libos_addr,
> + __bindgen_padding_0: Default::default(),
> + });
> +
> + params
> + }
> +}
> +
> +pub(crate) type GspFmcBootParams = bindings::GSP_FMC_BOOT_PARAMS;
> +
> +// SAFETY: Padding is explicit and will not contain uninitialized data.
> +unsafe impl AsBytes for GspFmcBootParams {}
> +// SAFETY: This struct only contains integer types for which all bit patterns are valid.
> +unsafe impl FromBytes for GspFmcBootParams {}
> +
> +impl GspFmcBootParams {
> + pub(crate) fn new(wpr_meta_addr: u64, libos_addr: u64) -> impl Init<Self> {
> + #[allow(non_snake_case)]
> + let init = init!(Self {
> + // Blackwell FSP obtains WPR info from other sources, so
> + // wprCarveoutOffset and wprCarveoutSize are left zero.
> + bootGspRmParams <- GspAcrBootGspRmParams::new(GspDmaTarget::CoherentSystem,
> + wpr_meta_addr),
> + gspRmParams <- GspRmParams::new(GspDmaTarget::NoncoherentSystem, libos_addr),
> + ..Zeroable::init_zeroed()
> + });
> +
> + init
> + }
> +}
> 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 1d592bd3f9ed..ea350f9b2cc4 100644
> --- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
> +++ b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
> @@ -883,6 +883,88 @@ fn default() -> Self {
> }
> }
> }
> +pub const GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB: GSP_DMA_TARGET = 0;
> +pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM: GSP_DMA_TARGET = 1;
> +pub const GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM: GSP_DMA_TARGET = 2;
> +pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COUNT: GSP_DMA_TARGET = 3;
> +pub type GSP_DMA_TARGET = ffi::c_uint;
> +#[repr(C)]
> +#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
> +pub struct GSP_FMC_INIT_PARAMS {
> + pub regkeys: u32_,
> +}
> +#[repr(C)]
> +#[derive(Debug, Copy, Clone, MaybeZeroable)]
> +pub struct GSP_ACR_BOOT_GSP_RM_PARAMS {
> + pub target: GSP_DMA_TARGET,
> + pub gspRmDescSize: u32_,
> + pub gspRmDescOffset: u64_,
> + pub wprCarveoutOffset: u64_,
> + pub wprCarveoutSize: u32_,
> + pub bIsGspRmBoot: u8_,
> + pub __bindgen_padding_0: [u8; 3usize],
> +}
> +impl Default for GSP_ACR_BOOT_GSP_RM_PARAMS {
> + 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()
> + }
> + }
> +}
> +#[repr(C)]
> +#[derive(Debug, Copy, Clone, MaybeZeroable)]
> +pub struct GSP_RM_PARAMS {
> + pub target: GSP_DMA_TARGET,
> + pub __bindgen_padding_0: [u8; 4usize],
> + pub bootArgsOffset: u64_,
> +}
> +impl Default for GSP_RM_PARAMS {
> + 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()
> + }
> + }
> +}
> +#[repr(C)]
> +#[derive(Debug, Copy, Clone, MaybeZeroable)]
> +pub struct GSP_SPDM_PARAMS {
> + pub target: GSP_DMA_TARGET,
> + pub __bindgen_padding_0: [u8; 4usize],
> + pub payloadBufferOffset: u64_,
> + pub payloadBufferSize: u32_,
> + pub __bindgen_padding_1: [u8; 4usize],
> +}
> +impl Default for GSP_SPDM_PARAMS {
> + 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()
> + }
> + }
> +}
> +#[repr(C)]
> +#[derive(Debug, Copy, Clone, MaybeZeroable)]
> +pub struct GSP_FMC_BOOT_PARAMS {
> + pub initParams: GSP_FMC_INIT_PARAMS,
> + pub __bindgen_padding_0: [u8; 4usize],
> + pub bootGspRmParams: GSP_ACR_BOOT_GSP_RM_PARAMS,
> + pub gspRmParams: GSP_RM_PARAMS,
> + pub gspSpdmParams: GSP_SPDM_PARAMS,
> +}
> +impl Default for GSP_FMC_BOOT_PARAMS {
> + 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()
> + }
> + }
> +}
> #[repr(C)]
> #[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
> pub struct rpc_unloading_guest_driver_v1F_07 {
> diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
> index b25970dd4561..f41f3fea15ff 100644
> --- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
> +++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
> @@ -20,7 +20,10 @@
> fsp::FspFirmware,
> FIRMWARE_VERSION, //
> },
> - fsp::Fsp,
> + fsp::{
> + FmcBootArgs,
> + Fsp, //
> + },
> gpu::Chipset,
> gsp::{
> boot::BootUnloadGuard,
> @@ -39,17 +42,27 @@ impl GspHal for Gh100 {
> /// the GSP boot internally - no manual GSP reset/boot is needed.
> fn boot<'a>(
> &self,
> - _gsp: &'a Gsp,
> + gsp: &'a Gsp,
> dev: &'a device::Device<device::Bound>,
> bar: &'a Bar0,
> chipset: Chipset,
> - _fb_layout: &FbLayout,
> - _wpr_meta: &Coherent<GspFwWprMeta>,
> + fb_layout: &FbLayout,
> + wpr_meta: &Coherent<GspFwWprMeta>,
> _gsp_falcon: &'a Falcon<GspEngine>,
> _sec2_falcon: &'a Falcon<Sec2>,
> ) -> Result<BootUnloadGuard<'a>> {
> let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
> - let _fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
> + let mut fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
> +
> + let args = FmcBootArgs::new(
> + dev,
> + chipset,
> + wpr_meta.dma_handle(),
> + gsp.libos.dma_handle(),
> + false,
> + )?;
> +
> + fsp.boot_fmc(dev, bar, fb_layout, &args)?;
>
> Err(ENOTSUPP)
> }
> diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
> index 90e289d4c3fe..482786e07bc7 100644
> --- a/drivers/gpu/nova-core/mctp.rs
> +++ b/drivers/gpu/nova-core/mctp.rs
> @@ -7,8 +7,6 @@
> //! Data Model) messages between the kernel driver and GPU firmware processors
> //! such as FSP and GSP.
>
> -#![expect(dead_code)]
> -
> use kernel::pci::Vendor;
>
> /// NVDM message type identifiers carried over MCTP.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot
2026-06-03 12:47 ` Eliot Courtney
@ 2026-06-03 13:35 ` Alexandre Courbot
0 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 13:35 UTC (permalink / raw)
To: Eliot Courtney
Cc: Danilo Krummrich, John Hubbard, Timur Tabi, Alistair Popple,
Shashank Sharma, Zhi Wang, David Airlie, Simona Vetter,
Bjorn Helgaas, Miguel Ojeda, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, Boqun Feng
On Wed Jun 3, 2026 at 9:47 PM JST, Eliot Courtney wrote:
> On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
>> From: John Hubbard <jhubbard@nvidia.com>
>>
>> Build and send the Chain of Trust message to FSP, bundling the
>> DMA-coherent boot parameters that FSP reads at boot time.
>>
>> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
>> Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> drivers/gpu/nova-core/firmware/fsp.rs | 2 -
>> drivers/gpu/nova-core/fsp.rs | 145 +++++++++++++++++++++-
>> drivers/gpu/nova-core/fsp/hal.rs | 1 -
>> drivers/gpu/nova-core/gsp.rs | 1 +
>> drivers/gpu/nova-core/gsp/fw.rs | 65 ++++++++++
>> drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs | 82 ++++++++++++
>> drivers/gpu/nova-core/gsp/hal/gh100.rs | 23 +++-
>> drivers/gpu/nova-core/mctp.rs | 2 -
>> 8 files changed, 308 insertions(+), 13 deletions(-)
>>
>> diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs
>> index 9b211426a75a..6eaf1c684b9d 100644
>> --- a/drivers/gpu/nova-core/firmware/fsp.rs
>> +++ b/drivers/gpu/nova-core/firmware/fsp.rs
>> @@ -39,10 +39,8 @@ pub(crate) struct FmcSignatures {
>>
>> pub(crate) struct FspFirmware {
>> /// FMC firmware image data (only the "image" ELF section).
>> - #[expect(dead_code)]
>> pub(crate) fmc_image: Coherent<[u8]>,
>> /// FMC firmware signatures.
>> - #[expect(dead_code)]
>> pub(crate) fmc_sigs: KBox<FmcSignatures>,
>> }
>>
>> diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
>> index cedfea173e50..883ac4f8b811 100644
>> --- a/drivers/gpu/nova-core/fsp.rs
>> +++ b/drivers/gpu/nova-core/fsp.rs
>> @@ -9,8 +9,14 @@
>>
>> use kernel::{
>> device,
>> + dma::Coherent,
>> io::poll::read_poll_timeout,
>> prelude::*,
>> + ptr::{
>> + Alignable,
>> + Alignment, //
>> + },
>> + sizes::SZ_2M,
>> time::Delta,
>> transmute::{
>> AsBytes,
>> @@ -24,13 +30,19 @@
>> fsp::Fsp as FspEngine,
>> Falcon, //
>> },
>> - firmware::fsp::FspFirmware,
>> + fb::FbLayout,
>> + firmware::fsp::{
>> + FmcSignatures,
>> + FspFirmware, //
>> + },
>> gpu::Chipset,
>> + gsp::GspFmcBootParams,
>> mctp::{
>> MctpHeader,
>> NvdmHeader,
>> NvdmType, //
>> },
>> + num,
>> regs, //
>> };
>>
>> @@ -66,6 +78,114 @@ pub(crate) trait MessageToFsp: AsBytes {
>> const NVDM_TYPE: NvdmType;
>> }
>>
>> +/// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main
>> +/// message body sent to FSP for Chain of Trust boot.
>> +#[repr(C, packed)]
>> +#[derive(Clone, Copy, Zeroable)]
>> +struct NvdmPayloadCot {
>> + version: u16,
>> + size: u16,
>> + gsp_fmc_sysmem_offset: u64,
>> + frts_sysmem_offset: u64,
>> + frts_sysmem_size: u32,
>> + frts_vidmem_offset: u64,
>> + frts_vidmem_size: u32,
>> + sigs: FmcSignatures,
>> + gsp_boot_args_sysmem_offset: u64,
>> +}
>> +
>> +/// Complete FSP message structure with MCTP and NVDM headers.
>> +#[repr(C)]
>> +#[derive(Clone, Copy)]
>> +struct FspMessage {
>> + mctp_header: MctpHeader,
>> + nvdm_header: NvdmHeader,
>> + cot: NvdmPayloadCot,
>> +}
>> +
>> +impl FspMessage {
>> + /// Returns an in-place initializer for [`FspMessage`].
>> + fn new<'a>(
>> + fb_layout: &FbLayout,
>> + fsp_fw: &'a FspFirmware,
>> + args: &'a FmcBootArgs,
>> + ) -> Result<impl Init<Self> + 'a> {
>> + // frts_offset is relative to FB end: FRTS_location = FB_END - frts_offset
>> + let frts_offset = if !args.resume {
>> + let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
>> +
>> + frts_reserved_size
>> + .align_up(Alignment::new::<SZ_2M>())
>> + .ok_or(EINVAL)?
>
> Why is `frts_offset` calculated using heap and pmu_reserved_size rather
> than looking at `fb_layout.frts`? If `fb_layout.frts` is the location we
> are meant to have the frts in then why use a (potentially) different
> location?
Conceptually the current code looks correct, but the naming is indeed
ambiguous. This seems to correspond to the CoT `frtsVidmemOffset`, not
the absolute FRTS address from `fb_layout.frts`. Will need to dig a bit
deeper but it looks like this requires a different name.
>
>> + } else {
>> + 0
>> + };
>> +
>> + let frts_size: u32 = if !args.resume {
>> + fb_layout.frts.len().try_into()?
>> + } else {
>> + 0
>> + };
>> +
>> + let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version();
>> + let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
>> +
>> + Ok(init!(Self {
>> + mctp_header: MctpHeader::single_packet(),
>> + nvdm_header: NvdmHeader::new(NvdmType::Cot),
>> + // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using
>> + // `chain`.
>> + cot <- pin_init::init_zeroed(),
>> + })
>> + .chain(move |msg| {
>> + msg.cot.version = version;
>> + msg.cot.size = size;
>> + msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle();
>> + msg.cot.frts_vidmem_offset = frts_offset;
>> + msg.cot.frts_vidmem_size = frts_size;
>> + msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle();
>> + msg.cot.sigs = *fsp_fw.fmc_sigs;
>
> I guess we are leaving sysmem frts unset for now since we don't use it
> for anything hey
Yes, but let me add a note so we don't forget to come back to this as
they will likely become needed for systems without VRAM.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (5 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 6/9] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 10:17 ` Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 8/9] gpu: nova-core: add non-sec2 unload path Alexandre Courbot
` (2 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: John Hubbard <jhubbard@nvidia.com>
On Hopper and Blackwell, FSP boots GSP with hardware lockdown enabled.
After FSP Chain of Trust completes, the driver must poll for lockdown
release before proceeding with GSP initialization. Add the register
bit and helper functions needed for this polling.
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/gsp.rs | 6 +++
drivers/gpu/nova-core/fsp.rs | 6 +++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 88 +++++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/regs.rs | 2 +
4 files changed, 100 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index df6d5a382c7a..136d6b24103f 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -57,4 +57,10 @@ pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Resul
)
.map(|_| true)
}
+
+ /// Returns whether the RISC-V branch privilege lockdown bit is set.
+ pub(crate) fn riscv_branch_privilege_lockdown(&self, bar: &Bar0) -> bool {
+ bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
+ .riscv_br_priv_lockdown()
+ }
}
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 883ac4f8b811..872898ffe0a3 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -184,6 +184,12 @@ pub(crate) fn new(
resume,
})
}
+
+ /// DMA address of the FMC boot parameters, needed after boot for lockdown
+ /// release polling.
+ pub(crate) fn boot_params_dma_handle(&self) -> u64 {
+ self.fmc_boot_params.dma_handle()
+ }
}
/// FSP interface for Hopper/Blackwell GPUs.
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index f41f3fea15ff..def41745a30f 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -5,7 +5,9 @@
use kernel::{
device,
- dma::Coherent, //
+ dma::Coherent,
+ io::poll::read_poll_timeout,
+ time::Delta, //
};
use crate::{
@@ -33,6 +35,86 @@
},
};
+/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
+/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
+const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
+const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
+
+/// GSP falcon mailbox state, used to track lockdown release status.
+struct GspMbox {
+ mbox0: u32,
+ mbox1: u32,
+}
+
+impl GspMbox {
+ /// Reads both mailboxes from the GSP falcon.
+ fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
+ Self {
+ mbox0: gsp_falcon.read_mailbox0(bar),
+ mbox1: gsp_falcon.read_mailbox1(bar),
+ }
+ }
+
+ /// Returns `true` if the lockdown pattern is present in `mbox0`.
+ fn is_locked_down(&self) -> bool {
+ (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
+ }
+
+ /// Combines mailbox0 and mailbox1 into a 64-bit address.
+ fn combined_addr(&self) -> u64 {
+ (u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
+ }
+
+ /// Returns `true` if GSP lockdown has been released.
+ ///
+ /// Checks the lockdown pattern, validates the boot params address,
+ /// and verifies the `HWCFG2` lockdown bit is clear.
+ fn lockdown_released(
+ &self,
+ gsp_falcon: &Falcon<GspEngine>,
+ bar: &Bar0,
+ fmc_boot_params_addr: u64,
+ ) -> bool {
+ if self.is_locked_down() {
+ return false;
+ }
+
+ if self.mbox0 != 0 && self.combined_addr() != fmc_boot_params_addr {
+ return true;
+ }
+
+ !gsp_falcon.riscv_branch_privilege_lockdown(bar)
+ }
+}
+
+/// Waits for GSP lockdown to be released after FSP Chain of Trust.
+fn wait_for_gsp_lockdown_release(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ fmc_boot_params_addr: u64,
+) -> Result {
+ dev_dbg!(dev, "Waiting for GSP lockdown release\n");
+
+ let mbox = read_poll_timeout(
+ || Ok(GspMbox::read(gsp_falcon, bar)),
+ |mbox| mbox.lockdown_released(gsp_falcon, bar, fmc_boot_params_addr),
+ Delta::from_millis(10),
+ Delta::from_secs(30),
+ )
+ .inspect_err(|_| {
+ dev_err!(dev, "GSP lockdown release timeout\n");
+ })?;
+
+ if mbox.mbox0 != 0 {
+ dev_err!(dev, "GSP-FMC boot failed (mbox: {:#x})\n", mbox.mbox0);
+ return Err(EIO);
+ }
+
+ dev_dbg!(dev, "GSP lockdown released\n");
+ Ok(())
+}
+
struct Gh100;
impl GspHal for Gh100 {
@@ -48,7 +130,7 @@ fn boot<'a>(
chipset: Chipset,
fb_layout: &FbLayout,
wpr_meta: &Coherent<GspFwWprMeta>,
- _gsp_falcon: &'a Falcon<GspEngine>,
+ gsp_falcon: &'a Falcon<GspEngine>,
_sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
@@ -64,6 +146,8 @@ fn boot<'a>(
fsp.boot_fmc(dev, bar, fb_layout, &args)?;
+ wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, args.boot_params_dma_handle())?;
+
Err(ENOTSUPP)
}
}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index c3c50b8d8f10..ce71772e0298 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -363,6 +363,8 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) NV_PFALCON_FALCON_HWCFG2(u32) @ PFalconBase + 0x000000f4 {
/// Signal indicating that reset is completed (GA102+).
31:31 reset_ready => bool;
+ /// RISC-V branch privilege lockdown bit.
+ 13:13 riscv_br_priv_lockdown => bool;
/// Set to 0 after memory scrubbing is completed.
12:12 mem_scrubbing => bool;
10:10 riscv => bool;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling
2026-06-03 7:30 ` [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling Alexandre Courbot
@ 2026-06-03 10:17 ` Alexandre Courbot
2026-06-03 11:53 ` Eliot Courtney
0 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 10:17 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> On Hopper and Blackwell, FSP boots GSP with hardware lockdown enabled.
> After FSP Chain of Trust completes, the driver must poll for lockdown
> release before proceeding with GSP initialization. Add the register
> bit and helper functions needed for this polling.
>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> drivers/gpu/nova-core/falcon/gsp.rs | 6 +++
> drivers/gpu/nova-core/fsp.rs | 6 +++
> drivers/gpu/nova-core/gsp/hal/gh100.rs | 88 +++++++++++++++++++++++++++++++++-
> drivers/gpu/nova-core/regs.rs | 2 +
> 4 files changed, 100 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
> index df6d5a382c7a..136d6b24103f 100644
> --- a/drivers/gpu/nova-core/falcon/gsp.rs
> +++ b/drivers/gpu/nova-core/falcon/gsp.rs
> @@ -57,4 +57,10 @@ pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Resul
> )
> .map(|_| true)
> }
> +
> + /// Returns whether the RISC-V branch privilege lockdown bit is set.
> + pub(crate) fn riscv_branch_privilege_lockdown(&self, bar: &Bar0) -> bool {
> + bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
> + .riscv_br_priv_lockdown()
> + }
> }
> diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
> index 883ac4f8b811..872898ffe0a3 100644
> --- a/drivers/gpu/nova-core/fsp.rs
> +++ b/drivers/gpu/nova-core/fsp.rs
> @@ -184,6 +184,12 @@ pub(crate) fn new(
> resume,
> })
> }
> +
> + /// DMA address of the FMC boot parameters, needed after boot for lockdown
> + /// release polling.
> + pub(crate) fn boot_params_dma_handle(&self) -> u64 {
> + self.fmc_boot_params.dma_handle()
> + }
> }
>
> /// FSP interface for Hopper/Blackwell GPUs.
> diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
> index f41f3fea15ff..def41745a30f 100644
> --- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
> +++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
> @@ -5,7 +5,9 @@
>
> use kernel::{
> device,
> - dma::Coherent, //
> + dma::Coherent,
> + io::poll::read_poll_timeout,
> + time::Delta, //
> };
>
> use crate::{
> @@ -33,6 +35,86 @@
> },
> };
>
> +/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
> +/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
> +const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
> +const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
> +
> +/// GSP falcon mailbox state, used to track lockdown release status.
> +struct GspMbox {
> + mbox0: u32,
> + mbox1: u32,
> +}
> +
> +impl GspMbox {
> + /// Reads both mailboxes from the GSP falcon.
> + fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
> + Self {
> + mbox0: gsp_falcon.read_mailbox0(bar),
> + mbox1: gsp_falcon.read_mailbox1(bar),
> + }
> + }
> +
> + /// Returns `true` if the lockdown pattern is present in `mbox0`.
> + fn is_locked_down(&self) -> bool {
> + (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
> + }
> +
> + /// Combines mailbox0 and mailbox1 into a 64-bit address.
> + fn combined_addr(&self) -> u64 {
> + (u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
> + }
> +
> + /// Returns `true` if GSP lockdown has been released.
> + ///
> + /// Checks the lockdown pattern, validates the boot params address,
> + /// and verifies the `HWCFG2` lockdown bit is clear.
> + fn lockdown_released(
> + &self,
> + gsp_falcon: &Falcon<GspEngine>,
> + bar: &Bar0,
> + fmc_boot_params_addr: u64,
> + ) -> bool {
> + if self.is_locked_down() {
> + return false;
> + }
> +
> + if self.mbox0 != 0 && self.combined_addr() != fmc_boot_params_addr {
> + return true;
> + }
This looks like a bug - if the mailboxes still contain the boot
parameters address, we will keep going and might return true on the next
line, which the caller will interpret as an error. OpenRM does the
opposite check and has an additional test for `mailbox0 != 0`, which we
can translate into this logic:
if self.mbox0 != 0 {
return self.combined_addr() != fmc_boot_params_addr;
}
I'll fix it and add a few comments explaining what the code does as it
can be a bit convoluted.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling
2026-06-03 10:17 ` Alexandre Courbot
@ 2026-06-03 11:53 ` Eliot Courtney
2026-06-03 14:53 ` Alexandre Courbot
0 siblings, 1 reply; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 11:53 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 7:17 PM JST, Alexandre Courbot wrote:
> On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
>> From: John Hubbard <jhubbard@nvidia.com>
>>
>> On Hopper and Blackwell, FSP boots GSP with hardware lockdown enabled.
>> After FSP Chain of Trust completes, the driver must poll for lockdown
>> release before proceeding with GSP initialization. Add the register
>> bit and helper functions needed for this polling.
>>
>> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> drivers/gpu/nova-core/falcon/gsp.rs | 6 +++
>> drivers/gpu/nova-core/fsp.rs | 6 +++
>> drivers/gpu/nova-core/gsp/hal/gh100.rs | 88 +++++++++++++++++++++++++++++++++-
>> drivers/gpu/nova-core/regs.rs | 2 +
>> 4 files changed, 100 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
>> index df6d5a382c7a..136d6b24103f 100644
>> --- a/drivers/gpu/nova-core/falcon/gsp.rs
>> +++ b/drivers/gpu/nova-core/falcon/gsp.rs
>> @@ -57,4 +57,10 @@ pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Resul
>> )
>> .map(|_| true)
>> }
>> +
>> + /// Returns whether the RISC-V branch privilege lockdown bit is set.
>> + pub(crate) fn riscv_branch_privilege_lockdown(&self, bar: &Bar0) -> bool {
>> + bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
>> + .riscv_br_priv_lockdown()
>> + }
>> }
>> diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
>> index 883ac4f8b811..872898ffe0a3 100644
>> --- a/drivers/gpu/nova-core/fsp.rs
>> +++ b/drivers/gpu/nova-core/fsp.rs
>> @@ -184,6 +184,12 @@ pub(crate) fn new(
>> resume,
>> })
>> }
>> +
>> + /// DMA address of the FMC boot parameters, needed after boot for lockdown
>> + /// release polling.
>> + pub(crate) fn boot_params_dma_handle(&self) -> u64 {
>> + self.fmc_boot_params.dma_handle()
>> + }
>> }
>>
>> /// FSP interface for Hopper/Blackwell GPUs.
>> diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
>> index f41f3fea15ff..def41745a30f 100644
>> --- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
>> +++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
>> @@ -5,7 +5,9 @@
>>
>> use kernel::{
>> device,
>> - dma::Coherent, //
>> + dma::Coherent,
>> + io::poll::read_poll_timeout,
>> + time::Delta, //
>> };
>>
>> use crate::{
>> @@ -33,6 +35,86 @@
>> },
>> };
>>
>> +/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
>> +/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
>> +const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
>> +const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
nit: these constants can be moved into impl GspMbox or made local to
`is_locked_down`
>> +
>> +/// GSP falcon mailbox state, used to track lockdown release status.
>> +struct GspMbox {
>> + mbox0: u32,
>> + mbox1: u32,
>> +}
>> +
>> +impl GspMbox {
>> + /// Reads both mailboxes from the GSP falcon.
>> + fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
>> + Self {
>> + mbox0: gsp_falcon.read_mailbox0(bar),
>> + mbox1: gsp_falcon.read_mailbox1(bar),
>> + }
>> + }
>> +
>> + /// Returns `true` if the lockdown pattern is present in `mbox0`.
>> + fn is_locked_down(&self) -> bool {
>> + (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
>> + }
>> +
>> + /// Combines mailbox0 and mailbox1 into a 64-bit address.
>> + fn combined_addr(&self) -> u64 {
>> + (u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
>> + }
>> +
>> + /// Returns `true` if GSP lockdown has been released.
>> + ///
>> + /// Checks the lockdown pattern, validates the boot params address,
>> + /// and verifies the `HWCFG2` lockdown bit is clear.
>> + fn lockdown_released(
>> + &self,
>> + gsp_falcon: &Falcon<GspEngine>,
>> + bar: &Bar0,
>> + fmc_boot_params_addr: u64,
>> + ) -> bool {
>> + if self.is_locked_down() {
>> + return false;
>> + }
>> +
>> + if self.mbox0 != 0 && self.combined_addr() != fmc_boot_params_addr {
>> + return true;
>> + }
>
> This looks like a bug - if the mailboxes still contain the boot
> parameters address, we will keep going and might return true on the next
> line, which the caller will interpret as an error. OpenRM does the
> opposite check and has an additional test for `mailbox0 != 0`, which we
> can translate into this logic:
>
> if self.mbox0 != 0 {
> return self.combined_addr() != fmc_boot_params_addr;
> }
>
> I'll fix it and add a few comments explaining what the code does as it
> can be a bit convoluted.
Yeah, I agree. I think the logic openrm uses is like:
1. wait until HWCFG2 != 0 && (HWCFG2 & 0xffffff00) != 0xbadf4100
2. wait until mbox0 == 0 || addr != fmc_boot_params_addr
3. wait until riscv_br_priv_lockdown == 0 || mbox0 != 0
So several things are different to openrm (not sure if they are wrong
though):
1. `is_locked_down` is checking the wrong register (should check HWCFG2
AFAICT). I think we should name this more like
'gsp_mailboxes_readable' or something, since IIUC it is meant to test
when it's valid to read mboxs.
2. other logic is wrong as you mentioned.
Maybe we could structure lockdown_released like this:
// can't read the mailboxes yet (even though we already did but the
// result is actually not valid so maybe this should be restructured)
if !gsp_mailboxes_readable {
return false;
}
// we can read the registers and we are still waiting for mbox0 to
// be zero
if mbox0 != 0 && addr == fmc_boot_params_addr {
return false;
}
// either lockdown is released or some error happened
return riscv_br_priv_lockdown == 0 || mbox0 != 0
I think your suggested change w.r.t. if self.mbox0 != 0; is also
correct. I think we should update the comment on the function and the
function name too to say it returns true on lockdown release OR an error
happened.
I don't know if there's a simpler logic that will work, just commenting
on how it compares to openrm.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling
2026-06-03 11:53 ` Eliot Courtney
@ 2026-06-03 14:53 ` Alexandre Courbot
0 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 14:53 UTC (permalink / raw)
To: Eliot Courtney
Cc: Danilo Krummrich, John Hubbard, Timur Tabi, Alistair Popple,
Shashank Sharma, Zhi Wang, David Airlie, Simona Vetter,
Bjorn Helgaas, Miguel Ojeda, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, Boqun Feng
On Wed Jun 3, 2026 at 8:53 PM JST, Eliot Courtney wrote:
> On Wed Jun 3, 2026 at 7:17 PM JST, Alexandre Courbot wrote:
>> On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
>>> From: John Hubbard <jhubbard@nvidia.com>
>>>
>>> On Hopper and Blackwell, FSP boots GSP with hardware lockdown enabled.
>>> After FSP Chain of Trust completes, the driver must poll for lockdown
>>> release before proceeding with GSP initialization. Add the register
>>> bit and helper functions needed for this polling.
>>>
>>> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>>> ---
>>> drivers/gpu/nova-core/falcon/gsp.rs | 6 +++
>>> drivers/gpu/nova-core/fsp.rs | 6 +++
>>> drivers/gpu/nova-core/gsp/hal/gh100.rs | 88 +++++++++++++++++++++++++++++++++-
>>> drivers/gpu/nova-core/regs.rs | 2 +
>>> 4 files changed, 100 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
>>> index df6d5a382c7a..136d6b24103f 100644
>>> --- a/drivers/gpu/nova-core/falcon/gsp.rs
>>> +++ b/drivers/gpu/nova-core/falcon/gsp.rs
>>> @@ -57,4 +57,10 @@ pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Resul
>>> )
>>> .map(|_| true)
>>> }
>>> +
>>> + /// Returns whether the RISC-V branch privilege lockdown bit is set.
>>> + pub(crate) fn riscv_branch_privilege_lockdown(&self, bar: &Bar0) -> bool {
>>> + bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
>>> + .riscv_br_priv_lockdown()
>>> + }
>>> }
>>> diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
>>> index 883ac4f8b811..872898ffe0a3 100644
>>> --- a/drivers/gpu/nova-core/fsp.rs
>>> +++ b/drivers/gpu/nova-core/fsp.rs
>>> @@ -184,6 +184,12 @@ pub(crate) fn new(
>>> resume,
>>> })
>>> }
>>> +
>>> + /// DMA address of the FMC boot parameters, needed after boot for lockdown
>>> + /// release polling.
>>> + pub(crate) fn boot_params_dma_handle(&self) -> u64 {
>>> + self.fmc_boot_params.dma_handle()
>>> + }
>>> }
>>>
>>> /// FSP interface for Hopper/Blackwell GPUs.
>>> diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
>>> index f41f3fea15ff..def41745a30f 100644
>>> --- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
>>> +++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
>>> @@ -5,7 +5,9 @@
>>>
>>> use kernel::{
>>> device,
>>> - dma::Coherent, //
>>> + dma::Coherent,
>>> + io::poll::read_poll_timeout,
>>> + time::Delta, //
>>> };
>>>
>>> use crate::{
>>> @@ -33,6 +35,86 @@
>>> },
>>> };
>>>
>>> +/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
>>> +/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
>>> +const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
>>> +const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
>
> nit: these constants can be moved into impl GspMbox or made local to
> `is_locked_down`
>
>>> +
>>> +/// GSP falcon mailbox state, used to track lockdown release status.
>>> +struct GspMbox {
>>> + mbox0: u32,
>>> + mbox1: u32,
>>> +}
>>> +
>>> +impl GspMbox {
>>> + /// Reads both mailboxes from the GSP falcon.
>>> + fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
>>> + Self {
>>> + mbox0: gsp_falcon.read_mailbox0(bar),
>>> + mbox1: gsp_falcon.read_mailbox1(bar),
>>> + }
>>> + }
>>> +
>>> + /// Returns `true` if the lockdown pattern is present in `mbox0`.
>>> + fn is_locked_down(&self) -> bool {
>>> + (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
>>> + }
>>> +
>>> + /// Combines mailbox0 and mailbox1 into a 64-bit address.
>>> + fn combined_addr(&self) -> u64 {
>>> + (u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
>>> + }
>>> +
>>> + /// Returns `true` if GSP lockdown has been released.
>>> + ///
>>> + /// Checks the lockdown pattern, validates the boot params address,
>>> + /// and verifies the `HWCFG2` lockdown bit is clear.
>>> + fn lockdown_released(
>>> + &self,
>>> + gsp_falcon: &Falcon<GspEngine>,
>>> + bar: &Bar0,
>>> + fmc_boot_params_addr: u64,
>>> + ) -> bool {
>>> + if self.is_locked_down() {
>>> + return false;
>>> + }
>>> +
>>> + if self.mbox0 != 0 && self.combined_addr() != fmc_boot_params_addr {
>>> + return true;
>>> + }
>>
>> This looks like a bug - if the mailboxes still contain the boot
>> parameters address, we will keep going and might return true on the next
>> line, which the caller will interpret as an error. OpenRM does the
>> opposite check and has an additional test for `mailbox0 != 0`, which we
>> can translate into this logic:
>>
>> if self.mbox0 != 0 {
>> return self.combined_addr() != fmc_boot_params_addr;
>> }
>>
>> I'll fix it and add a few comments explaining what the code does as it
>> can be a bit convoluted.
>
> Yeah, I agree. I think the logic openrm uses is like:
>
> 1. wait until HWCFG2 != 0 && (HWCFG2 & 0xffffff00) != 0xbadf4100
> 2. wait until mbox0 == 0 || addr != fmc_boot_params_addr
> 3. wait until riscv_br_priv_lockdown == 0 || mbox0 != 0
>
> So several things are different to openrm (not sure if they are wrong
> though):
>
> 1. `is_locked_down` is checking the wrong register (should check HWCFG2
> AFAICT). I think we should name this more like
> 'gsp_mailboxes_readable' or something, since IIUC it is meant to test
> when it's valid to read mboxs.
> 2. other logic is wrong as you mentioned.
>
> Maybe we could structure lockdown_released like this:
>
> // can't read the mailboxes yet (even though we already did but the
> // result is actually not valid so maybe this should be restructured)
> if !gsp_mailboxes_readable {
> return false;
> }
>
> // we can read the registers and we are still waiting for mbox0 to
> // be zero
> if mbox0 != 0 && addr == fmc_boot_params_addr {
> return false;
> }
>
> // either lockdown is released or some error happened
> return riscv_br_priv_lockdown == 0 || mbox0 != 0
>
> I think your suggested change w.r.t. if self.mbox0 != 0; is also
> correct. I think we should update the comment on the function and the
> function name too to say it returns true on lockdown release OR an error
> happened.
>
> I don't know if there's a simpler logic that will work, just commenting
> on how it compares to openrm.
Wow, the wrong register check in `is_locked_down` is pretty significant.
Thanks for catching this.
I'll fix the logic when applying; the resulting diff is a bit longer
than I wish it was, but not so scary.
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index 136d6b24103f..98a1c1dc8465 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -24,6 +24,10 @@
regs,
};
+/// Pattern returned by GSP register reads while the PRIV target mask still blocks CPU access.
+const GSP_TARGET_MASK_LOCKED_PATTERN: u32 = 0xbadf_4100;
+const GSP_TARGET_MASK_LOCKED_MASK: u32 = 0xffff_ff00;
+
/// Type specifying the `Gsp` falcon engine. Cannot be instantiated.
pub(crate) struct Gsp(());
@@ -63,4 +67,13 @@ pub(crate) fn riscv_branch_privilege_lockdown(&self, bar: &Bar0) -> bool {
bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
.riscv_br_priv_lockdown()
}
+
+ /// Returns whether GSP registers can be read by the CPU.
+ pub(crate) fn priv_target_mask_released(&self, bar: &Bar0) -> bool {
+ let hwcfg2 = bar
+ .read(regs::NV_PFALCON_FALCON_HWCFG2::of::<Gsp>())
+ .into_raw();
+
+ hwcfg2 != 0 && (hwcfg2 & GSP_TARGET_MASK_LOCKED_MASK) != GSP_TARGET_MASK_LOCKED_PATTERN
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index acdfb7fc06fc..57e31ef4819d 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -35,11 +35,6 @@
},
};
-/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
-/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
-const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
-const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
-
/// GSP falcon mailbox state, used to track lockdown release status.
struct GspMbox {
mbox0: u32,
@@ -55,30 +50,21 @@ fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
}
}
- /// Returns `true` if the lockdown pattern is present in `mbox0`.
- fn is_locked_down(&self) -> bool {
- (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
- }
-
/// Combines mailbox0 and mailbox1 into a 64-bit address.
fn combined_addr(&self) -> u64 {
(u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
}
- /// Returns `true` if GSP lockdown has been released.
+ /// Returns `true` if GSP lockdown has been released or a GSP-FMC error happened.
///
/// Returns `true` both on successful lockdown release and on GSP-FMC-reported errors, since
/// either condition should stop the poll loop.
- fn lockdown_released(
+ fn lockdown_released_or_error(
&self,
gsp_falcon: &Falcon<GspEngine>,
bar: &Bar0,
fmc_boot_params_addr: u64,
) -> bool {
- if self.is_locked_down() {
- return false;
- }
-
// GSP-FMC normally clears the boot parameters address from the mailboxes early during
// boot. If the address is still there, keep polling rather than treating it as an error.
// Any other non-zero mailbox0 value is a GSP-FMC error code.
@@ -100,14 +86,25 @@ fn wait_for_gsp_lockdown_release(
dev_dbg!(dev, "Waiting for GSP lockdown release\n");
let mbox = read_poll_timeout(
- || Ok(GspMbox::read(gsp_falcon, bar)),
- |mbox| mbox.lockdown_released(gsp_falcon, bar, fmc_boot_params_addr),
+ || {
+ // While the PRIV target mask is still locked to FSP, GSP register and mailbox reads
+ // are not meaningful. Wait until HWCFG2 says the CPU can read them.
+ Ok(match gsp_falcon.priv_target_mask_released(bar) {
+ false => None,
+ true => Some(GspMbox::read(gsp_falcon, bar)),
+ })
+ },
+ |mbox| match mbox {
+ None => false,
+ Some(mbox) => mbox.lockdown_released_or_error(gsp_falcon, bar, fmc_boot_params_addr),
+ },
Delta::from_millis(10),
Delta::from_secs(30),
)
.inspect_err(|_| {
dev_err!(dev, "GSP lockdown release timeout\n");
- })?;
+ })?
+ .ok_or(EIO)?;
// If polling stopped with a non-zero mailbox0, it was not the boot parameters address
// anymore and therefore represents a GSP-FMC error code.
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v13 8/9] gpu: nova-core: add non-sec2 unload path
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (6 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 7/9] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 7:30 ` [PATCH v13 9/9] gpu: nova-core: gsp: enable FSP boot path Alexandre Courbot
2026-06-03 15:04 ` [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
9 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
From: Eliot Courtney <ecourtney@nvidia.com>
For non-sec2 it is only required to wait for GSP falcon to halt. This is
because GSP does the main work of unloading on GPUs not using sec2.
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
[ jhubbard: use Result instead of Result<()> in the UnloadBundle impl ]
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/hal/gh100.rs | 38 ++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index def41745a30f..51ac3ddb832a 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -29,7 +29,10 @@
gpu::Chipset,
gsp::{
boot::BootUnloadGuard,
- hal::GspHal,
+ hal::{
+ GspHal,
+ UnloadBundle, //
+ },
Gsp,
GspFwWprMeta, //
},
@@ -115,6 +118,28 @@ fn wait_for_gsp_lockdown_release(
Ok(())
}
+struct FspUnloadBundle;
+
+impl UnloadBundle for FspUnloadBundle {
+ fn run(
+ &self,
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ _sec2_falcon: &Falcon<Sec2>,
+ ) -> Result {
+ // GSP falcon does most of the work of resetting, so just wait for it to finish.
+ read_poll_timeout(
+ || Ok(gsp_falcon.is_riscv_active(bar)),
+ |&active| !active,
+ Delta::from_millis(10),
+ Delta::from_secs(5),
+ )
+ .map(|_| ())
+ .inspect_err(|_| dev_err!(dev, "GSP falcon failed to halt\n"))
+ }
+}
+
struct Gh100;
impl GspHal for Gh100 {
@@ -131,9 +156,18 @@ fn boot<'a>(
fb_layout: &FbLayout,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
- _sec2_falcon: &'a Falcon<Sec2>,
+ sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
+
+ let unload_bundle = crate::gsp::UnloadBundle(
+ KBox::new(FspUnloadBundle, GFP_KERNEL)? as KBox<dyn UnloadBundle>
+ );
+
+ // Wrap the unload bundle into a drop guard so it is automatically run upon failure.
+ let _unload_guard =
+ BootUnloadGuard::new(gsp, dev, bar, gsp_falcon, sec2_falcon, Some(unload_bundle));
+
let mut fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
let args = FmcBootArgs::new(
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v13 9/9] gpu: nova-core: gsp: enable FSP boot path
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (7 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 8/9] gpu: nova-core: add non-sec2 unload path Alexandre Courbot
@ 2026-06-03 7:30 ` Alexandre Courbot
2026-06-03 11:04 ` Eliot Courtney
2026-06-03 15:04 ` [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
9 siblings, 1 reply; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 7:30 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
Now that all the elements are in place, enable the FSP boot path so
Hopper and Blackwell can boot.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/hal/gh100.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index 51ac3ddb832a..ea2b900e28a2 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -165,7 +165,7 @@ fn boot<'a>(
);
// Wrap the unload bundle into a drop guard so it is automatically run upon failure.
- let _unload_guard =
+ let unload_guard =
BootUnloadGuard::new(gsp, dev, bar, gsp_falcon, sec2_falcon, Some(unload_bundle));
let mut fsp = Fsp::wait_secure_boot(dev, bar, chipset, fsp_fw)?;
@@ -182,7 +182,7 @@ fn boot<'a>(
wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, args.boot_params_dma_handle())?;
- Err(ENOTSUPP)
+ Ok(unload_guard)
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v13 9/9] gpu: nova-core: gsp: enable FSP boot path
2026-06-03 7:30 ` [PATCH v13 9/9] gpu: nova-core: gsp: enable FSP boot path Alexandre Courbot
@ 2026-06-03 11:04 ` Eliot Courtney
0 siblings, 0 replies; 20+ messages in thread
From: Eliot Courtney @ 2026-06-03 11:04 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Boqun Feng
On Wed Jun 3, 2026 at 4:30 PM JST, Alexandre Courbot wrote:
> Now that all the elements are in place, enable the FSP boot path so
> Hopper and Blackwell can boot.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support
2026-06-03 7:30 [PATCH v13 0/9] gpu: nova-core: Hopper/Blackwell support Alexandre Courbot
` (8 preceding siblings ...)
2026-06-03 7:30 ` [PATCH v13 9/9] gpu: nova-core: gsp: enable FSP boot path Alexandre Courbot
@ 2026-06-03 15:04 ` Alexandre Courbot
9 siblings, 0 replies; 20+ messages in thread
From: Alexandre Courbot @ 2026-06-03 15:04 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Shashank Sharma,
Zhi Wang, David Airlie, Simona Vetter, Bjorn Helgaas,
Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, nova-gpu, LKML,
Alexandre Courbot, Boqun Feng
> Alexandre Courbot (1):
> gpu: nova-core: gsp: enable FSP boot path
>
> Eliot Courtney (1):
> gpu: nova-core: add non-sec2 unload path
>
> John Hubbard (7):
> gpu: nova-core: Hopper/Blackwell: add FSP falcon EMEM operations
> gpu: nova-core: Hopper/Blackwell: add FSP message infrastructure
> gpu: nova-core: add MCTP/NVDM protocol types for firmware communication
> gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging
> gpu: nova-core: Hopper/Blackwell: select FSP Chain of Trust version
> gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot
> gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling
All patches pushed to `drm-rust-next`, with some last-minute fixups.
Tested successfully on Turing, Ampere, Blackwell.
Thanks everyone!
^ permalink raw reply [flat|nested] 20+ messages in thread