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
94/// within the register struct through the `register_structs!()`
95/// macro.
96///
97/// This macro expands to an expression which contains static
98/// assertions about various parameters of the individual fields in
99/// the register struct definition. It will test for:
100///
101/// - Proper start offset of padding fields. It will fail in cases
102///   such as
103///
104///   ```should_fail
105///   # #[macro_use]
106///   # extern crate tock_registers;
107///   # use tock_registers::register_structs;
108///   # use tock_registers::registers::ReadWrite;
109///   register_structs! {
110///       UartRegisters {
111///           (0x04 => _reserved),
112///           (0x08 => foo: ReadWrite<u32>),
113///           (0x0C => @END),
114///       }
115///   }
116///   # // This is required for rustdoc to not place this code snipped into an
117///   # // fn main() {...} function.
118///   # fn main() { }
119///   ```
120///
121///   In this example, the start offset of `_reserved` should have been `0x00`
122///   instead of `0x04`.
123///
124/// - Correct start offset and end offset (start offset of next field) in actual
125///   fields. It will fail in cases such as
126///
127///   ```should_fail
128///   # #[macro_use]
129///   # extern crate tock_registers;
130///   # use tock_registers::register_structs;
131///   # use tock_registers::registers::ReadWrite;
132///   register_structs! {
133///       UartRegisters {
134///           (0x00 => foo: ReadWrite<u32>),
135///           (0x05 => bar: ReadWrite<u32>),
136///           (0x08 => @END),
137///       }
138///   }
139///   # // This is required for rustdoc to not place this code snipped into an
140///   # // fn main() {...} function.
141///   # fn main() { }
142///   ```
143///
144///   In this example, the start offset of `bar` and thus the end offset of
145///   `foo` should have been `0x04` instead of `0x05`.
146///
147/// - Invalid alignment of fields.
148///
149/// - That the end marker matches the actual generated struct size. This will
150///   fail in cases such as
151///
152///   ```should_fail
153///   # #[macro_use]
154///   # extern crate tock_registers;
155///   # use tock_registers::register_structs;
156///   # use tock_registers::registers::ReadWrite;
157///   register_structs! {
158///       UartRegisters {
159///           (0x00 => foo: ReadWrite<u32>),
160///           (0x04 => bar: ReadWrite<u32>),
161///           (0x10 => @END),
162///       }
163///   }
164///   # // This is required for rustdoc to not place this code snipped into an
165///   # // fn main() {...} function.
166///   # fn main() { }
167///   ```
168#[macro_export]
169macro_rules! test_fields {
170    // This macro works by iterating over all defined fields, until it hits an
171    // ($size:expr => @END) field. Each iteration generates an expression which,
172    // when evaluated, yields the current byte offset in the fields. Thus, when
173    // reading a field or padding, the field or padding length must be added to
174    // the returned size.
175    //
176    // By feeding this expression recursively into the macro, deeper invocations
177    // can continue validating fields through knowledge of the current offset
178    // and the remaining fields.
179    //
180    // The nested expression returned by this macro is guaranteed to be
181    // const-evaluable.
182
183    // Macro entry point.
184    (@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
185        // Start recursion at offset 0.
186        $crate::test_fields!(@munch $struct $(<$life>)? ($($input)*) : (0, 0));
187    };
188
189    // Consume the ($size:expr => @END) field, which MUST be the last field in
190    // the register struct.
191    (@munch $struct:ident $(<$life:lifetime>)?
192        (
193            $(#[$attr_end:meta])*
194            ($size:expr => @END),
195        )
196        : $stmts:expr
197    ) => {
198        const _: () = {
199            // We've reached the end! Normally it is sufficient to compare the
200            // struct's size to the reported end offet. However, we must
201            // evaluate the previous iterations' expressions for them to have an
202            // effect anyways, so we can perform an internal sanity check on
203            // this value as well.
204            const SUM_MAX_ALIGN: (usize, usize) = $stmts;
205            const SUM: usize = SUM_MAX_ALIGN.0;
206            const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
207
208            // Internal sanity check. If we have reached this point and
209            // correctly iterated over the struct's fields, the current offset
210            // and the claimed end offset MUST be equal.
211            assert!(SUM == $size);
212
213            const STRUCT_SIZE: usize = core::mem::size_of::<$struct $(<$life>)?>();
214            const ALIGNMENT_CORRECTED_SIZE: usize = if $size % MAX_ALIGN != 0 { $size + (MAX_ALIGN - ($size % MAX_ALIGN)) } else { $size };
215
216            assert!(
217                STRUCT_SIZE == ALIGNMENT_CORRECTED_SIZE,
218                "{}",
219                concat!(
220                    "Invalid size for struct ",
221                    stringify!($struct),
222                    " (expected ",
223                    $size,
224                    ", actual struct size differs)",
225                ),
226            );
227        };
228    };
229
230    // Consume a proper ($offset:expr => $field:ident: $ty:ty) field.
231    (@munch $struct:ident $(<$life:lifetime>)?
232        (
233            $(#[$attr:meta])*
234            ($offset_start:expr => $vis:vis $field:ident: $ty:ty),
235            $(#[$attr_next:meta])*
236            ($offset_end:expr => $($next:tt)*),
237            $($after:tt)*
238        )
239        : $output:expr
240    ) => {
241        $crate::test_fields!(
242            @munch $struct $(<$life>)? (
243                $(#[$attr_next])*
244                ($offset_end => $($next)*),
245                $($after)*
246            ) : {
247                // Evaluate the previous iterations' expression to determine the
248                // current offset.
249                const SUM_MAX_ALIGN: (usize, usize) = $output;
250                const SUM: usize = SUM_MAX_ALIGN.0;
251                const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
252
253                // Validate the start offset of the current field. This check is
254                // mostly relevant for when this is the first field in the
255                // struct, as any subsequent start offset error will be detected
256                // by an end offset error of the previous field.
257                assert!(
258                    SUM == $offset_start,
259                    "{}",
260                    concat!(
261                        "Invalid start offset for field ",
262                        stringify!($field),
263                        " (expected ",
264                        $offset_start,
265                        " but actual value differs)",
266                    ),
267                );
268
269                // Validate that the start offset of the current field within
270                // the struct matches the type's minimum alignment constraint.
271                const ALIGN: usize = core::mem::align_of::<$ty>();
272                // Clippy can tell that (align - 1) is zero for some fields, so
273                // we allow this lint and further encapsule the assert! as an
274                // expression, such that the allow attr can apply.
275                #[allow(clippy::bad_bit_mask)]
276                {
277                    assert!(
278                        SUM & (ALIGN - 1) == 0,
279                        "{}",
280                        concat!(
281                            "Invalid alignment for field ",
282                            stringify!($field),
283                            " (offset differs from expected)",
284                        ),
285                    );
286                }
287
288                // Add the current field's length to the offset and validate the
289                // end offset of the field based on the next field's claimed
290                // start offset.
291                const NEW_SUM: usize = SUM + core::mem::size_of::<$ty>();
292                assert!(
293                    NEW_SUM == $offset_end,
294                    "{}",
295                    concat!(
296                        "Invalid end offset for field ",
297                        stringify!($field),
298                        " (expected ",
299                        $offset_end,
300                        " but actual value differs)",
301                    ),
302                );
303
304                // Determine the new maximum alignment. core::cmp::max(ALIGN,
305                // MAX_ALIGN) does not work here, as the function is not const.
306                const NEW_MAX_ALIGN: usize = if ALIGN > MAX_ALIGN { ALIGN } else { MAX_ALIGN };
307
308                // Provide the updated offset and alignment to the next
309                // iteration.
310                (NEW_SUM, NEW_MAX_ALIGN)
311            }
312        );
313    };
314
315    // Consume a padding ($offset:expr => $padding:ident) field.
316    (@munch $struct:ident $(<$life:lifetime>)?
317        (
318            $(#[$attr:meta])*
319            ($offset_start:expr => $padding:ident),
320            $(#[$attr_next:meta])*
321            ($offset_end:expr => $($next:tt)*),
322            $($after:tt)*
323        )
324        : $output:expr
325    ) => {
326        $crate::test_fields!(
327            @munch $struct $(<$life>)? (
328                $(#[$attr_next])*
329                ($offset_end => $($next)*),
330                $($after)*
331            ) : {
332                // Evaluate the previous iterations' expression to determine the
333                // current offset.
334                const SUM_MAX_ALIGN: (usize, usize) = $output;
335                const SUM: usize = SUM_MAX_ALIGN.0;
336                const MAX_ALIGN: usize = SUM_MAX_ALIGN.1;
337
338                // Validate the start offset of the current padding field. This
339                // check is mostly relevant for when this is the first field in
340                // the struct, as any subsequent start offset error will be
341                // detected by an end offset error of the previous field.
342                assert!(
343                    SUM == $offset_start,
344                    concat!(
345                        "Invalid start offset for padding ",
346                        stringify!($padding),
347                        " (expected ",
348                        $offset_start,
349                        " but actual value differs)",
350                    ),
351                );
352
353                // The padding field is automatically sized. Provide the start
354                // offset of the next field to the next iteration.
355                ($offset_end, MAX_ALIGN)
356            }
357        );
358    };
359}
360
361#[macro_export]
362macro_rules! register_structs {
363    {
364        $(
365            $(#[$attr:meta])*
366            $vis_struct:vis $name:ident $(<$life:lifetime>)? {
367                $( $fields:tt )*
368            }
369        ),*
370    } => {
371        $( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
372        $( $crate::test_fields!(@root $name $(<$life>)? { $($fields)* } ); )*
373    };
374}