xref: /linux/drivers/gpu/nova-core/falcon/fsp.rs (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
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