// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. use core::{ array, convert::Infallible, ffi::FromBytesUntilNulError, ops::Range, str::Utf8Error, // }; use kernel::{ device, pci, prelude::*, transmute::{ AsBytes, FromBytes, // }, // }; use crate::{ gpu::Chipset, gsp::{ cmdq::{ Cmdq, CommandToGsp, MessageFromGsp, NoReply, // }, fw::{ self, MsgFunction, // }, }, sbuffer::SBufferIter, vgpu::VgpuState, // }; /// The `GspSetSystemInfo` command. pub(crate) struct SetSystemInfo<'a> { pdev: &'a pci::Device, chipset: Chipset, } impl<'a> SetSystemInfo<'a> { /// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`. pub(crate) fn new(pdev: &'a pci::Device, chipset: Chipset) -> Self { Self { pdev, chipset } } } impl<'a> CommandToGsp for SetSystemInfo<'a> { const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo; type Command = fw::commands::GspSetSystemInfo; type Reply = NoReply; type InitError = Error; fn init(&self) -> impl Init { Self::Command::init(self.pdev, self.chipset) } } struct RegistryEntry { key: &'static str, value: u32, } /// The `SetRegistry` command. pub(crate) struct SetRegistry { entries: KVec, } impl SetRegistry { /// Creates a new `SetRegistry` command, using a set of hardcoded entries. pub(crate) fn new(vgpu_state: VgpuState) -> Result { let mut entries = KVec::new(); // RMSecBusResetEnable - enables PCI secondary bus reset entries.push( RegistryEntry { key: "RMSecBusResetEnable", value: 1, }, GFP_KERNEL, )?; // RMForcePcieConfigSave - forces GSP-RM to preserve PCI configuration registers on // any PCI reset. entries.push( RegistryEntry { key: "RMForcePcieConfigSave", value: 1, }, GFP_KERNEL, )?; // RMDevidCheckIgnore - allows GSP-RM to boot even if the PCI dev ID is not found // in the internal product name database. entries.push( RegistryEntry { key: "RMDevidCheckIgnore", value: 1, }, GFP_KERNEL, )?; if matches!(vgpu_state, VgpuState::Enabled { .. }) { // RMSetSriovMode - required when vGPU is enabled. entries.push( RegistryEntry { key: "RMSetSriovMode", value: 1, }, GFP_KERNEL, )?; } Ok(Self { entries }) } } impl CommandToGsp for SetRegistry { const FUNCTION: MsgFunction = MsgFunction::SetRegistry; type Command = fw::commands::PackedRegistryTable; type Reply = NoReply; type InitError = Infallible; fn init(&self) -> impl Init { Self::Command::init(self.entries.len() as u32, self.size() as u32) } fn variable_payload_len(&self) -> usize { let mut key_size = 0; for entry in self.entries.iter() { key_size += entry.key.len() + 1; // +1 for NULL terminator } self.entries.len() * size_of::() + key_size } fn init_variable_payload( &self, dst: &mut SBufferIter>, ) -> Result { let string_data_start_offset = size_of::() + self.entries.len() * size_of::(); // Array for string data. let mut string_data = KVec::new(); for entry in self.entries.iter() { dst.write_all( fw::commands::PackedRegistryEntry::new( (string_data_start_offset + string_data.len()) as u32, entry.value, ) .as_bytes(), )?; let key_bytes = entry.key.as_bytes(); string_data.extend_from_slice(key_bytes, GFP_KERNEL)?; string_data.push(0, GFP_KERNEL)?; } dst.write_all(string_data.as_slice()) } } /// Message type for GSP initialization done notification. struct GspInitDone; // SAFETY: `GspInitDone` is a zero-sized type with no bytes, therefore it // trivially has no uninitialized bytes. unsafe impl FromBytes for GspInitDone {} impl MessageFromGsp for GspInitDone { const FUNCTION: MsgFunction = MsgFunction::GspInitDone; type InitError = Infallible; type Message = (); fn read( _msg: &Self::Message, _sbuffer: &mut SBufferIter>, ) -> Result { Ok(GspInitDone) } } /// Waits for GSP initialization to complete. pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result { loop { match cmdq.receive_msg::(Cmdq::RECEIVE_TIMEOUT) { Ok(_) => break Ok(()), Err(ERANGE) => continue, Err(e) => break Err(e), } } } /// The `GetGspStaticInfo` command. pub(crate) struct GetGspStaticInfo; impl CommandToGsp for GetGspStaticInfo { const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo; type Command = fw::commands::GspStaticConfigInfo; type Reply = GetGspStaticInfoReply; type InitError = Infallible; fn init(&self) -> impl Init { Self::Command::init_zeroed() } } /// The reply from the GSP to the [`GetGspStaticInfo`] command. pub(crate) struct GetGspStaticInfoReply { gpu_name: [u8; 64], /// Usable FB (VRAM) regions for driver memory allocation. pub(crate) usable_fb_regions: KVec>, } impl MessageFromGsp for GetGspStaticInfoReply { const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo; type Message = fw::commands::GspStaticConfigInfo; type InitError = Error; fn read( msg: &Self::Message, _sbuffer: &mut SBufferIter>, ) -> Result { let mut usable_fb_regions = KVec::new(); for region in msg.usable_fb_regions() { usable_fb_regions.push(region, GFP_KERNEL)?; } Ok(GetGspStaticInfoReply { gpu_name: msg.gpu_name_str(), usable_fb_regions, }) } } /// Error type for [`GetGspStaticInfoReply::gpu_name`]. #[derive(Debug)] pub(crate) enum GpuNameError { /// The GPU name string does not contain a null terminator. NoNullTerminator(FromBytesUntilNulError), /// The GPU name string contains invalid UTF-8. #[expect(dead_code)] InvalidUtf8(Utf8Error), } impl GetGspStaticInfoReply { /// Returns the name of the GPU as a string. /// /// Returns an error if the string given by the GSP does not contain a null terminator or /// contains invalid UTF-8. pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> { CStr::from_bytes_until_nul(&self.gpu_name) .map_err(GpuNameError::NoNullTerminator)? .to_str() .map_err(GpuNameError::InvalidUtf8) } } pub(crate) use fw::commands::PowerStateLevel; /// The `UnloadingGuestDriver` command, used to shut down the GSP. /// /// Only used within the `gsp` module. pub(super) struct UnloadingGuestDriver { level: PowerStateLevel, } impl UnloadingGuestDriver { /// Creates a new `UnloadingGuestDriver` command for the given [`PowerStateLevel`]. pub(super) fn new(level: PowerStateLevel) -> Self { Self { level } } } impl CommandToGsp for UnloadingGuestDriver { const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver; type Command = fw::commands::UnloadingGuestDriver; type Reply = UnloadingGuestDriverReply; type InitError = Infallible; fn init(&self) -> impl Init { fw::commands::UnloadingGuestDriver::new(self.level) } } /// The reply from the GSP to the [`UnloadingGuestDriver`] command. pub(super) struct UnloadingGuestDriverReply; impl MessageFromGsp for UnloadingGuestDriverReply { const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver; type InitError = Infallible; type Message = (); fn read( _msg: &Self::Message, _sbuffer: &mut SBufferIter>, ) -> Result { Ok(UnloadingGuestDriverReply) } }