All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Alexandre Courbot" <acourbot@nvidia.com>
To: "Gary Guo" <gary@garyguo.net>
Cc: "Danilo Krummrich" <dakr@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Yury Norov" <yury.norov@gmail.com>,
	"John Hubbard" <jhubbard@nvidia.com>,
	"Alistair Popple" <apopple@nvidia.com>,
	"Joel Fernandes" <joelagnelf@nvidia.com>,
	"Timur Tabi" <ttabi@nvidia.com>, "Edwin Peer" <epeer@nvidia.com>,
	"Eliot Courtney" <ecourtney@nvidia.com>,
	"Dirk Behme" <dirk.behme@de.bosch.com>,
	"Steven Price" <steven.price@arm.com>,
	rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2 4/5] rust: io: add `register!` macro
Date: Mon, 26 Jan 2026 12:24:11 +0900	[thread overview]
Message-ID: <DFY76VCL7KO7.29FCRAOOY7FVN@nvidia.com> (raw)
In-Reply-To: <DFUCNHHYB6VV.43MOY4Y1NV06@garyguo.net>

On Wed Jan 21, 2026 at 11:50 PM JST, Gary Guo wrote:
> On Wed Jan 21, 2026 at 7:23 AM GMT, Alexandre Courbot wrote:
>> Add a macro for defining hardware register types with I/O accessors.
>>
>> Each register field is represented as a `Bounded` of the appropriate bit
>> width, ensuring field values are never silently truncated.
>>
>> Fields can optionally be converted to/from custom types, either fallibly
>> or infallibly.
>>
>> The address of registers can be direct, relative, or indexed, supporting
>> most of the patterns in which registers are arranged.
>>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>>  rust/kernel/io.rs          |    1 +
>>  rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 1199 insertions(+)
>>
>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>> index a97eb44a9a87..eccaa176b6b9 100644
>> --- a/rust/kernel/io.rs
>> +++ b/rust/kernel/io.rs
>> @@ -11,6 +11,7 @@
>>  
>>  pub mod mem;
>>  pub mod poll;
>> +pub mod register;
>>  pub mod resource;
>>  
>>  pub use resource::Resource;
>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
>> new file mode 100644
>> index 000000000000..e414aebe4c86
>> --- /dev/null
>> +++ b/rust/kernel/io/register.rs
>> @@ -0,0 +1,1198 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +/// 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.
>> +///
>> +/// A register is essentially a [`bitfield!`] with I/O capabilities. The syntax of the `register!`
>> +/// macro reflects that fact, being essentially identical to that of [`bitfield!`] with the
>> +/// addition of addressing information after the `@` token.
>> +///
>> +/// Example:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
>> +///     7:4 major_revision, "Major revision of the chip";
>> +///     3:0 minor_revision, "Minor revision of the chip";
>> +/// });
>
> The comment is inserted as doc comment, but it uses the string syntax.
>
> I guess the idea is that you want write everything in a single line so you can
> visually align the fields? I think it
> looks fine on the fields, but the same-line documentation of the type itself
> looks a bit off.
>
> Something like this will definitely feel much more Rusty:
>
> register!(
>     /// Basic revision information about the chip.
>     pub struct BOOT_0(u32) @ 0x00000100 {
>         /// Major revision of the chip.
>         major_version: [7:4],
>         /// Minor revision of the chip.
>         ///
>         /// This would also allow you easily expand the documentation into
>         /// multiple lines!
>         ///
>         /// Perhaps useful to document some quirks about the register!
>         /// I know currently registers and their fields are very underdocumented
>         /// and they probably don't need multiple lines, but I hope that'll not
>         /// true in the future and we would have detailed docs in the driver --
>         /// in which case visually aligning becomes impossible anyway.
>         minor_version: [3:0],
>         //           ^~ closer to the variable syntax in Rust
>         //             ^~ I keep the hi:lo syntax which I suppose is to reflect Verilog.
>     }
> )

That would definitely be better, unfortunately since this is a
declarative macro it cannot match against comments, hence the current
syntax.

>
> Another top-level question I have is whether we should define multiple registers
> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
> crate which is widely used in embedded Rust.

I agree that would be nice, especially as it opens the way for better
syntax for e.g. register blocks with a base.

I am having trouble adding that to the current design though. Some rules
use the tt muncher pattern and these will greedily consume all tokens,
including those for the next register. I am a bit at my wit's end here,
but if you have an idea for how to do it nicely then by all means. :)

>
> (Although, they use a completely different strategy in generating register
> mapping; all registers are becoming fields in the same struct).
>
>> +/// ```
>> +///
>> +/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
>> +/// region. 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:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +/// use kernel::num::Bounded;
>> +///
>> +/// # register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
>> +/// #     7:4 major_revision, "Major revision of the chip";
>> +/// #     3:0 minor_revision, "Minor revision of the chip";
>> +/// # });
>> +/// # fn test(bar: &kernel::io::Io) {
>> +/// // Read from the register's defined offset (0x100).
>> +/// let boot0 = BOOT_0::read(&bar);
>> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
>> +///
>> +/// // Update some fields and write the value back.
>> +/// boot0
>> +///     .set_major_revision(Bounded::<u32, _>::new::<3>())
>> +///     .set_minor_revision(Bounded::<u32, _>::new::<10>())
>
> This looks very verbose...

Yes, it is a bit unfortunate (although real-life code is unlikely to be
that verbose if you look at the top patch).

And a bit frustrating as one could expect the first generic parameter to
be inferred, so we could write `Bounded::new<3>()`. But due to how the
`new` constructor of `Bounded` is declared (using one dedicated
impl block per dedicated type as we cannot use generics with const
code), the compiler requires the backing type to be explicitly
specified. :/

Once we have the ability to use const trait methods this can be improved
though.

>
>> +///     .write(&bar);
>> +///
>> +/// // Or, just read and update the register in a single step:
>> +/// BOOT_0::update(&bar, |r| r
>> +///     .set_major_revision(Bounded::<u32, _>::new::<3>())
>> +///     .set_minor_revision(Bounded::<u32, _>::new::<10>())
>> +/// );
>> +/// # }
>> +/// ```
>> +///
>> +/// 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.
>> +///
>> +/// Attributes can be applied to the generated struct. The `#[allow(non_camel_case_types)]`
>> +/// attribute is automatically added since register names typically use SCREAMING_CASE:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register! {
>> +///     pub STATUS(u32) @ 0x00000000, "Status register" {
>> +///         0:0 ready, "Device ready flag";
>> +///     }
>> +/// }
>> +/// ```
>> +///
>> +/// It is also possible to create an alias register by using the `=> ALIAS` syntax. This is useful
>> +/// for cases where a register's interpretation depends on the context:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register!(pub SCRATCH(u32) @ 0x00000200, "Scratch register" {
>> +///     31:0 value, "Raw value";
>> +/// });
>> +///
>> +/// register!(pub SCRATCH_BOOT_STATUS(u32) => SCRATCH, "Boot status of the firmware" {
>
> How about "as"?
>
> register!(pub SCRATCH_BOOT_STATUS(u32) as SCRATCH);

I'd rather not, this could be interpreted as the conversion being done
using the `as` keyword while we are using `Into`.

>
>> +///     0:0 completed, "Whether the firmware has completed booting";
>> +/// });
>> +/// ```
>> +///
>> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, 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
>> +/// instance, imagine the following I/O space:
>> +///
>> +/// ```text
>> +///           +-----------------------------+
>> +///           |             ...             |
>> +///           |                             |
>> +///  0x100--->+------------CPU0-------------+
>> +///           |                             |
>> +///  0x110--->+-----------------------------+
>> +///           |           CPU_CTL           |
>> +///           +-----------------------------+
>> +///           |             ...             |
>> +///           |                             |
>> +///           |                             |
>> +///  0x200--->+------------CPU1-------------+
>> +///           |                             |
>> +///  0x210--->+-----------------------------+
>> +///           |           CPU_CTL           |
>> +///           +-----------------------------+
>> +///           |             ...             |
>> +///           +-----------------------------+
>> +/// ```
>> +///
>> +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O
>> +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define
>> +/// them twice and would prefer a way to select which one to use from a single definition
>> +///
>> +/// This can be done using 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`] trait to provide the base as a constant, i.e. each type providing a base for
>> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
>> +/// into code:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +/// use kernel::io::register::RegisterBase;
>> +///
>> +/// // Type used to identify the base.
>> +/// pub struct CpuCtlBase;
>> +///
>> +/// // ZST describing `CPU0`.
>> +/// struct Cpu0;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>> +///     const BASE: usize = 0x100;
>> +/// }
>> +/// // Singleton of `CPU0` used to identify it.
>> +/// const CPU0: Cpu0 = Cpu0;
>> +///
>> +/// // ZST describing `CPU1`.
>> +/// struct Cpu1;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>> +///     const BASE: usize = 0x200;
>> +/// }
>> +/// // Singleton of `CPU1` used to identify it.
>> +/// const CPU1: Cpu1 = Cpu1;
>> +///
>> +/// # fn test(bar: &kernel::io::Io) {
>> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
>> +/// register!(pub CPU_CTL(u32) @ CpuCtlBase[0x10], "CPU core control" {
>
> Maybe `CpuCtlBase + 0x10`? I think this means the offset is going to be differ
> by 0x10, not that it's going to be the 17th register?

Yup, I tend to agree with you on the readability side. This requires
changing some captures from `ty` to `ident` as `+` is not allowed after
`ty` though, which will forbid namespaces from being specified in the
type's path. Do you think this is an acceptable compromise?

>
>> +///     0:0 start, "Start the CPU core";
>> +/// });
>> +///
>> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
>> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the
>> +/// // register.
>> +///
>> +/// // Start `CPU0`.
>> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true));
>> +///
>> +/// // Start `CPU1`.
>> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true));
>> +///
>> +/// // Aliases can also be defined for relative register.
>> +/// register!(pub CPU_CTL_ALIAS(u32) => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
>> +///     1:1 alias_start, "Start the aliased CPU core";
>> +/// });
>> +///
>> +/// // Start the aliased `CPU0`.
>> +/// CPU_CTL_ALIAS::update(&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 `update` methods:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +///
>> +/// # fn no_run(bar: &kernel::io::Io) -> Result<(), Error> {
>> +/// # fn get_scratch_idx() -> usize {
>> +/// #   0x15
>> +/// # }
>> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
>> +/// register!(pub SCRATCH(u32) @ 0x00000080[64], "Scratch registers" {
>
> This syntax is way to close to the base syntax above.
>
> Perhaps `pub SCRATCH([u32; 64]) @ 0x00000080` would make more sense?

So we also need to specify the optional stride of the array, which adds
one more parameter. We can go with the following though:

  pub SCRATCH(u32)[64] @ 0x00000080

or with the stride:

  pub SCRATCH(u32)[64; 16] @ 0x00000080

How does it look?

>
>> +///     31:0 value;
>> +/// });
>> +///
>> +/// // Read scratch register 0, i.e. I/O address `0x80`.
>> +/// let scratch_0 = SCRATCH::read(&bar, 0).value();
>> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
>> +/// let scratch_15 = SCRATCH::read(&bar, 15).value();
>> +///
>> +/// // This is out of bounds and 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.
>> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
>> +/// register!(pub FIRMWARE_STATUS(u32) => SCRATCH[8], "Firmware exit status code" {
>> +///     7:0 status;
>> +/// });
>> +///
>> +/// 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!(pub SCRATCH_INTERLEAVED_0(u32) @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
>> +///     31:0 value;
>> +/// });
>> +/// register!(pub SCRATCH_INTERLEAVED_1(u32) @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
>> +///     31:0 value;
>> +/// });
>> +/// # 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
>> +/// use kernel::register;
>> +/// use kernel::io::register::RegisterBase;
>> +///
>> +/// # fn no_run(bar: &kernel::io::Io) -> Result<(), Error> {
>> +/// # fn get_scratch_idx() -> usize {
>> +/// #   0x15
>> +/// # }
>> +/// // Type used as parameter of `RegisterBase` to specify the base.
>> +/// pub struct CpuCtlBase;
>> +///
>> +/// // ZST describing `CPU0`.
>> +/// struct Cpu0;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>> +///     const BASE: usize = 0x100;
>> +/// }
>> +/// // Singleton of `CPU0` used to identify it.
>> +/// const CPU0: Cpu0 = Cpu0;
>> +///
>> +/// // ZST describing `CPU1`.
>> +/// struct Cpu1;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>> +///     const BASE: usize = 0x200;
>> +/// }
>> +/// // Singleton of `CPU1` used to identify it.
>> +/// const CPU1: Cpu1 = Cpu1;
>> +///
>> +/// // 64 per-cpu scratch registers, arranged as a contiguous array.
>> +/// register!(pub CPU_SCRATCH(u32) @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
>
> Ah... I don't like this.

If we adopt your syntax suggestions this should thankfully look much
better.

>
>> +///     31:0 value;
>> +/// });
>> +///
>> +/// 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!(pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase[CPU_SCRATCH[8]],
>> +///     "Per-CPU firmware exit status code" {
>> +///     7:0 status;
>> +/// });
>> +///
>> +/// 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!(pub CPU_SCRATCH_INTERLEAVED_0(u32) @ CpuCtlBase[0x00000d00[16 ; 8]],
>
> For striding, SystemRDL uses syntax like this:
>
>     type name[len] @ addr += stride;
>
> which I think is better as this won't be confused with array length syntax in
> Rust.
>
> Crazy idea: is it possible to just use SystemRDL (maybe via a proc macro).

I wasn't familiar with SystemRDL, your comment made me take a look.
Interestingly some of the syntax is already close to that of this macro
(notably the use of `@`). That makes me think that we should try to
align when possible/relevant.

There are also lots of ideas that we could probably integrate (like
register blocks and address maps), but parsing the syntax as-is seems
overkill to me, and would delay this work quite a bit as we would
probably reach the limits of what we can do with declarative macros.

  parent reply	other threads:[~2026-01-26  3:24 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-21  7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
2026-01-21  7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
2026-01-21 11:48   ` Gary Guo
2026-01-21  7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
2026-01-21 14:12   ` Gary Guo
2026-01-26  3:23     ` Alexandre Courbot
2026-01-26  3:28       ` Miguel Ojeda
2026-01-21 17:49   ` kernel test robot
2026-01-21  7:23 ` [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot
2026-01-21 14:13   ` Gary Guo
2026-01-21  7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
2026-01-21 13:13   ` Alexandre Courbot
2026-01-21 14:15   ` Gary Guo
2026-01-26  3:23     ` Alexandre Courbot
2026-01-21 14:50   ` Gary Guo
2026-01-21 16:15     ` Miguel Ojeda
2026-01-26  4:31       ` John Hubbard
2026-01-26  4:33         ` John Hubbard
2026-01-26  3:24     ` Alexandre Courbot [this message]
2026-01-26  6:57       ` Alexandre Courbot
2026-01-26  7:45       ` Alexandre Courbot
2026-01-26 11:46         ` Alexandre Courbot
2026-01-21 21:39   ` kernel test robot
2026-01-21  7:23 ` [PATCH FOR REFERENCE v2 5/5] gpu: nova-core: use the kernel " Alexandre Courbot
2026-01-21  9:16 ` [PATCH v2 0/5] rust: add " Dirk Behme

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DFY76VCL7KO7.29FCRAOOY7FVN@nvidia.com \
    --to=acourbot@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=aliceryhl@google.com \
    --cc=apopple@nvidia.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=dirk.behme@de.bosch.com \
    --cc=ecourtney@nvidia.com \
    --cc=epeer@nvidia.com \
    --cc=gary@garyguo.net \
    --cc=jhubbard@nvidia.com \
    --cc=joelagnelf@nvidia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=steven.price@arm.com \
    --cc=tmgross@umich.edu \
    --cc=ttabi@nvidia.com \
    --cc=yury.norov@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.