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}