[PATCH v2 03/31] gpu: nova-core: extract radix3 page table into its own module
John Hubbard <[email protected]>
| Newsgroups | dev.linux.lists.nova-gpu,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Move the 3-level radix page table construction out of GspFirmware into a standalone Radix3 type in firmware/radix3.rs. The ucodes (bindata) firmware that GSP-RM loads at runtime also needs a radix3 page table, so the logic needs to be reusable. Refactor GspFirmware to use the new Radix3 type and expose firmware size through a size() method instead of a public field. Assisted-by: Cursor:claude-opus-5 Reviewed-by: Timur Tabi <[email protected]> Signed-off-by: John Hubbard <[email protected]> --- drivers/gpu/nova-core/fb.rs | 2 +- drivers/gpu/nova-core/firmware.rs | 1 + drivers/gpu/nova-core/firmware/gsp.rs | 116 ++++---------------- drivers/gpu/nova-core/firmware/radix3.rs | 133 +++++++++++++++++++++++ drivers/gpu/nova-core/gsp/fw.rs | 4 +- 5 files changed, 159 insertions(+), 97 deletions(-) create mode 100644 drivers/gpu/nova-core/firmware/radix3.rs diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs index 1576399389b1..f208d6deb5e3 100644 --- a/drivers/gpu/nova-core/fb.rs +++ b/drivers/gpu/nova-core/fb.rs @@ -236,7 +236,7 @@ pub(crate) fn new( let elf = { const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>(); - let elf_size = u64::from_safe_cast(gsp_fw.size); + let elf_size = u64::from_safe_cast(gsp_fw.size()); let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN); FbRange(elf_addr..elf_addr + elf_size) diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs index b49613a90bf0..34657004568c 100644 --- a/drivers/gpu/nova-core/firmware.rs +++ b/drivers/gpu/nova-core/firmware.rs @@ -26,6 +26,7 @@ pub(crate) mod fsp; pub(crate) mod fwsec; pub(crate) mod gsp; +pub(crate) mod radix3; pub(crate) mod riscv; pub(crate) mod tlv; diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs index e8f9491e84cc..48c73a676ecb 100644 --- a/drivers/gpu/nova-core/firmware/gsp.rs +++ b/drivers/gpu/nova-core/firmware/gsp.rs @@ -4,21 +4,16 @@ device, dma::{ Coherent, - CoherentBox, - DataDirection, DmaAddress, // }, firmware, prelude::*, - scatterlist::{ - Owned, - SGTable, // - }, str::CString, }; use crate::{ firmware::{ + radix3::Radix3, riscv::RiscvFirmware, // tlv::{ request_tlv, // @@ -26,38 +21,21 @@ }, }, gpu::Chipset, - gsp::GSP_PAGE_SIZE, num::FromSafeCast, }; /// GSP firmware with 3-level radix page tables for the GSP bootloader. /// -/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address -/// space: -/// -/// ```text -/// Level 0: 1 page, 1 entry -> points to first level 1 page -/// Level 1: Multiple pages/entries -> each entry points to a level 2 page -/// Level 2: Multiple pages/entries -> each entry points to a firmware page -/// ``` -/// -/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address). /// Also known as "Radix3" firmware. #[pin_data] pub(crate) struct GspFirmware { - /// The GSP firmware inside a [`VVec`], device-mapped via a SG table. - #[pin] - fw: SGTable<Owned<VVec<u8>>>, - /// Level 2 page table whose entries contain DMA addresses of firmware pages. - #[pin] - level2: SGTable<Owned<VVec<u8>>>, - /// Level 1 page table whose entries contain DMA addresses of level 2 pages. + /// The GSP firmware image mapped via a 3-level radix page table. #[pin] - level1: SGTable<Owned<VVec<u8>>>, - /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page. - level0: Coherent<[u64]>, - /// Size in bytes of the firmware contained in [`Self::fw`]. - pub(crate) size: usize, + radix3: Radix3, + /// Firmware file path requested from userspace. + pub(crate) fw_path: CString, + /// Firmware version from the TLV metadata. + pub(crate) fw_version: CString, /// Device-mapped GSP signatures matching the GPU's [`Chipset`]. pub(crate) signatures: Coherent<[u8]>, /// GSP bootloader, verifies the GSP firmware before loading and running it. @@ -74,7 +52,12 @@ pub(crate) fn new<'a>( pin_init::pin_init_scope(move || { let firmware = request_tlv(dev, chipset, "gsp")?; let tlv = Tlv::new(firmware.data())?; - dev_dbg!(dev, "loaded gsp firmware v{}\n", tlv.get_string(b"VERS")?); + let fw_version = CString::try_from_fmt(fmt!("{}", tlv.get_string(b"VERS")?))?; + dev_dbg!( + dev, + "loaded gsp firmware v{}\n", + fw_version.to_str().unwrap_or("unknown") + ); let size = usize::from_safe_cast(tlv.get_u32(b"SIZE")?); let mut fw_vvec = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?; @@ -87,49 +70,9 @@ pub(crate) fn new<'a>( let signatures = Coherent::from_slice(dev, tlv.get_bytes(b"SIGN")?, GFP_KERNEL)?; Ok(try_pin_init!(Self { - fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL), - level2 <- { - // Allocate the level 2 page table, map the firmware onto it, and map it into - // the device address space. - VVec::<u8>::with_capacity( - fw.iter().count() * core::mem::size_of::<u64>(), - GFP_KERNEL, - ) - .map_err(|_| ENOMEM) - .and_then(|level2| map_into_lvl(&fw, level2)) - .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))? - }, - level1 <- { - // Allocate the level 1 page table, map the level 2 page table onto it, and map - // it into the device address space. - VVec::<u8>::with_capacity( - level2.iter().count() * core::mem::size_of::<u64>(), - GFP_KERNEL, - ) - .map_err(|_| ENOMEM) - .and_then(|level1| map_into_lvl(&level2, level1)) - .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))? - }, - level0: { - // Allocate the level 0 page table as a device-visible DMA object, and map the - // level 1 page table onto it. - - // Fill level 1 page entry. - let level1_entry = level1.iter().next().ok_or(EINVAL)?; - let level1_entry_addr = level1_entry.dma_address(); - - // Create level 0 page table data and fill its first entry with the level 1 - // table. - let mut level0 = CoherentBox::<[u64]>::zeroed_slice( - dev, - GSP_PAGE_SIZE / size_of::<u64>(), - GFP_KERNEL - )?; - level0[0] = level1_entry_addr.to_le(); - - level0.into() - }, - size, + radix3 <- Radix3::new(dev, fw_vvec), + fw_path: filename, + fw_version, signatures, bootloader: { let bl = request_tlv(dev, chipset, "gsp_bootloader")?; @@ -140,28 +83,13 @@ pub(crate) fn new<'a>( }) } - /// Returns the DMA address of the radix3 level 0 page table. - pub(crate) fn radix3_dma_address(&self) -> DmaAddress { - self.level0.dma_address() + /// Returns the size of the GSP firmware image, in bytes. + pub(crate) fn size(&self) -> usize { + self.radix3.size() } -} -/// Build a page table from a scatter-gather list. -/// -/// Takes each DMA-mapped region from `sg_table` and writes page table entries -/// for all 4KB pages within that region. For example, a 16KB SG entry becomes -/// 4 consecutive page table entries. -fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> { - for sg_entry in sg_table.iter() { - // Number of pages we need to map. - let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE); - - for i in 0..num_pages { - let entry = sg_entry.dma_address() - + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE)); - dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?; - } + /// Returns the DMA address of the radix3 level 0 page table. + pub(crate) fn radix3_dma_address(&self) -> DmaAddress { + self.radix3.dma_address() } - - Ok(dst) } diff --git a/drivers/gpu/nova-core/firmware/radix3.rs b/drivers/gpu/nova-core/firmware/radix3.rs new file mode 100644 index 000000000000..b60611c7bea0 --- /dev/null +++ b/drivers/gpu/nova-core/firmware/radix3.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +//! 3-level radix page table for GSP firmware data. +//! +//! The GSP bootloader expects data to be mapped via a 3-level page table: +//! +//! ```text +//! Level 0: 1 page, 1 entry -> points to first level 1 page +//! Level 1: Multiple pages/entries -> each entry points to a level 2 page +//! Level 2: Multiple pages/entries -> each entry points to a data page +//! ``` +//! +//! Each page is 4KB, each entry is 8 bytes (64-bit DMA address). + +use core::mem::size_of; + +use kernel::{ + device, + dma::{ + Coherent, + CoherentBox, + DataDirection, + DmaAddress, // + }, + prelude::*, + scatterlist::{ + Owned, + SGTable, // + }, +}; + +use crate::{ + gsp::GSP_PAGE_SIZE, + num::FromSafeCast, // +}; + +/// 3-level radix page table mapping arbitrary data for the GSP. +#[pin_data] +pub(crate) struct Radix3 { + /// The data mapped via a SG table. + #[pin] + data: SGTable<Owned<VVec<u8>>>, + /// Level 2 page table whose entries contain DMA addresses of data pages. + #[pin] + level2: SGTable<Owned<VVec<u8>>>, + /// Level 1 page table whose entries contain DMA addresses of level 2 pages. + #[pin] + level1: SGTable<Owned<VVec<u8>>>, + /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page. + level0: Coherent<[u64]>, + /// Size in bytes of the data contained in [`Self::data`]. + size: usize, +} + +impl Radix3 { + /// Builds a 3-level radix page table that maps `data` into `dev`'s DMA address space. + /// + /// Takes ownership of `data`. May sleep. + pub(crate) fn new<'a>( + dev: &'a device::Device<device::Bound>, + data: VVec<u8>, + ) -> impl PinInit<Self, Error> + 'a { + let size = data.len(); + + pin_init::pin_init_scope(move || { + Ok(try_pin_init!(Self { + data <- SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL), + level2 <- { + VVec::<u8>::with_capacity( + data.iter().count() * core::mem::size_of::<u64>(), + GFP_KERNEL, + ) + .map_err(|_| ENOMEM) + .and_then(|level2| map_into_lvl(&data, level2)) + .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))? + }, + level1 <- { + VVec::<u8>::with_capacity( + level2.iter().count() * core::mem::size_of::<u64>(), + GFP_KERNEL, + ) + .map_err(|_| ENOMEM) + .and_then(|level1| map_into_lvl(&level2, level1)) + .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))? + }, + level0: { + let level1_entry = level1.iter().next().ok_or(EINVAL)?; + let level1_entry_addr = level1_entry.dma_address(); + + let mut level0 = CoherentBox::<[u64]>::zeroed_slice( + dev, + GSP_PAGE_SIZE / size_of::<u64>(), + GFP_KERNEL, + )?; + level0[0] = level1_entry_addr.to_le(); + + level0.into() + }, + size, + })) + }) + } + + /// Returns the DMA address of the radix3 level 0 page table. + pub(crate) fn dma_address(&self) -> DmaAddress { + self.level0.dma_address() + } + + /// Returns the size of the mapped data, in bytes. + pub(crate) fn size(&self) -> usize { + self.size + } +} + +/// Build a page table from a scatter-gather list. +/// +/// Takes each DMA-mapped region from `sg_table` and writes page table entries +/// for all 4KB pages within that region. For example, a 16KB SG entry becomes +/// 4 consecutive page table entries. +fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> { + for sg_entry in sg_table.iter() { + let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE); + + for i in 0..num_pages { + let entry = sg_entry.dma_address() + + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE)); + dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?; + } + } + + Ok(dst) +} diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs index 9b29308678a7..d3678f16750a 100644 --- a/drivers/gpu/nova-core/gsp/fw.rs +++ b/drivers/gpu/nova-core/gsp/fw.rs @@ -188,7 +188,7 @@ pub(crate) fn from_ranges<'a>( magic: bindings::GSP_FW_WPR_META_MAGIC as u64, revision: u64::from(bindings::GSP_FW_WPR_META_REVISION), sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(), - sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size), + sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()), sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(), sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()), bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset), @@ -243,7 +243,7 @@ pub(crate) fn from_sizes<'a>( magic: bindings::GSP_FW_WPR_META_MAGIC as u64, revision: u64::from(bindings::GSP_FW_WPR_META_REVISION), sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(), - sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size), + sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()), sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(), sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()), bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset), -- 2.55.0