uefi_macros/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#![recursion_limit = "128"]
4
5extern crate proc_macro;
6
7use proc_macro::TokenStream;
8
9use proc_macro2::TokenStream as TokenStream2;
10use quote::{TokenStreamExt, quote, quote_spanned};
11use syn::spanned::Spanned;
12use syn::{
13 Error, Expr, ExprLit, ExprPath, ItemFn, ItemStruct, Lit, Visibility, parse_macro_input,
14 parse_quote, parse_quote_spanned,
15};
16
17macro_rules! err {
18 ($span:expr, $message:expr $(,)?) => {
19 Error::new($span.span(), $message).to_compile_error()
20 };
21 ($span:expr, $message:expr, $($args:expr),*) => {
22 Error::new($span.span(), format!($message, $($args),*)).to_compile_error()
23 };
24}
25
26/// Attribute macro for marking structs as UEFI protocols.
27///
28/// The macro can only be applied to a struct, and takes one argument, either a
29/// GUID string or the path to a `Guid` constant.
30///
31/// The macro implements the [`Protocol`] trait and the `unsafe` [`Identify`]
32/// trait for the struct. See the [`Protocol`] trait for details of how it is
33/// used.
34///
35/// # Safety
36///
37/// The caller must ensure that the correct GUID is attached to the
38/// type. An incorrect GUID could lead to invalid casts and other
39/// unsound behavior.
40///
41/// # Example
42///
43/// ```
44/// use uefi::{Guid, Identify, guid};
45/// use uefi::proto::unsafe_protocol;
46///
47/// #[unsafe_protocol("12345678-9abc-def0-1234-56789abcdef0")]
48/// struct ExampleProtocol1 {}
49///
50/// const PROTO_GUID: Guid = guid!("12345678-9abc-def0-1234-56789abcdef0");
51/// #[unsafe_protocol(PROTO_GUID)]
52/// struct ExampleProtocol2 {}
53///
54/// assert_eq!(ExampleProtocol1::GUID, PROTO_GUID);
55/// assert_eq!(ExampleProtocol2::GUID, PROTO_GUID);
56/// ```
57///
58/// [`Identify`]: https://docs.rs/uefi/latest/uefi/data_types/trait.Identify.html
59/// [`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html
60/// [send-and-sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
61#[proc_macro_attribute]
62pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream {
63 let expr = parse_macro_input!(args as Expr);
64
65 let guid_val = match expr {
66 Expr::Lit(ExprLit {
67 lit: Lit::Str(lit), ..
68 }) => {
69 quote!(::uefi::guid!(#lit))
70 }
71 Expr::Path(ExprPath { path, .. }) => quote!(#path),
72 _ => err!(
73 expr,
74 "macro input must be either a string literal or path to a constant"
75 ),
76 };
77
78 let item_struct = parse_macro_input!(input as ItemStruct);
79
80 let ident = &item_struct.ident;
81 let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
82
83 quote! {
84 #item_struct
85
86 unsafe impl #impl_generics ::uefi::Identify for #ident #ty_generics #where_clause {
87 const GUID: ::uefi::Guid = #guid_val;
88 }
89
90 impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {}
91 }
92 .into()
93}
94
95/// Custom attribute for a UEFI executable entry point.
96///
97/// This attribute modifies a function to mark it as the entry point for
98/// a UEFI executable. The function:
99/// * Must return [`Status`].
100/// * Must have zero parameters.
101/// * Can optionally be `unsafe`.
102///
103/// The global system table pointer and global image handle will be set
104/// automatically.
105///
106/// # Examples
107///
108/// ```no_run
109/// #![no_main]
110///
111/// use uefi::prelude::*;
112///
113/// #[entry]
114/// fn main() -> Status {
115/// Status::SUCCESS
116/// }
117/// ```
118///
119/// [`Status`]: https://docs.rs/uefi/latest/uefi/struct.Status.html
120#[proc_macro_attribute]
121pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
122 // This code is inspired by the approach in this embedded Rust crate:
123 // https://github.com/rust-embedded/cortex-m-rt/blob/965bf1e3291571e7e3b34834864117dc020fb391/macros/src/lib.rs#L85
124
125 let mut errors = TokenStream2::new();
126
127 if !args.is_empty() {
128 errors.append_all(err!(
129 TokenStream2::from(args),
130 "Entry attribute accepts no arguments"
131 ));
132 }
133
134 let mut f = parse_macro_input!(input as ItemFn);
135
136 if let Some(ref abi) = f.sig.abi {
137 errors.append_all(err!(abi, "Entry function must have no ABI modifier"));
138 }
139 if let Some(asyncness) = f.sig.asyncness {
140 errors.append_all(err!(asyncness, "Entry function should not be async"));
141 }
142 if let Some(constness) = f.sig.constness {
143 errors.append_all(err!(constness, "Entry function should not be const"));
144 }
145 if !f.sig.generics.params.is_empty() {
146 errors.append_all(err!(
147 f.sig.generics.params,
148 "Entry function should not be generic"
149 ));
150 }
151 if !f.sig.inputs.is_empty() {
152 errors.append_all(err!(f.sig.inputs, "Entry function must have no arguments"));
153 }
154
155 // Show most errors all at once instead of one by one.
156 if !errors.is_empty() {
157 return errors.into();
158 }
159
160 let signature_span = f.sig.span();
161
162 // Fill in the image handle and system table arguments automatically.
163 let image_handle_ident = quote!(internal_image_handle);
164 let system_table_ident = quote!(internal_system_table);
165 f.sig.inputs = parse_quote_spanned!(
166 signature_span=>
167 #image_handle_ident: ::uefi::Handle,
168 #system_table_ident: *const ::core::ffi::c_void,
169 );
170
171 // Insert code at the beginning of the entry function to set the global
172 // image handle and system table pointer.
173 f.block.stmts.insert(
174 0,
175 parse_quote! {
176 unsafe {
177 ::uefi::boot::set_image_handle(#image_handle_ident);
178 ::uefi::table::set_system_table(#system_table_ident.cast());
179 }
180 },
181 );
182
183 // Set the required ABI.
184 f.sig.abi = Some(parse_quote_spanned!(signature_span=> extern "efiapi"));
185
186 // Strip any visibility modifiers.
187 f.vis = Visibility::Inherited;
188
189 let unsafety = &f.sig.unsafety;
190 let fn_ident = &f.sig.ident;
191 let fn_output = &f.sig.output;
192
193 // Get the expected argument types for the main function.
194 let expected_args = quote!(::uefi::Handle, *const core::ffi::c_void);
195
196 let fn_type_check = quote_spanned! {signature_span=>
197 // Cast from the function type to a function pointer with the same
198 // signature first, then try to assign that to an unnamed constant with
199 // the desired function pointer type.
200 //
201 // The cast is used to avoid an "expected fn pointer, found fn item"
202 // error if the signature is wrong, since that's not what we are
203 // interested in here. Instead we want to tell the user what
204 // specifically in the function signature is incorrect.
205 const _:
206 // The expected fn pointer type.
207 #unsafety extern "efiapi" fn(#expected_args) -> ::uefi::Status =
208 // Cast from a fn item to a function pointer.
209 #fn_ident as #unsafety extern "efiapi" fn(#expected_args) #fn_output;
210 };
211
212 let result = quote! {
213 #fn_type_check
214
215 #[unsafe(export_name = "efi_main")]
216 #f
217
218 };
219 result.into()
220}