diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs index 0956a75ef7aa..8fa47a8abb83 100644 --- a/drivers/gpu/nova-core/falcon/fsp.rs +++ b/drivers/gpu/nova-core/falcon/fsp.rs @@ -8,13 +8,16 @@ use kernel::{ io::{ + poll::read_poll_timeout, register::{ + Array, RegisterBase, WithBase, // }, Io, // }, prelude::*, + time::Delta, }; use crate::{ @@ -25,9 +28,13 @@ PFalcon2Base, PFalconBase, // }, - regs, + num, + regs, // }; +/// FSP message timeout in milliseconds. +const FSP_MSG_TIMEOUT_MS: i64 = 2000; + /// Type specifying the `Fsp` falcon engine. Cannot be instantiated. pub(crate) struct Fsp(()); @@ -46,7 +53,6 @@ impl Falcon { /// /// `data` is interpreted as little-endian 32-bit words. Returns `EINVAL` /// if the `data` length is not 4-byte aligned. - #[expect(dead_code)] fn write_emem(&mut self, bar: &Bar0, data: &[u8]) -> Result { if data.len() % 4 != 0 { return Err(EINVAL); @@ -75,7 +81,6 @@ fn write_emem(&mut self, bar: &Bar0, data: &[u8]) -> Result { /// /// `data` is stored as little-endian 32-bit words. Returns `EINVAL` if /// the `data` length is not 4-byte aligned. - #[expect(dead_code)] fn read_emem(&mut self, bar: &Bar0, data: &mut [u8]) -> Result { if data.len() % 4 != 0 { return Err(EINVAL); @@ -95,4 +100,74 @@ fn read_emem(&mut self, bar: &Bar0, data: &mut [u8]) -> Result { Ok(()) } + + /// Poll FSP for incoming data. + /// + /// Returns the size of available data in bytes, or 0 if no data is available. + /// + /// The FSP message queue is not circular. Pointers are reset to 0 after each + /// message exchange, so `tail >= head` is always true when data is present. + fn poll_msgq(&self, bar: &Bar0) -> u32 { + let head = bar.read(regs::NV_PFSP_MSGQ_HEAD::at(0)).val(); + let tail = bar.read(regs::NV_PFSP_MSGQ_TAIL::at(0)).val(); + + if head == tail { + return 0; + } + + // TAIL points at last DWORD written, so add 4 to get total size. + tail.saturating_sub(head).saturating_add(4) + } + + /// 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); + } + + self.write_emem(bar, packet)?; + + // Update queue pointers. TAIL points at the last DWORD written. + let tail_offset = u32::try_from(packet.len() - 4).map_err(|_| EINVAL)?; + bar.write( + Array::at(0), + regs::NV_PFSP_QUEUE_TAIL::zeroed().with_address(tail_offset), + ); + bar.write( + Array::at(0), + regs::NV_PFSP_QUEUE_HEAD::zeroed().with_address(0), + ); + + Ok(()) + } + + /// Reads the next message from FSP EMEM into a newly-allocated buffer and resets the queue + /// pointers. + /// + /// 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)), + |&size| size > 0, + Delta::from_millis(10), + Delta::from_millis(FSP_MSG_TIMEOUT_MS), + ) + .map(num::u32_as_usize)?; + + let mut buffer = KVec::::new(); + buffer.resize(msg_size, 0, GFP_KERNEL)?; + + self.read_emem(bar, &mut buffer)?; + + // Reset message queue pointers after reading. + bar.write(Array::at(0), regs::NV_PFSP_MSGQ_TAIL::zeroed().with_val(0)); + bar.write(Array::at(0), regs::NV_PFSP_MSGQ_HEAD::zeroed().with_val(0)); + + Ok(buffer) + } } diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index e602c7860459..ce2392ef2f8b 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -579,6 +579,27 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { } } +// FSP (Foundation Security Processor) queue registers for Hopper/Blackwell Chain of Trust. +// These registers manage falcon EMEM communication queues. + +register! { + pub(crate) NV_PFSP_QUEUE_HEAD(u32)[8] @ 0x008f2c00 { + 31:0 address => u32; + } + + pub(crate) NV_PFSP_QUEUE_TAIL(u32)[8] @ 0x008f2c04 { + 31:0 address => u32; + } + + pub(crate) NV_PFSP_MSGQ_HEAD(u32)[8] @ 0x008f2c80 { + 31:0 val => u32; + } + + pub(crate) NV_PFSP_MSGQ_TAIL(u32)[8] @ 0x008f2c84 { + 31:0 val => u32; + } +} + // The modules below provide registers that are not identical on all supported chips. They should // only be used in HAL modules.