xref: /linux/drivers/gpu/nova-core/fb.rs (revision 220994d61cebfc04f071d69049127657c7e8191b)
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