Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2
3use core::ops::Range;
4
5use kernel::prelude::*;
6use kernel::sizes::*;
7use kernel::types::ARef;
8use kernel::{dev_warn, device};
9
10use crate::dma::DmaObject;
11use crate::driver::Bar0;
12use crate::gpu::Chipset;
13use crate::regs;
14
15mod 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.
32pub(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
40impl SysmemFlush {
41 /// Allocate a memory page and register it as the sysmem flush page.
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.
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)]
88pub(crate) struct FbLayout {
89 pub(crate) fb: Range<u64>,
90 pub(crate) vga_workspace: Range<u64>,
91 pub(crate) frts: Range<u64>,
92}
93
94impl FbLayout {
95 /// Computes the FB layout.
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}