Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Alexandre Courbot 879 67.41% 8 36.36%
John Hubbard 357 27.38% 7 31.82%
Danilo Krummrich 39 2.99% 3 13.64%
Joel A Fernandes 10 0.77% 1 4.55%
Timur Tabi 10 0.77% 2 9.09%
Gary Guo 9 0.69% 1 4.55%
Total 1304 22


// 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,
    regs, //
};

mod hal;

/// 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_handle())?;

        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_handle() {
            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 FbLayout {
    /// 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,
    pub(crate) heap: FbRange,
    pub(crate) vf_partition_count: u8,
    /// PMU reserved memory size, in bytes.
    pub(crate) pmu_reserved_size: u32,
}

impl FbLayout {
    /// Computes the FB layout for `chipset` required to run the `gsp_fw` GSP firmware.
    pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, gsp_fw: &GspFirmware) -> 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 wpr2_heap = {
            const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
            let wpr2_heap_size =
                gsp::LibosParams::from_chipset(chipset).wpr_heap_size(chipset, fb.end)?;
            let wpr2_heap_addr = (elf.start - wpr2_heap_size).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 heap = {
            let heap_size = u64::from(hal.non_wpr_heap_size());
            FbRange(wpr2.start - heap_size..wpr2.start)
        };

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