Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use kernel::{
bits,
device,
dma::Coherent,
io::poll::read_poll_timeout,
pci,
prelude::*,
time::Delta,
types::ScopeGuard, //
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp,
sec2::Sec2,
Falcon, //
},
fb::FbLayout,
firmware::{
gsp::GspFirmware,
FIRMWARE_VERSION, //
},
gpu::Chipset,
gsp::{
cmdq::Cmdq,
commands,
GspFwWprMeta, //
},
};
/// Arguments required to call [`Gsp::unload`](super::Gsp::unload).
///
/// Stored as their own type to avoid repeating a long and tedious list in [`BootUnloadGuard`].
pub(super) struct BootUnloadArgs<'a> {
gsp: &'a super::Gsp,
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
gsp_falcon: &'a Falcon<Gsp>,
sec2_falcon: &'a Falcon<Sec2>,
unload_bundle: Option<super::UnloadBundle>,
}
/// Guard that calls [`Gsp::unload`](super::Gsp::unload) with a
/// [`UnloadBundle`](super::UnloadBundle) when dropped.
///
/// Used to ensure the `UnloadBundle` is run during failure paths.
pub(super) struct BootUnloadGuard<'a> {
guard: ScopeGuard<BootUnloadArgs<'a>, fn(BootUnloadArgs<'a>)>,
}
impl<'a> BootUnloadGuard<'a> {
/// Wraps `unload_bundle` into a guard that executes it when dropped.
pub(super) fn new(
gsp: &'a super::Gsp,
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
gsp_falcon: &'a Falcon<Gsp>,
sec2_falcon: &'a Falcon<Sec2>,
unload_bundle: Option<super::UnloadBundle>,
) -> Self {
Self {
guard: ScopeGuard::new_with_data(
BootUnloadArgs {
gsp,
dev,
bar,
gsp_falcon,
sec2_falcon,
unload_bundle,
},
|args| {
let _ = super::Gsp::unload(
args.gsp,
args.dev,
args.bar,
args.gsp_falcon,
args.sec2_falcon,
args.unload_bundle,
);
},
),
}
}
/// Disarms the guard and returns the [`UnloadBundle`](super::UnloadBundle) it contains.
pub(super) fn dismiss(self) -> Option<super::UnloadBundle> {
self.guard.dismiss().unload_bundle
}
}
impl super::Gsp {
/// Attempt to boot the GSP.
///
/// This is a GPU-dependent and complex procedure that involves loading firmware files from
/// user-space, patching them with signatures, and building firmware-specific intricate data
/// structures that the GSP will use at runtime.
///
/// Upon return, the GSP is up and running, and its unload bundle (to be given as argument to
/// [`Self::unload`]) returned.
pub(crate) fn boot(
self: Pin<&mut Self>,
pdev: &pci::Device<device::Bound>,
bar: Bar0<'_>,
chipset: Chipset,
gsp_falcon: &Falcon<Gsp>,
sec2_falcon: &Falcon<Sec2>,
) -> Result<Option<super::UnloadBundle>> {
let dev = pdev.as_ref();
let hal = super::hal::gsp_hal(chipset);
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, FIRMWARE_VERSION), GFP_KERNEL)?;
let fb_layout = FbLayout::new(chipset, bar, &gsp_fw)?;
dev_dbg!(dev, "{:#x?}\n", fb_layout);
let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_guard = hal.boot(
&self,
dev,
bar,
chipset,
&fb_layout,
&wpr_meta,
gsp_falcon,
sec2_falcon,
)?;
gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);
// Poll for RISC-V to become active before continuing.
read_poll_timeout(
|| Ok(gsp_falcon.is_riscv_active(bar)),
|val: &bool| *val,
Delta::from_millis(10),
Delta::from_secs(5),
)?;
dev_dbg!(pdev, "RISC-V active? {}\n", gsp_falcon.is_riscv_active(bar),);
self.cmdq
.send_command_no_wait(bar, commands::SetSystemInfo::new(pdev, chipset))?;
self.cmdq
.send_command_no_wait(bar, commands::SetRegistry::new())?;
hal.post_boot(&self, dev, bar, &gsp_fw, gsp_falcon, sec2_falcon)?;
// Wait until GSP is fully initialized.
commands::wait_gsp_init_done(&self.cmdq)?;
// Obtain and display basic GPU information.
let info = self.cmdq.send_command(bar, commands::GetGspStaticInfo)?;
match info.gpu_name() {
Ok(name) => dev_info!(pdev, "GPU name: {}\n", name),
Err(e) => dev_warn!(pdev, "GPU name unavailable: {:?}\n", e),
}
Ok(unload_guard.dismiss())
}
/// Shut down the GSP and wait until it is offline.
fn shutdown_gsp(
cmdq: &Cmdq,
bar: Bar0<'_>,
gsp_falcon: &Falcon<Gsp>,
mode: commands::PowerStateLevel,
) -> Result {
// Command to shut the GSP down.
cmdq.send_command(bar, commands::UnloadingGuestDriver::new(mode))?;
// Wait until GSP signals it is suspended.
const LIBOS_INTERRUPT_PROCESSOR_SUSPENDED: u32 = bits::bit_u32(31);
read_poll_timeout(
|| Ok(gsp_falcon.read_mailbox0(bar)),
|&mb0| mb0 & LIBOS_INTERRUPT_PROCESSOR_SUSPENDED != 0,
Delta::from_millis(10),
Delta::from_secs(5),
)
.map(|_| ())
}
/// Attempts to unload the GSP firmware.
///
/// This stops all activity on the GSP.
pub(crate) fn unload(
&self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
gsp_falcon: &Falcon<Gsp>,
sec2_falcon: &Falcon<Sec2>,
unload_bundle: Option<super::UnloadBundle>,
) -> Result {
// Shut down the GSP. Keep going even in case of error.
let mut res = Self::shutdown_gsp(
&self.cmdq,
bar,
gsp_falcon,
commands::PowerStateLevel::Level0,
)
.inspect_err(|e| dev_err!(dev, "GSP shutdown failed: {:?}\n", e));
// Run the unload bundle to reset the GSP so it can be booted again.
if let Some(unload_bundle) = unload_bundle {
res = res.and(
unload_bundle
.0
.run(dev, bar, gsp_falcon, sec2_falcon)
.inspect_err(|e| dev_err!(dev, "Unload bundle failed: {:?}\n", e)),
);
} else {
dev_warn!(
dev,
"Unload bundle is missing, GSP won't be properly reset.\n"
);
res = Err(EAGAIN);
}
res.inspect(|()| dev_info!(dev, "GSP successfully unloaded\n"))
}
}
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0
mod continuation;
use core::mem;
use kernel::{
device,
dma::{
Coherent,
DmaAddress, //
},
dma_write,
io::{
poll::read_poll_timeout,
Io, //
},
new_mutex,
prelude::*,
ptr,
sync::{
aref::ARef,
Mutex, //
},
time::Delta,
transmute::{
AsBytes,
FromBytes, //
},
};
use continuation::{
ContinuationRecord,
SplitState, //
};
use pin_init::pin_init_scope;
use crate::{
driver::Bar0,
gsp::{
fw::{
GspMsgElement,
MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
GSP_PAGE_SHIFT,
GSP_PAGE_SIZE, //
},
num,
regs,
sbuffer::SBufferIter, //
};
/// Marker type representing the absence of a reply for a command. Commands using this as their
/// reply type are sent using [`Cmdq::send_command_no_wait`].
pub(crate) struct NoReply;
/// Trait implemented by types representing a command to send to the GSP.
///
/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
/// a given command.
///
/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
/// into the space reserved for it in the command queue buffer.
///
/// Some commands may be followed by a variable-length payload. For these, the
/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
/// defined as well.
pub(crate) trait CommandToGsp {
/// Function identifying this command to the GSP.
const FUNCTION: MsgFunction;
/// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
type Command: FromBytes + AsBytes;
/// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
/// have a reply.
type Reply;
/// Error type returned by [`CommandToGsp::init`].
type InitError;
/// In-place command initializer responsible for filling the command in the command queue
/// buffer.
fn init(&self) -> impl Init<Self::Command, Self::InitError>;
/// Size of the variable-length payload following the command structure generated by
/// [`CommandToGsp::init`].
///
/// Most commands don't have a variable-length payload, so this is zero by default.
fn variable_payload_len(&self) -> usize {
0
}
/// Method initializing the variable-length payload.
///
/// The command buffer is circular, which means that we may need to jump back to its beginning
/// while in the middle of a command. For this reason, the variable-length payload is
/// initialized using a [`SBufferIter`].
///
/// This method will receive a buffer of the length returned by
/// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
/// unwritten space will lead to an error.
///
/// Most commands don't have a variable-length payload, so this does nothing by default.
fn init_variable_payload(
&self,
_dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
Ok(())
}
/// Total size of the command (including its variable-length payload) without the
/// [`GspMsgElement`] header.
fn size(&self) -> usize {
size_of::<Self::Command>() + self.variable_payload_len()
}
}
/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
pub(crate) trait MessageFromGsp: Sized {
/// Function identifying this message from the GSP.
const FUNCTION: MsgFunction;
/// Error type returned by [`MessageFromGsp::read`].
type InitError;
/// Type containing the raw message to be read from the message queue.
type Message: FromBytes;
/// Method reading the message from the message queue and returning it.
///
/// From a `Self::Message` and a [`SBufferIter`], constructs an instance of `Self` and returns
/// it.
fn read(
msg: &Self::Message,
sbuffer: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError>;
}
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
/// Circular buffer of a [`Msgq`].
///
/// This area of memory is to be shared between the driver and the GSP to exchange commands or
/// messages.
#[repr(C, align(0x1000))]
#[derive(Debug)]
struct MsgqData {
data: [[u8; GSP_PAGE_SIZE]; num::u32_as_usize(MSGQ_NUM_PAGES)],
}
// Annoyingly we are forced to use a literal to specify the alignment of
// `MsgqData`, so check that it corresponds to the actual GSP page size here.
static_assert!(align_of::<MsgqData>() == GSP_PAGE_SIZE);
/// Unidirectional message queue.
///
/// Contains the data for a message queue, that either the driver or GSP writes to.
///
/// Note that while the write pointer of `tx` corresponds to the `msgq` of the same instance, the
/// read pointer of `rx` actually refers to the `Msgq` owned by the other side.
/// This design ensures that only the driver or GSP ever writes to a given instance of this struct.
#[repr(C)]
// There is no struct defined for this in the open-gpu-kernel-source headers.
// Instead it is defined by code in `GspMsgQueuesInit()`.
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
pub(super) struct Msgq {
/// Header for sending messages, including the write pointer.
pub(super) tx: MsgqTxHeader,
/// Header for receiving messages, including the read pointer.
pub(super) rx: MsgqRxHeader,
/// The message queue proper.
msgq: MsgqData,
}
/// Structure shared between the driver and the GSP and containing the command and message queues.
#[repr(C)]
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
pub(super) struct GspMem {
/// Self-mapping page table entries.
ptes: PteArray<{ Self::PTE_ARRAY_SIZE }>,
/// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
/// write and read pointers that the CPU updates. This means that the read pointer here is an
/// index into the GSP queue.
///
/// This member is read-only for the GSP.
pub(super) cpuq: Msgq,
/// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
/// write and read pointers that the GSP updates. This means that the read pointer here is an
/// index into the CPU queue.
///
/// This member is read-only for the driver.
pub(super) gspq: Msgq,
}
impl GspMem {
const PTE_ARRAY_SIZE: usize = GSP_PAGE_SIZE / size_of::<u64>();
}
// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl AsBytes for GspMem {}
// SAFETY: These structs don't meet the no-padding requirements of FromBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl FromBytes for GspMem {}
/// Wrapper around [`GspMem`] to share it with the GPU using a [`Coherent`].
///
/// This provides the low-level functionality to communicate with the GSP, including allocation of
/// queue space to write messages to and management of read/write pointers.
///
/// This is shared with the GSP, with clear ownership rules regarding the command queues:
///
/// * The driver owns (i.e. can write to) the part of the CPU message queue between the CPU write
/// pointer and the GSP read pointer. This region is returned by [`Self::driver_write_area`].
/// * The driver owns (i.e. can read from) the part of the GSP message queue between the CPU read
/// pointer and the GSP write pointer. This region is returned by [`Self::driver_read_area`].
struct DmaGspMem(Coherent<GspMem>);
impl DmaGspMem {
/// Allocate a new instance and map it for `dev`.
fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::<Msgq>() }>();
const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();
let gsp_mem = Coherent::<GspMem>::zeroed(dev, GFP_KERNEL)?;
let start = gsp_mem.dma_handle();
// Write values one by one to avoid an on-stack instance of `PteArray`.
for i in 0..GspMem::PTE_ARRAY_SIZE {
dma_write!(gsp_mem, .ptes.0[build: i], PteArray::<0>::entry(start, i)?);
}
dma_write!(
gsp_mem,
.cpuq.tx,
MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES)
);
dma_write!(gsp_mem, .cpuq.rx, MsgqRxHeader::new());
Ok(Self(gsp_mem))
}
/// Returns the region of the CPU message queue that the driver is currently allowed to write
/// to.
///
/// As the message queue is a circular buffer, the region may be discontiguous in memory. In
/// that case the second slice will have a non-zero length.
fn driver_write_area(&mut self) -> (&mut [[u8; GSP_PAGE_SIZE]], &mut [[u8; GSP_PAGE_SIZE]]) {
let tx = self.cpu_write_ptr();
let rx = self.gsp_read_ptr();
// Pointer to the first entry of the CPU message queue.
let data = ptr::project!(mut self.0.as_mut_ptr(), .cpuq.msgq.data[build: 0]);
let (tail_end, wrap_end) = if rx == 0 {
// The write area is non-wrapping, and stops at the second-to-last entry of the command
// queue (to leave the last one empty).
(MSGQ_NUM_PAGES - 1, 0)
} else if rx <= tx {
// The write area wraps and continues until `rx - 1`.
(MSGQ_NUM_PAGES, rx - 1)
} else {
// The write area doesn't wrap and stops at `rx - 1`.
(rx - 1, 0)
};
// SAFETY:
// - `data` was created from a valid pointer, and `rx` and `tx` are in the
// `0..MSGQ_NUM_PAGES` range per the invariants of `cpu_write_ptr` and `gsp_read_ptr`,
// thus the created slices are valid.
// - The area starting at `tx` and ending at `rx - 2` modulo `MSGQ_NUM_PAGES`,
// inclusive, belongs to the driver for writing and is not accessed concurrently by
// the GSP.
// - The caller holds a reference to `self` for as long as the returned slices are live,
// meaning the CPU write pointer cannot be advanced and thus that the returned area
// remains exclusive to the CPU for the duration of the slices.
// - The created slices point to non-overlapping sub-ranges of `data` in all
// branches (in the `rx <= tx` case, the second slice ends at `rx - 1` which is strictly
// less than `tx` where the first slice starts; in the other cases the second slice is
// empty), so creating two `&mut` references from them does not violate aliasing rules.
unsafe {
(
core::slice::from_raw_parts_mut(
data.add(num::u32_as_usize(tx)),
num::u32_as_usize(tail_end - tx),
),
core::slice::from_raw_parts_mut(data, num::u32_as_usize(wrap_end)),
)
}
}
/// Returns the size of the region of the CPU message queue that the driver is currently allowed
/// to write to, in bytes.
fn driver_write_area_size(&self) -> usize {
let tx = self.cpu_write_ptr();
let rx = self.gsp_read_ptr();
// `rx` and `tx` are both in `0..MSGQ_NUM_PAGES` per the invariants of `gsp_read_ptr` and
// `cpu_write_ptr`. The minimum value case is where `rx == 0` and `tx == MSGQ_NUM_PAGES -
// 1`, which gives `0 + MSGQ_NUM_PAGES - (MSGQ_NUM_PAGES - 1) - 1 == 0`.
let slots = (rx + MSGQ_NUM_PAGES - tx - 1) % MSGQ_NUM_PAGES;
num::u32_as_usize(slots) * GSP_PAGE_SIZE
}
/// Returns the region of the GSP message queue that the driver is currently allowed to read
/// from.
///
/// As the message queue is a circular buffer, the region may be discontiguous in memory. In
/// that case the second slice will have a non-zero length.
fn driver_read_area(&self) -> (&[[u8; GSP_PAGE_SIZE]], &[[u8; GSP_PAGE_SIZE]]) {
let tx = self.gsp_write_ptr();
let rx = self.cpu_read_ptr();
// Pointer to the first entry of the GSP message queue.
let data = ptr::project!(self.0.as_ptr(), .gspq.msgq.data[build: 0]);
let (tail_end, wrap_end) = if rx <= tx {
// Read area is non-wrapping and stops right before `tx`.
(tx, 0)
} else {
// Read area is wrapping and stops right before `tx`.
(MSGQ_NUM_PAGES, tx)
};
// SAFETY:
// - `data` was created from a valid pointer, and `rx` and `tx` are in the
// `0..MSGQ_NUM_PAGES` range per the invariants of `gsp_write_ptr` and `cpu_read_ptr`,
// thus the created slices are valid.
// - The area starting at `rx` and ending at `tx - 1` modulo `MSGQ_NUM_PAGES`,
// inclusive, belongs to the driver for reading and is not accessed concurrently by
// the GSP.
// - The caller holds a reference to `self` for as long as the returned slices are live,
// meaning the CPU read pointer cannot be advanced and thus that the returned area
// remains exclusive to the CPU for the duration of the slices.
unsafe {
(
core::slice::from_raw_parts(
data.add(num::u32_as_usize(rx)),
num::u32_as_usize(tail_end - rx),
),
core::slice::from_raw_parts(data, num::u32_as_usize(wrap_end)),
)
}
}
/// Allocates a region on the command queue that is large enough to send a command of `size`
/// bytes, waiting for space to become available based on the provided timeout.
///
/// This returns a [`GspCommand`] ready to be written to by the caller.
///
/// # Errors
///
/// - `EMSGSIZE` if the command is larger than [`GSP_MSG_QUEUE_ELEMENT_SIZE_MAX`].
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the command header is not properly aligned.
fn allocate_command(&mut self, size: usize, timeout: Delta) -> Result<GspCommand<'_>> {
if size_of::<GspMsgElement>() + size > GSP_MSG_QUEUE_ELEMENT_SIZE_MAX {
return Err(EMSGSIZE);
}
read_poll_timeout(
|| Ok(self.driver_write_area_size()),
|available_bytes| *available_bytes >= size_of::<GspMsgElement>() + size,
Delta::from_micros(1),
timeout,
)?;
// Get the current writable area as an array of bytes.
let (slice_1, slice_2) = {
let (slice_1, slice_2) = self.driver_write_area();
(slice_1.as_flattened_mut(), slice_2.as_flattened_mut())
};
// Extract area for the `GspMsgElement`.
let (header, slice_1) = GspMsgElement::from_bytes_mut_prefix(slice_1).ok_or(EIO)?;
// Create the contents area.
let (slice_1, slice_2) = if slice_1.len() > size {
// Contents fits entirely in `slice_1`.
(&mut slice_1[..size], &mut slice_2[0..0])
} else {
// Need all of `slice_1` and some of `slice_2`.
let slice_2_len = size - slice_1.len();
(slice_1, &mut slice_2[..slice_2_len])
};
Ok(GspCommand {
header,
contents: (slice_1, slice_2),
})
}
// Returns the index of the memory page the GSP will write the next message to.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn gsp_write_ptr(&self) -> u32 {
super::fw::gsp_mem::gsp_write_ptr(&self.0)
}
// Returns the index of the memory page the GSP will read the next command from.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn gsp_read_ptr(&self) -> u32 {
super::fw::gsp_mem::gsp_read_ptr(&self.0)
}
// Returns the index of the memory page the CPU can read the next message from.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn cpu_read_ptr(&self) -> u32 {
super::fw::gsp_mem::cpu_read_ptr(&self.0)
}
// Informs the GSP that it can send `elem_count` new pages into the message queue.
fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
super::fw::gsp_mem::advance_cpu_read_ptr(&self.0, elem_count)
}
// Returns the index of the memory page the CPU can write the next command to.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn cpu_write_ptr(&self) -> u32 {
super::fw::gsp_mem::cpu_write_ptr(&self.0)
}
// Informs the GSP that it can process `elem_count` new pages from the command queue.
fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
super::fw::gsp_mem::advance_cpu_write_ptr(&self.0, elem_count)
}
}
/// A command ready to be sent on the command queue.
///
/// This is the type returned by [`DmaGspMem::allocate_command`].
struct GspCommand<'a> {
// Writable reference to the header of the command.
header: &'a mut GspMsgElement,
// Writable slices to the contents of the command. The second slice is zero unless the command
// loops over the command queue.
contents: (&'a mut [u8], &'a mut [u8]),
}
/// A message ready to be processed from the message queue.
///
/// This is the type returned by [`Cmdq::wait_for_msg`].
struct GspMessage<'a> {
// Reference to the header of the message.
header: &'a GspMsgElement,
// Slices to the contents of the message. The second slice is zero unless the message loops
// over the message queue.
contents: (&'a [u8], &'a [u8]),
}
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
/// area.
#[pin_data]
pub(crate) struct Cmdq {
/// Inner mutex-protected state.
#[pin]
inner: Mutex<CmdqInner>,
/// DMA handle of the command queue's shared memory region.
pub(super) dma_handle: DmaAddress,
}
impl Cmdq {
/// Offset of the data after the PTEs.
const POST_PTE_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq);
/// Offset of command queue ring buffer.
pub(crate) const CMDQ_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq)
+ core::mem::offset_of!(Msgq, msgq)
- Self::POST_PTE_OFFSET;
/// Offset of message queue ring buffer.
pub(crate) const STATQ_OFFSET: usize = core::mem::offset_of!(GspMem, gspq)
+ core::mem::offset_of!(Msgq, msgq)
- Self::POST_PTE_OFFSET;
/// Number of page table entries for the GSP shared region.
pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
/// Default timeout for receiving a message from the GSP.
pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
/// Creates a new command queue for `dev`.
pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
pin_init_scope(move || {
let gsp_mem = DmaGspMem::new(dev)?;
Ok(try_pin_init!(Self {
dma_handle: gsp_mem.0.dma_handle(),
inner <- new_mutex!(CmdqInner {
dev: dev.into(),
gsp_mem,
seq: 0,
}),
}))
})
}
/// Computes the checksum for the message pointed to by `it`.
///
/// A message is made of several parts, so `it` is an iterator over byte slices representing
/// these parts.
fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
let sum64 = it
.enumerate()
.map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
.fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));
((sum64 >> 32) as u32) ^ (sum64 as u32)
}
/// Notifies the GSP that we have updated the command queue pointers.
fn notify_gsp(bar: Bar0<'_>) {
bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
}
/// Sends `command` to the GSP and waits for the reply.
///
/// Messages with non-matching function codes are silently consumed until the expected reply
/// arrives.
///
/// The queue is locked for the entire send+receive cycle to ensure that no other command can
/// be interleaved.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available to send the command, or if the reply is
/// not received within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command and reply initializers are propagated as-is.
pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Reply>
where
M: CommandToGsp,
M::Reply: MessageFromGsp,
Error: From<M::InitError>,
Error: From<<M::Reply as MessageFromGsp>::InitError>,
{
let mut inner = self.inner.lock();
inner.send_command(bar, command)?;
loop {
match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
}
}
}
/// Sends `command` to the GSP without waiting for a reply.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
pub(crate) fn send_command_no_wait<M>(&self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp<Reply = NoReply>,
Error: From<M::InitError>,
{
self.inner.lock().send_command(bar, command)
}
/// Receive a message from the GSP.
///
/// See [`CmdqInner::receive_msg`] for details.
pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
self.inner.lock().receive_msg(timeout)
}
}
/// Inner mutex protected state of [`Cmdq`].
struct CmdqInner {
/// Device this command queue belongs to.
dev: ARef<device::Device>,
/// Current command sequence number.
seq: u32,
/// Memory area shared with the GSP for communicating commands and messages.
gsp_mem: DmaGspMem,
}
impl CmdqInner {
/// Timeout for waiting for space on the command queue.
const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1);
/// Sends `command` to the GSP, without splitting it.
///
/// # Errors
///
/// - `EMSGSIZE` if the command exceeds the maximum queue element size.
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp,
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
let size_in_bytes = command.size();
let dst = self
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
// Extract area for the command itself. The GSP message header and the command header
// together are guaranteed to fit entirely into a single page, so it's ok to only look
// at `dst.contents.0` here.
let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
// Fill the header and command in-place.
let msg_element = GspMsgElement::init(self.seq, size_in_bytes, M::FUNCTION);
// SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
// fails.
unsafe {
msg_element.__init(core::ptr::from_mut(dst.header))?;
command.init().__init(core::ptr::from_mut(cmd))?;
}
// Fill the variable-length payload, which may be empty.
let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
command.init_variable_payload(&mut sbuffer)?;
if !sbuffer.is_empty() {
return Err(EIO);
}
drop(sbuffer);
// Compute checksum now that the whole message is ready.
dst.header
.set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
dst.header.as_bytes(),
dst.contents.0,
dst.contents.1,
])));
dev_dbg!(
&self.dev,
"GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
self.seq,
M::FUNCTION,
dst.header.length(),
);
// All set - update the write pointer and inform the GSP of the new command.
let elem_count = dst.header.element_count();
self.seq += 1;
self.gsp_mem.advance_cpu_write_ptr(elem_count);
Cmdq::notify_gsp(bar);
Ok(())
}
/// Sends `command` to the GSP.
///
/// The command may be split into multiple messages if it is large.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
fn send_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
{
match SplitState::new(command)? {
SplitState::Single(command) => self.send_single_command(bar, command),
SplitState::Split(command, mut continuations) => {
self.send_single_command(bar, command)?;
while let Some(continuation) = continuations.next() {
// Turbofish needed because the compiler cannot infer M here.
self.send_single_command::<ContinuationRecord<'_>>(bar, continuation)?;
}
Ok(())
}
}
}
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
/// beyond the advertised length in its [`GspMsgElement`].
///
/// This method returns:
///
/// - A reference to the [`GspMsgElement`] of the message,
/// - Two byte slices with the contents of the message. The second slice is empty unless the
/// message loops across the message queue.
///
/// # Errors
///
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
/// message queue.
///
/// Error codes returned by the message constructor are propagated as-is.
fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
// Wait for a message to arrive from the GSP.
let (slice_1, slice_2) = read_poll_timeout(
|| Ok(self.gsp_mem.driver_read_area()),
|driver_area| !driver_area.0.is_empty(),
Delta::from_millis(1),
timeout,
)
.map(|(slice_1, slice_2)| (slice_1.as_flattened(), slice_2.as_flattened()))?;
// Extract the `GspMsgElement`.
let (header, slice_1) = GspMsgElement::from_bytes_prefix(slice_1).ok_or(EIO)?;
dev_dbg!(
&self.dev,
"GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
header.sequence(),
header.function(),
header.length(),
);
let payload_length = header.payload_length();
// Check that the driver read area is large enough for the message.
if slice_1.len() + slice_2.len() < payload_length {
return Err(EIO);
}
// Cut the message slices down to the actual length of the message.
let (slice_1, slice_2) = if slice_1.len() > payload_length {
// PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
(slice_1.split_at(payload_length).0, &slice_2[0..0])
} else {
(
slice_1,
// PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
// large as `payload_length`.
slice_2.split_at(payload_length - slice_1.len()).0,
)
};
// Validate checksum.
if Cmdq::calculate_checksum(SBufferIter::new_reader([
header.as_bytes(),
slice_1,
slice_2,
])) != 0
{
dev_err!(
&self.dev,
"GSP RPC: receive: Call {} - bad checksum\n",
header.sequence()
);
return Err(EIO);
}
Ok(GspMessage {
header,
contents: (slice_1, slice_2),
})
}
/// Receive a message from the GSP.
///
/// The expected message type is specified using the `M` generic parameter. If the pending
/// message has a different function code, `ERANGE` is returned and the message is consumed.
///
/// The read pointer is always advanced past the message, regardless of whether it matched.
///
/// # Errors
///
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
/// message queue.
/// - `EINVAL` if the function code of the message was not recognized.
/// - `ERANGE` if the message had a recognized but non-matching function code.
///
/// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
let message = self.wait_for_msg(timeout)?;
let function = message.header.function().map_err(|_| EINVAL)?;
// Extract the message. Store the result as we want to advance the read pointer even in
// case of failure.
let result = if function == M::FUNCTION {
let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
let mut sbuffer = SBufferIter::new_reader([contents_1, message.contents.1]);
M::read(cmd, &mut sbuffer)
.map_err(|e| e.into())
.inspect(|_| {
if !sbuffer.is_empty() {
dev_warn!(
&self.dev,
"GSP message {:?} has unprocessed data\n",
function
);
}
})
} else {
Err(ERANGE)
};
// Advance the read pointer past this message.
self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
message.header.length().div_ceil(GSP_PAGE_SIZE),
)?);
result
}
}
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0
//! Support for splitting large GSP commands across continuation records.
use core::convert::Infallible;
use kernel::prelude::*;
use super::{
CommandToGsp,
NoReply, //
};
use crate::{
gsp::fw::{
GspMsgElement,
MsgFunction,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
sbuffer::SBufferIter,
};
/// Maximum command size that fits in a single queue element.
const MAX_CMD_SIZE: usize = GSP_MSG_QUEUE_ELEMENT_SIZE_MAX - size_of::<GspMsgElement>();
/// Acts as an iterator over the continuation records for a split command.
pub(super) struct ContinuationRecords {
payload: KVVec<u8>,
offset: usize,
}
impl ContinuationRecords {
/// Creates a new iterator over continuation records for the given payload.
fn new(payload: KVVec<u8>) -> Self {
Self { payload, offset: 0 }
}
/// Returns the next continuation record, or [`None`] if there are no more.
pub(super) fn next(&mut self) -> Option<ContinuationRecord<'_>> {
let remaining = self.payload.len() - self.offset;
if remaining > 0 {
let chunk_size = remaining.min(MAX_CMD_SIZE);
let record =
ContinuationRecord::new(&self.payload[self.offset..(self.offset + chunk_size)]);
self.offset += chunk_size;
Some(record)
} else {
None
}
}
}
/// The [`ContinuationRecord`] command.
pub(super) struct ContinuationRecord<'a> {
data: &'a [u8],
}
impl<'a> ContinuationRecord<'a> {
/// Creates a new [`ContinuationRecord`] command with the given data.
fn new(data: &'a [u8]) -> Self {
Self { data }
}
}
impl<'a> CommandToGsp for ContinuationRecord<'a> {
const FUNCTION: MsgFunction = MsgFunction::ContinuationRecord;
type Command = ();
type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
<()>::init_zeroed()
}
fn variable_payload_len(&self) -> usize {
self.data.len()
}
fn init_variable_payload(
&self,
dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
dst.write_all(self.data)
}
}
/// Whether a command needs to be split across continuation records or not.
pub(super) enum SplitState<C: CommandToGsp> {
/// A command that fits in a single queue element.
Single(C),
/// A command split across continuation records.
Split(SplitCommand<C>, ContinuationRecords),
}
impl<C: CommandToGsp> SplitState<C> {
/// Maximum variable payload size that fits in the first command alongside the command header.
const MAX_FIRST_PAYLOAD: usize = MAX_CMD_SIZE - size_of::<C::Command>();
/// Creates a new [`SplitState`] for the given command.
///
/// If the command is too large, it will be split into a main command and some number of
/// continuation records.
pub(super) fn new(command: C) -> Result<Self> {
let payload_len = command.variable_payload_len();
if command.size() > MAX_CMD_SIZE {
let mut command_payload =
KVVec::<u8>::from_elem(0u8, payload_len.min(Self::MAX_FIRST_PAYLOAD), GFP_KERNEL)?;
let mut continuation_payload =
KVVec::<u8>::from_elem(0u8, payload_len - command_payload.len(), GFP_KERNEL)?;
let mut sbuffer = SBufferIter::new_writer([
command_payload.as_mut_slice(),
continuation_payload.as_mut_slice(),
]);
command.init_variable_payload(&mut sbuffer)?;
if !sbuffer.is_empty() {
return Err(EIO);
}
drop(sbuffer);
Ok(Self::Split(
SplitCommand::new(command, command_payload),
ContinuationRecords::new(continuation_payload),
))
} else {
Ok(Self::Single(command))
}
}
}
/// A command that has been truncated to maximum accepted length of the command queue.
///
/// The remainder of its payload is expected to be sent using [`ContinuationRecords`].
pub(super) struct SplitCommand<C: CommandToGsp> {
command: C,
payload: KVVec<u8>,
}
impl<C: CommandToGsp> SplitCommand<C> {
/// Creates a new [`SplitCommand`] wrapping `command` with the given truncated payload.
fn new(command: C, payload: KVVec<u8>) -> Self {
Self { command, payload }
}
}
impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> {
const FUNCTION: MsgFunction = C::FUNCTION;
type Command = C::Command;
type Reply = C::Reply;
type InitError = C::InitError;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
self.command.init()
}
fn variable_payload_len(&self) -> usize {
self.payload.len()
}
fn init_variable_payload(
&self,
dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
dst.write_all(&self.payload)
}
}
#[kunit_tests(nova_core_gsp_continuation)]
mod tests {
use super::*;
use kernel::transmute::{
AsBytes,
FromBytes, //
};
/// Non-zero-sized command header for testing.
#[repr(C)]
#[derive(Clone, Copy, Zeroable)]
struct TestHeader([u8; 64]);
// SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid.
unsafe impl FromBytes for TestHeader {}
// SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid.
unsafe impl AsBytes for TestHeader {}
struct TestPayload {
data: KVVec<u8>,
}
impl TestPayload {
fn generate_pattern(len: usize) -> Result<KVVec<u8>> {
let mut data = KVVec::with_capacity(len, GFP_KERNEL)?;
for i in 0..len {
// Mix in higher bits so the pattern does not repeat every 256 bytes.
data.push((i ^ (i >> 8)) as u8, GFP_KERNEL)?;
}
Ok(data)
}
fn new(len: usize) -> Result<Self> {
Ok(Self {
data: Self::generate_pattern(len)?,
})
}
}
impl CommandToGsp for TestPayload {
const FUNCTION: MsgFunction = MsgFunction::Nop;
type Command = TestHeader;
type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
TestHeader::init_zeroed()
}
fn variable_payload_len(&self) -> usize {
self.data.len()
}
fn init_variable_payload(
&self,
dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
dst.write_all(self.data.as_slice())
}
}
/// Maximum variable payload size that fits in the first command alongside the header.
const MAX_FIRST_PAYLOAD: usize = SplitState::<TestPayload>::MAX_FIRST_PAYLOAD;
fn read_payload(cmd: impl CommandToGsp) -> Result<KVVec<u8>> {
let len = cmd.variable_payload_len();
let mut buf = KVVec::from_elem(0u8, len, GFP_KERNEL)?;
let mut sbuf = SBufferIter::new_writer([buf.as_mut_slice(), &mut []]);
cmd.init_variable_payload(&mut sbuf)?;
drop(sbuf);
Ok(buf)
}
struct SplitTest {
payload_size: usize,
num_continuations: usize,
}
fn check_split(t: SplitTest) -> Result {
let payload = TestPayload::new(t.payload_size)?;
let mut num_continuations = 0;
let buf = match SplitState::new(payload)? {
SplitState::Single(cmd) => read_payload(cmd)?,
SplitState::Split(cmd, mut continuations) => {
let mut buf = read_payload(cmd)?;
assert!(size_of::<TestHeader>() + buf.len() <= MAX_CMD_SIZE);
while let Some(cont) = continuations.next() {
let payload = read_payload(cont)?;
assert!(payload.len() <= MAX_CMD_SIZE);
buf.extend_from_slice(&payload, GFP_KERNEL)?;
num_continuations += 1;
}
buf
}
};
assert_eq!(num_continuations, t.num_continuations);
assert_eq!(
buf.as_slice(),
TestPayload::generate_pattern(t.payload_size)?.as_slice()
);
Ok(())
}
#[test]
fn split_command() -> Result {
check_split(SplitTest {
payload_size: 0,
num_continuations: 0,
})?;
check_split(SplitTest {
payload_size: MAX_FIRST_PAYLOAD,
num_continuations: 0,
})?;
check_split(SplitTest {
payload_size: MAX_FIRST_PAYLOAD + 1,
num_continuations: 1,
})?;
check_split(SplitTest {
payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE,
num_continuations: 1,
})?;
check_split(SplitTest {
payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE + 1,
num_continuations: 2,
})?;
check_split(SplitTest {
payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE * 3 + MAX_CMD_SIZE / 2,
num_continuations: 4,
})?;
Ok(())
}
}
@@ -0,0 +1,279 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use core::{
array,
convert::Infallible,
ffi::FromBytesUntilNulError,
str::Utf8Error, //
};
use kernel::{
device,
pci,
prelude::*,
transmute::{
AsBytes,
FromBytes, //
}, //
};
use crate::{
gpu::Chipset,
gsp::{
cmdq::{
Cmdq,
CommandToGsp,
MessageFromGsp,
NoReply, //
},
fw::{
self,
MsgFunction, //
},
},
sbuffer::SBufferIter,
};
/// The `GspSetSystemInfo` command.
pub(crate) struct SetSystemInfo<'a> {
pdev: &'a pci::Device<device::Bound>,
chipset: Chipset,
}
impl<'a> SetSystemInfo<'a> {
/// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`.
pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Self {
Self { pdev, chipset }
}
}
impl<'a> CommandToGsp for SetSystemInfo<'a> {
const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
type Command = fw::commands::GspSetSystemInfo;
type Reply = NoReply;
type InitError = Error;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
Self::Command::init(self.pdev, self.chipset)
}
}
struct RegistryEntry {
key: &'static str,
value: u32,
}
/// The `SetRegistry` command.
pub(crate) struct SetRegistry {
entries: [RegistryEntry; Self::NUM_ENTRIES],
}
impl SetRegistry {
// For now we hard-code the registry entries. Future work will allow others to
// be added as module parameters.
const NUM_ENTRIES: usize = 3;
/// Creates a new `SetRegistry` command, using a set of hardcoded entries.
pub(crate) fn new() -> Self {
Self {
entries: [
// RMSecBusResetEnable - enables PCI secondary bus reset
RegistryEntry {
key: "RMSecBusResetEnable",
value: 1,
},
// RMForcePcieConfigSave - forces GSP-RM to preserve PCI configuration registers on
// any PCI reset.
RegistryEntry {
key: "RMForcePcieConfigSave",
value: 1,
},
// RMDevidCheckIgnore - allows GSP-RM to boot even if the PCI dev ID is not found
// in the internal product name database.
RegistryEntry {
key: "RMDevidCheckIgnore",
value: 1,
},
],
}
}
}
impl CommandToGsp for SetRegistry {
const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
type Command = fw::commands::PackedRegistryTable;
type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
Self::Command::init(Self::NUM_ENTRIES as u32, self.variable_payload_len() as u32)
}
fn variable_payload_len(&self) -> usize {
let mut key_size = 0;
for i in 0..Self::NUM_ENTRIES {
key_size += self.entries[i].key.len() + 1; // +1 for NULL terminator
}
Self::NUM_ENTRIES * size_of::<fw::commands::PackedRegistryEntry>() + key_size
}
fn init_variable_payload(
&self,
dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
let string_data_start_offset = size_of::<Self::Command>()
+ Self::NUM_ENTRIES * size_of::<fw::commands::PackedRegistryEntry>();
// Array for string data.
let mut string_data = KVec::new();
for entry in self.entries.iter().take(Self::NUM_ENTRIES) {
dst.write_all(
fw::commands::PackedRegistryEntry::new(
(string_data_start_offset + string_data.len()) as u32,
entry.value,
)
.as_bytes(),
)?;
let key_bytes = entry.key.as_bytes();
string_data.extend_from_slice(key_bytes, GFP_KERNEL)?;
string_data.push(0, GFP_KERNEL)?;
}
dst.write_all(string_data.as_slice())
}
}
/// Message type for GSP initialization done notification.
struct GspInitDone;
// SAFETY: `GspInitDone` is a zero-sized type with no bytes, therefore it
// trivially has no uninitialized bytes.
unsafe impl FromBytes for GspInitDone {}
impl MessageFromGsp for GspInitDone {
const FUNCTION: MsgFunction = MsgFunction::GspInitDone;
type InitError = Infallible;
type Message = ();
fn read(
_msg: &Self::Message,
_sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError> {
Ok(GspInitDone)
}
}
/// Waits for GSP initialization to complete.
pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
loop {
match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
Ok(_) => break Ok(()),
Err(ERANGE) => continue,
Err(e) => break Err(e),
}
}
}
/// The `GetGspStaticInfo` command.
pub(crate) struct GetGspStaticInfo;
impl CommandToGsp for GetGspStaticInfo {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
type Command = fw::commands::GspStaticConfigInfo;
type Reply = GetGspStaticInfoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
Self::Command::init_zeroed()
}
}
/// The reply from the GSP to the [`GetGspInfo`] command.
pub(crate) struct GetGspStaticInfoReply {
gpu_name: [u8; 64],
}
impl MessageFromGsp for GetGspStaticInfoReply {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
type Message = fw::commands::GspStaticConfigInfo;
type InitError = Infallible;
fn read(
msg: &Self::Message,
_sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError> {
Ok(GetGspStaticInfoReply {
gpu_name: msg.gpu_name_str(),
})
}
}
/// Error type for [`GetGspStaticInfoReply::gpu_name`].
#[derive(Debug)]
pub(crate) enum GpuNameError {
/// The GPU name string does not contain a null terminator.
NoNullTerminator(FromBytesUntilNulError),
/// The GPU name string contains invalid UTF-8.
#[expect(dead_code)]
InvalidUtf8(Utf8Error),
}
impl GetGspStaticInfoReply {
/// Returns the name of the GPU as a string.
///
/// Returns an error if the string given by the GSP does not contain a null terminator or
/// contains invalid UTF-8.
pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
CStr::from_bytes_until_nul(&self.gpu_name)
.map_err(GpuNameError::NoNullTerminator)?
.to_str()
.map_err(GpuNameError::InvalidUtf8)
}
}
pub(crate) use fw::commands::PowerStateLevel;
/// The `UnloadingGuestDriver` command, used to shut down the GSP.
///
/// Only used within the `gsp` module.
pub(super) struct UnloadingGuestDriver {
level: PowerStateLevel,
}
impl UnloadingGuestDriver {
/// Creates a new `UnloadingGuestDriver` command for the given [`PowerStateLevel`].
pub(super) fn new(level: PowerStateLevel) -> Self {
Self { level }
}
}
impl CommandToGsp for UnloadingGuestDriver {
const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
type Command = fw::commands::UnloadingGuestDriver;
type Reply = UnloadingGuestDriverReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
fw::commands::UnloadingGuestDriver::new(self.level)
}
}
/// The reply from the GSP to the [`UnloadingGuestDriver`] command.
pub(super) struct UnloadingGuestDriverReply;
impl MessageFromGsp for UnloadingGuestDriverReply {
const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
type InitError = Infallible;
type Message = ();
fn read(
_msg: &Self::Message,
_sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError> {
Ok(UnloadingGuestDriverReply)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use kernel::{
device,
pci,
prelude::*,
transmute::{
AsBytes,
FromBytes, //
}, //
};
use crate::{
gpu::Chipset,
gsp::GSP_PAGE_SIZE, //
};
use super::bindings;
/// Payload of the `GspSetSystemInfo` command.
#[repr(transparent)]
pub(crate) struct GspSetSystemInfo {
inner: bindings::GspSystemInfo,
}
static_assert!(size_of::<GspSetSystemInfo>() < GSP_PAGE_SIZE);
impl GspSetSystemInfo {
/// Returns an in-place initializer for the `GspSetSystemInfo` command.
#[allow(non_snake_case)]
pub(crate) fn init<'a>(
dev: &'a pci::Device<device::Bound>,
chipset: Chipset,
) -> impl Init<Self, Error> + 'a {
type InnerGspSystemInfo = bindings::GspSystemInfo;
let pci_config_mirror_range = chipset.pci_config_mirror_range();
let init_inner = try_init!(InnerGspSystemInfo {
gpuPhysAddr: dev.resource_start(0)?,
gpuPhysFbAddr: dev.resource_start(1)?,
gpuPhysInstAddr: dev.resource_start(3)?,
nvDomainBusDeviceFunc: u64::from(dev.dev_id()),
// Using TASK_SIZE in r535_gsp_rpc_set_system_info() seems wrong because
// TASK_SIZE is per-task. That's probably a design issue in GSP-RM though.
maxUserVa: (1 << 47) - 4096,
pciConfigMirrorBase: pci_config_mirror_range.start,
pciConfigMirrorSize: pci_config_mirror_range.end - pci_config_mirror_range.start,
PCIDeviceID: (u32::from(dev.device_id()) << 16) | u32::from(dev.vendor_id().as_raw()),
PCISubDeviceID: (u32::from(dev.subsystem_device_id()) << 16)
| u32::from(dev.subsystem_vendor_id()),
PCIRevisionID: u32::from(dev.revision_id()),
bIsPrimary: 0,
bPreserveVideoMemoryAllocations: 0,
..Zeroable::init_zeroed()
});
try_init!(GspSetSystemInfo {
inner <- init_inner,
})
}
}
// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl AsBytes for GspSetSystemInfo {}
// SAFETY: These structs don't meet the no-padding requirements of FromBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl FromBytes for GspSetSystemInfo {}
#[repr(transparent)]
pub(crate) struct PackedRegistryEntry(bindings::PACKED_REGISTRY_ENTRY);
impl PackedRegistryEntry {
pub(crate) fn new(offset: u32, value: u32) -> Self {
Self({
bindings::PACKED_REGISTRY_ENTRY {
nameOffset: offset,
// We only support DWORD types for now. Support for other types
// will come later if required.
type_: bindings::REGISTRY_TABLE_ENTRY_TYPE_DWORD as u8,
__bindgen_padding_0: Default::default(),
data: value,
length: 0,
}
})
}
}
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for PackedRegistryEntry {}
/// Payload of the `SetRegistry` command.
#[repr(transparent)]
pub(crate) struct PackedRegistryTable {
inner: bindings::PACKED_REGISTRY_TABLE,
}
impl PackedRegistryTable {
#[allow(non_snake_case)]
pub(crate) fn init(num_entries: u32, size: u32) -> impl Init<Self> {
type InnerPackedRegistryTable = bindings::PACKED_REGISTRY_TABLE;
let init_inner = init!(InnerPackedRegistryTable {
numEntries: num_entries,
size,
entries: Default::default()
});
init!(PackedRegistryTable { inner <- init_inner })
}
}
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for PackedRegistryTable {}
// SAFETY: This struct only contains integer types for which all bit patterns
// are valid.
unsafe impl FromBytes for PackedRegistryTable {}
/// Payload of the `GetGspStaticInfo` command and message.
#[repr(transparent)]
#[derive(Zeroable)]
pub(crate) struct GspStaticConfigInfo(bindings::GspStaticConfigInfo_t);
impl GspStaticConfigInfo {
/// Returns a bytes array containing the (hopefully) zero-terminated name of this GPU.
pub(crate) fn gpu_name_str(&self) -> [u8; 64] {
self.0.gpuNameString
}
}
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for GspStaticConfigInfo {}
// SAFETY: This struct only contains integer types for which all bit patterns
// are valid.
unsafe impl FromBytes for GspStaticConfigInfo {}
/// Power level requested to the [`UnloadingGuestDriver`] command.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
#[expect(unused)]
pub(crate) enum PowerStateLevel {
/// Full unload.
Level0 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0,
/// S3 (suspend to RAM).
Level3 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3,
/// Hibernate (suspend to disk).
Level7 = bindings::NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_7,
}
impl PowerStateLevel {
/// Returns `true` if this state represents a power management transition, i.e. some GPU state
/// must survive it (as opposed to a full unload).
pub(crate) fn is_power_transition(self) -> bool {
self != PowerStateLevel::Level0
}
}
/// Payload of the `UnloadingGuestDriver` command and message.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Zeroable)]
pub(crate) struct UnloadingGuestDriver(bindings::rpc_unloading_guest_driver_v1F_07);
impl UnloadingGuestDriver {
pub(crate) fn new(level: PowerStateLevel) -> Self {
Self(bindings::rpc_unloading_guest_driver_v1F_07 {
bInPMTransition: u8::from(level.is_power_transition()),
bGc6Entering: 0,
newLevel: level as u32,
..Zeroable::zeroed()
})
}
}
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for UnloadingGuestDriver {}
// SAFETY: This struct only contains integer types for which all bit patterns
// are valid.
unsafe impl FromBytes for UnloadingGuestDriver {}
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
//! Firmware bindings.
//!
//! Imports the generated bindings by `bindgen`.
//!
//! This module may not be directly used. Please abstract or re-export the needed symbols in the
//! parent module instead.
#![allow(
dead_code,
clippy::all,
clippy::undocumented_unsafe_blocks,
clippy::ptr_as_ptr,
clippy::ref_as_ptr,
missing_docs,
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
improper_ctypes,
unreachable_pub,
unsafe_op_in_unsafe_fn
)]
use kernel::ffi;
use pin_init::MaybeZeroable;
include!("r570_144/bindings.rs");
// SAFETY: This type has a size of zero, so its inclusion into another type should not affect their
// ability to implement `Zeroable`.
unsafe impl<T> kernel::prelude::Zeroable for __IncompleteArrayField<T> {}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
mod gh100;
mod tu102;
use kernel::prelude::*;
use kernel::{
device,
dma::Coherent, //
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp as GspEngine,
sec2::Sec2,
Falcon, //
},
fb::FbLayout,
firmware::gsp::GspFirmware,
gpu::{
Architecture,
Chipset, //
},
gsp::{
boot::BootUnloadGuard,
Gsp,
GspFwWprMeta, //
},
};
/// Trait for types containing the resources and code required to fully reset the GSP.
///
/// The GSP unload code might run in a situation where we cannot load firmware dynamically (e.g.
/// because we are in shutdown and the file system is not accessible anymore). Thus, the firmware
/// required for unloading is prepared at load time, and stored here until it needs to be run.
pub(super) trait UnloadBundle: Send {
/// Performs the steps required to properly reset the GSP after it has been stopped.
fn run(
&self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
) -> Result;
}
/// Trait implemented by GSP HALs.
pub(super) trait GspHal: Send {
/// Performs the GSP boot process, loading and running the required firmwares as needed.
///
/// Upon success, returns a guard that runs the GSP unload sequence if GSP boot does not
/// complete.
#[allow(clippy::too_many_arguments)]
fn boot<'a>(
&self,
gsp: &'a Gsp,
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
fb_layout: &FbLayout,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>>;
/// Performs HAL-specific post-GSP boot tasks.
///
/// This method is called by the GSP boot code after the GSP is confirmed to be running, and
/// after the initialization commands have been pushed onto its queue.
fn post_boot(
&self,
_gsp: &Gsp,
_dev: &device::Device<device::Bound>,
_bar: Bar0<'_>,
_gsp_fw: &GspFirmware,
_gsp_falcon: &Falcon<GspEngine>,
_sec2_falcon: &Falcon<Sec2>,
) -> Result {
Ok(())
}
}
/// Returns the GSP HAL to be used for `chipset`.
pub(super) fn gsp_hal(chipset: Chipset) -> &'static dyn GspHal {
match chipset.arch() {
Architecture::Turing | Architecture::Ampere | Architecture::Ada => tu102::TU102_HAL,
Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
gh100::GH100_HAL
}
}
}
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use kernel::prelude::*;
use kernel::{
device,
dma::Coherent,
io::poll::read_poll_timeout,
time::Delta, //
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp as GspEngine,
sec2::Sec2,
Falcon, //
},
fb::FbLayout,
firmware::{
fsp::FspFirmware,
FIRMWARE_VERSION, //
},
fsp::{
FmcBootArgs,
Fsp, //
},
gpu::Chipset,
gsp::{
boot::BootUnloadGuard,
hal::{
GspHal,
UnloadBundle, //
},
Gsp,
GspFwWprMeta, //
},
};
/// 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),
}
}
/// 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 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_or_error(
&self,
gsp_falcon: &Falcon<GspEngine>,
bar: Bar0<'_>,
fmc_boot_params_addr: u64,
) -> bool {
// 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.
if self.mbox0 != 0 {
return self.combined_addr() != fmc_boot_params_addr;
}
!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(
|| {
// 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.
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 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 {
/// Boot GSP via FSP Chain of Trust (Hopper/Blackwell+ path).
///
/// This path uses FSP to establish a chain of trust and boot GSP-FMC. FSP handles
/// the GSP boot internally - no manual GSP reset/boot is needed.
fn boot<'a>(
&self,
gsp: &'a Gsp,
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
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 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(
dev,
chipset,
wpr_meta.dma_handle(),
gsp.libos.dma_handle(),
false,
)?;
fsp.boot_fmc(dev, bar, fb_layout, &args)?;
wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, args.boot_params_dma_handle())?;
Ok(unload_guard)
}
}
const GH100: Gh100 = Gh100;
pub(super) const GH100_HAL: &dyn GspHal = &GH100;
@@ -0,0 +1,349 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use kernel::prelude::*;
use kernel::{
device,
dma::Coherent,
io::Io, //
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp as GspEngine,
sec2::Sec2,
Falcon, //
},
fb::FbLayout,
firmware::{
booter::{
BooterFirmware,
BooterKind, //
},
fwsec::{
bootloader::FwsecFirmwareWithBl,
FwsecCommand,
FwsecFirmware, //
},
gsp::GspFirmware,
FIRMWARE_VERSION, //
},
gpu::Chipset,
gsp::{
boot::BootUnloadGuard,
hal::{
GspHal,
UnloadBundle, //
},
sequencer::{
GspSequencer,
GspSequencerParams, //
},
Gsp,
GspFwWprMeta, //
},
regs,
vbios::Vbios, //
};
// A ready-to-run FWSEC unload firmware.
//
// Since there are two variants of the prepared firmware (with and without a bootloader), this type
// abstracts the difference.
enum FwsecUnloadFirmware {
WithoutBl(FwsecFirmware),
WithBl(FwsecFirmwareWithBl),
}
impl FwsecUnloadFirmware {
/// Loads the FWSEC SB firmware, as well as its bootloader if `chipset` requires it.
fn new(
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
chipset: Chipset,
bios: &Vbios,
gsp_falcon: &Falcon<GspEngine>,
) -> Result<Self> {
let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bar, bios, FwsecCommand::Sb)?;
Ok(if chipset.needs_fwsec_bootloader() {
Self::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?)
} else {
Self::WithoutBl(fwsec_sb)
})
}
/// Runs the FWSEC SB firmware.
fn run(
&self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
gsp_falcon: &Falcon<GspEngine>,
) -> Result {
match self {
Self::WithoutBl(fw) => fw.run(dev, gsp_falcon, bar),
Self::WithBl(fw) => fw.run(dev, gsp_falcon, bar),
}
}
}
// Contains the firmware required to fully reset GSP on chipsets where the GSP is started using
// FWSEC/Booter.
struct Sec2UnloadBundle {
fwsec_sb: FwsecUnloadFirmware,
booter_unloader: BooterFirmware,
}
impl Sec2UnloadBundle {
/// Load and prepare the resources required to properly reset the GSP after it has been stopped.
fn build(
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
chipset: Chipset,
bios: &Vbios,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
) -> Result<KBox<dyn UnloadBundle>> {
KBox::new(
Self {
fwsec_sb: FwsecUnloadFirmware::new(dev, bar, chipset, bios, gsp_falcon)?,
booter_unloader: BooterFirmware::new(
dev,
BooterKind::Unloader,
chipset,
FIRMWARE_VERSION,
sec2_falcon,
bar,
)?,
},
GFP_KERNEL,
)
.map(|b| b as KBox<dyn UnloadBundle>)
.map_err(Into::into)
}
}
impl UnloadBundle for Sec2UnloadBundle {
fn run(
&self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
) -> Result {
// Run FWSEC-SB to reset the GSP falcon to its pre-libos state.
self.fwsec_sb.run(dev, bar, gsp_falcon)?;
// Remove WPR2 region if set.
let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);
if wpr2_hi.is_wpr2_set() {
sec2_falcon.reset(bar)?;
sec2_falcon.load(dev, bar, &self.booter_unloader)?;
// Sentinel value to confirm that Booter Unloader has run.
const MAILBOX_SENTINEL: u32 = 0xff;
let (mbox0, _) =
sec2_falcon.boot(bar, Some(MAILBOX_SENTINEL), Some(MAILBOX_SENTINEL))?;
if mbox0 != 0 {
dev_err!(dev, "Booter Unloader returned error 0x{:x}\n", mbox0);
return Err(EINVAL);
}
// Confirm that the WPR2 region has been removed.
let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);
if wpr2_hi.is_wpr2_set() {
dev_err!(
dev,
"WPR2 region still set after Booter Unloader returned\n"
);
return Err(EBUSY);
}
}
Ok(())
}
}
/// Helper function to load and run the FWSEC-FRTS firmware and confirm that it has properly
/// created the WPR2 region.
fn run_fwsec_frts(
dev: &device::Device<device::Bound>,
chipset: Chipset,
falcon: &Falcon<GspEngine>,
bar: Bar0<'_>,
bios: &Vbios,
fb_layout: &FbLayout,
) -> Result {
// Check that the WPR2 region does not already exist - if it does, we cannot run
// FWSEC-FRTS until the GPU is reset.
if bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound() != 0 {
dev_err!(
dev,
"WPR2 region already exists - GPU needs to be reset to proceed\n"
);
return Err(EBUSY);
}
// FWSEC-FRTS will create the WPR2 region.
let fwsec_frts = FwsecFirmware::new(
dev,
falcon,
bar,
bios,
FwsecCommand::Frts {
frts_addr: fb_layout.frts.start,
frts_size: fb_layout.frts.len(),
},
)?;
if chipset.needs_fwsec_bootloader() {
let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
// Load and run the bootloader, which will load FWSEC-FRTS and run it.
fwsec_frts_bl.run(dev, falcon, bar)?;
} else {
// Load and run FWSEC-FRTS directly.
fwsec_frts.run(dev, falcon, bar)?;
}
// SCRATCH_E contains the error code for FWSEC-FRTS.
let frts_status = bar
.read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR)
.frts_err_code();
if frts_status != 0 {
dev_err!(
dev,
"FWSEC-FRTS returned with error code {:#x}\n",
frts_status
);
return Err(EIO);
}
// Check that the WPR2 region has been created as we requested.
let (wpr2_lo, wpr2_hi) = (
bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO).lower_bound(),
bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound(),
);
match (wpr2_lo, wpr2_hi) {
(_, 0) => {
dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n");
Err(EIO)
}
(wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => {
dev_err!(
dev,
"WPR2 region created at unexpected address {:#x}; expected {:#x}\n",
wpr2_lo,
fb_layout.frts.start,
);
Err(EIO)
}
(wpr2_lo, wpr2_hi) => {
dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_lo, wpr2_hi);
dev_dbg!(dev, "GPU instance built\n");
Ok(())
}
}
}
struct Tu102;
impl GspHal for Tu102 {
fn boot<'a>(
&self,
gsp: &'a Gsp,
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
fb_layout: &FbLayout,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
let bios = Vbios::new(dev, bar)?;
// Try and prepare the unload bundle.
//
// If the unload bundle creation fails, the GPU will need to be reset before the driver can
// be probed again.
let unload_bundle =
Sec2UnloadBundle::build(dev, bar, chipset, &bios, gsp_falcon, sec2_falcon)
.inspect_err(|e| {
dev_warn!(dev, "Failed to prepare unload firmware: {:?}\n", e);
dev_warn!(dev, "The GSP won't be able to unload properly on unbind.\n");
dev_warn!(
dev,
"The GPU will need to be reset before the driver can bind again.\n"
);
})
.ok()
.map(crate::gsp::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, unload_bundle);
// FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
if !fb_layout.frts.is_empty() {
run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, fb_layout)?;
}
gsp_falcon.reset(bar)?;
let libos_handle = gsp.libos.dma_handle();
let (mbox0, mbox1) = gsp_falcon.boot(
bar,
Some(libos_handle as u32),
Some((libos_handle >> 32) as u32),
)?;
dev_dbg!(dev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
dev_dbg!(
dev,
"Using SEC2 to load and run the booter_load firmware...\n"
);
BooterFirmware::new(
dev,
BooterKind::Loader,
chipset,
FIRMWARE_VERSION,
sec2_falcon,
bar,
)?
.run(dev, bar, sec2_falcon, wpr_meta)?;
Ok(unload_guard)
}
fn post_boot(
&self,
gsp: &Gsp,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
gsp_fw: &GspFirmware,
gsp_falcon: &Falcon<GspEngine>,
sec2_falcon: &Falcon<Sec2>,
) -> Result {
// Create and run the GSP sequencer.
let seq_params = GspSequencerParams {
bootloader_app_version: gsp_fw.bootloader.app_version,
libos_dma_handle: gsp.libos.dma_handle(),
gsp_falcon,
sec2_falcon,
dev,
bar,
};
GspSequencer::run(&gsp.cmdq, seq_params)?;
Ok(())
}
}
const TU102: Tu102 = Tu102;
pub(super) const TU102_HAL: &dyn GspHal = &TU102;
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: GPL-2.0
//! GSP Sequencer implementation for Pre-hopper GSP boot sequence.
use core::array;
use kernel::{
device,
io::{
poll::read_poll_timeout,
Io, //
},
prelude::*,
time::{
delay::fsleep,
Delta, //
},
transmute::FromBytes, //
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp,
sec2::Sec2,
Falcon, //
},
gsp::{
cmdq::{
Cmdq,
MessageFromGsp, //
},
fw,
},
num::FromSafeCast,
sbuffer::SBufferIter,
};
/// GSP Sequencer information containing the command sequence and data.
struct GspSequence {
/// Current command index for error reporting.
cmd_index: u32,
/// Command data buffer containing the sequence of commands.
cmd_data: KVec<u8>,
}
impl MessageFromGsp for GspSequence {
const FUNCTION: fw::MsgFunction = fw::MsgFunction::GspRunCpuSequencer;
type InitError = Error;
type Message = fw::RunCpuSequencer;
fn read(
msg: &Self::Message,
sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError> {
let cmd_data = sbuffer.flush_into_kvec(GFP_KERNEL)?;
Ok(GspSequence {
cmd_index: msg.cmd_index(),
cmd_data,
})
}
}
const CMD_SIZE: usize = size_of::<fw::SequencerBufferCmd>();
/// GSP Sequencer Command types with payload data.
/// Commands have an opcode and an opcode-dependent struct.
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub(crate) enum GspSeqCmd {
RegWrite(fw::RegWritePayload),
RegModify(fw::RegModifyPayload),
RegPoll(fw::RegPollPayload),
DelayUs(fw::DelayUsPayload),
RegStore(fw::RegStorePayload),
CoreReset,
CoreStart,
CoreWaitForHalt,
CoreResume,
}
impl GspSeqCmd {
/// Creates a new `GspSeqCmd` from raw data returning the command and its size in bytes.
pub(crate) fn new(data: &[u8], dev: &device::Device) -> Result<(Self, usize)> {
let fw_cmd = fw::SequencerBufferCmd::from_bytes(data).ok_or(EINVAL)?;
let opcode_size = core::mem::size_of::<u32>();
let (cmd, size) = match fw_cmd.opcode()? {
fw::SeqBufOpcode::RegWrite => {
let payload = fw_cmd.reg_write_payload()?;
let size = opcode_size + size_of_val(&payload);
(GspSeqCmd::RegWrite(payload), size)
}
fw::SeqBufOpcode::RegModify => {
let payload = fw_cmd.reg_modify_payload()?;
let size = opcode_size + size_of_val(&payload);
(GspSeqCmd::RegModify(payload), size)
}
fw::SeqBufOpcode::RegPoll => {
let payload = fw_cmd.reg_poll_payload()?;
let size = opcode_size + size_of_val(&payload);
(GspSeqCmd::RegPoll(payload), size)
}
fw::SeqBufOpcode::DelayUs => {
let payload = fw_cmd.delay_us_payload()?;
let size = opcode_size + size_of_val(&payload);
(GspSeqCmd::DelayUs(payload), size)
}
fw::SeqBufOpcode::RegStore => {
let payload = fw_cmd.reg_store_payload()?;
let size = opcode_size + size_of_val(&payload);
(GspSeqCmd::RegStore(payload), size)
}
fw::SeqBufOpcode::CoreReset => (GspSeqCmd::CoreReset, opcode_size),
fw::SeqBufOpcode::CoreStart => (GspSeqCmd::CoreStart, opcode_size),
fw::SeqBufOpcode::CoreWaitForHalt => (GspSeqCmd::CoreWaitForHalt, opcode_size),
fw::SeqBufOpcode::CoreResume => (GspSeqCmd::CoreResume, opcode_size),
};
if data.len() < size {
dev_err!(dev, "Data is not enough for command\n");
return Err(EINVAL);
}
Ok((cmd, size))
}
}
/// GSP Sequencer for executing firmware commands during boot.
pub(crate) struct GspSequencer<'a> {
/// Sequencer information with command data.
seq_info: GspSequence,
/// `Bar0` for register access.
bar: Bar0<'a>,
/// SEC2 falcon for core operations.
sec2_falcon: &'a Falcon<Sec2>,
/// GSP falcon for core operations.
gsp_falcon: &'a Falcon<Gsp>,
/// LibOS DMA handle address.
libos_dma_handle: u64,
/// Bootloader application version.
bootloader_app_version: u32,
/// Device for logging.
dev: &'a device::Device,
}
impl fw::RegWritePayload {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
let addr = usize::from_safe_cast(self.addr());
sequencer.bar.try_write32(self.val(), addr)
}
}
impl fw::RegModifyPayload {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
let addr = usize::from_safe_cast(self.addr());
sequencer.bar.try_read32(addr).and_then(|val| {
sequencer
.bar
.try_write32((val & !self.mask()) | self.val(), addr)
})
}
}
impl fw::RegPollPayload {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
let addr = usize::from_safe_cast(self.addr());
// Default timeout to 4 seconds.
let timeout_us = if self.timeout() == 0 {
4_000_000
} else {
i64::from(self.timeout())
};
// First read.
sequencer.bar.try_read32(addr)?;
// Poll the requested register with requested timeout.
read_poll_timeout(
|| sequencer.bar.try_read32(addr),
|current| (current & self.mask()) == self.val(),
Delta::ZERO,
Delta::from_micros(timeout_us),
)
.map(|_| ())
}
}
impl fw::DelayUsPayload {
fn run(&self, _sequencer: &GspSequencer<'_>) -> Result {
fsleep(Delta::from_micros(i64::from(self.val())));
Ok(())
}
}
impl fw::RegStorePayload {
fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
let addr = usize::from_safe_cast(self.addr());
sequencer.bar.try_read32(addr).map(|_| ())
}
}
impl GspSeqCmd {
fn run(&self, seq: &GspSequencer<'_>) -> Result {
match self {
GspSeqCmd::RegWrite(cmd) => cmd.run(seq),
GspSeqCmd::RegModify(cmd) => cmd.run(seq),
GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
GspSeqCmd::DelayUs(cmd) => cmd.run(seq),
GspSeqCmd::RegStore(cmd) => cmd.run(seq),
GspSeqCmd::CoreReset => {
seq.gsp_falcon.reset(seq.bar)?;
seq.gsp_falcon.dma_reset(seq.bar);
Ok(())
}
GspSeqCmd::CoreStart => {
seq.gsp_falcon.start(seq.bar)?;
Ok(())
}
GspSeqCmd::CoreWaitForHalt => {
seq.gsp_falcon.wait_till_halted(seq.bar)?;
Ok(())
}
GspSeqCmd::CoreResume => {
// At this point, 'SEC2-RTOS' has been loaded into SEC2 by the sequencer
// but neither SEC2-RTOS nor GSP-RM is running yet. This part of the
// sequencer will start both.
// Reset the GSP to prepare it for resuming.
seq.gsp_falcon.reset(seq.bar)?;
// Write the libOS DMA handle to GSP mailboxes.
seq.gsp_falcon.write_mailboxes(
seq.bar,
Some(seq.libos_dma_handle as u32),
Some((seq.libos_dma_handle >> 32) as u32),
);
// Start the SEC2 falcon which will trigger GSP-RM to resume on the GSP.
seq.sec2_falcon.start(seq.bar)?;
// Poll until GSP-RM reload/resume has completed (up to 2 seconds).
seq.gsp_falcon
.check_reload_completed(seq.bar, Delta::from_secs(2))?;
// Verify SEC2 completed successfully by checking its mailbox for errors.
let mbox0 = seq.sec2_falcon.read_mailbox0(seq.bar);
if mbox0 != 0 {
dev_err!(seq.dev, "Sequencer: sec2 errors: {:?}\n", mbox0);
return Err(EIO);
}
// Configure GSP with the bootloader version.
seq.gsp_falcon
.write_os_version(seq.bar, seq.bootloader_app_version);
// Verify the GSP's RISC-V core is active indicating successful GSP boot.
if !seq.gsp_falcon.is_riscv_active(seq.bar) {
dev_err!(seq.dev, "Sequencer: RISC-V core is not active\n");
return Err(EIO);
}
Ok(())
}
}
}
}
/// Iterator over GSP sequencer commands.
pub(crate) struct GspSeqIter<'a> {
/// Command data buffer.
cmd_data: &'a [u8],
/// Current position in the buffer.
current_offset: usize,
/// Total number of commands to process.
total_cmds: u32,
/// Number of commands processed so far.
cmds_processed: u32,
/// Device for logging.
dev: &'a device::Device,
}
impl<'a> Iterator for GspSeqIter<'a> {
type Item = Result<GspSeqCmd>;
fn next(&mut self) -> Option<Self::Item> {
// Stop if we've processed all commands or reached the end of data.
if self.cmds_processed >= self.total_cmds || self.current_offset >= self.cmd_data.len() {
return None;
}
// Check if we have enough data for opcode.
if self.current_offset + core::mem::size_of::<u32>() > self.cmd_data.len() {
return Some(Err(EIO));
}
let offset = self.current_offset;
// Handle command creation based on available data,
// zero-pad if necessary (since last command may not be full size).
let mut buffer = [0u8; CMD_SIZE];
let copy_len = if offset + CMD_SIZE <= self.cmd_data.len() {
CMD_SIZE
} else {
self.cmd_data.len() - offset
};
buffer[..copy_len].copy_from_slice(&self.cmd_data[offset..offset + copy_len]);
let cmd_result = GspSeqCmd::new(&buffer, self.dev);
cmd_result.map_or_else(
|_err| {
dev_err!(self.dev, "Error parsing command at offset {}\n", offset);
None
},
|(cmd, size)| {
self.current_offset += size;
self.cmds_processed += 1;
Some(Ok(cmd))
},
)
}
}
impl<'a> GspSequencer<'a> {
fn iter(&self) -> GspSeqIter<'_> {
let cmd_data = &self.seq_info.cmd_data[..];
GspSeqIter {
cmd_data,
current_offset: 0,
total_cmds: self.seq_info.cmd_index,
cmds_processed: 0,
dev: self.dev,
}
}
}
/// Parameters for running the GSP sequencer.
pub(crate) struct GspSequencerParams<'a> {
/// Bootloader application version.
pub(crate) bootloader_app_version: u32,
/// LibOS DMA handle address.
pub(crate) libos_dma_handle: u64,
/// GSP falcon for core operations.
pub(crate) gsp_falcon: &'a Falcon<Gsp>,
/// SEC2 falcon for core operations.
pub(crate) sec2_falcon: &'a Falcon<Sec2>,
/// Device for logging.
pub(crate) dev: &'a device::Device,
/// BAR0 for register access.
pub(crate) bar: Bar0<'a>,
}
impl<'a> GspSequencer<'a> {
pub(crate) fn run(cmdq: &Cmdq, params: GspSequencerParams<'a>) -> Result {
let seq_info = loop {
match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
Ok(seq_info) => break seq_info,
Err(ERANGE) => continue,
Err(e) => return Err(e),
}
};
let sequencer = GspSequencer {
seq_info,
bar: params.bar,
sec2_falcon: params.sec2_falcon,
gsp_falcon: params.gsp_falcon,
libos_dma_handle: params.libos_dma_handle,
bootloader_app_version: params.bootloader_app_version,
dev: params.dev,
};
dev_dbg!(sequencer.dev, "Running CPU Sequencer commands\n");
for cmd_result in sequencer.iter() {
match cmd_result {
Ok(cmd) => cmd.run(&sequencer)?,
Err(e) => {
dev_err!(
sequencer.dev,
"Error running command at index {}\n",
sequencer.seq_info.cmd_index
);
return Err(e);
}
}
}
dev_dbg!(
sequencer.dev,
"CPU Sequencer commands completed successfully\n"
);
Ok(())
}
}