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
