summaryrefslogtreecommitdiff
path: root/drivers/gpu/nova-core/fb.rs
blob: 1576399389b19212dfbf4401f7c7d1c5695d0a18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// SPDX-License-Identifier: GPL-2.0

use core::ops::{
    Deref,
    Range, //
};

use kernel::{
    device,
    dma::CoherentHandle,
    fmt,
    io::Io,
    prelude::*,
    ptr::{
        Alignable,
        Alignment, //
    },
    sizes::*, //
};

use crate::{
    driver::Bar0,
    firmware::gsp::GspFirmware,
    gpu::Chipset,
    gsp,
    num::FromSafeCast,
    vgpu::VgpuState, //
};

mod hal;
mod regs;

/// Type holding the sysmem flush memory page, a page of memory to be written into the
/// `NV_PFB_NISO_FLUSH_SYSMEM_ADDR*` registers and used to maintain memory coherency.
///
/// A system memory page is required for `sysmembar`, which is a GPU-initiated hardware
/// memory-barrier operation that flushes all pending GPU-side memory writes that were done through
/// PCIE to system memory. It is required for falcons to be reset as the reset operation involves a
/// reset handshake. When the falcon acknowledges a reset, it writes into system memory. To ensure
/// this write is visible to the host and prevent driver timeouts, the falcon must perform a
/// sysmembar operation to flush its writes.
///
/// Because of this, the sysmem flush memory page must be registered as early as possible during
/// driver initialization, and before any falcon is reset.
///
pub(crate) struct SysmemFlush<'sys> {
    /// Chipset we are operating on.
    chipset: Chipset,
    device: &'sys device::Device,
    bar: Bar0<'sys>,
    /// Keep the page alive as long as we need it.
    page: CoherentHandle,
}

impl<'sys> SysmemFlush<'sys> {
    /// Allocate a memory page and register it as the sysmem flush page.
    pub(crate) fn register(
        dev: &'sys device::Device<device::Bound>,
        bar: Bar0<'sys>,
        chipset: Chipset,
    ) -> Result<Self> {
        let page = CoherentHandle::alloc(dev, kernel::page::PAGE_SIZE, GFP_KERNEL)?;

        hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_address())?;

        Ok(Self {
            chipset,
            device: dev,
            bar,
            page,
        })
    }
}

impl Drop for SysmemFlush<'_> {
    fn drop(&mut self) {
        let hal = hal::fb_hal(self.chipset);

        if hal.read_sysmem_flush_page(self.bar) == self.page.dma_address() {
            let _ = hal.write_sysmem_flush_page(self.bar, 0).inspect_err(|e| {
                dev_warn!(
                    &self.device,
                    "failed to unregister sysmem flush page: {:?}\n",
                    e
                )
            });
        } else {
            // Another page has been registered after us for some reason - warn as this is a bug.
            dev_warn!(
                &self.device,
                "attempt to unregister a sysmem flush page that is not active\n"
            );
        }
    }
}

pub(crate) struct FbRange(Range<u64>);

impl FbRange {
    pub(crate) fn len(&self) -> u64 {
        self.0.end - self.0.start
    }
}

impl From<Range<u64>> for FbRange {
    fn from(range: Range<u64>) -> Self {
        Self(range)
    }
}

impl Deref for FbRange {
    type Target = Range<u64>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Debug for FbRange {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Use alternate format ({:#?}) to include size, compact format ({:?}) for just the range.
        if f.alternate() {
            let size = self.len();

            if size < u64::SZ_1M {
                let size_kib = size / u64::SZ_1K;
                f.write_fmt(fmt!(
                    "{:#x}..{:#x} ({} KiB)",
                    self.0.start,
                    self.0.end,
                    size_kib
                ))
            } else {
                let size_mib = size / u64::SZ_1M;
                f.write_fmt(fmt!(
                    "{:#x}..{:#x} ({} MiB)",
                    self.0.start,
                    self.0.end,
                    size_mib
                ))
            }
        } else {
            f.write_fmt(fmt!("{:#x}..{:#x}", self.0.start, self.0.end))
        }
    }
}

/// Layout of the GPU framebuffer memory.
///
/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.
#[derive(Debug)]
pub(crate) struct FbRanges {
    /// Range of the framebuffer. Starts at `0`.
    pub(crate) fb: FbRange,
    /// VGA workspace, small area of reserved memory at the end of the framebuffer.
    pub(crate) vga_workspace: FbRange,
    /// FRTS range.
    pub(crate) frts: FbRange,
    /// Memory area containing the GSP bootloader image.
    pub(crate) boot: FbRange,
    /// Memory area containing the GSP firmware image.
    pub(crate) elf: FbRange,
    /// WPR2 heap.
    pub(crate) wpr2_heap: FbRange,
    /// WPR2 region range, starting with an instance of `GspFwWprMeta`.
    pub(crate) wpr2: FbRange,
    /// Non-WPR heap, located just below WPR2.
    pub(crate) non_wpr_heap: FbRange,
    /// Number of VF partitions.
    pub(crate) vf_partition_count: u8,
    /// PMU reserved memory size, in bytes.
    pub(crate) pmu_reserved_size: u32,
}

impl FbRanges {
    /// Computes concrete framebuffer ranges required on non-FSP booting architectures.
    pub(crate) fn new(
        chipset: Chipset,
        bar: Bar0<'_>,
        gsp_fw: &GspFirmware,
        vgpu_state: VgpuState,
    ) -> Result<Self> {
        let hal = hal::fb_hal(chipset);

        let fb = {
            let fb_size = hal.vidmem_size(bar);

            FbRange(0..fb_size)
        };

        let vga_workspace = {
            let vga_base = {
                const NV_PRAMIN_SIZE: u64 = u64::SZ_1M;
                let base = fb.end - NV_PRAMIN_SIZE;

                if hal.supports_display(bar) {
                    match bar
                        .read(regs::NV_PDISP_VGA_WORKSPACE_BASE)
                        .vga_workspace_addr()
                    {
                        Some(addr) => {
                            if addr < base {
                                const VBIOS_WORKSPACE_SIZE: u64 = u64::SZ_128K;

                                // Point workspace address to end of framebuffer.
                                fb.end - VBIOS_WORKSPACE_SIZE
                            } else {
                                addr
                            }
                        }
                        None => base,
                    }
                } else {
                    base
                }
            };

            FbRange(vga_base..fb.end)
        };

        let frts = {
            const FRTS_DOWN_ALIGN: Alignment = Alignment::new::<SZ_128K>();
            let frts_size: u64 = hal.frts_size();
            let frts_base = vga_workspace.start.align_down(FRTS_DOWN_ALIGN) - frts_size;

            FbRange(frts_base..frts_base + frts_size)
        };

        let boot = {
            const BOOTLOADER_DOWN_ALIGN: Alignment = Alignment::new::<SZ_4K>();
            let bootloader_size = u64::from_safe_cast(gsp_fw.bootloader.ucode.size());
            let bootloader_base = (frts.start - bootloader_size).align_down(BOOTLOADER_DOWN_ALIGN);

            FbRange(bootloader_base..bootloader_base + bootloader_size)
        };

        let elf = {
            const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
            let elf_size = u64::from_safe_cast(gsp_fw.size);
            let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN);

            FbRange(elf_addr..elf_addr + elf_size)
        };

        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb.end)?;

        let wpr2_heap = {
            const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
            let wpr2_heap_addr = elf
                .start
                .checked_sub(wpr2_heap_size)
                .ok_or(EOVERFLOW)?
                .align_down(WPR2_HEAP_DOWN_ALIGN);

            FbRange(wpr2_heap_addr..(elf.start).align_down(WPR2_HEAP_DOWN_ALIGN))
        };

        let wpr2 = {
            const WPR2_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
            let wpr2_addr = (wpr2_heap.start - u64::from_safe_cast(size_of::<gsp::GspFwWprMeta>()))
                .align_down(WPR2_DOWN_ALIGN);

            FbRange(wpr2_addr..frts.end)
        };

        let non_wpr_heap = {
            let non_wpr_heap_size = hal.non_wpr_heap_size();
            FbRange(wpr2.start - non_wpr_heap_size..wpr2.start)
        };

        Ok(Self {
            fb,
            vga_workspace,
            frts,
            boot,
            elf,
            wpr2_heap,
            wpr2,
            non_wpr_heap,
            vf_partition_count,
            pmu_reserved_size: hal.pmu_reserved_size(),
        })
    }
}

/// Reads the WPR2 memory region registers and returns the range if set.
/// Returns `None` if the WPR2 region is not set.
pub(crate) fn wpr2_range(bar: Bar0<'_>) -> Option<Range<u64>> {
    let wpr2_hi = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI);

    if !wpr2_hi.is_wpr2_set() {
        return None;
    }

    let wpr2_lo = bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO);

    Some(wpr2_lo.lower_bound()..wpr2_hi.higher_bound())
}

/// Computes the number of VF partitions and the WPR2 heap size from the vGPU state.
fn wpr2_heap_params(chipset: Chipset, vgpu_state: VgpuState, fb_size: u64) -> Result<(u8, u64)> {
    Ok(match vgpu_state {
        VgpuState::Disabled => (
            0,
            gsp::LibosParams::from_chipset(chipset).wpr_heap_size(chipset, fb_size)?,
        ),
        VgpuState::Enabled { total_vfs } => (
            u8::try_from(total_vfs.get()).map_err(|_| EINVAL)?,
            gsp::LibosParams::vgpu_wpr_heap_size(),
        ),
    })
}

/// Framebuffer region sizes needed for GSP-FMC boot.
#[derive(Debug)]
pub(crate) struct FbSizes {
    /// FRTS size, in bytes.
    pub(crate) frts_size: u64,
    /// WPR2 heap size, in bytes.
    pub(crate) wpr2_heap_size: u64,
    /// Non-WPR heap size, in bytes.
    pub(crate) non_wpr_heap_size: u64,
    /// PMU reserved memory size, in bytes.
    pub(crate) pmu_reserved_size: u32,
    /// Number of VF partitions.
    pub(crate) vf_partition_count: u8,
}

impl FbSizes {
    /// Computes the framebuffer region sizes for GSP-FMC boot.
    pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, vgpu_state: VgpuState) -> Result<Self> {
        let hal = hal::fb_hal(chipset);
        let fb_size = hal.vidmem_size(bar);
        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb_size)?;

        Ok(Self {
            frts_size: hal.frts_size(),
            wpr2_heap_size,
            non_wpr_heap_size: hal.non_wpr_heap_size(),
            pmu_reserved_size: hal.pmu_reserved_size(),
            vf_partition_count,
        })
    }
}