diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ffe91c6beb7417..f285fce0e68014 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -205,6 +205,10 @@ jobs: - run: | curl -o bin/bindgen https://raw.githubusercontent.com/Rust-for-Linux/ci-bin/master/bindgen-0.56.0/bin/bindgen chmod u+x bin/bindgen + + # Setup: Procedural macro dependencies + - run: | + make rust-fetch-deps # Setup: ccache - run: | diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index 43965f8535ee8c..be6867e6b50624 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -89,6 +89,18 @@ Install it via (this will build the tool from source):: cargo install --locked --version 0.56.0 bindgen +Procedural macro dependencies +***************************** + +We use procedural macros that need to parse Rust code. We depend on a few +well-established crates in Rust community (`syn`, `quote` and `proc-macro2`) +for this task. + +Fetch them via:: + + make rust-fetch-deps + + Requirements: Developing ------------------------ diff --git a/Makefile b/Makefile index 2c4bac4898e0a8..b95512bdc1e661 100644 --- a/Makefile +++ b/Makefile @@ -270,7 +270,7 @@ no-dot-config-targets := $(clean-targets) \ cscope gtags TAGS tags help% %docs check% coccicheck \ $(version_h) headers headers_% archheaders archscripts \ %asm-generic kernelversion %src-pkg dt_binding_check \ - outputmakefile rustfmt rustfmtcheck + outputmakefile rustfmt rustfmtcheck rust-fetch-deps no-sync-config-targets := $(no-dot-config-targets) %install kernelrelease \ image_name single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.s %.symtypes %/ @@ -1832,6 +1832,11 @@ rustfmt: rustfmtcheck: find $(srctree) -type f -name '*.rs' | xargs $(RUSTFMT) --check +# Procedural macros dependency fetch +PHONY += rust-fetch-deps +rust-fetch-deps: + $(Q)$(MAKE) $(build)=rust $@ + # IDE support targets PHONY += rust-analyzer rust-analyzer: prepare0 diff --git a/rust/.gitignore b/rust/.gitignore index 8875e08ed0b17e..0cc32e0350e7a5 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -2,4 +2,5 @@ bindings_generated.rs exports_*_generated.h -doc/ \ No newline at end of file +doc/ +target/ diff --git a/rust/Makefile b/rust/Makefile index 7621e92b3dc70f..52120641cd0ddd 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_RUST) += core.o compiler_builtins.o helpers.o extra-$(CONFIG_RUST) += exports_core_generated.h -extra-$(CONFIG_RUST) += libmodule.so +extra-$(CONFIG_RUST) += libmacros.so extra-$(CONFIG_RUST) += bindings_generated.rs obj-$(CONFIG_RUST) += alloc.o kernel.o @@ -35,21 +35,16 @@ quiet_cmd_rustdoc = RUSTDOC $< --output $(objtree)/rust/doc --crate-name $(subst rustdoc-,,$@) \ -Fmissing-docs @$(objtree)/include/generated/rustc_cfg $< -rustdoc: rustdoc-module rustdoc-compiler_builtins rustdoc-kernel - -rustdoc-module: private rustdoc_target_flags = --crate-type proc-macro \ - --extern proc_macro -rustdoc-module: $(srctree)/rust/module.rs FORCE - $(call if_changed,rustdoc_host) +rustdoc: rustdoc-compiler_builtins rustdoc-kernel rustdoc-compiler_builtins: $(srctree)/rust/compiler_builtins.rs FORCE $(call if_changed,rustdoc) rustdoc-kernel: private rustdoc_target_flags = --extern alloc \ --extern build_error \ - --extern module=$(objtree)/rust/libmodule.so -rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-module \ - $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE + --extern macros=$(objtree)/rust/libmacros.so +rustdoc-kernel: $(srctree)/rust/kernel/lib.rs \ + $(objtree)/rust/libmacros.so $(objtree)/rust/bindings_generated.rs FORCE $(call if_changed,rustdoc) ifdef CONFIG_CC_IS_CLANG @@ -113,22 +108,27 @@ $(objtree)/rust/exports_alloc_generated.h: $(objtree)/rust/alloc.o FORCE $(objtree)/rust/exports_kernel_generated.h: $(objtree)/rust/kernel.o FORCE $(call if_changed,exports) +CARGO = cargo + # `-Cpanic=unwind -Cforce-unwind-tables=y` overrides `rustc_flags` in order to # avoid the https://github.com/rust-lang/rust/issues/82320 rustc crash. -quiet_cmd_rustc_procmacro = $(RUSTC_OR_CLIPPY_QUIET) P $@ +quiet_cmd_rustc_procmacro = CARGO P $@ cmd_rustc_procmacro = \ - $(RUSTC_OR_CLIPPY) $(rustc_flags) \ - --emit=dep-info,link --extern proc_macro \ - -Cpanic=unwind -Cforce-unwind-tables=y \ - --crate-type proc-macro --out-dir $(objtree)/rust/ \ - --crate-name $(patsubst lib%.so,%,$(notdir $@)) $<; \ - mv $(objtree)/rust/$(patsubst lib%.so,%,$(notdir $@)).d $(depfile); \ + $(CARGO) build --locked --offline --release \ + --manifest-path $< \ + --target-dir $(objtree)/rust/target; \ + cp $(objtree)/rust/target/release/$(notdir $@) $@; \ + mv $(objtree)/rust/target/release/$(patsubst %.so,%,$(notdir $@)).d $(depfile); \ + sed -i 's/build.rs//' $(depfile); \ sed -i '/^\#/d' $(depfile) +rust-fetch-deps: + $(CARGO) fetch --locked --manifest-path $(srctree)/rust/macros/Cargo.toml + # Procedural macros can only be used with the `rustc` that compiled it. -# Therefore, to get `libmodule.so` automatically recompiled when the compiler +# Therefore, to get `libmacros.so` automatically recompiled when the compiler # version changes, we add `core.o` as a dependency (even if it is not needed). -$(objtree)/rust/libmodule.so: $(srctree)/rust/module.rs \ +$(objtree)/rust/libmacros.so: $(srctree)/rust/macros/Cargo.toml \ $(objtree)/rust/core.o FORCE $(call if_changed_dep,rustc_procmacro) @@ -169,11 +169,11 @@ $(objtree)/rust/build_error.o: $(srctree)/rust/build_error.rs \ $(objtree)/rust/compiler_builtins.o FORCE $(call if_changed_dep,rustc_library) -# ICE on `--extern module`: https://github.com/rust-lang/rust/issues/56935 +# ICE on `--extern macros`: https://github.com/rust-lang/rust/issues/56935 $(objtree)/rust/kernel.o: private rustc_target_flags = --extern alloc \ --extern build_error \ - --extern module=$(objtree)/rust/libmodule.so + --extern macros=$(objtree)/rust/libmacros.so $(objtree)/rust/kernel.o: $(srctree)/rust/kernel/lib.rs $(objtree)/rust/alloc.o \ $(objtree)/rust/build_error.o \ - $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE + $(objtree)/rust/libmacros.so $(objtree)/rust/bindings_generated.rs FORCE $(call if_changed_dep,rustc_library) diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs index c70f61367347f3..fc6a6b01c588db 100644 --- a/rust/kernel/module_param.rs +++ b/rust/kernel/module_param.rs @@ -52,7 +52,7 @@ pub trait ModuleParam: core::fmt::Display + core::marker::Sized { /// Get the current value of the parameter for use in the kernel module. /// /// This function should not be used directly. Instead use the wrapper - /// `read` which will be generated by [`module::module`]. + /// `read` which will be generated by [`macros::module`]. fn value(&self) -> &Self::Value; /// Set the module parameter from a string. @@ -428,7 +428,7 @@ impl ModuleParam /// A C-style string parameter. /// /// The Rust version of the [`charp`] parameter. This type is meant to be -/// used by the [`module::module`] macro, not handled directly. Instead use the +/// used by the [`macros::module`] macro, not handled directly. Instead use the /// `read` method generated by that macro. /// /// [`charp`]: ../../../include/linux/moduleparam.h diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index f0835fb19b2f7b..8a6161e2b0e262 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -15,7 +15,7 @@ pub use alloc::{borrow::ToOwned, string::String}; pub use super::build_assert; -pub use module::{module, module_misc_device}; +pub use macros::{module, module_misc_device}; pub use super::{pr_alert, pr_cont, pr_crit, pr_emerg, pr_err, pr_info, pr_notice, pr_warn}; diff --git a/rust/macros/Cargo.lock b/rust/macros/Cargo.lock new file mode 100644 index 00000000000000..bf7d701398dd21 --- /dev/null +++ b/rust/macros/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "macros" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/rust/macros/Cargo.toml b/rust/macros/Cargo.toml new file mode 100644 index 00000000000000..de6efaa04ce1bb --- /dev/null +++ b/rust/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "macros" +version = "0.0.0" +edition = "2018" +license = "GPL-2.0-only" + +[lib] +path = "lib.rs" +proc_macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs new file mode 100644 index 00000000000000..a0c882dc8aa81d --- /dev/null +++ b/rust/macros/lib.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Crate for all kernel procedural macros. +mod module; + +use proc_macro::TokenStream; + +/// Declares a kernel module. +/// +/// The `type` argument should be a type which implements the [`KernelModule`] +/// trait. Also accepts various forms of kernel metadata. +/// +/// C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h) +/// +/// [`KernelModule`]: ../kernel/trait.KernelModule.html +/// +/// # Examples +/// +/// ```rust,no_run +/// use kernel::prelude::*; +/// +/// module!{ +/// type: MyKernelModule, +/// name: b"my_kernel_module", +/// author: b"Rust for Linux Contributors", +/// description: b"My very own kernel module!", +/// license: b"GPL v2", +/// params: { +/// my_i32: i32 { +/// default: 42, +/// permissions: 0o000, +/// description: b"Example of i32", +/// }, +/// writeable_i32: i32 { +/// default: 42, +/// permissions: 0o644, +/// description: b"Example of i32", +/// }, +/// }, +/// } +/// +/// struct MyKernelModule; +/// +/// impl KernelModule for MyKernelModule { +/// fn init() -> Result { +/// // If the parameter is writeable, then the kparam lock must be +/// // taken to read the parameter: +/// { +/// let lock = THIS_MODULE.kernel_param_lock(); +/// pr_info!("i32 param is: {}\n", writeable_i32.read(&lock)); +/// } +/// // If the parameter is read only, it can be read without locking +/// // the kernel parameters: +/// pr_info!("i32 param is: {}\n", my_i32.read()); +/// Ok(MyKernelModule) +/// } +/// } +/// ``` +/// +/// # Supported argument types +/// - `type`: type which implements the [`KernelModule`] trait (required). +/// - `name`: byte array of the name of the kernel module (required). +/// - `author`: byte array of the author of the kernel module. +/// - `description`: byte array of the description of the kernel module. +/// - `license`: byte array of the license of the kernel module (required). +/// - `alias`: byte array of alias name of the kernel module. +/// - `alias_rtnl_link`: byte array of the `rtnl_link_alias` of the kernel module (mutually exclusive with `alias`). +/// - `params`: parameters for the kernel module, as described below. +/// +/// # Supported parameter types +/// +/// - `bool`: Corresponds to C `bool` param type. +/// - `i8`: No equivalent C param type. +/// - `u8`: Corresponds to C `char` param type. +/// - `i16`: Corresponds to C `short` param type. +/// - `u16`: Corresponds to C `ushort` param type. +/// - `i32`: Corresponds to C `int` param type. +/// - `u32`: Corresponds to C `uint` param type. +/// - `i64`: No equivalent C param type. +/// - `u64`: Corresponds to C `ullong` param type. +/// - `isize`: No equivalent C param type. +/// - `usize`: No equivalent C param type. +/// - `str`: Corresponds to C `charp` param type. Reading returns a byte slice. +/// - `ArrayParam`: Corresponds to C parameters created using `module_param_array`. An array +/// of `T`'s of length at **most** `N`. +/// +/// `invbool` is unsupported: it was only ever used in a few modules. +/// Consider using a `bool` and inverting the logic instead. +#[proc_macro] +pub fn module(ts: TokenStream) -> TokenStream { + module::module(ts) +} + +/// Declares a kernel module that exposes a single misc device. +/// +/// The `type` argument should be a type which implements the [`FileOpener`] trait. Also accepts +/// various forms of kernel metadata. +/// +/// C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h) +/// +/// [`FileOpener`]: ../kernel/file_operations/trait.FileOpener.html +/// +/// # Examples +/// +/// ```rust,no_run +/// use kernel::prelude::*; +/// +/// module_misc_device! { +/// type: MyFile, +/// name: b"my_miscdev_kernel_module", +/// author: b"Rust for Linux Contributors", +/// description: b"My very own misc device kernel module!", +/// license: b"GPL v2", +/// } +/// +/// #[derive(Default)] +/// struct MyFile; +/// +/// impl kernel::file_operations::FileOperations for MyFile { +/// kernel::declare_file_operations!(); +/// } +/// ``` +#[proc_macro] +pub fn module_misc_device(ts: TokenStream) -> TokenStream { + module::module_misc_device(ts) +} diff --git a/rust/module.rs b/rust/macros/module.rs similarity index 86% rename from rust/module.rs rename to rust/macros/module.rs index 9a7009644d3cd7..3f6aa202cc87bb 100644 --- a/rust/module.rs +++ b/rust/macros/module.rs @@ -1,9 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -//! Proc macro crate implementing the [`module!`] magic. -//! -//! C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h) - #![deny(clippy::complexity)] #![deny(clippy::correctness)] #![deny(clippy::perf)] @@ -399,86 +395,6 @@ impl ModuleInfo { } } -/// Declares a kernel module. -/// -/// The `type` argument should be a type which implements the [`KernelModule`] -/// trait. Also accepts various forms of kernel metadata. -/// -/// [`KernelModule`]: ../kernel/trait.KernelModule.html -/// -/// # Examples -/// -/// ```rust,no_run -/// use kernel::prelude::*; -/// -/// module!{ -/// type: MyKernelModule, -/// name: b"my_kernel_module", -/// author: b"Rust for Linux Contributors", -/// description: b"My very own kernel module!", -/// license: b"GPL v2", -/// params: { -/// my_i32: i32 { -/// default: 42, -/// permissions: 0o000, -/// description: b"Example of i32", -/// }, -/// writeable_i32: i32 { -/// default: 42, -/// permissions: 0o644, -/// description: b"Example of i32", -/// }, -/// }, -/// } -/// -/// struct MyKernelModule; -/// -/// impl KernelModule for MyKernelModule { -/// fn init() -> Result { -/// // If the parameter is writeable, then the kparam lock must be -/// // taken to read the parameter: -/// { -/// let lock = THIS_MODULE.kernel_param_lock(); -/// pr_info!("i32 param is: {}\n", writeable_i32.read(&lock)); -/// } -/// // If the parameter is read only, it can be read without locking -/// // the kernel parameters: -/// pr_info!("i32 param is: {}\n", my_i32.read()); -/// Ok(MyKernelModule) -/// } -/// } -/// ``` -/// -/// # Supported argument types -/// - `type`: type which implements the [`KernelModule`] trait (required). -/// - `name`: byte array of the name of the kernel module (required). -/// - `author`: byte array of the author of the kernel module. -/// - `description`: byte array of the description of the kernel module. -/// - `license`: byte array of the license of the kernel module (required). -/// - `alias`: byte array of alias name of the kernel module. -/// - `alias_rtnl_link`: byte array of the `rtnl_link_alias` of the kernel module (mutually exclusive with `alias`). -/// - `params`: parameters for the kernel module, as described below. -/// -/// # Supported parameter types -/// -/// - `bool`: Corresponds to C `bool` param type. -/// - `i8`: No equivalent C param type. -/// - `u8`: Corresponds to C `char` param type. -/// - `i16`: Corresponds to C `short` param type. -/// - `u16`: Corresponds to C `ushort` param type. -/// - `i32`: Corresponds to C `int` param type. -/// - `u32`: Corresponds to C `uint` param type. -/// - `i64`: No equivalent C param type. -/// - `u64`: Corresponds to C `ullong` param type. -/// - `isize`: No equivalent C param type. -/// - `usize`: No equivalent C param type. -/// - `str`: Corresponds to C `charp` param type. Reading returns a byte slice. -/// - `ArrayParam`: Corresponds to C parameters created using `module_param_array`. An array -/// of `T`'s of length at **most** `N`. -/// -/// `invbool` is unsupported: it was only ever used in a few modules. -/// Consider using a `bool` and inverting the logic instead. -#[proc_macro] pub fn module(ts: TokenStream) -> TokenStream { let mut it = ts.into_iter(); @@ -775,34 +691,6 @@ pub fn module(ts: TokenStream) -> TokenStream { ).parse().expect("Error parsing formatted string into token stream.") } -/// Declares a kernel module that exposes a single misc device. -/// -/// The `type` argument should be a type which implements the [`FileOpener`] trait. Also accepts -/// various forms of kernel metadata. -/// -/// [`FileOpener`]: ../kernel/file_operations/trait.FileOpener.html -/// -/// # Examples -/// -/// ```rust,no_run -/// use kernel::prelude::*; -/// -/// module_misc_device! { -/// type: MyFile, -/// name: b"my_miscdev_kernel_module", -/// author: b"Rust for Linux Contributors", -/// description: b"My very own misc device kernel module!", -/// license: b"GPL v2", -/// } -/// -/// #[derive(Default)] -/// struct MyFile; -/// -/// impl kernel::file_operations::FileOperations for MyFile { -/// kernel::declare_file_operations!(); -/// } -/// ``` -#[proc_macro] pub fn module_misc_device(ts: TokenStream) -> TokenStream { let mut it = ts.into_iter(); diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index e88d4fc9eb3000..2a7b22be642bbe 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -63,13 +63,13 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg): ) append_crate( - "module", - srctree / "rust" / "module.rs", + "macros", + srctree / "rust" / "macros" / "lib.rs", True, [], [], ) - crates[-1]["proc_macro_dylib_path"] = "rust/libmodule.so" + crates[-1]["proc_macro_dylib_path"] = "rust/libmacros.so" append_crate( "build_error", @@ -83,7 +83,7 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg): "kernel", srctree / "rust" / "kernel" / "lib.rs", True, - ["core", "alloc", "module", "build_error"], + ["core", "alloc", "macros", "build_error"], cfg, ) crates[-1]["env"]["RUST_BINDINGS_FILE"] = str(bindings_file.resolve(True))