| // SPDX-License-Identifier: GPL-2.0 |
| |
| use core::ops::Range; |
| |
| use kernel::prelude::*; |
| use kernel::sizes::*; |
| use kernel::types::ARef; |
| use kernel::{dev_warn, device}; |
| |
| use crate::dma::DmaObject; |
| use crate::driver::Bar0; |
| use crate::gpu::Chipset; |
| use crate::regs; |
| |
| mod hal; |
| |
| /// 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. |
| /// |
| /// Users are responsible for manually calling [`Self::unregister`] before dropping this object, |
| /// otherwise the GPU might still use it even after it has been freed. |
| pub(crate) struct SysmemFlush { |
| /// Chipset we are operating on. |
| chipset: Chipset, |
| device: ARef<device::Device>, |
| /// Keep the page alive as long as we need it. |
| page: DmaObject, |
| } |
| |
| impl SysmemFlush { |
| /// Allocate a memory page and register it as the sysmem flush page. |
| pub(crate) fn register( |
| dev: &device::Device<device::Bound>, |
| bar: &Bar0, |
| chipset: Chipset, |
| ) -> Result<Self> { |
| let page = DmaObject::new(dev, kernel::page::PAGE_SIZE)?; |
| |
| hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?; |
| |
| Ok(Self { |
| chipset, |
| device: dev.into(), |
| page, |
| }) |
| } |
| |
| /// Unregister the managed sysmem flush page. |
| /// |
| /// In order to gracefully tear down the GPU, users must make sure to call this method before |
| /// dropping the object. |
| pub(crate) fn unregister(&self, bar: &Bar0) { |
| let hal = hal::fb_hal(self.chipset); |
| |
| if hal.read_sysmem_flush_page(bar) == self.page.dma_handle() { |
| let _ = hal.write_sysmem_flush_page(bar, 0).inspect_err(|e| { |
| dev_warn!( |
| &self.device, |
| "failed to unregister sysmem flush page: {:?}", |
| 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" |
| ); |
| } |
| } |
| } |
| |
| /// Layout of the GPU framebuffer memory. |
| /// |
| /// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process. |
| #[derive(Debug)] |
| #[expect(dead_code)] |
| pub(crate) struct FbLayout { |
| pub(crate) fb: Range<u64>, |
| pub(crate) vga_workspace: Range<u64>, |
| pub(crate) frts: Range<u64>, |
| } |
| |
| impl FbLayout { |
| /// Computes the FB layout. |
| pub(crate) fn new(chipset: Chipset, bar: &Bar0) -> Result<Self> { |
| let hal = hal::fb_hal(chipset); |
| |
| let fb = { |
| let fb_size = hal.vidmem_size(bar); |
| |
| 0..fb_size |
| }; |
| |
| let vga_workspace = { |
| let vga_base = { |
| const NV_PRAMIN_SIZE: u64 = SZ_1M as u64; |
| let base = fb.end - NV_PRAMIN_SIZE; |
| |
| if hal.supports_display(bar) { |
| match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() { |
| Some(addr) => { |
| if addr < base { |
| const VBIOS_WORKSPACE_SIZE: u64 = SZ_128K as u64; |
| |
| // Point workspace address to end of framebuffer. |
| fb.end - VBIOS_WORKSPACE_SIZE |
| } else { |
| addr |
| } |
| } |
| None => base, |
| } |
| } else { |
| base |
| } |
| }; |
| |
| vga_base..fb.end |
| }; |
| |
| let frts = { |
| const FRTS_DOWN_ALIGN: u64 = SZ_128K as u64; |
| const FRTS_SIZE: u64 = SZ_1M as u64; |
| // TODO[NUMM]: replace with `align_down` once it lands. |
| let frts_base = (vga_workspace.start & !(FRTS_DOWN_ALIGN - 1)) - FRTS_SIZE; |
| |
| frts_base..frts_base + FRTS_SIZE |
| }; |
| |
| Ok(Self { |
| fb, |
| vga_workspace, |
| frts, |
| }) |
| } |
| } |