From: John Hubbard Date: Wed, 3 Jun 2026 07:30:21 +0000 (+0900) Subject: gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e9e2a24d9493f50a3cc73e112b0dbdf91f319851;p=thirdparty%2Fkernel%2Fstable.git gpu: nova-core: Hopper/Blackwell: add FSP send/receive messaging FSP exchanges are request/response: the driver sends an MCTP/NVDM message and must match the reply against the request before acting on it. Add the synchronous send-and-wait path that validates the response transport and message headers and confirms the reply corresponds to the request that was sent. Signed-off-by: John Hubbard Reviewed-by: Eliot Courtney Link: https://patch.msgid.link/20260603-b4-blackwell-v13-4-d9f3a06939e0@nvidia.com [acourbot: make `MessageToFsp` private.] Signed-off-by: Alexandre Courbot --- diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs index 8fa47a8abb83..d322b81d7345 100644 --- a/drivers/gpu/nova-core/falcon/fsp.rs +++ b/drivers/gpu/nova-core/falcon/fsp.rs @@ -122,7 +122,6 @@ impl Falcon { /// Writes `packet` to FSP EMEM and updates the queue pointers to notify FSP. /// /// Returns `EINVAL` if `packet` is empty or its length is not 4-byte aligned. - #[expect(dead_code)] pub(crate) fn send_msg(&mut self, bar: &Bar0, packet: &[u8]) -> Result { if packet.is_empty() { return Err(EINVAL); @@ -149,7 +148,6 @@ impl Falcon { /// /// Returns `ETIMEDOUT` if no message was available until timeout, or a regular error code if a /// memory allocation error occurred. - #[expect(dead_code)] pub(crate) fn recv_msg(&mut self, bar: &Bar0) -> Result> { let msg_size = read_poll_timeout( || Ok(self.poll_msgq(bar)), diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs index 908dc112aa6f..f1f74832bc40 100644 --- a/drivers/gpu/nova-core/fsp.rs +++ b/drivers/gpu/nova-core/fsp.rs @@ -11,7 +11,11 @@ use kernel::{ device, io::poll::read_poll_timeout, prelude::*, - time::Delta, // + time::Delta, + transmute::{ + AsBytes, + FromBytes, // + }, }; use crate::{ @@ -22,18 +26,52 @@ use crate::{ }, firmware::fsp::FspFirmware, gpu::Chipset, + mctp::{ + MctpHeader, + NvdmHeader, + NvdmType, // + }, regs, // }; mod hal; +/// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`). +#[repr(C, packed)] +#[derive(Clone, Copy)] +struct NvdmPayloadCommandResponse { + task_id: u32, + command_nvdm_type: u32, + error_code: u32, +} + +/// Complete FSP response structure with MCTP and NVDM headers. +#[repr(C, packed)] +#[derive(Clone, Copy)] +struct FspResponse { + mctp_header: MctpHeader, + nvdm_header: NvdmHeader, + response: NvdmPayloadCommandResponse, +} + +// SAFETY: FspResponse is a packed C struct with only integral fields. +unsafe impl FromBytes for FspResponse {} + +/// Trait implemented by types representing a message to send to FSP. +/// +/// This provides [`Fsp::send_sync_fsp`] with the information it needs to send +/// a given message, following the same pattern as GSP's `CommandToGsp`. +trait MessageToFsp: AsBytes { + /// NVDM type identifying this message to FSP. + const NVDM_TYPE: NvdmType; +} + /// FSP interface for Hopper/Blackwell GPUs. /// /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent /// Chain of Trust boot. pub(crate) struct Fsp { - #[expect(dead_code)] falcon: Falcon, #[expect(dead_code)] fsp_fw: FspFirmware, @@ -69,4 +107,67 @@ impl Fsp { Ok(Fsp { falcon, fsp_fw }) } + + /// Sends a message to FSP and waits for the response. + #[expect(dead_code)] + fn send_sync_fsp(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result + where + M: MessageToFsp, + { + self.falcon.send_msg(bar, msg.as_bytes())?; + + let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| { + dev_err!(dev, "FSP response error: {:?}\n", e); + })?; + + let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { + dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); + EIO + })?; + + let mctp_header = response.mctp_header; + let nvdm_header = response.nvdm_header; + let command_nvdm_type = response.response.command_nvdm_type; + let error_code = response.response.error_code; + + if !mctp_header.is_single_packet() { + dev_err!( + dev, + "Unexpected MCTP header in FSP reply: {:x?}\n", + mctp_header, + ); + return Err(EIO); + } + + if !nvdm_header.validate(NvdmType::FspResponse) { + dev_err!( + dev, + "Unexpected NVDM header in FSP reply: {:x?}\n", + nvdm_header, + ); + return Err(EIO); + } + + if command_nvdm_type != u8::from(M::NVDM_TYPE).into() { + dev_err!( + dev, + "Expected NVDM type {:?} in reply, got {:#x}\n", + M::NVDM_TYPE, + command_nvdm_type + ); + return Err(EIO); + } + + if error_code != 0 { + dev_err!( + dev, + "NVDM command {:?} failed with error {:#x}\n", + M::NVDM_TYPE, + error_code + ); + return Err(EIO); + } + + Ok(()) + } }