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