tock_registers/
macros.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Macros for cleanly defining peripheral registers.
6
7#[macro_export]
8macro_rules! register_fields {
9    // Macro entry point.
10    (@root $(#[$attr_struct:meta])* $vis_struct:vis $name:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
11        $crate::register_fields!(
12            @munch (
13                $($input)*
14            ) -> {
15                $vis_struct struct $(#[$attr_struct])* $name $(<$life>)?
16            }
17        );
18    };
19
20    // Print the struct once all fields have been munched.
21    (@munch
22        (
23            $(#[$attr_end:meta])*
24            ($offset:expr => @END),
25        )
26        -> {$vis_struct:vis struct $(#[$attr_struct:meta])* $name:ident $(<$life:lifetime>)? $(
27                $(#[$attr:meta])*
28                ($vis:vis $id:ident: $ty:ty)
29            )*}
30    ) => {
31        $(#[$attr_struct])*
32        #[repr(C)]
33        $vis_struct struct $name $(<$life>)? {
34            $(
35                $(#[$attr])*
36                $vis $id: $ty
37            ),*
38        }
39    };
40
41    // Munch field.
42    (@munch
43        (
44            $(#[$attr:meta])*
45            ($offset_start:expr => $vis:vis $field:ident: $ty:ty),
46            $($after:tt)*
47        )
48        -> {$($output:tt)*}
49    ) => {
50        $crate::register_fields!(
51            @munch (
52                $($after)*
53            ) -> {
54                $($output)*
55                $(#[$attr])*
56                ($vis $field: $ty)
57            }
58        );
59    };
60
61    // Munch padding.
62    (@munch
63        (
64            $(#[$attr:meta])*
65            ($offset_start:expr => $padding:ident),
66            $(#[$attr_next:meta])*
67            ($offset_end:expr => $($next:tt)*),
68            $($after:tt)*
69        )
70        -> {$($output:tt)*}
71    ) => {
72        $crate::register_fields!(
73            @munch (
74                $(#[$attr_next])*
75                ($offset_end => $($next)*),
76                $($after)*
77            ) -> {
78                $($output)*
79                $(#[$attr])*
80                ($padding: [u8; $offset_end - $offset_start])
81            }
82        );
83    };
84}
85
86// TODO: All of the rustdoc tests below use a `should_fail` attribute instead of
87// `should_panic` because a const panic will result in a failure to evaluate a
88// constant value, and thus a compiler error. However, this means that these
89// examples could break for unrelated reasons, trigger a compiler error, but not
90// test the desired assertion any longer. This should be switched to a
91// `should_panic`-akin attribute which works for const panics, once that is
92// available.
93/// Statically validate the size and offsets of the fields defined within the
94/// register struct through the `register_structs!()` macro.
95///
96/// This macro expands to an expression which contains static assertions about
97/// various parameters of the individual fields in the register struct
98/// definition. It will test for:
99///
100/// - Proper start offset of padding fields. It will fail in cases such as
101///
102///   ```should_fail
103///   # #[macro_use]
104///   # extern crate tock_registers;
105///   # use tock_registers::register_structs;
106///   # use tock_registers::registers::ReadWrite;
107///   register_structs! {
108///       UartRegisters {
109///           (0x04 => _reserved),
110///           (0x08 => foo: ReadWrite<u32>),
111///           (0x0C => @END),
112///       }
113///   }
114///   # // This is required for rustdoc to not place this code snipped into an
115///   # // fn main() {...} function.
116///   # fn main() { }
117///   ```
118///
119///   In this example, the start offset of `_reserved` should have been `0x00`
120///   instead of `0x04`.
121///
122/// - Correct start offset and end offset (start offset of next field) in actual
123///   fields. It will fail in cases such as
124///
125///   ```should_fail
126///   # #[macro_use]
127///   # extern crate tock_registers;
128///   # use tock_registers::register_structs;
129///   # use tock_registers::registers::ReadWrite;
130///   register_structs! {
131///       UartRegisters {
132///           (0x00 => foo: ReadWrite<u32>),
133///           (0x05 => bar: ReadWrite<u32>),
134///           (0x08 => @END),
135///       }
136///   }
137///   # // This is required for rustdoc to not place this code snipped into an
138///   # // fn main() {...} function.
139///   # fn main() { }
140///   ```
141///
142///   In this example, the start offset of `bar` and thus the end offset of
143///   `foo` should have been `0x04` instead of `0x05`.
144///
145/// - Invalid alignment of fields.
146///
147/// - That the end marker matches the actual generated struct size. This will
148/// fail in cases such as
149///
150///   ```should_fail
151///   # #[macro_use]
152///   # extern crate tock_registers;
153///   # use tock_registers::register_structs;
154///   # use tock_registers::registers::ReadWrite;
155///   register_structs! {
156///       UartRegisters {
157///           (0x00 => foo: ReadWrite<u32>),
158///           (0x04 => bar: ReadWrite<u32>),
159///           (0x10 => @END),
160///       }
161///   }
162///   # // This is required for rustdoc to not place this code snipped into an
163///   # // fn main() {...} function.
164///   # fn main() { }
165///   ```
166#[macro_export]
167macro_rules! test_fields {
168    // This macro works by iterating over all defined fields, until it hits an
169    // ($size:expr => @END) field. Each iteration generates an expression which,
170    // when evaluated, yields the current byte offset in the fields. Thus, when
171    // reading a field or padding, the field or padding length must be added to
172    // the returned size.
173    //
174    // By feeding this expression recursively into the macro, deeper invocations
175    // can continue validating fields through knowledge of the current offset
176    // and the remaining fields.
177    //
178    // The nested expression returned by this macro is guaranteed to be
179    // const-evaluable.
180
181    // Macro entry point.
182    (@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
183        // Start recursion at offset 0.
184        $crate::test_fields!(@munch $struct $(<$life>)? ($($input)*) : (0, 0));
185    };
186
187    // Consume the ($size:expr => @END) field, which MUST be the last field in
188    // the register struct.
189    (@munch $struct:ident $(<$life:lifetime>)?
190        (
191            $(#[$attr_end:meta])*
192            ($size:expr => @END),
193        )
194        : $stmts:expr
195    ) => {
196        const _: () = {
197            // We've reached the end! Normally it is sufficient to compare the
198            // struct's size to the reported end offet. However, we must
199            // evaluate the previous iterations' expressions for them to have an
200            // effect anyways, so we can perform an internal sanity check on
201            // this value as well.
202            const SUM_MAX_ALIGN: (usize, usize) = $stmts;
203            const SUM: usize = SUM_MAX_ALIGN.0;
204            const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
205
206            // Internal sanity check. If we have reached this point and
207            // correctly iterated over the struct's fields, the current offset
208            // and the claimed end offset MUST be equal.
209            assert!(SUM == $size);
210
211            const STRUCT_SIZE: usize = core::mem::size_of::<$struct $(<$life>)?>();
212            const ALIGNMENT_CORRECTED_SIZE: usize = if $size % MAX_ALIGN != 0 { $size + (MAX_ALIGN - ($size % MAX_ALIGN)) } else { $size };
213
214            assert!(
215                STRUCT_SIZE == ALIGNMENT_CORRECTED_SIZE,
216                "{}",
217                concat!(
218                    "Invalid size for struct ",
219                    stringify!($struct),
220                    " (expected ",
221                    stringify!($size),
222                    ", actual struct size differs)",
223                ),
224            );
225        };
226    };
227
228    // Consume a proper ($offset:expr => $field:ident: $ty:ty) field.
229    (@munch $struct:ident $(<$life:lifetime>)?
230        (
231            $(#[$attr:meta])*
232            ($offset_start:expr => $vis:vis $field:ident: $ty:ty),
233            $(#[$attr_next:meta])*
234            ($offset_end:expr => $($next:tt)*),
235            $($after:tt)*
236        )
237        : $output:expr
238    ) => {
239        $crate::test_fields!(
240            @munch $struct $(<$life>)? (
241                $(#[$attr_next])*
242                ($offset_end => $($next)*),
243                $($after)*
244            ) : {
245                // Evaluate the previous iterations' expression to determine the
246                // current offset.
247                const SUM_MAX_ALIGN: (usize, usize) = $output;
248                const SUM: usize = SUM_MAX_ALIGN.0;
249                const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
250
251                // Validate the start offset of the current field. This check is
252                // mostly relevant for when this is the first field in the
253                // struct, as any subsequent start offset error will be detected
254                // by an end offset error of the previous field.
255                assert!(
256                    SUM == $offset_start,
257                    "{}",
258                    concat!(
259                        "Invalid start offset for field ",
260                        stringify!($field),
261                        " (expected ",
262                        stringify!($offset_start),
263                        " but actual value differs)",
264                    ),
265                );
266
267                // Validate that the start offset of the current field within
268                // the struct matches the type's minimum alignment constraint.
269                const ALIGN: usize = core::mem::align_of::<$ty>();
270                // Clippy can tell that (align - 1) is zero for some fields, so
271                // we allow this lint and further encapsule the assert! as an
272                // expression, such that the allow attr can apply.
273                #[allow(clippy::bad_bit_mask)]
274                {
275                    assert!(
276                        SUM & (ALIGN - 1) == 0,
277                        "{}",
278                        concat!(
279                            "Invalid alignment for field ",
280                            stringify!($field),
281                            " (offset differs from expected)",
282                        ),
283                    );
284                }
285
286                // Add the current field's length to the offset and validate the
287                // end offset of the field based on the next field's claimed
288                // start offset.
289                const NEW_SUM: usize = SUM + core::mem::size_of::<$ty>();
290                assert!(
291                    NEW_SUM == $offset_end,
292                    "{}",
293                    concat!(
294                        "Invalid end offset for field ",
295                        stringify!($field),
296                        " (expected ",
297                        stringify!($offset_end),
298                        " but actual value differs)",
299                    ),
300                );
301
302                // Determine the new maximum alignment. core::cmp::max(ALIGN,
303                // MAX_ALIGN) does not work here, as the function is not const.
304                const NEW_MAX_ALIGN: usize = if ALIGN > MAX_ALIGN { ALIGN } else { MAX_ALIGN };
305
306                // Provide the updated offset and alignment to the next
307                // iteration.
308                (NEW_SUM, NEW_MAX_ALIGN)
309            }
310        );
311    };
312
313    // Consume a padding ($offset:expr => $padding:ident) field.
314    (@munch $struct:ident $(<$life:lifetime>)?
315        (
316            $(#[$attr:meta])*
317            ($offset_start:expr => $padding:ident),
318            $(#[$attr_next:meta])*
319            ($offset_end:expr => $($next:tt)*),
320            $($after:tt)*
321        )
322        : $output:expr
323    ) => {
324        $crate::test_fields!(
325            @munch $struct $(<$life>)? (
326                $(#[$attr_next])*
327                ($offset_end => $($next)*),
328                $($after)*
329            ) : {
330                // Evaluate the previous iterations' expression to determine the
331                // current offset.
332                const SUM_MAX_ALIGN: (usize, usize) = $output;
333                const SUM: usize = SUM_MAX_ALIGN.0;
334                const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
335
336                // Validate the start offset of the current padding field. This
337                // check is mostly relevant for when this is the first field in
338                // the struct, as any subsequent start offset error will be
339                // detected by an end offset error of the previous field.
340                assert!(
341                    SUM == $offset_start,
342                    concat!(
343                        "Invalid start offset for padding ",
344                        stringify!($padding),
345                        " (expected ",
346                        stringify!($offset_start),
347                        " but actual value differs)",
348                    ),
349                );
350
351                // The padding field is automatically sized. Provide the start
352                // offset of the next field to the next iteration.
353                ($offset_end, MAX_ALIGN)
354            }
355        );
356    };
357}
358
359/// Define a peripheral memory map containing registers.
360///
361/// Implementations of memory-mapped registers can use this macro to define the
362/// individual registers in the peripheral and their relative address offset
363/// from the start of the peripheral's mapped address. An example use for a
364/// hypothetical UART driver might look like:
365///
366/// ```rust,ignore
367/// register_structs! {
368///     pub UartRegisters {
369///         (0x00 => control: ReadWrite<u32, CONTROL::Register>),
370///         (0x04 => write_byte: ReadWrite<u32, BYTE::Register>),
371///         (0x08 => _reserved1),
372///         (0x20 => interrupt_enable: ReadWrite<u32, INTERRUPT::Register>),
373///         (0x24 => interrupt_status: ReadWrite<u32, INTERRUPT::Register>),
374///         (0x28 => @END),
375///     }
376/// }
377/// ```
378///
379/// By convention, gaps in the register memory map are named `_reserved`. The
380/// macro will automatically compute the size of the reserved field so that the
381/// next register is at the correct address.
382///
383/// The size of the register is denoted by the first parameter in the
384/// [`ReadWrite`](crate::registers::ReadWrite) type. The second parameter in the
385/// [`ReadWrite`](crate::registers::ReadWrite) type is a register definition
386/// which is specified with the
387/// [`register_bitfields!()`](crate::register_bitfields) macro.
388#[macro_export]
389macro_rules! register_structs {
390    {
391        $(
392            $(#[$attr:meta])*
393            $vis_struct:vis $name:ident $(<$life:lifetime>)? {
394                $( $fields:tt )*
395            }
396        ),*
397    } => {
398        $( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
399        $( $crate::test_fields!(@root $name $(<$life>)? { $($fields)* } ); )*
400    };
401}