| #![recursion_limit = "128"] |
| |
| extern crate proc_macro; |
| |
| use proc_macro::TokenStream; |
| |
| use proc_macro2::TokenStream as TokenStream2; |
| use quote::{quote, quote_spanned, TokenStreamExt}; |
| use syn::spanned::Spanned; |
| use syn::{ |
| parse_macro_input, parse_quote, parse_quote_spanned, Error, Expr, ExprLit, ExprPath, ItemFn, |
| ItemStruct, Lit, Visibility, |
| }; |
| |
| macro_rules! err { |
| ($span:expr, $message:expr $(,)?) => { |
| Error::new($span.span(), $message).to_compile_error() |
| }; |
| ($span:expr, $message:expr, $($args:expr),*) => { |
| Error::new($span.span(), format!($message, $($args),*)).to_compile_error() |
| }; |
| } |
| |
| /// Attribute macro for marking structs as UEFI protocols. |
| /// |
| /// The macro takes one argument, either a GUID string or the path to a `Guid` |
| /// constant. |
| /// |
| /// The macro can only be applied to a struct. It implements the |
| /// [`Protocol`] trait and the `unsafe` [`Identify`] trait for the |
| /// struct. |
| /// |
| /// # Safety |
| /// |
| /// The caller must ensure that the correct GUID is attached to the |
| /// type. An incorrect GUID could lead to invalid casts and other |
| /// unsound behavior. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use uefi::{Guid, Identify, guid}; |
| /// use uefi::proto::unsafe_protocol; |
| /// |
| /// #[unsafe_protocol("12345678-9abc-def0-1234-56789abcdef0")] |
| /// struct ExampleProtocol1 {} |
| /// |
| /// const PROTO_GUID: Guid = guid!("12345678-9abc-def0-1234-56789abcdef0"); |
| /// #[unsafe_protocol(PROTO_GUID)] |
| /// struct ExampleProtocol2 {} |
| /// |
| /// assert_eq!(ExampleProtocol1::GUID, PROTO_GUID); |
| /// assert_eq!(ExampleProtocol2::GUID, PROTO_GUID); |
| /// ``` |
| /// |
| /// [`Identify`]: https://docs.rs/uefi/latest/uefi/trait.Identify.html |
| /// [`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html |
| /// [send-and-sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html |
| #[proc_macro_attribute] |
| pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream { |
| let expr = parse_macro_input!(args as Expr); |
| |
| let guid_val = match expr { |
| Expr::Lit(ExprLit { |
| lit: Lit::Str(lit), .. |
| }) => { |
| quote!(::uefi::guid!(#lit)) |
| } |
| Expr::Path(ExprPath { path, .. }) => quote!(#path), |
| _ => { |
| return err!( |
| expr, |
| "macro input must be either a string literal or path to a constant" |
| ) |
| .into() |
| } |
| }; |
| |
| let item_struct = parse_macro_input!(input as ItemStruct); |
| |
| let ident = &item_struct.ident; |
| let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl(); |
| |
| quote! { |
| #item_struct |
| |
| unsafe impl #impl_generics ::uefi::Identify for #ident #ty_generics #where_clause { |
| const GUID: ::uefi::Guid = #guid_val; |
| } |
| |
| impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {} |
| } |
| .into() |
| } |
| |
| /// Custom attribute for a UEFI executable entry point. |
| /// |
| /// This attribute modifies a function to mark it as the entry point for |
| /// a UEFI executable. The function: |
| /// * Must return [`Status`]. |
| /// * Must have zero parameters. |
| /// * Can optionally be `unsafe`. |
| /// |
| /// The global system table pointer and global image handle will be set |
| /// automatically. |
| /// |
| /// # Examples |
| /// |
| /// ```no_run |
| /// #![no_main] |
| /// |
| /// use uefi::prelude::*; |
| /// |
| /// #[entry] |
| /// fn main() -> Status { |
| /// Status::SUCCESS |
| /// } |
| /// ``` |
| /// |
| /// [`Status`]: https://docs.rs/uefi/latest/uefi/struct.Status.html |
| #[proc_macro_attribute] |
| pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { |
| // This code is inspired by the approach in this embedded Rust crate: |
| // https://github.com/rust-embedded/cortex-m-rt/blob/965bf1e3291571e7e3b34834864117dc020fb391/macros/src/lib.rs#L85 |
| |
| let mut errors = TokenStream2::new(); |
| |
| if !args.is_empty() { |
| errors.append_all(err!( |
| TokenStream2::from(args), |
| "Entry attribute accepts no arguments" |
| )); |
| } |
| |
| let mut f = parse_macro_input!(input as ItemFn); |
| |
| if let Some(ref abi) = f.sig.abi { |
| errors.append_all(err!(abi, "Entry function must have no ABI modifier")); |
| } |
| if let Some(asyncness) = f.sig.asyncness { |
| errors.append_all(err!(asyncness, "Entry function should not be async")); |
| } |
| if let Some(constness) = f.sig.constness { |
| errors.append_all(err!(constness, "Entry function should not be const")); |
| } |
| if !f.sig.generics.params.is_empty() { |
| errors.append_all(err!( |
| f.sig.generics.params, |
| "Entry function should not be generic" |
| )); |
| } |
| if !f.sig.inputs.is_empty() { |
| errors.append_all(err!(f.sig.inputs, "Entry function must have no arguments")); |
| } |
| |
| // Show most errors all at once instead of one by one. |
| if !errors.is_empty() { |
| return errors.into(); |
| } |
| |
| let signature_span = f.sig.span(); |
| |
| // Fill in the image handle and system table arguments automatically. |
| let image_handle_ident = quote!(internal_image_handle); |
| let system_table_ident = quote!(internal_system_table); |
| f.sig.inputs = parse_quote_spanned!( |
| signature_span=> |
| #image_handle_ident: ::uefi::Handle, |
| #system_table_ident: *const ::core::ffi::c_void, |
| ); |
| |
| // Insert code at the beginning of the entry function to set the global |
| // image handle and system table pointer. |
| f.block.stmts.insert( |
| 0, |
| parse_quote! { |
| unsafe { |
| ::uefi::boot::set_image_handle(#image_handle_ident); |
| ::uefi::table::set_system_table(#system_table_ident.cast()); |
| } |
| }, |
| ); |
| |
| // Set the required ABI. |
| f.sig.abi = Some(parse_quote_spanned!(signature_span=> extern "efiapi")); |
| |
| // Strip any visibility modifiers. |
| f.vis = Visibility::Inherited; |
| |
| let unsafety = &f.sig.unsafety; |
| let fn_ident = &f.sig.ident; |
| let fn_output = &f.sig.output; |
| |
| // Get the expected argument types for the main function. |
| let expected_args = quote!(::uefi::Handle, *const core::ffi::c_void); |
| |
| let fn_type_check = quote_spanned! {signature_span=> |
| // Cast from the function type to a function pointer with the same |
| // signature first, then try to assign that to an unnamed constant with |
| // the desired function pointer type. |
| // |
| // The cast is used to avoid an "expected fn pointer, found fn item" |
| // error if the signature is wrong, since that's not what we are |
| // interested in here. Instead we want to tell the user what |
| // specifically in the function signature is incorrect. |
| const _: |
| // The expected fn pointer type. |
| #unsafety extern "efiapi" fn(#expected_args) -> ::uefi::Status = |
| // Cast from a fn item to a function pointer. |
| #fn_ident as #unsafety extern "efiapi" fn(#expected_args) #fn_output; |
| }; |
| |
| let result = quote! { |
| #fn_type_check |
| |
| #[export_name = "efi_main"] |
| #f |
| |
| }; |
| result.into() |
| } |