Skip to main content

yuffie_proc_macros/
lib.rs

1// SPDX-License-Identifier: BSD-2-Clause-Patent
2// SPDX-FileCopyrightText: 2024 System76, Inc.
3
4//! Rust procedural macros for Yuffie.
5
6use proc_macro::TokenStream;
7use syn::spanned::Spanned;
8
9/// Attribute to declare the entry point of a UEFI image.
10///
11/// The entry point should be declared as:
12///
13/// ```rust
14/// use yuffie::prelude::*;
15///
16/// #[entry]
17/// fn main(handle: Handle, st: &mut SystemTable) -> Status {
18///     // ...
19/// }
20/// ```
21///
22/// The entry point name can be anything. `main` is used as a convention.
23/// The exported symbol for the function will be `efi_main`, which the
24/// `*-unknown-uefi` targets look for as the entry point for binaries.
25///
26/// ## References
27///
28/// - [UEFI Specification, Version 2.10][UEFI Spec]
29///   - 4.1 UEFI Image Entry Point
30/// - [UEFI Platform Integration Specification, version 1.8][UEFI PI]
31///   - II-4.2 UEFI Image Entry Point Examples
32///
33/// [UEFI Spec]: https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf
34/// [UEFI PI]: https://uefi.org/sites/default/files/resources/UEFI_PI_Spec_1_8_March3.pdf
35#[proc_macro_attribute]
36pub fn entry(args: TokenStream, stream: TokenStream) -> TokenStream {
37    let input = syn::parse_macro_input!(stream as syn::ItemFn);
38
39    if !args.is_empty() {
40        return syn::Error::new(input.span(), "attribute does not take any arguments")
41            .to_compile_error()
42            .into();
43    }
44
45    let attrs = &input.attrs;
46    let sig = &input.sig;
47    let ident = &sig.ident;
48    let block = &input.block;
49
50    // Validate the signature
51    if sig.constness.is_some() {
52        return syn::Error::new(sig.constness.span(), "entry point must not be `const`")
53            .to_compile_error()
54            .into();
55    }
56    if sig.asyncness.is_some() {
57        return syn::Error::new(sig.asyncness.span(), "entry point must not be `async`")
58            .to_compile_error()
59            .into();
60    }
61    if sig.unsafety.is_some() {
62        return syn::Error::new(sig.unsafety.span(), "entry point must not be `unsafe`")
63            .to_compile_error()
64            .into();
65    }
66    if sig.abi.is_some() {
67        return syn::Error::new(sig.abi.span(), "entry point must not declare an ABI")
68            .to_compile_error()
69            .into();
70    }
71    if !sig.generics.params.is_empty() {
72        return syn::Error::new(sig.span(), "entry point must not use generics")
73            .to_compile_error()
74            .into();
75    }
76    if sig.variadic.is_some() {
77        return syn::Error::new(sig.span(), "entry point must not be variadic")
78            .to_compile_error()
79            .into();
80    }
81
82    #[rustfmt::skip]
83    let tok = TokenStream::from(quote::quote! {
84        #(#attrs)*
85        #[export_name = "efi_main"]
86        extern "efiapi" #sig
87        #block
88
89        // Check inputs and return type
90        const _: ::yuffie::ImageEntryFn = #ident;
91    });
92
93    tok
94}