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