Skip to content

Commit 60c397f

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 d0c3e9e commit 60c397f

File tree

5 files changed

+289
-0
lines changed

5 files changed

+289
-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

+58
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,64 @@
4242
#![warn(missing_docs)]
4343
#![deny(unsafe_op_in_unsafe_fn)]
4444

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

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(Volatile, 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

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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(Volatile)] 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(Volatile)] 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(Volatile)] structs must be `#[repr(C)]` or `#[repr(transparent)]`"
35+
);
36+
}
37+
38+
Ok(())
39+
}
40+
41+
fn parse_attrs(fields: &Fields) -> Result<Vec<Vec<Attribute>>> {
42+
let mut attrss = vec![];
43+
44+
for field in fields.iter() {
45+
let mut attrs = vec![];
46+
for attr in &field.attrs {
47+
if attr.path().is_ident("doc") {
48+
attrs.push(attr.clone());
49+
}
50+
}
51+
attrss.push(attrs);
52+
}
53+
54+
Ok(attrss)
55+
}
56+
57+
fn parse_sigs(fields: &Fields) -> Result<Vec<Signature>> {
58+
let mut sigs = vec![];
59+
60+
for field in fields.iter() {
61+
let ident = field.ident.as_ref().unwrap();
62+
let ty = &field.ty;
63+
64+
let mut access: Path = parse_quote! { ::volatile::access::ReadWrite };
65+
for attr in &field.attrs {
66+
if attr.path().is_ident("access") {
67+
access = attr.parse_args()?;
68+
}
69+
}
70+
71+
let sig = parse_quote! {
72+
fn #ident(self) -> ::volatile::VolatilePtr<'a, #ty, #access>
73+
};
74+
sigs.push(sig);
75+
}
76+
77+
Ok(sigs)
78+
}
79+
80+
fn emit_trait(
81+
vis: &Visibility,
82+
ident: &Ident,
83+
attrs: &[Vec<Attribute>],
84+
sigs: &[Signature],
85+
) -> Result<ItemTrait> {
86+
let item_trait = parse_quote! {
87+
#[allow(non_camel_case_types)]
88+
#vis trait #ident <'a> {
89+
#(
90+
#(#attrs)*
91+
#sigs;
92+
)*
93+
}
94+
};
95+
96+
Ok(item_trait)
97+
}
98+
99+
fn emit_impl(trait_ident: &Ident, struct_ident: &Ident, sigs: &[Signature]) -> Result<ItemImpl> {
100+
let fields = sigs.iter().map(|sig| &sig.ident);
101+
102+
let item_impl = parse_quote! {
103+
#[automatically_derived]
104+
impl<'a> #trait_ident<'a> for ::volatile::VolatilePtr<'a, #struct_ident, ::volatile::access::ReadWrite> {
105+
#(
106+
#sigs {
107+
::volatile::map_field!(self.#fields).restrict()
108+
}
109+
)*
110+
}
111+
};
112+
113+
Ok(item_impl)
114+
}
115+
116+
pub fn derive_volatile(input: ItemStruct) -> Result<Vec<Item>> {
117+
validate_input(&input)?;
118+
let attrs = parse_attrs(&input.fields)?;
119+
let sigs = parse_sigs(&input.fields)?;
120+
let trait_ident = format_ident!("{}Volatile", input.ident);
121+
122+
let item_trait = emit_trait(&input.vis, &trait_ident, &attrs, &sigs)?;
123+
let item_impl = emit_impl(&item_trait.ident, &input.ident, &sigs)?;
124+
Ok(vec![Item::Trait(item_trait), Item::Impl(item_impl)])
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use quote::{quote, ToTokens};
130+
131+
use super::*;
132+
133+
#[test]
134+
fn test_derive() -> Result<()> {
135+
let input = parse_quote! {
136+
#[repr(C)]
137+
#[derive(Volatile, Default)]
138+
pub struct DeviceConfig {
139+
feature_select: u32,
140+
/// This is a good field.
141+
#[access(ReadOnly)]
142+
feature: u32,
143+
}
144+
};
145+
146+
let result = derive_volatile(input)?;
147+
148+
let expected_trait = quote! {
149+
#[allow(non_camel_case_types)]
150+
pub trait DeviceConfigVolatile<'a> {
151+
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite>;
152+
153+
/// This is a good field.
154+
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly>;
155+
}
156+
};
157+
158+
let expected_impl = quote! {
159+
#[automatically_derived]
160+
impl<'a> DeviceConfigVolatile<'a> for ::volatile::VolatilePtr<'a, DeviceConfig, ::volatile::access::ReadWrite> {
161+
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite> {
162+
::volatile::map_field!(self.feature_select).restrict()
163+
}
164+
165+
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly> {
166+
::volatile::map_field!(self.feature).restrict()
167+
}
168+
}
169+
};
170+
171+
assert_eq!(
172+
expected_trait.to_string(),
173+
result[0].to_token_stream().to_string()
174+
);
175+
assert_eq!(
176+
expected_impl.to_string(),
177+
result[1].to_token_stream().to_string()
178+
);
179+
180+
Ok(())
181+
}
182+
}

0 commit comments

Comments
 (0)