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 <[email protected]>
---
 drivers/gpu/nova-core/gsp/fw/commands.rs | 447 ++++++++++++++++++++++++++++++-
 drivers/gpu/nova-core/gsp/nvkv.rs        |   3 -
 drivers/gpu/nova-core/gsp/nvkv/decode.rs |   1 +
 drivers/gpu/nova-core/gsp/nvkv/encode.rs |   1 +
 4 files changed, 448 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs 
b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 32856ff74183..02de225af917 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -4,6 +4,8 @@
 use core::ops::Range;
 
 use kernel::{
+    alloc::ArrayVec,
+    bitfield,
     device,
     pci,
     prelude::*,
@@ -15,7 +17,21 @@
 
 use crate::{
     gpu::Chipset,
-    gsp::GSP_PAGE_SIZE,
+    gsp::{
+        nvkv::{
+            nvkv_decode,
+            nvkv_encode,
+            Accumulated,
+            Array,
+            DecoderValue,
+            Encodable,
+            Encoder,
+            Key,
+            KeyId,
+            Required, //
+        },
+        GSP_PAGE_SIZE, //
+    },
     num::IntoSafeCast, //
 };
 
@@ -230,3 +246,432 @@ 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))]
+    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.
+    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,
+        DecoderValue,
+        Index,
+        Schema,
+        UnknownKeyPolicy,
+        Visit, //
+    };
+
+    use super::*;
+
+    /// Decodes the registry keys of a `GspInitRequest` into (name, value) 
pairs.
+    #[derive(Default)]
+    struct RegKeysSchema<'d> {
+        pairs: KVVec<(&'d [u8], u32)>,
+        // A name whose value has not been decoded yet.
+        name: Option<&'d [u8]>,
+    }
+
+    impl<'d> Schema for RegKeysSchema<'d> {
+        type Target = KVVec<(&'d [u8], u32)>;
+
+        fn init() -> impl Init<Self> {
+            Self::default()
+        }
+
+        fn finish(&mut self) -> impl Init<Self::Target, Error> + '_ {
+            // A name must be followed by its value.
+            if self.name.take().is_some() {
+                return Err(EINVAL);
+            }
+            Ok(core::mem::take(&mut self.pairs))
+        }
+    }
+
+    impl<'d> Visit<'d> for RegKeysSchema<'d> {
+        fn visit(&mut self, key: KeyId, index: Index, value: DecoderValue<'d>) 
-> Result<bool> {
+            if key != RegKey::REGKEY_NAME_KEY && key != 
RegKey::REGKEY_VALUE_U32_KEY {
+                return Ok(false);
+            }
+            if index != Index::new::<0>() {
+                return Err(EINVAL);
+            }
+            if key == RegKey::REGKEY_NAME_KEY {
+                // A name must be followed by its value before the next name.
+                if self.name.replace(value.try_into()?).is_some() {
+                    return Err(EINVAL);
+                }
+            } else {
+                let name = self.name.take().ok_or(EINVAL)?;
+                self.pairs.push((name, value.try_into()?), GFP_KERNEL)?;
+            }
+            Ok(true)
+        }
+    }
+
+    // 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"testkey1\0".into(),
+                key_value: 0xdead_beef.into(),
+            },
+            GFP_KERNEL,
+        )?;
+        regkeys.push(
+            RegKey {
+                key_name: b"testkey2\0".into(),
+                key_value: 0x1234_5678.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(), 26);
+
+        type Req = GspInitRequest;
+        let index0 = Index::new::<0>();
+        let mut expected = Encoder::new();
+        expected.encode_u32(Req::PCI_DEVICE_ID_KEY, index0, 45)?;
+        expected.encode_u32(Req::PCI_SUBDEVICE_ID_KEY, index0, 67)?;
+        expected.encode_u32(Req::PCI_REVISION_ID_KEY, index0, 3)?;
+        expected.encode_u32(Req::PCI_CONFIG_MIRROR_BASE_KEY, index0, 
0x1234_5678)?;
+        expected.encode_u32(Req::PCI_CONFIG_MIRROR_SIZE_KEY, index0, 0x1000)?;
+        expected.encode_u32(Req::HOST_ARCH_KEY, index0, 
HostArch::Aarch64.into())?;
+        expected.encode_u64(
+            Req::NV_DOMAIN_BUS_DEVICE_FUNC_KEY,
+            index0,
+            0x0001_0203_0405_0607,
+        )?;
+        // Each registry key is its name followed by its value, both at index 
0.
+        expected.encode_array8(RegKey::REGKEY_NAME_KEY, index0, 
b"testkey1\0")?;
+        expected.encode_u32(RegKey::REGKEY_VALUE_U32_KEY, index0, 
0xdead_beef)?;
+        expected.encode_array8(RegKey::REGKEY_NAME_KEY, index0, 
b"testkey2\0")?;
+        expected.encode_u32(RegKey::REGKEY_VALUE_U32_KEY, index0, 
0x1234_5678)?;
+        expected.encode_u32(VfInfo::VF_TOTAL_VFS_KEY, index0, 8)?;
+        expected.encode_u32(VfInfo::VF_FIRST_VF_OFFSET_KEY, index0, 1)?;
+        expected.encode_u64(VfInfo::VF_FLAGS_KEY, index0, 0x7)?;
+        expected.encode_u64(VfInfo::VF_FIRST_BAR0_ADDRESS_KEY, index0, 
0x1000_0000)?;
+        expected.encode_u64(VfInfo::VF_FIRST_BAR1_ADDRESS_KEY, index0, 
0x2000_0000)?;
+        expected.encode_u64(VfInfo::VF_FIRST_BAR2_ADDRESS_KEY, index0, 
0x3000_0000)?;
+        assert_eq!(*encoded, *expected.finish());
+
+        // Decode the registry keys back out of the request, ignoring the 
other keys.
+        let decoder = Decoder::new(&encoded, UnknownKeyPolicy::Ignore);
+        let mut schema = KBox::init(RegKeysSchema::init(), GFP_KERNEL)?;
+        let decoded = KBox::try_init(decoder.decode(&mut *schema)?, 
GFP_KERNEL)?;
+        assert_eq!(
+            decoded.as_slice(),
+            &[
+                (&b"testkey1\0"[..], 0xdead_beef),
+                (&b"testkey2\0"[..], 0x1234_5678),
+            ]
+        );
+
+        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 mut schema = KBox::init(FbRegionSchema::init(), GFP_KERNEL)?;
+        let init = decoder.decode(&mut *schema)?;
+        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 mut schema = KBox::init(Resp::init(), GFP_KERNEL)?;
+        let response = KBox::try_init(decoder.decode(&mut *schema)?, 
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 mut schema = KBox::init(Resp::init(), GFP_KERNEL)?;
+        let response = KBox::try_init(decoder.decode(&mut *schema)?, 
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 5791df07a7fa..353125e2d321 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,
     ops::{
diff --git a/drivers/gpu/nova-core/gsp/nvkv/decode.rs 
b/drivers/gpu/nova-core/gsp/nvkv/decode.rs
index 24dad31296cb..c592d3098816 100644
--- a/drivers/gpu/nova-core/gsp/nvkv/decode.rs
+++ b/drivers/gpu/nova-core/gsp/nvkv/decode.rs
@@ -132,6 +132,7 @@ fn visit(
         }
     };
 }
+pub(crate) use nvkv_decode;
 
 impl<T: Default, const KEY_ID: KeyId> Schema for Key<T, KEY_ID> {
     type Target = T;
diff --git a/drivers/gpu/nova-core/gsp/nvkv/encode.rs 
b/drivers/gpu/nova-core/gsp/nvkv/encode.rs
index 2cc0cfbad814..6d06682fca5f 100644
--- a/drivers/gpu/nova-core/gsp/nvkv/encode.rs
+++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs
@@ -62,6 +62,7 @@ fn encode(&self, encoder: &mut $crate::gsp::nvkv::Encoder) -> 
::kernel::error::R
         }
     };
 }
+pub(crate) use nvkv_encode;
 
 /// A value with a specific index that encodes under the NVKV key `KEY_ID`.
 struct IndexedKey<T, const KEY_ID: KeyId> {

-- 
2.55.0

Reply via email to