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