Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 00/18] gpu: nova-core: register!() macro improvements
This patch series introduces a number of improvements to nova-core's register!() macro in order to make it more useful to Nova itself, and to bring it closer to graduation into the wider kernel crate. The first half is trivial fixes and code reorganization to let the following patches apply more cleanly. The interesting stuff begins with the introduction of proper `Debug` and `Default` implementations leveraging the field information that is made available by the first half of the patchset. `Debug` now displays the interpreted values of all the fields on top of the hexadecimal representation of the register; and `Default` now initializes all the fields to their declared default value instead of just zeroes. Then goes a complete redesign of the way relative registers work. The previous way was very unsafe as it accepted any literal value as the base. Now, valid bases can (and must) be explicitly defined for specific group of relative registers. All these bases are belong to us, and thus can be validated at build-time. Next come arrays of registers, a useful feature to represent contiguous groups of registers that are interpreted identically. For these we have both build-time and runtime checked accessors. We immediately make use of them to clean up the FUSE registers code, which was a bit unsightly due to the lack of this feature. Finally, combining the two features: arrays of relative registers, which we don't really need at the moment, but will become needed for GSP booting. There are still features that need to be implemented before this macro can be considered ready for other drivers: - Make I/O accessors optional, - Support other sizes than `u32`, - Allow visibility control for registers and individual fields, - Convert the range syntax to inclusive slices instead of NVIDIA's OpenRM format, - ... and proper suitability assessment by other driver contributors. These should be trivial compared to the work that is done in this series. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- Alexandre Courbot (17): gpu: nova-core: register: fix typo gpu: nova-core: register: fix alias documentation gpu: nova-core: register: simplify @leaf_accessor rule gpu: nova-core: register: remove `try_` accessors for relative registers gpu: nova-core: register: move OFFSET declaration to I/O impl block gpu: nova-core: register: fix documentation and indentation gpu: nova-core: register: add missing doccomments for fixed registers I/O accessors gpu: nova-core: register: add fields dispatcher internal rule gpu: nova-core: register: improve `Debug` implementation gpu: nova-core: register: generate correct `Default` implementation gpu: nova-core: register: split @io rule into fixed and relative versions gpu: nova-core: register: use #[inline(always)] for all methods gpu: nova-core: register: redesign relative registers gpu: nova-core: falcon: add distinct base address for PFALCON2 gpu: nova-core: register: add support for register arrays gpu: nova-core: falcon: use register arrays for FUSE registers gpu: nova-core: registers: add support for relative array registers John Hubbard (1): gpu: nova-core: register: minor grammar and spelling fixes Documentation/gpu/nova/core/todo.rst | 2 - drivers/gpu/nova-core/falcon.rs | 72 +-- drivers/gpu/nova-core/falcon/gsp.rs | 16 +- drivers/gpu/nova-core/falcon/hal/ga102.rs | 47 +- drivers/gpu/nova-core/falcon/sec2.rs | 13 +- drivers/gpu/nova-core/gpu.rs | 2 +- drivers/gpu/nova-core/regs.rs | 79 ++-- drivers/gpu/nova-core/regs/macros.rs | 736 +++++++++++++++++++++++++----- 8 files changed, 749 insertions(+), 218 deletions(-) --- base-commit: 4092e1b41202ff39aad75a40a03ac1d318443670 change-id: 20250703-nova-regs-24dddef5fba3 Best regards, -- Alexandre Courbot <acourbot at nvidia.com>
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 01/18] gpu: nova-core: register: minor grammar and spelling fixes
From: John Hubbard <jhubbard at nvidia.com> There is only one top-level macro in this file at the moment, but the "macros.rs" file name allows for more. Change the wording so that it will remain valid even if additional macros are added to the file. Fix a couple of spelling errors and grammatical errors, and break up a run-on sentence, for clarity. Cc: Alexandre Courbot <acourbot at nvidia.com> Cc: Danilo Krummrich <dakr at kernel.org> Signed-off-by: John Hubbard <jhubbard at nvidia.com> Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index cdf668073480ed703c89ffa8628f5c9de6494687..864d1e83bed2979f5661e038f4c9fd87d33f69a7 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -1,17 +1,17 @@ // SPDX-License-Identifier: GPL-2.0 -//! Macro to define register layout and accessors. +//! `register!` macro to define register layout and accessors. //! //! A single register typically includes several fields, which are accessed through a combination //! of bit-shift and mask operations that introduce a class of potential mistakes, notably because //! not all possible field values are necessarily valid. //! -//! The macro in this module allow to define, using an intruitive and readable syntax, a dedicated -//! type for each register with its own field accessors that can return an error is a field's value -//! is invalid. +//! The `register!` macro in this module provides an intuitive and readable syntax for defining a +//! dedicated type for each register. Each such type comes with its own field accessors that can +//! return an error if a field's value is invalid. -/// Defines a dedicated type for a register with an absolute offset, alongside with getter and -/// setter methods for its fields and methods to read and write it from an `Io` region. +/// Defines a dedicated type for a register with an absolute offset, including getter and setter +/// methods for its fields and methods to read and write it from an `Io` region. /// /// Example: /// @@ -24,7 +24,7 @@ /// ``` /// /// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io` -/// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 less +/// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 least /// significant bits of the register. Each field can be accessed and modified using accessor /// methods: /// -- 2.50.0
A space was missing between arguments in this invocation. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 864d1e83bed2979f5661e038f4c9fd87d33f69a7..93e9055d5ebd5f78ea534aafd44d884da0fce345 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -116,7 +116,7 @@ macro_rules! register { ) => { register!(@common $name @ $offset $(, $comment)?); register!(@field_accessors $name { $($fields)* }); - register!(@io$name @ + $offset); + register!(@io $name @ + $offset); }; // Creates a alias register of relative offset register `alias` with its own fields. -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 03/18] gpu: nova-core: register: fix alias documentation
The provided example had its matching closing parentheses missing. Also simplify the example itself and reword the explanation a bit. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 93e9055d5ebd5f78ea534aafd44d884da0fce345..8a2001c07654cd6516b09be330e7a595695b365b 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -76,15 +76,17 @@ /// for cases where a register's interpretation depends on the context: /// /// ```no_run -/// register!(SCRATCH_0 @ 0x0000100, "Scratch register 0" { +/// register!(SCRATCH @ 0x0000200, "Scratch register" { /// 31:0 value as u32, "Raw value"; +/// }); /// -/// register!(SCRATCH_0_BOOT_STATUS => SCRATCH_0, "Boot status of the firmware" { +/// register!(SCRATCH_BOOT_STATUS => SCRATCH, "Boot status of the firmware" { /// 0:0 completed as bool, "Whether the firmware has completed booting"; +/// }); /// ``` /// /// In this example, `SCRATCH_0_BOOT_STATUS` uses the same I/O address as `SCRATCH_0`, while also -/// providing its own `completed` method. +/// providing its own `completed` field. macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ( -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 04/18] gpu: nova-core: register: simplify @leaf_accessor rule
The `$type` metavariable is not used in the @leaf_accessor rule, so remove it. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 8a2001c07654cd6516b09be330e7a595695b365b..7d5cd45d3e787301acab0b4d8ce1002c2dd2cab2 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -230,7 +230,7 @@ impl $name { $(, $comment:literal)?; ) => { register!( - @leaf_accessor $name $hi:$lo $field as bool + @leaf_accessor $name $hi:$lo $field { |f| <$into_type>::from(if f != 0 { true } else { false }) } $into_type => $into_type $(, $comment)?; ); @@ -248,7 +248,7 @@ impl $name { @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt ?=> $try_into_type:ty $(, $comment:literal)?; ) => { - register!(@leaf_accessor $name $hi:$lo $field as $type + register!(@leaf_accessor $name $hi:$lo $field { |f| <$try_into_type>::try_from(f as $type) } $try_into_type => ::core::result::Result< $try_into_type, @@ -262,7 +262,7 @@ impl $name { @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt => $into_type:ty $(, $comment:literal)?; ) => { - register!(@leaf_accessor $name $hi:$lo $field as $type + register!(@leaf_accessor $name $hi:$lo $field { |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;); }; @@ -276,7 +276,7 @@ impl $name { // Generates the accessor methods for a single field. ( - @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:ty + @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident { $process:expr } $to_type:ty => $res_type:ty $(, $comment:literal)?; ) => { ::kernel::macros::paste!( -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 05/18] gpu: nova-core: register: remove `try_` accessors for relative registers
Relative registers are always accessed using a literal base, meaning their validity can always be checked at compile-time. Thus remove the `try_` accessors that have no purpose. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 38 +----------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 7d5cd45d3e787301acab0b4d8ce1002c2dd2cab2..087f92f78788a013702cbc0a6e156e40f7695347 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -57,9 +57,7 @@ /// definition, or the field getter and setter methods they are attached to. /// /// Putting a `+` before the address of the register makes it relative to a base: the `read` and -/// `write` methods take a `base` argument that is added to the specified address before access, -/// and `try_read` and `try_write` methods are also created, allowing access with offsets unknown -/// at compile-time: +/// `write` methods take a `base` argument that is added to the specified address before access: /// /// ```no_run /// register!(CPU_CTL @ +0x0000010, "CPU core control" { @@ -386,40 +384,6 @@ pub(crate) fn alter<const SIZE: usize, T, F>( let reg = f(Self::read(io, base)); reg.write(io, base); } - - #[inline] - pub(crate) fn try_read<const SIZE: usize, T>( - io: &T, - base: usize, - ) -> ::kernel::error::Result<Self> where - T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, - { - io.try_read32(base + $offset).map(Self) - } - - #[inline] - pub(crate) fn try_write<const SIZE: usize, T>( - self, - io: &T, - base: usize, - ) -> ::kernel::error::Result<()> where - T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, - { - io.try_write32(self.0, base + $offset) - } - - #[inline] - pub(crate) fn try_alter<const SIZE: usize, T, F>( - io: &T, - base: usize, - f: F, - ) -> ::kernel::error::Result<()> where - T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, - F: ::core::ops::FnOnce(Self) -> Self, - { - let reg = f(Self::try_read(io, base)?); - reg.try_write(io, base) - } } }; } -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 06/18] gpu: nova-core: register: move OFFSET declaration to I/O impl block
The OFFSET const is an I/O property, and having to pass it to the @common rule makes it impossible to make I/O optional, as we want to get to eventually. Thus, move OFFSET to the I/O impl block so it is not needed by the @common rule anymore. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 087f92f78788a013702cbc0a6e156e40f7695347..b096a3689483fa79dc22f16f4baac2f9a9495c28 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -92,7 +92,7 @@ macro_rules! register { $($fields:tt)* } ) => { - register!(@common $name @ $offset $(, $comment)?); + register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ $offset); }; @@ -103,7 +103,7 @@ macro_rules! register { $($fields:tt)* } ) => { - register!(@common $name @ $alias::OFFSET $(, $comment)?); + register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ $alias::OFFSET); }; @@ -114,7 +114,7 @@ macro_rules! register { $($fields:tt)* } ) => { - register!(@common $name @ $offset $(, $comment)?); + register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ + $offset); }; @@ -125,7 +125,7 @@ macro_rules! register { $($fields:tt)* } ) => { - register!(@common $name @ $alias::OFFSET $(, $comment)?); + register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ + $alias::OFFSET); }; @@ -134,7 +134,7 @@ macro_rules! register { // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`, // and conversion to regular `u32`). - (@common $name:ident @ $offset:expr $(, $comment:literal)?) => { + (@common $name:ident $(, $comment:literal)?) => { $( #[doc=$comment] )? @@ -142,11 +142,6 @@ macro_rules! register { #[derive(Clone, Copy, Default)] pub(crate) struct $name(u32); - #[allow(dead_code)] - impl $name { - pub(crate) const OFFSET: usize = $offset; - } - // TODO[REGA]: display the raw hex value, then the value of all the fields. This requires // matching the fields, which will complexify the syntax considerably... impl ::core::fmt::Debug for $name { @@ -319,6 +314,8 @@ pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { (@io $name:ident @ $offset:expr) => { #[allow(dead_code)] impl $name { + pub(crate) const OFFSET: usize = $offset; + #[inline] pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, @@ -351,6 +348,8 @@ pub(crate) fn alter<const SIZE: usize, T, F>( (@io $name:ident @ + $offset:literal) => { #[allow(dead_code)] impl $name { + pub(crate) const OFFSET: usize = $offset; + #[inline] pub(crate) fn read<const SIZE: usize, T>( io: &T, -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 07/18] gpu: nova-core: register: fix documentation and indentation
Fix a few documentation inconsistencies, and harmonize indentation where possible. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index b096a3689483fa79dc22f16f4baac2f9a9495c28..73ee72a6c7baa7e9d1032ec0da119940b8f1a3f7 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -87,44 +87,28 @@ /// providing its own `completed` field. macro_rules! register { // Creates a register at a fixed offset of the MMIO space. - ( - $name:ident @ $offset:literal $(, $comment:literal)? { - $($fields:tt)* - } - ) => { + ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ $offset); }; - // Creates a alias register of fixed offset register `alias` with its own fields. - ( - $name:ident => $alias:ident $(, $comment:literal)? { - $($fields:tt)* - } - ) => { + // Creates an alias register of fixed offset register `alias` with its own fields. + ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ $alias::OFFSET); }; // Creates a register at a relative offset from a base address. - ( - $name:ident @ + $offset:literal $(, $comment:literal)? { - $($fields:tt)* - } - ) => { + ($name:ident @ + $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ + $offset); }; - // Creates a alias register of relative offset register `alias` with its own fields. - ( - $name:ident => + $alias:ident $(, $comment:literal)? { - $($fields:tt)* - } - ) => { + // Creates an alias register of relative offset register `alias` with its own fields. + ($name:ident => + $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@common $name $(, $comment)?); register!(@field_accessors $name { $($fields)* }); register!(@io $name @ + $alias::OFFSET); @@ -259,7 +243,7 @@ impl $name { { |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;); }; - // Shortcut for fields defined as non-`bool` without the `=>` or `?=>` syntax. + // Shortcut for non-boolean fields defined without the `=>` or `?=>` syntax. ( @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt $(, $comment:literal)?; @@ -310,7 +294,7 @@ pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { ); }; - // Creates the IO accessors for a fixed offset register. + // Generates the IO accessors for a fixed offset register. (@io $name:ident @ $offset:expr) => { #[allow(dead_code)] impl $name { @@ -344,7 +328,7 @@ pub(crate) fn alter<const SIZE: usize, T, F>( } }; - // Create the IO accessors for a relative offset register. + // Generates the IO accessors for a relative offset register. (@io $name:ident @ + $offset:literal) => { #[allow(dead_code)] impl $name { -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 08/18] gpu: nova-core: register: add missing doccomments for fixed registers I/O accessors
Add the missing doccomments for these accessors, as having a bit of inline documentation is always helpful. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 73ee72a6c7baa7e9d1032ec0da119940b8f1a3f7..60e5e6a2250a9db453a7a648108af6acaa047342 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -300,6 +300,7 @@ pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { impl $name { pub(crate) const OFFSET: usize = $offset; + /// Read the register from its address in `io`. #[inline] pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, @@ -307,6 +308,7 @@ pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where Self(io.read32($offset)) } + /// Write the value contained in `self` to the register address in `io`. #[inline] pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, @@ -314,6 +316,8 @@ pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where io.write32(self.0, $offset) } + /// Read the register from its address in `io` and run `f` on its value to obtain a new + /// value to write back. #[inline] pub(crate) fn alter<const SIZE: usize, T, F>( io: &T, -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 09/18] gpu: nova-core: register: add fields dispatcher internal rule
Fields are complex and cumbersome to match in a rule, and were only captured in order to generate the field accessors. However, there are other places (like the `Debug` and `Default` implementations) where we would benefit from having access to at least some of the field information, but refrained from doing so because it would have meant matching the whole fields in a rule more complex than we need. Introduce a new `@fields_dispatcher` internal rule that captures all the field information and passes it to `@field_accessors`. It does not provide any functional change in itself, but allows us to reuse the captured field information partially to provide better `Debug` and `Default` implementations in following patches. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 42 +++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 60e5e6a2250a9db453a7a648108af6acaa047342..1d473ceb7b97e18e36246569abf0bb04e7b02060 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -88,37 +88,33 @@ macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { - register!(@common $name $(, $comment)?); - register!(@field_accessors $name { $($fields)* }); + register!(@core $name $(, $comment)? { $($fields)* } ); register!(@io $name @ $offset); }; // Creates an alias register of fixed offset register `alias` with its own fields. ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { - register!(@common $name $(, $comment)?); - register!(@field_accessors $name { $($fields)* }); + register!(@core $name $(, $comment)? { $($fields)* } ); register!(@io $name @ $alias::OFFSET); }; // Creates a register at a relative offset from a base address. ($name:ident @ + $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { - register!(@common $name $(, $comment)?); - register!(@field_accessors $name { $($fields)* }); + register!(@core $name $(, $comment)? { $($fields)* } ); register!(@io $name @ + $offset); }; // Creates an alias register of relative offset register `alias` with its own fields. ($name:ident => + $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { - register!(@common $name $(, $comment)?); - register!(@field_accessors $name { $($fields)* }); + register!(@core $name $(, $comment)? { $($fields)* } ); register!(@io $name @ + $alias::OFFSET); }; // All rules below are helpers. // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`, - // and conversion to regular `u32`). - (@common $name:ident $(, $comment:literal)?) => { + // and conversion to the value type) and field accessor methods. + (@core $name:ident $(, $comment:literal)? { $($fields:tt)* }) => { $( #[doc=$comment] )? @@ -149,6 +145,32 @@ fn from(reg: $name) -> u32 { reg.0 } } + + register!(@fields_dispatcher $name { $($fields)* }); + }; + + // Captures the fields and passes them to all the implementers that require field information. + // + // Used to simplify the matching rules for implementers, so they don't need to match the entire + // complex fields rule even though they only make use of part of it. + (@fields_dispatcher $name:ident { + $($hi:tt:$lo:tt $field:ident as $type:tt + $(?=> $try_into_type:ty)? + $(=> $into_type:ty)? + $(, $comment:literal)? + ; + )* + } + ) => { + register!(@field_accessors $name { + $( + $hi:$lo $field as $type + $(?=> $try_into_type)? + $(=> $into_type)? + $(, $comment)? + ; + )* + }); }; // Defines all the field getter/methods methods for `$name`. -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 10/18] gpu: nova-core: register: improve `Debug` implementation
Now that we have an internal rule to dispatch field information where needed, use it to generate a better `Debug` implementation where the raw hexadecimal value of the register is displayed, as well as the `Debug` values of its individual fields. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 1d473ceb7b97e18e36246569abf0bb04e7b02060..75b7b742b117240543ad292fc69e5e1341728174 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -122,16 +122,6 @@ macro_rules! register { #[derive(Clone, Copy, Default)] pub(crate) struct $name(u32); - // TODO[REGA]: display the raw hex value, then the value of all the fields. This requires - // matching the fields, which will complexify the syntax considerably... - impl ::core::fmt::Debug for $name { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(stringify!($name)) - .field(&format_args!("0x{0:x}", &self.0)) - .finish() - } - } - impl ::core::ops::BitOr for $name { type Output = Self; @@ -171,6 +161,7 @@ fn from(reg: $name) -> u32 { ; )* }); + register!(@debug $name { $($field;)* }); }; // Defines all the field getter/methods methods for `$name`. @@ -316,6 +307,20 @@ pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { ); }; + // Generates the `Debug` implementation for `$name`. + (@debug $name:ident { $($field:ident;)* }) => { + impl ::core::fmt::Debug for $name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct(stringify!($name)) + .field("<raw>", &format_args!("{:#x}", &self.0)) + $( + .field(stringify!($field), &self.$field()) + )* + .finish() + } + } + }; + // Generates the IO accessors for a fixed offset register. (@io $name:ident @ $offset:expr) => { #[allow(dead_code)] -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 11/18] gpu: nova-core: register: generate correct `Default` implementation
The `Default` implementation of a register should be the aggregate of the default values of all its fields, and not simply be zeroed. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 75b7b742b117240543ad292fc69e5e1341728174..97895800ff7031d287b3bbd03a00b1ca71014aa8 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -112,14 +112,14 @@ macro_rules! register { // All rules below are helpers. - // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`, - // and conversion to the value type) and field accessor methods. + // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, + // `Default`, `BitOr`, and conversion to the value type) and field accessor methods. (@core $name:ident $(, $comment:literal)? { $($fields:tt)* }) => { $( #[doc=$comment] )? #[repr(transparent)] - #[derive(Clone, Copy, Default)] + #[derive(Clone, Copy)] pub(crate) struct $name(u32); impl ::core::ops::BitOr for $name { @@ -162,6 +162,7 @@ fn from(reg: $name) -> u32 { )* }); register!(@debug $name { $($field;)* }); + register!(@default $name { $($field;)* }); }; // Defines all the field getter/methods methods for `$name`. @@ -321,6 +322,25 @@ fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { } }; + // Generates the `Default` implementation for `$name`. + (@default $name:ident { $($field:ident;)* }) => { + /// Returns a value for the register where all fields are set to their default value. + impl ::core::default::Default for $name { + fn default() -> Self { + #[allow(unused_mut)] + let mut value = Self(Default::default()); + + ::kernel::macros::paste!( + $( + value.[<set_ $field>](Default::default()); + )* + ); + + value + } + } + }; + // Generates the IO accessors for a fixed offset register. (@io $name:ident @ $offset:expr) => { #[allow(dead_code)] -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 12/18] gpu: nova-core: register: split @io rule into fixed and relative versions
We used the same @io rule with different patterns to define both the fixed and relative I/O accessors. This can be confusing as the matching rules are very similar. Since all call sites know which version they want to call, split @io into @io_fixed and @io_relative to remove any ambiguity. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 97895800ff7031d287b3bbd03a00b1ca71014aa8..1b2074e38381f9918c48691301e5a6f62861501f 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -89,25 +89,25 @@ macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io $name @ $offset); + register!(@io_fixed $name @ $offset); }; // Creates an alias register of fixed offset register `alias` with its own fields. ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io $name @ $alias::OFFSET); + register!(@io_fixed $name @ $alias::OFFSET); }; // Creates a register at a relative offset from a base address. ($name:ident @ + $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io $name @ + $offset); + register!(@io_relative $name @ + $offset); }; // Creates an alias register of relative offset register `alias` with its own fields. ($name:ident => + $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io $name @ + $alias::OFFSET); + register!(@io_relative $name @ + $alias::OFFSET); }; // All rules below are helpers. @@ -342,7 +342,7 @@ fn default() -> Self { }; // Generates the IO accessors for a fixed offset register. - (@io $name:ident @ $offset:expr) => { + (@io_fixed $name:ident @ $offset:expr) => { #[allow(dead_code)] impl $name { pub(crate) const OFFSET: usize = $offset; @@ -380,7 +380,7 @@ pub(crate) fn alter<const SIZE: usize, T, F>( }; // Generates the IO accessors for a relative offset register. - (@io $name:ident @ + $offset:literal) => { + (@io_relative $name:ident @ + $offset:literal) => { #[allow(dead_code)] impl $name { pub(crate) const OFFSET: usize = $offset; -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 13/18] gpu: nova-core: register: use #[inline(always)] for all methods
These methods should always be inlined, so use the strongest compiler hint that exists to maximize the chance they will indeed be. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/regs/macros.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 1b2074e38381f9918c48691301e5a6f62861501f..316b67ee1cea7c7fa2894c1778e7d43f853bdb19 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -280,7 +280,7 @@ impl $name { #[doc="Returns the value of this field:"] #[doc=$comment] )? - #[inline] + #[inline(always)] pub(crate) fn $field(self) -> $res_type { ::kernel::macros::paste!( const MASK: u32 = $name::[<$field:upper _MASK>]; @@ -296,7 +296,7 @@ pub(crate) fn $field(self) -> $res_type { #[doc="Sets the value of this field:"] #[doc=$comment] )? - #[inline] + #[inline(always)] pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self { const MASK: u32 = $name::[<$field:upper _MASK>]; const SHIFT: u32 = $name::[<$field:upper _SHIFT>]; @@ -348,7 +348,7 @@ impl $name { pub(crate) const OFFSET: usize = $offset; /// Read the register from its address in `io`. - #[inline] + #[inline(always)] pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, { @@ -356,7 +356,7 @@ pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where } /// Write the value contained in `self` to the register address in `io`. - #[inline] + #[inline(always)] pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, { @@ -365,7 +365,7 @@ pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where /// Read the register from its address in `io` and run `f` on its value to obtain a new /// value to write back. - #[inline] + #[inline(always)] pub(crate) fn alter<const SIZE: usize, T, F>( io: &T, f: F, @@ -385,7 +385,7 @@ pub(crate) fn alter<const SIZE: usize, T, F>( impl $name { pub(crate) const OFFSET: usize = $offset; - #[inline] + #[inline(always)] pub(crate) fn read<const SIZE: usize, T>( io: &T, base: usize, @@ -395,7 +395,7 @@ pub(crate) fn read<const SIZE: usize, T>( Self(io.read32(base + $offset)) } - #[inline] + #[inline(always)] pub(crate) fn write<const SIZE: usize, T>( self, io: &T, @@ -406,7 +406,7 @@ pub(crate) fn write<const SIZE: usize, T>( io.write32(self.0, base + $offset) } - #[inline] + #[inline(always)] pub(crate) fn alter<const SIZE: usize, T, F>( io: &T, base: usize, -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 14/18] gpu: nova-core: register: redesign relative registers
The relative registers are currently very unsafe to use: callers can specify any constant as the base address for access, meaning they can effectively interpret any I/O address as any relative register. Ideally, valid base addresses for a family of registers should be explicitly defined in the code, and could only be used with the relevant registers This patch changes the relative register declaration into this: register!(REGISTER_NAME @ BaseTrait[offset] ... Where `BaseTrait` is the name of a ZST used as a parameter of the `RegisterBase<>` trait to define a trait unique to a class of register. This specialized trait is then implemented for every type that provides a valid base address, enabling said types to be passed as the base address provider for the register's I/O accessor methods. This design thus makes it impossible to pass an unexpected base address to a relative register, and, since the valid bases are all known at compile-time, also guarantees that all I/O accesses are done within the valid bounds of the I/O range. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- Documentation/gpu/nova/core/todo.rst | 1 - drivers/gpu/nova-core/falcon.rs | 67 +++++++++------- drivers/gpu/nova-core/falcon/gsp.rs | 12 ++- drivers/gpu/nova-core/falcon/hal/ga102.rs | 14 ++-- drivers/gpu/nova-core/falcon/sec2.rs | 9 ++- drivers/gpu/nova-core/regs.rs | 50 ++++++------ drivers/gpu/nova-core/regs/macros.rs | 126 +++++++++++++++++++++++------- 7 files changed, 182 insertions(+), 97 deletions(-) diff --git a/Documentation/gpu/nova/core/todo.rst b/Documentation/gpu/nova/core/todo.rst index 894a1e9c3741a43ad4eb76d24a9486862999874e..a1d12c1b289d89251d914fc271b7243ced11d487 100644 --- a/Documentation/gpu/nova/core/todo.rst +++ b/Documentation/gpu/nova/core/todo.rst @@ -131,7 +131,6 @@ crate so it can be used by other components as well. Features desired before this happens: -* Relative register with build-time base address validation, * Arrays of registers with build-time index validation, * Make I/O optional I/O (for field values that are not registers), * Support other sizes than `u32`, diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs index c2c6f9eb380ab390befe5af1b3c5df260ccd7595..66400d07d186938735f705c116d7c7df5fb77fca 100644 --- a/drivers/gpu/nova-core/falcon.rs +++ b/drivers/gpu/nova-core/falcon.rs @@ -14,6 +14,7 @@ use crate::driver::Bar0; use crate::gpu::Chipset; use crate::regs; +use crate::regs::macros::RegisterBase; use crate::util; pub(crate) mod gsp; @@ -259,10 +260,16 @@ fn from(value: bool) -> Self { } } -/// Trait defining the parameters of a given Falcon instance. -pub(crate) trait FalconEngine: Sync { - /// Base I/O address for the falcon, relative from which its registers are accessed. - const BASE: usize; +/// Type used to represent the `PFALCON` registers address base for a given falcon engine. +pub(crate) struct PFalconBase(()); + +/// Trait defining the parameters of a given Falcon engine. +/// +/// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used +/// to identify a given Falcon instance with register I/O methods. +pub(crate) trait FalconEngine: Sync + RegisterBase<PFalconBase> + Sized { + /// Singleton of the engine, used to identify it with register I/O methods. + const ID: Self; } /// Represents a portion of the firmware to be loaded into a particular memory (e.g. IMEM or DMEM). @@ -328,13 +335,13 @@ pub(crate) fn new( bar: &Bar0, need_riscv: bool, ) -> Result<Self> { - let hwcfg1 = regs::NV_PFALCON_FALCON_HWCFG1::read(bar, E::BASE); + let hwcfg1 = regs::NV_PFALCON_FALCON_HWCFG1::read(bar, &E::ID); // Check that the revision and security model contain valid values. let _ = hwcfg1.core_rev()?; let _ = hwcfg1.security_model()?; if need_riscv { - let hwcfg2 = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); + let hwcfg2 = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); if !hwcfg2.riscv() { dev_err!( dev, @@ -354,7 +361,7 @@ pub(crate) fn new( fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { // TIMEOUT: memory scrubbing should complete in less than 20ms. util::wait_on(Delta::from_millis(20), || { - if regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE).mem_scrubbing_done() { + if regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID).mem_scrubbing_done() { Some(()) } else { None @@ -364,12 +371,12 @@ fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { /// Reset the falcon engine. fn reset_eng(&self, bar: &Bar0) -> Result { - let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); + let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); // According to OpenRM's `kflcnPreResetWait_GA102` documentation, HW sometimes does not set // RESET_READY so a non-failing timeout is used. let _ = util::wait_on(Delta::from_micros(150), || { - let r = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, E::BASE); + let r = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); if r.reset_ready() { Some(()) } else { @@ -377,13 +384,13 @@ fn reset_eng(&self, bar: &Bar0) -> Result { } }); - regs::NV_PFALCON_FALCON_ENGINE::alter(bar, E::BASE, |v| v.set_reset(true)); + regs::NV_PFALCON_FALCON_ENGINE::alter(bar, &E::ID, |v| v.set_reset(true)); // TODO[DLAY]: replace with udelay() or equivalent once available. // TIMEOUT: falcon engine should not take more than 10us to reset. let _: Result = util::wait_on(Delta::from_micros(10), || None); - regs::NV_PFALCON_FALCON_ENGINE::alter(bar, E::BASE, |v| v.set_reset(false)); + regs::NV_PFALCON_FALCON_ENGINE::alter(bar, &E::ID, |v| v.set_reset(false)); self.reset_wait_mem_scrubbing(bar)?; @@ -398,7 +405,7 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result { regs::NV_PFALCON_FALCON_RM::default() .set_value(regs::NV_PMC_BOOT_0::read(bar).into()) - .write(bar, E::BASE); + .write(bar, &E::ID); Ok(()) } @@ -449,10 +456,10 @@ fn dma_wr<F: FalconFirmware<Target = E>>( regs::NV_PFALCON_FALCON_DMATRFBASE::default() .set_base((dma_start >> 8) as u32) - .write(bar, E::BASE); + .write(bar, &E::ID); regs::NV_PFALCON_FALCON_DMATRFBASE1::default() .set_base((dma_start >> 40) as u16) - .write(bar, E::BASE); + .write(bar, &E::ID); let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default() .set_size(DmaTrfCmdSize::Size256B) @@ -463,17 +470,17 @@ fn dma_wr<F: FalconFirmware<Target = E>>( // Perform a transfer of size `DMA_LEN`. regs::NV_PFALCON_FALCON_DMATRFMOFFS::default() .set_offs(load_offsets.dst_start + pos) - .write(bar, E::BASE); + .write(bar, &E::ID); regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default() .set_offs(src_start + pos) - .write(bar, E::BASE); - cmd.write(bar, E::BASE); + .write(bar, &E::ID); + cmd.write(bar, &E::ID); // Wait for the transfer to complete. // TIMEOUT: arbitrarily large value, no DMA transfer to the falcon's small memories // should ever take that long. util::wait_on(Delta::from_secs(2), || { - let r = regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, E::BASE); + let r = regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, &E::ID); if r.idle() { Some(()) } else { @@ -487,9 +494,9 @@ fn dma_wr<F: FalconFirmware<Target = E>>( /// Perform a DMA load into `IMEM` and `DMEM` of `fw`, and prepare the falcon to run it. pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result { - regs::NV_PFALCON_FBIF_CTL::alter(bar, E::BASE, |v| v.set_allow_phys_no_ctx(true)); - regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, E::BASE); - regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, E::BASE, |v| { + regs::NV_PFALCON_FBIF_CTL::alter(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true)); + regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID); + regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, &E::ID, |v| { v.set_target(FalconFbifTarget::CoherentSysmem) .set_mem_type(FalconFbifMemType::Physical) }); @@ -502,7 +509,7 @@ pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) // Set `BootVec` to start of non-secure code. regs::NV_PFALCON_FALCON_BOOTVEC::default() .set_value(fw.boot_addr()) - .write(bar, E::BASE); + .write(bar, &E::ID); Ok(()) } @@ -523,27 +530,27 @@ pub(crate) fn boot( if let Some(mbox0) = mbox0 { regs::NV_PFALCON_FALCON_MAILBOX0::default() .set_value(mbox0) - .write(bar, E::BASE); + .write(bar, &E::ID); } if let Some(mbox1) = mbox1 { regs::NV_PFALCON_FALCON_MAILBOX1::default() .set_value(mbox1) - .write(bar, E::BASE); + .write(bar, &E::ID); } - match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, E::BASE).alias_en() { + match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en() { true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default() .set_startcpu(true) - .write(bar, E::BASE), + .write(bar, &E::ID), false => regs::NV_PFALCON_FALCON_CPUCTL::default() .set_startcpu(true) - .write(bar, E::BASE), + .write(bar, &E::ID), } // TIMEOUT: arbitrarily large value, firmwares should complete in less than 2 seconds. util::wait_on(Delta::from_secs(2), || { - let r = regs::NV_PFALCON_FALCON_CPUCTL::read(bar, E::BASE); + let r = regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID); if r.halted() { Some(()) } else { @@ -552,8 +559,8 @@ pub(crate) fn boot( })?; let (mbox0, mbox1) = ( - regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, E::BASE).value(), - regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, E::BASE).value(), + regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value(), + regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value(), ); Ok((mbox0, mbox1)) diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs index d622e9a64470932af0b48032be5a1d4b518bf4a7..0db9f94036a6a7ced5a461aec2cff2ce246a5e0e 100644 --- a/drivers/gpu/nova-core/falcon/gsp.rs +++ b/drivers/gpu/nova-core/falcon/gsp.rs @@ -2,23 +2,27 @@ use crate::{ driver::Bar0, - falcon::{Falcon, FalconEngine}, - regs, + falcon::{Falcon, FalconEngine, PFalconBase}, + regs::{self, macros::RegisterBase}, }; /// Type specifying the `Gsp` falcon engine. Cannot be instantiated. pub(crate) struct Gsp(()); -impl FalconEngine for Gsp { +impl RegisterBase<PFalconBase> for Gsp { const BASE: usize = 0x00110000; } +impl FalconEngine for Gsp { + const ID: Self = Gsp(()); +} + impl Falcon<Gsp> { /// Clears the SWGEN0 bit in the Falcon's IRQ status clear register to /// allow GSP to signal CPU for processing new messages in message queue. pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) { regs::NV_PFALCON_FALCON_IRQSCLR::default() .set_swgen0(true) - .write(bar, Gsp::BASE); + .write(bar, &Gsp::ID); } } diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs index 52c33d3f22a8e920742b45940c346c47fdc70e93..3fdacd19322dd122eb00e245de4be8d1edd61a5f 100644 --- a/drivers/gpu/nova-core/falcon/hal/ga102.rs +++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs @@ -16,15 +16,15 @@ use super::FalconHal; fn select_core_ga102<E: FalconEngine>(bar: &Bar0) -> Result { - let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, E::BASE); + let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); if bcr_ctrl.core_select() != PeregrineCoreSelect::Falcon { regs::NV_PRISCV_RISCV_BCR_CTRL::default() .set_core_select(PeregrineCoreSelect::Falcon) - .write(bar, E::BASE); + .write(bar, &E::ID); // TIMEOUT: falcon core should take less than 10ms to report being enabled. util::wait_on(Delta::from_millis(10), || { - let r = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, E::BASE); + let r = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); if r.valid() { Some(()) } else { @@ -76,16 +76,16 @@ fn signature_reg_fuse_version_ga102( fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result { regs::NV_PFALCON2_FALCON_BROM_PARAADDR::default() .set_value(params.pkc_data_offset) - .write(bar, E::BASE); + .write(bar, &E::ID); regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default() .set_value(u32::from(params.engine_id_mask)) - .write(bar, E::BASE); + .write(bar, &E::ID); regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::default() .set_ucode_id(params.ucode_id) - .write(bar, E::BASE); + .write(bar, &E::ID); regs::NV_PFALCON2_FALCON_MOD_SEL::default() .set_algo(FalconModSelAlgo::Rsa3k) - .write(bar, E::BASE); + .write(bar, &E::ID); Ok(()) } diff --git a/drivers/gpu/nova-core/falcon/sec2.rs b/drivers/gpu/nova-core/falcon/sec2.rs index 5147d9e2a7fe859210727504688d84cca4de991b..dbc486a712ffce30efa3a4264b0757974962302e 100644 --- a/drivers/gpu/nova-core/falcon/sec2.rs +++ b/drivers/gpu/nova-core/falcon/sec2.rs @@ -1,10 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 -use crate::falcon::FalconEngine; +use crate::falcon::{FalconEngine, PFalconBase}; +use crate::regs::macros::RegisterBase; /// Type specifying the `Sec2` falcon engine. Cannot be instantiated. pub(crate) struct Sec2(()); -impl FalconEngine for Sec2 { +impl RegisterBase<PFalconBase> for Sec2 { const BASE: usize = 0x00840000; } + +impl FalconEngine for Sec2 { + const ID: Self = Sec2(()); +} diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index e8b8aabce3f36abe6a7fba3e11f677e36baa3897..4e047fc4947b6dc09dbd34e26eeda1ff14dff4ad 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -5,11 +5,11 @@ #![allow(non_camel_case_types)] #[macro_use] -mod macros; +pub(crate) mod macros; use crate::falcon::{ DmaTrfCmdSize, FalconCoreRev, FalconCoreRevSubversion, FalconFbifMemType, FalconFbifTarget, - FalconModSelAlgo, FalconSecurityModel, PeregrineCoreSelect, + FalconModSelAlgo, FalconSecurityModel, PFalconBase, PeregrineCoreSelect, }; use crate::gpu::{Architecture, Chipset}; use kernel::prelude::*; @@ -181,24 +181,24 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> { /* PFALCON */ -register!(NV_PFALCON_FALCON_IRQSCLR @ +0x00000004 { +register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] { 4:4 halt as bool; 6:6 swgen0 as bool; }); -register!(NV_PFALCON_FALCON_MAILBOX0 @ +0x00000040 { +register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] { 31:0 value as u32; }); -register!(NV_PFALCON_FALCON_MAILBOX1 @ +0x00000044 { +register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] { 31:0 value as u32; }); -register!(NV_PFALCON_FALCON_RM @ +0x00000084 { +register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] { 31:0 value as u32; }); -register!(NV_PFALCON_FALCON_HWCFG2 @ +0x000000f4 { +register!(NV_PFALCON_FALCON_HWCFG2 @ PFalconBase[0x000000f4] { 10:10 riscv as bool; 12:12 mem_scrubbing as bool, "Set to 0 after memory scrubbing is completed"; 31:31 reset_ready as bool, "Signal indicating that reset is completed (GA102+)"; @@ -211,17 +211,17 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { } } -register!(NV_PFALCON_FALCON_CPUCTL @ +0x00000100 { +register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] { 1:1 startcpu as bool; 4:4 halted as bool; 6:6 alias_en as bool; }); -register!(NV_PFALCON_FALCON_BOOTVEC @ +0x00000104 { +register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] { 31:0 value as u32; }); -register!(NV_PFALCON_FALCON_DMACTL @ +0x0000010c { +register!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] { 0:0 require_ctx as bool; 1:1 dmem_scrubbing as bool; 2:2 imem_scrubbing as bool; @@ -229,15 +229,15 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { 7:7 secure_stat as bool; }); -register!(NV_PFALCON_FALCON_DMATRFBASE @ +0x00000110 { +register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] { 31:0 base as u32; }); -register!(NV_PFALCON_FALCON_DMATRFMOFFS @ +0x00000114 { +register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] { 23:0 offs as u32; }); -register!(NV_PFALCON_FALCON_DMATRFCMD @ +0x00000118 { +register!(NV_PFALCON_FALCON_DMATRFCMD @ PFalconBase[0x00000118] { 0:0 full as bool; 1:1 idle as bool; 3:2 sec as u8; @@ -248,60 +248,60 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { 16:16 set_dmtag as u8; }); -register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ +0x0000011c { +register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] { 31:0 offs as u32; }); -register!(NV_PFALCON_FALCON_DMATRFBASE1 @ +0x00000128 { +register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] { 8:0 base as u16; }); -register!(NV_PFALCON_FALCON_HWCFG1 @ +0x0000012c { +register!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] { 3:0 core_rev as u8 ?=> FalconCoreRev, "Core revision"; 5:4 security_model as u8 ?=> FalconSecurityModel, "Security model"; 7:6 core_rev_subversion as u8 ?=> FalconCoreRevSubversion, "Core revision subversion"; }); -register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ +0x00000130 { +register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] { 1:1 startcpu as bool; }); // Actually known as `NV_PSEC_FALCON_ENGINE` and `NV_PGSP_FALCON_ENGINE` depending on the falcon // instance. -register!(NV_PFALCON_FALCON_ENGINE @ +0x000003c0 { +register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] { 0:0 reset as bool; }); // TODO[REGA]: this is an array of registers. -register!(NV_PFALCON_FBIF_TRANSCFG @ +0x00000600 { +register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600] { 1:0 target as u8 ?=> FalconFbifTarget; 2:2 mem_type as bool => FalconFbifMemType; }); -register!(NV_PFALCON_FBIF_CTL @ +0x00000624 { +register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] { 7:7 allow_phys_no_ctx as bool; }); -register!(NV_PFALCON2_FALCON_MOD_SEL @ +0x00001180 { +register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalconBase[0x00001180] { 7:0 algo as u8 ?=> FalconModSelAlgo; }); -register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ +0x00001198 { +register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalconBase[0x00001198] { 7:0 ucode_id as u8; }); -register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ +0x0000119c { +register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalconBase[0x0000119c] { 31:0 value as u32; }); // TODO[REGA]: this is an array of registers. -register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ +0x00001210 { +register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalconBase[0x00001210] { 31:0 value as u32; }); /* PRISCV */ -register!(NV_PRISCV_RISCV_BCR_CTRL @ +0x00001668 { +register!(NV_PRISCV_RISCV_BCR_CTRL @ PFalconBase[0x00001668] { 0:0 valid as bool; 4:4 core_select as bool => PeregrineCoreSelect; 8:8 br_fetch as bool; diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 316b67ee1cea7c7fa2894c1778e7d43f853bdb19..1f0375d20b5f4328d496a14120eea8a54123f8cc 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -10,6 +10,16 @@ //! dedicated type for each register. Each such type comes with its own field accessors that can //! return an error if a field's value is invalid. +/// Trait providing a base address to be added to the offset of a relative register to obtain +/// its actual offset. +/// +/// The `T` generic argument is used to distinguish which base to use, in case a type provides +/// several bases. It is given to the `register!` macro to restrict the use of the register to +/// implementors of this particular variant. +pub(crate) trait RegisterBase<T> { + const BASE: usize; +} + /// Defines a dedicated type for a register with an absolute offset, including getter and setter /// methods for its fields and methods to read and write it from an `Io` region. /// @@ -56,20 +66,6 @@ /// The documentation strings are optional. If present, they will be added to the type's /// definition, or the field getter and setter methods they are attached to. /// -/// Putting a `+` before the address of the register makes it relative to a base: the `read` and -/// `write` methods take a `base` argument that is added to the specified address before access: -/// -/// ```no_run -/// register!(CPU_CTL @ +0x0000010, "CPU core control" { -/// 0:0 start as bool, "Start the CPU core"; -/// }); -/// -/// // Flip the `start` switch for the CPU core which base address is at `CPU_BASE`. -/// let cpuctl = CPU_CTL::read(&bar, CPU_BASE); -/// pr_info!("CPU CTL: {:#x}", cpuctl); -/// cpuctl.set_start(true).write(&bar, CPU_BASE); -/// ``` -/// /// It is also possible to create a alias register by using the `=> ALIAS` syntax. This is useful /// for cases where a register's interpretation depends on the context: /// @@ -85,6 +81,57 @@ /// /// In this example, `SCRATCH_0_BOOT_STATUS` uses the same I/O address as `SCRATCH_0`, while also /// providing its own `completed` field. +/// +/// ## Relative registers +/// +/// A register can be defined as being accessible from a fixed offset of a provided base. For this, +/// use the `Base[Offset]` syntax when specifying the register's address. +/// +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the +/// `RegisterBase` to provide the base as a constant, i.e. each type providing a base for this +/// register needs to implement `RegisterBase<Base>`. +/// +/// The`read`, `write` and `alter` methods of relative registers take an extra `base` argument that +/// is used to resolve the final address of the register. +/// +/// ```no_run +/// // Type used as parameter of `RegisterBase` to specify the base. +/// pub(crate) struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0xf00; +/// } +/// // Singleton of `CPU0` used to identify it. +/// const CPU0: Cpu0 = Cpu0; +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x1f00; +/// } +/// // Singleton of `CPU1` used to identify it. +/// const CPU1: Cpu1 = Cpu1; +/// +/// register!(CPU_CTL @ CpuCtlBase[0x10], "CPU core control" { +/// 0:0 start as bool, "Start the CPU core"; +/// }); +/// +/// // Start `CPU0`. +/// CPU_CTL::alter(bar, &CPU0, |r| r.set_start(true)); +/// +/// // Start `CPU1`. +/// CPU_CTL::alter(bar, &CPU1, |r| r.set_start(true)); +/// +/// // Alias of a relative register. +/// register!(CPU_CTL_ALIAS => CpuCtlBase[CPU_CTL], "Alias to CPU core control" { +/// 1:1 alias_start as bool, "Start the aliased CPU core"; +/// }); +/// +/// // Start the aliased `CPU0`. +/// CPU_CTL_ALIAS::alter(bar, &CPU0, |r| r.set_alias_start(true)); +/// ``` macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { @@ -98,16 +145,16 @@ macro_rules! register { register!(@io_fixed $name @ $alias::OFFSET); }; - // Creates a register at a relative offset from a base address. - ($name:ident @ + $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { + // Creates a register at a relative offset from a base address provider. + ($name:ident @ $base:ty [ $offset:literal ] $(, $comment:literal)? { $($fields:tt)* } ) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io_relative $name @ + $offset); + register!(@io_relative $name @ $base [ $offset ]); }; // Creates an alias register of relative offset register `alias` with its own fields. - ($name:ident => + $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => { + ($name:ident => $base:ty [ $alias:ident ] $(, $comment:literal)? { $($fields:tt)* }) => { register!(@core $name $(, $comment)? { $($fields)* } ); - register!(@io_relative $name @ + $alias::OFFSET); + register!(@io_relative $name @ $base [ $alias::OFFSET ]); }; // All rules below are helpers. @@ -380,39 +427,62 @@ pub(crate) fn alter<const SIZE: usize, T, F>( }; // Generates the IO accessors for a relative offset register. - (@io_relative $name:ident @ + $offset:literal) => { + (@io_relative $name:ident @ $base:ty [ $offset:expr ]) => { #[allow(dead_code)] impl $name { pub(crate) const OFFSET: usize = $offset; + /// Read the register from `io`, using the base address provided by `base` and adding + /// the register's offset to it. #[inline(always)] - pub(crate) fn read<const SIZE: usize, T>( + pub(crate) fn read<const SIZE: usize, T, B>( io: &T, - base: usize, + #[allow(unused_variables)] + base: &B, ) -> Self where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, { - Self(io.read32(base + $offset)) + const OFFSET: usize = $name::OFFSET; + + let value = io.read32( + <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET + ); + + Self(value) } + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the register's offset to it. #[inline(always)] - pub(crate) fn write<const SIZE: usize, T>( + pub(crate) fn write<const SIZE: usize, T, B>( self, io: &T, - base: usize, + #[allow(unused_variables)] + base: &B, ) where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, { - io.write32(self.0, base + $offset) + const OFFSET: usize = $name::OFFSET; + + io.write32( + self.0, + <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET + ); } + /// Read the register from `io`, using the base address provided by `base` and adding + /// the register's offset to it, then run `f` on its value to obtain a new value to + /// write back. #[inline(always)] - pub(crate) fn alter<const SIZE: usize, T, F>( + pub(crate) fn alter<const SIZE: usize, T, B, F>( io: &T, - base: usize, + base: &B, f: F, ) where T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, F: ::core::ops::FnOnce(Self) -> Self, { let reg = f(Self::read(io, base)); -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 15/18] gpu: nova-core: falcon: add distinct base address for PFALCON2
Falcon engines have two distinct register bases: `PFALCON` and `PFALCON2`. So far we assumed that `PFALCON2` was located at `PFALCON + 0x1000` because that is the case of most engines, but there are exceptions (NVDEC uses `0x1c00`). Fix this shortcoming by leveraging the redesigned relative registers definitions to assign a distinct `PFalcon2Base` base address to each falcon engine. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/falcon.rs | 7 ++++++- drivers/gpu/nova-core/falcon/gsp.rs | 6 +++++- drivers/gpu/nova-core/falcon/sec2.rs | 6 +++++- drivers/gpu/nova-core/regs.rs | 12 +++++++----- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs index 66400d07d186938735f705c116d7c7df5fb77fca..23a4ff591c8db8d4600ec43dfaa42233cbf65c0e 100644 --- a/drivers/gpu/nova-core/falcon.rs +++ b/drivers/gpu/nova-core/falcon.rs @@ -263,11 +263,16 @@ fn from(value: bool) -> Self { /// Type used to represent the `PFALCON` registers address base for a given falcon engine. pub(crate) struct PFalconBase(()); +/// Type used to represent the `PFALCON2` registers address base for a given falcon engine. +pub(crate) struct PFalcon2Base(()); + /// Trait defining the parameters of a given Falcon engine. /// /// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used /// to identify a given Falcon instance with register I/O methods. -pub(crate) trait FalconEngine: Sync + RegisterBase<PFalconBase> + Sized { +pub(crate) trait FalconEngine: + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized +{ /// Singleton of the engine, used to identify it with register I/O methods. const ID: Self; } diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs index 0db9f94036a6a7ced5a461aec2cff2ce246a5e0e..f17599cb49fa1e5077a554dc14b3715aa62a4ebd 100644 --- a/drivers/gpu/nova-core/falcon/gsp.rs +++ b/drivers/gpu/nova-core/falcon/gsp.rs @@ -2,7 +2,7 @@ use crate::{ driver::Bar0, - falcon::{Falcon, FalconEngine, PFalconBase}, + falcon::{Falcon, FalconEngine, PFalcon2Base, PFalconBase}, regs::{self, macros::RegisterBase}, }; @@ -13,6 +13,10 @@ impl RegisterBase<PFalconBase> for Gsp { const BASE: usize = 0x00110000; } +impl RegisterBase<PFalcon2Base> for Gsp { + const BASE: usize = 0x00111000; +} + impl FalconEngine for Gsp { const ID: Self = Gsp(()); } diff --git a/drivers/gpu/nova-core/falcon/sec2.rs b/drivers/gpu/nova-core/falcon/sec2.rs index dbc486a712ffce30efa3a4264b0757974962302e..815786c8480db6cb74541d7ab574112baeb816fe 100644 --- a/drivers/gpu/nova-core/falcon/sec2.rs +++ b/drivers/gpu/nova-core/falcon/sec2.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 -use crate::falcon::{FalconEngine, PFalconBase}; +use crate::falcon::{FalconEngine, PFalcon2Base, PFalconBase}; use crate::regs::macros::RegisterBase; /// Type specifying the `Sec2` falcon engine. Cannot be instantiated. @@ -10,6 +10,10 @@ impl RegisterBase<PFalconBase> for Sec2 { const BASE: usize = 0x00840000; } +impl RegisterBase<PFalcon2Base> for Sec2 { + const BASE: usize = 0x00841000; +} + impl FalconEngine for Sec2 { const ID: Self = Sec2(()); } diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 4e047fc4947b6dc09dbd34e26eeda1ff14dff4ad..e6382c116adf699ab24de097c15ddca08017b36d 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -9,7 +9,7 @@ use crate::falcon::{ DmaTrfCmdSize, FalconCoreRev, FalconCoreRevSubversion, FalconFbifMemType, FalconFbifTarget, - FalconModSelAlgo, FalconSecurityModel, PFalconBase, PeregrineCoreSelect, + FalconModSelAlgo, FalconSecurityModel, PFalcon2Base, PFalconBase, PeregrineCoreSelect, }; use crate::gpu::{Architecture, Chipset}; use kernel::prelude::*; @@ -282,20 +282,22 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { 7:7 allow_phys_no_ctx as bool; }); -register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalconBase[0x00001180] { +/* PFALCON2 */ + +register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] { 7:0 algo as u8 ?=> FalconModSelAlgo; }); -register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalconBase[0x00001198] { +register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] { 7:0 ucode_id as u8; }); -register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalconBase[0x0000119c] { +register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] { 31:0 value as u32; }); // TODO[REGA]: this is an array of registers. -register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalconBase[0x00001210] { +register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210] { 31:0 value as u32; }); -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 16/18] gpu: nova-core: register: add support for register arrays
Having registers that can be interpreted identically in a contiguous I/O area (or at least, following a given stride) is a common way to organize registers, and is used by NVIDIA hardware. Thus, add a way to simply and safely declare such a layout using the register!() macro. Build-time bound-checking is effective for array accesses performed with a constant. For cases where the index cannot be known at compile time, `try_` variants of the accessors are also made available that return `EINVAL` if the access is out-of-bounds. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/gpu.rs | 2 +- drivers/gpu/nova-core/regs.rs | 15 +-- drivers/gpu/nova-core/regs/macros.rs | 191 +++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index 8e32af16b669ca773e63e184d34c3e0427bc9b76..d4a0dc2e9a6a3cef368e2f577271f659f0f2c71b 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -221,7 +221,7 @@ fn run_fwsec_frts( fwsec_frts.run(dev, falcon, bar)?; // SCRATCH_E contains the error code for FWSEC-FRTS. - let frts_status = regs::NV_PBUS_SW_SCRATCH_0E::read(bar).frts_err_code(); + let frts_status = regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR::read(bar).frts_err_code(); if frts_status != 0 { dev_err!( dev, diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index e6382c116adf699ab24de097c15ddca08017b36d..95b2c0d6b88f3b04af96f07c055c539870649937 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -44,8 +44,10 @@ pub(crate) fn chipset(self) -> Result<Chipset> { /* PBUS */ -// TODO[REGA]: this is an array of registers. -register!(NV_PBUS_SW_SCRATCH_0E at 0x00001438 { +register!(NV_PBUS_SW_SCRATCH @ 0x00001400[64] {}); + +register!(NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe], + "scratch register 0xe used as FRTS firmware error code" { 31:16 frts_err_code as u16; }); @@ -110,13 +112,12 @@ pub(crate) fn higher_bound(self) -> u64 { 0:0 read_protection_level0 as bool, "Set after FWSEC lowers its protection level"; }); -// TODO[REGA]: This is an array of registers. -register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234 { - 31:0 value as u32; -}); +// OpenRM defines this as a register array, but doesn't specify its size and only uses its first +// element. Be conservative until we know the actual size or need to use more registers. +register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234[1] {}); register!( - NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05, + NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0], "Scratch group 05 register 0 used as GFW boot progress indicator" { 7:0 progress as u8, "Progress of GFW boot (0xff means completed)"; } diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index 1f0375d20b5f4328d496a14120eea8a54123f8cc..af432f0535d2944fa4609f4a44c98a0b1394a2c0 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -132,6 +132,53 @@ pub(crate) trait RegisterBase<T> { /// // Start the aliased `CPU0`. /// CPU_CTL_ALIAS::alter(bar, &CPU0, |r| r.set_alias_start(true)); /// ``` +/// +/// ## Arrays of registers +/// +/// Some I/O areas contain consecutive values that can be interpreted in the same way. These areas +/// can be defined as an array of identical registers, allowing them to be accessed by index with +/// compile-time or runtime bound checking. Simply define their address as `Address[Size]`, and add +/// an `idx` parameter to their `read`, `write` and `alter` methods: +/// +/// ```no_run +/// # fn no_run() -> Result<(), Error> { +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// register!(SCRATCH @ 0x00000c00[64], "Scratch registers" { +/// 31:0 value as u32; +/// }); +/// +/// let scratch_0 = SCRATCH::read(bar, 0).value(); +/// let scratch_15 = SCRATCH::read(bar, 15).value(); +/// // This won't build. +/// // let scratch_128 = SCRATCH::read(bar, 128).value(); +/// +/// // Runtime-obtained array index. +/// let scratch_idx = get_scratch_idx(); +/// // Access on a runtime index returns an error if it is out-of-bounds. +/// let some_scratch = SCRATCH::try_read(bar, scratch_idx)?.value(); +/// +/// // Alias to a particular register in an array. +/// // `SCRATCH[8]` is used to convey the firmware exit code. +/// register!(FIRMWARE_STATUS => SCRATCH[8], "Firmware exit status code" { +/// 7:0 status as u8; +/// }); +/// +/// let status = FIRMWARE_STATUS::read(bar).status(); +/// +/// // Non-contiguous register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the +/// // registers of the two declarations below are interleaved. +/// register!(SCRATCH_INTERLEAVED_0 @ 0x00000d00[16 ; 8], "Scratch registers bank 0" { +/// 31:0 value as u32; +/// }); +/// register!(SCRATCH_INTERLEAVED_1 @ 0x00000d04[16 ; 8], "Scratch registers bank 1" { +/// 31:0 value as u32; +/// }); +/// # Ok(()) +/// # } +/// ``` macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { @@ -157,6 +204,35 @@ macro_rules! register { register!(@io_relative $name @ $base [ $alias::OFFSET ]); }; + // Creates an array of registers at a fixed offset of the MMIO space. + ( + $name:ident @ $offset:literal [ $size:expr ; $stride:expr ] $(, $comment:literal)? { + $($fields:tt)* + } + ) => { + static_assert!(::core::mem::size_of::<u32>() <= $stride); + register!(@core $name $(, $comment)? { $($fields)* } ); + register!(@io_array $name @ $offset [ $size ; $stride ]); + }; + + // Shortcut for contiguous array of registers (stride == size of element). + ( + $name:ident @ $offset:literal [ $size:expr ] $(, $comment:literal)? { + $($fields:tt)* + } + ) => { + register!($name @ $offset [ $size ; ::core::mem::size_of::<u32>() ] $(, $comment)? { + $($fields)* + } ); + }; + + // Creates an alias of register `idx` of array of registers `alias` with its own fields. + ($name:ident => $alias:ident [ $idx:expr ] $(, $comment:literal)? { $($fields:tt)* }) => { + static_assert!($idx < $alias::SIZE); + register!(@core $name $(, $comment)? { $($fields)* } ); + register!(@io_fixed $name @ $alias::OFFSET + $idx * $alias::STRIDE ); + }; + // All rules below are helpers. // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, @@ -490,4 +566,119 @@ pub(crate) fn alter<const SIZE: usize, T, B, F>( } } }; + + // Generates the IO accessors for an array of registers. + (@io_array $name:ident @ $offset:literal [ $size:expr ; $stride:expr ]) => { + #[allow(dead_code)] + impl $name { + pub(crate) const OFFSET: usize = $offset; + pub(crate) const SIZE: usize = $size; + pub(crate) const STRIDE: usize = $stride; + + /// Read the array register at index `idx` from its address in `io`. + #[inline(always)] + pub(crate) fn read<const SIZE: usize, T>( + io: &T, + idx: usize, + ) -> Self where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + build_assert!(idx < Self::SIZE); + + let offset = Self::OFFSET + (idx * Self::STRIDE); + let value = io.read32(offset); + + Self(value) + } + + /// Write the value contained in `self` to the array register with index `idx` in `io`. + #[inline(always)] + pub(crate) fn write<const SIZE: usize, T>( + self, + io: &T, + idx: usize + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + build_assert!(idx < Self::SIZE); + + let offset = Self::OFFSET + (idx * Self::STRIDE); + + io.write32(self.0, offset); + } + + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a + /// new value to write back. + #[inline(always)] + pub(crate) fn alter<const SIZE: usize, T, F>( + io: &T, + idx: usize, + f: F, + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io, idx)); + reg.write(io, idx); + } + + /// Read the array register at index `idx` from its address in `io`. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_read<const SIZE: usize, T>( + io: &T, + idx: usize, + ) -> ::kernel::error::Result<Self> where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + if idx < Self::SIZE { + Ok(Self::read(io, idx)) + } else { + Err(EINVAL) + } + } + + /// Write the value contained in `self` to the array register with index `idx` in `io`. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_write<const SIZE: usize, T>( + self, + io: &T, + idx: usize, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + { + if idx < Self::SIZE { + Ok(self.write(io, idx)) + } else { + Err(EINVAL) + } + } + + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a + /// new value to write back. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_alter<const SIZE: usize, T, F>( + io: &T, + idx: usize, + f: F, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + F: ::core::ops::FnOnce(Self) -> Self, + { + if idx < Self::SIZE { + Ok(Self::alter(io, idx, f)) + } else { + Err(EINVAL) + } + } + } + }; } -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 17/18] gpu: nova-core: falcon: use register arrays for FUSE registers
FUSE registers are an array of 16 consecutive registers. Use the newly available register array feature to define them properly and improve the code using them. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- drivers/gpu/nova-core/falcon/hal/ga102.rs | 33 ++++++++++++++----------------- drivers/gpu/nova-core/regs.rs | 8 +++++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs index 3fdacd19322dd122eb00e245de4be8d1edd61a5f..13c945fd6d6b7b1acbb466678af0bf18da506265 100644 --- a/drivers/gpu/nova-core/falcon/hal/ga102.rs +++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs @@ -42,35 +42,32 @@ fn signature_reg_fuse_version_ga102( engine_id_mask: u16, ucode_id: u8, ) -> Result<u32> { - // TODO[REGA]: The ucode fuse versions are contained in the - // FUSE_OPT_FPF_<ENGINE>_UCODE<X>_VERSION registers, which are an array. Our register - // definition macros do not allow us to manage them properly, so we need to hardcode their - // addresses for now. Clean this up once we support register arrays. + const NV_FUSE_OPT_FPF_SIZE: u8 = regs::NV_FUSE_OPT_FPF_SIZE as u8; // Each engine has 16 ucode version registers numbered from 1 to 16. - if ucode_id == 0 || ucode_id > 16 { - dev_err!(dev, "invalid ucode id {:#x}", ucode_id); - return Err(EINVAL); - } + let ucode_idx = match ucode_id { + 1..=NV_FUSE_OPT_FPF_SIZE => (ucode_id - 1) as usize, + _ => { + dev_err!(dev, "invalid ucode id {:#x}", ucode_id); + return Err(EINVAL); + } + }; - // Base address of the FUSE registers array corresponding to the engine. - let reg_fuse_base = if engine_id_mask & 0x0001 != 0 { - regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::OFFSET + // `ucode_idx` is guaranteed to be in the range [0..15], making the `read` calls provable valid + // at build-time. + let reg_fuse_version = if engine_id_mask & 0x0001 != 0 { + regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data() } else if engine_id_mask & 0x0004 != 0 { - regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::OFFSET + regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data() } else if engine_id_mask & 0x0400 != 0 { - regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::OFFSET + regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::read(bar, ucode_idx).data() } else { dev_err!(dev, "unexpected engine_id_mask {:#x}", engine_id_mask); return Err(EINVAL); }; - // Read `reg_fuse_base[ucode_id - 1]`. - let reg_fuse_version - bar.read32(reg_fuse_base + ((ucode_id - 1) as usize * core::mem::size_of::<u32>())); - // TODO[NUMM]: replace with `last_set_bit` once it lands. - Ok(u32::BITS - reg_fuse_version.leading_zeros()) + Ok(u16::BITS - reg_fuse_version.leading_zeros()) } fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result { diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 95b2c0d6b88f3b04af96f07c055c539870649937..5156ab1910501e86b4391a34549817d2e7818e07 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -168,15 +168,17 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> { /* FUSE */ -register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100 { +pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16; + +register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] { 15:0 data as u16; }); -register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140 { +register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] { 15:0 data as u16; }); -register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0 { +register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] { 15:0 data as u16; }); -- 2.50.0
Alexandre Courbot
2025-Jul-04 07:25 UTC
[PATCH 18/18] gpu: nova-core: registers: add support for relative array registers
Add support for declaring arrays of registers available from a variable base. This is effectively a combination of the relative and array registers features. nova-core does not make much use of this yet, but it will become helpful to have for GSP boot. Signed-off-by: Alexandre Courbot <acourbot at nvidia.com> --- Documentation/gpu/nova/core/todo.rst | 1 - drivers/gpu/nova-core/falcon.rs | 2 +- drivers/gpu/nova-core/falcon/hal/ga102.rs | 2 +- drivers/gpu/nova-core/regs.rs | 8 +- drivers/gpu/nova-core/regs/macros.rs | 241 ++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+), 7 deletions(-) diff --git a/Documentation/gpu/nova/core/todo.rst b/Documentation/gpu/nova/core/todo.rst index a1d12c1b289d89251d914fc271b7243ced11d487..48b20656dcb16056db7784fa186f161126aae9aa 100644 --- a/Documentation/gpu/nova/core/todo.rst +++ b/Documentation/gpu/nova/core/todo.rst @@ -131,7 +131,6 @@ crate so it can be used by other components as well. Features desired before this happens: -* Arrays of registers with build-time index validation, * Make I/O optional I/O (for field values that are not registers), * Support other sizes than `u32`, * Allow visibility control for registers and individual fields, diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs index 23a4ff591c8db8d4600ec43dfaa42233cbf65c0e..2929032a97a6ccecb2d090b82af966991b53797b 100644 --- a/drivers/gpu/nova-core/falcon.rs +++ b/drivers/gpu/nova-core/falcon.rs @@ -501,7 +501,7 @@ fn dma_wr<F: FalconFirmware<Target = E>>( pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result { regs::NV_PFALCON_FBIF_CTL::alter(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true)); regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID); - regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, &E::ID, |v| { + regs::NV_PFALCON_FBIF_TRANSCFG::alter(bar, &E::ID, 0, |v| { v.set_target(FalconFbifTarget::CoherentSysmem) .set_mem_type(FalconFbifMemType::Physical) }); diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs index 13c945fd6d6b7b1acbb466678af0bf18da506265..0b1cbe7853b3e85cb9c03f8e3987ec50a30253fb 100644 --- a/drivers/gpu/nova-core/falcon/hal/ga102.rs +++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs @@ -73,7 +73,7 @@ fn signature_reg_fuse_version_ga102( fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result { regs::NV_PFALCON2_FALCON_BROM_PARAADDR::default() .set_value(params.pkc_data_offset) - .write(bar, &E::ID); + .write(bar, &E::ID, 0); regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default() .set_value(u32::from(params.engine_id_mask)) .write(bar, &E::ID); diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 5156ab1910501e86b4391a34549817d2e7818e07..0a5ccd845eae755c3dbfe25971b6d0cdf4189f8e 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -275,8 +275,7 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { 0:0 reset as bool; }); -// TODO[REGA]: this is an array of registers. -register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600] { +register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] { 1:0 target as u8 ?=> FalconFbifTarget; 2:2 mem_type as bool => FalconFbifMemType; }); @@ -299,8 +298,9 @@ pub(crate) fn mem_scrubbing_done(self) -> bool { 31:0 value as u32; }); -// TODO[REGA]: this is an array of registers. -register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210] { +// OpenRM defines this as a register array, but doesn't specify its size and only uses its first +// element. Be conservative until we know the actual size or need to use more registers. +register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210[1]] { 31:0 value as u32; }); diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index af432f0535d2944fa4609f4a44c98a0b1394a2c0..0a342f1ac665fc99eecb08e61889427543f63bc8 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -179,6 +179,73 @@ pub(crate) trait RegisterBase<T> { /// # Ok(()) /// # } /// ``` +/// +/// ## Relative arrays of registers +/// +/// Combining the two features described in the sections above, arrays of registers accessible from +/// a base can also be defined: +/// +/// ```no_run +/// # fn no_run() -> Result<(), Error> { +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// // Type used as parameter of `RegisterBase` to specify the base. +/// pub(crate) struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0xf00; +/// } +/// // Singleton of `CPU0` used to identify it. +/// const CPU0: Cpu0 = Cpu0; +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x1f00; +/// } +/// // Singleton of `CPU1` used to identify it. +/// const CPU1: Cpu1 = Cpu1; +/// +/// // 64 per-cpu scratch registers, arranged as an contiguous array. +/// register!(CPU_SCRATCH @ CpuCtlBase[0x1000[64]], "Per-CPU scratch registers" { +/// 31:0 value as u32; +/// }); +/// +/// let cpu0_scratch_0 = CPU_SCRATCH::read(bar, &Cpu0, 0).value(); +/// let cpu1_scratch_15 = CPU_SCRATCH::read(bar, &Cpu1, 15).value(); +/// // This won't build. +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(bar, &Cpu0, 128).value(); +/// +/// // Runtime-obtained array index. +/// let scratch_idx = get_scratch_idx(); +/// // Access on a runtime value returns an error if it is out-of-bounds. +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(bar, &Cpu0, scratch_idx)?.value(); +/// +/// // `SCRATCH[8]` is used to convey the firmware exit code. +/// register!(CPU_FIRMWARE_STATUS => CpuCtlBase[CPU_SCRATCH[8]], +/// "Per-CPU firmware exit status code" { +/// 7:0 status as u8; +/// }); +/// +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(bar, &Cpu0).status(); +/// +/// // Non-contiguous register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the +/// // registers of the two declarations below are interleaved. +/// register!(CPU_SCRATCH_INTERLEAVED_0 @ CpuCtlBase[0x00000d00[16 ; 8]], +/// "Scratch registers bank 0" { +/// 31:0 value as u32; +/// }); +/// register!(CPU_SCRATCH_INTERLEAVED_1 @ CpuCtlBase[0x00000d04[16 ; 8]], +/// "Scratch registers bank 1" { +/// 31:0 value as u32; +/// }); +/// # Ok(()) +/// # } +/// ``` macro_rules! register { // Creates a register at a fixed offset of the MMIO space. ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => { @@ -226,7 +293,41 @@ macro_rules! register { } ); }; + // Creates an array of registers at a relative offset from a base address provider. + ( + $name:ident @ $base:ty [ $offset:literal [ $size:expr ; $stride:expr ] ] + $(, $comment:literal)? { $($fields:tt)* } + ) => { + static_assert!(::core::mem::size_of::<u32>() <= $stride); + register!(@core $name $(, $comment)? { $($fields)* } ); + register!(@io_relative_array $name @ $base [ $offset [ $size ; $stride ] ]); + }; + + // Shortcut for contiguous array of relative registers (stride == size of element). + ( + $name:ident @ $base:ty [ $offset:literal [ $size:expr ] ] $(, $comment:literal)? { + $($fields:tt)* + } + ) => { + register!($name @ $base [ $offset [ $size ; ::core::mem::size_of::<u32>() ] ] + $(, $comment)? { $($fields)* } ); + }; + + // Creates an alias of register `idx` of relative array of registers `alias` with its own + // fields. + ( + $name:ident => $base:ty [ $alias:ident [ $idx:expr ] ] $(, $comment:literal)? { + $($fields:tt)* + } + ) => { + static_assert!($idx < $alias::SIZE); + register!(@core $name $(, $comment)? { $($fields)* } ); + register!(@io_relative $name @ $base [ $alias::OFFSET + $idx * $alias::STRIDE ] ); + }; + // Creates an alias of register `idx` of array of registers `alias` with its own fields. + // This rule belongs to the (non-relative) register arrays set, but needs to be put last + // to avoid it being interpreted in place of the relative register array alias rule. ($name:ident => $alias:ident [ $idx:expr ] $(, $comment:literal)? { $($fields:tt)* }) => { static_assert!($idx < $alias::SIZE); register!(@core $name $(, $comment)? { $($fields)* } ); @@ -681,4 +782,144 @@ pub(crate) fn try_alter<const SIZE: usize, T, F>( } } }; + + // Generates the IO accessors for an array of relative registers. + ( + @io_relative_array $name:ident @ $base:ty + [ $offset:literal [ $size:expr ; $stride:expr ] ] + ) => { + #[allow(dead_code)] + impl $name { + pub(crate) const OFFSET: usize = $offset; + pub(crate) const SIZE: usize = $size; + pub(crate) const STRIDE: usize = $stride; + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it. + #[inline(always)] + pub(crate) fn read<const SIZE: usize, T, B>( + io: &T, + #[allow(unused_variables)] + base: &B, + idx: usize, + ) -> Self where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + { + build_assert!(idx < Self::SIZE); + + let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE + + Self::OFFSET + (idx * Self::STRIDE); + let value = io.read32(offset); + + Self(value) + } + + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the offset of array register `idx` to it. + #[inline(always)] + pub(crate) fn write<const SIZE: usize, T, B>( + self, + io: &T, + #[allow(unused_variables)] + base: &B, + idx: usize + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + { + build_assert!(idx < Self::SIZE); + + let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE + + Self::OFFSET + (idx * Self::STRIDE); + + io.write32(self.0, offset); + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it, then run `f` on its value to + /// obtain a new value to write back. + #[inline(always)] + pub(crate) fn alter<const SIZE: usize, T, B, F>( + io: &T, + base: &B, + idx: usize, + f: F, + ) where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io, base, idx)); + reg.write(io, base, idx); + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_read<const SIZE: usize, T, B>( + io: &T, + base: &B, + idx: usize, + ) -> ::kernel::error::Result<Self> where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + { + if idx < Self::SIZE { + Ok(Self::read(io, base, idx)) + } else { + Err(EINVAL) + } + } + + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the offset of array register `idx` to it. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_write<const SIZE: usize, T, B>( + self, + io: &T, + base: &B, + idx: usize, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + { + if idx < Self::SIZE { + Ok(self.write(io, base, idx)) + } else { + Err(EINVAL) + } + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it, then run `f` on its value to + /// obtain a new value to write back. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the + /// access was out-of-bounds. + #[inline(always)] + pub(crate) fn try_alter<const SIZE: usize, T, B, F>( + io: &T, + base: &B, + idx: usize, + f: F, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>, + B: crate::regs::macros::RegisterBase<$base>, + F: ::core::ops::FnOnce(Self) -> Self, + { + if idx < Self::SIZE { + Ok(Self::alter(io, base, idx, f)) + } else { + Err(EINVAL) + } + } + } + }; } -- 2.50.0