Rust for Linux List
 help / color / mirror / Atom feed
From: Eliot Courtney <ecourtney@nvidia.com>
To: "Danilo Krummrich" <dakr@kernel.org>,
	"Lorenzo Stoakes" <ljs@kernel.org>,
	"Vlastimil Babka" <vbabka@kernel.org>,
	"Liam R. Howlett" <liam@infradead.org>,
	"Uladzislau Rezki" <urezki@gmail.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>,
	"David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>
Cc: John Hubbard <jhubbard@nvidia.com>,
	 Alistair Popple <apopple@nvidia.com>,
	Timur Tabi <ttabi@nvidia.com>,
	 rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
	 nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org,
	 Eliot Courtney <ecourtney@nvidia.com>
Subject: [PATCH 6/6] gpu: nova-core: add NVKV GSP_INIT schemas
Date: Mon, 17 Aug 2026 21:56:41 +0900	[thread overview]
Message-ID: <20260817-b4-nvkv-v1-6-b84db5e84b67@nvidia.com> (raw)
In-Reply-To: <20260817-b4-nvkv-v1-0-b84db5e84b67@nvidia.com>

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@nvidia.com>
---
 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


      parent reply	other threads:[~2026-08-17 12:59 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17 12:56 [PATCH 0/6] gpu: nova-core: add NVKV codec Eliot Courtney
2026-08-17 12:56 ` [PATCH 1/6] rust: alloc: add Vec::push_init Eliot Courtney
2026-08-17 14:02   ` Gary Guo
2026-08-19  7:43     ` Eliot Courtney
2026-08-19 10:49       ` Danilo Krummrich
2026-08-19 11:23   ` Danilo Krummrich
2026-08-19 12:08     ` Gary Guo
2026-08-19 12:14     ` Gary Guo
2026-08-25  1:52     ` Eliot Courtney
2026-08-17 12:56 ` [PATCH 2/6] gpu: nova-core: add NVKV encoder Eliot Courtney
2026-08-19 16:32   ` Danilo Krummrich
2026-08-19 16:47     ` Danilo Krummrich
2026-08-24 12:58       ` Eliot Courtney
2026-08-17 12:56 ` [PATCH 3/6] gpu: nova-core: add NVKV decoder Eliot Courtney
2026-08-17 12:56 ` [PATCH 4/6] gpu: nova-core: add NVKV typed encoding Eliot Courtney
2026-08-17 12:56 ` [PATCH 5/6] gpu: nova-core: add NVKV typed decoding Eliot Courtney
2026-08-19 18:59   ` Danilo Krummrich
2026-08-25  6:46     ` Eliot Courtney
2026-08-17 12:56 ` Eliot Courtney [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260817-b4-nvkv-v1-6-b84db5e84b67@nvidia.com \
    --to=ecourtney@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=airlied@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=apopple@nvidia.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=gary@garyguo.net \
    --cc=jhubbard@nvidia.com \
    --cc=liam@infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ljs@kernel.org \
    --cc=lossin@kernel.org \
    --cc=nova-gpu@lists.linux.dev \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=simona@ffwll.ch \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=ttabi@nvidia.com \
    --cc=urezki@gmail.com \
    --cc=vbabka@kernel.org \
    --cc=work@onurozkan.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox