[PATCH 6/6] gpu: nova-core: add NVKV GSP_INIT schemas
From: Eliot Courtney
Date: Mon Aug 17 2026 - 09:02:25 EST
Add the first user of NVKV encode/decode which is the request and
response for GSP init. For now this is exercised via unit tests. Later
patches will support GMCAPI in `Cmdq` and use these messages.
Signed-off-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
---
drivers/gpu/nova-core/gsp/fw/commands.rs | 349 +++++++++++++++++++++++++++++++
drivers/gpu/nova-core/gsp/nvkv.rs | 3 -
2 files changed, 349 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 6dc31d1bf5ae..4de44c2dc6aa 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -4,6 +4,7 @@
use core::ops::Range;
use kernel::{
+ bitfield,
device,
pci,
prelude::*,
@@ -19,6 +20,20 @@
num::IntoSafeCast, //
};
+use crate::gsp::nvkv::{
+ nvkv_decode,
+ nvkv_encode,
+ Accumulated,
+ Array,
+ ArrayVec,
+ DecoderValue,
+ Encodable,
+ Encoder,
+ Key,
+ KeyId,
+ Required, //
+};
+
use super::bindings;
/// Payload of the `GspSetSystemInfo` command.
@@ -217,3 +232,337 @@ unsafe impl AsBytes for UnloadingGuestDriver {}
// SAFETY: This struct only contains integer types for which all bit patterns
// are valid.
unsafe impl FromBytes for UnloadingGuestDriver {}
+
+/// The host CPU architecture.
+#[derive(Clone, Copy)]
+pub(crate) enum HostArch {
+ None = 0,
+ X86_64 = 1,
+ Ppc64le = 2,
+ Arm = 3,
+ Aarch64 = 4,
+ Riscv64 = 5,
+}
+
+// TODO[FPRI]: This is a temporary solution to be replaced with the corresponding derive macros once
+// they land.
+impl TryFrom<u32> for HostArch {
+ type Error = Error;
+
+ fn try_from(value: u32) -> Result<Self> {
+ match value {
+ 0 => Ok(Self::None),
+ 1 => Ok(Self::X86_64),
+ 2 => Ok(Self::Ppc64le),
+ 3 => Ok(Self::Arm),
+ 4 => Ok(Self::Aarch64),
+ 5 => Ok(Self::Riscv64),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl From<HostArch> for u32 {
+ fn from(value: HostArch) -> Self {
+ value as u32
+ }
+}
+
+nvkv_encode! {
+ /// A GSP registry entry.
+ struct RegKey {
+ key_name: Key<&'static [u8], { Self::REGKEY_NAME_KEY }>,
+ key_value: Key<u32, { Self::REGKEY_VALUE_U32_KEY }>,
+ }
+}
+
+impl RegKey {
+ // Define the Key IDs read/written by GSP.
+ const REGKEY_NAME_KEY: KeyId = 0x3070;
+ const REGKEY_VALUE_U32_KEY: KeyId = 0x3071;
+}
+
+impl Encodable for KVVec<RegKey> {
+ fn encode(&self, encoder: &mut Encoder) -> Result {
+ for regkey in self {
+ regkey.encode(encoder)?;
+ }
+ Ok(())
+ }
+}
+
+nvkv_encode! {
+ /// SR-IOV virtual function information.
+ struct VfInfo {
+ total_vfs: Key<u32, { Self::VF_TOTAL_VFS_KEY }>,
+ first_vf_offset: Key<u32, { Self::VF_FIRST_VF_OFFSET_KEY }>,
+ flags: Key<u64, { Self::VF_FLAGS_KEY }>,
+ first_bar0_address: Key<u64, { Self::VF_FIRST_BAR0_ADDRESS_KEY }>,
+ first_bar1_address: Key<u64, { Self::VF_FIRST_BAR1_ADDRESS_KEY }>,
+ first_bar2_address: Key<u64, { Self::VF_FIRST_BAR2_ADDRESS_KEY }>,
+ }
+}
+
+impl VfInfo {
+ // Define the Key IDs read/written by GSP.
+ const VF_TOTAL_VFS_KEY: KeyId = 0x0080;
+ const VF_FIRST_VF_OFFSET_KEY: KeyId = 0x0081;
+ const VF_FLAGS_KEY: KeyId = 0x1003;
+ const VF_FIRST_BAR0_ADDRESS_KEY: KeyId = 0x1050;
+ const VF_FIRST_BAR1_ADDRESS_KEY: KeyId = 0x1051;
+ const VF_FIRST_BAR2_ADDRESS_KEY: KeyId = 0x1052;
+}
+
+nvkv_encode! {
+ /// Payload of the `GSP_INIT` command.
+ // TODO: expect() doesn't work here due to Self:: reference, fixed in 1.97.0
+ // https://github.com/rust-lang/rust/pull/154377
+ #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
+ 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 }>,
+ pci_config_mirror_base: Key<u32, { Self::PCI_CONFIG_MIRROR_BASE_KEY }>,
+ pci_config_mirror_size: Key<u32, { Self::PCI_CONFIG_MIRROR_SIZE_KEY }>,
+ host_arch: Key<HostArch, { Self::HOST_ARCH_KEY }, u32>,
+ bus_device_func: Key<u64, { Self::NV_DOMAIN_BUS_DEVICE_FUNC_KEY }>,
+ regkeys: KVVec<RegKey>,
+ vf_info: Option<VfInfo>,
+ }
+}
+
+impl GspInitRequest {
+ // Define the Key IDs read/written by GSP.
+ const PCI_DEVICE_ID_KEY: KeyId = 0x0001;
+ const PCI_SUBDEVICE_ID_KEY: KeyId = 0x0002;
+ const PCI_REVISION_ID_KEY: KeyId = 0x0003;
+ const PCI_CONFIG_MIRROR_BASE_KEY: KeyId = 0x0010;
+ const PCI_CONFIG_MIRROR_SIZE_KEY: KeyId = 0x0011;
+ const HOST_ARCH_KEY: KeyId = 0x0070;
+ const NV_DOMAIN_BUS_DEVICE_FUNC_KEY: KeyId = 0x1020;
+}
+
+// Decode:
+
+// Should decode with UnknownKeyPolicy::Ignore.
+nvkv_decode! {
+ /// Schema for the `GSP_INIT` response.
+ // TODO: expect() doesn't work here due to Self:: reference, fixed in 1.97.0
+ // https://github.com/rust-lang/rust/pull/154377
+ #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
+ #[derive(Default)]
+ struct GspInitResponseSchema => GspInitResponse {
+ gpu_name:
+ Array<u8, { GspInitResponse::MAX_GPU_NAME_LEN }, { Self::GPU_NAME_STRING_KEY }>,
+ fb_regions: Accumulated<FbRegionSchema>,
+ bar1_pde_base: Required<u64, { Self::BAR1_PDE_BASE_KEY }>,
+ vmmu_segment_size: Key<u64, { Self::VMMU_SEGMENT_SIZE_KEY }>,
+ }
+}
+
+impl GspInitResponseSchema {
+ // Define the Key IDs read/written by GSP.
+ const GPU_NAME_STRING_KEY: KeyId = 0x2000;
+ const BAR1_PDE_BASE_KEY: KeyId = 0x1020;
+ const VMMU_SEGMENT_SIZE_KEY: KeyId = 0x1050;
+}
+
+/// Payload of the `GSP_INIT` response.
+struct GspInitResponse {
+ gpu_name: ArrayVec<u8, { Self::MAX_GPU_NAME_LEN }>,
+ fb_regions: KVVec<FbRegion>,
+ bar1_pde_base: u64,
+ vmmu_segment_size: u64,
+}
+
+impl GspInitResponse {
+ const MAX_GPU_NAME_LEN: usize = 64;
+}
+
+nvkv_decode! {
+ /// Schema for one FB region of the `GSP_INIT` response.
+ #[derive(Default)]
+ struct FbRegionSchema => FbRegion {
+ base: Required<u64, { Self::BASE_KEY }>,
+ limit: Required<u64, { Self::LIMIT_KEY }>,
+ flags: Required<FbRegionFlags, { Self::FLAGS_KEY }>,
+ tag: Required<u32, { Self::TAG_KEY }>,
+ }
+}
+
+impl FbRegionSchema {
+ // Define the Key IDs read/written by GSP.
+ const BASE_KEY: KeyId = 0x1011;
+ const LIMIT_KEY: KeyId = 0x1012;
+ const FLAGS_KEY: KeyId = 0x0012;
+ const TAG_KEY: KeyId = 0x0013;
+}
+
+bitfield! {
+ /// FB region attribute flags.
+ struct FbRegionFlags(u32) {
+ 0:0 support_compressed => bool;
+ 1:1 support_iso => bool;
+ 2:2 protected => bool;
+ }
+}
+
+impl TryFrom<DecoderValue<'_>> for FbRegionFlags {
+ type Error = Error;
+
+ fn try_from(value: DecoderValue<'_>) -> Result<Self> {
+ if let DecoderValue::Scalar32(v) = value {
+ Ok(v.into())
+ } else {
+ Err(EINVAL)
+ }
+ }
+}
+
+/// One FB memory region.
+struct FbRegion {
+ base: u64,
+ limit: u64,
+ flags: FbRegionFlags,
+ tag: u32,
+}
+
+#[kunit_tests(nova_core_fw_commands)]
+mod tests {
+ use crate::gsp::nvkv::{
+ Decoder,
+ Index,
+ UnknownKeyPolicy, //
+ };
+
+ use super::*;
+
+ // Tests that `GspInitRequest` encodes correctly.
+ #[test]
+ fn gsp_init_request() -> Result {
+ let mut encoder = Encoder::new();
+
+ let mut regkeys = KVVec::new();
+ regkeys.push(
+ RegKey {
+ key_name: b"test_key\0".into(),
+ key_value: 0xdead_beef.into(),
+ },
+ GFP_KERNEL,
+ )?;
+
+ let gsp_init = GspInitRequest {
+ pci_device_id: 45.into(),
+ pci_sub_device_id: 67.into(),
+ pci_revision_id: 3.into(),
+ pci_config_mirror_base: 0x1234_5678.into(),
+ pci_config_mirror_size: 0x1000.into(),
+ host_arch: HostArch::Aarch64.into(),
+ bus_device_func: 0x0001_0203_0405_0607.into(),
+ regkeys,
+ vf_info: Some(VfInfo {
+ total_vfs: 8.into(),
+ first_vf_offset: 1.into(),
+ flags: 0x7.into(),
+ first_bar0_address: 0x1000_0000.into(),
+ first_bar1_address: 0x2000_0000.into(),
+ first_bar2_address: 0x3000_0000.into(),
+ }),
+ };
+
+ gsp_init.encode(&mut encoder)?;
+ let encoded = encoder.finish();
+ assert_eq!(encoded.len(), 22);
+
+ Ok(())
+ }
+
+ // Tests that FB region decoding fails when required keys are missing.
+ #[test]
+ fn decode_fb_region_missing_required_fails() -> Result {
+ let mut encoder = Encoder::new();
+ encoder.encode_u64(FbRegionSchema::BASE_KEY, Index::new::<0>(), 0x1000_0000)?;
+ let data = encoder.finish();
+
+ let decoder = Decoder::new(&data, UnknownKeyPolicy::Ignore);
+ let init = decoder.decode(FbRegionSchema::default())?;
+ assert!(KBox::try_init(init, GFP_KERNEL).is_err());
+
+ Ok(())
+ }
+
+ // Tests that a minimal and a full `GSP_INIT` response decode correctly.
+ #[test]
+ fn gsp_init_response() -> Result {
+ let name = b"test name\0";
+ const BAR1_PDE_BASE: u64 = 0xdead_0000;
+ const FB_REGION0_BASE: u64 = 0x1000_0000;
+ const FB_REGION0_LIMIT: u64 = 0x1fff_ffff;
+ const FB_REGION0_FLAGS: u32 = 0x7;
+ const FB_REGION0_TAG: u32 = 0;
+ const FB_REGION1_BASE: u64 = 0x2000_0000;
+ const FB_REGION1_LIMIT: u64 = 0x2fff_ffff;
+ const FB_REGION1_FLAGS: u32 = 0x3;
+ const FB_REGION1_TAG: u32 = 1;
+ const VMMU_SEGMENT_SIZE: u64 = 0x0200_0000;
+
+ type Resp = GspInitResponseSchema;
+
+ let index0 = Index::new::<0>();
+ let index1 = Index::new::<1>();
+
+ // A minimal response: only the BAR1 PDE base, so the FB region list stays empty.
+ let mut encoder = Encoder::new();
+ encoder.encode_u64(Resp::BAR1_PDE_BASE_KEY, index0, BAR1_PDE_BASE)?;
+ let data = encoder.finish();
+
+ let decoder = Decoder::new(&data, UnknownKeyPolicy::Ignore);
+ let response = KBox::try_init(decoder.decode(Resp::default())?, GFP_KERNEL)?;
+ assert_eq!(response.bar1_pde_base, BAR1_PDE_BASE);
+ assert!(response.fb_regions.is_empty());
+
+ // A full response.
+ let mut encoder = Encoder::new();
+ encoder.encode_array8(Resp::GPU_NAME_STRING_KEY, index0, name)?;
+ encoder.encode_u64(Resp::BAR1_PDE_BASE_KEY, index0, BAR1_PDE_BASE)?;
+ encoder.encode_u64(FbRegionSchema::BASE_KEY, index0, FB_REGION0_BASE)?;
+ encoder.encode_u64(FbRegionSchema::LIMIT_KEY, index0, FB_REGION0_LIMIT)?;
+ encoder.encode_u32(FbRegionSchema::FLAGS_KEY, index0, FB_REGION0_FLAGS)?;
+ encoder.encode_u32(FbRegionSchema::TAG_KEY, index0, FB_REGION0_TAG)?;
+
+ // Test that this unrelated key can safely interleave.
+ encoder.encode_u64(Resp::VMMU_SEGMENT_SIZE_KEY, index0, VMMU_SEGMENT_SIZE)?;
+
+ encoder.encode_u64(FbRegionSchema::BASE_KEY, index1, FB_REGION1_BASE)?;
+ encoder.encode_u64(FbRegionSchema::LIMIT_KEY, index1, FB_REGION1_LIMIT)?;
+ encoder.encode_u32(FbRegionSchema::FLAGS_KEY, index1, FB_REGION1_FLAGS)?;
+ encoder.encode_u32(FbRegionSchema::TAG_KEY, index1, FB_REGION1_TAG)?;
+ let data = encoder.finish();
+
+ let decoder = Decoder::new(&data, UnknownKeyPolicy::Error);
+ let response = KBox::try_init(decoder.decode(Resp::default())?, GFP_KERNEL)?;
+
+ assert_eq!(&*response.gpu_name, &name[..]);
+ assert_eq!(response.bar1_pde_base, BAR1_PDE_BASE);
+ assert_eq!(response.fb_regions.len(), 2);
+
+ let fb_region0 = &response.fb_regions[0];
+ assert_eq!(fb_region0.base, FB_REGION0_BASE);
+ assert_eq!(fb_region0.limit, FB_REGION0_LIMIT);
+ assert_eq!(fb_region0.flags.into_raw(), FB_REGION0_FLAGS);
+ assert!(fb_region0.flags.support_compressed());
+ assert!(fb_region0.flags.support_iso());
+ assert!(fb_region0.flags.protected());
+ assert_eq!(fb_region0.tag, FB_REGION0_TAG);
+
+ let fb_region1 = &response.fb_regions[1];
+ assert_eq!(fb_region1.base, FB_REGION1_BASE);
+ assert_eq!(fb_region1.limit, FB_REGION1_LIMIT);
+ assert_eq!(fb_region1.flags.into_raw(), FB_REGION1_FLAGS);
+ assert_eq!(fb_region1.tag, FB_REGION1_TAG);
+
+ assert_eq!(response.vmmu_segment_size, VMMU_SEGMENT_SIZE);
+
+ Ok(())
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/nvkv.rs
index a0068847bb80..0afd6d5c48bd 100644
--- a/drivers/gpu/nova-core/gsp/nvkv.rs
+++ b/drivers/gpu/nova-core/gsp/nvkv.rs
@@ -9,9 +9,6 @@
//! function calls will map to some struct - for example, f(GPU_NAME_STRING_KEY, 0, b"some gpu")
//! naturally maps to storing a &str with the GPU name.
-#![cfg_attr(not(CONFIG_KUNIT), expect(unused_imports))]
-#![cfg_attr(not(CONFIG_KUNIT), expect(unused_macros))]
-
use core::marker::PhantomData;
use core::ops::{
Deref,
--
2.55.0