1 // SPDX-License-Identifier: GPL-2.0 2 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 4 //! FSP (Foundation Security Processor) falcon engine for Hopper/Blackwell GPUs. 5 //! 6 //! The FSP falcon handles secure boot and Chain of Trust operations 7 //! on Hopper and Blackwell architectures, replacing SEC2's role. 8 9 use kernel::{ 10 io::{ 11 io_project, 12 poll::read_poll_timeout, 13 register, 14 register::Array, 15 Io, 16 Mmio, // 17 }, 18 prelude::*, 19 sizes::SZ_1K, 20 time::Delta, 21 }; 22 23 use crate::{ 24 driver::{ 25 Bar0, 26 NovaRegisters, // 27 }, 28 falcon::{ 29 Falcon, 30 FalconEngine, // 31 }, 32 num, 33 regs, // 34 }; 35 36 /// FSP message timeout in milliseconds. 37 const FSP_MSG_TIMEOUT_MS: i64 = 2000; 38 39 /// Size of the FSP EMEM channel 0 that we can use. 40 const FSP_EMEM_CHANNEL_0_SIZE: usize = SZ_1K; 41 42 /// Type specifying the `Fsp` falcon engine. Cannot be instantiated. 43 pub(crate) struct Fsp(()); 44 45 register! { 46 base: NovaRegisters; 47 48 PFALCON: super::PFalconRegisters @ 0x8f2000; 49 PFALCON2: super::PFalcon2Registers @ 0x8f3000; 50 } 51 52 impl FalconEngine for Fsp { 53 #[inline] 54 fn pfalcon(io: Bar0<'_>) -> Mmio<'_, super::PFalconRegisters> { 55 io_project!(io, build: PFALCON) 56 } 57 58 #[inline] 59 fn pfalcon2(io: Bar0<'_>) -> Mmio<'_, super::PFalcon2Registers> { 60 io_project!(io, build: PFALCON2) 61 } 62 } 63 64 impl<'a> Falcon<'a, Fsp> { 65 /// Writes `data` to FSP external memory at offset `0`. 66 /// 67 /// `data` is interpreted as little-endian 32-bit words. Returns `EINVAL` 68 /// if the `data` length is not 4-byte aligned. 69 fn write_emem(&mut self, data: &[u8]) -> Result { 70 if data.len() % 4 != 0 { 71 return Err(EINVAL); 72 } 73 74 // Begin a write burst at offset `0`, auto-incrementing on each write. 75 self.pfalcon 76 .write_reg(regs::NV_PFALCON_FALCON_EMEMC::zeroed().with_aincw(true)); 77 78 for chunk in data.chunks_exact(4) { 79 let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); 80 81 // Write the next 32-bit `value`; hardware advances the offset. 82 self.pfalcon 83 .write_reg(regs::NV_PFALCON_FALCON_EMEMD::zeroed().with_data(value)); 84 } 85 86 Ok(()) 87 } 88 89 /// Reads FSP external memory from offset `0` into `data`. 90 /// 91 /// `data` is stored as little-endian 32-bit words. Returns `EINVAL` if 92 /// the `data` length is not 4-byte aligned. 93 fn read_emem(&mut self, data: &mut [u8]) -> Result { 94 if data.len() % 4 != 0 { 95 return Err(EINVAL); 96 } 97 98 // Begin a read burst at offset `0`, auto-incrementing on each read. 99 self.pfalcon 100 .write_reg(regs::NV_PFALCON_FALCON_EMEMC::zeroed().with_aincr(true)); 101 102 for chunk in data.chunks_exact_mut(4) { 103 // Read the next 32-bit word; hardware advances the offset. 104 let value = self.pfalcon.read(regs::NV_PFALCON_FALCON_EMEMD).data(); 105 chunk.copy_from_slice(&value.to_le_bytes()); 106 } 107 108 Ok(()) 109 } 110 111 /// Poll FSP for incoming data. 112 /// 113 /// Returns the size of available data in bytes, or 0 if no data is available. 114 /// 115 /// Returns [`EIO`] if the queue pointers are bogus (`tail < head`). 116 /// 117 /// The FSP message queue is not circular. Pointers are reset to 0 after each 118 /// message exchange, so `tail >= head` is always true when data is present. 119 fn poll_msgq(&self) -> Result<u32> { 120 let head = self.bar.read(regs::NV_PFSP_MSGQ_HEAD::at(0)).val(); 121 let tail = self.bar.read(regs::NV_PFSP_MSGQ_TAIL::at(0)).val(); 122 123 if head == tail { 124 Ok(0) 125 } else { 126 // TAIL points at the last DWORD written, so the size is `tail - head + 4`. 127 tail.checked_sub(head) 128 .and_then(|delta| delta.checked_add(4)) 129 .ok_or(EIO) 130 } 131 } 132 133 /// Writes `packet` to FSP EMEM and updates the queue pointers to notify FSP. 134 /// 135 /// Returns `EINVAL` if `packet` is empty or its length is not 4-byte aligned. 136 pub(crate) fn send_msg(&mut self, packet: &[u8]) -> Result { 137 if packet.is_empty() { 138 return Err(EINVAL); 139 } 140 141 self.write_emem(packet)?; 142 143 // Update queue pointers. TAIL points at the last DWORD written. 144 let tail_offset = u32::try_from(packet.len() - 4).map_err(|_| EINVAL)?; 145 self.bar.write( 146 Array::at(0), 147 regs::NV_PFSP_QUEUE_TAIL::zeroed().with_address(tail_offset), 148 ); 149 self.bar.write( 150 Array::at(0), 151 regs::NV_PFSP_QUEUE_HEAD::zeroed().with_address(0), 152 ); 153 154 Ok(()) 155 } 156 157 /// Reads the next message from FSP EMEM into a newly-allocated buffer and resets the queue 158 /// pointers. 159 /// 160 /// Returns `ETIMEDOUT` if no message was available until timeout, or a regular error code if a 161 /// memory allocation error occurred. 162 pub(crate) fn recv_msg(&mut self) -> Result<KVec<u8>> { 163 let msg_size = read_poll_timeout( 164 || self.poll_msgq(), 165 |&size| size > 0, 166 Delta::from_millis(10), 167 Delta::from_millis(FSP_MSG_TIMEOUT_MS), 168 ) 169 .map(num::u32_as_usize)?; 170 171 // Don't blindly allocate more than the maximum we expect from FSP. 172 if msg_size > FSP_EMEM_CHANNEL_0_SIZE { 173 return Err(EMSGSIZE); 174 } 175 176 let mut buffer = KVec::<u8>::new(); 177 buffer.resize(msg_size, 0, GFP_KERNEL)?; 178 179 self.read_emem(&mut buffer)?; 180 181 // Reset message queue pointers after reading. 182 self.bar 183 .write(Array::at(0), regs::NV_PFSP_MSGQ_TAIL::zeroed().with_val(0)); 184 self.bar 185 .write(Array::at(0), regs::NV_PFSP_MSGQ_HEAD::zeroed().with_val(0)); 186 187 Ok(buffer) 188 } 189 } 190