* [PATCH 1/8] rust: module_param: add StringParam type for C string parameters
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-28 1:32 ` Miguel Ojeda
2026-03-05 12:47 ` Petr Pavlu
2026-02-26 23:47 ` [PATCH 2/8] rust: module_param: wire StringParam into the module! macro Matthew Wood
` (7 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Introduce StringParam, a Copy wrapper around *const c_char that
represents a string module parameter whose memory is managed by the
kernel parameter subsystem.
StringParam provides:
- from_ptr(): construct from a raw C string pointer
- from_c_str(): construct from a static CStr (for compile-time
default values)
- null(): construct an unset/empty parameter
- as_cstr() / as_bytes(): safe accessors that return None when the
pointer is null
The type is marked Send + Sync because the underlying pointer is
effectively 'static for the module's lifetime — the kernel guarantees
the string memory remains valid while the module is loaded.
This is a prerequisite for wiring string parameters into the module!
macro in subsequent patches.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
rust/kernel/module_param.rs | 69 +++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 6a8a7a875643..80fe8643c0ab 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -6,6 +6,7 @@
use crate::prelude::*;
use crate::str::BStr;
+use crate::str::CStr;
use bindings;
use kernel::sync::SetOnce;
@@ -106,6 +107,74 @@ fn try_from_param_arg(arg: &BStr) -> Result<Self> {
impl_int_module_param!(isize);
impl_int_module_param!(usize);
+/// A module parameter that holds a C string pointer.
+///
+/// This type is `Copy` by storing only a raw pointer. The underlying string
+/// memory is managed by the kernel's parameter subsystem.
+///
+/// # Safety
+///
+/// The pointer is only valid while the module is loaded. The kernel ensures
+/// the string memory remains valid for the module's lifetime.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct StringParam {
+ ptr: *const c_char,
+}
+
+impl StringParam {
+ /// Creates a new `StringParam` from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must be valid and point to a null-terminated string,
+ /// or be null for an empty/unset parameter.
+ pub const unsafe fn from_ptr(ptr: *const c_char) -> Self {
+ Self { ptr }
+ }
+
+ /// Creates a `StringParam` from a static `CStr` reference.
+ ///
+ /// Useful for compile-time default values in module parameter declarations.
+ pub const fn from_c_str(s: &'static CStr) -> Self {
+ Self {
+ ptr: crate::str::as_char_ptr_in_const_context(s),
+ }
+ }
+
+ /// Creates a null/empty `StringParam`.
+ pub const fn null() -> Self {
+ Self {
+ ptr: core::ptr::null(),
+ }
+ }
+
+ /// Returns `true` if the parameter is null/unset.
+ pub fn is_null(&self) -> bool {
+ self.ptr.is_null()
+ }
+
+ /// Returns the string as a `CStr` reference, if set.
+ pub fn as_cstr(&self) -> Option<&CStr> {
+ if self.ptr.is_null() {
+ None
+ } else {
+ // SAFETY: pointer validity is checked above
+ Some(unsafe { CStr::from_char_ptr(self.ptr) })
+ }
+ }
+
+ /// Returns the string as bytes, if set.
+ pub fn as_bytes(&self) -> Option<&[u8]> {
+ self.as_cstr().map(|s| s.to_bytes())
+ }
+}
+
+// SAFETY: The pointer is managed by the kernel and is effectively 'static
+// for the module's lifetime.
+unsafe impl Send for StringParam {}
+unsafe impl Sync for StringParam {}
+
/// A wrapper for kernel parameters.
///
/// This type is instantiated by the [`module!`] macro when module parameters are
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 1/8] rust: module_param: add StringParam type for C string parameters
2026-02-26 23:47 ` [PATCH 1/8] rust: module_param: add StringParam type for C string parameters Matthew Wood
@ 2026-02-28 1:32 ` Miguel Ojeda
2026-03-05 12:47 ` Petr Pavlu
1 sibling, 0 replies; 17+ messages in thread
From: Miguel Ojeda @ 2026-02-28 1:32 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On Fri, Feb 27, 2026 at 12:47 AM Matthew Wood <thepacketgeek@gmail.com> wrote:
>
> +/// This type is `Copy` by storing only a raw pointer. The underlying string
Please use intra-doc links where possible/reasonable.
> +/// # Safety
> +///
> +/// The pointer is only valid while the module is loaded. The kernel ensures
> +/// the string memory remains valid for the module's lifetime.
> +#[derive(Copy, Clone)]
> +#[repr(transparent)]
> +pub struct StringParam {
A `# Safety` section for a type? What do you mean here?
I think you may want to establish an invariant instead.
> + // SAFETY: pointer validity is checked above
> + Some(unsafe { CStr::from_char_ptr(self.ptr) })
I assume you mean that the non-null implies the validity, but validity
is not really "checked" above. That is why you probably want to have a
proper invariant in the type, so that you can invoke it here etc.
> +unsafe impl Sync for StringParam {}
This is missing a `// SAFETY:` comment -- please double-check
`CLIPPY=1` passes clean.
Cheers,
Miguel
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 1/8] rust: module_param: add StringParam type for C string parameters
2026-02-26 23:47 ` [PATCH 1/8] rust: module_param: add StringParam type for C string parameters Matthew Wood
2026-02-28 1:32 ` Miguel Ojeda
@ 2026-03-05 12:47 ` Petr Pavlu
1 sibling, 0 replies; 17+ messages in thread
From: Petr Pavlu @ 2026-03-05 12:47 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On 2/27/26 12:47 AM, Matthew Wood wrote:
> Introduce StringParam, a Copy wrapper around *const c_char that
> represents a string module parameter whose memory is managed by the
> kernel parameter subsystem.
>
> StringParam provides:
> - from_ptr(): construct from a raw C string pointer
> - from_c_str(): construct from a static CStr (for compile-time
> default values)
> - null(): construct an unset/empty parameter
> - as_cstr() / as_bytes(): safe accessors that return None when the
> pointer is null
>
> The type is marked Send + Sync because the underlying pointer is
> effectively 'static for the module's lifetime — the kernel guarantees
> the string memory remains valid while the module is loaded.
It is correct that module::args is currently valid for the module's
lifetime. However, I wonder if this is still needed and we're not
unnecessarily wasting memory. Since commit e180a6b7759a ("param: fix
charp parameters set via sysfs"), even charp parameters are kmalloc'ed,
unless they end up pointing into static_command_line when the specific
parameters are built into vmlinux and set during boot.
Similarly, if Rust modules eventually need to support parameters set via
sysfs, it will also be necessary to make copies.
--
Cheers,
Petr
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
2026-02-26 23:47 ` [PATCH 1/8] rust: module_param: add StringParam type for C string parameters Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-03-04 8:13 ` Petr Pavlu
2026-03-06 19:27 ` Sami Tolvanen
2026-02-26 23:47 ` [PATCH 3/8] samples: rust_minimal: demonstrate string module parameter Matthew Wood
` (6 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Add support for `string` as a parameter type in the module! macro.
On the runtime side, add:
- set_string_param(): an extern "C" callback matching the
kernel_param_ops::set signature that stores the raw C string
pointer directly into the SetOnce<StringParam> container, avoiding
an unnecessary copy-and-parse round-trip.
- PARAM_OPS_STRING: a static kernel_param_ops that uses
set_string_param as its setter.
- ModuleParam impl for StringParam with try_from_param_arg()
returning -EINVAL, since string parameters are populated
exclusively through the kernel's set callback.
On the macro side:
- Change the Parameter::ptype field from Ident to syn::Type to
support path-qualified types.
- Recognize the `string` shorthand and resolve it to the fully
qualified ::kernel::module_param::StringParam type during code
generation.
- Wrap string default values with StringParam::from_c_str(c_str!(...))
to produce a compile-time CStr-backed default.
- Route `string` to PARAM_OPS_STRING in param_ops_path().
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
rust/kernel/module_param.rs | 48 +++++++++++++++++++++++++++++++++++++
rust/macros/module.rs | 42 +++++++++++++++++++++++++-------
2 files changed, 81 insertions(+), 9 deletions(-)
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 80fe8643c0ab..67ff6f2ea9c2 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -86,6 +86,36 @@ pub trait ModuleParam: Sized + Copy {
})
}
+/// Set a string module parameter from a string.
+///
+/// Similar to [`set_param`] but for [`StringParam`].
+///
+/// # Safety
+///
+/// Same requirements as [`set_param`].
+unsafe extern "C" fn set_string_param(
+ val: *const c_char,
+ param: *const bindings::kernel_param,
+) -> c_int {
+ if val.is_null() {
+ crate::pr_warn!("Null pointer passed to `module_param::set_string_param`");
+ return EINVAL.to_errno();
+ }
+
+ crate::error::from_result(|| {
+ // SAFETY: val points to a valid C string from the kernel.
+ let cstr_param = unsafe { StringParam::from_ptr(val) };
+
+ // SAFETY: By function safety requirements, param.arg points to our SetOnce<StringParam>.
+ let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<StringParam>>()) };
+
+ container
+ .populate(cstr_param)
+ .then_some(0)
+ .ok_or(kernel::error::code::EEXIST)
+ })
+}
+
macro_rules! impl_int_module_param {
($ty:ident) => {
impl ModuleParam for $ty {
@@ -175,6 +205,15 @@ pub fn as_bytes(&self) -> Option<&[u8]> {
unsafe impl Send for StringParam {}
unsafe impl Sync for StringParam {}
+impl ModuleParam for StringParam {
+ fn try_from_param_arg(_arg: &BStr) -> Result<Self> {
+ // For StringParam, we don't parse here - the kernel's set callback
+ // directly stores the pointer. This method should not be called
+ // when using PARAM_OPS_STRING.
+ Err(EINVAL)
+ }
+}
+
/// A wrapper for kernel parameters.
///
/// This type is instantiated by the [`module!`] macro when module parameters are
@@ -249,3 +288,12 @@ macro_rules! make_param_ops {
make_param_ops!(PARAM_OPS_U64, u64);
make_param_ops!(PARAM_OPS_ISIZE, isize);
make_param_ops!(PARAM_OPS_USIZE, usize);
+
+/// Parameter ops for string parameters.
+#[doc(hidden)]
+pub static PARAM_OPS_STRING: bindings::kernel_param_ops = bindings::kernel_param_ops {
+ flags: 0,
+ set: Some(set_string_param),
+ get: None,
+ free: None,
+};
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index e16298e520c7..0d76743741fb 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -8,7 +8,8 @@
};
use quote::{
format_ident,
- quote, //
+ quote,
+ ToTokens, //
};
use syn::{
braced,
@@ -120,13 +121,15 @@ fn emit_params(&mut self, info: &ModuleInfo) {
for param in params {
let param_name_str = param.name.to_string();
- let param_type_str = param.ptype.to_string();
+ let param_type_str = param.ptype.to_token_stream().to_string();
+ // Clean up the type string for modinfo (remove spaces around ::)
+ let param_type_clean = param_type_str.replace(" ", "");
let ops = param_ops_path(¶m_type_str);
// Note: The spelling of these fields is dictated by the user space
// tool `modinfo`.
- self.emit_param("parmtype", ¶m_name_str, ¶m_type_str);
+ self.emit_param("parmtype", ¶m_name_str, ¶m_type_clean);
self.emit_param("parm", ¶m_name_str, ¶m.description.value());
let static_name = format_ident!("__{}_{}_struct", self.module, param.name);
@@ -137,14 +140,32 @@ fn emit_params(&mut self, info: &ModuleInfo) {
.expect("name contains NUL-terminator");
let param_name = ¶m.name;
- let param_type = ¶m.ptype;
let param_default = ¶m.default;
+ // `string` is a shorthand for `StringParam` in the macro — resolve to
+ // the real type for code generation.
+ let is_str_param = param_type_str == "string";
+ let actual_type: Type = if is_str_param {
+ parse_quote!(::kernel::module_param::StringParam)
+ } else {
+ param.ptype.clone()
+ };
+
+ // For `string` params the default is always a string literal which
+ // gets wrapped with StringParam::from_c_str(kernel::c_str!(...)).
+ let default_expr = if is_str_param {
+ quote! {
+ ::kernel::module_param::StringParam::from_c_str(::kernel::c_str!(#param_default))
+ }
+ } else {
+ quote!(#param_default)
+ };
+
self.param_ts.extend(quote! {
#[allow(non_upper_case_globals)]
pub(crate) static #param_name:
- ::kernel::module_param::ModuleParamAccess<#param_type> =
- ::kernel::module_param::ModuleParamAccess::new(#param_default);
+ ::kernel::module_param::ModuleParamAccess<#actual_type> =
+ ::kernel::module_param::ModuleParamAccess::new(#default_expr);
const _: () = {
#[allow(non_upper_case_globals)]
@@ -186,7 +207,9 @@ fn emit_params(&mut self, info: &ModuleInfo) {
}
fn param_ops_path(param_type: &str) -> Path {
- match param_type {
+ let type_name = param_type.rsplit("::").next().unwrap_or(param_type).trim();
+
+ match type_name {
"i8" => parse_quote!(::kernel::module_param::PARAM_OPS_I8),
"u8" => parse_quote!(::kernel::module_param::PARAM_OPS_U8),
"i16" => parse_quote!(::kernel::module_param::PARAM_OPS_I16),
@@ -197,6 +220,7 @@ fn param_ops_path(param_type: &str) -> Path {
"u64" => parse_quote!(::kernel::module_param::PARAM_OPS_U64),
"isize" => parse_quote!(::kernel::module_param::PARAM_OPS_ISIZE),
"usize" => parse_quote!(::kernel::module_param::PARAM_OPS_USIZE),
+ "string" => parse_quote!(::kernel::module_param::PARAM_OPS_STRING),
t => panic!("Unsupported parameter type {}", t),
}
}
@@ -340,7 +364,7 @@ macro_rules! parse_ordered_fields {
struct Parameter {
name: Ident,
- ptype: Ident,
+ ptype: Type,
default: Expr,
description: LitStr,
}
@@ -349,7 +373,7 @@ impl Parse for Parameter {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let name = input.parse()?;
input.parse::<Token![:]>()?;
- let ptype = input.parse()?;
+ let ptype: Type = input.parse()?;
let fields;
braced!(fields in input);
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
2026-02-26 23:47 ` [PATCH 2/8] rust: module_param: wire StringParam into the module! macro Matthew Wood
@ 2026-03-04 8:13 ` Petr Pavlu
2026-03-09 2:24 ` Matthew Wood
2026-03-06 19:27 ` Sami Tolvanen
1 sibling, 1 reply; 17+ messages in thread
From: Petr Pavlu @ 2026-03-04 8:13 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On 2/27/26 12:47 AM, Matthew Wood wrote:
> Add support for `string` as a parameter type in the module! macro.
>
> On the runtime side, add:
> - set_string_param(): an extern "C" callback matching the
> kernel_param_ops::set signature that stores the raw C string
> pointer directly into the SetOnce<StringParam> container, avoiding
> an unnecessary copy-and-parse round-trip.
> - PARAM_OPS_STRING: a static kernel_param_ops that uses
> set_string_param as its setter.
> - ModuleParam impl for StringParam with try_from_param_arg()
> returning -EINVAL, since string parameters are populated
> exclusively through the kernel's set callback.
>
> On the macro side:
> - Change the Parameter::ptype field from Ident to syn::Type to
> support path-qualified types.
Why is it necessary to change the type of Parameter::ptype? My
understanding is that this token can currently be "i8", "u8", ...,
"isize", "usize". Additionally, the value "string" should now be
accepted. When should one use a path-qualified type in this context?
> - Recognize the `string` shorthand and resolve it to the fully
> qualified ::kernel::module_param::StringParam type during code
> generation.
> - Wrap string default values with StringParam::from_c_str(c_str!(...))
> to produce a compile-time CStr-backed default.
> - Route `string` to PARAM_OPS_STRING in param_ops_path().
>
> Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
> ---
> rust/kernel/module_param.rs | 48 +++++++++++++++++++++++++++++++++++++
> rust/macros/module.rs | 42 +++++++++++++++++++++++++-------
> 2 files changed, 81 insertions(+), 9 deletions(-)
>
> diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
> index 80fe8643c0ab..67ff6f2ea9c2 100644
> --- a/rust/kernel/module_param.rs
> +++ b/rust/kernel/module_param.rs
> @@ -86,6 +86,36 @@ pub trait ModuleParam: Sized + Copy {
> })
> }
>
> +/// Set a string module parameter from a string.
> +///
> +/// Similar to [`set_param`] but for [`StringParam`].
> +///
> +/// # Safety
> +///
> +/// Same requirements as [`set_param`].
> +unsafe extern "C" fn set_string_param(
> + val: *const c_char,
> + param: *const bindings::kernel_param,
> +) -> c_int {
The safety comment is somewhat inaccurate because set_param() says that
the input value needs to be valid only for the duration of the call,
whereas set_string_param() and StringParam require it to be valid for
the module's lifetime.
--
Thanks,
Petr
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
2026-03-04 8:13 ` Petr Pavlu
@ 2026-03-09 2:24 ` Matthew Wood
0 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-03-09 2:24 UTC (permalink / raw)
To: Petr Pavlu
Cc: Miguel Ojeda, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On Wed, Mar 4, 2026 at 12:13 AM Petr Pavlu <petr.pavlu@suse.com> wrote:
>
> On 2/27/26 12:47 AM, Matthew Wood wrote:
> > Add support for `string` as a parameter type in the module! macro.
> >
> > On the runtime side, add:
> > - set_string_param(): an extern "C" callback matching the
> > kernel_param_ops::set signature that stores the raw C string
> > pointer directly into the SetOnce<StringParam> container, avoiding
> > an unnecessary copy-and-parse round-trip.
> > - PARAM_OPS_STRING: a static kernel_param_ops that uses
> > set_string_param as its setter.
> > - ModuleParam impl for StringParam with try_from_param_arg()
> > returning -EINVAL, since string parameters are populated
> > exclusively through the kernel's set callback.
> >
> > On the macro side:
> > - Change the Parameter::ptype field from Ident to syn::Type to
> > support path-qualified types.
>
> Why is it necessary to change the type of Parameter::ptype? My
> understanding is that this token can currently be "i8", "u8", ...,
> "isize", "usize". Additionally, the value "string" should now be
> accepted. When should one use a path-qualified type in this context?
>
Hi Petr,
Thanks for the review! Yes, I believe your point is correct and I will look
at this again. I think I left that after wanting to be able to use the
StringParam
type, but realized that matching on `string` is more ergonomic.
> > +/// Set a string module parameter from a string.
> > +///
> > +/// Similar to [`set_param`] but for [`StringParam`].
> > +///
> > +/// # Safety
> > +///
> > +/// Same requirements as [`set_param`].
> > +unsafe extern "C" fn set_string_param(
> > + val: *const c_char,
> > + param: *const bindings::kernel_param,
> > +) -> c_int {
>
> The safety comment is somewhat inaccurate because set_param() says that
> the input value needs to be valid only for the duration of the call,
> whereas set_string_param() and StringParam require it to be valid for
> the module's lifetime.
>
Yes, thank you. After reading these safety comments back I agree they need
to be fixed. Miguel had a similar point as well. I'll update these in v2.
Thank you!
- Mat
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
2026-02-26 23:47 ` [PATCH 2/8] rust: module_param: wire StringParam into the module! macro Matthew Wood
2026-03-04 8:13 ` Petr Pavlu
@ 2026-03-06 19:27 ` Sami Tolvanen
2026-03-09 2:27 ` Matthew Wood
1 sibling, 1 reply; 17+ messages in thread
From: Sami Tolvanen @ 2026-03-06 19:27 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On Thu, Feb 26, 2026 at 03:47:28PM -0800, Matthew Wood wrote:
> +/// Set a string module parameter from a string.
> +///
> +/// Similar to [`set_param`] but for [`StringParam`].
> +///
> +/// # Safety
> +///
> +/// Same requirements as [`set_param`].
> +unsafe extern "C" fn set_string_param(
> + val: *const c_char,
> + param: *const bindings::kernel_param,
> +) -> c_int {
> + if val.is_null() {
> + crate::pr_warn!("Null pointer passed to `module_param::set_string_param`");
> + return EINVAL.to_errno();
> + }
> +
> + crate::error::from_result(|| {
> + // SAFETY: val points to a valid C string from the kernel.
> + let cstr_param = unsafe { StringParam::from_ptr(val) };
> +
> + // SAFETY: By function safety requirements, param.arg points to our SetOnce<StringParam>.
> + let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<StringParam>>()) };
I do realize this matches set_param, and there's a good chance I
missed something when reading the macros, but doesn't arg actually
point to ModuleParamAccess<T> here? Since the struct is not repr(C),
isn't the compiler technically speaking allowed to reorder the
fields, which means SetOnce<T> might not actually be at offset 0?
> +
> + container
> + .populate(cstr_param)
> + .then_some(0)
> + .ok_or(kernel::error::code::EEXIST)
Does this mean the behavior for Rust modules differs from C modules if
the user specifies multiple instances of the same parameter? I believe
we just use the last value of the parameter instead of failing in C.
Sami
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
2026-03-06 19:27 ` Sami Tolvanen
@ 2026-03-09 2:27 ` Matthew Wood
0 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-03-09 2:27 UTC (permalink / raw)
To: Sami Tolvanen
Cc: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On Fri, Mar 6, 2026 at 11:28 AM Sami Tolvanen <samitolvanen@google.com> wrote:
>
> On Thu, Feb 26, 2026 at 03:47:28PM -0800, Matthew Wood wrote:
> > +/// Set a string module parameter from a string.
> > +///
> > +/// Similar to [`set_param`] but for [`StringParam`].
> > +///
> > +/// # Safety
> > +///
> > +/// Same requirements as [`set_param`].
> > +unsafe extern "C" fn set_string_param(
> > + val: *const c_char,
> > + param: *const bindings::kernel_param,
> > +) -> c_int {
> > + if val.is_null() {
> > + crate::pr_warn!("Null pointer passed to `module_param::set_string_param`");
> > + return EINVAL.to_errno();
> > + }
> > +
> > + crate::error::from_result(|| {
> > + // SAFETY: val points to a valid C string from the kernel.
> > + let cstr_param = unsafe { StringParam::from_ptr(val) };
> > +
> > + // SAFETY: By function safety requirements, param.arg points to our SetOnce<StringParam>.
> > + let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<StringParam>>()) };
>
> I do realize this matches set_param, and there's a good chance I
> missed something when reading the macros, but doesn't arg actually
> point to ModuleParamAccess<T> here? Since the struct is not repr(C),
> isn't the compiler technically speaking allowed to reorder the
> fields, which means SetOnce<T> might not actually be at offset 0?
>
Hi Sami,
I appreciate the review! This does seem to be an oversight, I'll try
to figure out the
correct implementation. I agree, `repr(C)` is likely needed here.
> > +
> > + container
> > + .populate(cstr_param)
> > + .then_some(0)
> > + .ok_or(kernel::error::code::EEXIST)
>
> Does this mean the behavior for Rust modules differs from C modules if
> the user specifies multiple instances of the same parameter? I believe
> we just use the last value of the parameter instead of failing in C.
>
> Sami
Yes, I'll fix that, the implementations should match.
Regards,
Mat
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 3/8] samples: rust_minimal: demonstrate string module parameter
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
2026-02-26 23:47 ` [PATCH 1/8] rust: module_param: add StringParam type for C string parameters Matthew Wood
2026-02-26 23:47 ` [PATCH 2/8] rust: module_param: wire StringParam into the module! macro Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-26 23:47 ` [PATCH 4/8] rust: module_param: add ObsKernelParam type Matthew Wood
` (5 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Add a `test_str` string parameter alongside the existing integer
parameter (renamed from `test_parameter` to `test_int` for clarity)
in the rust_minimal sample module.
The init function now prints both parameters to the kernel log,
showing how string parameters are declared, defaulted, and read
back via StringParam::as_cstr().
Also add module-level documentation showing usage via insmod and
kernel command-line with dotted notation.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
samples/rust/rust_minimal.rs | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs
index 8eb9583571d7..59955e95e31a 100644
--- a/samples/rust/rust_minimal.rs
+++ b/samples/rust/rust_minimal.rs
@@ -1,6 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
//! Rust minimal sample.
+//!
+//! This is a sample module written in Rust. It is intended to be a minimal
+//! example of how to write a module in Rust. It does not do anything useful,
+//! except print a message when it is loaded and unloaded.
+//!
+//! It provides examples of how to receive module parameters, which can be provided
+//! by the user when the module is loaded:
+//!
+//! ```
+//! insmod /lib/modules/$(uname -r)/kernel/samples/rust/rust_minimal.ko test_int=2 test_str=world
+//! ```
+//!
+//! or via kernel cmdline with module dotted notation (when built-in and not built as a module):
+//!
+//! ```
+//! ... rust_minimal.test_int=2 rust_minimal.test_str=world ...
+//! ```
use kernel::prelude::*;
@@ -11,10 +28,14 @@
description: "Rust minimal sample",
license: "GPL",
params: {
- test_parameter: i64 {
+ test_int: i64 {
default: 1,
description: "This parameter has a default of 1",
},
+ test_str: string {
+ default: "hello",
+ description: "This parameter has a default of hello",
+ }
},
}
@@ -26,9 +47,13 @@ impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
+ pr_info!("test_int: {}\n", *module_parameters::test_int.value());
pr_info!(
- "test_parameter: {}\n",
- *module_parameters::test_parameter.value()
+ "test_str: {}\n",
+ module_parameters::test_str
+ .value()
+ .as_cstr()
+ .expect("test_str has a default value")
);
let mut numbers = KVec::new();
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 4/8] rust: module_param: add ObsKernelParam type
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (2 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 3/8] samples: rust_minimal: demonstrate string module parameter Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-26 23:47 ` [PATCH 5/8] rust: module_param: add from_setup_arg() to ModuleParam trait Matthew Wood
` (4 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Add ObsKernelParam, a #[repr(C)] struct that mirrors the C
obs_kernel_param structure used by the __setup / early_param
infrastructure to register boot-time command-line handlers.
The struct contains:
- str_: pointer to the null-terminated setup string
- setup_func: the callback invoked when the kernel parses the
matching command-line token
- early: flag distinguishing early_param (1) from __setup (0)
Mark it Sync because instances are written at compile time and only
read during single-threaded early boot initialization.
This type will be used by the module! macro to emit __setup entries
in the .init.setup ELF section in subsequent patches.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
rust/kernel/module_param.rs | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 67ff6f2ea9c2..54379a2bba51 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -26,6 +26,20 @@ pub const fn new(val: bindings::kernel_param) -> Self {
// from Rust module.
unsafe impl Sync for KernelParam {}
+/// Placed in the `.init.setup` ELF section by the `module!` macro.
+#[repr(C)]
+pub struct ObsKernelParam {
+ /// Pointer to the null-terminated setup string (e.g., `"netconsole=\0"`).
+ pub str_: *const c_char,
+ /// The setup callback function.
+ pub setup_func: Option<unsafe extern "C" fn(*mut c_char) -> c_int>,
+ /// Whether this is an early parameter (0 for `__setup`, 1 for `early_param`).
+ pub early: c_int,
+}
+
+// SAFETY: Only written at compile time, read during single-threaded boot init.
+unsafe impl Sync for ObsKernelParam {}
+
/// Types that can be used for module parameters.
// NOTE: This trait is `Copy` because drop could produce unsoundness during teardown.
pub trait ModuleParam: Sized + Copy {
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 5/8] rust: module_param: add from_setup_arg() to ModuleParam trait
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (3 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 4/8] rust: module_param: add ObsKernelParam type Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-26 23:47 ` [PATCH 6/8] rust: macros: add early_param support to module! macro Matthew Wood
` (3 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Extend the ModuleParam trait with from_setup_arg(), which constructs
a parameter value from a raw C string pointer received via a __setup
callback during early boot.
The default implementation converts the pointer to a CStr and
delegates to try_from_param_arg(), which handles the parse-from-string
path used by integer types.
StringParam overrides this method to store the raw pointer directly
without parsing, since the pointer originates from static_command_line
and remains valid for the kernel's lifetime.
Also add ModuleParamAccess::set_value() to allow the __setup callback
generated by the module! macro to populate the SetOnce container with
first-write-wins semantics.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
rust/kernel/module_param.rs | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 54379a2bba51..5e14dbe03865 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -45,6 +45,21 @@ unsafe impl Sync for ObsKernelParam {}
pub trait ModuleParam: Sized + Copy {
/// Parse a parameter argument into the parameter value.
fn try_from_param_arg(arg: &BStr) -> Result<Self>;
+
+ /// Create a parameter value from a raw `__setup` callback argument.
+ ///
+ /// Default implementation: parse the null-terminated C string via
+ /// [`ModuleParam::try_from_param_arg`]. `StringParam` overrides this to store the pointer
+ /// directly.
+ ///
+ /// # Safety
+ ///
+ /// `val` must point to a valid null-terminated string.
+ unsafe fn from_setup_arg(val: *const c_char) -> Result<Self> {
+ // SAFETY: Caller guarantees `val` points to a valid null-terminated string.
+ let cstr = unsafe { CStr::from_char_ptr(val) };
+ Self::try_from_param_arg(cstr.as_ref())
+ }
}
/// Set the module parameter from a string.
@@ -226,6 +241,12 @@ fn try_from_param_arg(_arg: &BStr) -> Result<Self> {
// when using PARAM_OPS_STRING.
Err(EINVAL)
}
+
+ unsafe fn from_setup_arg(val: *const c_char) -> Result<Self> {
+ // SAFETY: Caller guarantees `val` points to a valid null-terminated string.
+ // The pointer comes from `static_command_line` which is valid for the kernel's lifetime.
+ Ok(unsafe { StringParam::from_ptr(val) })
+ }
}
/// A wrapper for kernel parameters.
@@ -266,6 +287,14 @@ pub fn value(&self) -> &T {
pub const fn as_void_ptr(&self) -> *mut c_void {
core::ptr::from_ref(self).cast_mut().cast()
}
+
+ /// Set the parameter value directly.
+ ///
+ /// Returns `true` if successfully set, `false` if already populated
+ /// (first-write-wins semantics via [`SetOnce`]).
+ pub fn set_value(&self, val: T) -> bool {
+ self.value.populate(val)
+ }
}
#[doc(hidden)]
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6/8] rust: macros: add early_param support to module! macro
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (4 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 5/8] rust: module_param: add from_setup_arg() to ModuleParam trait Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-03-06 17:22 ` Petr Pavlu
2026-02-26 23:47 ` [PATCH 7/8] samples: rust_minimal: demonstrate early_param usage Matthew Wood
` (2 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
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 <thepacketgeek@gmail.com>
---
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
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 6/8] rust: macros: add early_param support to module! macro
2026-02-26 23:47 ` [PATCH 6/8] rust: macros: add early_param support to module! macro Matthew Wood
@ 2026-03-06 17:22 ` Petr Pavlu
0 siblings, 0 replies; 17+ messages in thread
From: Petr Pavlu @ 2026-03-06 17:22 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
On 2/27/26 12:47 AM, Matthew Wood wrote:
> 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 early_param() macro on the C side creates an obs_kernel_param entry
with early=1. However, this patch handles the new early_param setting in
Rust by creating an obs_kernel_param with early=0. Non-early
obs_kernel_params are processed by obsolete_checksetup() during the
parse_args("Booting kernel") call in start_kernel(), which also handles
regular kernel_params.
Actual early parameters are handled by parse_early_param(). Note that
the command line parsed in this case is marked as __initdata, so it
would not be available for the kernel's entire lifetime, as the previous
patches assume.
So I'm somewhat confused by this patch, or perhaps I misunderstood
something?
> 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
[...]
> + #[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,
> + };
--
Thanks,
Petr
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 7/8] samples: rust_minimal: demonstrate early_param usage
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (5 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 6/8] rust: macros: add early_param support to module! macro Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-26 23:47 ` [PATCH 8/8] rust: macros: add configurable initcall levels to module! macro Matthew Wood
2026-02-27 13:27 ` [PATCH 0/8] rust: module parameter extensions Matthew Wood
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Add an early_param annotation to the test_str parameter in the
rust_minimal sample module, registering it as "testing_early_str="
on the kernel command line.
When the module is built-in, this allows the parameter to be set
directly on the boot command line without dotted module notation:
testing_early_str=world
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
samples/rust/rust_minimal.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs
index 59955e95e31a..9fc0c0a864d3 100644
--- a/samples/rust/rust_minimal.rs
+++ b/samples/rust/rust_minimal.rs
@@ -35,6 +35,8 @@
test_str: string {
default: "hello",
description: "This parameter has a default of hello",
+ // This will allow the arg to be used in kernel cmdline without dotted notation
+ early_param: "testing_early_str=",
}
},
}
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 8/8] rust: macros: add configurable initcall levels to module! macro
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (6 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 7/8] samples: rust_minimal: demonstrate early_param usage Matthew Wood
@ 2026-02-26 23:47 ` Matthew Wood
2026-02-27 13:27 ` [PATCH 0/8] rust: module parameter extensions Matthew Wood
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-26 23:47 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
Currently all Rust modules using the module! macro are placed at
initcall level 6 (device_initcall). Some modules need to initialize
earlier in the boot sequence to provide services that other subsystems
depend on.
Add an InitCallLevel enum representing all eight standard initcall
levels (pure through late) and map each to its corresponding
.initcallN.init ELF section.
Expose this as an optional `initcall` field in the module! macro.
When omitted, the default remains level 6 (device) so existing
modules are unaffected. Example usage:
module! {
...
initcall: subsys,
}
This only affects built-in modules; loadable modules always enter
through init_module() regardless of the declared level.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
---
rust/macros/lib.rs | 4 ++++
rust/macros/module.rs | 56 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 83dcc89a425a..cfde59e81cdd 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -135,6 +135,10 @@
/// - `alias`: array of ASCII string literals of the alias names of the kernel module.
/// - `firmware`: array of ASCII string literals of the firmware files of
/// the kernel module.
+/// - `initcall`: initcall level for built-in modules. Valid values are:
+/// `pure` (0), `core` (1), `postcore` (2), `arch` (3), `subsys` (4),
+/// `fs` (5), `device` (6, the default), and `late` (7).
+/// This only affects built-in modules; loadable modules always use `init_module()`.
#[proc_macro]
pub fn module(input: TokenStream) -> TokenStream {
module::module(parse_macro_input!(input))
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 4d2e144fa6de..1e210fee4506 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -436,6 +436,53 @@ macro_rules! parse_ordered_fields {
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum InitCallLevel {
+ Pure,
+ Core,
+ PostCore,
+ Arch,
+ Subsys,
+ Fs,
+ Device,
+ Late,
+}
+
+impl InitCallLevel {
+ fn section(&self) -> &'static str {
+ match self {
+ Self::Pure => ".initcall0.init",
+ Self::Core => ".initcall1.init",
+ Self::PostCore => ".initcall2.init",
+ Self::Arch => ".initcall3.init",
+ Self::Subsys => ".initcall4.init",
+ Self::Fs => ".initcall5.init",
+ Self::Device => ".initcall6.init",
+ Self::Late => ".initcall7.init",
+ }
+ }
+}
+
+impl Parse for InitCallLevel {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let ident: Ident = input.call(Ident::parse_any)?;
+ match ident.to_string().as_str() {
+ "pure" => Ok(Self::Pure),
+ "core" => Ok(Self::Core),
+ "postcore" => Ok(Self::PostCore),
+ "arch" => Ok(Self::Arch),
+ "subsys" => Ok(Self::Subsys),
+ "fs" => Ok(Self::Fs),
+ "device" => Ok(Self::Device),
+ "late" => Ok(Self::Late),
+ _ => Err(Error::new_spanned(
+ ident,
+ "invalid initcall level. Valid values are: pure, core, postcore, arch, subsys, fs, device, late",
+ )),
+ }
+ }
+}
+
struct Parameter {
name: Ident,
ptype: Type,
@@ -480,6 +527,7 @@ pub(crate) struct ModuleInfo {
firmware: Option<Punctuated<AsciiLitStr, Token![,]>>,
imports_ns: Option<Punctuated<AsciiLitStr, Token![,]>>,
params: Option<Punctuated<Parameter, Token![,]>>,
+ initcall: Option<InitCallLevel>,
}
impl Parse for ModuleInfo {
@@ -515,6 +563,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
braced!(list in input);
Punctuated::parse_terminated(&list)?
},
+ initcall => input.parse()?,
);
Ok(ModuleInfo {
@@ -527,6 +576,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
firmware,
imports_ns,
params,
+ initcall,
})
}
}
@@ -542,6 +592,7 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
firmware,
imports_ns,
params: _,
+ initcall,
} = &info;
// Rust does not allow hyphens in identifiers, use underscore instead.
@@ -587,7 +638,10 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
let ident_init = format_ident!("__{ident}_init");
let ident_exit = format_ident!("__{ident}_exit");
let ident_initcall = format_ident!("__{ident}_initcall");
- let initcall_section = ".initcall6.init";
+ let initcall_section = initcall
+ .as_ref()
+ .unwrap_or(&InitCallLevel::Device)
+ .section();
let global_asm = format!(
r#".section "{initcall_section}", "a"
--
2.52.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 0/8] rust: module parameter extensions
2026-02-26 23:47 [PATCH 0/8] rust: module parameter extensions Matthew Wood
` (7 preceding siblings ...)
2026-02-26 23:47 ` [PATCH 8/8] rust: macros: add configurable initcall levels to module! macro Matthew Wood
@ 2026-02-27 13:27 ` Matthew Wood
8 siblings, 0 replies; 17+ messages in thread
From: Matthew Wood @ 2026-02-27 13:27 UTC (permalink / raw)
To: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen
Cc: Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel, Breno Leitao
> This series extends the Rust module! macro with three capabilities that
> are available to C modules but currently missing from the Rust
> abstractions: string parameters, early boot command-line parameters, and
> configurable initcall levels.
>
> The existing Rust module parameter infrastructure supports integer types
> (i8..u64, isize, usize) but has no way to accept string values.
> Additionally, built-in Rust modules cannot register parameters for early
> boot command-line parsing (__setup / early_param), and all Rust modules
> are hard-coded to initcall level 6 (device_initcall) with no way to
> control initialization ordering.
>
I forgot to mention that these changes are a dependency for a
netconsole rust implementation
I'm working on and I need these three features to match the current behavior.
Regards,
Matthew
^ permalink raw reply [flat|nested] 17+ messages in thread