xref: /linux/drivers/gpu/nova-core/fb.rs (revision dc395c2831b59a90b4605ef38e6c6ef83cf8cc4f)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 use core::ops::{
4     Deref,
5     Range, //
6 };
7 
8 use kernel::{
9     device,
10     dma::CoherentHandle,
11     fmt,
12     io::Io,
13     prelude::*,
14     ptr::{
15         Alignable,
16         Alignment, //
17     },
18     sizes::*, //
19 };
20 
21 use crate::{
22     driver::Bar0,
23     firmware::gsp::GspFirmware,
24     gpu::Chipset,
25     gsp,
26     num::FromSafeCast,
27     regs, //
28 };
29 
30 mod hal;
31 
32 /// Type holding the sysmem flush memory page, a page of memory to be written into the
33 /// `NV_PFB_NISO_FLUSH_SYSMEM_ADDR*` registers and used to maintain memory coherency.
34 ///
35 /// A system memory page is required for `sysmembar`, which is a GPU-initiated hardware
36 /// memory-barrier operation that flushes all pending GPU-side memory writes that were done through
37 /// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a
38 /// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure
39 /// this write is visible to the host and prevent driver timeouts, the falcon must perform a
40 /// sysmembar operation to flush its writes.
41 ///
42 /// Because of this, the sysmem flush memory page must be registered as early as possible during
43 /// driver initialization, and before any falcon is reset.
44 ///
45 pub(crate) struct SysmemFlush<'sys> {
46     /// Chipset we are operating on.
47     chipset: Chipset,
48     device: &'sys device::Device,
49     bar: &'sys Bar0,
50     /// Keep the page alive as long as we need it.
51     page: CoherentHandle,
52 }
53 
54 impl<'sys> SysmemFlush<'sys> {
55     /// Allocate a memory page and register it as the sysmem flush page.
56     pub(crate) fn register(
57         dev: &'sys device::Device<device::Bound>,
58         bar: &'sys Bar0,
59         chipset: Chipset,
60     ) -> Result<Self> {
61         let page = CoherentHandle::alloc(dev, kernel::page::PAGE_SIZE, GFP_KERNEL)?;
62 
63         hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?;
64 
65         Ok(Self {
66             chipset,
67             device: dev,
68             bar,
69             page,
70         })
71     }
72 }
73 
74 impl Drop for SysmemFlush<'_> {
75     fn drop(&mut self) {
76         let hal = hal::fb_hal(self.chipset);
77 
78         if hal.read_sysmem_flush_page(self.bar) == self.page.dma_handle() {
79             let _ = hal.write_sysmem_flush_page(self.bar, 0).inspect_err(|e| {
80                 dev_warn!(
81                     &self.device,
82                     "failed to unregister sysmem flush page: {:?}\n",
83                     e
84                 )
85             });
86         } else {
87             // Another page has been registered after us for some reason - warn as this is a bug.
88             dev_warn!(
89                 &self.device,
90                 "attempt to unregister a sysmem flush page that is not active\n"
91             );
92         }
93     }
94 }
95 
96 pub(crate) struct FbRange(Range<u64>);
97 
98 impl FbRange {
99     pub(crate) fn len(&self) -> u64 {
100         self.0.end - self.0.start
101     }
102 }
103 
104 impl From<Range<u64>> for FbRange {
105     fn from(range: Range<u64>) -> Self {
106         Self(range)
107     }
108 }
109 
110 impl Deref for FbRange {
111     type Target = Range<u64>;
112 
113     fn deref(&self) -> &Self::Target {
114         &self.0
115     }
116 }
117 
118 impl fmt::Debug for FbRange {
119     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120         // Use alternate format ({:#?}) to include size, compact format ({:?}) for just the range.
121         if f.alternate() {
122             let size = self.len();
123 
124             if size < u64::SZ_1M {
125                 let size_kib = size / u64::SZ_1K;
126                 f.write_fmt(fmt!(
127                     "{:#x}..{:#x} ({} KiB)",
128                     self.0.start,
129                     self.0.end,
130                     size_kib
131                 ))
132             } else {
133                 let size_mib = size / u64::SZ_1M;
134                 f.write_fmt(fmt!(
135                     "{:#x}..{:#x} ({} MiB)",
136                     self.0.start,
137                     self.0.end,
138                     size_mib
139                 ))
140             }
141         } else {
142             f.write_fmt(fmt!("{:#x}..{:#x}", self.0.start, self.0.end))
143         }
144     }
145 }
146 
147 /// Layout of the GPU framebuffer memory.
148 ///
149 /// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.
150 #[derive(Debug)]
151 pub(crate) struct FbLayout {
152     /// Range of the framebuffer. Starts at `0`.
153     pub(crate) fb: FbRange,
154     /// VGA workspace, small area of reserved memory at the end of the framebuffer.
155     pub(crate) vga_workspace: FbRange,
156     /// FRTS range.
157     pub(crate) frts: FbRange,
158     /// Memory area containing the GSP bootloader image.
159     pub(crate) boot: FbRange,
160     /// Memory area containing the GSP firmware image.
161     pub(crate) elf: FbRange,
162     /// WPR2 heap.
163     pub(crate) wpr2_heap: FbRange,
164     /// WPR2 region range, starting with an instance of `GspFwWprMeta`.
165     pub(crate) wpr2: FbRange,
166     pub(crate) heap: FbRange,
167     pub(crate) vf_partition_count: u8,
168 }
169 
170 impl FbLayout {
171     /// Computes the FB layout for `chipset` required to run the `gsp_fw` GSP firmware.
172     pub(crate) fn new(chipset: Chipset, bar: &Bar0, gsp_fw: &GspFirmware) -> Result<Self> {
173         let hal = hal::fb_hal(chipset);
174 
175         let fb = {
176             let fb_size = hal.vidmem_size(bar);
177 
178             FbRange(0..fb_size)
179         };
180 
181         let vga_workspace = {
182             let vga_base = {
183                 const NV_PRAMIN_SIZE: u64 = u64::SZ_1M;
184                 let base = fb.end - NV_PRAMIN_SIZE;
185 
186                 if hal.supports_display(bar) {
187                     match bar
188                         .read(regs::NV_PDISP_VGA_WORKSPACE_BASE)
189                         .vga_workspace_addr()
190                     {
191                         Some(addr) => {
192                             if addr < base {
193                                 const VBIOS_WORKSPACE_SIZE: u64 = u64::SZ_128K;
194 
195                                 // Point workspace address to end of framebuffer.
196                                 fb.end - VBIOS_WORKSPACE_SIZE
197                             } else {
198                                 addr
199                             }
200                         }
201                         None => base,
202                     }
203                 } else {
204                     base
205                 }
206             };
207 
208             FbRange(vga_base..fb.end)
209         };
210 
211         let frts = {
212             const FRTS_DOWN_ALIGN: Alignment = Alignment::new::<SZ_128K>();
213             let frts_size: u64 = hal.frts_size();
214             let frts_base = vga_workspace.start.align_down(FRTS_DOWN_ALIGN) - frts_size;
215 
216             FbRange(frts_base..frts_base + frts_size)
217         };
218 
219         let boot = {
220             const BOOTLOADER_DOWN_ALIGN: Alignment = Alignment::new::<SZ_4K>();
221             let bootloader_size = u64::from_safe_cast(gsp_fw.bootloader.ucode.size());
222             let bootloader_base = (frts.start - bootloader_size).align_down(BOOTLOADER_DOWN_ALIGN);
223 
224             FbRange(bootloader_base..bootloader_base + bootloader_size)
225         };
226 
227         let elf = {
228             const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
229             let elf_size = u64::from_safe_cast(gsp_fw.size);
230             let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN);
231 
232             FbRange(elf_addr..elf_addr + elf_size)
233         };
234 
235         let wpr2_heap = {
236             const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
237             let wpr2_heap_size =
238                 gsp::LibosParams::from_chipset(chipset).wpr_heap_size(chipset, fb.end)?;
239             let wpr2_heap_addr = (elf.start - wpr2_heap_size).align_down(WPR2_HEAP_DOWN_ALIGN);
240 
241             FbRange(wpr2_heap_addr..(elf.start).align_down(WPR2_HEAP_DOWN_ALIGN))
242         };
243 
244         let wpr2 = {
245             const WPR2_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
246             let wpr2_addr = (wpr2_heap.start - u64::from_safe_cast(size_of::<gsp::GspFwWprMeta>()))
247                 .align_down(WPR2_DOWN_ALIGN);
248 
249             FbRange(wpr2_addr..frts.end)
250         };
251 
252         let heap = {
253             const HEAP_SIZE: u64 = u64::SZ_1M;
254 
255             FbRange(wpr2.start - HEAP_SIZE..wpr2.start)
256         };
257 
258         Ok(Self {
259             fb,
260             vga_workspace,
261             frts,
262             boot,
263             elf,
264             wpr2_heap,
265             wpr2,
266             heap,
267             vf_partition_count: 0,
268         })
269     }
270 }
271