// SPDX-License-Identifier: GPL-2.012use core::ops::Range;34use kernel::prelude::*;5use kernel::ptr::{Alignable, Alignment};6use kernel::sizes::*;7use kernel::sync::aref::ARef;8use kernel::{dev_warn, device};910use crate::dma::DmaObject;11use crate::driver::Bar0;12use crate::gpu::Chipset;13use crate::regs;1415mod hal;1617/// Type holding the sysmem flush memory page, a page of memory to be written into the18/// `NV_PFB_NISO_FLUSH_SYSMEM_ADDR*` registers and used to maintain memory coherency.19///20/// A system memory page is required for `sysmembar`, which is a GPU-initiated hardware21/// memory-barrier operation that flushes all pending GPU-side memory writes that were done through22/// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a23/// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure24/// this write is visible to the host and prevent driver timeouts, the falcon must perform a25/// sysmembar operation to flush its writes.26///27/// Because of this, the sysmem flush memory page must be registered as early as possible during28/// driver initialization, and before any falcon is reset.29///30/// Users are responsible for manually calling [`Self::unregister`] before dropping this object,31/// otherwise the GPU might still use it even after it has been freed.32pub(crate) struct SysmemFlush {33/// Chipset we are operating on.34chipset: Chipset,35device: ARef<device::Device>,36/// Keep the page alive as long as we need it.37page: DmaObject,38}3940impl SysmemFlush {41/// Allocate a memory page and register it as the sysmem flush page.42pub(crate) fn register(43dev: &device::Device<device::Bound>,44bar: &Bar0,45chipset: Chipset,46) -> Result<Self> {47let page = DmaObject::new(dev, kernel::page::PAGE_SIZE)?;4849hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?;5051Ok(Self {52chipset,53device: dev.into(),54page,55})56}5758/// Unregister the managed sysmem flush page.59///60/// In order to gracefully tear down the GPU, users must make sure to call this method before61/// dropping the object.62pub(crate) fn unregister(&self, bar: &Bar0) {63let hal = hal::fb_hal(self.chipset);6465if hal.read_sysmem_flush_page(bar) == self.page.dma_handle() {66let _ = hal.write_sysmem_flush_page(bar, 0).inspect_err(|e| {67dev_warn!(68&self.device,69"failed to unregister sysmem flush page: {:?}",70e71)72});73} else {74// Another page has been registered after us for some reason - warn as this is a bug.75dev_warn!(76&self.device,77"attempt to unregister a sysmem flush page that is not active\n"78);79}80}81}8283/// Layout of the GPU framebuffer memory.84///85/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.86#[derive(Debug)]87#[expect(dead_code)]88pub(crate) struct FbLayout {89pub(crate) fb: Range<u64>,90pub(crate) vga_workspace: Range<u64>,91pub(crate) frts: Range<u64>,92}9394impl FbLayout {95/// Computes the FB layout.96pub(crate) fn new(chipset: Chipset, bar: &Bar0) -> Result<Self> {97let hal = hal::fb_hal(chipset);9899let fb = {100let fb_size = hal.vidmem_size(bar);1011020..fb_size103};104105let vga_workspace = {106let vga_base = {107const NV_PRAMIN_SIZE: u64 = SZ_1M as u64;108let base = fb.end - NV_PRAMIN_SIZE;109110if hal.supports_display(bar) {111match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() {112Some(addr) => {113if addr < base {114const VBIOS_WORKSPACE_SIZE: u64 = SZ_128K as u64;115116// Point workspace address to end of framebuffer.117fb.end - VBIOS_WORKSPACE_SIZE118} else {119addr120}121}122None => base,123}124} else {125base126}127};128129vga_base..fb.end130};131132let frts = {133const FRTS_DOWN_ALIGN: Alignment = Alignment::new::<SZ_128K>();134const FRTS_SIZE: u64 = SZ_1M as u64;135let frts_base = vga_workspace.start.align_down(FRTS_DOWN_ALIGN) - FRTS_SIZE;136137frts_base..frts_base + FRTS_SIZE138};139140Ok(Self {141fb,142vga_workspace,143frts,144})145}146}147148149