[PATCH v2 23/31] gpu: nova-core: gsp: add the GSP_INIT request builder
From: John Hubbard
Date: Fri Aug 21 2026 - 22:04:04 EST
The r000 GSP boot protocol replaces the separate system-info, registry
and static-info commands with one GSP_INIT request, whose payload is an
NVKV key-value blob rather than a packed struct.
Nova-core has the NVKV codec and the GSP_INIT schema, but nothing that
fills the schema in from a PCI device.
Add the builder. It encodes the PCI identifiers and config mirror range
the RPC path already sends, and the registry entries, whose names carry
the NULL that GSP-RM counts in the encoded length. The PCI location goes
out as domain, bus and device in bits 63:32, 15:8 and 7:0, which is what
GSP-RM decodes, rather than the bus, device and function that PCI_DEVID
packs.
Assisted-by: Cursor:claude-opus-5
Reviewed-by: Timur Tabi <ttabi@xxxxxxxxxx>
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/gsp/commands.rs | 52 +++++++++++++++
drivers/gpu/nova-core/gsp/fw/commands.rs | 84 +++++++++++++++++++++++-
2 files changed, 134 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index d5575c036eb9..0c3832ccf726 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -30,8 +30,16 @@
},
fw::{
self,
+ commands::{
+ GspInitRequest,
+ RegKey, //
+ },
MsgFunction, //
},
+ nvkv::{
+ Encodeable,
+ Encoder, //
+ },
},
sbuffer::SBufferIter,
vgpu::VgpuState, //
@@ -259,6 +267,50 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
}
}
+/// Registry entries the driver sends to GSP-RM on every boot, each with the NULL terminator that
+/// Open RM counts in the encoded name length.
+///
+/// `RMSecBusResetEnable` enables PCI secondary bus reset. `RMForcePcieConfigSave` makes GSP-RM
+/// preserve PCI configuration registers across any PCI reset. `RMDevidCheckIgnore` lets GSP-RM
+/// boot when the PCI device id is absent from its product name database.
+///
+/// [`SetRegistry::new`] carries the same entries for the RPC path, where the names have no
+/// terminator because that encoding appends one.
+const REGISTRY_ENTRIES: &[(&[u8], u32)] = &[
+ (b"RMSecBusResetEnable\0", 1),
+ (b"RMForcePcieConfigSave\0", 1),
+ (b"RMDevidCheckIgnore\0", 1),
+];
+
+/// Builds the NVKV-encoded payload of a `GSP_INIT` request.
+///
+/// The payload carries the system information GSP-RM reads before it starts, and
+/// [`REGISTRY_ENTRIES`] as `REGKEY_NAME` and `REGKEY_VALUE_U32` pairs. GSP-RM requires each name
+/// to be followed by its value, which is the order [`RegKey`] declares them in.
+///
+/// # Errors
+///
+/// - `ENOMEM` if the registry list or the encoder buffer cannot be allocated.
+#[expect(dead_code)]
+pub(crate) fn build_gsp_init_payload(
+ pdev: &pci::Device<device::Bound>,
+ chipset: Chipset,
+ vgpu_state: VgpuState,
+) -> Result<KVVec<u64>> {
+ let mut regkeys = KVVec::new();
+ for &(name, value) in REGISTRY_ENTRIES {
+ regkeys.push(RegKey::new(name, value), GFP_KERNEL)?;
+ }
+ if matches!(vgpu_state, VgpuState::Enabled { .. }) {
+ regkeys.push(RegKey::new(b"RMSetSriovMode\0", 1), GFP_KERNEL)?;
+ }
+
+ let mut encoder = Encoder::new();
+ GspInitRequest::new(pdev, chipset, regkeys).encode(&mut encoder)?;
+
+ Ok(encoder.finish())
+}
+
pub(crate) use fw::commands::PowerStateLevel;
/// The `UnloadingGuestDriver` command, used to shut down the GSP.
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 5cd2d9646de3..c9deee8d23c8 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -245,6 +245,25 @@ pub(crate) enum OorArch {
Riscv64 = 5,
}
+impl OorArch {
+ /// Returns the variant naming the architecture this kernel is built for.
+ fn host() -> Self {
+ if cfg!(target_arch = "x86_64") {
+ Self::X86_64
+ } else if cfg!(target_arch = "aarch64") {
+ Self::Aarch64
+ } else if cfg!(target_arch = "powerpc64") {
+ Self::Ppc64le
+ } else if cfg!(target_arch = "arm") {
+ Self::Arm
+ } else if cfg!(target_arch = "riscv64") {
+ Self::Riscv64
+ } else {
+ Self::None
+ }
+ }
+}
+
// TODO[FPRI]: This is a temporary solution to be replaced with the corresponding derive macros once
// they land.
impl TryFrom<u32> for OorArch {
@@ -271,7 +290,7 @@ fn from(value: OorArch) -> Self {
nvkv_encode! {
/// A GSP registry entry.
- struct RegKey {
+ pub(crate) struct RegKey {
key_name: Key<&'static [u8], { Self::REGKEY_NAME_KEY }>,
key_value: Key<u32, { Self::REGKEY_VALUE_U32_KEY }>,
}
@@ -280,6 +299,15 @@ struct RegKey {
impl RegKey {
const REGKEY_NAME_KEY: KeyId = 0x3070;
const REGKEY_VALUE_U32_KEY: KeyId = 0x3071;
+
+ /// Creates a registry entry. `key_name` must include its NULL terminator, which GSP-RM counts
+ /// in the encoded name length.
+ pub(crate) fn new(key_name: &'static [u8], key_value: u32) -> Self {
+ Self {
+ key_name: key_name.into(),
+ key_value: key_value.into(),
+ }
+ }
}
impl Encodeable for KVVec<RegKey> {
@@ -317,7 +345,7 @@ impl VfInfo {
nvkv_encode! {
/// Payload of the `GSP_INIT` command.
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
- struct GspInitRequest {
+ pub(crate) struct GspInitRequest {
pci_device_id: Key<u32, { Self::PCI_DEVICE_ID_KEY }>,
pci_sub_device_id: Key<u32, { Self::PCI_SUBDEVICE_ID_KEY }>,
pci_revision_id: Key<u32, { Self::PCI_REVISION_ID_KEY }>,
@@ -330,6 +358,27 @@ struct GspInitRequest {
}
}
+bitfield! {
+ /// PCI bus, device and function, packed the way `PCI_DEVID` packs them, which is what
+ /// [`pci::Device::dev_id`] returns.
+ struct PciDevId(u16) {
+ 15:8 bus;
+ 7:3 device;
+ 2:0 function;
+ }
+}
+
+bitfield! {
+ /// A GPU's PCI location, encoded the way GSP-RM decodes it, matching Open RM's
+ /// `gpuEncodeDomainBusDevice`. Despite the name GSP-RM gives the key, the function number
+ /// is not part of it.
+ struct DomainBusDevice(u64) {
+ 63:32 domain;
+ 15:8 bus;
+ 7:0 device;
+ }
+}
+
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
impl GspInitRequest {
const PCI_DEVICE_ID_KEY: KeyId = 0x0001;
@@ -339,6 +388,37 @@ impl GspInitRequest {
const PCI_CONFIG_MIRROR_SIZE_KEY: KeyId = 0x0011;
const OOR_ARCH_KEY: KeyId = 0x0070;
const NV_DOMAIN_BUS_DEVICE_FUNC_KEY: KeyId = 0x1020;
+
+ /// Describes `dev` to GSP-RM and asks it to apply `regkeys`.
+ ///
+ /// The same identifiers reach GSP-RM through [`GspSetSystemInfo::init`] on the RPC path.
+ pub(crate) fn new(
+ dev: &pci::Device<device::Bound>,
+ chipset: Chipset,
+ regkeys: KVVec<RegKey>,
+ ) -> Self {
+ let mirror = chipset.pci_config_mirror_range();
+ let dev_id = PciDevId::from(dev.dev_id());
+ let bus_device_func = DomainBusDevice::zeroed()
+ .with_domain(dev.domain_nr())
+ .with_bus(u8::from(dev_id.bus()))
+ .with_device(u8::from(dev_id.device()));
+ let device_id = (u32::from(dev.device_id()) << 16) | u32::from(dev.vendor_id().as_raw());
+ let sub_device_id =
+ (u32::from(dev.subsystem_device_id()) << 16) | u32::from(dev.subsystem_vendor_id());
+
+ Self {
+ pci_device_id: device_id.into(),
+ pci_sub_device_id: sub_device_id.into(),
+ pci_revision_id: u32::from(dev.revision_id()).into(),
+ pci_config_mirror_base: mirror.start.into(),
+ pci_config_mirror_size: (mirror.end - mirror.start).into(),
+ oor_arch: OorArch::host().into(),
+ bus_device_func: u64::from(bus_device_func).into(),
+ regkeys,
+ vf_info: None,
+ }
+ }
}
// Decode:
--
2.55.0