// SPDX-License-Identifier: GPL-2.012use core::ops::Range;34use kernel::prelude::*;5use kernel::sizes::*;6use kernel::types::ARef;7use kernel::{dev_warn, device};89use crate::dma::DmaObject;10use crate::driver::Bar0;11use crate::gpu::Chipset;12use crate::regs;1314mod hal;1516/// Type holding the sysmem flush memory page, a page of memory to be written into the17/// `NV_PFB_NISO_FLUSH_SYSMEM_ADDR*` registers and used to maintain memory coherency.18///19/// A system memory page is required for `sysmembar`, which is a GPU-initiated hardware20/// memory-barrier operation that flushes all pending GPU-side memory writes that were done through21/// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a22/// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure23/// this write is visible to the host and prevent driver timeouts, the falcon must perform a24/// sysmembar operation to flush its writes.25///26/// Because of this, the sysmem flush memory page must be registered as early as possible during27/// driver initialization, and before any falcon is reset.28///29/// Users are responsible for manually calling [`Self::unregister`] before dropping this object,30/// otherwise the GPU might still use it even after it has been freed.31pub(crate) struct SysmemFlush {32/// Chipset we are operating on.33chipset: Chipset,34device: ARef<device::Device>,35/// Keep the page alive as long as we need it.36page: DmaObject,37}3839impl SysmemFlush {40/// Allocate a memory page and register it as the sysmem flush page.41pub(crate) fn register(42dev: &device::Device<device::Bound>,43bar: &Bar0,44chipset: Chipset,45) -> Result<Self> {46let page = DmaObject::new(dev, kernel::page::PAGE_SIZE)?;4748hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?;4950Ok(Self {51chipset,52device: dev.into(),53page,54})55}5657/// Unregister the managed sysmem flush page.58///59/// In order to gracefully tear down the GPU, users must make sure to call this method before60/// dropping the object.61pub(crate) fn unregister(&self, bar: &Bar0) {62let hal = hal::fb_hal(self.chipset);6364if hal.read_sysmem_flush_page(bar) == self.page.dma_handle() {65let _ = hal.write_sysmem_flush_page(bar, 0).inspect_err(|e| {66dev_warn!(67&self.device,68"failed to unregister sysmem flush page: {:?}",69e70)71});72} else {73// Another page has been registered after us for some reason - warn as this is a bug.74dev_warn!(75&self.device,76"attempt to unregister a sysmem flush page that is not active\n"77);78}79}80}8182/// Layout of the GPU framebuffer memory.83///84/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.85#[derive(Debug)]86#[expect(dead_code)]87pub(crate) struct FbLayout {88pub(crate) fb: Range<u64>,89pub(crate) vga_workspace: Range<u64>,90pub(crate) frts: Range<u64>,91}9293impl FbLayout {94/// Computes the FB layout.95pub(crate) fn new(chipset: Chipset, bar: &Bar0) -> Result<Self> {96let hal = hal::fb_hal(chipset);9798let fb = {99let fb_size = hal.vidmem_size(bar);1001010..fb_size102};103104let vga_workspace = {105let vga_base = {106const NV_PRAMIN_SIZE: u64 = SZ_1M as u64;107let base = fb.end - NV_PRAMIN_SIZE;108109if hal.supports_display(bar) {110match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() {111Some(addr) => {112if addr < base {113const VBIOS_WORKSPACE_SIZE: u64 = SZ_128K as u64;114115// Point workspace address to end of framebuffer.116fb.end - VBIOS_WORKSPACE_SIZE117} else {118addr119}120}121None => base,122}123} else {124base125}126};127128vga_base..fb.end129};130131let frts = {132const FRTS_DOWN_ALIGN: u64 = SZ_128K as u64;133const FRTS_SIZE: u64 = SZ_1M as u64;134// TODO[NUMM]: replace with `align_down` once it lands.135let frts_base = (vga_workspace.start & !(FRTS_DOWN_ALIGN - 1)) - FRTS_SIZE;136137frts_base..frts_base + FRTS_SIZE138};139140Ok(Self {141fb,142vga_workspace,143frts,144})145}146}147148149