// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. use kernel::prelude::*; use kernel::{ device, dma::Coherent, io::Io, types::ScopeGuard, // }; use crate::{ driver::Bar0, falcon::{ gsp::Gsp as GspEngine, sec2::Sec2, Falcon, // }, fb::{ wpr2_range, FbRanges, // }, firmware::{ booter::{ BooterFirmware, BooterKind, // }, fwsec::{ bootloader::FwsecFirmwareWithBl, FwsecCommand, FwsecFirmware, // }, gsp::GspFirmware, // }, gpu::Chipset, gsp::{ hal::{ GspHal, UnloadBundle, // }, regs, sequencer::GspSequencer, Gsp, GspBootContext, GspFwWprMeta, // }, vbios::Vbios, // }; // A ready-to-run FWSEC unload firmware. // // Since there are two variants of the prepared firmware (with and without a bootloader), this type // abstracts the difference. enum FwsecUnloadFirmware { WithoutBl(FwsecFirmware), WithBl(FwsecFirmwareWithBl), } impl FwsecUnloadFirmware { /// Runs the FWSEC SB firmware. fn run( &self, dev: &device::Device, bar: Bar0<'_>, gsp_falcon: &Falcon<'_, GspEngine>, ) -> Result { match self { Self::WithoutBl(fw) => fw.run(dev, gsp_falcon), Self::WithBl(fw) => fw.run(dev, gsp_falcon, bar), } } } // Contains the firmware required to fully reset GSP on chipsets where the GSP is started using // FWSEC/Booter. struct Sec2UnloadBundle { fwsec_sb: FwsecUnloadFirmware, booter_unloader: BooterFirmware, } impl UnloadBundle for Sec2UnloadBundle { fn run(&self, ctx: &mut GspBootContext<'_, '_>) -> Result { let dev = ctx.dev(); let bar = ctx.bar; // Run FWSEC-SB to reset the GSP falcon to its pre-libos state. // Log errors but keep going if it fails. let fwsec_sb_res = self .fwsec_sb .run(dev, bar, ctx.gsp_falcon) .inspect_err(|e| dev_err!(dev, "FWSEC-SB failed to run: {:?}\n", e)); // Remove WPR2 region if set. let booter_unloader_res = (|| { if wpr2_range(bar).is_none() { return Ok(()); } ctx.sec2_falcon.reset()?; ctx.sec2_falcon.load(&self.booter_unloader)?; // Sentinel value to confirm that Booter Unloader has run. const MAILBOX_SENTINEL: u32 = 0xff; let (mbox0, _) = ctx .sec2_falcon .boot(Some(MAILBOX_SENTINEL), Some(MAILBOX_SENTINEL))?; if mbox0 != 0 { dev_err!(dev, "Booter Unloader returned error 0x{:x}\n", mbox0); return Err(EINVAL); } // Confirm that the WPR2 region has been removed. if wpr2_range(bar).is_some() { dev_err!( dev, "WPR2 region still set after Booter Unloader returned\n" ); return Err(EBUSY); } Ok(()) })() .inspect_err(|e| dev_err!(dev, "Booter Unloader failed to run: {:?}\n", e)); fwsec_sb_res.and(booter_unloader_res) } } pub(super) struct Tu102 { /// If `true`, then the FWSEC-FRTS bootloader will be used to load the actual firmware. pub(super) needs_fwsec_bootloader: bool, } impl Tu102 { /// Helper method to load and run the FWSEC-FRTS firmware and confirm that it has properly /// created the WPR2 region. fn run_fwsec_frts( &self, dev: &device::Device, chipset: Chipset, falcon: &Falcon<'_, GspEngine>, bar: Bar0<'_>, bios: &Vbios, fb_ranges: &FbRanges, ) -> Result { // Check that the WPR2 region does not already exist - if it does, we cannot run // FWSEC-FRTS until the GPU is reset. if wpr2_range(bar).is_some() { dev_err!( dev, "WPR2 region already exists - GPU needs to be reset to proceed\n" ); return Err(EBUSY); } // FWSEC-FRTS will create the WPR2 region. let fwsec_frts = FwsecFirmware::new( dev, falcon, bios, FwsecCommand::Frts { frts_addr: fb_ranges.frts.start, frts_size: fb_ranges.frts.len(), }, )?; if self.needs_fwsec_bootloader { let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?; // Load and run the bootloader, which will load FWSEC-FRTS and run it. fwsec_frts_bl.run(dev, falcon, bar)?; } else { // Load and run FWSEC-FRTS directly. fwsec_frts.run(dev, falcon)?; } // SCRATCH_E contains the error code for FWSEC-FRTS. let frts_status = bar .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR) .frts_err_code(); if frts_status != 0 { dev_err!( dev, "FWSEC-FRTS returned with error code {:#x}\n", frts_status ); return Err(EIO); } // Check that the WPR2 region has been created as we requested. let Some(wpr2_range) = wpr2_range(bar) else { dev_err!(dev, "WPR2 region not created after running FWSEC-FRTS\n"); return Err(EIO); }; if wpr2_range.start != fb_ranges.frts.start { dev_err!( dev, "WPR2 region created at unexpected address {:#x}; expected {:#x}\n", wpr2_range.start, fb_ranges.frts.start, ); return Err(EIO); } dev_dbg!(dev, "WPR2: {:#x}-{:#x}\n", wpr2_range.start, wpr2_range.end); dev_dbg!(dev, "GPU instance built\n"); Ok(()) } /// Load and prepare the resources required to properly reset the GSP after it has been stopped. fn build_unload_bundle( &self, dev: &device::Device, chipset: Chipset, bios: &Vbios, gsp_falcon: &Falcon<'_, GspEngine>, sec2_falcon: &Falcon<'_, Sec2>, ) -> Result { // Load the FWSEC SB firmware, as well as its bootloader if required. let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bios, FwsecCommand::Sb)?; let fwsec_sb = if self.needs_fwsec_bootloader { FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?) } else { FwsecUnloadFirmware::WithoutBl(fwsec_sb) }; KBox::new( Sec2UnloadBundle { fwsec_sb, booter_unloader: BooterFirmware::new( dev, BooterKind::Unloader, chipset, sec2_falcon, )?, }, GFP_KERNEL, ) .map(|b| crate::gsp::UnloadBundle(b)) .map_err(Into::into) } } impl GspHal for Tu102 { fn boot( &self, gsp: &Gsp, ctx: &mut GspBootContext<'_, '_>, gsp_fw: &GspFirmware, ) -> Result> { let dev = ctx.dev(); let bar = ctx.bar; let chipset = ctx.chipset; let gsp_falcon = ctx.gsp_falcon; let sec2_falcon = ctx.sec2_falcon; let fb_ranges = FbRanges::new(chipset, bar, gsp_fw, ctx.vgpu.state())?; dev_dbg!(dev, "{:#x?}\n", fb_ranges); // Declared before the unload guard so that if Booter fails while running, SEC2 is reset // by the guard before this allocation is freed. let wpr_meta = Coherent::init( dev, GFP_KERNEL, GspFwWprMeta::from_ranges(gsp_fw, &fb_ranges), )?; let bios = Vbios::new(dev, bar)?; // Try and prepare the unload bundle. // // If the unload bundle creation fails, the GPU will need to be reset before the driver can // be probed again. let unload_bundle = self .build_unload_bundle(dev, chipset, &bios, gsp_falcon, sec2_falcon) .inspect_err(|e| dev_warn!(dev, "Failed to prepare unload firmware: {:?}\n", e)) .ok(); // Run the unload bundle to try and recover the GSP if an error occurs. let unload_guard = ScopeGuard::new_with_data(unload_bundle, |unload_bundle| { if let Some(unload_bundle) = unload_bundle { let _ = unload_bundle.0.run(ctx); } }); // FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100). if !fb_ranges.frts.is_empty() { self.run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, &fb_ranges)?; } gsp_falcon.reset()?; let libos_dma_address = gsp.libos.dma_address(); let (mbox0, mbox1) = gsp_falcon.boot( Some(libos_dma_address as u32), Some((libos_dma_address >> 32) as u32), )?; dev_dbg!(dev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1); dev_dbg!( dev, "Using SEC2 to load and run the booter_load firmware...\n" ); BooterFirmware::new(dev, BooterKind::Loader, chipset, sec2_falcon)?.run( dev, sec2_falcon, &wpr_meta, )?; Ok(unload_guard.dismiss()) } fn post_boot( &self, gsp: &Gsp, ctx: &mut GspBootContext<'_, '_>, gsp_fw: &GspFirmware, ) -> Result { GspSequencer::run(&gsp.cmdq, ctx, &gsp.libos, gsp_fw.bootloader.app_version)?; Ok(()) } } /// The TU102 HAL requires the use of the FWSEC bootloader. const TU102: Tu102 = Tu102 { needs_fwsec_bootloader: true, }; pub(super) const TU102_HAL: &dyn GspHal = &TU102;