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