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