Add the FSP messaging infrastructure needed for Chain of Trust communication on Hopper/Blackwell GPUs.
Reviewed-by: Joel Fernandes <[email protected]> Signed-off-by: John Hubbard <[email protected]> --- drivers/gpu/nova-core/falcon/fsp.rs | 79 ++++++++++++++++++++++++++++- drivers/gpu/nova-core/regs.rs | 47 +++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/nova-core/falcon/fsp.rs b/drivers/gpu/nova-core/falcon/fsp.rs index 5152c2f1ed26..081b9fce9278 100644 --- a/drivers/gpu/nova-core/falcon/fsp.rs +++ b/drivers/gpu/nova-core/falcon/fsp.rs @@ -46,7 +46,6 @@ impl Falcon<Fsp> { /// Writes `data` to FSP external memory at byte `offset` using Falcon PIO. /// /// Returns `EINVAL` if offset or data length is not 4-byte aligned. - #[expect(unused)] pub(crate) fn write_emem(&self, bar: &Bar0, offset: u32, data: &[u8]) -> Result { // TODO: replace with `is_multiple_of` once the MSRV is >= 1.82. if offset % 4 != 0 || data.len() % 4 != 0 { @@ -70,7 +69,6 @@ pub(crate) fn write_emem(&self, bar: &Bar0, offset: u32, data: &[u8]) -> Result /// Reads FSP external memory at byte `offset` into `data` using Falcon PIO. /// /// Returns `EINVAL` if offset or data length is not 4-byte aligned. - #[expect(unused)] pub(crate) fn read_emem(&self, bar: &Bar0, offset: u32, data: &mut [u8]) -> Result { // TODO: replace with `is_multiple_of` once the MSRV is >= 1.82. if offset % 4 != 0 || data.len() % 4 != 0 { @@ -88,4 +86,81 @@ pub(crate) fn read_emem(&self, bar: &Bar0, offset: u32, data: &mut [u8]) -> Resu 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. + #[expect(unused)] + pub(crate) fn poll_msgq(&self, bar: &Bar0) -> u32 { + let head = regs::NV_PFSP_MSGQ_HEAD::read(bar).address(); + let tail = regs::NV_PFSP_MSGQ_TAIL::read(bar).address(); + + if head == tail { + return 0; + } + + // TAIL points at last DWORD written, so add 4 to get total size + tail.saturating_sub(head) + 4 + } + + /// Send message to FSP. + /// + /// Writes a message to FSP EMEM and updates queue pointers to notify FSP. + /// + /// # Arguments + /// * `bar` - BAR0 memory mapping + /// * `packet` - Message data (must be 4-byte aligned in length) + /// + /// # Returns + /// `Ok(())` on success, `Err(EINVAL)` if packet is empty or not 4-byte aligned + #[expect(unused)] + pub(crate) fn send_msg(&self, bar: &Bar0, packet: &[u8]) -> Result { + if packet.is_empty() { + return Err(EINVAL); + } + + // Write message to EMEM at offset 0 (validates 4-byte alignment) + self.write_emem(bar, 0, packet)?; + + // Update queue pointers - TAIL points at last DWORD written + let tail_offset = u32::try_from(packet.len() - 4).map_err(|_| EINVAL)?; + regs::NV_PFSP_QUEUE_TAIL::default() + .set_address(tail_offset) + .write(bar); + regs::NV_PFSP_QUEUE_HEAD::default() + .set_address(0) + .write(bar); + + Ok(()) + } + + /// Receive message from FSP. + /// + /// Reads a message from FSP EMEM and resets queue pointers. + /// + /// # Arguments + /// * `bar` - BAR0 memory mapping + /// * `buffer` - Buffer to receive message data + /// * `size` - Size of message to read in bytes (from `poll_msgq`) + /// + /// # Returns + /// `Ok(bytes_read)` on success, `Err(EINVAL)` if size is 0, exceeds buffer, or not aligned + #[expect(unused)] + pub(crate) fn recv_msg(&self, bar: &Bar0, buffer: &mut [u8], size: usize) -> Result<usize> { + if size == 0 || size > buffer.len() { + return Err(EINVAL); + } + + // Read response from EMEM at offset 0 (validates 4-byte alignment) + self.read_emem(bar, 0, &mut buffer[..size])?; + + // Reset message queue pointers after reading + regs::NV_PFSP_MSGQ_TAIL::default().set_address(0).write(bar); + regs::NV_PFSP_MSGQ_HEAD::default().set_address(0).write(bar); + + Ok(size) + } } diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 30a5a49edeab..861dbde5a0ac 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -441,6 +441,53 @@ pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { 31:0 data as u32; // EMEM data register }); +// FSP (Firmware System Processor) queue registers for Hopper/Blackwell Chain of Trust +// These registers manage falcon EMEM communication queues +register!(NV_PFSP_QUEUE_HEAD @ 0x008f2c00 { + 31:0 address as u32; +}); + +register!(NV_PFSP_QUEUE_TAIL @ 0x008f2c04 { + 31:0 address as u32; +}); + +register!(NV_PFSP_MSGQ_HEAD @ 0x008f2c80 { + 31:0 address as u32; +}); + +register!(NV_PFSP_MSGQ_TAIL @ 0x008f2c84 { + 31:0 address as u32; +}); + +// PTHERM registers + +// FSP secure boot completion status register used by FSP to signal boot completion. +// This is the NV_THERM_I2CS_SCRATCH register. +// Different architectures use different addresses: +// - Hopper (GH100): 0x000200bc +// - Blackwell (GB202): 0x00ad00bc +pub(crate) fn fsp_thermal_scratch_reg_addr(arch: Architecture) -> Result<usize> { + match arch { + Architecture::Hopper => Ok(0x000200bc), + Architecture::Blackwell => Ok(0x00ad00bc), + _ => Err(kernel::error::code::ENOTSUPP), + } +} + +/// FSP writes this value to indicate successful boot completion. +#[expect(unused)] +pub(crate) const FSP_BOOT_COMPLETE_SUCCESS: u32 = 0xff; + +// Helper function to read FSP boot completion status from the correct register +#[expect(unused)] +pub(crate) fn read_fsp_boot_complete_status( + bar: &crate::driver::Bar0, + arch: Architecture, +) -> Result<u32> { + let addr = fsp_thermal_scratch_reg_addr(arch)?; + Ok(bar.read32(addr)) +} + // The modules below provide registers that are not identical on all supported chips. They should // only be used in HAL modules. -- 2.52.0
