xref: /linux/drivers/gpu/nova-core/gsp/hal/tu102.rs (revision 2d7eaeb05ae265bd64270376e6a2c0d80244771f)
1 // SPDX-License-Identifier: GPL-2.0
2 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3 
4 use kernel::prelude::*;
5 
6 use kernel::{
7     device,
8     dma::Coherent,
9     io::Io,
10     types::ScopeGuard, //
11 };
12 
13 use crate::{
14     driver::Bar0,
15     falcon::{
16         gsp::Gsp as GspEngine,
17         sec2::Sec2,
18         Falcon, //
19     },
20     fb::{
21         wpr2_range,
22         FbRanges, //
23     },
24     firmware::{
25         booter::{
26             BooterFirmware,
27             BooterKind, //
28         },
29         fwsec::{
30             bootloader::FwsecFirmwareWithBl,
31             FwsecCommand,
32             FwsecFirmware, //
33         },
34         gsp::GspFirmware, //
35     },
36     gpu::Chipset,
37     gsp::{
38         hal::{
39             GspHal,
40             UnloadBundle, //
41         },
42         regs,
43         sequencer::GspSequencer,
44         Gsp,
45         GspBootContext,
46         GspFwWprMeta, //
47     },
48     vbios::Vbios, //
49 };
50 
51 // A ready-to-run FWSEC unload firmware.
52 //
53 // Since there are two variants of the prepared firmware (with and without a bootloader), this type
54 // abstracts the difference.
55 enum FwsecUnloadFirmware<'a> {
56     WithoutBl(FwsecFirmware),
57     WithBl(FwsecFirmwareWithBl<'a>),
58 }
59 
60 impl FwsecUnloadFirmware<'_> {
61     /// Runs the FWSEC SB firmware.
62     fn run(
63         &self,
64         dev: &device::Device<device::Bound>,
65         gsp_falcon: &Falcon<'_, GspEngine>,
66     ) -> Result {
67         match self {
68             Self::WithoutBl(fw) => fw.run(dev, gsp_falcon),
69             Self::WithBl(fw) => fw.run(dev, gsp_falcon),
70         }
71     }
72 }
73 
74 // Contains the firmware required to fully reset GSP on chipsets where the GSP is started using
75 // FWSEC/Booter.
76 struct Sec2UnloadBundle<'a> {
77     fwsec_sb: FwsecUnloadFirmware<'a>,
78     booter_unloader: BooterFirmware,
79 }
80 
81 impl UnloadBundle for Sec2UnloadBundle<'_> {
82     fn run(&self, ctx: &mut GspBootContext<'_, '_>) -> Result {
83         let dev = ctx.dev();
84         let bar = ctx.bar;
85 
86         // Run FWSEC-SB to reset the GSP falcon to its pre-libos state.
87         // Log errors but keep going if it fails.
88         let fwsec_sb_res = self
89             .fwsec_sb
90             .run(dev, ctx.gsp_falcon)
91             .inspect_err(|e| dev_err!(dev, "FWSEC-SB failed to run: {:?}\n", e));
92 
93         // Remove WPR2 region if set.
94         let booter_unloader_res = (|| {
95             if wpr2_range(bar).is_none() {
96                 return Ok(());
97             }
98 
99             ctx.sec2_falcon.reset()?;
100             ctx.sec2_falcon.load(&self.booter_unloader)?;
101 
102             // Sentinel value to confirm that Booter Unloader has run.
103             const MAILBOX_SENTINEL: u32 = 0xff;
104             let (mbox0, _) = ctx
105                 .sec2_falcon
106                 .boot(Some(MAILBOX_SENTINEL), Some(MAILBOX_SENTINEL))?;
107             if mbox0 != 0 {
108                 dev_err!(dev, "Booter Unloader returned error 0x{:x}\n", mbox0);
109                 return Err(EINVAL);
110             }
111 
112             // Confirm that the WPR2 region has been removed.
113             if wpr2_range(bar).is_some() {
114                 dev_err!(
115                     dev,
116                     "WPR2 region still set after Booter Unloader returned\n"
117                 );
118                 return Err(EBUSY);
119             }
120 
121             Ok(())
122         })()
123         .inspect_err(|e| dev_err!(dev, "Booter Unloader failed to run: {:?}\n", e));
124 
125         fwsec_sb_res.and(booter_unloader_res)
126     }
127 }
128 
129 pub(super) struct Tu102 {
130     /// If `true`, then the FWSEC-FRTS bootloader will be used to load the actual firmware.
131     pub(super) needs_fwsec_bootloader: bool,
132 }
133 
134 impl Tu102 {
135     /// Helper method to load and run the FWSEC-FRTS firmware and confirm that it has properly
136     /// created the WPR2 region.
137     fn run_fwsec_frts(
138         &self,
139         dev: &device::Device<device::Bound>,
140         chipset: Chipset,
141         falcon: &Falcon<'_, GspEngine>,
142         bar: Bar0<'_>,
143         bios: &Vbios,
144         fb_ranges: &FbRanges,
145     ) -> Result {
146         // Check that the WPR2 region does not already exist - if it does, we cannot run
147         // FWSEC-FRTS until the GPU is reset.
148         if wpr2_range(bar).is_some() {
149             dev_err!(
150                 dev,
151                 "WPR2 region already exists - GPU needs to be reset to proceed\n"
152             );
153             return Err(EBUSY);
154         }
155 
156         // FWSEC-FRTS will create the WPR2 region.
157         let fwsec_frts = FwsecFirmware::new(
158             dev,
159             falcon,
160             bios,
161             FwsecCommand::Frts {
162                 frts_addr: fb_ranges.frts.start,
163                 frts_size: fb_ranges.frts.len(),
164             },
165         )?;
166 
167         if self.needs_fwsec_bootloader {
168             let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
169             // Load and run the bootloader, which will load FWSEC-FRTS and run it.
170             fwsec_frts_bl.run(dev, falcon)?;
171         } else {
172             // Load and run FWSEC-FRTS directly.
173             fwsec_frts.run(dev, falcon)?;
174         }
175 
176         // SCRATCH_E contains the error code for FWSEC-FRTS.
177         let frts_status = bar
178             .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR)
179             .frts_err_code();
180         if frts_status != 0 {
181             dev_err!(
182                 dev,
183                 "FWSEC-FRTS returned with error code {:#x}\n",
184                 frts_status
185             );
186 
187             return Err(EIO);
188         }
189 
190         // Check that the WPR2 region has been created as we requested.
191         let Some(wpr2_range) = wpr2_range(bar) else {
192             dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n");
193 
194             return Err(EIO);
195         };
196 
197         if wpr2_range.start != fb_ranges.frts.start {
198             dev_err!(
199                 dev,
200                 "WPR2 region created at unexpected address {:#x}; expected {:#x}\n",
201                 wpr2_range.start,
202                 fb_ranges.frts.start,
203             );
204 
205             return Err(EIO);
206         }
207 
208         dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_range.start, wpr2_range.end);
209         dev_dbg!(dev, "GPU instance built\n");
210 
211         Ok(())
212     }
213 
214     /// Load and prepare the resources required to properly reset the GSP after it has been stopped.
215     fn build_unload_bundle<'gpu>(
216         &self,
217         dev: &'gpu device::Device<device::Bound>,
218         chipset: Chipset,
219         bios: &Vbios,
220         gsp_falcon: &Falcon<'_, GspEngine>,
221         sec2_falcon: &Falcon<'_, Sec2>,
222     ) -> Result<crate::gsp::UnloadBundle<'gpu>> {
223         // Load the FWSEC SB firmware, as well as its bootloader if required.
224         let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bios, FwsecCommand::Sb)?;
225         let fwsec_sb = if self.needs_fwsec_bootloader {
226             FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?)
227         } else {
228             FwsecUnloadFirmware::WithoutBl(fwsec_sb)
229         };
230 
231         KBox::new(
232             Sec2UnloadBundle {
233                 fwsec_sb,
234                 booter_unloader: BooterFirmware::new(
235                     dev,
236                     BooterKind::Unloader,
237                     chipset,
238                     sec2_falcon,
239                 )?,
240             },
241             GFP_KERNEL,
242         )
243         .map(|b| crate::gsp::UnloadBundle(b as KBox<dyn UnloadBundle + 'gpu>))
244         .map_err(Into::into)
245     }
246 }
247 
248 impl GspHal for Tu102 {
249     fn boot<'gpu>(
250         &self,
251         gsp: &Gsp<'gpu>,
252         ctx: &mut GspBootContext<'_, 'gpu>,
253         gsp_fw: &GspFirmware<'gpu>,
254     ) -> Result<Option<crate::gsp::UnloadBundle<'gpu>>> {
255         let dev = ctx.dev();
256         let bar = ctx.bar;
257         let chipset = ctx.chipset;
258         let gsp_falcon = ctx.gsp_falcon;
259         let sec2_falcon = ctx.sec2_falcon;
260 
261         let fb_ranges = FbRanges::new(chipset, bar, gsp_fw, ctx.vgpu.state())?;
262         dev_dbg!(dev, "{:#x?}\n", fb_ranges);
263 
264         // Declared before the unload guard so that if Booter fails while running, SEC2 is reset
265         // by the guard before this allocation is freed.
266         let wpr_meta = Coherent::init(
267             dev,
268             GFP_KERNEL,
269             GspFwWprMeta::from_ranges(gsp_fw, &fb_ranges),
270         )?;
271 
272         let bios = Vbios::new(dev, bar)?;
273 
274         // Try and prepare the unload bundle.
275         //
276         // If the unload bundle creation fails, the GPU will need to be reset before the driver can
277         // be probed again.
278         let unload_bundle = self
279             .build_unload_bundle(dev, chipset, &bios, gsp_falcon, sec2_falcon)
280             .inspect_err(|e| dev_warn!(dev, "Failed to prepare unload firmware: {:?}\n", e))
281             .ok();
282 
283         // Run the unload bundle to try and recover the GSP if an error occurs.
284         let unload_guard = ScopeGuard::new_with_data(unload_bundle, |unload_bundle| {
285             if let Some(unload_bundle) = unload_bundle {
286                 let _ = unload_bundle.0.run(ctx);
287             }
288         });
289 
290         // FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
291         if !fb_ranges.frts.is_empty() {
292             self.run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, &fb_ranges)?;
293         }
294 
295         gsp_falcon.reset()?;
296         let libos_dma_address = gsp.libos.dma_address();
297         let (mbox0, mbox1) = gsp_falcon.boot(
298             Some(libos_dma_address as u32),
299             Some((libos_dma_address >> 32) as u32),
300         )?;
301         dev_dbg!(dev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
302 
303         dev_dbg!(
304             dev,
305             "Using SEC2 to load and run the booter_load firmware...\n"
306         );
307 
308         BooterFirmware::new(dev, BooterKind::Loader, chipset, sec2_falcon)?.run(
309             dev,
310             sec2_falcon,
311             &wpr_meta,
312         )?;
313 
314         Ok(unload_guard.dismiss())
315     }
316 
317     fn post_boot(
318         &self,
319         gsp: &Gsp<'_>,
320         ctx: &mut GspBootContext<'_, '_>,
321         gsp_fw: &GspFirmware<'_>,
322     ) -> Result {
323         GspSequencer::run(&gsp.cmdq, ctx, &gsp.libos, gsp_fw.bootloader.app_version)?;
324 
325         Ok(())
326     }
327 }
328 
329 /// The TU102 HAL requires the use of the FWSEC bootloader.
330 const TU102: Tu102 = Tu102 {
331     needs_fwsec_bootloader: true,
332 };
333 
334 pub(super) const TU102_HAL: &dyn GspHal = &TU102;
335