@@ -17,7 +17,7 @@
pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec};
#[doc(no_inline)]
-pub use macros::{module, pin_data, pinned_drop, vtable, Zeroable};
+pub use macros::{export, module, pin_data, pinned_drop, vtable, Zeroable};
pub use super::{build_assert, build_error};
new file mode 100644
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use crate::helpers::function_name;
+use proc_macro::TokenStream;
+
+/// Please see [`crate::export`] for documentation.
+pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
+ let Some(name) = function_name(ts.clone()) else {
+ return "::core::compile_error!(\"The #[export] attribute must be used on a function.\");"
+ .parse::<TokenStream>()
+ .unwrap();
+ };
+
+ // This verifies that the function has the same signature as the declaration generated by
+ // bindgen. It makes use of the fact that all branches of an if/else must have the same type.
+ let signature_check = quote!(
+ const _: () = {
+ if true {
+ ::kernel::bindings::#name
+ } else {
+ #name
+ };
+ };
+ );
+
+ let no_mangle = "#[no_mangle]".parse::<TokenStream>().unwrap();
+ TokenStream::from_iter([signature_check, no_mangle, ts])
+}
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{token_stream, Group, TokenStream, TokenTree};
+use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
if let Some(TokenTree::Ident(ident)) = it.next() {
@@ -215,3 +215,20 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
rest,
)
}
+
+/// Given a function declaration, finds the name of the function.
+pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
+ let mut input = input.into_iter();
+ while let Some(token) = input.next() {
+ match token {
+ TokenTree::Ident(i) if i.to_string() == "fn" => {
+ if let Some(TokenTree::Ident(i)) = input.next() {
+ return Some(i);
+ }
+ return None;
+ }
+ _ => continue,
+ }
+ }
+ None
+}
@@ -9,6 +9,7 @@
#[macro_use]
mod quote;
mod concat_idents;
+mod export;
mod helpers;
mod module;
mod paste;
@@ -174,6 +175,29 @@ pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
vtable::vtable(attr, ts)
}
+/// Export a function so that C code can call it via a header file.
+///
+/// Functions exported using this macro can be called from C code using the declaration in the
+/// appropriate header file. It should only be used in cases where C calls the function through a
+/// header file; cases where C calls into Rust via a function pointer in a vtable (such as
+/// `file_operations`) should not use this macro.
+///
+/// This macro has the following effect:
+///
+/// * Disables name mangling for this function.
+/// * Verifies at compile-time that the function signature matches the declaration in the header
+/// file.
+///
+/// You must declare the signature of the Rust function in a header file that is included by
+/// `rust/bindings/bindings_helper.h`.
+///
+/// This macro is *not* the same as the C macros `EXPORT_SYMBOL_*`, since all Rust symbols are
+/// currently automatically exported with `EXPORT_SYMBOL_GPL`.
+#[proc_macro_attribute]
+pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
+ export::export(attr, ts)
+}
+
/// Concatenate two identifiers.
///
/// This is useful in macros that need to declare or reference items with names
Rust has two different tools for generating function declarations to call across the FFI boundary: * bindgen. Generates Rust declarations from a C header. * cbindgen. Generates C headers from Rust declarations. In the kernel, we only use bindgen. This is because cbindgen assumes a cargo-based buildsystem, so it is not compatible with the kernel's build system. This means that when C code calls a Rust function by name, its signature must be duplicated in both Rust code and a C header, and the signature needs to be kept in sync manually. To eliminate this manual checking, introduce a new macro that verifies at compile time that the two function declarations use the same signature. The idea is to run the C declaration through bindgen, and then have rustc verify that the function pointers have the same type. The signature must still be written twice, but at least you can no longer get it wrong. If the signatures don't match, you will get errors that look like this: error[E0308]: `if` and `else` have incompatible types --> <linux>/rust/kernel/print.rs:22:22 | 21 | #[export] | --------- expected because of this 22 | unsafe extern "C" fn rust_fmt_argument( | ^^^^^^^^^^^^^^^^^ expected `u8`, found `i8` | = note: expected fn item `unsafe extern "C" fn(*mut u8, *mut u8, *mut c_void) -> *mut u8 {bindings::rust_fmt_argument}` found fn item `unsafe extern "C" fn(*mut i8, *mut i8, *const c_void) -> *mut i8 {print::rust_fmt_argument}` It is unfortunate that the error message starts out by saying "`if` and `else` have incompatible types", but I believe the rest of the error message is reasonably clear and not too confusing. Signed-off-by: Alice Ryhl <aliceryhl@google.com> --- rust/kernel/prelude.rs | 2 +- rust/macros/export.rs | 28 ++++++++++++++++++++++++++++ rust/macros/helpers.rs | 19 ++++++++++++++++++- rust/macros/lib.rs | 24 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-)