// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. //! The radix3 page table, through which firmware running on the GPU reads a buffer in system //! memory. LibOS, the operating system of the GSP, defines the format: three levels of //! [`GSP_PAGE_SIZE`] pages, each entry the little-endian DMA address of one page. //! //! ```text //! Level 0: one page, one entry -> the first level 1 page //! Level 1: pages of entries -> each entry a level 2 page //! Level 2: pages of entries -> each entry a page of the buffer //! ``` use kernel::{ device, dma::{ Coherent, CoherentBox, DataDirection, DmaAddress, // }, prelude::*, scatterlist::{ Owned, SGTable, // }, }; use crate::{ gsp::GSP_PAGE_SIZE, num::FromSafeCast, // }; /// A radix3 page table and the data it maps. #[pin_data] pub(crate) struct Radix3<'a> { /// The mapped data. #[pin] data: SGTable>>, /// Level 2: one entry per page of `data`. #[pin] level2: SGTable>>, /// Level 1: one entry per page of `level2`. #[pin] level1: SGTable>>, /// Level 0: one page, whose single entry is the DMA address of the first `level1` page. level0: Coherent<'a, [u64]>, /// Length of `data`, in bytes. size: usize, } impl<'a> Radix3<'a> { /// Builds a radix3 page table over `data`, mapped for `dev` to read. May sleep. pub(crate) fn new( dev: &'a device::Device, data: VVec, ) -> impl PinInit + '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 <- { build_lvl(&data) .map(|l2| SGTable::new(dev, l2, DataDirection::ToDevice, GFP_KERNEL))? }, level1 <- { build_lvl(&level2) .map(|l1| SGTable::new(dev, l1, 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::(), GFP_KERNEL )?; level0[0] = level1_entry_addr.to_le(); level0.into() }, size, })) }) } /// Returns the DMA address of the level 0 page, which is the address of the table. pub(crate) fn dma_address(&self) -> DmaAddress { self.level0.dma_address() } /// Returns the length of the mapped data, in bytes. pub(crate) fn size(&self) -> usize { self.size } } /// Returns the size of the level that maps `sg_table`: one `u64` entry per [`GSP_PAGE_SIZE`] /// page of each DMA-mapped region, rounded up to whole pages. fn lvl_size(sg_table: &SGTable>>) -> usize { let entries: usize = sg_table .iter() .map(|sg_entry| usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE)) .sum(); (entries * size_of::()).next_multiple_of(GSP_PAGE_SIZE) } /// Builds one level of the table over `sg_table`: one entry per [`GSP_PAGE_SIZE`] page of each /// DMA-mapped region, in region order. The level is a whole number of pages and every byte past /// the last entry is zero, because the booter reads a level a whole page at a time. /// /// # Errors /// /// - `ENOMEM` if the level cannot be allocated. /// - `EINVAL` if `sg_table` yields more entries than [`lvl_size`] sized the level for. fn build_lvl(sg_table: &SGTable>>) -> Result> { let mut dst = VVec::::zeroed(lvl_size(sg_table), GFP_KERNEL).map_err(|_| ENOMEM)?; let mut entries = dst.chunks_exact_mut(size_of::()); 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)); entries .next() .ok_or(EINVAL)? .copy_from_slice(&entry.to_le_bytes()); } } Ok(dst) }