Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rust: add #[export] macro

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.

However, we only use bindgen in the kernel. 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.

Introducing cbindgen as a mandatory dependency to build the kernel would
be a rather complex and large change, so we do not consider that at this
time. Instead, 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.

Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
Link: https://lore.kernel.org/r/20250303-export-macro-v3-3-41fbad85a27f@google.com
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>

authored by

Alice Ryhl and committed by
Miguel Ojeda
44e333fe 85525eda

+72 -2
+1 -1
rust/kernel/prelude.rs
··· 17 17 pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec}; 18 18 19 19 #[doc(no_inline)] 20 - pub use macros::{module, pin_data, pinned_drop, vtable, Zeroable}; 20 + pub use macros::{export, module, pin_data, pinned_drop, vtable, Zeroable}; 21 21 22 22 pub use super::{build_assert, build_error}; 23 23
+29
rust/macros/export.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use crate::helpers::function_name; 4 + use proc_macro::TokenStream; 5 + 6 + /// Please see [`crate::export`] for documentation. 7 + pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream { 8 + let Some(name) = function_name(ts.clone()) else { 9 + return "::core::compile_error!(\"The #[export] attribute must be used on a function.\");" 10 + .parse::<TokenStream>() 11 + .unwrap(); 12 + }; 13 + 14 + // This verifies that the function has the same signature as the declaration generated by 15 + // bindgen. It makes use of the fact that all branches of an if/else must have the same type. 16 + let signature_check = quote!( 17 + const _: () = { 18 + if true { 19 + ::kernel::bindings::#name 20 + } else { 21 + #name 22 + }; 23 + }; 24 + ); 25 + 26 + let no_mangle = quote!(#[no_mangle]); 27 + 28 + TokenStream::from_iter([signature_check, no_mangle, ts]) 29 + }
+18 -1
rust/macros/helpers.rs
··· 1 1 // SPDX-License-Identifier: GPL-2.0 2 2 3 - use proc_macro::{token_stream, Group, TokenStream, TokenTree}; 3 + use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree}; 4 4 5 5 pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> { 6 6 if let Some(TokenTree::Ident(ident)) = it.next() { ··· 214 214 }, 215 215 rest, 216 216 ) 217 + } 218 + 219 + /// Given a function declaration, finds the name of the function. 220 + pub(crate) fn function_name(input: TokenStream) -> Option<Ident> { 221 + let mut input = input.into_iter(); 222 + while let Some(token) = input.next() { 223 + match token { 224 + TokenTree::Ident(i) if i.to_string() == "fn" => { 225 + if let Some(TokenTree::Ident(i)) = input.next() { 226 + return Some(i); 227 + } 228 + return None; 229 + } 230 + _ => continue, 231 + } 232 + } 233 + None 217 234 }
+24
rust/macros/lib.rs
··· 9 9 #[macro_use] 10 10 mod quote; 11 11 mod concat_idents; 12 + mod export; 12 13 mod helpers; 13 14 mod module; 14 15 mod paste; ··· 173 172 #[proc_macro_attribute] 174 173 pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream { 175 174 vtable::vtable(attr, ts) 175 + } 176 + 177 + /// Export a function so that C code can call it via a header file. 178 + /// 179 + /// Functions exported using this macro can be called from C code using the declaration in the 180 + /// appropriate header file. It should only be used in cases where C calls the function through a 181 + /// header file; cases where C calls into Rust via a function pointer in a vtable (such as 182 + /// `file_operations`) should not use this macro. 183 + /// 184 + /// This macro has the following effect: 185 + /// 186 + /// * Disables name mangling for this function. 187 + /// * Verifies at compile-time that the function signature matches the declaration in the header 188 + /// file. 189 + /// 190 + /// You must declare the signature of the Rust function in a header file that is included by 191 + /// `rust/bindings/bindings_helper.h`. 192 + /// 193 + /// This macro is *not* the same as the C macros `EXPORT_SYMBOL_*`. All Rust symbols are currently 194 + /// automatically exported with `EXPORT_SYMBOL_GPL`. 195 + #[proc_macro_attribute] 196 + pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream { 197 + export::export(attr, ts) 176 198 } 177 199 178 200 /// Concatenate two identifiers.