mirror of
https://github.com/torvalds/linux.git
synced 2025-04-12 06:49:52 +00:00
gpu: nova-core: add initial driver stub
Add the initial nova-core driver stub. nova-core is intended to serve as a common base for nova-drm (the corresponding DRM driver) and the vGPU manager VFIO driver, serving as a hard- and firmware abstraction layer for GSP-based NVIDIA GPUs. The Nova project, including nova-core and nova-drm, in the long term, is intended to serve as the successor of Nouveau for all GSP-based GPUs. The motivation for both, starting a successor project for Nouveau and doing so using the Rust programming language, is documented in detail through a previous post on the mailing list [1], an LWN article [2] and a talk from LPC '24. In order to avoid the chicken and egg problem to require a user to upstream Rust abstractions, but at the same time require the Rust abstractions to implement the driver, nova-core kicks off as a driver stub and is subsequently developed upstream. Link: https://lore.kernel.org/dri-devel/Zfsj0_tb-0-tNrJy@cassiopeiae/T/#u [1] Link: https://lwn.net/Articles/990736/ [2] Link: https://youtu.be/3Igmx28B3BQ?si=sBdSEer4tAPKGpOs [3] Reviewed-by: Alexandre Courbot <acourbot@nvidia.com> Link: https://lore.kernel.org/r/20250306222336.23482-5-dakr@kernel.org Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
parent
1d121a33ad
commit
54e6baf123
10
MAINTAINERS
10
MAINTAINERS
@ -7449,6 +7449,16 @@ T: git https://gitlab.freedesktop.org/drm/nouveau.git
|
||||
F: drivers/gpu/drm/nouveau/
|
||||
F: include/uapi/drm/nouveau_drm.h
|
||||
|
||||
CORE DRIVER FOR NVIDIA GPUS [RUST]
|
||||
M: Danilo Krummrich <dakr@kernel.org>
|
||||
L: nouveau@lists.freedesktop.org
|
||||
S: Supported
|
||||
Q: https://patchwork.freedesktop.org/project/nouveau/
|
||||
B: https://gitlab.freedesktop.org/drm/nova/-/issues
|
||||
C: irc://irc.oftc.net/nouveau
|
||||
T: git https://gitlab.freedesktop.org/drm/nova.git nova-next
|
||||
F: drivers/gpu/nova-core/
|
||||
|
||||
DRM DRIVER FOR OLIMEX LCD-OLINUXINO PANELS
|
||||
M: Stefan Mavrodiev <stefan@olimex.com>
|
||||
S: Maintained
|
||||
|
@ -5,3 +5,4 @@
|
||||
obj-y += host1x/ drm/ vga/
|
||||
obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
|
||||
obj-$(CONFIG_TRACE_GPU_MEM) += trace/
|
||||
obj-$(CONFIG_NOVA_CORE) += nova-core/
|
||||
|
14
drivers/gpu/nova-core/Kconfig
Normal file
14
drivers/gpu/nova-core/Kconfig
Normal file
@ -0,0 +1,14 @@
|
||||
config NOVA_CORE
|
||||
tristate "Nova Core GPU driver"
|
||||
depends on PCI
|
||||
depends on RUST
|
||||
depends on RUST_FW_LOADER_ABSTRACTIONS
|
||||
default n
|
||||
help
|
||||
Choose this if you want to build the Nova Core driver for Nvidia
|
||||
GPUs based on the GPU System Processor (GSP). This is true for Turing
|
||||
and later GPUs.
|
||||
|
||||
This driver is work in progress and may not be functional.
|
||||
|
||||
If M is selected, the module will be called nova_core.
|
3
drivers/gpu/nova-core/Makefile
Normal file
3
drivers/gpu/nova-core/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_NOVA_CORE) += nova_core.o
|
47
drivers/gpu/nova-core/driver.rs
Normal file
47
drivers/gpu/nova-core/driver.rs
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use kernel::{bindings, c_str, pci, prelude::*};
|
||||
|
||||
use crate::gpu::Gpu;
|
||||
|
||||
#[pin_data]
|
||||
pub(crate) struct NovaCore {
|
||||
#[pin]
|
||||
pub(crate) gpu: Gpu,
|
||||
}
|
||||
|
||||
const BAR0_SIZE: usize = 8;
|
||||
pub(crate) type Bar0 = pci::Bar<BAR0_SIZE>;
|
||||
|
||||
kernel::pci_device_table!(
|
||||
PCI_TABLE,
|
||||
MODULE_PCI_TABLE,
|
||||
<NovaCore as pci::Driver>::IdInfo,
|
||||
[(
|
||||
pci::DeviceId::from_id(bindings::PCI_VENDOR_ID_NVIDIA, bindings::PCI_ANY_ID as _),
|
||||
()
|
||||
)]
|
||||
);
|
||||
|
||||
impl pci::Driver for NovaCore {
|
||||
type IdInfo = ();
|
||||
const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
|
||||
|
||||
fn probe(pdev: &mut pci::Device, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
|
||||
dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n");
|
||||
|
||||
pdev.enable_device_mem()?;
|
||||
pdev.set_master();
|
||||
|
||||
let bar = pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?;
|
||||
|
||||
let this = KBox::pin_init(
|
||||
try_pin_init!(Self {
|
||||
gpu <- Gpu::new(pdev, bar)?,
|
||||
}),
|
||||
GFP_KERNEL,
|
||||
)?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
}
|
45
drivers/gpu/nova-core/firmware.rs
Normal file
45
drivers/gpu/nova-core/firmware.rs
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use crate::gpu;
|
||||
use kernel::firmware;
|
||||
|
||||
pub(crate) struct ModInfoBuilder<const N: usize>(firmware::ModInfoBuilder<N>);
|
||||
|
||||
impl<const N: usize> ModInfoBuilder<N> {
|
||||
const VERSION: &'static str = "535.113.01";
|
||||
|
||||
const fn make_entry_file(self, chipset: &str, fw: &str) -> Self {
|
||||
ModInfoBuilder(
|
||||
self.0
|
||||
.new_entry()
|
||||
.push("nvidia/")
|
||||
.push(chipset)
|
||||
.push("/gsp/")
|
||||
.push(fw)
|
||||
.push("-")
|
||||
.push(Self::VERSION)
|
||||
.push(".bin"),
|
||||
)
|
||||
}
|
||||
|
||||
const fn make_entry_chipset(self, chipset: &str) -> Self {
|
||||
self.make_entry_file(chipset, "booter_load")
|
||||
.make_entry_file(chipset, "booter_unload")
|
||||
.make_entry_file(chipset, "bootloader")
|
||||
.make_entry_file(chipset, "gsp")
|
||||
}
|
||||
|
||||
pub(crate) const fn create(
|
||||
module_name: &'static kernel::str::CStr,
|
||||
) -> firmware::ModInfoBuilder<N> {
|
||||
let mut this = Self(firmware::ModInfoBuilder::new(module_name));
|
||||
let mut i = 0;
|
||||
|
||||
while i < gpu::Chipset::NAMES.len() {
|
||||
this = this.make_entry_chipset(gpu::Chipset::NAMES[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
this.0
|
||||
}
|
||||
}
|
199
drivers/gpu/nova-core/gpu.rs
Normal file
199
drivers/gpu/nova-core/gpu.rs
Normal file
@ -0,0 +1,199 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use kernel::{
|
||||
device, devres::Devres, error::code::*, firmware, fmt, pci, prelude::*, str::CString,
|
||||
};
|
||||
|
||||
use crate::driver::Bar0;
|
||||
use crate::regs;
|
||||
use crate::util;
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! define_chipset {
|
||||
({ $($variant:ident = $value:expr),* $(,)* }) =>
|
||||
{
|
||||
/// Enum representation of the GPU chipset.
|
||||
#[derive(fmt::Debug)]
|
||||
pub(crate) enum Chipset {
|
||||
$($variant = $value),*,
|
||||
}
|
||||
|
||||
impl Chipset {
|
||||
pub(crate) const ALL: &'static [Chipset] = &[
|
||||
$( Chipset::$variant, )*
|
||||
];
|
||||
|
||||
pub(crate) const NAMES: [&'static str; Self::ALL.len()] = [
|
||||
$( util::const_bytes_to_str(
|
||||
util::to_lowercase_bytes::<{ stringify!($variant).len() }>(
|
||||
stringify!($variant)
|
||||
).as_slice()
|
||||
), )*
|
||||
];
|
||||
}
|
||||
|
||||
// TODO replace with something like derive(FromPrimitive)
|
||||
impl TryFrom<u32> for Chipset {
|
||||
type Error = kernel::error::Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
$( $value => Ok(Chipset::$variant), )*
|
||||
_ => Err(ENODEV),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_chipset!({
|
||||
// Turing
|
||||
TU102 = 0x162,
|
||||
TU104 = 0x164,
|
||||
TU106 = 0x166,
|
||||
TU117 = 0x167,
|
||||
TU116 = 0x168,
|
||||
// Ampere
|
||||
GA102 = 0x172,
|
||||
GA103 = 0x173,
|
||||
GA104 = 0x174,
|
||||
GA106 = 0x176,
|
||||
GA107 = 0x177,
|
||||
// Ada
|
||||
AD102 = 0x192,
|
||||
AD103 = 0x193,
|
||||
AD104 = 0x194,
|
||||
AD106 = 0x196,
|
||||
AD107 = 0x197,
|
||||
});
|
||||
|
||||
impl Chipset {
|
||||
pub(crate) fn arch(&self) -> Architecture {
|
||||
match self {
|
||||
Self::TU102 | Self::TU104 | Self::TU106 | Self::TU117 | Self::TU116 => {
|
||||
Architecture::Turing
|
||||
}
|
||||
Self::GA102 | Self::GA103 | Self::GA104 | Self::GA106 | Self::GA107 => {
|
||||
Architecture::Ampere
|
||||
}
|
||||
Self::AD102 | Self::AD103 | Self::AD104 | Self::AD106 | Self::AD107 => {
|
||||
Architecture::Ada
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
//
|
||||
// The resulting strings are used to generate firmware paths, hence the
|
||||
// generated strings have to be stable.
|
||||
//
|
||||
// Hence, replace with something like strum_macros derive(Display).
|
||||
//
|
||||
// For now, redirect to fmt::Debug for convenience.
|
||||
impl fmt::Display for Chipset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum representation of the GPU generation.
|
||||
#[derive(fmt::Debug)]
|
||||
pub(crate) enum Architecture {
|
||||
Turing,
|
||||
Ampere,
|
||||
Ada,
|
||||
}
|
||||
|
||||
pub(crate) struct Revision {
|
||||
major: u8,
|
||||
minor: u8,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
fn from_boot0(boot0: regs::Boot0) -> Self {
|
||||
Self {
|
||||
major: boot0.major_rev(),
|
||||
minor: boot0.minor_rev(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Revision {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:x}.{:x}", self.major, self.minor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure holding the metadata of the GPU.
|
||||
pub(crate) struct Spec {
|
||||
chipset: Chipset,
|
||||
/// The revision of the chipset.
|
||||
revision: Revision,
|
||||
}
|
||||
|
||||
impl Spec {
|
||||
fn new(bar: &Devres<Bar0>) -> Result<Spec> {
|
||||
let bar = bar.try_access().ok_or(ENXIO)?;
|
||||
let boot0 = regs::Boot0::read(&bar);
|
||||
|
||||
Ok(Self {
|
||||
chipset: boot0.chipset().try_into()?,
|
||||
revision: Revision::from_boot0(boot0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure encapsulating the firmware blobs required for the GPU to operate.
|
||||
#[expect(dead_code)]
|
||||
pub(crate) struct Firmware {
|
||||
booter_load: firmware::Firmware,
|
||||
booter_unload: firmware::Firmware,
|
||||
bootloader: firmware::Firmware,
|
||||
gsp: firmware::Firmware,
|
||||
}
|
||||
|
||||
impl Firmware {
|
||||
fn new(dev: &device::Device, spec: &Spec, ver: &str) -> Result<Firmware> {
|
||||
let mut chip_name = CString::try_from_fmt(fmt!("{}", spec.chipset))?;
|
||||
chip_name.make_ascii_lowercase();
|
||||
|
||||
let request = |name_| {
|
||||
CString::try_from_fmt(fmt!("nvidia/{}/gsp/{}-{}.bin", &*chip_name, name_, ver))
|
||||
.and_then(|path| firmware::Firmware::request(&path, dev))
|
||||
};
|
||||
|
||||
Ok(Firmware {
|
||||
booter_load: request("booter_load")?,
|
||||
booter_unload: request("booter_unload")?,
|
||||
bootloader: request("bootloader")?,
|
||||
gsp: request("gsp")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure holding the resources required to operate the GPU.
|
||||
#[pin_data]
|
||||
pub(crate) struct Gpu {
|
||||
spec: Spec,
|
||||
/// MMIO mapping of PCI BAR 0
|
||||
bar: Devres<Bar0>,
|
||||
fw: Firmware,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
pub(crate) fn new(pdev: &pci::Device, bar: Devres<Bar0>) -> Result<impl PinInit<Self>> {
|
||||
let spec = Spec::new(&bar)?;
|
||||
let fw = Firmware::new(pdev.as_ref(), &spec, "535.113.01")?;
|
||||
|
||||
dev_info!(
|
||||
pdev.as_ref(),
|
||||
"NVIDIA (Chipset: {}, Architecture: {:?}, Revision: {})\n",
|
||||
spec.chipset,
|
||||
spec.chipset.arch(),
|
||||
spec.revision
|
||||
);
|
||||
|
||||
Ok(pin_init!(Self { spec, bar, fw }))
|
||||
}
|
||||
}
|
20
drivers/gpu/nova-core/nova_core.rs
Normal file
20
drivers/gpu/nova-core/nova_core.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Nova Core GPU Driver
|
||||
|
||||
mod driver;
|
||||
mod firmware;
|
||||
mod gpu;
|
||||
mod regs;
|
||||
mod util;
|
||||
|
||||
kernel::module_pci_driver! {
|
||||
type: driver::NovaCore,
|
||||
name: "NovaCore",
|
||||
author: "Danilo Krummrich",
|
||||
description: "Nova Core GPU driver",
|
||||
license: "GPL v2",
|
||||
firmware: [],
|
||||
}
|
||||
|
||||
kernel::module_firmware!(firmware::ModInfoBuilder);
|
55
drivers/gpu/nova-core/regs.rs
Normal file
55
drivers/gpu/nova-core/regs.rs
Normal file
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use crate::driver::Bar0;
|
||||
|
||||
// TODO
|
||||
//
|
||||
// Create register definitions via generic macros. See task "Generic register
|
||||
// abstraction" in Documentation/gpu/nova/core/todo.rst.
|
||||
|
||||
const BOOT0_OFFSET: usize = 0x00000000;
|
||||
|
||||
// 3:0 - chipset minor revision
|
||||
const BOOT0_MINOR_REV_SHIFT: u8 = 0;
|
||||
const BOOT0_MINOR_REV_MASK: u32 = 0x0000000f;
|
||||
|
||||
// 7:4 - chipset major revision
|
||||
const BOOT0_MAJOR_REV_SHIFT: u8 = 4;
|
||||
const BOOT0_MAJOR_REV_MASK: u32 = 0x000000f0;
|
||||
|
||||
// 23:20 - chipset implementation Identifier (depends on architecture)
|
||||
const BOOT0_IMPL_SHIFT: u8 = 20;
|
||||
const BOOT0_IMPL_MASK: u32 = 0x00f00000;
|
||||
|
||||
// 28:24 - chipset architecture identifier
|
||||
const BOOT0_ARCH_MASK: u32 = 0x1f000000;
|
||||
|
||||
// 28:20 - chipset identifier (virtual register field combining BOOT0_IMPL and
|
||||
// BOOT0_ARCH)
|
||||
const BOOT0_CHIPSET_SHIFT: u8 = BOOT0_IMPL_SHIFT;
|
||||
const BOOT0_CHIPSET_MASK: u32 = BOOT0_IMPL_MASK | BOOT0_ARCH_MASK;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct Boot0(u32);
|
||||
|
||||
impl Boot0 {
|
||||
#[inline]
|
||||
pub(crate) fn read(bar: &Bar0) -> Self {
|
||||
Self(bar.readl(BOOT0_OFFSET))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn chipset(&self) -> u32 {
|
||||
(self.0 & BOOT0_CHIPSET_MASK) >> BOOT0_CHIPSET_SHIFT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn minor_rev(&self) -> u8 {
|
||||
((self.0 & BOOT0_MINOR_REV_MASK) >> BOOT0_MINOR_REV_SHIFT) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn major_rev(&self) -> u8 {
|
||||
((self.0 & BOOT0_MAJOR_REV_MASK) >> BOOT0_MAJOR_REV_SHIFT) as u8
|
||||
}
|
||||
}
|
21
drivers/gpu/nova-core/util.rs
Normal file
21
drivers/gpu/nova-core/util.rs
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
pub(crate) const fn to_lowercase_bytes<const N: usize>(s: &str) -> [u8; N] {
|
||||
let src = s.as_bytes();
|
||||
let mut dst = [0; N];
|
||||
let mut i = 0;
|
||||
|
||||
while i < src.len() && i < N {
|
||||
dst[i] = (src[i] as char).to_ascii_lowercase() as u8;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
dst
|
||||
}
|
||||
|
||||
pub(crate) const fn const_bytes_to_str(bytes: &[u8]) -> &str {
|
||||
match core::str::from_utf8(bytes) {
|
||||
Ok(string) => string,
|
||||
Err(_) => kernel::build_error!("Bytes are not valid UTF-8."),
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ source "drivers/gpu/vga/Kconfig"
|
||||
|
||||
source "drivers/gpu/host1x/Kconfig"
|
||||
source "drivers/gpu/ipu-v3/Kconfig"
|
||||
source "drivers/gpu/nova-core/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/Kconfig"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user