Nova changes for v6.15

nova-core:
   - initial skeleton driver
   - documentation
     - project guidelines
     - task (todo) list
 
 firmware:
   - `module_firmware!` macro
   - `firmware::ModInfoBuilder`
 
 Rust:
   - `LocalModule` type alias
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQS2q/xV6QjXAdC7k+1FlHeO1qrKLgUCZ84SdgAKCRBFlHeO1qrK
 Lgp6AQCMx43G1praGv+BJZn8FG0l/VsVYeRTZf7vCT1EWyN0ogEAm2sGwqHP0977
 3KghBZ8SeIPabuLP84Dw0TjBuPWTGgw=
 =IVPn
 -----END PGP SIGNATURE-----

Merge tag 'nova-next-6.15-2025-03-09' of gitlab.freedesktop.org:drm/nova into drm-next

Nova changes for v6.15

nova-core:
  - initial skeleton driver
  - documentation
    - project guidelines
    - task (todo) list

firmware:
  - `module_firmware!` macro
  - `firmware::ModInfoBuilder`

Rust:
  - `LocalModule` type alias
Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Danilo Krummrich <dakr@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/Z84dHHEn6xfvlRxk@cassiopeiae
This commit is contained in:
Dave Airlie 2025-03-13 06:03:43 +10:00
commit 4e64a62032
18 changed files with 1207 additions and 0 deletions

View File

@ -25,6 +25,7 @@ GPU Driver Documentation
panfrost
panthor
zynqmp
nova/index
.. only:: subproject and html

View File

@ -0,0 +1,24 @@
.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
==========
Guidelines
==========
This documents contains the guidelines for nova-core. Additionally, all common
guidelines of the Nova project do apply.
Driver API
==========
One main purpose of nova-core is to implement the abstraction around the
firmware interface of GSP and provide a firmware (version) independent API for
2nd level drivers, such as nova-drm or the vGPU manager VFIO driver.
Therefore, it is not permitted to leak firmware (version) specifics, through the
driver API, to 2nd level drivers.
Acceptance Criteria
===================
- To the extend possible, patches submitted to nova-core must be tested for
regressions with all 2nd level drivers.

View File

@ -0,0 +1,446 @@
.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
=========
Task List
=========
Tasks may have the following fields:
- ``Complexity``: Describes the required familiarity with Rust and / or the
corresponding kernel APIs or subsystems. There are four different complexities,
``Beginner``, ``Intermediate``, ``Advanced`` and ``Expert``.
- ``Reference``: References to other tasks.
- ``Link``: Links to external resources.
- ``Contact``: The person that can be contacted for further information about
the task.
Enablement (Rust)
=================
Tasks that are not directly related to nova-core, but are preconditions in terms
of required APIs.
FromPrimitive API
-----------------
Sometimes the need arises to convert a number to a value of an enum or a
structure.
A good example from nova-core would be the ``Chipset`` enum type, which defines
the value ``AD102``. When probing the GPU the value ``0x192`` can be read from a
certain register indication the chipset AD102. Hence, the enum value ``AD102``
should be derived from the number ``0x192``. Currently, nova-core uses a custom
implementation (``Chipset::from_u32`` for this.
Instead, it would be desirable to have something like the ``FromPrimitive``
trait [1] from the num crate.
Having this generalization also helps with implementing a generic macro that
automatically generates the corresponding mappings between a value and a number.
| Complexity: Beginner
| Link: https://docs.rs/num/latest/num/trait.FromPrimitive.html
Generic register abstraction
----------------------------
Work out how register constants and structures can be automatically generated
through generalized macros.
Example:
.. code-block:: rust
register!(BOOT0, 0x0, u32, pci::Bar<SIZE>, Fields [
MINOR_REVISION(3:0, RO),
MAJOR_REVISION(7:4, RO),
REVISION(7:0, RO), // Virtual register combining major and minor rev.
])
This could expand to something like:
.. code-block:: rust
const BOOT0_OFFSET: usize = 0x00000000;
const BOOT0_MINOR_REVISION_SHIFT: u8 = 0;
const BOOT0_MINOR_REVISION_MASK: u32 = 0x0000000f;
const BOOT0_MAJOR_REVISION_SHIFT: u8 = 4;
const BOOT0_MAJOR_REVISION_MASK: u32 = 0x000000f0;
const BOOT0_REVISION_SHIFT: u8 = BOOT0_MINOR_REVISION_SHIFT;
const BOOT0_REVISION_MASK: u32 = BOOT0_MINOR_REVISION_MASK | BOOT0_MAJOR_REVISION_MASK;
struct Boot0(u32);
impl Boot0 {
#[inline]
fn read(bar: &RevocableGuard<'_, pci::Bar<SIZE>>) -> Self {
Self(bar.readl(BOOT0_OFFSET))
}
#[inline]
fn minor_revision(&self) -> u32 {
(self.0 & BOOT0_MINOR_REVISION_MASK) >> BOOT0_MINOR_REVISION_SHIFT
}
#[inline]
fn major_revision(&self) -> u32 {
(self.0 & BOOT0_MAJOR_REVISION_MASK) >> BOOT0_MAJOR_REVISION_SHIFT
}
#[inline]
fn revision(&self) -> u32 {
(self.0 & BOOT0_REVISION_MASK) >> BOOT0_REVISION_SHIFT
}
}
Usage:
.. code-block:: rust
let bar = bar.try_access().ok_or(ENXIO)?;
let boot0 = Boot0::read(&bar);
pr_info!("Revision: {}\n", boot0.revision());
| Complexity: Advanced
Delay / Sleep abstractions
--------------------------
Rust abstractions for the kernel's delay() and sleep() functions.
FUJITA Tomonori plans to work on abstractions for read_poll_timeout_atomic()
(and friends) [1].
| Complexity: Beginner
| Link: https://lore.kernel.org/netdev/20250228.080550.354359820929821928.fujita.tomonori@gmail.com/ [1]
IRQ abstractions
----------------
Rust abstractions for IRQ handling.
There is active ongoing work from Daniel Almeida [1] for the "core" abstractions
to request IRQs.
Besides optional review and testing work, the required ``pci::Device`` code
around those core abstractions needs to be worked out.
| Complexity: Intermediate
| Link: https://lore.kernel.org/lkml/20250122163932.46697-1-daniel.almeida@collabora.com/ [1]
| Contact: Daniel Almeida
Page abstraction for foreign pages
----------------------------------
Rust abstractions for pages not created by the Rust page abstraction without
direct ownership.
There is active onging work from Abdiel Janulgue [1] and Lina [2].
| Complexity: Advanced
| Link: https://lore.kernel.org/linux-mm/20241119112408.779243-1-abdiel.janulgue@gmail.com/ [1]
| Link: https://lore.kernel.org/rust-for-linux/20250202-rust-page-v1-0-e3170d7fe55e@asahilina.net/ [2]
Scatterlist / sg_table abstractions
-----------------------------------
Rust abstractions for scatterlist / sg_table.
There is preceding work from Abdiel Janulgue, which hasn't made it to the
mailing list yet.
| Complexity: Intermediate
| Contact: Abdiel Janulgue
ELF utils
---------
Rust implementation of ELF header representation to retrieve section header
tables, names, and data from an ELF-formatted images.
There is preceding work from Abdiel Janulgue, which hasn't made it to the
mailing list yet.
| Complexity: Beginner
| Contact: Abdiel Janulgue
PCI MISC APIs
-------------
Extend the existing PCI device / driver abstractions by SR-IOV, config space,
capability, MSI API abstractions.
| Complexity: Beginner
Auxiliary bus abstractions
--------------------------
Rust abstraction for the auxiliary bus APIs.
This is needed to connect nova-core to the nova-drm driver.
| Complexity: Intermediate
Debugfs abstractions
--------------------
Rust abstraction for debugfs APIs.
| Reference: Export GSP log buffers
| Complexity: Intermediate
Vec extensions
--------------
Implement ``Vec::truncate`` and ``Vec::resize``.
Currently this is used for some experimental code to parse the vBIOS.
| Reference vBIOS support
| Complexity: Beginner
GPU (general)
=============
Parse firmware headers
----------------------
Parse ELF headers from the firmware files loaded from the filesystem.
| Reference: ELF utils
| Complexity: Beginner
| Contact: Abdiel Janulgue
Build radix3 page table
-----------------------
Build the radix3 page table to map the firmware.
| Complexity: Intermediate
| Contact: Abdiel Janulgue
vBIOS support
-------------
Parse the vBIOS and probe the structures required for driver initialization.
| Contact: Dave Airlie
| Reference: Vec extensions
| Complexity: Intermediate
Initial Devinit support
-----------------------
Implement BIOS Device Initialization, i.e. memory sizing, waiting, PLL
configuration.
| Contact: Dave Airlie
| Complexity: Beginner
Boot Falcon controller
----------------------
Infrastructure to load and execute falcon (sec2) firmware images; handle the
GSP falcon processor and fwsec loading.
| Complexity: Advanced
| Contact: Dave Airlie
GPU Timer support
-----------------
Support for the GPU's internal timer peripheral.
| Complexity: Beginner
| Contact: Dave Airlie
MMU / PT management
-------------------
Work out the architecture for MMU / page table management.
We need to consider that nova-drm will need rather fine-grained control,
especially in terms of locking, in order to be able to implement asynchronous
Vulkan queues.
While generally sharing the corresponding code is desirable, it needs to be
evaluated how (and if at all) sharing the corresponding code is expedient.
| Complexity: Expert
VRAM memory allocator
---------------------
Investigate options for a VRAM memory allocator.
Some possible options:
- Rust abstractions for
- RB tree (interval tree) / drm_mm
- maple_tree
- native Rust collections
| Complexity: Advanced
Instance Memory
---------------
Implement support for instmem (bar2) used to store page tables.
| Complexity: Intermediate
| Contact: Dave Airlie
GPU System Processor (GSP)
==========================
Export GSP log buffers
----------------------
Recent patches from Timur Tabi [1] added support to expose GSP-RM log buffers
(even after failure to probe the driver) through debugfs.
This is also an interesting feature for nova-core, especially in the early days.
| Link: https://lore.kernel.org/nouveau/20241030202952.694055-2-ttabi@nvidia.com/ [1]
| Reference: Debugfs abstractions
| Complexity: Intermediate
GSP firmware abstraction
------------------------
The GSP-RM firmware API is unstable and may incompatibly change from version to
version, in terms of data structures and semantics.
This problem is one of the big motivations for using Rust for nova-core, since
it turns out that Rust's procedural macro feature provides a rather elegant way
to address this issue:
1. generate Rust structures from the C headers in a separate namespace per version
2. build abstraction structures (within a generic namespace) that implement the
firmware interfaces; annotate the differences in implementation with version
identifiers
3. use a procedural macro to generate the actual per version implementation out
of this abstraction
4. instantiate the correct version type one on runtime (can be sure that all
have the same interface because it's defined by a common trait)
There is a PoC implementation of this pattern, in the context of the nova-core
PoC driver.
This task aims at refining the feature and ideally generalize it, to be usable
by other drivers as well.
| Complexity: Expert
GSP message queue
-----------------
Implement low level GSP message queue (command, status) for communication
between the kernel driver and GSP.
| Complexity: Advanced
| Contact: Dave Airlie
Bootstrap GSP
-------------
Call the boot firmware to boot the GSP processor; execute initial control
messages.
| Complexity: Intermediate
| Contact: Dave Airlie
Client / Device APIs
--------------------
Implement the GSP message interface for client / device allocation and the
corresponding client and device allocation APIs.
| Complexity: Intermediate
| Contact: Dave Airlie
Bar PDE handling
----------------
Synchronize page table handling for BARs between the kernel driver and GSP.
| Complexity: Beginner
| Contact: Dave Airlie
FIFO engine
-----------
Implement support for the FIFO engine, i.e. the corresponding GSP message
interface and provide an API for chid allocation and channel handling.
| Complexity: Advanced
| Contact: Dave Airlie
GR engine
---------
Implement support for the graphics engine, i.e. the corresponding GSP message
interface and provide an API for (golden) context creation and promotion.
| Complexity: Advanced
| Contact: Dave Airlie
CE engine
---------
Implement support for the copy engine, i.e. the corresponding GSP message
interface.
| Complexity: Intermediate
| Contact: Dave Airlie
VFN IRQ controller
------------------
Support for the VFN interrupt controller.
| Complexity: Intermediate
| Contact: Dave Airlie
External APIs
=============
nova-core base API
------------------
Work out the common pieces of the API to connect 2nd level drivers, i.e. vGPU
manager and nova-drm.
| Complexity: Advanced
vGPU manager API
----------------
Work out the API parts required by the vGPU manager, which are not covered by
the base API.
| Complexity: Advanced
nova-core C API
---------------
Implement a C wrapper for the APIs required by the vGPU manager driver.
| Complexity: Intermediate
Testing
=======
CI pipeline
-----------
Investigate option for continuous integration testing.
This can go from as simple as running KUnit tests over running (graphics) CTS to
booting up (multiple) guest VMs to test VFIO use-cases.
It might also be worth to consider the introduction of a new test suite directly
sitting on top of the uAPI for more targeted testing and debugging. There may be
options for collaboration / shared code with the Mesa project.
| Complexity: Advanced

View File

@ -0,0 +1,69 @@
.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
==========
Guidelines
==========
This document describes the general project guidelines that apply to nova-core
and nova-drm.
Language
========
The Nova project uses the Rust programming language. In this context, all rules
of the Rust for Linux project as documented in
:doc:`../../rust/general-information` apply. Additionally, the following rules
apply.
- Unless technically necessary otherwise (e.g. uAPI), any driver code is written
in Rust.
- Unless technically necessary, unsafe Rust code must be avoided. In case of
technical necessity, unsafe code should be isolated in a separate component
providing a safe API for other driver code to use.
Style
-----
All rules of the Rust for Linux project as documented in
:doc:`../../rust/coding-guidelines` apply.
For a submit checklist, please also see the `Rust for Linux Submit checklist
addendum <https://rust-for-linux.com/contributing#submit-checklist-addendum>`_.
Documentation
=============
The availability of proper documentation is essential in terms of scalability,
accessibility for new contributors and maintainability of a project in general,
but especially for a driver running as complex hardware as Nova is targeting.
Hence, adding documentation of any kind is very much encouraged by the project.
Besides that, there are some minimum requirements.
- Every non-private structure needs at least a brief doc comment explaining the
semantical sense of the structure, as well as potential locking and lifetime
requirements. It is encouraged to have the same minimum documentation for
non-trivial private structures.
- uAPIs must be fully documented with kernel-doc comments; additionally, the
semantical behavior must be explained including potential special or corner
cases.
- The APIs connecting the 1st level driver (nova-core) with 2nd level drivers
must be fully documented. This includes doc comments, potential locking and
lifetime requirements, as well as example code if applicable.
- Abbreviations must be explained when introduced; terminology must be uniquely
defined.
- Register addresses, layouts, shift values and masks must be defined properly;
unless obvious, the semantical sense must be documented. This only applies if
the author is able to obtain the corresponding information.
Acceptance Criteria
===================
- Patches must only be applied if reviewed by at least one other person on the
mailing list; this also applies for maintainers.

View File

@ -0,0 +1,30 @@
.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
=======================
nova NVIDIA GPU drivers
=======================
The nova driver project consists out of two separate drivers nova-core and
nova-drm and intends to supersede the nouveau driver for NVIDIA GPUs based on
the GPU System Processor (GSP).
The following documents apply to both nova-core and nova-drm.
.. toctree::
:titlesonly:
guidelines
nova-core
=========
The nova-core driver is the core driver for NVIDIA GPUs based on GSP. nova-core,
as the 1st level driver, provides an abstraction around the GPUs hard- and
firmware interfaces providing a common base for 2nd level drivers, such as the
vGPU manager VFIO driver and the nova-drm driver.
.. toctree::
:titlesonly:
core/guidelines
core/todo

View File

@ -7457,6 +7457,17 @@ 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: Documentation/gpu/nova/
F: drivers/gpu/nova-core/
DRM DRIVER FOR OLIMEX LCD-OLINUXINO PANELS
M: Stefan Mavrodiev <stefan@olimex.com>
S: Maintained

View File

@ -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/

View 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.

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NOVA_CORE) += nova_core.o

View 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)
}
}

View 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
}
}

View 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 }))
}
}

View 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);

View 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
}
}

View 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."),
}
}

View File

@ -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"

View File

@ -115,3 +115,219 @@ unsafe impl Send for Firmware {}
// SAFETY: `Firmware` only holds a pointer to a C `struct firmware`, references to which are safe to
// be used from any thread.
unsafe impl Sync for Firmware {}
/// Create firmware .modinfo entries.
///
/// This macro is the counterpart of the C macro `MODULE_FIRMWARE()`, but instead of taking a
/// simple string literals, which is already covered by the `firmware` field of
/// [`crate::prelude::module!`], it allows the caller to pass a builder type, based on the
/// [`ModInfoBuilder`], which can create the firmware modinfo strings in a more flexible way.
///
/// Drivers should extend the [`ModInfoBuilder`] with their own driver specific builder type.
///
/// The `builder` argument must be a type which implements the following function.
///
/// `const fn create(module_name: &'static CStr) -> ModInfoBuilder`
///
/// `create` should pass the `module_name` to the [`ModInfoBuilder`] and, with the help of
/// it construct the corresponding firmware modinfo.
///
/// Typically, such contracts would be enforced by a trait, however traits do not (yet) support
/// const functions.
///
/// # Example
///
/// ```
/// # mod module_firmware_test {
/// # use kernel::firmware;
/// # use kernel::prelude::*;
/// #
/// # struct MyModule;
/// #
/// # impl kernel::Module for MyModule {
/// # fn init(_module: &'static ThisModule) -> Result<Self> {
/// # Ok(Self)
/// # }
/// # }
/// #
/// #
/// struct Builder<const N: usize>;
///
/// impl<const N: usize> Builder<N> {
/// const DIR: &'static str = "vendor/chip/";
/// const FILES: [&'static str; 3] = [ "foo", "bar", "baz" ];
///
/// const fn create(module_name: &'static kernel::str::CStr) -> firmware::ModInfoBuilder<N> {
/// let mut builder = firmware::ModInfoBuilder::new(module_name);
///
/// let mut i = 0;
/// while i < Self::FILES.len() {
/// builder = builder.new_entry()
/// .push(Self::DIR)
/// .push(Self::FILES[i])
/// .push(".bin");
///
/// i += 1;
/// }
///
/// builder
/// }
/// }
///
/// module! {
/// type: MyModule,
/// name: "module_firmware_test",
/// author: "Rust for Linux",
/// description: "module_firmware! test module",
/// license: "GPL",
/// }
///
/// kernel::module_firmware!(Builder);
/// # }
/// ```
#[macro_export]
macro_rules! module_firmware {
// The argument is the builder type without the const generic, since it's deferred from within
// this macro. Hence, we can neither use `expr` nor `ty`.
($($builder:tt)*) => {
const _: () = {
const __MODULE_FIRMWARE_PREFIX: &'static $crate::str::CStr = if cfg!(MODULE) {
$crate::c_str!("")
} else {
<LocalModule as $crate::ModuleMetadata>::NAME
};
#[link_section = ".modinfo"]
#[used]
static __MODULE_FIRMWARE: [u8; $($builder)*::create(__MODULE_FIRMWARE_PREFIX)
.build_length()] = $($builder)*::create(__MODULE_FIRMWARE_PREFIX).build();
};
};
}
/// Builder for firmware module info.
///
/// [`ModInfoBuilder`] is a helper component to flexibly compose firmware paths strings for the
/// .modinfo section in const context.
///
/// Therefore the [`ModInfoBuilder`] provides the methods [`ModInfoBuilder::new_entry`] and
/// [`ModInfoBuilder::push`], where the latter is used to push path components and the former to
/// mark the beginning of a new path string.
///
/// [`ModInfoBuilder`] is meant to be used in combination with [`kernel::module_firmware!`].
///
/// The const generic `N` as well as the `module_name` parameter of [`ModInfoBuilder::new`] is an
/// internal implementation detail and supplied through the above macro.
pub struct ModInfoBuilder<const N: usize> {
buf: [u8; N],
n: usize,
module_name: &'static CStr,
}
impl<const N: usize> ModInfoBuilder<N> {
/// Create an empty builder instance.
pub const fn new(module_name: &'static CStr) -> Self {
Self {
buf: [0; N],
n: 0,
module_name,
}
}
const fn push_internal(mut self, bytes: &[u8]) -> Self {
let mut j = 0;
if N == 0 {
self.n += bytes.len();
return self;
}
while j < bytes.len() {
if self.n < N {
self.buf[self.n] = bytes[j];
}
self.n += 1;
j += 1;
}
self
}
/// Push an additional path component.
///
/// Append path components to the [`ModInfoBuilder`] instance. Paths need to be separated
/// with [`ModInfoBuilder::new_entry`].
///
/// # Example
///
/// ```
/// use kernel::firmware::ModInfoBuilder;
///
/// # const DIR: &str = "vendor/chip/";
/// # const fn no_run<const N: usize>(builder: ModInfoBuilder<N>) {
/// let builder = builder.new_entry()
/// .push(DIR)
/// .push("foo.bin")
/// .new_entry()
/// .push(DIR)
/// .push("bar.bin");
/// # }
/// ```
pub const fn push(self, s: &str) -> Self {
// Check whether there has been an initial call to `next_entry()`.
if N != 0 && self.n == 0 {
crate::build_error!("Must call next_entry() before push().");
}
self.push_internal(s.as_bytes())
}
const fn push_module_name(self) -> Self {
let mut this = self;
let module_name = this.module_name;
if !this.module_name.is_empty() {
this = this.push_internal(module_name.as_bytes_with_nul());
if N != 0 {
// Re-use the space taken by the NULL terminator and swap it with the '.' separator.
this.buf[this.n - 1] = b'.';
}
}
this
}
/// Prepare the [`ModInfoBuilder`] for the next entry.
///
/// This method acts as a separator between module firmware path entries.
///
/// Must be called before constructing a new entry with subsequent calls to
/// [`ModInfoBuilder::push`].
///
/// See [`ModInfoBuilder::push`] for an example.
pub const fn new_entry(self) -> Self {
self.push_internal(b"\0")
.push_module_name()
.push_internal(b"firmware=")
}
/// Build the byte array.
pub const fn build(self) -> [u8; N] {
// Add the final NULL terminator.
let this = self.push_internal(b"\0");
if this.n == N {
this.buf
} else {
crate::build_error!("Length mismatch.");
}
}
}
impl ModInfoBuilder<0> {
/// Return the length of the byte array to build.
pub const fn build_length(self) -> usize {
// Compensate for the NULL terminator added by `build`.
self.n + 1
}
}

View File

@ -228,6 +228,10 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
kernel::ThisModule::from_ptr(core::ptr::null_mut())
}};
/// The `LocalModule` type is the type of the module created by `module!`,
/// `module_pci_driver!`, `module_platform_driver!`, etc.
type LocalModule = {type_};
impl kernel::ModuleMetadata for {type_} {{
const NAME: &'static kernel::str::CStr = kernel::c_str!(\"{name}\");
}}