Re: [PATCH v6 0/9] rust: add `register!` macro

From: Alexandre Courbot

Date: Sun Feb 22 2026 - 08:25:56 EST


Hi Dirk,

On Fri Feb 20, 2026 at 10:20 PM JST, Dirk Behme wrote:
> Hi Alexandre,
>
> On 16.02.26 09:04, Alexandre Courbot wrote:
>> This new revision took some time because it is (yet another) overhaul.
>> ^_^;
>>
>> Thanks to a breakthrough by Gary, we found a way to have the I/O type
>> perform the actual I/O instead of the register type, which moves us from
>> this access pattern:
>>
>> let boot0 = regs::NV_PMC_BOOT_0::read(bar);
>>
>> to this arguably more natural one:
>>
>> let boot0 = bar.read(regs::NV_PMC_BOOT_0);
>>
>> It also has the benefit of taking advantage of deref coercion for types
>> that wrap an `Io`, something the register-based methods couldn't do and
>> which would have required extra `AsRef` implementations just for this
>> purpose.
>>
>> Furthermore, this resolves the inconsistency of the former register API
>> that couldn't use the `try_` I/O accessors (and even had methods whose
>> names clashed with them). Now if `Io` supports it, it can be done on a
>> register.
>>
>> Another benefit is that there is less work done within macros, and more
>> in generic code, which is (generally) a win for readability. The
>> `register!` macro is considerably smaller and easier to work on, and now
>> mostly made up of the bitfield accessors that will eventually be moved
>> into another macro.
>>
>> I decided to remove a couple of tags because the code has changed quite
>> a bit since they were obtained.
>
> Last time I gave this a try was with v2. From my aarch64 simple timer
> test on that version I have [1] below. Could you give a hint how to
> convert this to v6? :)

Yeah I'm sorry, this is quite a heavy change. An LLM should do a good
job at updating your code once you give it an example.

>
> Many thanks!
>
> Dirk
>
> [1]
>
> register!(TCR(u16) @ 0x10 {
> 9:9 icpf;
> 8:8 unf;
> 7:6 icpe;
> 5:5 unie;
> 4:3 ckeg;
> 2:0 tpsc;
> });

This would become:

register! {
TCR(u16) @ 0x10 {
9:9 icpf;
8:8 unf;
7:6 icpe;
5:5 unie;
4:3 ckeg;
2:0 tpsc;
}
}

This part doesn't change much - it would however if the fields were
documented.

Note also that you can now declare several registers in the same
`register!` invocation.

>
>
> impl TCR {
> fn handle_underflow<const SIZE: usize, T>(io: &T)
> where
> T: Deref<Target = Io<SIZE>>,

`Io` is a trait now, so this will need updating as well.

> {
> let tcr = Self::read(io);

// Note `TCR` exists as both the type and the contant. `read`
// expects the latter, so you cannot use `Self` here.
let tcr = io.read(TCR);

> if tcr.unf().into() {
> tcr.set_unf(false).write(io);
> }

if tcr.unf().into_bool() {
// You will need to have `IoRef` (`IoLoc` in v7) in scope
// for `set` to be visible.
io.write(TCR.set(tcr.with_unf(false)));
}

The direction of `read` and `write` is now more natural (and more
flexible with respect to the way I/O now works).

I am also contemplating allowing the following syntax for the write:

io.write(tcr.with_unf(false));

which is shorter, but will also only work for fixed registers. Guess I
should ask for opinions as an RFC patch.

Hope this helps - let me know if anything is unclear or if you notice
pain points!