mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

sysmembar is a critical operation that the GSP falcon needs to perform in the reset sequence. Add some code comments to clarify. [acourbot@nvdidia.com: move relevant documentation to SysmemFlush type] Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> Link: https://lore.kernel.org/r/20250708-nova-docs-v4-2-9d188772c4c7@nvidia.com [ Minor grammar fix in the PFB register documentation. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
147 lines
4.7 KiB
Rust
147 lines
4.7 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use core::ops::Range;
|
|
|
|
use kernel::prelude::*;
|
|
use kernel::sizes::*;
|
|
use kernel::types::ARef;
|
|
use kernel::{dev_warn, device};
|
|
|
|
use crate::dma::DmaObject;
|
|
use crate::driver::Bar0;
|
|
use crate::gpu::Chipset;
|
|
use crate::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.
|
|
///
|
|
/// Users are responsible for manually calling [`Self::unregister`] before dropping this object,
|
|
/// otherwise the GPU might still use it even after it has been freed.
|
|
pub(crate) struct SysmemFlush {
|
|
/// Chipset we are operating on.
|
|
chipset: Chipset,
|
|
device: ARef<device::Device>,
|
|
/// Keep the page alive as long as we need it.
|
|
page: DmaObject,
|
|
}
|
|
|
|
impl SysmemFlush {
|
|
/// Allocate a memory page and register it as the sysmem flush page.
|
|
pub(crate) fn register(
|
|
dev: &device::Device<device::Bound>,
|
|
bar: &Bar0,
|
|
chipset: Chipset,
|
|
) -> Result<Self> {
|
|
let page = DmaObject::new(dev, kernel::page::PAGE_SIZE)?;
|
|
|
|
hal::fb_hal(chipset).write_sysmem_flush_page(bar, page.dma_handle())?;
|
|
|
|
Ok(Self {
|
|
chipset,
|
|
device: dev.into(),
|
|
page,
|
|
})
|
|
}
|
|
|
|
/// Unregister the managed sysmem flush page.
|
|
///
|
|
/// In order to gracefully tear down the GPU, users must make sure to call this method before
|
|
/// dropping the object.
|
|
pub(crate) fn unregister(&self, bar: &Bar0) {
|
|
let hal = hal::fb_hal(self.chipset);
|
|
|
|
if hal.read_sysmem_flush_page(bar) == self.page.dma_handle() {
|
|
let _ = hal.write_sysmem_flush_page(bar, 0).inspect_err(|e| {
|
|
dev_warn!(
|
|
&self.device,
|
|
"failed to unregister sysmem flush page: {:?}",
|
|
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"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Layout of the GPU framebuffer memory.
|
|
///
|
|
/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.
|
|
#[derive(Debug)]
|
|
#[expect(dead_code)]
|
|
pub(crate) struct FbLayout {
|
|
pub(crate) fb: Range<u64>,
|
|
pub(crate) vga_workspace: Range<u64>,
|
|
pub(crate) frts: Range<u64>,
|
|
}
|
|
|
|
impl FbLayout {
|
|
/// Computes the FB layout.
|
|
pub(crate) fn new(chipset: Chipset, bar: &Bar0) -> Result<Self> {
|
|
let hal = hal::fb_hal(chipset);
|
|
|
|
let fb = {
|
|
let fb_size = hal.vidmem_size(bar);
|
|
|
|
0..fb_size
|
|
};
|
|
|
|
let vga_workspace = {
|
|
let vga_base = {
|
|
const NV_PRAMIN_SIZE: u64 = SZ_1M as u64;
|
|
let base = fb.end - NV_PRAMIN_SIZE;
|
|
|
|
if hal.supports_display(bar) {
|
|
match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() {
|
|
Some(addr) => {
|
|
if addr < base {
|
|
const VBIOS_WORKSPACE_SIZE: u64 = SZ_128K as u64;
|
|
|
|
// Point workspace address to end of framebuffer.
|
|
fb.end - VBIOS_WORKSPACE_SIZE
|
|
} else {
|
|
addr
|
|
}
|
|
}
|
|
None => base,
|
|
}
|
|
} else {
|
|
base
|
|
}
|
|
};
|
|
|
|
vga_base..fb.end
|
|
};
|
|
|
|
let frts = {
|
|
const FRTS_DOWN_ALIGN: u64 = SZ_128K as u64;
|
|
const FRTS_SIZE: u64 = SZ_1M as u64;
|
|
// TODO[NUMM]: replace with `align_down` once it lands.
|
|
let frts_base = (vga_workspace.start & !(FRTS_DOWN_ALIGN - 1)) - FRTS_SIZE;
|
|
|
|
frts_base..frts_base + FRTS_SIZE
|
|
};
|
|
|
|
Ok(Self {
|
|
fb,
|
|
vga_workspace,
|
|
frts,
|
|
})
|
|
}
|
|
}
|