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