Skip to content

Commit 582e118

Browse files
committed
Add #[derive(Volatile)] for easy, access-limited field-based access to structs
Signed-off-by: Martin Kröning <[email protected]>
1 parent 9c53163 commit 582e118

File tree

5 files changed

+329
-0
lines changed

5 files changed

+329
-0
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ repository = "https://github.com/rust-osdev/volatile"
1010
edition = "2021"
1111

1212
[dependencies]
13+
volatile-macro = { version = "=0.5.2", optional = true, path = "volatile-macro" }
1314

1415
[features]
16+
derive = ["dep:volatile-macro"]
1517
# Enable unstable features; requires Rust nightly; might break on compiler updates
1618
unstable = []
1719
# Enable unstable and experimental features; requires Rust nightly; might break on compiler updates
@@ -28,3 +30,6 @@ pre-release-commit-message = "Release version {{version}}"
2830

2931
[package.metadata.docs.rs]
3032
features = ["unstable"]
33+
34+
[workspace]
35+
members = ["volatile-macro"]

src/lib.rs

+65
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,71 @@
4545
#![doc(test(attr(allow(dead_code))))]
4646
#![doc(test(attr(allow(unused_variables))))]
4747

48+
/// A derive macro for method-based accesses to volatile structures.
49+
///
50+
/// This macro allows you to access the fields of a volatile structure via methods that enforce access limitations.
51+
/// It is also more easily chainable than [`map_field`].
52+
///
53+
/// <div class="warning">
54+
///
55+
/// This macro generates and implements a new `{T}VolatileFieldAccess` trait, that you have to import if used from other modules.
56+
/// Currently, the trait is only implemented for `VolatilePtr<'_, _, ReadWrite>`.
57+
///
58+
/// </div>
59+
///
60+
/// # Examples
61+
///
62+
/// ```
63+
/// use volatile::access::ReadOnly;
64+
/// use volatile::{VolatileFieldAccess, VolatilePtr, VolatileRef};
65+
///
66+
/// #[repr(C)]
67+
/// #[derive(VolatileFieldAccess, Default)]
68+
/// pub struct DeviceConfig {
69+
/// feature_select: u32,
70+
/// #[access(ReadOnly)]
71+
/// feature: u32,
72+
/// }
73+
///
74+
/// let mut device_config = DeviceConfig::default();
75+
/// let mut volatile_ref = VolatileRef::from_mut_ref(&mut device_config);
76+
/// let mut volatile_ptr = volatile_ref.as_mut_ptr();
77+
///
78+
/// volatile_ptr.feature_select().write(42);
79+
/// assert_eq!(volatile_ptr.feature_select().read(), 42);
80+
///
81+
/// // This does not compile, because we specified `#[access(ReadOnly)]` for this field.
82+
/// // volatile_ptr.feature().write(42);
83+
///
84+
/// // A real device might have changed the value, though.
85+
/// assert_eq!(volatile_ptr.feature().read(), 0);
86+
/// ```
87+
///
88+
/// # Details
89+
///
90+
/// This macro generates a new trait (`{T}VolatileFieldAccess`) and implements it for `VolatilePtr<'a, T, ReadWrite>`.
91+
/// The example above results in (roughly) the following code:
92+
///
93+
/// ```
94+
/// pub trait DeviceConfigVolatileFieldAccess<'a> {
95+
/// fn feature_select(self) -> VolatilePtr<'a, u32, ReadWrite>;
96+
///
97+
/// fn feature(self) -> VolatilePtr<'a, u32, ReadOnly>;
98+
/// }
99+
///
100+
/// impl<'a> DeviceConfigVolatileFieldAccess<'a> for VolatilePtr<'a, DeviceConfig, ReadWrite> {
101+
/// fn feature_select(self) -> VolatilePtr<'a, u32, ReadWrite> {
102+
/// map_field!(self.feature_select).restrict()
103+
/// }
104+
///
105+
/// fn feature(self) -> VolatilePtr<'a, u32, ReadOnly> {
106+
/// map_field!(self.feature).restrict()
107+
/// }
108+
/// }
109+
/// ```
110+
#[cfg(feature = "derive")]
111+
pub use volatile_macro::VolatileFieldAccess;
112+
48113
pub use volatile_ptr::VolatilePtr;
49114
pub use volatile_ref::VolatileRef;
50115

volatile-macro/Cargo.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "volatile-macro"
3+
version = "0.5.2"
4+
authors = ["Martin Kröning <[email protected]>"]
5+
edition = "2021"
6+
description = "Procedural macros for the volatile crate."
7+
repository = "https://github.com/rust-osdev/volatile"
8+
license = "MIT OR Apache-2.0"
9+
keywords = ["volatile"]
10+
categories = ["no-std", "no-std::no-alloc"]
11+
12+
[lib]
13+
proc-macro = true
14+
15+
[dependencies]
16+
proc-macro2 = "1"
17+
quote = "1"
18+
syn = { version = "2", features = ["full"] }

volatile-macro/src/lib.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use quote::ToTokens;
4+
use syn::parse_macro_input;
5+
6+
macro_rules! bail {
7+
($span:expr, $($tt:tt)*) => {
8+
return Err(syn::Error::new_spanned($span, format!($($tt)*)))
9+
};
10+
}
11+
12+
mod volatile;
13+
14+
#[proc_macro_derive(VolatileFieldAccess, attributes(access))]
15+
pub fn derive_volatile(item: TokenStream) -> TokenStream {
16+
match volatile::derive_volatile(parse_macro_input!(item)) {
17+
Ok(items) => {
18+
let mut tokens = TokenStream2::new();
19+
for item in &items {
20+
item.to_tokens(&mut tokens);
21+
}
22+
tokens.into()
23+
}
24+
Err(e) => e.to_compile_error().into(),
25+
}
26+
}

volatile-macro/src/volatile.rs

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use quote::format_ident;
2+
use syn::{
3+
parse_quote, Attribute, Fields, Ident, Item, ItemImpl, ItemStruct, ItemTrait, Path, Result,
4+
Signature, Visibility,
5+
};
6+
7+
fn validate_input(input: &ItemStruct) -> Result<()> {
8+
if !matches!(&input.fields, Fields::Named(_)) {
9+
bail!(
10+
&input.fields,
11+
"#[derive(VolatileFieldAccess)] can only be used on structs with named fields"
12+
);
13+
}
14+
15+
if !input.generics.params.is_empty() {
16+
bail!(
17+
&input.generics,
18+
"#[derive(VolatileFieldAccess)] cannot be used with generic structs"
19+
);
20+
}
21+
22+
let mut valid_repr = false;
23+
for attr in &input.attrs {
24+
if attr.path().is_ident("repr") {
25+
let ident = attr.parse_args::<Ident>()?;
26+
if ident == "C" || ident == "transparent" {
27+
valid_repr = true;
28+
}
29+
}
30+
}
31+
if !valid_repr {
32+
bail!(
33+
&input.ident,
34+
"#[derive(VolatileFieldAccess)] structs must be `#[repr(C)]` or `#[repr(transparent)]`"
35+
);
36+
}
37+
38+
Ok(())
39+
}
40+
41+
struct ParsedInput {
42+
attrs: Vec<Attribute>,
43+
vis: Visibility,
44+
trait_ident: Ident,
45+
struct_ident: Ident,
46+
method_attrs: Vec<Vec<Attribute>>,
47+
sigs: Vec<Signature>,
48+
}
49+
50+
fn parse_input(input: &ItemStruct) -> Result<ParsedInput> {
51+
let mut attrs = vec![];
52+
for attr in &input.attrs {
53+
if attr.path().is_ident("doc") {
54+
attrs.push(attr.clone());
55+
}
56+
}
57+
58+
let mut method_attrs = vec![];
59+
for field in &input.fields {
60+
let mut attrs = vec![];
61+
for attr in &field.attrs {
62+
if attr.path().is_ident("doc") {
63+
attrs.push(attr.clone());
64+
}
65+
}
66+
method_attrs.push(attrs);
67+
}
68+
69+
let mut sigs = vec![];
70+
for field in &input.fields {
71+
let ident = field.ident.as_ref().unwrap();
72+
let ty = &field.ty;
73+
74+
let mut access: Path = parse_quote! { ::volatile::access::ReadWrite };
75+
for attr in &field.attrs {
76+
if attr.path().is_ident("access") {
77+
access = attr.parse_args()?;
78+
}
79+
}
80+
81+
let sig = parse_quote! {
82+
fn #ident(self) -> ::volatile::VolatilePtr<'a, #ty, #access>
83+
};
84+
sigs.push(sig);
85+
}
86+
87+
Ok(ParsedInput {
88+
attrs,
89+
vis: input.vis.clone(),
90+
trait_ident: format_ident!("{}VolatileFieldAccess", input.ident),
91+
struct_ident: input.ident.clone(),
92+
method_attrs,
93+
sigs,
94+
})
95+
}
96+
97+
fn emit_trait(
98+
ParsedInput {
99+
attrs,
100+
vis,
101+
trait_ident,
102+
method_attrs,
103+
sigs,
104+
..
105+
}: &ParsedInput,
106+
) -> ItemTrait {
107+
parse_quote! {
108+
#(#attrs)*
109+
#[allow(non_camel_case_types)]
110+
#vis trait #trait_ident <'a> {
111+
#(
112+
#(#method_attrs)*
113+
#sigs;
114+
)*
115+
}
116+
}
117+
}
118+
119+
fn emit_impl(
120+
ParsedInput {
121+
trait_ident,
122+
struct_ident,
123+
sigs,
124+
..
125+
}: &ParsedInput,
126+
) -> ItemImpl {
127+
let fields = sigs.iter().map(|sig| &sig.ident);
128+
129+
parse_quote! {
130+
#[automatically_derived]
131+
impl<'a> #trait_ident<'a> for ::volatile::VolatilePtr<'a, #struct_ident, ::volatile::access::ReadWrite> {
132+
#(
133+
#sigs {
134+
::volatile::map_field!(self.#fields).restrict()
135+
}
136+
)*
137+
}
138+
}
139+
}
140+
141+
pub fn derive_volatile(input: ItemStruct) -> Result<Vec<Item>> {
142+
validate_input(&input)?;
143+
let parsed_input = parse_input(&input)?;
144+
let item_trait = emit_trait(&parsed_input);
145+
let item_impl = emit_impl(&parsed_input);
146+
Ok(vec![Item::Trait(item_trait), Item::Impl(item_impl)])
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use quote::{quote, ToTokens};
152+
153+
use super::*;
154+
155+
#[test]
156+
fn test_derive() -> Result<()> {
157+
let input = parse_quote! {
158+
/// Struct documentation.
159+
///
160+
/// This is a wonderful struct.
161+
#[repr(C)]
162+
#[derive(VolatileFieldAccess, Default)]
163+
pub struct DeviceConfig {
164+
feature_select: u32,
165+
166+
/// Feature.
167+
///
168+
/// This is a good field.
169+
#[access(ReadOnly)]
170+
feature: u32,
171+
}
172+
};
173+
174+
let result = derive_volatile(input)?;
175+
176+
let expected_trait = quote! {
177+
/// Struct documentation.
178+
///
179+
/// This is a wonderful struct.
180+
#[allow(non_camel_case_types)]
181+
pub trait DeviceConfigVolatileFieldAccess<'a> {
182+
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite>;
183+
184+
/// Feature.
185+
///
186+
/// This is a good field.
187+
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly>;
188+
}
189+
};
190+
191+
let expected_impl = quote! {
192+
#[automatically_derived]
193+
impl<'a> DeviceConfigVolatileFieldAccess<'a> for ::volatile::VolatilePtr<'a, DeviceConfig, ::volatile::access::ReadWrite> {
194+
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite> {
195+
::volatile::map_field!(self.feature_select).restrict()
196+
}
197+
198+
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly> {
199+
::volatile::map_field!(self.feature).restrict()
200+
}
201+
}
202+
};
203+
204+
assert_eq!(
205+
expected_trait.to_string(),
206+
result[0].to_token_stream().to_string()
207+
);
208+
assert_eq!(
209+
expected_impl.to_string(),
210+
result[1].to_token_stream().to_string()
211+
);
212+
213+
Ok(())
214+
}
215+
}

0 commit comments

Comments
 (0)