Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+416
+10
MAINTAINERS
··· 7449 7449 F: drivers/gpu/drm/nouveau/ 7450 7450 F: include/uapi/drm/nouveau_drm.h 7451 7451 7452 + CORE DRIVER FOR NVIDIA GPUS [RUST] 7453 + M: Danilo Krummrich <dakr@kernel.org> 7454 + L: nouveau@lists.freedesktop.org 7455 + S: Supported 7456 + Q: https://patchwork.freedesktop.org/project/nouveau/ 7457 + B: https://gitlab.freedesktop.org/drm/nova/-/issues 7458 + C: irc://irc.oftc.net/nouveau 7459 + T: git https://gitlab.freedesktop.org/drm/nova.git nova-next 7460 + F: drivers/gpu/nova-core/ 7461 + 7452 7462 DRM DRIVER FOR OLIMEX LCD-OLINUXINO PANELS 7453 7463 M: Stefan Mavrodiev <stefan@olimex.com> 7454 7464 S: Maintained
+1
drivers/gpu/Makefile
··· 5 5 obj-y += host1x/ drm/ vga/ 6 6 obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ 7 7 obj-$(CONFIG_TRACE_GPU_MEM) += trace/ 8 + obj-$(CONFIG_NOVA_CORE) += nova-core/
+14
drivers/gpu/nova-core/Kconfig
··· 1 + config NOVA_CORE 2 + tristate "Nova Core GPU driver" 3 + depends on PCI 4 + depends on RUST 5 + depends on RUST_FW_LOADER_ABSTRACTIONS 6 + default n 7 + help 8 + Choose this if you want to build the Nova Core driver for Nvidia 9 + GPUs based on the GPU System Processor (GSP). This is true for Turing 10 + and later GPUs. 11 + 12 + This driver is work in progress and may not be functional. 13 + 14 + If M is selected, the module will be called nova_core.
+3
drivers/gpu/nova-core/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + obj-$(CONFIG_NOVA_CORE) += nova_core.o
+47
drivers/gpu/nova-core/driver.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use kernel::{bindings, c_str, pci, prelude::*}; 4 + 5 + use crate::gpu::Gpu; 6 + 7 + #[pin_data] 8 + pub(crate) struct NovaCore { 9 + #[pin] 10 + pub(crate) gpu: Gpu, 11 + } 12 + 13 + const BAR0_SIZE: usize = 8; 14 + pub(crate) type Bar0 = pci::Bar<BAR0_SIZE>; 15 + 16 + kernel::pci_device_table!( 17 + PCI_TABLE, 18 + MODULE_PCI_TABLE, 19 + <NovaCore as pci::Driver>::IdInfo, 20 + [( 21 + pci::DeviceId::from_id(bindings::PCI_VENDOR_ID_NVIDIA, bindings::PCI_ANY_ID as _), 22 + () 23 + )] 24 + ); 25 + 26 + impl pci::Driver for NovaCore { 27 + type IdInfo = (); 28 + const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE; 29 + 30 + fn probe(pdev: &mut pci::Device, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> { 31 + dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n"); 32 + 33 + pdev.enable_device_mem()?; 34 + pdev.set_master(); 35 + 36 + let bar = pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?; 37 + 38 + let this = KBox::pin_init( 39 + try_pin_init!(Self { 40 + gpu <- Gpu::new(pdev, bar)?, 41 + }), 42 + GFP_KERNEL, 43 + )?; 44 + 45 + Ok(this) 46 + } 47 + }
+45
drivers/gpu/nova-core/firmware.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use crate::gpu; 4 + use kernel::firmware; 5 + 6 + pub(crate) struct ModInfoBuilder<const N: usize>(firmware::ModInfoBuilder<N>); 7 + 8 + impl<const N: usize> ModInfoBuilder<N> { 9 + const VERSION: &'static str = "535.113.01"; 10 + 11 + const fn make_entry_file(self, chipset: &str, fw: &str) -> Self { 12 + ModInfoBuilder( 13 + self.0 14 + .new_entry() 15 + .push("nvidia/") 16 + .push(chipset) 17 + .push("/gsp/") 18 + .push(fw) 19 + .push("-") 20 + .push(Self::VERSION) 21 + .push(".bin"), 22 + ) 23 + } 24 + 25 + const fn make_entry_chipset(self, chipset: &str) -> Self { 26 + self.make_entry_file(chipset, "booter_load") 27 + .make_entry_file(chipset, "booter_unload") 28 + .make_entry_file(chipset, "bootloader") 29 + .make_entry_file(chipset, "gsp") 30 + } 31 + 32 + pub(crate) const fn create( 33 + module_name: &'static kernel::str::CStr, 34 + ) -> firmware::ModInfoBuilder<N> { 35 + let mut this = Self(firmware::ModInfoBuilder::new(module_name)); 36 + let mut i = 0; 37 + 38 + while i < gpu::Chipset::NAMES.len() { 39 + this = this.make_entry_chipset(gpu::Chipset::NAMES[i]); 40 + i += 1; 41 + } 42 + 43 + this.0 44 + } 45 + }
+199
drivers/gpu/nova-core/gpu.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use kernel::{ 4 + device, devres::Devres, error::code::*, firmware, fmt, pci, prelude::*, str::CString, 5 + }; 6 + 7 + use crate::driver::Bar0; 8 + use crate::regs; 9 + use crate::util; 10 + use core::fmt; 11 + 12 + macro_rules! define_chipset { 13 + ({ $($variant:ident = $value:expr),* $(,)* }) => 14 + { 15 + /// Enum representation of the GPU chipset. 16 + #[derive(fmt::Debug)] 17 + pub(crate) enum Chipset { 18 + $($variant = $value),*, 19 + } 20 + 21 + impl Chipset { 22 + pub(crate) const ALL: &'static [Chipset] = &[ 23 + $( Chipset::$variant, )* 24 + ]; 25 + 26 + pub(crate) const NAMES: [&'static str; Self::ALL.len()] = [ 27 + $( util::const_bytes_to_str( 28 + util::to_lowercase_bytes::<{ stringify!($variant).len() }>( 29 + stringify!($variant) 30 + ).as_slice() 31 + ), )* 32 + ]; 33 + } 34 + 35 + // TODO replace with something like derive(FromPrimitive) 36 + impl TryFrom<u32> for Chipset { 37 + type Error = kernel::error::Error; 38 + 39 + fn try_from(value: u32) -> Result<Self, Self::Error> { 40 + match value { 41 + $( $value => Ok(Chipset::$variant), )* 42 + _ => Err(ENODEV), 43 + } 44 + } 45 + } 46 + } 47 + } 48 + 49 + define_chipset!({ 50 + // Turing 51 + TU102 = 0x162, 52 + TU104 = 0x164, 53 + TU106 = 0x166, 54 + TU117 = 0x167, 55 + TU116 = 0x168, 56 + // Ampere 57 + GA102 = 0x172, 58 + GA103 = 0x173, 59 + GA104 = 0x174, 60 + GA106 = 0x176, 61 + GA107 = 0x177, 62 + // Ada 63 + AD102 = 0x192, 64 + AD103 = 0x193, 65 + AD104 = 0x194, 66 + AD106 = 0x196, 67 + AD107 = 0x197, 68 + }); 69 + 70 + impl Chipset { 71 + pub(crate) fn arch(&self) -> Architecture { 72 + match self { 73 + Self::TU102 | Self::TU104 | Self::TU106 | Self::TU117 | Self::TU116 => { 74 + Architecture::Turing 75 + } 76 + Self::GA102 | Self::GA103 | Self::GA104 | Self::GA106 | Self::GA107 => { 77 + Architecture::Ampere 78 + } 79 + Self::AD102 | Self::AD103 | Self::AD104 | Self::AD106 | Self::AD107 => { 80 + Architecture::Ada 81 + } 82 + } 83 + } 84 + } 85 + 86 + // TODO 87 + // 88 + // The resulting strings are used to generate firmware paths, hence the 89 + // generated strings have to be stable. 90 + // 91 + // Hence, replace with something like strum_macros derive(Display). 92 + // 93 + // For now, redirect to fmt::Debug for convenience. 94 + impl fmt::Display for Chipset { 95 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 96 + write!(f, "{:?}", self) 97 + } 98 + } 99 + 100 + /// Enum representation of the GPU generation. 101 + #[derive(fmt::Debug)] 102 + pub(crate) enum Architecture { 103 + Turing, 104 + Ampere, 105 + Ada, 106 + } 107 + 108 + pub(crate) struct Revision { 109 + major: u8, 110 + minor: u8, 111 + } 112 + 113 + impl Revision { 114 + fn from_boot0(boot0: regs::Boot0) -> Self { 115 + Self { 116 + major: boot0.major_rev(), 117 + minor: boot0.minor_rev(), 118 + } 119 + } 120 + } 121 + 122 + impl fmt::Display for Revision { 123 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 124 + write!(f, "{:x}.{:x}", self.major, self.minor) 125 + } 126 + } 127 + 128 + /// Structure holding the metadata of the GPU. 129 + pub(crate) struct Spec { 130 + chipset: Chipset, 131 + /// The revision of the chipset. 132 + revision: Revision, 133 + } 134 + 135 + impl Spec { 136 + fn new(bar: &Devres<Bar0>) -> Result<Spec> { 137 + let bar = bar.try_access().ok_or(ENXIO)?; 138 + let boot0 = regs::Boot0::read(&bar); 139 + 140 + Ok(Self { 141 + chipset: boot0.chipset().try_into()?, 142 + revision: Revision::from_boot0(boot0), 143 + }) 144 + } 145 + } 146 + 147 + /// Structure encapsulating the firmware blobs required for the GPU to operate. 148 + #[expect(dead_code)] 149 + pub(crate) struct Firmware { 150 + booter_load: firmware::Firmware, 151 + booter_unload: firmware::Firmware, 152 + bootloader: firmware::Firmware, 153 + gsp: firmware::Firmware, 154 + } 155 + 156 + impl Firmware { 157 + fn new(dev: &device::Device, spec: &Spec, ver: &str) -> Result<Firmware> { 158 + let mut chip_name = CString::try_from_fmt(fmt!("{}", spec.chipset))?; 159 + chip_name.make_ascii_lowercase(); 160 + 161 + let request = |name_| { 162 + CString::try_from_fmt(fmt!("nvidia/{}/gsp/{}-{}.bin", &*chip_name, name_, ver)) 163 + .and_then(|path| firmware::Firmware::request(&path, dev)) 164 + }; 165 + 166 + Ok(Firmware { 167 + booter_load: request("booter_load")?, 168 + booter_unload: request("booter_unload")?, 169 + bootloader: request("bootloader")?, 170 + gsp: request("gsp")?, 171 + }) 172 + } 173 + } 174 + 175 + /// Structure holding the resources required to operate the GPU. 176 + #[pin_data] 177 + pub(crate) struct Gpu { 178 + spec: Spec, 179 + /// MMIO mapping of PCI BAR 0 180 + bar: Devres<Bar0>, 181 + fw: Firmware, 182 + } 183 + 184 + impl Gpu { 185 + pub(crate) fn new(pdev: &pci::Device, bar: Devres<Bar0>) -> Result<impl PinInit<Self>> { 186 + let spec = Spec::new(&bar)?; 187 + let fw = Firmware::new(pdev.as_ref(), &spec, "535.113.01")?; 188 + 189 + dev_info!( 190 + pdev.as_ref(), 191 + "NVIDIA (Chipset: {}, Architecture: {:?}, Revision: {})\n", 192 + spec.chipset, 193 + spec.chipset.arch(), 194 + spec.revision 195 + ); 196 + 197 + Ok(pin_init!(Self { spec, bar, fw })) 198 + } 199 + }
+20
drivers/gpu/nova-core/nova_core.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + //! Nova Core GPU Driver 4 + 5 + mod driver; 6 + mod firmware; 7 + mod gpu; 8 + mod regs; 9 + mod util; 10 + 11 + kernel::module_pci_driver! { 12 + type: driver::NovaCore, 13 + name: "NovaCore", 14 + author: "Danilo Krummrich", 15 + description: "Nova Core GPU driver", 16 + license: "GPL v2", 17 + firmware: [], 18 + } 19 + 20 + kernel::module_firmware!(firmware::ModInfoBuilder);
+55
drivers/gpu/nova-core/regs.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use crate::driver::Bar0; 4 + 5 + // TODO 6 + // 7 + // Create register definitions via generic macros. See task "Generic register 8 + // abstraction" in Documentation/gpu/nova/core/todo.rst. 9 + 10 + const BOOT0_OFFSET: usize = 0x00000000; 11 + 12 + // 3:0 - chipset minor revision 13 + const BOOT0_MINOR_REV_SHIFT: u8 = 0; 14 + const BOOT0_MINOR_REV_MASK: u32 = 0x0000000f; 15 + 16 + // 7:4 - chipset major revision 17 + const BOOT0_MAJOR_REV_SHIFT: u8 = 4; 18 + const BOOT0_MAJOR_REV_MASK: u32 = 0x000000f0; 19 + 20 + // 23:20 - chipset implementation Identifier (depends on architecture) 21 + const BOOT0_IMPL_SHIFT: u8 = 20; 22 + const BOOT0_IMPL_MASK: u32 = 0x00f00000; 23 + 24 + // 28:24 - chipset architecture identifier 25 + const BOOT0_ARCH_MASK: u32 = 0x1f000000; 26 + 27 + // 28:20 - chipset identifier (virtual register field combining BOOT0_IMPL and 28 + // BOOT0_ARCH) 29 + const BOOT0_CHIPSET_SHIFT: u8 = BOOT0_IMPL_SHIFT; 30 + const BOOT0_CHIPSET_MASK: u32 = BOOT0_IMPL_MASK | BOOT0_ARCH_MASK; 31 + 32 + #[derive(Copy, Clone)] 33 + pub(crate) struct Boot0(u32); 34 + 35 + impl Boot0 { 36 + #[inline] 37 + pub(crate) fn read(bar: &Bar0) -> Self { 38 + Self(bar.readl(BOOT0_OFFSET)) 39 + } 40 + 41 + #[inline] 42 + pub(crate) fn chipset(&self) -> u32 { 43 + (self.0 & BOOT0_CHIPSET_MASK) >> BOOT0_CHIPSET_SHIFT 44 + } 45 + 46 + #[inline] 47 + pub(crate) fn minor_rev(&self) -> u8 { 48 + ((self.0 & BOOT0_MINOR_REV_MASK) >> BOOT0_MINOR_REV_SHIFT) as u8 49 + } 50 + 51 + #[inline] 52 + pub(crate) fn major_rev(&self) -> u8 { 53 + ((self.0 & BOOT0_MAJOR_REV_MASK) >> BOOT0_MAJOR_REV_SHIFT) as u8 54 + } 55 + }
+21
drivers/gpu/nova-core/util.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + pub(crate) const fn to_lowercase_bytes<const N: usize>(s: &str) -> [u8; N] { 4 + let src = s.as_bytes(); 5 + let mut dst = [0; N]; 6 + let mut i = 0; 7 + 8 + while i < src.len() && i < N { 9 + dst[i] = (src[i] as char).to_ascii_lowercase() as u8; 10 + i += 1; 11 + } 12 + 13 + dst 14 + } 15 + 16 + pub(crate) const fn const_bytes_to_str(bytes: &[u8]) -> &str { 17 + match core::str::from_utf8(bytes) { 18 + Ok(string) => string, 19 + Err(_) => kernel::build_error!("Bytes are not valid UTF-8."), 20 + } 21 + }
+1
drivers/video/Kconfig
··· 39 39 40 40 source "drivers/gpu/host1x/Kconfig" 41 41 source "drivers/gpu/ipu-v3/Kconfig" 42 + source "drivers/gpu/nova-core/Kconfig" 42 43 43 44 source "drivers/gpu/drm/Kconfig" 44 45