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