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 pub(crate) fn send_msg(&mut self, bar: &Bar0, packet: &[u8]) -> Result { 126 if packet.is_empty() { 127 return Err(EINVAL); 128 } 129 130 self.write_emem(bar, packet)?; 131 132 // Update queue pointers. TAIL points at the last DWORD written. 133 let tail_offset = u32::try_from(packet.len() - 4).map_err(|_| EINVAL)?; 134 bar.write( 135 Array::at(0), 136 regs::NV_PFSP_QUEUE_TAIL::zeroed().with_address(tail_offset), 137 ); 138 bar.write( 139 Array::at(0), 140 regs::NV_PFSP_QUEUE_HEAD::zeroed().with_address(0), 141 ); 142 143 Ok(()) 144 } 145 146 /// Reads the next message from FSP EMEM into a newly-allocated buffer and resets the queue 147 /// pointers. 148 /// 149 /// Returns `ETIMEDOUT` if no message was available until timeout, or a regular error code if a 150 /// memory allocation error occurred. 151 pub(crate) fn recv_msg(&mut self, bar: &Bar0) -> Result<KVec<u8>> { 152 let msg_size = read_poll_timeout( 153 || Ok(self.poll_msgq(bar)), 154 |&size| size > 0, 155 Delta::from_millis(10), 156 Delta::from_millis(FSP_MSG_TIMEOUT_MS), 157 ) 158 .map(num::u32_as_usize)?; 159 160 let mut buffer = KVec::<u8>::new(); 161 buffer.resize(msg_size, 0, GFP_KERNEL)?; 162 163 self.read_emem(bar, &mut buffer)?; 164 165 // Reset message queue pointers after reading. 166 bar.write(Array::at(0), regs::NV_PFSP_MSGQ_TAIL::zeroed().with_val(0)); 167 bar.write(Array::at(0), regs::NV_PFSP_MSGQ_HEAD::zeroed().with_val(0)); 168 169 Ok(buffer) 170 } 171 } 172