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::ptr::{Alignable, Alignment};
7use kernel::sizes::*;
8use kernel::sync::aref::ARef;
9use kernel::{dev_warn, device};
10
11use crate::dma::DmaObject;
12use crate::driver::Bar0;
13use crate::gpu::Chipset;
14use crate::regs;
15
16mod 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.
33pub(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
41impl 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)]
89pub(crate) struct FbLayout {
90 pub(crate) fb: Range<u64>,
91 pub(crate) vga_workspace: Range<u64>,
92 pub(crate) frts: Range<u64>,
93}
94
95impl 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}