blob: 94235bd1209de93f9dd9cba470e88f842c28a66f [file] [edit]
// 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<Owned<VVec<u8>>>,
/// Level 2: one entry per page of `data`.
#[pin]
level2: SGTable<Owned<VVec<u8>>>,
/// Level 1: one entry per page of `level2`.
#[pin]
level1: SGTable<Owned<VVec<u8>>>,
/// 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<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 <- {
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::<u64>(),
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<Owned<VVec<u8>>>) -> 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::<u64>()).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<Owned<VVec<u8>>>) -> Result<VVec<u8>> {
let mut dst = VVec::<u8>::zeroed(lvl_size(sg_table), GFP_KERNEL).map_err(|_| ENOMEM)?;
let mut entries = dst.chunks_exact_mut(size_of::<u64>());
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)
}