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