1 // SPDX-License-Identifier: GPL-2.0 2 3 use core::ops::Range; 4 5 use kernel::prelude::*; 6 use kernel::sizes::*; 7 use kernel::types::ARef; 8 use kernel::{dev_warn, device}; 9 10 use crate::dma::DmaObject; 11 use crate::driver::Bar0; 12 use crate::gpu::Chipset; 13 use crate::regs; 14 15 mod hal; 16 17 /// Type holding the sysmem flush memory page, a page of memory to be written into the 18 /// `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 hardware 21 /// memory-barrier operation that flushes all pending GPU-side memory writes that were done through 22 /// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a 23 /// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure 24 /// this write is visible to the host and prevent driver timeouts, the falcon must perform a 25 /// sysmembar operation to flush its writes. 26 /// 27 /// Because of this, the sysmem flush memory page must be registered as early as possible during 28 /// 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. 32 pub(crate) struct SysmemFlush { 33 /// Chipset we are operating on. 34 chipset: Chipset, 35 device: ARef<device::Device>, 36 /// Keep the page alive as long as we need it. 37 page: DmaObject, 38 } 39 40 impl SysmemFlush { 41 /// Allocate a memory page and register it as the sysmem flush page. register( dev: &device::Device<device::Bound>, bar: &Bar0, chipset: Chipset, ) -> Result<Self>42 pub(crate) fn register( 43 dev: &device::Device<device::Bound>, 44 bar: &Bar0, 45 chipset: Chipset, 46 ) -> Result<Self> { 47 let page = DmaObject::new(dev, kernel::page::PAGE_SIZE)?; 48 49 hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?; 50 51 Ok(Self { 52 chipset, 53 device: dev.into(), 54 page, 55 }) 56 } 57 58 /// Unregister the managed sysmem flush page. 59 /// 60 /// In order to gracefully tear down the GPU, users must make sure to call this method before 61 /// dropping the object. unregister(&self, bar: &Bar0)62 pub(crate) fn unregister(&self, bar: &Bar0) { 63 let hal = hal::fb_hal(self.chipset); 64 65 if hal.read_sysmem_flush_page(bar) == self.page.dma_handle() { 66 let _ = hal.write_sysmem_flush_page(bar, 0).inspect_err(|e| { 67 dev_warn!( 68 &self.device, 69 "failed to unregister sysmem flush page: {:?}", 70 e 71 ) 72 }); 73 } else { 74 // Another page has been registered after us for some reason - warn as this is a bug. 75 dev_warn!( 76 &self.device, 77 "attempt to unregister a sysmem flush page that is not active\n" 78 ); 79 } 80 } 81 } 82 83 /// 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)] 88 pub(crate) struct FbLayout { 89 pub(crate) fb: Range<u64>, 90 pub(crate) vga_workspace: Range<u64>, 91 pub(crate) frts: Range<u64>, 92 } 93 94 impl FbLayout { 95 /// Computes the FB layout. new(chipset: Chipset, bar: &Bar0) -> Result<Self>96 pub(crate) fn new(chipset: Chipset, bar: &Bar0) -> Result<Self> { 97 let hal = hal::fb_hal(chipset); 98 99 let fb = { 100 let fb_size = hal.vidmem_size(bar); 101 102 0..fb_size 103 }; 104 105 let vga_workspace = { 106 let vga_base = { 107 const NV_PRAMIN_SIZE: u64 = SZ_1M as u64; 108 let base = fb.end - NV_PRAMIN_SIZE; 109 110 if hal.supports_display(bar) { 111 match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() { 112 Some(addr) => { 113 if addr < base { 114 const VBIOS_WORKSPACE_SIZE: u64 = SZ_128K as u64; 115 116 // Point workspace address to end of framebuffer. 117 fb.end - VBIOS_WORKSPACE_SIZE 118 } else { 119 addr 120 } 121 } 122 None => base, 123 } 124 } else { 125 base 126 } 127 }; 128 129 vga_base..fb.end 130 }; 131 132 let frts = { 133 const FRTS_DOWN_ALIGN: u64 = SZ_128K as u64; 134 const FRTS_SIZE: u64 = SZ_1M as u64; 135 // TODO[NUMM]: replace with `align_down` once it lands. 136 let frts_base = (vga_workspace.start & !(FRTS_DOWN_ALIGN - 1)) - FRTS_SIZE; 137 138 frts_base..frts_base + FRTS_SIZE 139 }; 140 141 Ok(Self { 142 fb, 143 vga_workspace, 144 frts, 145 }) 146 } 147 } 148