linux-modules.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 02/11] rust: macros: use `quote!` from vendored crate
       [not found] <20260107161729.3855851-1-gary@kernel.org>
@ 2026-01-07 16:15 ` Gary Guo
  2026-01-07 16:15 ` [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 8+ messages in thread
From: Gary Guo @ 2026-01-07 16:15 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar,
	Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Tamir Duberstein, Igor Korotin,
	José Expósito, Greg Kroah-Hartman
  Cc: rust-for-linux, Guilherme Giacomo Simoes, linux-kernel,
	linux-kselftest, kunit-dev, linux-modules

From: Gary Guo <gary@garyguo.net>

With `quote` crate now vendored in the kernel, we can remove our custom
`quote!` macro implementation and just rely on that crate instead.

The `quote` crate uses types from the `proc-macro2` library so we also
update to use that, and perform conversion in the top-level lib.rs.

Clippy complains about unnecessary `.to_string()` as `proc-macro2`
provides additional `PartialEq` impl, so they are removed.

Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/macros/concat_idents.rs |   2 +-
 rust/macros/export.rs        |   4 +-
 rust/macros/fmt.rs           |   4 +-
 rust/macros/helpers.rs       |   4 +-
 rust/macros/kunit.rs         |   5 +-
 rust/macros/lib.rs           |  21 ++--
 rust/macros/module.rs        |   6 +-
 rust/macros/paste.rs         |   2 +-
 rust/macros/quote.rs         | 182 -----------------------------------
 rust/macros/vtable.rs        |   7 +-
 10 files changed, 32 insertions(+), 205 deletions(-)
 delete mode 100644 rust/macros/quote.rs

diff --git a/rust/macros/concat_idents.rs b/rust/macros/concat_idents.rs
index 7e4b450f3a507..12cb231c3d715 100644
--- a/rust/macros/concat_idents.rs
+++ b/rust/macros/concat_idents.rs
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Ident, TokenStream, TokenTree};
 
 use crate::helpers::expect_punct;
 
diff --git a/rust/macros/export.rs b/rust/macros/export.rs
index a08f6337d5c8d..92d9b30971929 100644
--- a/rust/macros/export.rs
+++ b/rust/macros/export.rs
@@ -1,7 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 
+use proc_macro2::TokenStream;
+use quote::quote;
+
 use crate::helpers::function_name;
-use proc_macro::TokenStream;
 
 /// Please see [`crate::export`] for documentation.
 pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
diff --git a/rust/macros/fmt.rs b/rust/macros/fmt.rs
index 2f4b9f6e22110..19f709262552b 100644
--- a/rust/macros/fmt.rs
+++ b/rust/macros/fmt.rs
@@ -1,8 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro::{Ident, TokenStream, TokenTree};
 use std::collections::BTreeSet;
 
+use proc_macro2::{Ident, TokenStream, TokenTree};
+use quote::quote_spanned;
+
 /// Please see [`crate::fmt`] for documentation.
 pub(crate) fn fmt(input: TokenStream) -> TokenStream {
     let mut input = input.into_iter();
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 365d7eb499c08..13fafaba12261 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
 
 pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
     if let Some(TokenTree::Ident(ident)) = it.next() {
@@ -86,7 +86,7 @@ pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
     let mut input = input.into_iter();
     while let Some(token) = input.next() {
         match token {
-            TokenTree::Ident(i) if i.to_string() == "fn" => {
+            TokenTree::Ident(i) if i == "fn" => {
                 if let Some(TokenTree::Ident(i)) = input.next() {
                     return Some(i);
                 }
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index b395bb0536959..5cd6aa5eef07d 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -4,10 +4,11 @@
 //!
 //! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
 
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
 use std::collections::HashMap;
 use std::fmt::Write;
 
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
 pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
     let attr = attr.to_string();
 
@@ -59,7 +60,7 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
                 }
                 _ => (),
             },
-            TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
+            TokenTree::Ident(i) if i == "fn" && attributes.contains_key("test") => {
                 if let Some(TokenTree::Ident(test_name)) = body_it.next() {
                     tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
                 }
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index b38002151871a..945982c21f703 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -11,8 +11,6 @@
 // to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0.
 #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
 
-#[macro_use]
-mod quote;
 mod concat_idents;
 mod export;
 mod fmt;
@@ -132,7 +130,7 @@
 ///     the kernel module.
 #[proc_macro]
 pub fn module(ts: TokenStream) -> TokenStream {
-    module::module(ts)
+    module::module(ts.into()).into()
 }
 
 /// Declares or implements a vtable trait.
@@ -207,7 +205,7 @@ pub fn module(ts: TokenStream) -> TokenStream {
 /// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
 #[proc_macro_attribute]
 pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
-    vtable::vtable(attr, ts)
+    vtable::vtable(attr.into(), ts.into()).into()
 }
 
 /// Export a function so that C code can call it via a header file.
@@ -230,7 +228,7 @@ pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
 /// automatically exported with `EXPORT_SYMBOL_GPL`.
 #[proc_macro_attribute]
 pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
-    export::export(attr, ts)
+    export::export(attr.into(), ts.into()).into()
 }
 
 /// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
@@ -248,7 +246,7 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
 /// [`pr_info!`]: ../kernel/macro.pr_info.html
 #[proc_macro]
 pub fn fmt(input: TokenStream) -> TokenStream {
-    fmt::fmt(input)
+    fmt::fmt(input.into()).into()
 }
 
 /// Concatenate two identifiers.
@@ -306,7 +304,7 @@ pub fn fmt(input: TokenStream) -> TokenStream {
 /// ```
 #[proc_macro]
 pub fn concat_idents(ts: TokenStream) -> TokenStream {
-    concat_idents::concat_idents(ts)
+    concat_idents::concat_idents(ts.into()).into()
 }
 
 /// Paste identifiers together.
@@ -444,9 +442,12 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {
 /// [`paste`]: https://docs.rs/paste/
 #[proc_macro]
 pub fn paste(input: TokenStream) -> TokenStream {
-    let mut tokens = input.into_iter().collect();
+    let mut tokens = proc_macro2::TokenStream::from(input).into_iter().collect();
     paste::expand(&mut tokens);
-    tokens.into_iter().collect()
+    tokens
+        .into_iter()
+        .collect::<proc_macro2::TokenStream>()
+        .into()
 }
 
 /// Registers a KUnit test suite and its test cases using a user-space like syntax.
@@ -473,5 +474,5 @@ pub fn paste(input: TokenStream) -> TokenStream {
 /// ```
 #[proc_macro_attribute]
 pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
-    kunit::kunit_tests(attr, ts)
+    kunit::kunit_tests(attr.into(), ts.into()).into()
 }
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 80cb9b16f5aaf..b855a2b586e18 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,9 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use crate::helpers::*;
-use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
 use std::fmt::Write;
 
+use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+
+use crate::helpers::*;
+
 fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
     let group = expect_group(it);
     assert_eq!(group.delimiter(), Delimiter::Bracket);
diff --git a/rust/macros/paste.rs b/rust/macros/paste.rs
index cce712d19855b..2181e312a7d32 100644
--- a/rust/macros/paste.rs
+++ b/rust/macros/paste.rs
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
+use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
 
 fn concat_helper(tokens: &[TokenTree]) -> Vec<(String, Span)> {
     let mut tokens = tokens.iter();
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
deleted file mode 100644
index ddfc21577539c..0000000000000
--- a/rust/macros/quote.rs
+++ /dev/null
@@ -1,182 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 OR MIT
-
-use proc_macro::{TokenStream, TokenTree};
-
-pub(crate) trait ToTokens {
-    fn to_tokens(&self, tokens: &mut TokenStream);
-}
-
-impl<T: ToTokens> ToTokens for Option<T> {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        if let Some(v) = self {
-            v.to_tokens(tokens);
-        }
-    }
-}
-
-impl ToTokens for proc_macro::Group {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend([TokenTree::from(self.clone())]);
-    }
-}
-
-impl ToTokens for proc_macro::Ident {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend([TokenTree::from(self.clone())]);
-    }
-}
-
-impl ToTokens for TokenTree {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend([self.clone()]);
-    }
-}
-
-impl ToTokens for TokenStream {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(self.clone());
-    }
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// the given span.
-///
-/// This is a similar to the
-/// [`quote_spanned!`](https://docs.rs/quote/latest/quote/macro.quote_spanned.html) macro from the
-/// `quote` crate but provides only just enough functionality needed by the current `macros` crate.
-macro_rules! quote_spanned {
-    ($span:expr => $($tt:tt)*) => {{
-        let mut tokens = ::proc_macro::TokenStream::new();
-        {
-            #[allow(unused_variables)]
-            let span = $span;
-            quote_spanned!(@proc tokens span $($tt)*);
-        }
-        tokens
-    }};
-    (@proc $v:ident $span:ident) => {};
-    (@proc $v:ident $span:ident #$id:ident $($tt:tt)*) => {
-        $crate::quote::ToTokens::to_tokens(&$id, &mut $v);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident #(#$id:ident)* $($tt:tt)*) => {
-        for token in $id {
-            $crate::quote::ToTokens::to_tokens(&token, &mut $v);
-        }
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident ( $($inner:tt)* ) $($tt:tt)*) => {
-        #[allow(unused_mut)]
-        let mut tokens = ::proc_macro::TokenStream::new();
-        quote_spanned!(@proc tokens $span $($inner)*);
-        $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
-            ::proc_macro::Delimiter::Parenthesis,
-            tokens,
-        ))]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident [ $($inner:tt)* ] $($tt:tt)*) => {
-        let mut tokens = ::proc_macro::TokenStream::new();
-        quote_spanned!(@proc tokens $span $($inner)*);
-        $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
-            ::proc_macro::Delimiter::Bracket,
-            tokens,
-        ))]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident { $($inner:tt)* } $($tt:tt)*) => {
-        let mut tokens = ::proc_macro::TokenStream::new();
-        quote_spanned!(@proc tokens $span $($inner)*);
-        $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
-            ::proc_macro::Delimiter::Brace,
-            tokens,
-        ))]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident :: $($tt:tt)*) => {
-        $v.extend([::proc_macro::Spacing::Joint, ::proc_macro::Spacing::Alone].map(|spacing| {
-            ::proc_macro::TokenTree::Punct(::proc_macro::Punct::new(':', spacing))
-        }));
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident : $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new(':', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident , $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new(',', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident @ $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('@', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident ! $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('!', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident ; $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new(';', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident + $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('+', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident = $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident # $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('#', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident & $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Punct(
-            ::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident _ $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Ident(
-            ::proc_macro::Ident::new("_", $span),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-    (@proc $v:ident $span:ident $id:ident $($tt:tt)*) => {
-        $v.extend([::proc_macro::TokenTree::Ident(
-            ::proc_macro::Ident::new(stringify!($id), $span),
-        )]);
-        quote_spanned!(@proc $v $span $($tt)*);
-    };
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// mixed site span ([`Span::mixed_site()`]).
-///
-/// This is a similar to the [`quote!`](https://docs.rs/quote/latest/quote/macro.quote.html) macro
-/// from the `quote` crate but provides only just enough functionality needed by the current
-/// `macros` crate.
-///
-/// [`Span::mixed_site()`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.mixed_site
-macro_rules! quote {
-    ($($tt:tt)*) => {
-        quote_spanned!(::proc_macro::Span::mixed_site() => $($tt)*)
-    }
-}
diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
index ee06044fcd4f3..a67d1cc81a2d3 100644
--- a/rust/macros/vtable.rs
+++ b/rust/macros/vtable.rs
@@ -1,9 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
 use std::collections::HashSet;
 use std::fmt::Write;
 
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
 pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
     let mut tokens: Vec<_> = ts.into_iter().collect();
 
@@ -31,7 +32,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
     let mut consts = HashSet::new();
     while let Some(token) = body_it.next() {
         match token {
-            TokenTree::Ident(ident) if ident.to_string() == "fn" => {
+            TokenTree::Ident(ident) if ident == "fn" => {
                 let fn_name = match body_it.next() {
                     Some(TokenTree::Ident(ident)) => ident.to_string(),
                     // Possibly we've encountered a fn pointer type instead.
@@ -39,7 +40,7 @@ pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
                 };
                 functions.push(fn_name);
             }
-            TokenTree::Ident(ident) if ident.to_string() == "const" => {
+            TokenTree::Ident(ident) if ident == "const" => {
                 let const_name = match body_it.next() {
                     Some(TokenTree::Ident(ident)) => ident.to_string(),
                     // Possibly we've encountered an inline const block instead.
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro
       [not found] <20260107161729.3855851-1-gary@kernel.org>
  2026-01-07 16:15 ` [PATCH v2 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2026-01-07 16:15 ` Gary Guo
  2026-01-07 17:11   ` Tamir Duberstein
  2026-01-07 16:15 ` [PATCH v2 05/11] rust: macros: use `quote!` for " Gary Guo
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 8+ messages in thread
From: Gary Guo @ 2026-01-07 16:15 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Aaron Tomlin, Tamir Duberstein,
	José Expósito
  Cc: rust-for-linux, Patrick Miller, linux-kernel, linux-modules

From: Gary Guo <gary@garyguo.net>

With `syn` being available in the kernel, use it to parse the complex
custom `module!` macro to replace existing helpers. Only parsing is
changed in this commit, the code generation is untouched.

This has the benefit of better error message when the macro is used
incorrectly, as it can point to a concrete span on what's going wrong.

For example, if a field is specified twice, previously it reads:

    error: proc macro panicked
      --> samples/rust/rust_minimal.rs:7:1
       |
    7  | / module! {
    8  | |     type: RustMinimal,
    9  | |     name: "rust_minimal",
    10 | |     author: "Rust for Linux Contributors",
    11 | |     description: "Rust minimal sample",
    12 | |     license: "GPL",
    13 | |     license: "GPL",
    14 | | }
       | |_^
       |
       = help: message: Duplicated key "license". Keys can only be specified once.

now it reads:

    error: duplicated key "license". Keys can only be specified once.
      --> samples/rust/rust_minimal.rs:13:5
       |
    13 |     license: "GPL",
       |     ^^^^^^^

Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/macros/helpers.rs | 109 ++++--------
 rust/macros/lib.rs     |   6 +-
 rust/macros/module.rs  | 389 +++++++++++++++++++++++++----------------
 3 files changed, 277 insertions(+), 227 deletions(-)

diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 13fafaba12261..fa66ef6eb0f3d 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,53 +1,21 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
-
-pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
-    if let Some(TokenTree::Ident(ident)) = it.next() {
-        Some(ident.to_string())
-    } else {
-        None
-    }
-}
-
-pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option<char> {
-    let peek = it.clone().next();
-    match peek {
-        Some(TokenTree::Punct(punct)) if punct.as_char() == '-' => {
-            let _ = it.next();
-            Some(punct.as_char())
-        }
-        _ => None,
-    }
-}
-
-pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
-    if let Some(TokenTree::Literal(literal)) = it.next() {
-        Some(literal.to_string())
-    } else {
-        None
-    }
-}
-
-pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
-    try_literal(it).and_then(|string| {
-        if string.starts_with('\"') && string.ends_with('\"') {
-            let content = &string[1..string.len() - 1];
-            if content.contains('\\') {
-                panic!("Escape sequences in string literals not yet handled");
-            }
-            Some(content.to_string())
-        } else if string.starts_with("r\"") {
-            panic!("Raw string literals are not yet handled");
-        } else {
-            None
-        }
-    })
-}
-
-pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
-    try_ident(it).expect("Expected Ident")
-}
+use proc_macro2::{
+    token_stream,
+    Ident,
+    TokenStream,
+    TokenTree, //
+};
+use quote::ToTokens;
+use syn::{
+    parse::{
+        Parse,
+        ParseStream, //
+    },
+    Error,
+    LitStr,
+    Result, //
+};
 
 pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
     if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
@@ -57,27 +25,28 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
     }
 }
 
-pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
-    try_string(it).expect("Expected string")
-}
+/// A string literal that is required to have ASCII value only.
+pub(crate) struct AsciiLitStr(LitStr);
 
-pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
-    let string = try_string(it).expect("Expected string");
-    assert!(string.is_ascii(), "Expected ASCII string");
-    string
+impl Parse for AsciiLitStr {
+    fn parse(input: ParseStream<'_>) -> Result<Self> {
+        let s: LitStr = input.parse()?;
+        if !s.value().is_ascii() {
+            return Err(Error::new_spanned(s, "expected ASCII-only string literal"));
+        }
+        Ok(Self(s))
+    }
 }
 
-pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
-    if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
-        group
-    } else {
-        panic!("Expected Group");
+impl ToTokens for AsciiLitStr {
+    fn to_tokens(&self, ts: &mut TokenStream) {
+        self.0.to_tokens(ts);
     }
 }
 
-pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
-    if it.next().is_some() {
-        panic!("Expected end");
+impl AsciiLitStr {
+    pub(crate) fn value(&self) -> String {
+        self.0.value()
     }
 }
 
@@ -114,17 +83,3 @@ 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, expected_name: &str) -> String {
-    assert_eq!(expect_ident(it), expected_name);
-    assert_eq!(expect_punct(it), ':');
-    let string = expect_string(it);
-    assert_eq!(expect_punct(it), ',');
-    string
-}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 9955c04dbaae3..c5347127a3a51 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -131,8 +131,10 @@
 ///   - `firmware`: array of ASCII string literals of the firmware files of
 ///     the kernel module.
 #[proc_macro]
-pub fn module(ts: TokenStream) -> TokenStream {
-    module::module(ts.into()).into()
+pub fn module(input: TokenStream) -> TokenStream {
+    module::module(parse_macro_input!(input))
+        .unwrap_or_else(|e| e.into_compile_error())
+        .into()
 }
 
 /// Declares or implements a vtable trait.
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index b855a2b586e18..6ad7b411ccde4 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -2,28 +2,30 @@
 
 use std::fmt::Write;
 
-use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+use proc_macro2::{
+    Literal,
+    TokenStream, //
+};
+use quote::ToTokens;
+use syn::{
+    braced,
+    bracketed,
+    ext::IdentExt,
+    parse::{
+        Parse,
+        ParseStream, //
+    },
+    punctuated::Punctuated,
+    Error,
+    Expr,
+    Ident,
+    LitStr,
+    Result,
+    Token, //
+};
 
 use crate::helpers::*;
 
-fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
-    let group = expect_group(it);
-    assert_eq!(group.delimiter(), Delimiter::Bracket);
-    let mut values = Vec::new();
-    let mut it = group.stream().into_iter();
-
-    while let Some(val) = try_string(&mut it) {
-        assert!(val.is_ascii(), "Expected ASCII string");
-        values.push(val);
-        match it.next() {
-            Some(TokenTree::Punct(punct)) => assert_eq!(punct.as_char(), ','),
-            None => break,
-            _ => panic!("Expected ',' or end of array"),
-        }
-    }
-    values
-}
-
 struct ModInfoBuilder<'a> {
     module: &'a str,
     counter: usize,
@@ -113,12 +115,16 @@ fn emit_params(&mut self, info: &ModuleInfo) {
         };
 
         for param in params {
-            let ops = param_ops_path(&param.ptype);
+            let param_name = param.name.to_string();
+            let param_type = param.ptype.to_string();
+            let param_default = param.default.to_token_stream().to_string();
+
+            let ops = param_ops_path(&param_type);
 
             // Note: The spelling of these fields is dictated by the user space
             // tool `modinfo`.
-            self.emit_param("parmtype", &param.name, &param.ptype);
-            self.emit_param("parm", &param.name, &param.description);
+            self.emit_param("parmtype", &param_name, &param_type);
+            self.emit_param("parm", &param_name, &param.description.value());
 
             write!(
                 self.param_buffer,
@@ -160,10 +166,7 @@ fn emit_params(&mut self, info: &ModuleInfo) {
                         );
                 }};
                 ",
-                module_name = info.name,
-                param_type = param.ptype,
-                param_default = param.default,
-                param_name = param.name,
+                module_name = info.name.value(),
                 ops = ops,
             )
             .unwrap();
@@ -187,127 +190,82 @@ fn param_ops_path(param_type: &str) -> &'static str {
     }
 }
 
-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 = try_sign(param_it);
-    let default = try_literal(param_it).expect("Expected default param value");
-    assert_eq!(expect_punct(param_it), ',');
-    let mut value = sign.map(String::from).unwrap_or_default();
-    value.push_str(&default);
-    value
-}
-
-#[derive(Debug, Default)]
-struct ModuleInfo {
-    type_: String,
-    license: String,
-    name: String,
-    authors: Option<Vec<String>>,
-    description: Option<String>,
-    alias: Option<Vec<String>>,
-    firmware: Option<Vec<String>>,
-    imports_ns: Option<Vec<String>>,
-    params: Option<Vec<Parameter>>,
-}
-
-#[derive(Debug)]
-struct Parameter {
-    name: String,
-    ptype: String,
-    default: String,
-    description: String,
-}
-
-fn expect_params(it: &mut token_stream::IntoIter) -> Vec<Parameter> {
-    let params = expect_group(it);
-    assert_eq!(params.delimiter(), Delimiter::Brace);
-    let mut it = params.stream().into_iter();
-    let mut parsed = Vec::new();
-
-    loop {
-        let param_name = match it.next() {
-            Some(TokenTree::Ident(ident)) => ident.to_string(),
-            Some(_) => panic!("Expected Ident or end"),
-            None => break,
-        };
-
-        assert_eq!(expect_punct(&mut it), ':');
-        let param_type = expect_ident(&mut it);
-        let group = expect_group(&mut it);
-        assert_eq!(group.delimiter(), Delimiter::Brace);
-        assert_eq!(expect_punct(&mut it), ',');
-
-        let mut param_it = group.stream().into_iter();
-        let param_default = expect_param_default(&mut param_it);
-        let param_description = expect_string_field(&mut param_it, "description");
-        expect_end(&mut param_it);
-
-        parsed.push(Parameter {
-            name: param_name,
-            ptype: param_type,
-            default: param_default,
-            description: param_description,
-        })
-    }
-
-    parsed
-}
-
-impl ModuleInfo {
-    fn parse(it: &mut token_stream::IntoIter) -> Self {
-        let mut info = ModuleInfo::default();
-
-        const EXPECTED_KEYS: &[&str] = &[
-            "type",
-            "name",
-            "authors",
-            "description",
-            "license",
-            "alias",
-            "firmware",
-            "imports_ns",
-            "params",
-        ];
-        const REQUIRED_KEYS: &[&str] = &["type", "name", "license"];
+/// Parse fields that are required to use a specific order.
+///
+/// As fields must follow a specific order, we *could* just parse fields one by one by peeking.
+/// However the error message generated when implementing that way is not very friendly.
+///
+/// So instead we parse fields in an arbitrary order, but only enforce the ordering after parsing,
+/// and if the wrong order is used, the proper order is communicated to the user with error message.
+///
+/// Usage looks like this:
+/// ```ignore
+/// parse_ordered_fields! {
+///     from input;
+///
+///     // This will extract "foo: <field>" into a variable named "foo".
+///     // The variable will have type `Option<_>`.
+///     foo => <expression that parses the field>,
+///
+///     // If you need the variable name to be different than the key name.
+///     // This extracts "baz: <field>" into a variable named "bar".
+///     // You might want this if "baz" is a keyword.
+///     baz as bar => <expression that parse the field>,
+///
+///     // You can mark a key as required, and the variable will no longer be `Option`.
+///     // foobar will be of type `Expr` instead of `Option<Expr>`.
+///     foobar [required] => input.parse::<Expr>()?,
+/// }
+/// ```
+macro_rules! parse_ordered_fields {
+    (@gen
+        [$input:expr]
+        [$([$name:ident; $key:ident; $parser:expr])*]
+        [$([$req_name:ident; $req_key:ident])*]
+    ) => {
+        $(let mut $name = None;)*
+
+        const EXPECTED_KEYS: &[&str] = &[$(stringify!($key),)*];
+        const REQUIRED_KEYS: &[&str] = &[$(stringify!($req_key),)*];
+
+        let span = $input.span();
         let mut seen_keys = Vec::new();
 
         loop {
-            let key = match it.next() {
-                Some(TokenTree::Ident(ident)) => ident.to_string(),
-                Some(_) => panic!("Expected Ident or end"),
-                None => break,
-            };
+            if $input.is_empty() {
+                break;
+            }
+
+            let key = $input.call(Ident::parse_any)?;
 
             if seen_keys.contains(&key) {
-                panic!("Duplicated key \"{key}\". Keys can only be specified once.");
+                Err(Error::new_spanned(
+                    &key,
+                    format!(r#"duplicated key "{key}". Keys can only be specified once."#),
+                ))?
             }
 
-            assert_eq!(expect_punct(it), ':');
-
-            match key.as_str() {
-                "type" => info.type_ = expect_ident(it),
-                "name" => info.name = expect_string_ascii(it),
-                "authors" => info.authors = Some(expect_string_array(it)),
-                "description" => info.description = Some(expect_string(it)),
-                "license" => info.license = expect_string_ascii(it),
-                "alias" => info.alias = Some(expect_string_array(it)),
-                "firmware" => info.firmware = Some(expect_string_array(it)),
-                "imports_ns" => info.imports_ns = Some(expect_string_array(it)),
-                "params" => info.params = Some(expect_params(it)),
-                _ => panic!("Unknown key \"{key}\". Valid keys are: {EXPECTED_KEYS:?}."),
+            $input.parse::<Token![:]>()?;
+
+            match &*key.to_string() {
+                $(
+                    stringify!($key) => $name = Some($parser),
+                )*
+                _ => {
+                    Err(Error::new_spanned(
+                        &key,
+                        format!(r#"unknown key "{key}". Valid keys are: {EXPECTED_KEYS:?}."#),
+                    ))?
+                }
             }
 
-            assert_eq!(expect_punct(it), ',');
-
+            $input.parse::<Token![,]>()?;
             seen_keys.push(key);
         }
 
-        expect_end(it);
-
         for key in REQUIRED_KEYS {
             if !seen_keys.iter().any(|e| e == key) {
-                panic!("Missing required key \"{key}\".");
+                Err(Error::new(span, format!(r#"missing required key "{key}""#)))?
             }
         }
 
@@ -319,43 +277,178 @@ fn parse(it: &mut token_stream::IntoIter) -> Self {
         }
 
         if seen_keys != ordered_keys {
-            panic!("Keys are not ordered as expected. Order them like: {ordered_keys:?}.");
+            Err(Error::new(
+                span,
+                format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
+            ))?
+        }
+
+        $(let $req_name = $req_name.expect("required field");)*
+    };
+
+    // Handle required fields.
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $key:ident as $name:ident [required] => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)* [$name; $key]] $($rest)*
+        )
+    };
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $name:ident [required] => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)* [$name; $name]] $($rest)*
+        )
+    };
+
+    // Handle optional fields.
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $key:ident as $name:ident => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)*] $($rest)*
+        )
+    };
+    (@gen
+        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
+        $name:ident => $parser:expr,
+        $($rest:tt)*
+    ) => {
+        parse_ordered_fields!(
+            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)*] $($rest)*
+        )
+    };
+
+    (from $input:expr; $($tok:tt)*) => {
+        parse_ordered_fields!(@gen [$input] [] [] $($tok)*)
+    }
+}
+
+struct Parameter {
+    name: Ident,
+    ptype: Ident,
+    default: Expr,
+    description: LitStr,
+}
+
+impl Parse for Parameter {
+    fn parse(input: ParseStream<'_>) -> Result<Self> {
+        let name = input.parse()?;
+        input.parse::<Token![:]>()?;
+        let ptype = input.parse()?;
+
+        let fields;
+        braced!(fields in input);
+
+        parse_ordered_fields! {
+            from fields;
+            default [required] => fields.parse()?,
+            description [required] => fields.parse()?,
         }
 
-        info
+        Ok(Self {
+            name,
+            ptype,
+            default,
+            description,
+        })
     }
 }
 
-pub(crate) fn module(ts: TokenStream) -> TokenStream {
-    let mut it = ts.into_iter();
+pub(crate) struct ModuleInfo {
+    type_: Ident,
+    license: AsciiLitStr,
+    name: AsciiLitStr,
+    authors: Option<Punctuated<AsciiLitStr, Token![,]>>,
+    description: Option<LitStr>,
+    alias: Option<Punctuated<AsciiLitStr, Token![,]>>,
+    firmware: Option<Punctuated<AsciiLitStr, Token![,]>>,
+    imports_ns: Option<Punctuated<AsciiLitStr, Token![,]>>,
+    params: Option<Punctuated<Parameter, Token![,]>>,
+}
 
-    let info = ModuleInfo::parse(&mut it);
+impl Parse for ModuleInfo {
+    fn parse(input: ParseStream<'_>) -> Result<Self> {
+        parse_ordered_fields!(
+            from input;
+            type as type_ [required] => input.parse()?,
+            name [required] => input.parse()?,
+            authors => {
+                let list;
+                bracketed!(list in input);
+                Punctuated::parse_terminated(&list)?
+            },
+            description => input.parse()?,
+            license [required] => input.parse()?,
+            alias => {
+                let list;
+                bracketed!(list in input);
+                Punctuated::parse_terminated(&list)?
+            },
+            firmware => {
+                let list;
+                bracketed!(list in input);
+                Punctuated::parse_terminated(&list)?
+            },
+            imports_ns => {
+                let list;
+                bracketed!(list in input);
+                Punctuated::parse_terminated(&list)?
+            },
+            params => {
+                let list;
+                braced!(list in input);
+                Punctuated::parse_terminated(&list)?
+            },
+        );
+
+        Ok(ModuleInfo {
+            type_,
+            license,
+            name,
+            authors,
+            description,
+            alias,
+            firmware,
+            imports_ns,
+            params,
+        })
+    }
+}
 
+pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
     // Rust does not allow hyphens in identifiers, use underscore instead.
-    let ident = info.name.replace('-', "_");
+    let ident = info.name.value().replace('-', "_");
     let mut modinfo = ModInfoBuilder::new(ident.as_ref());
     if let Some(authors) = &info.authors {
         for author in authors {
-            modinfo.emit("author", author);
+            modinfo.emit("author", &author.value());
         }
     }
     if let Some(description) = &info.description {
-        modinfo.emit("description", description);
+        modinfo.emit("description", &description.value());
     }
-    modinfo.emit("license", &info.license);
+    modinfo.emit("license", &info.license.value());
     if let Some(aliases) = &info.alias {
         for alias in aliases {
-            modinfo.emit("alias", alias);
+            modinfo.emit("alias", &alias.value());
         }
     }
     if let Some(firmware) = &info.firmware {
         for fw in firmware {
-            modinfo.emit("firmware", fw);
+            modinfo.emit("firmware", &fw.value());
         }
     }
     if let Some(imports) = &info.imports_ns {
         for ns in imports {
-            modinfo.emit("import_ns", ns);
+            modinfo.emit("import_ns", &ns.value());
         }
     }
 
@@ -366,7 +459,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
 
     modinfo.emit_params(&info);
 
-    format!(
+    Ok(format!(
         "
             /// The module name.
             ///
@@ -536,12 +629,12 @@ mod module_parameters {{
             }}
         ",
         type_ = info.type_,
-        name = info.name,
+        name = info.name.value(),
         ident = ident,
         modinfo = modinfo.buffer,
         params = modinfo.param_buffer,
         initcall_section = ".initcall6.init"
     )
     .parse()
-    .expect("Error parsing formatted string into token stream.")
+    .expect("Error parsing formatted string into token stream."))
 }
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 05/11] rust: macros: use `quote!` for `module!` macro
       [not found] <20260107161729.3855851-1-gary@kernel.org>
  2026-01-07 16:15 ` [PATCH v2 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
  2026-01-07 16:15 ` [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
@ 2026-01-07 16:15 ` Gary Guo
  2026-01-07 17:19   ` Tamir Duberstein
  2026-01-07 16:15 ` [PATCH v2 09/11] rust: macros: allow arbitrary types to be used in " Gary Guo
  2026-01-07 16:15 ` [PATCH v2 10/11] rust: macros: rearrange `#[doc(hidden)]` " Gary Guo
  4 siblings, 1 reply; 8+ messages in thread
From: Gary Guo @ 2026-01-07 16:15 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Aaron Tomlin
  Cc: rust-for-linux, linux-modules, linux-kernel

From: Gary Guo <gary@garyguo.net>

This has no behavioural change, but is good for maintainability. With
`quote!`, we're no longer using string templates, so we don't need to
quote " and {} inside the template anymore. Further more, editors can
now highlight the code template.

This also improves the robustness as it eliminates the need for string
quoting and escaping.

Co-developed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/macros/module.rs | 558 ++++++++++++++++++++++--------------------
 1 file changed, 295 insertions(+), 263 deletions(-)

diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 6ad7b411ccde4..ba345d672839e 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,12 +1,15 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use std::fmt::Write;
+use std::ffi::CString;
 
 use proc_macro2::{
     Literal,
     TokenStream, //
 };
-use quote::ToTokens;
+use quote::{
+    format_ident,
+    quote, //
+};
 use syn::{
     braced,
     bracketed,
@@ -15,11 +18,13 @@
         Parse,
         ParseStream, //
     },
+    parse_quote,
     punctuated::Punctuated,
     Error,
     Expr,
     Ident,
     LitStr,
+    Path,
     Result,
     Token, //
 };
@@ -29,8 +34,8 @@
 struct ModInfoBuilder<'a> {
     module: &'a str,
     counter: usize,
-    buffer: String,
-    param_buffer: String,
+    ts: TokenStream,
+    param_ts: TokenStream,
 }
 
 impl<'a> ModInfoBuilder<'a> {
@@ -38,8 +43,8 @@ fn new(module: &'a str) -> Self {
         ModInfoBuilder {
             module,
             counter: 0,
-            buffer: String::new(),
-            param_buffer: String::new(),
+            ts: TokenStream::new(),
+            param_ts: TokenStream::new(),
         }
     }
 
@@ -56,33 +61,32 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool)
             // Loadable modules' modinfo strings go as-is.
             format!("{field}={content}\0")
         };
-
-        let buffer = if param {
-            &mut self.param_buffer
+        let length = string.len();
+        let string = Literal::byte_string(string.as_bytes());
+        let cfg = if builtin {
+            quote!(#[cfg(not(MODULE))])
         } else {
-            &mut self.buffer
+            quote!(#[cfg(MODULE)])
         };
 
-        write!(
-            buffer,
-            "
-                {cfg}
-                #[doc(hidden)]
-                #[cfg_attr(not(target_os = \"macos\"), link_section = \".modinfo\")]
-                #[used(compiler)]
-                pub static __{module}_{counter}: [u8; {length}] = *{string};
-            ",
-            cfg = if builtin {
-                "#[cfg(not(MODULE))]"
-            } else {
-                "#[cfg(MODULE)]"
-            },
+        let counter = format_ident!(
+            "__{module}_{counter}",
             module = self.module.to_uppercase(),
-            counter = self.counter,
-            length = string.len(),
-            string = Literal::byte_string(string.as_bytes()),
-        )
-        .unwrap();
+            counter = self.counter
+        );
+        let item = quote! {
+            #cfg
+            #[doc(hidden)]
+            #[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
+            #[used(compiler)]
+            pub static #counter: [u8; #length] = *#string;
+        };
+
+        if param {
+            self.param_ts.extend(item);
+        } else {
+            self.ts.extend(item);
+        }
 
         self.counter += 1;
     }
@@ -115,77 +119,86 @@ fn emit_params(&mut self, info: &ModuleInfo) {
         };
 
         for param in params {
-            let param_name = param.name.to_string();
-            let param_type = param.ptype.to_string();
-            let param_default = param.default.to_token_stream().to_string();
+            let param_name_str = param.name.to_string();
+            let param_type_str = param.ptype.to_string();
 
-            let ops = param_ops_path(&param_type);
+            let ops = param_ops_path(&param_type_str);
 
             // Note: The spelling of these fields is dictated by the user space
             // tool `modinfo`.
-            self.emit_param("parmtype", &param_name, &param_type);
-            self.emit_param("parm", &param_name, &param.description.value());
-
-            write!(
-                self.param_buffer,
-                "
-                pub(crate) static {param_name}:
-                    ::kernel::module_param::ModuleParamAccess<{param_type}> =
-                        ::kernel::module_param::ModuleParamAccess::new({param_default});
-
-                const _: () = {{
-                    #[link_section = \"__param\"]
-                    #[used]
-                    static __{module_name}_{param_name}_struct:
+            self.emit_param("parmtype", &param_name_str, &param_type_str);
+            self.emit_param("parm", &param_name_str, &param.description.value());
+
+            let static_name = format_ident!("__{}_{}_struct", self.module, param.name);
+            let param_name_cstr = Literal::c_string(
+                &CString::new(param_name_str).expect("name contains NUL-terminator"),
+            );
+            let param_name_cstr_with_module = Literal::c_string(
+                &CString::new(format!("{}.{}", self.module, param.name))
+                    .expect("name contains NUL-terminator"),
+            );
+
+            let param_name = &param.name;
+            let param_type = &param.ptype;
+            let param_default = &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);
+
+                const _: () = {
+                    #[allow(non_upper_case_globals)]
+                    #[link_section = "__param"]
+                    #[used(compiler)]
+                    static #static_name:
                         ::kernel::module_param::KernelParam =
                         ::kernel::module_param::KernelParam::new(
-                            ::kernel::bindings::kernel_param {{
-                                name: if ::core::cfg!(MODULE) {{
-                                    ::kernel::c_str!(\"{param_name}\").to_bytes_with_nul()
-                                }} else {{
-                                    ::kernel::c_str!(\"{module_name}.{param_name}\")
-                                        .to_bytes_with_nul()
-                                }}.as_ptr(),
+                            ::kernel::bindings::kernel_param {
+                                name: kernel::str::as_char_ptr_in_const_context(
+                                    if ::core::cfg!(MODULE) {
+                                        #param_name_cstr
+                                    } else {
+                                        #param_name_cstr_with_module
+                                    }
+                                ),
                                 // SAFETY: `__this_module` is constructed by the kernel at load
                                 // time and will not be freed until the module is unloaded.
                                 #[cfg(MODULE)]
-                                mod_: unsafe {{
+                                mod_: unsafe {
                                     core::ptr::from_ref(&::kernel::bindings::__this_module)
                                         .cast_mut()
-                                }},
+                                },
                                 #[cfg(not(MODULE))]
                                 mod_: ::core::ptr::null_mut(),
-                                ops: core::ptr::from_ref(&{ops}),
+                                ops: core::ptr::from_ref(&#ops),
                                 perm: 0, // Will not appear in sysfs
                                 level: -1,
                                 flags: 0,
-                                __bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {{
-                                    arg: {param_name}.as_void_ptr()
-                                }},
-                            }}
+                                __bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {
+                                    arg: #param_name.as_void_ptr()
+                                },
+                            }
                         );
-                }};
-                ",
-                module_name = info.name.value(),
-                ops = ops,
-            )
-            .unwrap();
+                };
+            });
         }
     }
 }
 
-fn param_ops_path(param_type: &str) -> &'static str {
+fn param_ops_path(param_type: &str) -> Path {
     match param_type {
-        "i8" => "::kernel::module_param::PARAM_OPS_I8",
-        "u8" => "::kernel::module_param::PARAM_OPS_U8",
-        "i16" => "::kernel::module_param::PARAM_OPS_I16",
-        "u16" => "::kernel::module_param::PARAM_OPS_U16",
-        "i32" => "::kernel::module_param::PARAM_OPS_I32",
-        "u32" => "::kernel::module_param::PARAM_OPS_U32",
-        "i64" => "::kernel::module_param::PARAM_OPS_I64",
-        "u64" => "::kernel::module_param::PARAM_OPS_U64",
-        "isize" => "::kernel::module_param::PARAM_OPS_ISIZE",
-        "usize" => "::kernel::module_param::PARAM_OPS_USIZE",
+        "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),
+        "u16" => parse_quote!(::kernel::module_param::PARAM_OPS_U16),
+        "i32" => parse_quote!(::kernel::module_param::PARAM_OPS_I32),
+        "u32" => parse_quote!(::kernel::module_param::PARAM_OPS_U32),
+        "i64" => parse_quote!(::kernel::module_param::PARAM_OPS_I64),
+        "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),
         t => panic!("Unsupported parameter type {}", t),
     }
 }
@@ -424,29 +437,41 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
 }
 
 pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
+    let ModuleInfo {
+        type_,
+        license,
+        name,
+        authors,
+        description,
+        alias,
+        firmware,
+        imports_ns,
+        params: _,
+    } = &info;
+
     // Rust does not allow hyphens in identifiers, use underscore instead.
-    let ident = info.name.value().replace('-', "_");
+    let ident = name.value().replace('-', "_");
     let mut modinfo = ModInfoBuilder::new(ident.as_ref());
-    if let Some(authors) = &info.authors {
+    if let Some(authors) = authors {
         for author in authors {
             modinfo.emit("author", &author.value());
         }
     }
-    if let Some(description) = &info.description {
+    if let Some(description) = description {
         modinfo.emit("description", &description.value());
     }
-    modinfo.emit("license", &info.license.value());
-    if let Some(aliases) = &info.alias {
+    modinfo.emit("license", &license.value());
+    if let Some(aliases) = alias {
         for alias in aliases {
             modinfo.emit("alias", &alias.value());
         }
     }
-    if let Some(firmware) = &info.firmware {
+    if let Some(firmware) = firmware {
         for fw in firmware {
             modinfo.emit("firmware", &fw.value());
         }
     }
-    if let Some(imports) = &info.imports_ns {
+    if let Some(imports) = imports_ns {
         for ns in imports {
             modinfo.emit("import_ns", &ns.value());
         }
@@ -459,182 +484,189 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
 
     modinfo.emit_params(&info);
 
-    Ok(format!(
-        "
-            /// The module name.
-            ///
-            /// Used by the printing macros, e.g. [`info!`].
-            const __LOG_PREFIX: &[u8] = b\"{name}\\0\";
-
-            // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
-            // freed until the module is unloaded.
-            #[cfg(MODULE)]
-            static THIS_MODULE: ::kernel::ThisModule = unsafe {{
-                extern \"C\" {{
-                    static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
-                }}
-
-                ::kernel::ThisModule::from_ptr(__this_module.get())
-            }};
-            #[cfg(not(MODULE))]
-            static THIS_MODULE: ::kernel::ThisModule = unsafe {{
-                ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
-            }};
-
-            /// The `LocalModule` type is the type of the module created by `module!`,
-            /// `module_pci_driver!`, `module_platform_driver!`, etc.
-            type LocalModule = {type_};
-
-            impl ::kernel::ModuleMetadata for {type_} {{
-                const NAME: &'static ::kernel::str::CStr = c\"{name}\";
-            }}
-
-            // Double nested modules, since then nobody can access the public items inside.
-            mod __module_init {{
-                mod __module_init {{
-                    use super::super::{type_};
-                    use pin_init::PinInit;
-
-                    /// The \"Rust loadable module\" mark.
-                    //
-                    // This may be best done another way later on, e.g. as a new modinfo
-                    // key or a new section. For the moment, keep it simple.
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[used(compiler)]
-                    static __IS_RUST_MODULE: () = ();
-
-                    static mut __MOD: ::core::mem::MaybeUninit<{type_}> =
-                        ::core::mem::MaybeUninit::uninit();
-
-                    // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
-                    /// # Safety
-                    ///
-                    /// This function must not be called after module initialization, because it may be
-                    /// freed after that completes.
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    #[link_section = \".init.text\"]
-                    pub unsafe extern \"C\" fn init_module() -> ::kernel::ffi::c_int {{
-                        // SAFETY: This function is inaccessible to the outside due to the double
-                        // module wrapping it. It is called exactly once by the C side via its
-                        // unique name.
-                        unsafe {{ __init() }}
-                    }}
-
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[used(compiler)]
-                    #[link_section = \".init.data\"]
-                    static __UNIQUE_ID___addressable_init_module: unsafe extern \"C\" fn() -> i32 = init_module;
-
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    #[link_section = \".exit.text\"]
-                    pub extern \"C\" fn cleanup_module() {{
-                        // SAFETY:
-                        // - This function is inaccessible to the outside due to the double
-                        //   module wrapping it. It is called exactly once by the C side via its
-                        //   unique name,
-                        // - furthermore it is only called after `init_module` has returned `0`
-                        //   (which delegates to `__init`).
-                        unsafe {{ __exit() }}
-                    }}
-
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[used(compiler)]
-                    #[link_section = \".exit.data\"]
-                    static __UNIQUE_ID___addressable_cleanup_module: extern \"C\" fn() = cleanup_module;
-
-                    // Built-in modules are initialized through an initcall pointer
-                    // and the identifiers need to be unique.
-                    #[cfg(not(MODULE))]
-                    #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
-                    #[doc(hidden)]
-                    #[link_section = \"{initcall_section}\"]
-                    #[used(compiler)]
-                    pub static __{ident}_initcall: extern \"C\" fn() ->
-                        ::kernel::ffi::c_int = __{ident}_init;
-
-                    #[cfg(not(MODULE))]
-                    #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
-                    ::core::arch::global_asm!(
-                        r#\".section \"{initcall_section}\", \"a\"
-                        __{ident}_initcall:
-                            .long   __{ident}_init - .
-                            .previous
-                        \"#
-                    );
-
-                    #[cfg(not(MODULE))]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    pub extern \"C\" fn __{ident}_init() -> ::kernel::ffi::c_int {{
-                        // SAFETY: This function is inaccessible to the outside due to the double
-                        // module wrapping it. It is called exactly once by the C side via its
-                        // placement above in the initcall section.
-                        unsafe {{ __init() }}
-                    }}
-
-                    #[cfg(not(MODULE))]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    pub extern \"C\" fn __{ident}_exit() {{
-                        // SAFETY:
-                        // - This function is inaccessible to the outside due to the double
-                        //   module wrapping it. It is called exactly once by the C side via its
-                        //   unique name,
-                        // - furthermore it is only called after `__{ident}_init` has
-                        //   returned `0` (which delegates to `__init`).
-                        unsafe {{ __exit() }}
-                    }}
-
-                    /// # Safety
-                    ///
-                    /// This function must only be called once.
-                    unsafe fn __init() -> ::kernel::ffi::c_int {{
-                        let initer =
-                            <{type_} as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
-                        // SAFETY: No data race, since `__MOD` can only be accessed by this module
-                        // and there only `__init` and `__exit` access it. These functions are only
-                        // called once and `__exit` cannot be called before or during `__init`.
-                        match unsafe {{ initer.__pinned_init(__MOD.as_mut_ptr()) }} {{
-                            Ok(m) => 0,
-                            Err(e) => e.to_errno(),
-                        }}
-                    }}
-
-                    /// # Safety
-                    ///
-                    /// This function must
-                    /// - only be called once,
-                    /// - be called after `__init` has been called and returned `0`.
-                    unsafe fn __exit() {{
-                        // SAFETY: No data race, since `__MOD` can only be accessed by this module
-                        // and there only `__init` and `__exit` access it. These functions are only
-                        // called once and `__init` was already called.
-                        unsafe {{
-                            // Invokes `drop()` on `__MOD`, which should be used for cleanup.
-                            __MOD.assume_init_drop();
-                        }}
-                    }}
-                    {modinfo}
-                }}
-            }}
-            mod module_parameters {{
-                {params}
-            }}
-        ",
-        type_ = info.type_,
-        name = info.name.value(),
-        ident = ident,
-        modinfo = modinfo.buffer,
-        params = modinfo.param_buffer,
-        initcall_section = ".initcall6.init"
-    )
-    .parse()
-    .expect("Error parsing formatted string into token stream."))
+    let modinfo_ts = modinfo.ts;
+    let params_ts = modinfo.param_ts;
+
+    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 global_asm = format!(
+        r#".section "{initcall_section}", "a"
+        __{ident}_initcall:
+            .long   __{ident}_init - .
+            .previous
+        "#
+    );
+
+    let name_cstr =
+        Literal::c_string(&CString::new(name.value()).expect("name contains NUL-terminator"));
+
+    Ok(quote! {
+        /// The module name.
+        ///
+        /// Used by the printing macros, e.g. [`info!`].
+        const __LOG_PREFIX: &[u8] = #name_cstr.to_bytes_with_nul();
+
+        // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
+        // freed until the module is unloaded.
+        #[cfg(MODULE)]
+        static THIS_MODULE: ::kernel::ThisModule = unsafe {
+            extern "C" {
+                static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
+            };
+
+            ::kernel::ThisModule::from_ptr(__this_module.get())
+        };
+
+        #[cfg(not(MODULE))]
+        static THIS_MODULE: ::kernel::ThisModule = unsafe {
+            ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
+        };
+
+        /// The `LocalModule` type is the type of the module created by `module!`,
+        /// `module_pci_driver!`, `module_platform_driver!`, etc.
+        type LocalModule = #type_;
+
+        impl ::kernel::ModuleMetadata for #type_ {
+            const NAME: &'static ::kernel::str::CStr = #name_cstr;
+        }
+
+        // Double nested modules, since then nobody can access the public items inside.
+        mod __module_init {
+            mod __module_init {
+                use super::super::#type_;
+                use pin_init::PinInit;
+
+                /// The "Rust loadable module" mark.
+                //
+                // This may be best done another way later on, e.g. as a new modinfo
+                // key or a new section. For the moment, keep it simple.
+                #[cfg(MODULE)]
+                #[doc(hidden)]
+                #[used(compiler)]
+                static __IS_RUST_MODULE: () = ();
+
+                static mut __MOD: ::core::mem::MaybeUninit<#type_> =
+                    ::core::mem::MaybeUninit::uninit();
+
+                // Loadable modules need to export the `{init,cleanup}_module` identifiers.
+                /// # Safety
+                ///
+                /// This function must not be called after module initialization, because it may be
+                /// freed after that completes.
+                #[cfg(MODULE)]
+                #[doc(hidden)]
+                #[no_mangle]
+                #[link_section = ".init.text"]
+                pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
+                    // SAFETY: This function is inaccessible to the outside due to the double
+                    // module wrapping it. It is called exactly once by the C side via its
+                    // unique name.
+                    unsafe { __init() }
+                }
+
+                #[cfg(MODULE)]
+                #[doc(hidden)]
+                #[used(compiler)]
+                #[link_section = ".init.data"]
+                static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
+                    init_module;
+
+                #[cfg(MODULE)]
+                #[doc(hidden)]
+                #[no_mangle]
+                #[link_section = ".exit.text"]
+                pub extern "C" fn cleanup_module() {
+                    // SAFETY:
+                    // - This function is inaccessible to the outside due to the double
+                    //   module wrapping it. It is called exactly once by the C side via its
+                    //   unique name,
+                    // - furthermore it is only called after `init_module` has returned `0`
+                    //   (which delegates to `__init`).
+                    unsafe { __exit() }
+                }
+
+                #[cfg(MODULE)]
+                #[doc(hidden)]
+                #[used(compiler)]
+                #[link_section = ".exit.data"]
+                static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
+
+                // Built-in modules are initialized through an initcall pointer
+                // and the identifiers need to be unique.
+                #[cfg(not(MODULE))]
+                #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
+                #[doc(hidden)]
+                #[link_section = #initcall_section]
+                #[used(compiler)]
+                pub static #ident_initcall: extern "C" fn() ->
+                    ::kernel::ffi::c_int = #ident_initcall;
+
+                #[cfg(not(MODULE))]
+                #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
+                ::core::arch::global_asm!(#global_asm);
+
+                #[cfg(not(MODULE))]
+                #[doc(hidden)]
+                #[no_mangle]
+                pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
+                    // SAFETY: This function is inaccessible to the outside due to the double
+                    // module wrapping it. It is called exactly once by the C side via its
+                    // placement above in the initcall section.
+                    unsafe { __init() }
+                }
+
+                #[cfg(not(MODULE))]
+                #[doc(hidden)]
+                #[no_mangle]
+                pub extern "C" fn #ident_exit() {
+                    // SAFETY:
+                    // - This function is inaccessible to the outside due to the double
+                    //   module wrapping it. It is called exactly once by the C side via its
+                    //   unique name,
+                    // - furthermore it is only called after `#ident_init` has
+                    //   returned `0` (which delegates to `__init`).
+                    unsafe { __exit() }
+                }
+
+                /// # Safety
+                ///
+                /// This function must only be called once.
+                unsafe fn __init() -> ::kernel::ffi::c_int {
+                    let initer =
+                        <#type_ as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
+                    // SAFETY: No data race, since `__MOD` can only be accessed by this module
+                    // and there only `__init` and `__exit` access it. These functions are only
+                    // called once and `__exit` cannot be called before or during `__init`.
+                    match unsafe { initer.__pinned_init(__MOD.as_mut_ptr()) } {
+                        Ok(m) => 0,
+                        Err(e) => e.to_errno(),
+                    }
+                }
+
+                /// # Safety
+                ///
+                /// This function must
+                /// - only be called once,
+                /// - be called after `__init` has been called and returned `0`.
+                unsafe fn __exit() {
+                    // SAFETY: No data race, since `__MOD` can only be accessed by this module
+                    // and there only `__init` and `__exit` access it. These functions are only
+                    // called once and `__init` was already called.
+                    unsafe {
+                        // Invokes `drop()` on `__MOD`, which should be used for cleanup.
+                        __MOD.assume_init_drop();
+                    }
+                }
+
+                #modinfo_ts
+            }
+        }
+
+        mod module_parameters {
+            #params_ts
+        }
+    })
 }
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 09/11] rust: macros: allow arbitrary types to be used in `module!` macro
       [not found] <20260107161729.3855851-1-gary@kernel.org>
                   ` (2 preceding siblings ...)
  2026-01-07 16:15 ` [PATCH v2 05/11] rust: macros: use `quote!` for " Gary Guo
@ 2026-01-07 16:15 ` Gary Guo
  2026-01-07 16:15 ` [PATCH v2 10/11] rust: macros: rearrange `#[doc(hidden)]` " Gary Guo
  4 siblings, 0 replies; 8+ messages in thread
From: Gary Guo @ 2026-01-07 16:15 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Aaron Tomlin
  Cc: rust-for-linux, linux-modules, linux-kernel

From: Gary Guo <gary@garyguo.net>

Previously this only accepts an identifier, but now with `syn` it is
easy to make it accepts any type.

Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/macros/module.rs | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index ba345d672839e..31d39764c6926 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -26,7 +26,8 @@
     LitStr,
     Path,
     Result,
-    Token, //
+    Token,
+    Type, //
 };
 
 use crate::helpers::*;
@@ -376,7 +377,7 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
 }
 
 pub(crate) struct ModuleInfo {
-    type_: Ident,
+    type_: Type,
     license: AsciiLitStr,
     name: AsciiLitStr,
     authors: Option<Punctuated<AsciiLitStr, Token![,]>>,
@@ -536,7 +537,6 @@ impl ::kernel::ModuleMetadata for #type_ {
         // Double nested modules, since then nobody can access the public items inside.
         mod __module_init {
             mod __module_init {
-                use super::super::#type_;
                 use pin_init::PinInit;
 
                 /// The "Rust loadable module" mark.
@@ -548,7 +548,7 @@ mod __module_init {
                 #[used(compiler)]
                 static __IS_RUST_MODULE: () = ();
 
-                static mut __MOD: ::core::mem::MaybeUninit<#type_> =
+                static mut __MOD: ::core::mem::MaybeUninit<super::super::LocalModule> =
                     ::core::mem::MaybeUninit::uninit();
 
                 // Loadable modules need to export the `{init,cleanup}_module` identifiers.
@@ -635,8 +635,9 @@ pub extern "C" fn #ident_exit() {
                 ///
                 /// This function must only be called once.
                 unsafe fn __init() -> ::kernel::ffi::c_int {
-                    let initer =
-                        <#type_ as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
+                    let initer = <super::super::LocalModule as ::kernel::InPlaceModule>::init(
+                        &super::super::THIS_MODULE
+                    );
                     // SAFETY: No data race, since `__MOD` can only be accessed by this module
                     // and there only `__init` and `__exit` access it. These functions are only
                     // called once and `__exit` cannot be called before or during `__init`.
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 10/11] rust: macros: rearrange `#[doc(hidden)]` in `module!` macro
       [not found] <20260107161729.3855851-1-gary@kernel.org>
                   ` (3 preceding siblings ...)
  2026-01-07 16:15 ` [PATCH v2 09/11] rust: macros: allow arbitrary types to be used in " Gary Guo
@ 2026-01-07 16:15 ` Gary Guo
  4 siblings, 0 replies; 8+ messages in thread
From: Gary Guo @ 2026-01-07 16:15 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Aaron Tomlin
  Cc: rust-for-linux, Tamir Duberstein, linux-modules, linux-kernel

From: Gary Guo <gary@garyguo.net>

This `#[doc(hidden)]` can just be applied on a module to hide anything
within.

Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
 rust/macros/module.rs | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 31d39764c6926..c86cced5e0e8f 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -77,7 +77,6 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool)
         );
         let item = quote! {
             #cfg
-            #[doc(hidden)]
             #[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
             #[used(compiler)]
             pub static #counter: [u8; #length] = *#string;
@@ -535,6 +534,7 @@ impl ::kernel::ModuleMetadata for #type_ {
         }
 
         // Double nested modules, since then nobody can access the public items inside.
+        #[doc(hidden)]
         mod __module_init {
             mod __module_init {
                 use pin_init::PinInit;
@@ -544,7 +544,6 @@ mod __module_init {
                 // This may be best done another way later on, e.g. as a new modinfo
                 // key or a new section. For the moment, keep it simple.
                 #[cfg(MODULE)]
-                #[doc(hidden)]
                 #[used(compiler)]
                 static __IS_RUST_MODULE: () = ();
 
@@ -557,7 +556,6 @@ mod __module_init {
                 /// This function must not be called after module initialization, because it may be
                 /// freed after that completes.
                 #[cfg(MODULE)]
-                #[doc(hidden)]
                 #[no_mangle]
                 #[link_section = ".init.text"]
                 pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
@@ -568,14 +566,12 @@ mod __module_init {
                 }
 
                 #[cfg(MODULE)]
-                #[doc(hidden)]
                 #[used(compiler)]
                 #[link_section = ".init.data"]
                 static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
                     init_module;
 
                 #[cfg(MODULE)]
-                #[doc(hidden)]
                 #[no_mangle]
                 #[link_section = ".exit.text"]
                 pub extern "C" fn cleanup_module() {
@@ -589,7 +585,6 @@ pub extern "C" fn cleanup_module() {
                 }
 
                 #[cfg(MODULE)]
-                #[doc(hidden)]
                 #[used(compiler)]
                 #[link_section = ".exit.data"]
                 static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
@@ -598,7 +593,6 @@ pub extern "C" fn cleanup_module() {
                 // and the identifiers need to be unique.
                 #[cfg(not(MODULE))]
                 #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
-                #[doc(hidden)]
                 #[link_section = #initcall_section]
                 #[used(compiler)]
                 pub static #ident_initcall: extern "C" fn() ->
@@ -609,7 +603,6 @@ pub extern "C" fn cleanup_module() {
                 ::core::arch::global_asm!(#global_asm);
 
                 #[cfg(not(MODULE))]
-                #[doc(hidden)]
                 #[no_mangle]
                 pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
                     // SAFETY: This function is inaccessible to the outside due to the double
@@ -619,7 +612,6 @@ pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
                 }
 
                 #[cfg(not(MODULE))]
-                #[doc(hidden)]
                 #[no_mangle]
                 pub extern "C" fn #ident_exit() {
                     // SAFETY:
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro
  2026-01-07 16:15 ` [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
@ 2026-01-07 17:11   ` Tamir Duberstein
  0 siblings, 0 replies; 8+ messages in thread
From: Tamir Duberstein @ 2026-01-07 17:11 UTC (permalink / raw)
  To: Gary Guo
  Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, José Expósito, rust-for-linux,
	Patrick Miller, linux-kernel, linux-modules

On Wed, Jan 7, 2026 at 11:30 AM Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> With `syn` being available in the kernel, use it to parse the complex
> custom `module!` macro to replace existing helpers. Only parsing is
> changed in this commit, the code generation is untouched.
>
> This has the benefit of better error message when the macro is used
> incorrectly, as it can point to a concrete span on what's going wrong.
>
> For example, if a field is specified twice, previously it reads:
>
>     error: proc macro panicked
>       --> samples/rust/rust_minimal.rs:7:1
>        |
>     7  | / module! {
>     8  | |     type: RustMinimal,
>     9  | |     name: "rust_minimal",
>     10 | |     author: "Rust for Linux Contributors",
>     11 | |     description: "Rust minimal sample",
>     12 | |     license: "GPL",
>     13 | |     license: "GPL",
>     14 | | }
>        | |_^
>        |
>        = help: message: Duplicated key "license". Keys can only be specified once.
>
> now it reads:
>
>     error: duplicated key "license". Keys can only be specified once.
>       --> samples/rust/rust_minimal.rs:13:5
>        |
>     13 |     license: "GPL",
>        |     ^^^^^^^
>
> Signed-off-by: Gary Guo <gary@garyguo.net>

Reviewed-by: Tamir Duberstein <tamird@gmail.com>

> ---
>  rust/macros/helpers.rs | 109 ++++--------
>  rust/macros/lib.rs     |   6 +-
>  rust/macros/module.rs  | 389 +++++++++++++++++++++++++----------------
>  3 files changed, 277 insertions(+), 227 deletions(-)
>
> diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
> index 13fafaba12261..fa66ef6eb0f3d 100644
> --- a/rust/macros/helpers.rs
> +++ b/rust/macros/helpers.rs
> @@ -1,53 +1,21 @@
>  // SPDX-License-Identifier: GPL-2.0
>
> -use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
> -
> -pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
> -    if let Some(TokenTree::Ident(ident)) = it.next() {
> -        Some(ident.to_string())
> -    } else {
> -        None
> -    }
> -}
> -
> -pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option<char> {
> -    let peek = it.clone().next();
> -    match peek {
> -        Some(TokenTree::Punct(punct)) if punct.as_char() == '-' => {
> -            let _ = it.next();
> -            Some(punct.as_char())
> -        }
> -        _ => None,
> -    }
> -}
> -
> -pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
> -    if let Some(TokenTree::Literal(literal)) = it.next() {
> -        Some(literal.to_string())
> -    } else {
> -        None
> -    }
> -}
> -
> -pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
> -    try_literal(it).and_then(|string| {
> -        if string.starts_with('\"') && string.ends_with('\"') {
> -            let content = &string[1..string.len() - 1];
> -            if content.contains('\\') {
> -                panic!("Escape sequences in string literals not yet handled");
> -            }
> -            Some(content.to_string())
> -        } else if string.starts_with("r\"") {
> -            panic!("Raw string literals are not yet handled");
> -        } else {
> -            None
> -        }
> -    })
> -}
> -
> -pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
> -    try_ident(it).expect("Expected Ident")
> -}
> +use proc_macro2::{
> +    token_stream,
> +    Ident,
> +    TokenStream,
> +    TokenTree, //
> +};
> +use quote::ToTokens;
> +use syn::{
> +    parse::{
> +        Parse,
> +        ParseStream, //
> +    },
> +    Error,
> +    LitStr,
> +    Result, //
> +};
>
>  pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
>      if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
> @@ -57,27 +25,28 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
>      }
>  }
>
> -pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
> -    try_string(it).expect("Expected string")
> -}
> +/// A string literal that is required to have ASCII value only.
> +pub(crate) struct AsciiLitStr(LitStr);
>
> -pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
> -    let string = try_string(it).expect("Expected string");
> -    assert!(string.is_ascii(), "Expected ASCII string");
> -    string
> +impl Parse for AsciiLitStr {
> +    fn parse(input: ParseStream<'_>) -> Result<Self> {
> +        let s: LitStr = input.parse()?;
> +        if !s.value().is_ascii() {
> +            return Err(Error::new_spanned(s, "expected ASCII-only string literal"));
> +        }
> +        Ok(Self(s))
> +    }
>  }
>
> -pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
> -    if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
> -        group
> -    } else {
> -        panic!("Expected Group");
> +impl ToTokens for AsciiLitStr {
> +    fn to_tokens(&self, ts: &mut TokenStream) {
> +        self.0.to_tokens(ts);
>      }
>  }
>
> -pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
> -    if it.next().is_some() {
> -        panic!("Expected end");
> +impl AsciiLitStr {
> +    pub(crate) fn value(&self) -> String {
> +        self.0.value()
>      }
>  }
>
> @@ -114,17 +83,3 @@ 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, expected_name: &str) -> String {
> -    assert_eq!(expect_ident(it), expected_name);
> -    assert_eq!(expect_punct(it), ':');
> -    let string = expect_string(it);
> -    assert_eq!(expect_punct(it), ',');
> -    string
> -}
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 9955c04dbaae3..c5347127a3a51 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -131,8 +131,10 @@
>  ///   - `firmware`: array of ASCII string literals of the firmware files of
>  ///     the kernel module.
>  #[proc_macro]
> -pub fn module(ts: TokenStream) -> TokenStream {
> -    module::module(ts.into()).into()
> +pub fn module(input: TokenStream) -> TokenStream {
> +    module::module(parse_macro_input!(input))
> +        .unwrap_or_else(|e| e.into_compile_error())
> +        .into()
>  }
>
>  /// Declares or implements a vtable trait.
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index b855a2b586e18..6ad7b411ccde4 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -2,28 +2,30 @@
>
>  use std::fmt::Write;
>
> -use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
> +use proc_macro2::{
> +    Literal,
> +    TokenStream, //
> +};
> +use quote::ToTokens;
> +use syn::{
> +    braced,
> +    bracketed,
> +    ext::IdentExt,
> +    parse::{
> +        Parse,
> +        ParseStream, //
> +    },
> +    punctuated::Punctuated,
> +    Error,
> +    Expr,
> +    Ident,
> +    LitStr,
> +    Result,
> +    Token, //
> +};
>
>  use crate::helpers::*;
>
> -fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
> -    let group = expect_group(it);
> -    assert_eq!(group.delimiter(), Delimiter::Bracket);
> -    let mut values = Vec::new();
> -    let mut it = group.stream().into_iter();
> -
> -    while let Some(val) = try_string(&mut it) {
> -        assert!(val.is_ascii(), "Expected ASCII string");
> -        values.push(val);
> -        match it.next() {
> -            Some(TokenTree::Punct(punct)) => assert_eq!(punct.as_char(), ','),
> -            None => break,
> -            _ => panic!("Expected ',' or end of array"),
> -        }
> -    }
> -    values
> -}
> -
>  struct ModInfoBuilder<'a> {
>      module: &'a str,
>      counter: usize,
> @@ -113,12 +115,16 @@ fn emit_params(&mut self, info: &ModuleInfo) {
>          };
>
>          for param in params {
> -            let ops = param_ops_path(&param.ptype);
> +            let param_name = param.name.to_string();
> +            let param_type = param.ptype.to_string();
> +            let param_default = param.default.to_token_stream().to_string();
> +
> +            let ops = param_ops_path(&param_type);
>
>              // Note: The spelling of these fields is dictated by the user space
>              // tool `modinfo`.
> -            self.emit_param("parmtype", &param.name, &param.ptype);
> -            self.emit_param("parm", &param.name, &param.description);
> +            self.emit_param("parmtype", &param_name, &param_type);
> +            self.emit_param("parm", &param_name, &param.description.value());
>
>              write!(
>                  self.param_buffer,
> @@ -160,10 +166,7 @@ fn emit_params(&mut self, info: &ModuleInfo) {
>                          );
>                  }};
>                  ",
> -                module_name = info.name,
> -                param_type = param.ptype,
> -                param_default = param.default,
> -                param_name = param.name,
> +                module_name = info.name.value(),
>                  ops = ops,
>              )
>              .unwrap();
> @@ -187,127 +190,82 @@ fn param_ops_path(param_type: &str) -> &'static str {
>      }
>  }
>
> -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 = try_sign(param_it);
> -    let default = try_literal(param_it).expect("Expected default param value");
> -    assert_eq!(expect_punct(param_it), ',');
> -    let mut value = sign.map(String::from).unwrap_or_default();
> -    value.push_str(&default);
> -    value
> -}
> -
> -#[derive(Debug, Default)]
> -struct ModuleInfo {
> -    type_: String,
> -    license: String,
> -    name: String,
> -    authors: Option<Vec<String>>,
> -    description: Option<String>,
> -    alias: Option<Vec<String>>,
> -    firmware: Option<Vec<String>>,
> -    imports_ns: Option<Vec<String>>,
> -    params: Option<Vec<Parameter>>,
> -}
> -
> -#[derive(Debug)]
> -struct Parameter {
> -    name: String,
> -    ptype: String,
> -    default: String,
> -    description: String,
> -}
> -
> -fn expect_params(it: &mut token_stream::IntoIter) -> Vec<Parameter> {
> -    let params = expect_group(it);
> -    assert_eq!(params.delimiter(), Delimiter::Brace);
> -    let mut it = params.stream().into_iter();
> -    let mut parsed = Vec::new();
> -
> -    loop {
> -        let param_name = match it.next() {
> -            Some(TokenTree::Ident(ident)) => ident.to_string(),
> -            Some(_) => panic!("Expected Ident or end"),
> -            None => break,
> -        };
> -
> -        assert_eq!(expect_punct(&mut it), ':');
> -        let param_type = expect_ident(&mut it);
> -        let group = expect_group(&mut it);
> -        assert_eq!(group.delimiter(), Delimiter::Brace);
> -        assert_eq!(expect_punct(&mut it), ',');
> -
> -        let mut param_it = group.stream().into_iter();
> -        let param_default = expect_param_default(&mut param_it);
> -        let param_description = expect_string_field(&mut param_it, "description");
> -        expect_end(&mut param_it);
> -
> -        parsed.push(Parameter {
> -            name: param_name,
> -            ptype: param_type,
> -            default: param_default,
> -            description: param_description,
> -        })
> -    }
> -
> -    parsed
> -}
> -
> -impl ModuleInfo {
> -    fn parse(it: &mut token_stream::IntoIter) -> Self {
> -        let mut info = ModuleInfo::default();
> -
> -        const EXPECTED_KEYS: &[&str] = &[
> -            "type",
> -            "name",
> -            "authors",
> -            "description",
> -            "license",
> -            "alias",
> -            "firmware",
> -            "imports_ns",
> -            "params",
> -        ];
> -        const REQUIRED_KEYS: &[&str] = &["type", "name", "license"];
> +/// Parse fields that are required to use a specific order.
> +///
> +/// As fields must follow a specific order, we *could* just parse fields one by one by peeking.
> +/// However the error message generated when implementing that way is not very friendly.
> +///
> +/// So instead we parse fields in an arbitrary order, but only enforce the ordering after parsing,
> +/// and if the wrong order is used, the proper order is communicated to the user with error message.
> +///
> +/// Usage looks like this:
> +/// ```ignore
> +/// parse_ordered_fields! {
> +///     from input;
> +///
> +///     // This will extract "foo: <field>" into a variable named "foo".
> +///     // The variable will have type `Option<_>`.
> +///     foo => <expression that parses the field>,
> +///
> +///     // If you need the variable name to be different than the key name.
> +///     // This extracts "baz: <field>" into a variable named "bar".
> +///     // You might want this if "baz" is a keyword.
> +///     baz as bar => <expression that parse the field>,
> +///
> +///     // You can mark a key as required, and the variable will no longer be `Option`.
> +///     // foobar will be of type `Expr` instead of `Option<Expr>`.
> +///     foobar [required] => input.parse::<Expr>()?,
> +/// }
> +/// ```

It's a shame that this macro bails on the first failure rather than
accumulating them all.

> +macro_rules! parse_ordered_fields {
> +    (@gen
> +        [$input:expr]
> +        [$([$name:ident; $key:ident; $parser:expr])*]
> +        [$([$req_name:ident; $req_key:ident])*]
> +    ) => {
> +        $(let mut $name = None;)*
> +
> +        const EXPECTED_KEYS: &[&str] = &[$(stringify!($key),)*];
> +        const REQUIRED_KEYS: &[&str] = &[$(stringify!($req_key),)*];
> +
> +        let span = $input.span();
>          let mut seen_keys = Vec::new();
>
>          loop {
> -            let key = match it.next() {
> -                Some(TokenTree::Ident(ident)) => ident.to_string(),
> -                Some(_) => panic!("Expected Ident or end"),
> -                None => break,
> -            };
> +            if $input.is_empty() {
> +                break;
> +            }

`while !$input.is_empty()`?

> +
> +            let key = $input.call(Ident::parse_any)?;
>
>              if seen_keys.contains(&key) {
> -                panic!("Duplicated key \"{key}\". Keys can only be specified once.");
> +                Err(Error::new_spanned(
> +                    &key,
> +                    format!(r#"duplicated key "{key}". Keys can only be specified once."#),
> +                ))?
>              }
>
> -            assert_eq!(expect_punct(it), ':');
> -
> -            match key.as_str() {
> -                "type" => info.type_ = expect_ident(it),
> -                "name" => info.name = expect_string_ascii(it),
> -                "authors" => info.authors = Some(expect_string_array(it)),
> -                "description" => info.description = Some(expect_string(it)),
> -                "license" => info.license = expect_string_ascii(it),
> -                "alias" => info.alias = Some(expect_string_array(it)),
> -                "firmware" => info.firmware = Some(expect_string_array(it)),
> -                "imports_ns" => info.imports_ns = Some(expect_string_array(it)),
> -                "params" => info.params = Some(expect_params(it)),
> -                _ => panic!("Unknown key \"{key}\". Valid keys are: {EXPECTED_KEYS:?}."),
> +            $input.parse::<Token![:]>()?;
> +
> +            match &*key.to_string() {
> +                $(
> +                    stringify!($key) => $name = Some($parser),
> +                )*
> +                _ => {
> +                    Err(Error::new_spanned(
> +                        &key,
> +                        format!(r#"unknown key "{key}". Valid keys are: {EXPECTED_KEYS:?}."#),
> +                    ))?
> +                }
>              }
>
> -            assert_eq!(expect_punct(it), ',');
> -
> +            $input.parse::<Token![,]>()?;
>              seen_keys.push(key);
>          }
>
> -        expect_end(it);
> -
>          for key in REQUIRED_KEYS {
>              if !seen_keys.iter().any(|e| e == key) {
> -                panic!("Missing required key \"{key}\".");
> +                Err(Error::new(span, format!(r#"missing required key "{key}""#)))?
>              }
>          }
>
> @@ -319,43 +277,178 @@ fn parse(it: &mut token_stream::IntoIter) -> Self {
>          }
>
>          if seen_keys != ordered_keys {
> -            panic!("Keys are not ordered as expected. Order them like: {ordered_keys:?}.");
> +            Err(Error::new(
> +                span,
> +                format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
> +            ))?
> +        }
> +
> +        $(let $req_name = $req_name.expect("required field");)*
> +    };
> +
> +    // Handle required fields.
> +    (@gen
> +        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
> +        $key:ident as $name:ident [required] => $parser:expr,
> +        $($rest:tt)*
> +    ) => {
> +        parse_ordered_fields!(
> +            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)* [$name; $key]] $($rest)*
> +        )
> +    };
> +    (@gen
> +        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
> +        $name:ident [required] => $parser:expr,
> +        $($rest:tt)*
> +    ) => {
> +        parse_ordered_fields!(
> +            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)* [$name; $name]] $($rest)*
> +        )
> +    };
> +
> +    // Handle optional fields.
> +    (@gen
> +        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
> +        $key:ident as $name:ident => $parser:expr,
> +        $($rest:tt)*
> +    ) => {
> +        parse_ordered_fields!(
> +            @gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)*] $($rest)*
> +        )
> +    };
> +    (@gen
> +        [$input:expr] [$($tok:tt)*] [$($req:tt)*]
> +        $name:ident => $parser:expr,
> +        $($rest:tt)*
> +    ) => {
> +        parse_ordered_fields!(
> +            @gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)*] $($rest)*
> +        )
> +    };

It would be nice to avoid the combinatorial explosion here, though I'm
not immediately sure how.

> +
> +    (from $input:expr; $($tok:tt)*) => {
> +        parse_ordered_fields!(@gen [$input] [] [] $($tok)*)
> +    }
> +}
> +
> +struct Parameter {
> +    name: Ident,
> +    ptype: Ident,
> +    default: Expr,
> +    description: LitStr,
> +}
> +
> +impl Parse for Parameter {
> +    fn parse(input: ParseStream<'_>) -> Result<Self> {
> +        let name = input.parse()?;
> +        input.parse::<Token![:]>()?;
> +        let ptype = input.parse()?;
> +
> +        let fields;
> +        braced!(fields in input);
> +
> +        parse_ordered_fields! {
> +            from fields;
> +            default [required] => fields.parse()?,
> +            description [required] => fields.parse()?,
>          }
>
> -        info
> +        Ok(Self {
> +            name,
> +            ptype,
> +            default,
> +            description,
> +        })
>      }
>  }
>
> -pub(crate) fn module(ts: TokenStream) -> TokenStream {
> -    let mut it = ts.into_iter();
> +pub(crate) struct ModuleInfo {
> +    type_: Ident,
> +    license: AsciiLitStr,
> +    name: AsciiLitStr,
> +    authors: Option<Punctuated<AsciiLitStr, Token![,]>>,
> +    description: Option<LitStr>,
> +    alias: Option<Punctuated<AsciiLitStr, Token![,]>>,
> +    firmware: Option<Punctuated<AsciiLitStr, Token![,]>>,
> +    imports_ns: Option<Punctuated<AsciiLitStr, Token![,]>>,
> +    params: Option<Punctuated<Parameter, Token![,]>>,
> +}
>
> -    let info = ModuleInfo::parse(&mut it);
> +impl Parse for ModuleInfo {
> +    fn parse(input: ParseStream<'_>) -> Result<Self> {
> +        parse_ordered_fields!(
> +            from input;
> +            type as type_ [required] => input.parse()?,
> +            name [required] => input.parse()?,
> +            authors => {
> +                let list;
> +                bracketed!(list in input);
> +                Punctuated::parse_terminated(&list)?
> +            },
> +            description => input.parse()?,
> +            license [required] => input.parse()?,
> +            alias => {
> +                let list;
> +                bracketed!(list in input);
> +                Punctuated::parse_terminated(&list)?
> +            },
> +            firmware => {
> +                let list;
> +                bracketed!(list in input);
> +                Punctuated::parse_terminated(&list)?
> +            },
> +            imports_ns => {
> +                let list;
> +                bracketed!(list in input);
> +                Punctuated::parse_terminated(&list)?
> +            },
> +            params => {
> +                let list;
> +                braced!(list in input);
> +                Punctuated::parse_terminated(&list)?
> +            },
> +        );
> +
> +        Ok(ModuleInfo {
> +            type_,
> +            license,
> +            name,
> +            authors,
> +            description,
> +            alias,
> +            firmware,
> +            imports_ns,
> +            params,
> +        })
> +    }
> +}
>
> +pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>      // Rust does not allow hyphens in identifiers, use underscore instead.
> -    let ident = info.name.replace('-', "_");
> +    let ident = info.name.value().replace('-', "_");
>      let mut modinfo = ModInfoBuilder::new(ident.as_ref());
>      if let Some(authors) = &info.authors {
>          for author in authors {
> -            modinfo.emit("author", author);
> +            modinfo.emit("author", &author.value());
>          }
>      }
>      if let Some(description) = &info.description {
> -        modinfo.emit("description", description);
> +        modinfo.emit("description", &description.value());
>      }
> -    modinfo.emit("license", &info.license);
> +    modinfo.emit("license", &info.license.value());
>      if let Some(aliases) = &info.alias {
>          for alias in aliases {
> -            modinfo.emit("alias", alias);
> +            modinfo.emit("alias", &alias.value());
>          }
>      }
>      if let Some(firmware) = &info.firmware {
>          for fw in firmware {
> -            modinfo.emit("firmware", fw);
> +            modinfo.emit("firmware", &fw.value());
>          }
>      }
>      if let Some(imports) = &info.imports_ns {
>          for ns in imports {
> -            modinfo.emit("import_ns", ns);
> +            modinfo.emit("import_ns", &ns.value());
>          }
>      }
>
> @@ -366,7 +459,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
>
>      modinfo.emit_params(&info);
>
> -    format!(
> +    Ok(format!(
>          "
>              /// The module name.
>              ///
> @@ -536,12 +629,12 @@ mod module_parameters {{
>              }}
>          ",
>          type_ = info.type_,
> -        name = info.name,
> +        name = info.name.value(),
>          ident = ident,
>          modinfo = modinfo.buffer,
>          params = modinfo.param_buffer,
>          initcall_section = ".initcall6.init"
>      )
>      .parse()
> -    .expect("Error parsing formatted string into token stream.")
> +    .expect("Error parsing formatted string into token stream."))
>  }
> --
> 2.51.2
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 05/11] rust: macros: use `quote!` for `module!` macro
  2026-01-07 16:15 ` [PATCH v2 05/11] rust: macros: use `quote!` for " Gary Guo
@ 2026-01-07 17:19   ` Tamir Duberstein
  2026-01-07 17:54     ` Gary Guo
  0 siblings, 1 reply; 8+ messages in thread
From: Tamir Duberstein @ 2026-01-07 17:19 UTC (permalink / raw)
  To: Gary Guo
  Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, rust-for-linux, linux-modules, linux-kernel

On Wed, Jan 7, 2026 at 11:59 AM Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> This has no behavioural change, but is good for maintainability. With
> `quote!`, we're no longer using string templates, so we don't need to
> quote " and {} inside the template anymore. Further more, editors can
> now highlight the code template.
>
> This also improves the robustness as it eliminates the need for string
> quoting and escaping.
>
> Co-developed-by: Benno Lossin <lossin@kernel.org>
> Signed-off-by: Benno Lossin <lossin@kernel.org>
> Signed-off-by: Gary Guo <gary@garyguo.net>

Are there significant changes here? You didn't pick up my tag.

> ---
>  rust/macros/module.rs | 558 ++++++++++++++++++++++--------------------
>  1 file changed, 295 insertions(+), 263 deletions(-)
>
> diff --git a/rust/macros/module.rs b/rust/macros/module.rs
> index 6ad7b411ccde4..ba345d672839e 100644
> --- a/rust/macros/module.rs
> +++ b/rust/macros/module.rs
> @@ -1,12 +1,15 @@
>  // SPDX-License-Identifier: GPL-2.0
>
> -use std::fmt::Write;
> +use std::ffi::CString;
>
>  use proc_macro2::{
>      Literal,
>      TokenStream, //
>  };
> -use quote::ToTokens;
> +use quote::{
> +    format_ident,
> +    quote, //
> +};
>  use syn::{
>      braced,
>      bracketed,
> @@ -15,11 +18,13 @@
>          Parse,
>          ParseStream, //
>      },
> +    parse_quote,
>      punctuated::Punctuated,
>      Error,
>      Expr,
>      Ident,
>      LitStr,
> +    Path,
>      Result,
>      Token, //
>  };
> @@ -29,8 +34,8 @@
>  struct ModInfoBuilder<'a> {
>      module: &'a str,
>      counter: usize,
> -    buffer: String,
> -    param_buffer: String,
> +    ts: TokenStream,
> +    param_ts: TokenStream,
>  }
>
>  impl<'a> ModInfoBuilder<'a> {
> @@ -38,8 +43,8 @@ fn new(module: &'a str) -> Self {
>          ModInfoBuilder {
>              module,
>              counter: 0,
> -            buffer: String::new(),
> -            param_buffer: String::new(),
> +            ts: TokenStream::new(),
> +            param_ts: TokenStream::new(),
>          }
>      }
>
> @@ -56,33 +61,32 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool)
>              // Loadable modules' modinfo strings go as-is.
>              format!("{field}={content}\0")
>          };
> -
> -        let buffer = if param {
> -            &mut self.param_buffer
> +        let length = string.len();
> +        let string = Literal::byte_string(string.as_bytes());
> +        let cfg = if builtin {
> +            quote!(#[cfg(not(MODULE))])
>          } else {
> -            &mut self.buffer
> +            quote!(#[cfg(MODULE)])
>          };
>
> -        write!(
> -            buffer,
> -            "
> -                {cfg}
> -                #[doc(hidden)]
> -                #[cfg_attr(not(target_os = \"macos\"), link_section = \".modinfo\")]
> -                #[used(compiler)]
> -                pub static __{module}_{counter}: [u8; {length}] = *{string};
> -            ",
> -            cfg = if builtin {
> -                "#[cfg(not(MODULE))]"
> -            } else {
> -                "#[cfg(MODULE)]"
> -            },
> +        let counter = format_ident!(
> +            "__{module}_{counter}",
>              module = self.module.to_uppercase(),
> -            counter = self.counter,
> -            length = string.len(),
> -            string = Literal::byte_string(string.as_bytes()),
> -        )
> -        .unwrap();
> +            counter = self.counter
> +        );
> +        let item = quote! {
> +            #cfg
> +            #[doc(hidden)]
> +            #[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
> +            #[used(compiler)]
> +            pub static #counter: [u8; #length] = *#string;
> +        };
> +
> +        if param {
> +            self.param_ts.extend(item);
> +        } else {
> +            self.ts.extend(item);
> +        }
>
>          self.counter += 1;
>      }
> @@ -115,77 +119,86 @@ fn emit_params(&mut self, info: &ModuleInfo) {
>          };
>
>          for param in params {
> -            let param_name = param.name.to_string();
> -            let param_type = param.ptype.to_string();
> -            let param_default = param.default.to_token_stream().to_string();
> +            let param_name_str = param.name.to_string();
> +            let param_type_str = param.ptype.to_string();

This renaming is unfortunately; these variables were just introduced
in the previous patch.

>
> -            let ops = param_ops_path(&param_type);
> +            let ops = param_ops_path(&param_type_str);
>
>              // Note: The spelling of these fields is dictated by the user space
>              // tool `modinfo`.
> -            self.emit_param("parmtype", &param_name, &param_type);
> -            self.emit_param("parm", &param_name, &param.description.value());
> -
> -            write!(
> -                self.param_buffer,
> -                "
> -                pub(crate) static {param_name}:
> -                    ::kernel::module_param::ModuleParamAccess<{param_type}> =
> -                        ::kernel::module_param::ModuleParamAccess::new({param_default});
> -
> -                const _: () = {{
> -                    #[link_section = \"__param\"]
> -                    #[used]
> -                    static __{module_name}_{param_name}_struct:
> +            self.emit_param("parmtype", &param_name_str, &param_type_str);
> +            self.emit_param("parm", &param_name_str, &param.description.value());
> +
> +            let static_name = format_ident!("__{}_{}_struct", self.module, param.name);
> +            let param_name_cstr = Literal::c_string(
> +                &CString::new(param_name_str).expect("name contains NUL-terminator"),
> +            );
> +            let param_name_cstr_with_module = Literal::c_string(
> +                &CString::new(format!("{}.{}", self.module, param.name))
> +                    .expect("name contains NUL-terminator"),
> +            );
> +
> +            let param_name = &param.name;
> +            let param_type = &param.ptype;
> +            let param_default = &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);
> +
> +                const _: () = {
> +                    #[allow(non_upper_case_globals)]
> +                    #[link_section = "__param"]
> +                    #[used(compiler)]
> +                    static #static_name:
>                          ::kernel::module_param::KernelParam =
>                          ::kernel::module_param::KernelParam::new(
> -                            ::kernel::bindings::kernel_param {{
> -                                name: if ::core::cfg!(MODULE) {{
> -                                    ::kernel::c_str!(\"{param_name}\").to_bytes_with_nul()
> -                                }} else {{
> -                                    ::kernel::c_str!(\"{module_name}.{param_name}\")
> -                                        .to_bytes_with_nul()
> -                                }}.as_ptr(),
> +                            ::kernel::bindings::kernel_param {
> +                                name: kernel::str::as_char_ptr_in_const_context(
> +                                    if ::core::cfg!(MODULE) {
> +                                        #param_name_cstr
> +                                    } else {
> +                                        #param_name_cstr_with_module
> +                                    }
> +                                ),
>                                  // SAFETY: `__this_module` is constructed by the kernel at load
>                                  // time and will not be freed until the module is unloaded.
>                                  #[cfg(MODULE)]
> -                                mod_: unsafe {{
> +                                mod_: unsafe {
>                                      core::ptr::from_ref(&::kernel::bindings::__this_module)
>                                          .cast_mut()
> -                                }},
> +                                },
>                                  #[cfg(not(MODULE))]
>                                  mod_: ::core::ptr::null_mut(),
> -                                ops: core::ptr::from_ref(&{ops}),
> +                                ops: core::ptr::from_ref(&#ops),
>                                  perm: 0, // Will not appear in sysfs
>                                  level: -1,
>                                  flags: 0,
> -                                __bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {{
> -                                    arg: {param_name}.as_void_ptr()
> -                                }},
> -                            }}
> +                                __bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {
> +                                    arg: #param_name.as_void_ptr()
> +                                },
> +                            }
>                          );
> -                }};
> -                ",
> -                module_name = info.name.value(),
> -                ops = ops,
> -            )
> -            .unwrap();
> +                };
> +            });
>          }
>      }
>  }
>
> -fn param_ops_path(param_type: &str) -> &'static str {
> +fn param_ops_path(param_type: &str) -> Path {
>      match param_type {
> -        "i8" => "::kernel::module_param::PARAM_OPS_I8",
> -        "u8" => "::kernel::module_param::PARAM_OPS_U8",
> -        "i16" => "::kernel::module_param::PARAM_OPS_I16",
> -        "u16" => "::kernel::module_param::PARAM_OPS_U16",
> -        "i32" => "::kernel::module_param::PARAM_OPS_I32",
> -        "u32" => "::kernel::module_param::PARAM_OPS_U32",
> -        "i64" => "::kernel::module_param::PARAM_OPS_I64",
> -        "u64" => "::kernel::module_param::PARAM_OPS_U64",
> -        "isize" => "::kernel::module_param::PARAM_OPS_ISIZE",
> -        "usize" => "::kernel::module_param::PARAM_OPS_USIZE",
> +        "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),
> +        "u16" => parse_quote!(::kernel::module_param::PARAM_OPS_U16),
> +        "i32" => parse_quote!(::kernel::module_param::PARAM_OPS_I32),
> +        "u32" => parse_quote!(::kernel::module_param::PARAM_OPS_U32),
> +        "i64" => parse_quote!(::kernel::module_param::PARAM_OPS_I64),
> +        "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),
>          t => panic!("Unsupported parameter type {}", t),
>      }
>  }
> @@ -424,29 +437,41 @@ fn parse(input: ParseStream<'_>) -> Result<Self> {
>  }
>
>  pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
> +    let ModuleInfo {
> +        type_,
> +        license,
> +        name,
> +        authors,
> +        description,
> +        alias,
> +        firmware,
> +        imports_ns,
> +        params: _,
> +    } = &info;
> +
>      // Rust does not allow hyphens in identifiers, use underscore instead.
> -    let ident = info.name.value().replace('-', "_");
> +    let ident = name.value().replace('-', "_");
>      let mut modinfo = ModInfoBuilder::new(ident.as_ref());
> -    if let Some(authors) = &info.authors {
> +    if let Some(authors) = authors {
>          for author in authors {
>              modinfo.emit("author", &author.value());
>          }
>      }
> -    if let Some(description) = &info.description {
> +    if let Some(description) = description {
>          modinfo.emit("description", &description.value());
>      }
> -    modinfo.emit("license", &info.license.value());
> -    if let Some(aliases) = &info.alias {
> +    modinfo.emit("license", &license.value());
> +    if let Some(aliases) = alias {
>          for alias in aliases {
>              modinfo.emit("alias", &alias.value());
>          }
>      }
> -    if let Some(firmware) = &info.firmware {
> +    if let Some(firmware) = firmware {
>          for fw in firmware {
>              modinfo.emit("firmware", &fw.value());
>          }
>      }
> -    if let Some(imports) = &info.imports_ns {
> +    if let Some(imports) = imports_ns {
>          for ns in imports {
>              modinfo.emit("import_ns", &ns.value());
>          }
> @@ -459,182 +484,189 @@ pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
>
>      modinfo.emit_params(&info);
>
> -    Ok(format!(
> -        "
> -            /// The module name.
> -            ///
> -            /// Used by the printing macros, e.g. [`info!`].
> -            const __LOG_PREFIX: &[u8] = b\"{name}\\0\";
> -
> -            // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
> -            // freed until the module is unloaded.
> -            #[cfg(MODULE)]
> -            static THIS_MODULE: ::kernel::ThisModule = unsafe {{
> -                extern \"C\" {{
> -                    static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
> -                }}
> -
> -                ::kernel::ThisModule::from_ptr(__this_module.get())
> -            }};
> -            #[cfg(not(MODULE))]
> -            static THIS_MODULE: ::kernel::ThisModule = unsafe {{
> -                ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
> -            }};
> -
> -            /// The `LocalModule` type is the type of the module created by `module!`,
> -            /// `module_pci_driver!`, `module_platform_driver!`, etc.
> -            type LocalModule = {type_};
> -
> -            impl ::kernel::ModuleMetadata for {type_} {{
> -                const NAME: &'static ::kernel::str::CStr = c\"{name}\";
> -            }}
> -
> -            // Double nested modules, since then nobody can access the public items inside.
> -            mod __module_init {{
> -                mod __module_init {{
> -                    use super::super::{type_};
> -                    use pin_init::PinInit;
> -
> -                    /// The \"Rust loadable module\" mark.
> -                    //
> -                    // This may be best done another way later on, e.g. as a new modinfo
> -                    // key or a new section. For the moment, keep it simple.
> -                    #[cfg(MODULE)]
> -                    #[doc(hidden)]
> -                    #[used(compiler)]
> -                    static __IS_RUST_MODULE: () = ();
> -
> -                    static mut __MOD: ::core::mem::MaybeUninit<{type_}> =
> -                        ::core::mem::MaybeUninit::uninit();
> -
> -                    // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
> -                    /// # Safety
> -                    ///
> -                    /// This function must not be called after module initialization, because it may be
> -                    /// freed after that completes.
> -                    #[cfg(MODULE)]
> -                    #[doc(hidden)]
> -                    #[no_mangle]
> -                    #[link_section = \".init.text\"]
> -                    pub unsafe extern \"C\" fn init_module() -> ::kernel::ffi::c_int {{
> -                        // SAFETY: This function is inaccessible to the outside due to the double
> -                        // module wrapping it. It is called exactly once by the C side via its
> -                        // unique name.
> -                        unsafe {{ __init() }}
> -                    }}
> -
> -                    #[cfg(MODULE)]
> -                    #[doc(hidden)]
> -                    #[used(compiler)]
> -                    #[link_section = \".init.data\"]
> -                    static __UNIQUE_ID___addressable_init_module: unsafe extern \"C\" fn() -> i32 = init_module;
> -
> -                    #[cfg(MODULE)]
> -                    #[doc(hidden)]
> -                    #[no_mangle]
> -                    #[link_section = \".exit.text\"]
> -                    pub extern \"C\" fn cleanup_module() {{
> -                        // SAFETY:
> -                        // - This function is inaccessible to the outside due to the double
> -                        //   module wrapping it. It is called exactly once by the C side via its
> -                        //   unique name,
> -                        // - furthermore it is only called after `init_module` has returned `0`
> -                        //   (which delegates to `__init`).
> -                        unsafe {{ __exit() }}
> -                    }}
> -
> -                    #[cfg(MODULE)]
> -                    #[doc(hidden)]
> -                    #[used(compiler)]
> -                    #[link_section = \".exit.data\"]
> -                    static __UNIQUE_ID___addressable_cleanup_module: extern \"C\" fn() = cleanup_module;
> -
> -                    // Built-in modules are initialized through an initcall pointer
> -                    // and the identifiers need to be unique.
> -                    #[cfg(not(MODULE))]
> -                    #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
> -                    #[doc(hidden)]
> -                    #[link_section = \"{initcall_section}\"]
> -                    #[used(compiler)]
> -                    pub static __{ident}_initcall: extern \"C\" fn() ->
> -                        ::kernel::ffi::c_int = __{ident}_init;
> -
> -                    #[cfg(not(MODULE))]
> -                    #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
> -                    ::core::arch::global_asm!(
> -                        r#\".section \"{initcall_section}\", \"a\"
> -                        __{ident}_initcall:
> -                            .long   __{ident}_init - .
> -                            .previous
> -                        \"#
> -                    );
> -
> -                    #[cfg(not(MODULE))]
> -                    #[doc(hidden)]
> -                    #[no_mangle]
> -                    pub extern \"C\" fn __{ident}_init() -> ::kernel::ffi::c_int {{
> -                        // SAFETY: This function is inaccessible to the outside due to the double
> -                        // module wrapping it. It is called exactly once by the C side via its
> -                        // placement above in the initcall section.
> -                        unsafe {{ __init() }}
> -                    }}
> -
> -                    #[cfg(not(MODULE))]
> -                    #[doc(hidden)]
> -                    #[no_mangle]
> -                    pub extern \"C\" fn __{ident}_exit() {{
> -                        // SAFETY:
> -                        // - This function is inaccessible to the outside due to the double
> -                        //   module wrapping it. It is called exactly once by the C side via its
> -                        //   unique name,
> -                        // - furthermore it is only called after `__{ident}_init` has
> -                        //   returned `0` (which delegates to `__init`).
> -                        unsafe {{ __exit() }}
> -                    }}
> -
> -                    /// # Safety
> -                    ///
> -                    /// This function must only be called once.
> -                    unsafe fn __init() -> ::kernel::ffi::c_int {{
> -                        let initer =
> -                            <{type_} as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
> -                        // SAFETY: No data race, since `__MOD` can only be accessed by this module
> -                        // and there only `__init` and `__exit` access it. These functions are only
> -                        // called once and `__exit` cannot be called before or during `__init`.
> -                        match unsafe {{ initer.__pinned_init(__MOD.as_mut_ptr()) }} {{
> -                            Ok(m) => 0,
> -                            Err(e) => e.to_errno(),
> -                        }}
> -                    }}
> -
> -                    /// # Safety
> -                    ///
> -                    /// This function must
> -                    /// - only be called once,
> -                    /// - be called after `__init` has been called and returned `0`.
> -                    unsafe fn __exit() {{
> -                        // SAFETY: No data race, since `__MOD` can only be accessed by this module
> -                        // and there only `__init` and `__exit` access it. These functions are only
> -                        // called once and `__init` was already called.
> -                        unsafe {{
> -                            // Invokes `drop()` on `__MOD`, which should be used for cleanup.
> -                            __MOD.assume_init_drop();
> -                        }}
> -                    }}
> -                    {modinfo}
> -                }}
> -            }}
> -            mod module_parameters {{
> -                {params}
> -            }}
> -        ",
> -        type_ = info.type_,
> -        name = info.name.value(),
> -        ident = ident,
> -        modinfo = modinfo.buffer,
> -        params = modinfo.param_buffer,
> -        initcall_section = ".initcall6.init"
> -    )
> -    .parse()
> -    .expect("Error parsing formatted string into token stream."))
> +    let modinfo_ts = modinfo.ts;
> +    let params_ts = modinfo.param_ts;
> +
> +    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 global_asm = format!(
> +        r#".section "{initcall_section}", "a"
> +        __{ident}_initcall:
> +            .long   __{ident}_init - .
> +            .previous
> +        "#
> +    );
> +
> +    let name_cstr =
> +        Literal::c_string(&CString::new(name.value()).expect("name contains NUL-terminator"));
> +
> +    Ok(quote! {
> +        /// The module name.
> +        ///
> +        /// Used by the printing macros, e.g. [`info!`].
> +        const __LOG_PREFIX: &[u8] = #name_cstr.to_bytes_with_nul();
> +
> +        // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
> +        // freed until the module is unloaded.
> +        #[cfg(MODULE)]
> +        static THIS_MODULE: ::kernel::ThisModule = unsafe {
> +            extern "C" {
> +                static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
> +            };
> +
> +            ::kernel::ThisModule::from_ptr(__this_module.get())
> +        };
> +
> +        #[cfg(not(MODULE))]
> +        static THIS_MODULE: ::kernel::ThisModule = unsafe {
> +            ::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
> +        };
> +
> +        /// The `LocalModule` type is the type of the module created by `module!`,
> +        /// `module_pci_driver!`, `module_platform_driver!`, etc.
> +        type LocalModule = #type_;
> +
> +        impl ::kernel::ModuleMetadata for #type_ {
> +            const NAME: &'static ::kernel::str::CStr = #name_cstr;
> +        }
> +
> +        // Double nested modules, since then nobody can access the public items inside.
> +        mod __module_init {
> +            mod __module_init {
> +                use super::super::#type_;
> +                use pin_init::PinInit;
> +
> +                /// The "Rust loadable module" mark.
> +                //
> +                // This may be best done another way later on, e.g. as a new modinfo
> +                // key or a new section. For the moment, keep it simple.
> +                #[cfg(MODULE)]
> +                #[doc(hidden)]
> +                #[used(compiler)]
> +                static __IS_RUST_MODULE: () = ();
> +
> +                static mut __MOD: ::core::mem::MaybeUninit<#type_> =
> +                    ::core::mem::MaybeUninit::uninit();
> +
> +                // Loadable modules need to export the `{init,cleanup}_module` identifiers.
> +                /// # Safety
> +                ///
> +                /// This function must not be called after module initialization, because it may be
> +                /// freed after that completes.
> +                #[cfg(MODULE)]
> +                #[doc(hidden)]
> +                #[no_mangle]
> +                #[link_section = ".init.text"]
> +                pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
> +                    // SAFETY: This function is inaccessible to the outside due to the double
> +                    // module wrapping it. It is called exactly once by the C side via its
> +                    // unique name.
> +                    unsafe { __init() }
> +                }
> +
> +                #[cfg(MODULE)]
> +                #[doc(hidden)]
> +                #[used(compiler)]
> +                #[link_section = ".init.data"]
> +                static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
> +                    init_module;
> +
> +                #[cfg(MODULE)]
> +                #[doc(hidden)]
> +                #[no_mangle]
> +                #[link_section = ".exit.text"]
> +                pub extern "C" fn cleanup_module() {
> +                    // SAFETY:
> +                    // - This function is inaccessible to the outside due to the double
> +                    //   module wrapping it. It is called exactly once by the C side via its
> +                    //   unique name,
> +                    // - furthermore it is only called after `init_module` has returned `0`
> +                    //   (which delegates to `__init`).
> +                    unsafe { __exit() }
> +                }
> +
> +                #[cfg(MODULE)]
> +                #[doc(hidden)]
> +                #[used(compiler)]
> +                #[link_section = ".exit.data"]
> +                static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
> +
> +                // Built-in modules are initialized through an initcall pointer
> +                // and the identifiers need to be unique.
> +                #[cfg(not(MODULE))]
> +                #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
> +                #[doc(hidden)]
> +                #[link_section = #initcall_section]
> +                #[used(compiler)]
> +                pub static #ident_initcall: extern "C" fn() ->
> +                    ::kernel::ffi::c_int = #ident_initcall;
> +
> +                #[cfg(not(MODULE))]
> +                #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
> +                ::core::arch::global_asm!(#global_asm);
> +
> +                #[cfg(not(MODULE))]
> +                #[doc(hidden)]
> +                #[no_mangle]
> +                pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
> +                    // SAFETY: This function is inaccessible to the outside due to the double
> +                    // module wrapping it. It is called exactly once by the C side via its
> +                    // placement above in the initcall section.
> +                    unsafe { __init() }
> +                }
> +
> +                #[cfg(not(MODULE))]
> +                #[doc(hidden)]
> +                #[no_mangle]
> +                pub extern "C" fn #ident_exit() {
> +                    // SAFETY:
> +                    // - This function is inaccessible to the outside due to the double
> +                    //   module wrapping it. It is called exactly once by the C side via its
> +                    //   unique name,
> +                    // - furthermore it is only called after `#ident_init` has
> +                    //   returned `0` (which delegates to `__init`).
> +                    unsafe { __exit() }
> +                }
> +
> +                /// # Safety
> +                ///
> +                /// This function must only be called once.
> +                unsafe fn __init() -> ::kernel::ffi::c_int {
> +                    let initer =
> +                        <#type_ as ::kernel::InPlaceModule>::init(&super::super::THIS_MODULE);
> +                    // SAFETY: No data race, since `__MOD` can only be accessed by this module
> +                    // and there only `__init` and `__exit` access it. These functions are only
> +                    // called once and `__exit` cannot be called before or during `__init`.
> +                    match unsafe { initer.__pinned_init(__MOD.as_mut_ptr()) } {
> +                        Ok(m) => 0,
> +                        Err(e) => e.to_errno(),
> +                    }
> +                }
> +
> +                /// # Safety
> +                ///
> +                /// This function must
> +                /// - only be called once,
> +                /// - be called after `__init` has been called and returned `0`.
> +                unsafe fn __exit() {
> +                    // SAFETY: No data race, since `__MOD` can only be accessed by this module
> +                    // and there only `__init` and `__exit` access it. These functions are only
> +                    // called once and `__init` was already called.
> +                    unsafe {
> +                        // Invokes `drop()` on `__MOD`, which should be used for cleanup.
> +                        __MOD.assume_init_drop();
> +                    }
> +                }
> +
> +                #modinfo_ts
> +            }
> +        }
> +
> +        mod module_parameters {
> +            #params_ts
> +        }
> +    })
>  }
> --
> 2.51.2
>
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 05/11] rust: macros: use `quote!` for `module!` macro
  2026-01-07 17:19   ` Tamir Duberstein
@ 2026-01-07 17:54     ` Gary Guo
  0 siblings, 0 replies; 8+ messages in thread
From: Gary Guo @ 2026-01-07 17:54 UTC (permalink / raw)
  To: Tamir Duberstein
  Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, rust-for-linux, linux-modules, linux-kernel

On Wed, 7 Jan 2026 12:19:26 -0500
Tamir Duberstein <tamird@gmail.com> wrote:

> On Wed, Jan 7, 2026 at 11:59 AM Gary Guo <gary@kernel.org> wrote:
> >
> > From: Gary Guo <gary@garyguo.net>
> >
> > This has no behavioural change, but is good for maintainability. With
> > `quote!`, we're no longer using string templates, so we don't need to
> > quote " and {} inside the template anymore. Further more, editors can
> > now highlight the code template.
> >
> > This also improves the robustness as it eliminates the need for string
> > quoting and escaping.
> >
> > Co-developed-by: Benno Lossin <lossin@kernel.org>
> > Signed-off-by: Benno Lossin <lossin@kernel.org>
> > Signed-off-by: Gary Guo <gary@garyguo.net>  
> 
> Are there significant changes here? You didn't pick up my tag.

There're changes related to added `params` which you probably already
noticed.

Best,
Gary

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-01-07 17:55 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260107161729.3855851-1-gary@kernel.org>
2026-01-07 16:15 ` [PATCH v2 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
2026-01-07 16:15 ` [PATCH v2 04/11] rust: macros: use `syn` to parse `module!` macro Gary Guo
2026-01-07 17:11   ` Tamir Duberstein
2026-01-07 16:15 ` [PATCH v2 05/11] rust: macros: use `quote!` for " Gary Guo
2026-01-07 17:19   ` Tamir Duberstein
2026-01-07 17:54     ` Gary Guo
2026-01-07 16:15 ` [PATCH v2 09/11] rust: macros: allow arbitrary types to be used in " Gary Guo
2026-01-07 16:15 ` [PATCH v2 10/11] rust: macros: rearrange `#[doc(hidden)]` " Gary Guo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).