// 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::poll::read_poll_timeout, time::Delta, types::ScopeGuard, // }; use crate::{ falcon::{ gsp::Gsp as GspEngine, Falcon, // }, fb::FbSizes, firmware::gsp::GspFirmware, fsp::FmcBootArgs, gsp::{ hal::{ GspHal, UnloadBundle, // }, Gsp, GspBootContext, GspFmcBootParams, GspFwWprMeta, // }, }; /// GSP falcon mailbox state, used to track lockdown release status. struct GspMbox { mbox0: u32, mbox1: u32, } impl GspMbox { /// Reads both mailboxes from the GSP falcon. fn read(gsp_falcon: &Falcon<'_, GspEngine>) -> Self { Self { mbox0: gsp_falcon.read_mailbox0(), mbox1: gsp_falcon.read_mailbox1(), } } /// Combines mailbox0 and mailbox1 into a 64-bit address. fn combined_addr(&self) -> u64 { (u64::from(self.mbox1) << 32) | u64::from(self.mbox0) } /// Returns `true` if GSP lockdown has been released or a GSP-FMC error happened. /// /// Returns `true` both on successful lockdown release and on GSP-FMC-reported errors, since /// either condition should stop the poll loop. fn lockdown_released_or_error( &self, gsp_falcon: &Falcon<'_, GspEngine>, fmc_boot_params: &Coherent, ) -> bool { // GSP-FMC normally clears the boot parameters address from the mailboxes early during // boot. If the address is still there, keep polling rather than treating it as an error. // Any other non-zero mailbox0 value is a GSP-FMC error code. if self.mbox0 != 0 { return self.combined_addr() != fmc_boot_params.dma_address(); } !gsp_falcon.riscv_branch_privilege_lockdown() } } /// Waits for GSP lockdown to be released after FSP Chain of Trust. fn wait_for_gsp_lockdown_release( dev: &device::Device, gsp_falcon: &Falcon<'_, GspEngine>, fmc_boot_params: &Coherent, ) -> Result { dev_dbg!(dev, "Waiting for GSP lockdown release\n"); let mbox = read_poll_timeout( || { // While the PRIV target mask is still locked to FSP, GSP register and mailbox reads // are not meaningful. Wait until HWCFG2 says the CPU can read them. Ok(match gsp_falcon.priv_target_mask_released() { false => None, true => Some(GspMbox::read(gsp_falcon)), }) }, |mbox| match mbox { None => false, Some(mbox) => mbox.lockdown_released_or_error(gsp_falcon, fmc_boot_params), }, Delta::from_millis(10), Delta::from_secs(30), ) .inspect_err(|_| { dev_err!(dev, "GSP lockdown release timeout\n"); })? .ok_or(EIO)?; // If polling stopped with a non-zero mailbox0, it was not the boot parameters address // anymore and therefore represents a GSP-FMC error code. if mbox.mbox0 != 0 { dev_err!(dev, "GSP-FMC boot failed (mbox: {:#x})\n", mbox.mbox0); return Err(EIO); } dev_dbg!(dev, "GSP lockdown released\n"); Ok(()) } struct FspUnloadBundle; impl UnloadBundle for FspUnloadBundle { fn run(&self, ctx: &mut GspBootContext<'_, '_>) -> Result { // GSP falcon does most of the work of resetting, so just wait for it to finish. read_poll_timeout( || { // GSP register reads are not meaningful until the PRIV target mask is released. if !ctx.gsp_falcon.priv_target_mask_released() { return Ok(false); } ctx.gsp_falcon.is_riscv_halted() }, |&halted| halted, Delta::from_millis(10), Delta::from_secs(5), ) .map(|_| ()) .inspect_err(|_| dev_err!(ctx.dev(), "GSP falcon failed to halt\n")) } } struct Gh100; impl GspHal for Gh100 { /// Boot GSP via FSP Chain of Trust (Hopper/Blackwell+ path). /// /// This path uses FSP to establish a chain of trust and boot GSP-FMC. FSP handles /// the GSP boot internally - no manual GSP reset/boot is needed. fn boot( &self, gsp: &Gsp, ctx: &mut GspBootContext<'_, '_>, gsp_fw: &GspFirmware, ) -> Result> { let dev = ctx.dev(); let chipset = ctx.chipset; let gsp_falcon = ctx.gsp_falcon; let fb_sizes = FbSizes::new(chipset, ctx.bar, ctx.vgpu.state())?; dev_dbg!(dev, "{:#x?}\n", fb_sizes); let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::from_sizes(gsp_fw, &fb_sizes))?; let args = FmcBootArgs::new(dev, chipset, wpr_meta, &gsp.libos, false)?; let unload_bundle = crate::gsp::UnloadBundle( KBox::new(FspUnloadBundle, GFP_KERNEL)? as KBox ); // Wait for the GSP RISC-V core to halt in case of error. We create this guard after `args` // to make sure that the boot args and the WPR metadata they own are kept alive until halt, // in case they are still being accessed. let mut unload_guard = ScopeGuard::new_with_data((unload_bundle, ctx), |(unload_bundle, ctx)| { let _ = unload_bundle.0.run(ctx); }); let fsp = unload_guard.1.fsp.as_mut().ok_or(ENODEV)?; fsp.boot_fmc(dev, &fb_sizes, &args)?; // Wait for GSP-FMC to release the GSP lockdown, indicating that `args` is not accessed // anymore. wait_for_gsp_lockdown_release(dev, gsp_falcon, args.boot_params())?; Ok(Some(unload_guard.dismiss().0)) } } const GH100: Gh100 = Gh100; pub(super) const GH100_HAL: &dyn GspHal = &GH100;