From nobody Thu Oct 2 02:05:53 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 417A8303CAA; Wed, 24 Sep 2025 12:40:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758717636; cv=none; b=MXHNMrukjY0vPpYNj/IQk4PfwL3o9IfT78wsuPuv9MP1ez/PlJ/ZqOAdNcV8HMP4SAcJYfuqHwNTQr2fLpxbWL92b3SsxJ4gYjSKS5QJWp32E6C6Ay7h6+GI3J6VbyjeUBXEHensHZSFRAj9qrAJLlVRcRWa9Itvp8brfFDtF9g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758717636; c=relaxed/simple; bh=f0aezQP+lTTBaLYyzhKAQG36mNqIE1d7LdhlwAQjbjo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mCQrvVgIUwK7dv6TCiSaL7OXnXjtGYMU29jfLuMmfLde/OJAKqKXlrQLzoNHPrDCodpWiV/4yOKnWDfQXc/TYciJiorC42q4tEsOseFNWpdefeYoub4qyoP5OE2b+Ri6OJ2Yq2jyAhz4e7X4bd+zMRF0rmwrlKriEbEij7mcDzw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ole11jZn; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ole11jZn" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 74244C4CEE7; Wed, 24 Sep 2025 12:40:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1758717635; bh=f0aezQP+lTTBaLYyzhKAQG36mNqIE1d7LdhlwAQjbjo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Ole11jZnZlaiL1G20R6P5t5vIg4Jt2gCoYgOHMIoAuEuQ2+/t0US0KRmyqsoHS32X D0rqtm7N5ymemF286CZzCKMCmJIdcxgHKonPXMepFXDZT8APL1bNqb7H/mpbe3nNWR c6UVE1x2herlRaxFmZjN34axKJCQi9yngXYA7JG2gikXAk2P9vhetUELcTPJbZQ+/B XeKS2TRE/MgJYulHM95kDREUj4HVt0UZUNCJEH6/1F+mMi9sTHsIALJ2N879WBqCGn q71odUd9zhylTXruZuAFad3uu5l02YBkrNvH+UB9eD6IJ81tzUumnzCXAIzZ5gJdjC Njt4mMpkbUuYw== From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:28 +0200 Subject: [PATCH v18 5/7] rust: module: update the module macro with module parameter support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250924-module-params-v3-v18-5-bf512c35d910@kernel.org> References: <20250924-module-params-v3-v18-0-bf512c35d910@kernel.org> In-Reply-To: <20250924-module-params-v3-v18-0-bf512c35d910@kernel.org> To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Alice Ryhl , Masahiro Yamada , Nathan Chancellor , Luis Chamberlain , Danilo Krummrich , Benno Lossin , Daniel Gomez , Benno Lossin , Nicolas Schier Cc: Trevor Gross , Adam Bratschi-Kaye , rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kbuild@vger.kernel.org, Petr Pavlu , Sami Tolvanen , Daniel Gomez , Simona Vetter , Greg KH , Fiona Behrens , Daniel Almeida , linux-modules@vger.kernel.org, Andreas Hindborg X-Mailer: b4 0.15-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=13520; i=a.hindborg@kernel.org; h=from:subject:message-id; bh=f0aezQP+lTTBaLYyzhKAQG36mNqIE1d7LdhlwAQjbjo=; b=owEBbQKS/ZANAwAIAeG4Gj55KGN3AcsmYgBo0+akj/GBJit09OPJq5euH1Xeio+bC4AC+37D0 d2TqZZsZ9mJAjMEAAEIAB0WIQQSwflHVr98KhXWwBLhuBo+eShjdwUCaNPmpAAKCRDhuBo+eShj d0r/D/9E1ASOq4KkX8os3CYf0a0KwkmwYF96boJzKBXuWQU1UMhXD6JvYwu8PpQbL68nNUiTOTo m9IA3WOuUBPrSloHjVQwNdzLmiZb/8ROOCcUW/5bm7zNT5XgOIsjVT8uQHp9+oGnsRLGJV/u7Xu juxzOOevcA1RX6vMjhtMKg8xs0K92qyaXLYnZVH96cEqiXwG8ZBL64ewyoZIYgmtsa3/HZzgFio p4nOFUiaodKeR5Pm+RdHIKidIC82vZVLxqzqKaWgEzE/vonoNkixXHtj1dQwk5y+J4XOuzlyORK /4JHGS44VTnkkwh7uwIZ6yzrUmNyVrytZtNtYffMb4xk2iRmv5ehUeM4Mm2Es+KLil4oMnAJAis mQ/4Jmdxfu3YUdCBhh60mdKMicJQUidaDEmf8nwGhYRr4pcCGllNjkSpXw2rcTzkuRdnAIh/NbC TgZMfySh265+7rkKm/rkq5CP2Wt3RM7D2Zh6NsZ4ydvSUl2VuA/iGHJuA3gVfKP5KtZdF7KVapj lYKPgrt8LY8EaEbWAczmUKXlQa2mX+tr0B6exRB9bHnbZApQtUbLx2RK7UJiphC80qyGt0elKpe LW3J82WP2I4qSH+t6owaUQ3YQwuLiPzBD44p28JQdgOuP2PX1tURP1KaewYXNtUavi/GKXH4IXT qMzL46WWr7fGV3A== X-Developer-Key: i=a.hindborg@kernel.org; a=openpgp; fpr=3108C10F46872E248D1FB221376EB100563EF7A7 Allow module parameters to be declared in the rust `module!` macro. Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/macros/helpers.rs | 25 +++++++ rust/macros/lib.rs | 31 +++++++++ rust/macros/module.rs | 178 +++++++++++++++++++++++++++++++++++++++++++++= +--- 3 files changed, 224 insertions(+), 10 deletions(-) diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs index e2602be402c10..365d7eb499c08 100644 --- a/rust/macros/helpers.rs +++ b/rust/macros/helpers.rs @@ -10,6 +10,17 @@ pub(crate) fn try_ident(it: &mut token_stream::IntoIter)= -> Option { } } =20 +pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option { + let peek =3D it.clone().next(); + match peek { + Some(TokenTree::Punct(punct)) if punct.as_char() =3D=3D '-' =3D> { + let _ =3D it.next(); + Some(punct.as_char()) + } + _ =3D> None, + } +} + pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option { if let Some(TokenTree::Literal(literal)) =3D it.next() { Some(literal.to_string()) @@ -103,3 +114,17 @@ pub(crate) fn file() -> String { proc_macro::Span::call_site().file() } } + +/// Parse a token stream of the form `expected_name: "value",` and return = the +/// string in the position of "value". +/// +/// # Panics +/// +/// - On parse error. +pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expecte= d_name: &str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let string =3D expect_string(it); + assert_eq!(expect_punct(it), ','); + string +} diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index fa847cf3a9b5f..2fb520dc930af 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -28,6 +28,30 @@ /// The `type` argument should be a type which implements the [`Module`] /// trait. Also accepts various forms of kernel metadata. /// +/// The `params` field describe module parameters. Each entry has the form +/// +/// ```ignore +/// parameter_name: type { +/// default: default_value, +/// description: "Description", +/// } +/// ``` +/// +/// `type` may be one of +/// +/// - [`i8`] +/// - [`u8`] +/// - [`i8`] +/// - [`u8`] +/// - [`i16`] +/// - [`u16`] +/// - [`i32`] +/// - [`u32`] +/// - [`i64`] +/// - [`u64`] +/// - [`isize`] +/// - [`usize`] +/// /// C header: [`include/linux/moduleparam.h`](srctree/include/linux/module= param.h) /// /// [`Module`]: ../kernel/trait.Module.html @@ -44,6 +68,12 @@ /// description: "My very own kernel module!", /// license: "GPL", /// alias: ["alternate_module_name"], +/// params: { +/// my_parameter: i64 { +/// default: 1, +/// description: "This parameter has a default of 1", +/// }, +/// }, /// } /// /// struct MyModule(i32); @@ -52,6 +82,7 @@ /// fn init(_module: &'static ThisModule) -> Result { /// let foo: i32 =3D 42; /// pr_info!("I contain: {}\n", foo); +/// pr_info!("i32 param is: {}\n", module_parameters::my_paramete= r.read()); /// Ok(Self(foo)) /// } /// } diff --git a/rust/macros/module.rs b/rust/macros/module.rs index cbf3ac0a8f7ba..d62e9c1e2a898 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -26,6 +26,7 @@ struct ModInfoBuilder<'a> { module: &'a str, counter: usize, buffer: String, + param_buffer: String, } =20 impl<'a> ModInfoBuilder<'a> { @@ -34,10 +35,11 @@ fn new(module: &'a str) -> Self { module, counter: 0, buffer: String::new(), + param_buffer: String::new(), } } =20 - fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { + fn emit_base(&mut self, field: &str, content: &str, builtin: bool, par= am: bool) { let string =3D if builtin { // Built-in modules prefix their modinfo strings by `module.`. format!( @@ -51,8 +53,14 @@ fn emit_base(&mut self, field: &str, content: &str, buil= tin: bool) { format!("{field}=3D{content}\0") }; =20 + let buffer =3D if param { + &mut self.param_buffer + } else { + &mut self.buffer + }; + write!( - &mut self.buffer, + buffer, " {cfg} #[doc(hidden)] @@ -75,20 +83,119 @@ fn emit_base(&mut self, field: &str, content: &str, bu= iltin: bool) { self.counter +=3D 1; } =20 - fn emit_only_builtin(&mut self, field: &str, content: &str) { - self.emit_base(field, content, true) + fn emit_only_builtin(&mut self, field: &str, content: &str, param: boo= l) { + self.emit_base(field, content, true, param) } =20 - fn emit_only_loadable(&mut self, field: &str, content: &str) { - self.emit_base(field, content, false) + fn emit_only_loadable(&mut self, field: &str, content: &str, param: bo= ol) { + self.emit_base(field, content, false, param) } =20 fn emit(&mut self, field: &str, content: &str) { - self.emit_only_builtin(field, content); - self.emit_only_loadable(field, content); + self.emit_internal(field, content, false); + } + + fn emit_internal(&mut self, field: &str, content: &str, param: bool) { + self.emit_only_builtin(field, content, param); + self.emit_only_loadable(field, content, param); + } + + fn emit_param(&mut self, field: &str, param: &str, content: &str) { + let content =3D format!("{param}:{content}", param =3D param, cont= ent =3D content); + self.emit_internal(field, &content, true); + } + + fn emit_params(&mut self, info: &ModuleInfo) { + let Some(params) =3D &info.params else { + return; + }; + + for param in params { + let ops =3D param_ops_path(¶m.ptype); + + // Note: The spelling of these fields is dictated by the user = space + // tool `modinfo`. + self.emit_param("parmtype", ¶m.name, ¶m.ptype); + self.emit_param("parm", ¶m.name, ¶m.description); + + write!( + self.param_buffer, + " + pub(crate) static {param_name}: + ::kernel::module_param::ModuleParamAccess<{param_type}= > =3D + ::kernel::module_param::ModuleParamAccess::new({pa= ram_default}); + + const _: () =3D {{ + #[link_section =3D \"__param\"] + #[used] + static __{module_name}_{param_name}_struct: + ::kernel::module_param::KernelParam =3D + ::kernel::module_param::KernelParam::new( + ::kernel::bindings::kernel_param {{ + name: if ::core::cfg!(MODULE) {{ + ::kernel::c_str!(\"{param_name}\").as_= bytes_with_nul() + }} else {{ + ::kernel::c_str!(\"{module_name}.{para= m_name}\") + .as_bytes_with_nul() + }}.as_ptr(), + // SAFETY: `__this_module` is constructed = by the kernel at load + // time and will not be freed until the mo= dule is unloaded. + #[cfg(MODULE)] + mod_: unsafe {{ + core::ptr::from_ref(&::kernel::binding= s::__this_module) + .cast_mut() + }}, + #[cfg(not(MODULE))] + mod_: ::core::ptr::null_mut(), + ops: core::ptr::from_ref(&{ops}), + perm: 0, // Will not appear in sysfs + level: -1, + flags: 0, + __bindgen_anon_1: ::kernel::bindings::kern= el_param__bindgen_ty_1 {{ + arg: {param_name}.as_void_ptr() + }}, + }} + ); + }}; + ", + module_name =3D info.name, + param_type =3D param.ptype, + param_default =3D param.default, + param_name =3D param.name, + ops =3D ops, + ) + .unwrap(); + } + } +} + +fn param_ops_path(param_type: &str) -> &'static str { + match param_type { + "i8" =3D> "::kernel::module_param::PARAM_OPS_I8", + "u8" =3D> "::kernel::module_param::PARAM_OPS_U8", + "i16" =3D> "::kernel::module_param::PARAM_OPS_I16", + "u16" =3D> "::kernel::module_param::PARAM_OPS_U16", + "i32" =3D> "::kernel::module_param::PARAM_OPS_I32", + "u32" =3D> "::kernel::module_param::PARAM_OPS_U32", + "i64" =3D> "::kernel::module_param::PARAM_OPS_I64", + "u64" =3D> "::kernel::module_param::PARAM_OPS_U64", + "isize" =3D> "::kernel::module_param::PARAM_OPS_ISIZE", + "usize" =3D> "::kernel::module_param::PARAM_OPS_USIZE", + t =3D> panic!("Unsupported parameter type {}", t), } } =20 +fn expect_param_default(param_it: &mut token_stream::IntoIter) -> String { + assert_eq!(expect_ident(param_it), "default"); + assert_eq!(expect_punct(param_it), ':'); + let sign =3D try_sign(param_it); + let default =3D try_literal(param_it).expect("Expected default param v= alue"); + assert_eq!(expect_punct(param_it), ','); + let mut value =3D sign.map(String::from).unwrap_or_default(); + value.push_str(&default); + value +} + #[derive(Debug, Default)] struct ModuleInfo { type_: String, @@ -98,6 +205,50 @@ struct ModuleInfo { description: Option, alias: Option>, firmware: Option>, + params: Option>, +} + +#[derive(Debug)] +struct Parameter { + name: String, + ptype: String, + default: String, + description: String, +} + +fn expect_params(it: &mut token_stream::IntoIter) -> Vec { + let params =3D expect_group(it); + assert_eq!(params.delimiter(), Delimiter::Brace); + let mut it =3D params.stream().into_iter(); + let mut parsed =3D Vec::new(); + + loop { + let param_name =3D match it.next() { + Some(TokenTree::Ident(ident)) =3D> ident.to_string(), + Some(_) =3D> panic!("Expected Ident or end"), + None =3D> break, + }; + + assert_eq!(expect_punct(&mut it), ':'); + let param_type =3D expect_ident(&mut it); + let group =3D expect_group(&mut it); + assert_eq!(group.delimiter(), Delimiter::Brace); + assert_eq!(expect_punct(&mut it), ','); + + let mut param_it =3D group.stream().into_iter(); + let param_default =3D expect_param_default(&mut param_it); + let param_description =3D expect_string_field(&mut param_it, "desc= ription"); + expect_end(&mut param_it); + + parsed.push(Parameter { + name: param_name, + ptype: param_type, + default: param_default, + description: param_description, + }) + } + + parsed } =20 impl ModuleInfo { @@ -112,6 +263,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { "license", "alias", "firmware", + "params", ]; const REQUIRED_KEYS: &[&str] =3D &["type", "name", "license"]; let mut seen_keys =3D Vec::new(); @@ -137,6 +289,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { "license" =3D> info.license =3D expect_string_ascii(it), "alias" =3D> info.alias =3D Some(expect_string_array(it)), "firmware" =3D> info.firmware =3D Some(expect_string_array= (it)), + "params" =3D> info.params =3D Some(expect_params(it)), _ =3D> panic!("Unknown key \"{key}\". Valid keys are: {EXP= ECTED_KEYS:?}."), } =20 @@ -199,7 +352,9 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { // Built-in modules also export the `file` modinfo string. let file =3D std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE= environmental variable"); - modinfo.emit_only_builtin("file", &file); + modinfo.emit_only_builtin("file", &file, false); + + modinfo.emit_params(&info); =20 format!( " @@ -363,15 +518,18 @@ unsafe fn __exit() {{ __MOD.assume_init_drop(); }} }} - {modinfo} }} }} + mod module_parameters {{ + {params} + }} ", type_ =3D info.type_, name =3D info.name, ident =3D ident, modinfo =3D modinfo.buffer, + params =3D modinfo.param_buffer, initcall_section =3D ".initcall6.init" ) .parse() --=20 2.47.2