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}