Skip to content

Commit dc7d765

Browse files
uefi-macros: Add unsafe_protocol macro
This attribute macro combines the functionality of the `unsafe_guid` attribute macro and the `Protocol` derive macro. It impls the `Protocol` and `Identify` traits, and it also marks the type as `!Send` and `!Sync`. This provides a slightly nicer API for protocols (`unsafe_protocol` is a clearer name than `unsafe_guid`, and only one macro is needed instead of two), but more importantly it does the `!Send` and `!Sync` part via a hidden `PhantomData` field rather than relying on the unstable `negative_impls` feature.
1 parent d095403 commit dc7d765

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

Diff for: uefi-macros/src/lib.rs

+80-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use syn::{
1010
parse::{Parse, ParseStream},
1111
parse_macro_input, parse_quote,
1212
spanned::Spanned,
13-
DeriveInput, Error, FnArg, Generics, Ident, ItemFn, ItemType, LitStr, Pat, Visibility,
13+
DeriveInput, Error, Fields, FnArg, Generics, Ident, ItemFn, ItemStruct, ItemType, LitStr, Pat,
14+
Visibility,
1415
};
1516

1617
/// Parses a type definition, extracts its identifier and generic parameters
@@ -80,6 +81,84 @@ pub fn unsafe_guid(args: TokenStream, input: TokenStream) -> TokenStream {
8081
result.into()
8182
}
8283

84+
/// Attribute macro for marking structs as UEFI protocols.
85+
///
86+
/// The macro takes one argument, a GUID string.
87+
///
88+
/// The macro can only be applied to a struct, and the struct must have
89+
/// named fields (i.e. not a unit or tuple struct). It implements the
90+
/// [`Protocol`] trait and the `unsafe` [`Identify`] trait for the
91+
/// struct. It also adds a hidden field that causes the struct to be
92+
/// marked as [`!Send` and `!Sync`][send-and-sync].
93+
///
94+
/// # Safety
95+
///
96+
/// The caller must ensure that the correct GUID is attached to the
97+
/// type. An incorrect GUID could lead to invalid casts and other
98+
/// unsound behavior.
99+
///
100+
/// # Example
101+
///
102+
/// ```
103+
/// use uefi::{Identify, guid};
104+
/// use uefi::proto::unsafe_protocol;
105+
///
106+
/// #[unsafe_protocol("12345678-9abc-def0-1234-56789abcdef0")]
107+
/// struct ExampleProtocol {}
108+
///
109+
/// assert_eq!(ExampleProtocol::GUID, guid!("12345678-9abc-def0-1234-56789abcdef0"));
110+
/// ```
111+
///
112+
/// [`Identify`]: https://docs.rs/uefi/latest/uefi/trait.Identify.html
113+
/// [`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html
114+
/// [send-and-sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
115+
#[proc_macro_attribute]
116+
pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream {
117+
// Parse `args` as a GUID string.
118+
let (time_low, time_mid, time_high_and_version, clock_seq_and_variant, node) =
119+
match parse_guid(parse_macro_input!(args as LitStr)) {
120+
Ok(data) => data,
121+
Err(tokens) => return tokens.into(),
122+
};
123+
124+
let item_struct = parse_macro_input!(input as ItemStruct);
125+
126+
let ident = &item_struct.ident;
127+
let struct_attrs = &item_struct.attrs;
128+
let struct_vis = &item_struct.vis;
129+
let struct_fields = if let Fields::Named(struct_fields) = &item_struct.fields {
130+
&struct_fields.named
131+
} else {
132+
return err!(item_struct, "Protocol struct must used named fields").into();
133+
};
134+
let struct_generics = &item_struct.generics;
135+
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
136+
137+
quote! {
138+
#(#struct_attrs)*
139+
#struct_vis struct #ident #struct_generics {
140+
// Add a hidden field with `PhantomData` of a raw
141+
// pointer. This has the implicit side effect of making the
142+
// struct !Send and !Sync.
143+
_no_send_or_sync: ::core::marker::PhantomData<*const u8>,
144+
#struct_fields
145+
}
146+
147+
unsafe impl #impl_generics ::uefi::Identify for #ident #ty_generics #where_clause {
148+
const GUID: ::uefi::Guid = ::uefi::Guid::from_values(
149+
#time_low,
150+
#time_mid,
151+
#time_high_and_version,
152+
#clock_seq_and_variant,
153+
#node,
154+
);
155+
}
156+
157+
impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {}
158+
}
159+
.into()
160+
}
161+
83162
/// Create a `Guid` at compile time.
84163
///
85164
/// # Example

Diff for: uefi/src/proto/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ where
6262
}
6363
}
6464

65-
pub use uefi_macros::Protocol;
65+
pub use uefi_macros::{unsafe_protocol, Protocol};
6666

6767
pub mod console;
6868
pub mod debug;

0 commit comments

Comments
 (0)