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