mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
The `dma_handle` naming is inherited from the C API, but what this really describes is the device DMA address; everything named `dma_handle` is actually a `dma_addr_t`. This naming introduces some confusion on the Rust API side, as handles are supposed to be opaque tokens, yet we were doing address computation on values returned by `dma_handle`. Rename `dma_handle` to `dma_address` while nova-core is still its only user. Suggested-by: John Hubbard <jhubbard@nvidia.com> Suggested-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/all/DK75LUA4NLGI.3P29AIZQE20V2@kernel.org/ Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> Reviewed-by: Robin Murphy <robin.murphy@arm.com> Link: https://patch.msgid.link/20260805-falcon-dma-projections-v2-2-4cc9f3f13ee9@nvidia.com [ Rebase and fix up build failures due to newly introduced dma_handle() calls. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
345 lines
10 KiB
Rust
345 lines
10 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use core::ops::{
|
|
Deref,
|
|
Range, //
|
|
};
|
|
|
|
use kernel::{
|
|
device,
|
|
dma::CoherentHandle,
|
|
fmt,
|
|
io::Io,
|
|
prelude::*,
|
|
ptr::{
|
|
Alignable,
|
|
Alignment, //
|
|
},
|
|
sizes::*, //
|
|
};
|
|
|
|
use crate::{
|
|
driver::Bar0,
|
|
firmware::gsp::GspFirmware,
|
|
gpu::Chipset,
|
|
gsp,
|
|
num::FromSafeCast,
|
|
vgpu::VgpuState, //
|
|
};
|
|
|
|
mod hal;
|
|
mod regs;
|
|
|
|
/// Type holding the sysmem flush memory page, a page of memory to be written into the
|
|
/// `NV_PFB_NISO_FLUSH_SYSMEM_ADDR*` registers and used to maintain memory coherency.
|
|
///
|
|
/// A system memory page is required for `sysmembar`, which is a GPU-initiated hardware
|
|
/// memory-barrier operation that flushes all pending GPU-side memory writes that were done through
|
|
/// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a
|
|
/// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure
|
|
/// this write is visible to the host and prevent driver timeouts, the falcon must perform a
|
|
/// sysmembar operation to flush its writes.
|
|
///
|
|
/// Because of this, the sysmem flush memory page must be registered as early as possible during
|
|
/// driver initialization, and before any falcon is reset.
|
|
///
|
|
pub(crate) struct SysmemFlush<'sys> {
|
|
/// Chipset we are operating on.
|
|
chipset: Chipset,
|
|
device: &'sys device::Device,
|
|
bar: Bar0<'sys>,
|
|
/// Keep the page alive as long as we need it.
|
|
page: CoherentHandle,
|
|
}
|
|
|
|
impl<'sys> SysmemFlush<'sys> {
|
|
/// Allocate a memory page and register it as the sysmem flush page.
|
|
pub(crate) fn register(
|
|
dev: &'sys device::Device<device::Bound>,
|
|
bar: Bar0<'sys>,
|
|
chipset: Chipset,
|
|
) -> Result<Self> {
|
|
let page = CoherentHandle::alloc(dev, kernel::page::PAGE_SIZE, GFP_KERNEL)?;
|
|
|
|
hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_address())?;
|
|
|
|
Ok(Self {
|
|
chipset,
|
|
device: dev,
|
|
bar,
|
|
page,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Drop for SysmemFlush<'_> {
|
|
fn drop(&mut self) {
|
|
let hal = hal::fb_hal(self.chipset);
|
|
|
|
if hal.read_sysmem_flush_page(self.bar) == self.page.dma_address() {
|
|
let _ = hal.write_sysmem_flush_page(self.bar, 0).inspect_err(|e| {
|
|
dev_warn!(
|
|
&self.device,
|
|
"failed to unregister sysmem flush page: {:?}\n",
|
|
e
|
|
)
|
|
});
|
|
} else {
|
|
// Another page has been registered after us for some reason - warn as this is a bug.
|
|
dev_warn!(
|
|
&self.device,
|
|
"attempt to unregister a sysmem flush page that is not active\n"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct FbRange(Range<u64>);
|
|
|
|
impl FbRange {
|
|
pub(crate) fn len(&self) -> u64 {
|
|
self.0.end - self.0.start
|
|
}
|
|
}
|
|
|
|
impl From<Range<u64>> for FbRange {
|
|
fn from(range: Range<u64>) -> Self {
|
|
Self(range)
|
|
}
|
|
}
|
|
|
|
impl Deref for FbRange {
|
|
type Target = Range<u64>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for FbRange {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
// Use alternate format ({:#?}) to include size, compact format ({:?}) for just the range.
|
|
if f.alternate() {
|
|
let size = self.len();
|
|
|
|
if size < u64::SZ_1M {
|
|
let size_kib = size / u64::SZ_1K;
|
|
f.write_fmt(fmt!(
|
|
"{:#x}..{:#x} ({} KiB)",
|
|
self.0.start,
|
|
self.0.end,
|
|
size_kib
|
|
))
|
|
} else {
|
|
let size_mib = size / u64::SZ_1M;
|
|
f.write_fmt(fmt!(
|
|
"{:#x}..{:#x} ({} MiB)",
|
|
self.0.start,
|
|
self.0.end,
|
|
size_mib
|
|
))
|
|
}
|
|
} else {
|
|
f.write_fmt(fmt!("{:#x}..{:#x}", self.0.start, self.0.end))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Layout of the GPU framebuffer memory.
|
|
///
|
|
/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.
|
|
#[derive(Debug)]
|
|
pub(crate) struct FbRanges {
|
|
/// Range of the framebuffer. Starts at `0`.
|
|
pub(crate) fb: FbRange,
|
|
/// VGA workspace, small area of reserved memory at the end of the framebuffer.
|
|
pub(crate) vga_workspace: FbRange,
|
|
/// FRTS range.
|
|
pub(crate) frts: FbRange,
|
|
/// Memory area containing the GSP bootloader image.
|
|
pub(crate) boot: FbRange,
|
|
/// Memory area containing the GSP firmware image.
|
|
pub(crate) elf: FbRange,
|
|
/// WPR2 heap.
|
|
pub(crate) wpr2_heap: FbRange,
|
|
/// WPR2 region range, starting with an instance of `GspFwWprMeta`.
|
|
pub(crate) wpr2: FbRange,
|
|
/// Non-WPR heap, located just below WPR2.
|
|
pub(crate) non_wpr_heap: FbRange,
|
|
/// Number of VF partitions.
|
|
pub(crate) vf_partition_count: u8,
|
|
/// PMU reserved memory size, in bytes.
|
|
pub(crate) pmu_reserved_size: u32,
|
|
}
|
|
|
|
impl FbRanges {
|
|
/// Computes concrete framebuffer ranges required on non-FSP booting architectures.
|
|
pub(crate) fn new(
|
|
chipset: Chipset,
|
|
bar: Bar0<'_>,
|
|
gsp_fw: &GspFirmware,
|
|
vgpu_state: VgpuState,
|
|
) -> Result<Self> {
|
|
let hal = hal::fb_hal(chipset);
|
|
|
|
let fb = {
|
|
let fb_size = hal.vidmem_size(bar);
|
|
|
|
FbRange(0..fb_size)
|
|
};
|
|
|
|
let vga_workspace = {
|
|
let vga_base = {
|
|
const NV_PRAMIN_SIZE: u64 = u64::SZ_1M;
|
|
let base = fb.end - NV_PRAMIN_SIZE;
|
|
|
|
if hal.supports_display(bar) {
|
|
match bar
|
|
.read(regs::NV_PDISP_VGA_WORKSPACE_BASE)
|
|
.vga_workspace_addr()
|
|
{
|
|
Some(addr) => {
|
|
if addr < base {
|
|
const VBIOS_WORKSPACE_SIZE: u64 = u64::SZ_128K;
|
|
|
|
// Point workspace address to end of framebuffer.
|
|
fb.end - VBIOS_WORKSPACE_SIZE
|
|
} else {
|
|
addr
|
|
}
|
|
}
|
|
None => base,
|
|
}
|
|
} else {
|
|
base
|
|
}
|
|
};
|
|
|
|
FbRange(vga_base..fb.end)
|
|
};
|
|
|
|
let frts = {
|
|
const FRTS_DOWN_ALIGN: Alignment = Alignment::new::<SZ_128K>();
|
|
let frts_size: u64 = hal.frts_size();
|
|
let frts_base = vga_workspace.start.align_down(FRTS_DOWN_ALIGN) - frts_size;
|
|
|
|
FbRange(frts_base..frts_base + frts_size)
|
|
};
|
|
|
|
let boot = {
|
|
const BOOTLOADER_DOWN_ALIGN: Alignment = Alignment::new::<SZ_4K>();
|
|
let bootloader_size = u64::from_safe_cast(gsp_fw.bootloader.ucode.size());
|
|
let bootloader_base = (frts.start - bootloader_size).align_down(BOOTLOADER_DOWN_ALIGN);
|
|
|
|
FbRange(bootloader_base..bootloader_base + bootloader_size)
|
|
};
|
|
|
|
let elf = {
|
|
const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
|
|
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)
|
|
};
|
|
|
|
let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb.end)?;
|
|
|
|
let wpr2_heap = {
|
|
const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
|
|
let wpr2_heap_addr = elf
|
|
.start
|
|
.checked_sub(wpr2_heap_size)
|
|
.ok_or(EOVERFLOW)?
|
|
.align_down(WPR2_HEAP_DOWN_ALIGN);
|
|
|
|
FbRange(wpr2_heap_addr..(elf.start).align_down(WPR2_HEAP_DOWN_ALIGN))
|
|
};
|
|
|
|
let wpr2 = {
|
|
const WPR2_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
|
|
let wpr2_addr = (wpr2_heap.start - u64::from_safe_cast(size_of::<gsp::GspFwWprMeta>()))
|
|
.align_down(WPR2_DOWN_ALIGN);
|
|
|
|
FbRange(wpr2_addr..frts.end)
|
|
};
|
|
|
|
let non_wpr_heap = {
|
|
let non_wpr_heap_size = hal.non_wpr_heap_size();
|
|
FbRange(wpr2.start - non_wpr_heap_size..wpr2.start)
|
|
};
|
|
|
|
Ok(Self {
|
|
fb,
|
|
vga_workspace,
|
|
frts,
|
|
boot,
|
|
elf,
|
|
wpr2_heap,
|
|
wpr2,
|
|
non_wpr_heap,
|
|
vf_partition_count,
|
|
pmu_reserved_size: hal.pmu_reserved_size(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Reads the WPR2 memory region registers and returns the range if set.
|
|
/// Returns `None` if the WPR2 region is not set.
|
|
pub(crate) fn wpr2_range(bar: Bar0<'_>) -> Option<Range<u64>> {
|
|
let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);
|
|
|
|
if !wpr2_hi.is_wpr2_set() {
|
|
return None;
|
|
}
|
|
|
|
let wpr2_lo = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO);
|
|
|
|
Some(wpr2_lo.lower_bound()..wpr2_hi.higher_bound())
|
|
}
|
|
|
|
/// Computes the number of VF partitions and the WPR2 heap size from the vGPU state.
|
|
fn wpr2_heap_params(chipset: Chipset, vgpu_state: VgpuState, fb_size: u64) -> Result<(u8, u64)> {
|
|
Ok(match vgpu_state {
|
|
VgpuState::Disabled => (
|
|
0,
|
|
gsp::LibosParams::from_chipset(chipset).wpr_heap_size(chipset, fb_size)?,
|
|
),
|
|
VgpuState::Enabled { total_vfs } => (
|
|
u8::try_from(total_vfs.get()).map_err(|_| EINVAL)?,
|
|
gsp::LibosParams::vgpu_wpr_heap_size(),
|
|
),
|
|
})
|
|
}
|
|
|
|
/// Framebuffer region sizes needed for GSP-FMC boot.
|
|
#[derive(Debug)]
|
|
pub(crate) struct FbSizes {
|
|
/// FRTS size, in bytes.
|
|
pub(crate) frts_size: u64,
|
|
/// WPR2 heap size, in bytes.
|
|
pub(crate) wpr2_heap_size: u64,
|
|
/// Non-WPR heap size, in bytes.
|
|
pub(crate) non_wpr_heap_size: u64,
|
|
/// PMU reserved memory size, in bytes.
|
|
pub(crate) pmu_reserved_size: u32,
|
|
/// Number of VF partitions.
|
|
pub(crate) vf_partition_count: u8,
|
|
}
|
|
|
|
impl FbSizes {
|
|
/// Computes the framebuffer region sizes for GSP-FMC boot.
|
|
pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, vgpu_state: VgpuState) -> Result<Self> {
|
|
let hal = hal::fb_hal(chipset);
|
|
let fb_size = hal.vidmem_size(bar);
|
|
let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb_size)?;
|
|
|
|
Ok(Self {
|
|
frts_size: hal.frts_size(),
|
|
wpr2_heap_size,
|
|
non_wpr_heap_size: hal.non_wpr_heap_size(),
|
|
pmu_reserved_size: hal.pmu_reserved_size(),
|
|
vf_partition_count,
|
|
})
|
|
}
|
|
}
|