summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2025-03-13 06:03:43 +1000
committerDave Airlie <airlied@redhat.com>2025-03-13 06:03:55 +1000
commit4e64a62032ac41e42457870c59a3c7985d594a0e (patch)
tree9cba218f63e028836ef1952b0510457388d707af
parent626fb115662c9fd44fcbdd744d96a45b0427b504 (diff)
parentb28786b190d1ae2df5e6a5181ad78c6f226ea3e1 (diff)
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
-rw-r--r--Documentation/gpu/drivers.rst1
-rw-r--r--Documentation/gpu/nova/core/guidelines.rst24
-rw-r--r--Documentation/gpu/nova/core/todo.rst446
-rw-r--r--Documentation/gpu/nova/guidelines.rst69
-rw-r--r--Documentation/gpu/nova/index.rst30
-rw-r--r--MAINTAINERS11
-rw-r--r--drivers/gpu/Makefile1
-rw-r--r--drivers/gpu/nova-core/Kconfig14
-rw-r--r--drivers/gpu/nova-core/Makefile3
-rw-r--r--drivers/gpu/nova-core/driver.rs47
-rw-r--r--drivers/gpu/nova-core/firmware.rs45
-rw-r--r--drivers/gpu/nova-core/gpu.rs199
-rw-r--r--drivers/gpu/nova-core/nova_core.rs20
-rw-r--r--drivers/gpu/nova-core/regs.rs55
-rw-r--r--drivers/gpu/nova-core/util.rs21
-rw-r--r--drivers/video/Kconfig1
-rw-r--r--rust/kernel/firmware.rs216
-rw-r--r--rust/macros/module.rs4
18 files changed, 1207 insertions, 0 deletions
diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index 74fc2cbf1b6f..78b80be17f21 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -25,6 +25,7 @@ GPU Driver Documentation
panfrost
panthor
zynqmp
+ nova/index
.. only:: subproject and html
diff --git a/Documentation/gpu/nova/core/guidelines.rst b/Documentation/gpu/nova/core/guidelines.rst
new file mode 100644
index 000000000000..a389d65d7982
--- /dev/null
+++ b/Documentation/gpu/nova/core/guidelines.rst
@@ -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.
diff --git a/Documentation/gpu/nova/core/todo.rst b/Documentation/gpu/nova/core/todo.rst
new file mode 100644
index 000000000000..ca08377d3b73
--- /dev/null
+++ b/Documentation/gpu/nova/core/todo.rst
@@ -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
diff --git a/Documentation/gpu/nova/guidelines.rst b/Documentation/gpu/nova/guidelines.rst
new file mode 100644
index 000000000000..13ab13984a18
--- /dev/null
+++ b/Documentation/gpu/nova/guidelines.rst
@@ -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.
diff --git a/Documentation/gpu/nova/index.rst b/Documentation/gpu/nova/index.rst
new file mode 100644
index 000000000000..2701b3f4af35
--- /dev/null
+++ b/Documentation/gpu/nova/index.rst
@@ -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
diff --git a/MAINTAINERS b/MAINTAINERS
index 640c784b59bf..1554908c94cc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile
index 8997f0096545..36a54d456630 100644
--- a/drivers/gpu/Makefile
+++ b/drivers/gpu/Makefile
@@ -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/
diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig
new file mode 100644
index 000000000000..ad0c06756516
--- /dev/null
+++ b/drivers/gpu/nova-core/Kconfig
@@ -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.
diff --git a/drivers/gpu/nova-core/Makefile b/drivers/gpu/nova-core/Makefile
new file mode 100644
index 000000000000..2d78c50126e1
--- /dev/null
+++ b/drivers/gpu/nova-core/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_NOVA_CORE) += nova_core.o
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
new file mode 100644
index 000000000000..63c19f140fbd
--- /dev/null
+++ b/drivers/gpu/nova-core/driver.rs
@@ -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)
+ }
+}
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
new file mode 100644
index 000000000000..6e6361c59ca1
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -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
+ }
+}
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
new file mode 100644
index 000000000000..17c9660da450
--- /dev/null
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -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 }))
+ }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
new file mode 100644
index 000000000000..a91cd924054b
--- /dev/null
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -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);
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
new file mode 100644
index 000000000000..50aefb150b0b
--- /dev/null
+++ b/drivers/gpu/nova-core/regs.rs
@@ -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
+ }
+}
diff --git a/drivers/gpu/nova-core/util.rs b/drivers/gpu/nova-core/util.rs
new file mode 100644
index 000000000000..332a64cfc6a9
--- /dev/null
+++ b/drivers/gpu/nova-core/util.rs
@@ -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."),
+ }
+}
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 44c9ef1435a2..5df981920a94 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -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"
diff --git a/rust/kernel/firmware.rs b/rust/kernel/firmware.rs
index c5162fdc95ff..f04b058b09b2 100644
--- a/rust/kernel/firmware.rs
+++ b/rust/kernel/firmware.rs
@@ -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
+ }
+}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index cdf94f4982df..110e59c64197 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -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}\");
}}