[PATCH v2] rust: add procedural macro for declaring configfs attributes

Malte Wechter posted 1 patch 1 week, 1 day ago
MAINTAINERS                     |   1 +
drivers/block/rnull/configfs.rs |   2 +-
rust/kernel/configfs.rs         | 251 ----------------------------------------
rust/macros/configfs_attrs.rs   | 182 +++++++++++++++++++++++++++++
rust/macros/helpers.rs          |  15 +++
rust/macros/lib.rs              |  85 ++++++++++++++
samples/rust/rust_configfs.rs   |   2 +-
7 files changed, 285 insertions(+), 253 deletions(-)
[PATCH v2] rust: add procedural macro for declaring configfs attributes
Posted by Malte Wechter 1 week, 1 day ago
Implement `configfs_attrs!` as a procedural macro using `syn`, this
improves readability and maintainability. Remove the old macro and
replace all uses with the new macro. Add the new macro implementation
file to MAINTAINERS.

Signed-off-by: Malte Wechter <maltewechter@gmail.com>
---
Changes in v2:
- Add a try_parse helper function to macros/helpers.rs
- Fix bug where 'child' configuration gets dropped if trailing comma is missing (sashiko)
- Link to v1: https://lore.kernel.org/r/20260520-configfs-syn-v1-1-6c5b80a9cef2@gmail.com
---
 MAINTAINERS                     |   1 +
 drivers/block/rnull/configfs.rs |   2 +-
 rust/kernel/configfs.rs         | 251 ----------------------------------------
 rust/macros/configfs_attrs.rs   | 182 +++++++++++++++++++++++++++++
 rust/macros/helpers.rs          |  15 +++
 rust/macros/lib.rs              |  85 ++++++++++++++
 samples/rust/rust_configfs.rs   |   2 +-
 7 files changed, 285 insertions(+), 253 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd163..45f7a1ec93b45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6464,6 +6464,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git config
 F:	fs/configfs/
 F:	include/linux/configfs.h
 F:	rust/kernel/configfs.rs
+F:	rust/macros/configfs_attrs.rs
 F:	samples/configfs/
 F:	samples/rust/rust_configfs.rs
 
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 7c2eb5c0b7228..f28ec69d79846 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -4,8 +4,8 @@
 use kernel::{
     block::mq::gen_disk::{GenDisk, GenDiskBuilder},
     configfs::{self, AttributeOperations},
-    configfs_attrs,
     fmt::{self, Write as _},
+    macros::configfs_attrs,
     new_mutex,
     page::PAGE_SIZE,
     prelude::*,
diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs
index 2339c6467325d..7a91e36677f52 100644
--- a/rust/kernel/configfs.rs
+++ b/rust/kernel/configfs.rs
@@ -791,254 +791,3 @@ fn as_ptr(&self) -> *const bindings::config_item_type {
         self.item_type.get()
     }
 }
-
-/// Define a list of configfs attributes statically.
-///
-/// Invoking the macro in the following manner:
-///
-/// ```ignore
-/// let item_type = configfs_attrs! {
-///     container: configfs::Subsystem<Configuration>,
-///     data: Configuration,
-///     child: Child,
-///     attributes: [
-///         message: 0,
-///         bar: 1,
-///     ],
-/// };
-/// ```
-///
-/// Expands the following output:
-///
-/// ```ignore
-/// let item_type = {
-///     static CONFIGURATION_MESSAGE_ATTR: kernel::configfs::Attribute<
-///         0,
-///         Configuration,
-///         Configuration,
-///     > = unsafe {
-///         kernel::configfs::Attribute::new({
-///             const S: &str = "message\u{0}";
-///             const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
-///                 S.as_bytes()
-///             ) {
-///                 Ok(v) => v,
-///                 Err(_) => {
-///                     core::panicking::panic_fmt(core::const_format_args!(
-///                         "string contains interior NUL"
-///                     ));
-///                 }
-///             };
-///             C
-///         })
-///     };
-///
-///     static CONFIGURATION_BAR_ATTR: kernel::configfs::Attribute<
-///             1,
-///             Configuration,
-///             Configuration
-///     > = unsafe {
-///         kernel::configfs::Attribute::new({
-///             const S: &str = "bar\u{0}";
-///             const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
-///                 S.as_bytes()
-///             ) {
-///                 Ok(v) => v,
-///                 Err(_) => {
-///                     core::panicking::panic_fmt(core::const_format_args!(
-///                         "string contains interior NUL"
-///                     ));
-///                 }
-///             };
-///             C
-///         })
-///     };
-///
-///     const N: usize = (1usize + (1usize + 0usize)) + 1usize;
-///
-///     static CONFIGURATION_ATTRS: kernel::configfs::AttributeList<N, Configuration> =
-///         unsafe { kernel::configfs::AttributeList::new() };
-///
-///     {
-///         const N: usize = 0usize;
-///         unsafe { CONFIGURATION_ATTRS.add::<N, 0, _>(&CONFIGURATION_MESSAGE_ATTR) };
-///     }
-///
-///     {
-///         const N: usize = (1usize + 0usize);
-///         unsafe { CONFIGURATION_ATTRS.add::<N, 1, _>(&CONFIGURATION_BAR_ATTR) };
-///     }
-///
-///     static CONFIGURATION_TPE:
-///       kernel::configfs::ItemType<configfs::Subsystem<Configuration> ,Configuration>
-///         = kernel::configfs::ItemType::<
-///                 configfs::Subsystem<Configuration>,
-///                 Configuration
-///                 >::new_with_child_ctor::<N,Child>(
-///             &THIS_MODULE,
-///             &CONFIGURATION_ATTRS
-///         );
-///
-///     &CONFIGURATION_TPE
-/// }
-/// ```
-#[macro_export]
-macro_rules! configfs_attrs {
-    (
-        container: $container:ty,
-        data: $data:ty,
-        attributes: [
-            $($name:ident: $attr:literal),* $(,)?
-        ] $(,)?
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child(),
-            @no_child(x),
-            @attrs($($name $attr)*),
-            @eat($($name $attr,)*),
-            @assign(),
-            @cnt(0usize),
-        )
-    };
-    (
-        container: $container:ty,
-        data: $data:ty,
-        child: $child:ty,
-        attributes: [
-            $($name:ident: $attr:literal),* $(,)?
-        ] $(,)?
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child($child),
-            @no_child(),
-            @attrs($($name $attr)*),
-            @eat($($name $attr,)*),
-            @assign(),
-            @cnt(0usize),
-        )
-    };
-    (count:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($aname:ident $aattr:literal)*),
-     @eat($name:ident $attr:literal, $($rname:ident $rattr:literal,)*),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) => {
-        $crate::configfs_attrs!(
-            count:
-            @container($container),
-            @data($data),
-            @child($($child)?),
-            @no_child($($no_child)?),
-            @attrs($($aname $aattr)*),
-            @eat($($rname $rattr,)*),
-            @assign($($assign)* {
-                const N: usize = $cnt;
-                // The following macro text expands to a call to `Attribute::add`.
-
-                // SAFETY: By design of this macro, the name of the variable we
-                // invoke the `add` method on below, is not visible outside of
-                // the macro expansion. The macro does not operate concurrently
-                // on this variable, and thus we have exclusive access to the
-                // variable.
-                unsafe {
-                    $crate::macros::paste!(
-                        [< $data:upper _ATTRS >]
-                            .add::<N, $attr, _>(&[< $data:upper _ $name:upper _ATTR >])
-                    )
-                };
-            }),
-            @cnt(1usize + $cnt),
-        )
-    };
-    (count:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($aname:ident $aattr:literal)*),
-     @eat(),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) =>
-    {
-        $crate::configfs_attrs!(
-            final:
-            @container($container),
-            @data($data),
-            @child($($child)?),
-            @no_child($($no_child)?),
-            @attrs($($aname $aattr)*),
-            @assign($($assign)*),
-            @cnt($cnt),
-        )
-    };
-    (final:
-     @container($container:ty),
-     @data($data:ty),
-     @child($($child:ty)?),
-     @no_child($($no_child:ident)?),
-     @attrs($($name:ident $attr:literal)*),
-     @assign($($assign:block)*),
-     @cnt($cnt:expr),
-    ) =>
-    {
-        $crate::macros::paste!{
-            {
-                $(
-                    // SAFETY: We are expanding `configfs_attrs`.
-                    static [< $data:upper _ $name:upper _ATTR >]:
-                        $crate::configfs::Attribute<$attr, $data, $data> =
-                            unsafe {
-                                $crate::configfs::Attribute::new(
-                                    $crate::c_str!(::core::stringify!($name)),
-                                )
-                            };
-                )*
-
-
-                // We need space for a null terminator.
-                const N: usize = $cnt + 1usize;
-
-                // SAFETY: We are expanding `configfs_attrs`.
-                static [< $data:upper _ATTRS >]:
-                $crate::configfs::AttributeList<N, $data> =
-                    unsafe { $crate::configfs::AttributeList::new() };
-
-                $($assign)*
-
-                $(
-                    const [<$no_child:upper>]: bool = true;
-
-                    static [< $data:upper _TPE >] : $crate::configfs::ItemType<$container, $data>  =
-                        $crate::configfs::ItemType::<$container, $data>::new::<N>(
-                            &THIS_MODULE, &[<$ data:upper _ATTRS >]
-                        );
-                )?
-
-                $(
-                    static [< $data:upper _TPE >]:
-                        $crate::configfs::ItemType<$container, $data>  =
-                            $crate::configfs::ItemType::<$container, $data>::
-                            new_with_child_ctor::<N, $child>(
-                                &THIS_MODULE, &[<$ data:upper _ATTRS >]
-                            );
-                )?
-
-                & [< $data:upper _TPE >]
-            }
-        }
-    };
-
-}
-
-pub use crate::configfs_attrs;
diff --git a/rust/macros/configfs_attrs.rs b/rust/macros/configfs_attrs.rs
new file mode 100644
index 0000000000000..a7fc75cdebcc0
--- /dev/null
+++ b/rust/macros/configfs_attrs.rs
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use quote::{
+    quote, //
+    ToTokens,
+};
+
+use proc_macro2::{
+    Span, //
+};
+
+use syn::{
+    bracketed,
+    parse::{
+        Parse,
+        ParseStream, //
+    },
+    punctuated::Punctuated,
+    spanned::Spanned,
+    Ident,
+    LitInt,
+    Token,
+    Type, //
+};
+
+use crate::helpers::try_parse;
+
+pub(crate) struct ConfigfsAttrs {
+    container: Type,
+    data: Type,
+    child: Option<Type>,
+    attributes: Vec<(Ident, LitInt)>,
+}
+
+fn parse_attribute_field(stream: ParseStream<'_>) -> syn::Result<(Ident, LitInt)> {
+    let id = stream.parse::<syn::Ident>()?;
+    let _colon = stream.parse::<Token![:]>()?;
+    let v = stream.parse::<LitInt>()?;
+    Ok((id, v))
+}
+
+fn parse_named_field(stream: ParseStream<'_>, name: &str) -> syn::Result<Type> {
+    let name_id = stream.parse::<Ident>()?;
+    if name_id != name {
+        return Err(parse_field_error(name_id.span(), &name_id, name));
+    }
+    let _colon = stream.parse::<Token![:]>()?;
+    let v = stream.parse::<Type>()?;
+    stream.parse::<Token![,]>()?;
+    Ok(v)
+}
+
+fn parse_attributes(stream: ParseStream<'_>) -> syn::Result<Vec<(Ident, LitInt)>> {
+    let attribute_id = stream.parse::<Ident>()?;
+    if attribute_id != "attributes" {
+        return Err(syn::Error::new(
+            attribute_id.span(),
+            format!(
+                "unexpected identifier: {}, expected: 'attributes'",
+                attribute_id
+            ),
+        ));
+    }
+    stream.parse::<Token![:]>()?;
+    let attr_stream;
+    let _bracket = bracketed!(attr_stream in stream);
+    let attributes = Punctuated::<(Ident, LitInt), Token![,]>::parse_terminated_with(
+        &attr_stream,
+        parse_attribute_field,
+    )?;
+    let attributes = attributes.into_iter().collect::<Vec<_>>();
+
+    stream.parse::<Option<Token![,]>>()?;
+    Ok(attributes)
+}
+
+fn parse_field_error(span: Span, name: &Ident, expected_name: &str) -> syn::Error {
+    syn::Error::new(
+        span,
+        format!("Unexpected field name, got: {name} expected: {expected_name}"),
+    )
+}
+
+impl Parse for ConfigfsAttrs {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let container = try_parse(input, |s| parse_named_field(s, "container"))?;
+        let data = try_parse(input, |s| parse_named_field(s, "data"))?;
+        let child = try_parse(input, |s| parse_named_field(s, "child")).ok();
+        let attributes = parse_attributes(input)?;
+
+        Ok(ConfigfsAttrs {
+            container,
+            data,
+            child,
+            attributes,
+        })
+    }
+}
+
+fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
+    let raw_id = quote! { #ty }.to_string();
+
+    // Sanitizing syn::Type::Path, this is safe since it is
+    // only used as the identifier.
+    let normalized = raw_id
+        .split("::")
+        .map(|s| String::from(s.trim()))
+        .reduce(|a, b| format!("{a}_{b}"))
+        .expect("Cannot be empty")
+        .to_uppercase()
+        .replace(|c: char| !c.is_alphanumeric(), "_");
+
+    Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
+}
+
+pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
+    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
+
+    let data_tp_ident = make_static_ident(data_ty, "TPE");
+    let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");
+
+    let n = cfs_attrs.attributes.len() + 1;
+
+    let attr_list = quote! {
+        static #data_attr_ident: kernel::configfs::AttributeList<#n, #data_ty> =
+            // SAFETY: We are expanding `configfs_attrs`.
+            unsafe { kernel::configfs::AttributeList::new() };
+    };
+
+    let mut attrs = Vec::new();
+    for (attr_idx, (name, id)) in cfs_attrs.attributes.iter().enumerate() {
+        let name_with_attr = make_static_ident(name, "ATTR");
+
+        let id: u64 = match id.base10_parse::<u64>() {
+            Ok(v) => v,
+            Err(_) => {
+                return syn::Error::new(id.span(), "Could not parse attribute ID as a u64")
+                    .to_compile_error();
+            }
+        };
+
+        attrs.push(quote! {
+        static #name_with_attr: kernel::configfs::Attribute<#id, #data_ty, #data_ty> =
+            // SAFETY: We are expanding `configfs_attrs`.
+            unsafe {
+              kernel::configfs::Attribute::new(kernel::c_str!(::core::stringify!(#name)))
+            };
+
+          // SAFETY: By design of this macro, the name of the variable we
+          // invoke the `add` method on below, is not visible outside of
+          // the macro expansion. The macro does not operate concurrently
+          // on this variable, and thus we have exclusive access to the
+          // variable.
+          unsafe { #data_attr_ident.add::<#attr_idx, #id, _>(&#name_with_attr) }
+        });
+    }
+
+    let has_child_code = if let Some(child) = cfs_attrs.child {
+        quote! { new_with_child_ctor::<#n, #child>}
+    } else {
+        quote! { new::<#n> }
+    };
+
+    let data_type = quote! {
+        {
+            static #data_tp_ident:
+            kernel::configfs::ItemType<#container_ty, #data_ty> =
+                kernel::configfs::ItemType::<#container_ty, #data_ty>::#has_child_code(
+                    &THIS_MODULE, &#data_attr_ident
+                );
+            &#data_tp_ident
+        }
+    };
+
+    quote! {
+        {
+            #attr_list
+            #(#attrs)*
+            #data_type
+        }
+    }
+}
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index d18fbf4daa0a5..fdab8804e1ba9 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -4,6 +4,7 @@
 use quote::ToTokens;
 use syn::{
     parse::{
+        discouraged::Speculative,
         Parse,
         ParseStream, //
     },
@@ -54,6 +55,20 @@ pub(crate) fn file() -> String {
     }
 }
 
+pub(crate) fn try_parse<T>(
+    input: ParseStream<'_>,
+    parser: impl FnOnce(ParseStream<'_>) -> Result<T>,
+) -> Result<T> {
+    let fork = input.fork();
+    match parser(&fork) {
+        Ok(value) => {
+            input.advance_to(&fork);
+            Ok(value)
+        }
+        Err(e) => Err(e),
+    }
+}
+
 /// Obtain all `#[cfg]` attributes.
 pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
     attr.iter().filter(|a| a.path().is_ident("cfg"))
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 2cfd59e0f9e7c..745ba83c828b9 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -15,6 +15,8 @@
 #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
 
 mod concat_idents;
+#[cfg(CONFIG_CONFIGFS_FS)]
+mod configfs_attrs;
 mod export;
 mod fmt;
 mod helpers;
@@ -489,3 +491,86 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
         .unwrap_or_else(|e| e.into_compile_error())
         .into()
 }
+
+/// Define a list of configfs attributes statically.
+///
+/// # Examples
+///
+/// ```ignore
+/// let item_type = configfs_attrs! {
+///     container: configfs::Subsystem<Configuration>,
+///     data: Configuration,
+///     child: Child,
+///     attributes: [
+///         message: 0,
+///         bar: 1,
+///     ],
+/// };
+///```
+///
+/// Expands the following output:
+///    let item_type = {
+///         static CONFIGURATION_ATTR_LIST: kernel::configfs::AttributeList<
+///             3usize,
+///             Configuration,
+///         > = unsafe { kernel::configfs::AttributeList::new() };
+///         static MESSAGE_ATTR: kernel::configfs::Attribute<
+///             0u64,
+///             Configuration,
+///             Configuration,
+///         > = unsafe {
+///             kernel::configfs::Attribute::new({
+///                 const S: &str = "message\u{0}";
+///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
+///                     S.as_bytes(),
+///                 ) {
+///                     Ok(v) => v,
+///                     Err(_) => {
+///                         ::core::panicking::panic_fmt(
+///                             format_args!("string contains interior NUL"),
+///                         );
+///                     }
+///                 };
+///                 C
+///             })
+///         };
+///         unsafe { CONFIGURATION_ATTR_LIST.add::<0usize, 0u64, _>(&MESSAGE_ATTR) }
+///         static BAR_ATTR: kernel::configfs::Attribute<
+///             1u64,
+///             Configuration,
+///             Configuration,
+///         > = unsafe {
+///             kernel::configfs::Attribute::new({
+///                 const S: &str = "bar\u{0}";
+///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
+///                     S.as_bytes(),
+///                 ) {
+///                     Ok(v) => v,
+///                     Err(_) => {
+///                         ::core::panicking::panic_fmt(
+///                             format_args!("string contains interior NUL"),
+///                         );
+///                     }
+///                 };
+///                 C
+///             })
+///         };
+///         unsafe { CONFIGURATION_ATTR_LIST.add::<1usize, 1u64, _>(&BAR_ATTR) }
+///         {
+///             static CONFIGURATION_TPE: kernel::configfs::ItemType<
+///                 Subsystem<Configuration>,
+///                 Configuration,
+///             > = kernel::configfs::ItemType::<
+///                 Subsystem<Configuration>,
+///                 Configuration,
+///             >::new_with_child_ctor::<3usize, Child>(&THIS_MODULE, &CONFIGURATION_ATTR_LIST);
+///             &CONFIGURATION_TPE
+///         }
+///     };
+///
+#[cfg(CONFIG_CONFIGFS_FS)]
+#[proc_macro]
+pub fn configfs_attrs(input: TokenStream) -> TokenStream {
+    configfs_attrs::configfs_attrs(parse_macro_input!(input as configfs_attrs::ConfigfsAttrs))
+        .into()
+}
diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
index a1bd9db6010da..876462f7789d1 100644
--- a/samples/rust/rust_configfs.rs
+++ b/samples/rust/rust_configfs.rs
@@ -4,7 +4,7 @@
 
 use kernel::alloc::flags;
 use kernel::configfs;
-use kernel::configfs::configfs_attrs;
+use kernel::macros::configfs_attrs;
 use kernel::new_mutex;
 use kernel::page::PAGE_SIZE;
 use kernel::prelude::*;

---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260417-configfs-syn-191e07130027

Best regards,
-- 
Malte Wechter <maltewechter@gmail.com>
Re: [PATCH v2] rust: add procedural macro for declaring configfs attributes
Posted by Gary Guo 1 week, 1 day ago
On Wed Jun 3, 2026 at 3:08 PM BST, Malte Wechter wrote:
> Implement `configfs_attrs!` as a procedural macro using `syn`, this
> improves readability and maintainability. Remove the old macro and
> replace all uses with the new macro. Add the new macro implementation
> file to MAINTAINERS.
>
> Signed-off-by: Malte Wechter <maltewechter@gmail.com>
> ---
> Changes in v2:
> - Add a try_parse helper function to macros/helpers.rs
> - Fix bug where 'child' configuration gets dropped if trailing comma is missing (sashiko)
> - Link to v1: https://lore.kernel.org/r/20260520-configfs-syn-v1-1-6c5b80a9cef2@gmail.com
> ---
>  MAINTAINERS                     |   1 +
>  drivers/block/rnull/configfs.rs |   2 +-
>  rust/kernel/configfs.rs         | 251 ----------------------------------------
>  rust/macros/configfs_attrs.rs   | 182 +++++++++++++++++++++++++++++
>  rust/macros/helpers.rs          |  15 +++
>  rust/macros/lib.rs              |  85 ++++++++++++++
>  samples/rust/rust_configfs.rs   |   2 +-
>  7 files changed, 285 insertions(+), 253 deletions(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2fb1c75afd163..45f7a1ec93b45 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6464,6 +6464,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git config
>  F:	fs/configfs/
>  F:	include/linux/configfs.h
>  F:	rust/kernel/configfs.rs
> +F:	rust/macros/configfs_attrs.rs
>  F:	samples/configfs/
>  F:	samples/rust/rust_configfs.rs
>  
> diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
> index 7c2eb5c0b7228..f28ec69d79846 100644
> --- a/drivers/block/rnull/configfs.rs
> +++ b/drivers/block/rnull/configfs.rs
> @@ -4,8 +4,8 @@
>  use kernel::{
>      block::mq::gen_disk::{GenDisk, GenDiskBuilder},
>      configfs::{self, AttributeOperations},
> -    configfs_attrs,
>      fmt::{self, Write as _},
> +    macros::configfs_attrs,
>      new_mutex,
>      page::PAGE_SIZE,
>      prelude::*,
> diff --git a/rust/macros/configfs_attrs.rs b/rust/macros/configfs_attrs.rs
> new file mode 100644
> index 0000000000000..a7fc75cdebcc0
> --- /dev/null
> +++ b/rust/macros/configfs_attrs.rs
> @@ -0,0 +1,182 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use quote::{
> +    quote, //
> +    ToTokens,
> +};
> +
> +use proc_macro2::{
> +    Span, //
> +};
> +
> +use syn::{
> +    bracketed,
> +    parse::{
> +        Parse,
> +        ParseStream, //
> +    },
> +    punctuated::Punctuated,
> +    spanned::Spanned,
> +    Ident,
> +    LitInt,
> +    Token,
> +    Type, //
> +};
> +
> +use crate::helpers::try_parse;
> +
> +pub(crate) struct ConfigfsAttrs {
> +    container: Type,
> +    data: Type,
> +    child: Option<Type>,
> +    attributes: Vec<(Ident, LitInt)>,
> +}
> +
> +fn parse_attribute_field(stream: ParseStream<'_>) -> syn::Result<(Ident, LitInt)> {
> +    let id = stream.parse::<syn::Ident>()?;
> +    let _colon = stream.parse::<Token![:]>()?;
> +    let v = stream.parse::<LitInt>()?;
> +    Ok((id, v))
> +}
> +
> +fn parse_named_field(stream: ParseStream<'_>, name: &str) -> syn::Result<Type> {
> +    let name_id = stream.parse::<Ident>()?;
> +    if name_id != name {
> +        return Err(parse_field_error(name_id.span(), &name_id, name));
> +    }
> +    let _colon = stream.parse::<Token![:]>()?;
> +    let v = stream.parse::<Type>()?;
> +    stream.parse::<Token![,]>()?;
> +    Ok(v)
> +}
> +
> +fn parse_attributes(stream: ParseStream<'_>) -> syn::Result<Vec<(Ident, LitInt)>> {
> +    let attribute_id = stream.parse::<Ident>()?;
> +    if attribute_id != "attributes" {
> +        return Err(syn::Error::new(
> +            attribute_id.span(),
> +            format!(
> +                "unexpected identifier: {}, expected: 'attributes'",
> +                attribute_id
> +            ),
> +        ));
> +    }
> +    stream.parse::<Token![:]>()?;
> +    let attr_stream;
> +    let _bracket = bracketed!(attr_stream in stream);
> +    let attributes = Punctuated::<(Ident, LitInt), Token![,]>::parse_terminated_with(
> +        &attr_stream,
> +        parse_attribute_field,
> +    )?;
> +    let attributes = attributes.into_iter().collect::<Vec<_>>();
> +
> +    stream.parse::<Option<Token![,]>>()?;
> +    Ok(attributes)
> +}
> +
> +fn parse_field_error(span: Span, name: &Ident, expected_name: &str) -> syn::Error {
> +    syn::Error::new(
> +        span,
> +        format!("Unexpected field name, got: {name} expected: {expected_name}"),
> +    )
> +}
> +
> +impl Parse for ConfigfsAttrs {
> +    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
> +        let container = try_parse(input, |s| parse_named_field(s, "container"))?;
> +        let data = try_parse(input, |s| parse_named_field(s, "data"))?;
> +        let child = try_parse(input, |s| parse_named_field(s, "child")).ok();
> +        let attributes = parse_attributes(input)?;

I have a `parse_ordered_fields!()` macro in module.rs, please extract it to
helpers and use it instead of implementing the parsing in an ad-hoc manner.

> +
> +        Ok(ConfigfsAttrs {
> +            container,
> +            data,
> +            child,
> +            attributes,
> +        })
> +    }
> +}
> +
> +fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
> +    let raw_id = quote! { #ty }.to_string();
> +
> +    // Sanitizing syn::Type::Path, this is safe since it is
> +    // only used as the identifier.
> +    let normalized = raw_id
> +        .split("::")
> +        .map(|s| String::from(s.trim()))
> +        .reduce(|a, b| format!("{a}_{b}"))
> +        .expect("Cannot be empty")
> +        .to_uppercase()
> +        .replace(|c: char| !c.is_alphanumeric(), "_");
> +
> +    Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
> +}
> +
> +pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
> +    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
> +
> +    let data_tp_ident = make_static_ident(data_ty, "TPE");
> +    let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");

Instead of creating identifers like this, just scope them properly so that
a fixed identifier is used without colliding.

> +
> +    let n = cfs_attrs.attributes.len() + 1;
> +
> +    let attr_list = quote! {
> +        static #data_attr_ident: kernel::configfs::AttributeList<#n, #data_ty> =
> +            // SAFETY: We are expanding `configfs_attrs`.
> +            unsafe { kernel::configfs::AttributeList::new() };
> +    };
> +
> +    let mut attrs = Vec::new();
> +    for (attr_idx, (name, id)) in cfs_attrs.attributes.iter().enumerate() {
> +        let name_with_attr = make_static_ident(name, "ATTR");
> +
> +        let id: u64 = match id.base10_parse::<u64>() {
> +            Ok(v) => v,
> +            Err(_) => {
> +                return syn::Error::new(id.span(), "Could not parse attribute ID as a u64")
> +                    .to_compile_error();
> +            }
> +        };

Why parsing? The ID looks like it's just substituted as is.

Best,
Gary

> +
> +        attrs.push(quote! {
> +        static #name_with_attr: kernel::configfs::Attribute<#id, #data_ty, #data_ty> =
> +            // SAFETY: We are expanding `configfs_attrs`.
> +            unsafe {
> +              kernel::configfs::Attribute::new(kernel::c_str!(::core::stringify!(#name)))
> +            };
> +
> +          // SAFETY: By design of this macro, the name of the variable we
> +          // invoke the `add` method on below, is not visible outside of
> +          // the macro expansion. The macro does not operate concurrently
> +          // on this variable, and thus we have exclusive access to the
> +          // variable.
> +          unsafe { #data_attr_ident.add::<#attr_idx, #id, _>(&#name_with_attr) }
> +        });
> +    }
> +
> +    let has_child_code = if let Some(child) = cfs_attrs.child {
> +        quote! { new_with_child_ctor::<#n, #child>}
> +    } else {
> +        quote! { new::<#n> }
> +    };
> +
> +    let data_type = quote! {
> +        {
> +            static #data_tp_ident:
> +            kernel::configfs::ItemType<#container_ty, #data_ty> =
> +                kernel::configfs::ItemType::<#container_ty, #data_ty>::#has_child_code(
> +                    &THIS_MODULE, &#data_attr_ident
> +                );
> +            &#data_tp_ident
> +        }
> +    };
> +
> +    quote! {
> +        {
> +            #attr_list
> +            #(#attrs)*
> +            #data_type
> +        }
> +    }
> +}
> diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
> index d18fbf4daa0a5..fdab8804e1ba9 100644
> --- a/rust/macros/helpers.rs
> +++ b/rust/macros/helpers.rs
> @@ -4,6 +4,7 @@
>  use quote::ToTokens;
>  use syn::{
>      parse::{
> +        discouraged::Speculative,
>          Parse,
>          ParseStream, //
>      },
> @@ -54,6 +55,20 @@ pub(crate) fn file() -> String {
>      }
>  }
>  
> +pub(crate) fn try_parse<T>(
> +    input: ParseStream<'_>,
> +    parser: impl FnOnce(ParseStream<'_>) -> Result<T>,
> +) -> Result<T> {
> +    let fork = input.fork();
> +    match parser(&fork) {
> +        Ok(value) => {
> +            input.advance_to(&fork);
> +            Ok(value)
> +        }
> +        Err(e) => Err(e),
> +    }
> +}
> +
>  /// Obtain all `#[cfg]` attributes.
>  pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
>      attr.iter().filter(|a| a.path().is_ident("cfg"))
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 2cfd59e0f9e7c..745ba83c828b9 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -15,6 +15,8 @@
>  #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
>  
>  mod concat_idents;
> +#[cfg(CONFIG_CONFIGFS_FS)]
> +mod configfs_attrs;
>  mod export;
>  mod fmt;
>  mod helpers;
> @@ -489,3 +491,86 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
>          .unwrap_or_else(|e| e.into_compile_error())
>          .into()
>  }
> +
> +/// Define a list of configfs attributes statically.
> +///
> +/// # Examples
> +///
> +/// ```ignore
> +/// let item_type = configfs_attrs! {
> +///     container: configfs::Subsystem<Configuration>,
> +///     data: Configuration,
> +///     child: Child,
> +///     attributes: [
> +///         message: 0,
> +///         bar: 1,
> +///     ],
> +/// };
> +///```
> +///
> +/// Expands the following output:
> +///    let item_type = {
> +///         static CONFIGURATION_ATTR_LIST: kernel::configfs::AttributeList<
> +///             3usize,
> +///             Configuration,
> +///         > = unsafe { kernel::configfs::AttributeList::new() };
> +///         static MESSAGE_ATTR: kernel::configfs::Attribute<
> +///             0u64,
> +///             Configuration,
> +///             Configuration,
> +///         > = unsafe {
> +///             kernel::configfs::Attribute::new({
> +///                 const S: &str = "message\u{0}";
> +///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
> +///                     S.as_bytes(),
> +///                 ) {
> +///                     Ok(v) => v,
> +///                     Err(_) => {
> +///                         ::core::panicking::panic_fmt(
> +///                             format_args!("string contains interior NUL"),
> +///                         );
> +///                     }
> +///                 };
> +///                 C
> +///             })
> +///         };
> +///         unsafe { CONFIGURATION_ATTR_LIST.add::<0usize, 0u64, _>(&MESSAGE_ATTR) }
> +///         static BAR_ATTR: kernel::configfs::Attribute<
> +///             1u64,
> +///             Configuration,
> +///             Configuration,
> +///         > = unsafe {
> +///             kernel::configfs::Attribute::new({
> +///                 const S: &str = "bar\u{0}";
> +///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
> +///                     S.as_bytes(),
> +///                 ) {
> +///                     Ok(v) => v,
> +///                     Err(_) => {
> +///                         ::core::panicking::panic_fmt(
> +///                             format_args!("string contains interior NUL"),
> +///                         );
> +///                     }
> +///                 };
> +///                 C
> +///             })
> +///         };
> +///         unsafe { CONFIGURATION_ATTR_LIST.add::<1usize, 1u64, _>(&BAR_ATTR) }
> +///         {
> +///             static CONFIGURATION_TPE: kernel::configfs::ItemType<
> +///                 Subsystem<Configuration>,
> +///                 Configuration,
> +///             > = kernel::configfs::ItemType::<
> +///                 Subsystem<Configuration>,
> +///                 Configuration,
> +///             >::new_with_child_ctor::<3usize, Child>(&THIS_MODULE, &CONFIGURATION_ATTR_LIST);
> +///             &CONFIGURATION_TPE
> +///         }
> +///     };
> +///
> +#[cfg(CONFIG_CONFIGFS_FS)]
> +#[proc_macro]
> +pub fn configfs_attrs(input: TokenStream) -> TokenStream {
> +    configfs_attrs::configfs_attrs(parse_macro_input!(input as configfs_attrs::ConfigfsAttrs))
> +        .into()
> +}
> diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
> index a1bd9db6010da..876462f7789d1 100644
> --- a/samples/rust/rust_configfs.rs
> +++ b/samples/rust/rust_configfs.rs
> @@ -4,7 +4,7 @@
>  
>  use kernel::alloc::flags;
>  use kernel::configfs;
> -use kernel::configfs::configfs_attrs;
> +use kernel::macros::configfs_attrs;
>  use kernel::new_mutex;
>  use kernel::page::PAGE_SIZE;
>  use kernel::prelude::*;
>
> ---
> base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
> change-id: 20260417-configfs-syn-191e07130027
>
> Best regards,
Re: [PATCH v2] rust: add procedural macro for declaring configfs attributes
Posted by Malte Wechter 1 day, 10 hours ago
On 6/3/26 4:32 PM, Gary Guo wrote:
> On Wed Jun 3, 2026 at 3:08 PM BST, Malte Wechter wrote:
>> Implement `configfs_attrs!` as a procedural macro using `syn`, this
>> improves readability and maintainability. Remove the old macro and
>> replace all uses with the new macro. Add the new macro implementation
>> file to MAINTAINERS.
>>
>> Signed-off-by: Malte Wechter <maltewechter@gmail.com>
>> ---
>> Changes in v2:
>> - Add a try_parse helper function to macros/helpers.rs
>> - Fix bug where 'child' configuration gets dropped if trailing comma is missing (sashiko)
>> - Link to v1: https://lore.kernel.org/r/20260520-configfs-syn-v1-1-6c5b80a9cef2@gmail.com
>> ---
>>   MAINTAINERS                     |   1 +
>>   drivers/block/rnull/configfs.rs |   2 +-
>>   rust/kernel/configfs.rs         | 251 ----------------------------------------
>>   rust/macros/configfs_attrs.rs   | 182 +++++++++++++++++++++++++++++
>>   rust/macros/helpers.rs          |  15 +++
>>   rust/macros/lib.rs              |  85 ++++++++++++++
>>   samples/rust/rust_configfs.rs   |   2 +-
>>   7 files changed, 285 insertions(+), 253 deletions(-)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2fb1c75afd163..45f7a1ec93b45 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -6464,6 +6464,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git config
>>   F:	fs/configfs/
>>   F:	include/linux/configfs.h
>>   F:	rust/kernel/configfs.rs
>> +F:	rust/macros/configfs_attrs.rs
>>   F:	samples/configfs/
>>   F:	samples/rust/rust_configfs.rs
>>   
>> diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
>> index 7c2eb5c0b7228..f28ec69d79846 100644
>> --- a/drivers/block/rnull/configfs.rs
>> +++ b/drivers/block/rnull/configfs.rs
>> @@ -4,8 +4,8 @@
>>   use kernel::{
>>       block::mq::gen_disk::{GenDisk, GenDiskBuilder},
>>       configfs::{self, AttributeOperations},
>> -    configfs_attrs,
>>       fmt::{self, Write as _},
>> +    macros::configfs_attrs,
>>       new_mutex,
>>       page::PAGE_SIZE,
>>       prelude::*,
>> diff --git a/rust/macros/configfs_attrs.rs b/rust/macros/configfs_attrs.rs
>> new file mode 100644
>> index 0000000000000..a7fc75cdebcc0
>> --- /dev/null
>> +++ b/rust/macros/configfs_attrs.rs
>> @@ -0,0 +1,182 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +use quote::{
>> +    quote, //
>> +    ToTokens,
>> +};
>> +
>> +use proc_macro2::{
>> +    Span, //
>> +};
>> +
>> +use syn::{
>> +    bracketed,
>> +    parse::{
>> +        Parse,
>> +        ParseStream, //
>> +    },
>> +    punctuated::Punctuated,
>> +    spanned::Spanned,
>> +    Ident,
>> +    LitInt,
>> +    Token,
>> +    Type, //
>> +};
>> +
>> +use crate::helpers::try_parse;
>> +
>> +pub(crate) struct ConfigfsAttrs {
>> +    container: Type,
>> +    data: Type,
>> +    child: Option<Type>,
>> +    attributes: Vec<(Ident, LitInt)>,
>> +}
>> +
>> +fn parse_attribute_field(stream: ParseStream<'_>) -> syn::Result<(Ident, LitInt)> {
>> +    let id = stream.parse::<syn::Ident>()?;
>> +    let _colon = stream.parse::<Token![:]>()?;
>> +    let v = stream.parse::<LitInt>()?;
>> +    Ok((id, v))
>> +}
>> +
>> +fn parse_named_field(stream: ParseStream<'_>, name: &str) -> syn::Result<Type> {
>> +    let name_id = stream.parse::<Ident>()?;
>> +    if name_id != name {
>> +        return Err(parse_field_error(name_id.span(), &name_id, name));
>> +    }
>> +    let _colon = stream.parse::<Token![:]>()?;
>> +    let v = stream.parse::<Type>()?;
>> +    stream.parse::<Token![,]>()?;
>> +    Ok(v)
>> +}
>> +
>> +fn parse_attributes(stream: ParseStream<'_>) -> syn::Result<Vec<(Ident, LitInt)>> {
>> +    let attribute_id = stream.parse::<Ident>()?;
>> +    if attribute_id != "attributes" {
>> +        return Err(syn::Error::new(
>> +            attribute_id.span(),
>> +            format!(
>> +                "unexpected identifier: {}, expected: 'attributes'",
>> +                attribute_id
>> +            ),
>> +        ));
>> +    }
>> +    stream.parse::<Token![:]>()?;
>> +    let attr_stream;
>> +    let _bracket = bracketed!(attr_stream in stream);
>> +    let attributes = Punctuated::<(Ident, LitInt), Token![,]>::parse_terminated_with(
>> +        &attr_stream,
>> +        parse_attribute_field,
>> +    )?;
>> +    let attributes = attributes.into_iter().collect::<Vec<_>>();
>> +
>> +    stream.parse::<Option<Token![,]>>()?;
>> +    Ok(attributes)
>> +}
>> +
>> +fn parse_field_error(span: Span, name: &Ident, expected_name: &str) -> syn::Error {
>> +    syn::Error::new(
>> +        span,
>> +        format!("Unexpected field name, got: {name} expected: {expected_name}"),
>> +    )
>> +}
>> +
>> +impl Parse for ConfigfsAttrs {
>> +    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
>> +        let container = try_parse(input, |s| parse_named_field(s, "container"))?;
>> +        let data = try_parse(input, |s| parse_named_field(s, "data"))?;
>> +        let child = try_parse(input, |s| parse_named_field(s, "child")).ok();
>> +        let attributes = parse_attributes(input)?;
> I have a `parse_ordered_fields!()` macro in module.rs, please extract it to
> helpers and use it instead of implementing the parsing in an ad-hoc manner.

That is a good idea.

>> +
>> +        Ok(ConfigfsAttrs {
>> +            container,
>> +            data,
>> +            child,
>> +            attributes,
>> +        })
>> +    }
>> +}
>> +
>> +fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
>> +    let raw_id = quote! { #ty }.to_string();
>> +
>> +    // Sanitizing syn::Type::Path, this is safe since it is
>> +    // only used as the identifier.
>> +    let normalized = raw_id
>> +        .split("::")
>> +        .map(|s| String::from(s.trim()))
>> +        .reduce(|a, b| format!("{a}_{b}"))
>> +        .expect("Cannot be empty")
>> +        .to_uppercase()
>> +        .replace(|c: char| !c.is_alphanumeric(), "_");
>> +
>> +    Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
>> +}
>> +
>> +pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
>> +    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
>> +
>> +    let data_tp_ident = make_static_ident(data_ty, "TPE");
>> +    let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");
> Instead of creating identifers like this, just scope them properly so that
> a fixed identifier is used without colliding.

I believe it is nice to keep the identifiers unique, especially if you
have many fields
you can easily differentiate them.

>> +
>> +    let n = cfs_attrs.attributes.len() + 1;
>> +
>> +    let attr_list = quote! {
>> +        static #data_attr_ident: kernel::configfs::AttributeList<#n, #data_ty> =
>> +            // SAFETY: We are expanding `configfs_attrs`.
>> +            unsafe { kernel::configfs::AttributeList::new() };
>> +    };
>> +
>> +    let mut attrs = Vec::new();
>> +    for (attr_idx, (name, id)) in cfs_attrs.attributes.iter().enumerate() {
>> +        let name_with_attr = make_static_ident(name, "ATTR");
>> +
>> +        let id: u64 = match id.base10_parse::<u64>() {
>> +            Ok(v) => v,
>> +            Err(_) => {
>> +                return syn::Error::new(id.span(), "Could not parse attribute ID as a u64")
>> +                    .to_compile_error();
>> +            }
>> +        };
> Why parsing? The ID looks like it's just substituted as is.

For correctness, if you pass a negative integer literal it will panic
without this parsing.
This is not likely, but it provides a nice error message if this were to happen.
I can maybe move it to the parsing logic instead.

>
> Best,
> Gary
>
>> +
>> +        attrs.push(quote! {
>> +        static #name_with_attr: kernel::configfs::Attribute<#id, #data_ty, #data_ty> =
>> +            // SAFETY: We are expanding `configfs_attrs`.
>> +            unsafe {
>> +              kernel::configfs::Attribute::new(kernel::c_str!(::core::stringify!(#name)))
>> +            };
>> +
>> +          // SAFETY: By design of this macro, the name of the variable we
>> +          // invoke the `add` method on below, is not visible outside of
>> +          // the macro expansion. The macro does not operate concurrently
>> +          // on this variable, and thus we have exclusive access to the
>> +          // variable.
>> +          unsafe { #data_attr_ident.add::<#attr_idx, #id, _>(&#name_with_attr) }
>> +        });
>> +    }
>> +
>> +    let has_child_code = if let Some(child) = cfs_attrs.child {
>> +        quote! { new_with_child_ctor::<#n, #child>}
>> +    } else {
>> +        quote! { new::<#n> }
>> +    };
>> +
>> +    let data_type = quote! {
>> +        {
>> +            static #data_tp_ident:
>> +            kernel::configfs::ItemType<#container_ty, #data_ty> =
>> +                kernel::configfs::ItemType::<#container_ty, #data_ty>::#has_child_code(
>> +                    &THIS_MODULE, &#data_attr_ident
>> +                );
>> +            &#data_tp_ident
>> +        }
>> +    };
>> +
>> +    quote! {
>> +        {
>> +            #attr_list
>> +            #(#attrs)*
>> +            #data_type
>> +        }
>> +    }
>> +}
>> diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
>> index d18fbf4daa0a5..fdab8804e1ba9 100644
>> --- a/rust/macros/helpers.rs
>> +++ b/rust/macros/helpers.rs
>> @@ -4,6 +4,7 @@
>>   use quote::ToTokens;
>>   use syn::{
>>       parse::{
>> +        discouraged::Speculative,
>>           Parse,
>>           ParseStream, //
>>       },
>> @@ -54,6 +55,20 @@ pub(crate) fn file() -> String {
>>       }
>>   }
>>   
>> +pub(crate) fn try_parse<T>(
>> +    input: ParseStream<'_>,
>> +    parser: impl FnOnce(ParseStream<'_>) -> Result<T>,
>> +) -> Result<T> {
>> +    let fork = input.fork();
>> +    match parser(&fork) {
>> +        Ok(value) => {
>> +            input.advance_to(&fork);
>> +            Ok(value)
>> +        }
>> +        Err(e) => Err(e),
>> +    }
>> +}
>> +
>>   /// Obtain all `#[cfg]` attributes.
>>   pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
>>       attr.iter().filter(|a| a.path().is_ident("cfg"))
>> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
>> index 2cfd59e0f9e7c..745ba83c828b9 100644
>> --- a/rust/macros/lib.rs
>> +++ b/rust/macros/lib.rs
>> @@ -15,6 +15,8 @@
>>   #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
>>   
>>   mod concat_idents;
>> +#[cfg(CONFIG_CONFIGFS_FS)]
>> +mod configfs_attrs;
>>   mod export;
>>   mod fmt;
>>   mod helpers;
>> @@ -489,3 +491,86 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
>>           .unwrap_or_else(|e| e.into_compile_error())
>>           .into()
>>   }
>> +
>> +/// Define a list of configfs attributes statically.
>> +///
>> +/// # Examples
>> +///
>> +/// ```ignore
>> +/// let item_type = configfs_attrs! {
>> +///     container: configfs::Subsystem<Configuration>,
>> +///     data: Configuration,
>> +///     child: Child,
>> +///     attributes: [
>> +///         message: 0,
>> +///         bar: 1,
>> +///     ],
>> +/// };
>> +///```
>> +///
>> +/// Expands the following output:
>> +///    let item_type = {
>> +///         static CONFIGURATION_ATTR_LIST: kernel::configfs::AttributeList<
>> +///             3usize,
>> +///             Configuration,
>> +///         > = unsafe { kernel::configfs::AttributeList::new() };
>> +///         static MESSAGE_ATTR: kernel::configfs::Attribute<
>> +///             0u64,
>> +///             Configuration,
>> +///             Configuration,
>> +///         > = unsafe {
>> +///             kernel::configfs::Attribute::new({
>> +///                 const S: &str = "message\u{0}";
>> +///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
>> +///                     S.as_bytes(),
>> +///                 ) {
>> +///                     Ok(v) => v,
>> +///                     Err(_) => {
>> +///                         ::core::panicking::panic_fmt(
>> +///                             format_args!("string contains interior NUL"),
>> +///                         );
>> +///                     }
>> +///                 };
>> +///                 C
>> +///             })
>> +///         };
>> +///         unsafe { CONFIGURATION_ATTR_LIST.add::<0usize, 0u64, _>(&MESSAGE_ATTR) }
>> +///         static BAR_ATTR: kernel::configfs::Attribute<
>> +///             1u64,
>> +///             Configuration,
>> +///             Configuration,
>> +///         > = unsafe {
>> +///             kernel::configfs::Attribute::new({
>> +///                 const S: &str = "bar\u{0}";
>> +///                 const C: &kernel::str::CStr = match kernel::str::CStr::from_bytes_with_nul(
>> +///                     S.as_bytes(),
>> +///                 ) {
>> +///                     Ok(v) => v,
>> +///                     Err(_) => {
>> +///                         ::core::panicking::panic_fmt(
>> +///                             format_args!("string contains interior NUL"),
>> +///                         );
>> +///                     }
>> +///                 };
>> +///                 C
>> +///             })
>> +///         };
>> +///         unsafe { CONFIGURATION_ATTR_LIST.add::<1usize, 1u64, _>(&BAR_ATTR) }
>> +///         {
>> +///             static CONFIGURATION_TPE: kernel::configfs::ItemType<
>> +///                 Subsystem<Configuration>,
>> +///                 Configuration,
>> +///             > = kernel::configfs::ItemType::<
>> +///                 Subsystem<Configuration>,
>> +///                 Configuration,
>> +///             >::new_with_child_ctor::<3usize, Child>(&THIS_MODULE, &CONFIGURATION_ATTR_LIST);
>> +///             &CONFIGURATION_TPE
>> +///         }
>> +///     };
>> +///
>> +#[cfg(CONFIG_CONFIGFS_FS)]
>> +#[proc_macro]
>> +pub fn configfs_attrs(input: TokenStream) -> TokenStream {
>> +    configfs_attrs::configfs_attrs(parse_macro_input!(input as configfs_attrs::ConfigfsAttrs))
>> +        .into()
>> +}
>> diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
>> index a1bd9db6010da..876462f7789d1 100644
>> --- a/samples/rust/rust_configfs.rs
>> +++ b/samples/rust/rust_configfs.rs
>> @@ -4,7 +4,7 @@
>>   
>>   use kernel::alloc::flags;
>>   use kernel::configfs;
>> -use kernel::configfs::configfs_attrs;
>> +use kernel::macros::configfs_attrs;
>>   use kernel::new_mutex;
>>   use kernel::page::PAGE_SIZE;
>>   use kernel::prelude::*;
>>
>> ---
>> base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
>> change-id: 20260417-configfs-syn-191e07130027
>>
>> Best regards,
Best regards
>
Re: [PATCH v2] rust: add procedural macro for declaring configfs attributes
Posted by Gary Guo 1 day, 9 hours ago
On Wed Jun 10, 2026 at 10:15 AM BST, Malte Wechter wrote:
>>> +
>>> +        Ok(ConfigfsAttrs {
>>> +            container,
>>> +            data,
>>> +            child,
>>> +            attributes,
>>> +        })
>>> +    }
>>> +}
>>> +
>>> +fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
>>> +    let raw_id = quote! { #ty }.to_string();
>>> +
>>> +    // Sanitizing syn::Type::Path, this is safe since it is
>>> +    // only used as the identifier.
>>> +    let normalized = raw_id
>>> +        .split("::")
>>> +        .map(|s| String::from(s.trim()))
>>> +        .reduce(|a, b| format!("{a}_{b}"))
>>> +        .expect("Cannot be empty")
>>> +        .to_uppercase()
>>> +        .replace(|c: char| !c.is_alphanumeric(), "_");
>>> +
>>> +    Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
>>> +}
>>> +
>>> +pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
>>> +    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
>>> +
>>> +    let data_tp_ident = make_static_ident(data_ty, "TPE");
>>> +    let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");
>> Instead of creating identifers like this, just scope them properly so that
>> a fixed identifier is used without colliding.
>
> I believe it is nice to keep the identifiers unique, especially if you
> have many fields
> you can easily differentiate them.

What make_static_ident is doing I think is quite hacky. I'd rather not having
them. Plus, the identifiers are not seen by users anyway.

Best,
Gary
Re: [PATCH v2] rust: add procedural macro for declaring configfs attributes
Posted by Malte Wechter 1 day, 5 hours ago
On 6/10/26 12:05 PM, Gary Guo wrote:
> On Wed Jun 10, 2026 at 10:15 AM BST, Malte Wechter wrote:
>>>> +
>>>> +        Ok(ConfigfsAttrs {
>>>> +            container,
>>>> +            data,
>>>> +            child,
>>>> +            attributes,
>>>> +        })
>>>> +    }
>>>> +}
>>>> +
>>>> +fn make_static_ident<T: ToTokens>(ty: &T, suffix: &str) -> syn::Ident {
>>>> +    let raw_id = quote! { #ty }.to_string();
>>>> +
>>>> +    // Sanitizing syn::Type::Path, this is safe since it is
>>>> +    // only used as the identifier.
>>>> +    let normalized = raw_id
>>>> +        .split("::")
>>>> +        .map(|s| String::from(s.trim()))
>>>> +        .reduce(|a, b| format!("{a}_{b}"))
>>>> +        .expect("Cannot be empty")
>>>> +        .to_uppercase()
>>>> +        .replace(|c: char| !c.is_alphanumeric(), "_");
>>>> +
>>>> +    Ident::new(&format!("{}_{}", normalized, suffix), ty.span())
>>>> +}
>>>> +
>>>> +pub(crate) fn configfs_attrs(cfs_attrs: ConfigfsAttrs) -> proc_macro2::TokenStream {
>>>> +    let (container_ty, data_ty) = (&cfs_attrs.container, &cfs_attrs.data);
>>>> +
>>>> +    let data_tp_ident = make_static_ident(data_ty, "TPE");
>>>> +    let data_attr_ident = make_static_ident(data_ty, "ATTR_LIST");
>>> Instead of creating identifers like this, just scope them properly so that
>>> a fixed identifier is used without colliding.
>> I believe it is nice to keep the identifiers unique, especially if you
>> have many fields
>> you can easily differentiate them.
> What make_static_ident is doing I think is quite hacky. I'd rather not having
> them. Plus, the identifiers are not seen by users anyway.
>
> Best,
> Gary

I will simplify the naming of the static variables greatly (removing 
make_static_ident), but still generate unique identifiers.

Best regards,

Malte