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