[PATCH 6/8] rust: macros: add early_param support to module! macro
From: Matthew Wood <hidden>
Date: 2026-02-26 23:47:45
Also in:
lkml, rust-for-linux
Subsystem:
module support, rust, the rest · Maintainers:
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen, Miguel Ojeda, Linus Torvalds
Allow module parameters to be registered as early boot command-line
parameters via the existing __setup infrastructure.
Add an optional `early_param` field to the parameter block in the
module! macro. When specified, the macro generates:
- A setup callback that calls ModuleParam::from_setup_arg() and
stores the result via ModuleParamAccess::set_value().
- A static string in .init.rodata with the command-line prefix.
- An ObsKernelParam entry in the .init.setup section linking the
two together.
The generated code is gated behind #[cfg(not(MODULE))] since __setup
parameters are only meaningful for built-in modules — loadable modules
receive their parameters through the standard module_param mechanism.
Signed-off-by: Matthew Wood <redacted>
---
rust/macros/lib.rs | 5 +++
rust/macros/module.rs | 81 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 86 insertions(+)
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 0c36194d9971..83dcc89a425a 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs@@ -35,9 +35,14 @@ /// parameter_name: type { /// default: default_value, /// description: "Description", +/// early_param: "cmdline_prefix=", // optional /// } /// ``` /// +/// The optional `early_param` field registers the parameter as an early +/// boot command-line parameter via `__setup`. The value is the command-line +/// prefix string (e.g. `"netconsole="`). +/// /// `type` may be one of /// /// - [`i8`]
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 0d76743741fb..4d2e144fa6de 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs@@ -38,6 +38,7 @@ struct ModInfoBuilder<'a> { counter: usize, ts: TokenStream, param_ts: TokenStream, + setup_ts: TokenStream, } impl<'a> ModInfoBuilder<'a> {
@@ -47,6 +48,7 @@ fn new(module: &'a str) -> Self { counter: 0, ts: TokenStream::new(), param_ts: TokenStream::new(), + setup_ts: TokenStream::new(), } }
@@ -204,6 +206,78 @@ fn emit_params(&mut self, info: &ModuleInfo) { }); } } + + fn emit_setup(&mut self, info: &ModuleInfo) { + let Some(params) = &info.params else { + return; + }; + + for param in params { + let Some(early_param) = ¶m.early_param else { + continue; + }; + + let setup_str_value = early_param.value(); + let param_name = ¶m.name; + + // Resolve `string` shorthand to the real `StringParam` type. + let param_type_str = param.ptype.to_token_stream().to_string(); + let actual_type: Type = if param_type_str == "string" { + parse_quote!(::kernel::module_param::StringParam) + } else { + param.ptype.clone() + }; + + // Create identifiers for the generated statics + let setup_fn_name = format_ident!("__setup_fn_{}", param_name); + let setup_str_name = + format_ident!("__SETUP_STR_{}", param_name.to_string().to_uppercase()); + let setup_name = format_ident!("__SETUP_{}", param_name.to_string().to_uppercase()); + + // The setup string with null terminator + let setup_str_bytes = format!("{}\0", setup_str_value); + let setup_str_len = setup_str_bytes.len(); + let setup_str_lit = Literal::byte_string(setup_str_bytes.as_bytes()); + + self.setup_ts.extend(quote! { + #[cfg(not(MODULE))] + const _: () = { + unsafe extern "C" fn #setup_fn_name( + val: *mut ::kernel::ffi::c_char, + ) -> ::kernel::ffi::c_int { + if val.is_null() { + return 0; + } + // SAFETY: The kernel passes a valid null-terminated string from + // `static_command_line`. + match unsafe { + <#actual_type as ::kernel::module_param::ModuleParam>::from_setup_arg( + val as *const _ + ) + } { + Ok(v) => { + if module_parameters::#param_name.set_value(v) { 1 } else { 0 } + } + Err(_) => 0, + } + } + + #[link_section = ".init.rodata"] + #[used(compiler)] + static #setup_str_name: [u8; #setup_str_len] = *#setup_str_lit; + + #[link_section = ".init.setup"] + #[used(compiler)] + static #setup_name: ::kernel::module_param::ObsKernelParam = + ::kernel::module_param::ObsKernelParam { + str_: #setup_str_name.as_ptr().cast(), + setup_func: ::core::option::Option::Some(#setup_fn_name), + early: 0, + }; + }; + }); + } + } } fn param_ops_path(param_type: &str) -> Path {
@@ -367,6 +441,7 @@ struct Parameter { ptype: Type, default: Expr, description: LitStr, + early_param: Option<LitStr>, } impl Parse for Parameter {
@@ -382,6 +457,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> { from fields; default [required] => fields.parse()?, description [required] => fields.parse()?, + early_param => fields.parse()?, } Ok(Self {
@@ -389,6 +465,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> { ptype, default, description, + early_param, }) } }
@@ -501,9 +578,11 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> { modinfo.emit_only_builtin("file", &file, false); modinfo.emit_params(&info); + modinfo.emit_setup(&info); let modinfo_ts = modinfo.ts; let params_ts = modinfo.param_ts; + let setup_ts = modinfo.setup_ts; let ident_init = format_ident!("__{ident}_init"); let ident_exit = format_ident!("__{ident}_exit");
@@ -678,5 +757,7 @@ unsafe fn __exit() { mod module_parameters { #params_ts } + + #setup_ts }) }
--
2.52.0