[PATCH v2 05/83] block: rnull: add macros to define configfs attributes

From: Andreas Hindborg

Date: Tue Jun 09 2026 - 15:12:28 EST


Defining configfs attributes in rust is a bit verbose at the moment. Add
some macros to make the attribute definition less verbose.

The configfs Rust abstractions should eventually provide procedural macros
for this task. When we get more users of the configfs Rust abstractions, we
shall consider this task.

Signed-off-by: Andreas Hindborg <a.hindborg@xxxxxxxxxx>
---
drivers/block/rnull/configfs.rs | 134 +++++++++---------------------
drivers/block/rnull/configfs/macros.rs | 143 +++++++++++++++++++++++++++++++++
2 files changed, 179 insertions(+), 98 deletions(-)

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index b165347e9413..fd309fc17e66 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -1,18 +1,39 @@
// SPDX-License-Identifier: GPL-2.0

-use super::{NullBlkDevice, THIS_MODULE};
+use super::{
+ NullBlkDevice,
+ THIS_MODULE, //
+};
use kernel::{
- block::mq::gen_disk::{GenDisk, GenDiskBuilder},
- configfs::{self, AttributeOperations},
+ block::mq::gen_disk::{
+ GenDisk,
+ GenDiskBuilder, //
+ },
+ configfs::{
+ self,
+ AttributeOperations, //
+ },
configfs_attrs,
- fmt::{self, Write as _},
+ fmt::{
+ self,
+ Write as _, //
+ },
new_mutex,
page::PAGE_SIZE,
prelude::*,
- str::{kstrtobool_bytes, CString},
- sync::Mutex,
+ str::{
+ kstrtobool_bytes,
+ CString, //
+ },
+ sync::Mutex, //
+};
+use macros::{
+ configfs_simple_bool_field,
+ configfs_simple_field, //
};

+mod macros;
+
pub(crate) fn subsystem() -> impl PinInit<kernel::configfs::Subsystem<Config>, Error> {
let item_type = configfs_attrs! {
container: configfs::Subsystem<Config>,
@@ -164,99 +185,16 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
}
}

-#[vtable]
-impl configfs::AttributeOperations<1> for DeviceConfig {
- type Data = DeviceConfig;
-
- fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
- let mut writer = kernel::str::Formatter::new(page);
- writer.write_fmt(fmt!("{}\n", this.data.lock().block_size))?;
- Ok(writer.bytes_written())
- }
-
- fn store(this: &DeviceConfig, page: &[u8]) -> Result {
- if this.data.lock().powered {
- return Err(EBUSY);
- }
-
- let text = core::str::from_utf8(page)?.trim();
- let value = text.parse::<u32>().map_err(|_| EINVAL)?;
-
- GenDiskBuilder::validate_block_size(value)?;
- this.data.lock().block_size = value;
- Ok(())
- }
-}
-
-#[vtable]
-impl configfs::AttributeOperations<2> for DeviceConfig {
- type Data = DeviceConfig;
-
- fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
- let mut writer = kernel::str::Formatter::new(page);
-
- if this.data.lock().rotational {
- writer.write_str("1\n")?;
- } else {
- writer.write_str("0\n")?;
- }
-
- Ok(writer.bytes_written())
- }
-
- fn store(this: &DeviceConfig, page: &[u8]) -> Result {
- if this.data.lock().powered {
- return Err(EBUSY);
- }
-
- this.data.lock().rotational = kstrtobool_bytes(page)?;
-
- Ok(())
- }
-}
-
-#[vtable]
-impl configfs::AttributeOperations<3> for DeviceConfig {
- type Data = DeviceConfig;
-
- fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
- let mut writer = kernel::str::Formatter::new(page);
- writer.write_fmt(fmt!("{}\n", this.data.lock().capacity_mib))?;
- Ok(writer.bytes_written())
- }
-
- fn store(this: &DeviceConfig, page: &[u8]) -> Result {
- if this.data.lock().powered {
- return Err(EBUSY);
- }
-
- let text = core::str::from_utf8(page)?.trim();
- let value = text.parse::<u64>().map_err(|_| EINVAL)?;
-
- this.data.lock().capacity_mib = value;
- Ok(())
- }
-}
-
-#[vtable]
-impl configfs::AttributeOperations<4> for DeviceConfig {
- type Data = DeviceConfig;
-
- fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
- let mut writer = kernel::str::Formatter::new(page);
- writer.write_fmt(fmt!("{}\n", this.data.lock().irq_mode))?;
- Ok(writer.bytes_written())
- }
+configfs_simple_field!(DeviceConfig, 1, block_size, u32, check GenDiskBuilder::validate_block_size);
+configfs_simple_bool_field!(DeviceConfig, 2, rotational);
+configfs_simple_field!(DeviceConfig, 3, capacity_mib, u64);
+configfs_simple_field!(DeviceConfig, 4, irq_mode, IRQMode);

- fn store(this: &DeviceConfig, page: &[u8]) -> Result {
- if this.data.lock().powered {
- return Err(EBUSY);
- }
+impl core::str::FromStr for IRQMode {
+ type Err = Error;

- let text = core::str::from_utf8(page)?.trim();
- let value = text.parse::<u8>().map_err(|_| EINVAL)?;
-
- this.data.lock().irq_mode = IRQMode::try_from(value)?;
- Ok(())
+ fn from_str(s: &str) -> Result<Self> {
+ let value: u8 = s.parse().map_err(|_| EINVAL)?;
+ value.try_into()
}
}
diff --git a/drivers/block/rnull/configfs/macros.rs b/drivers/block/rnull/configfs/macros.rs
new file mode 100644
index 000000000000..30bb32238457
--- /dev/null
+++ b/drivers/block/rnull/configfs/macros.rs
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::{
+ DeviceConfig,
+ DeviceConfigInner, //
+};
+use core::str::FromStr;
+use kernel::{
+ fmt::{
+ self,
+ Write, //
+ },
+ page::PAGE_SIZE,
+ prelude::*,
+};
+
+pub(crate) fn show_field<T: fmt::Display>(value: T, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+ let mut writer = kernel::str::Formatter::new(page);
+ writer.write_fmt(fmt!("{}\n", value))?;
+ Ok(writer.bytes_written())
+}
+
+// The lock guard is passed to `store_fn` so the powered check and the
+// store happen atomically. Releasing the lock between the two would
+// allow another writer to power the device on in the gap.
+pub(crate) fn store_with_power_check<F>(this: &DeviceConfig, page: &[u8], store_fn: F) -> Result
+where
+ F: FnOnce(&mut DeviceConfigInner, &[u8]) -> Result,
+{
+ let mut guard = this.data.lock();
+ if guard.powered {
+ return Err(EBUSY);
+ }
+ store_fn(&mut guard, page)
+}
+
+pub(crate) fn store_number_with_power_check<F, T>(
+ this: &DeviceConfig,
+ page: &[u8],
+ store_fn: F,
+) -> Result
+where
+ F: FnOnce(&mut DeviceConfigInner, T) -> Result,
+ T: FromStr,
+{
+ let text = core::str::from_utf8(page)?.trim();
+ let value = text.parse::<T>().map_err(|_| EINVAL)?;
+
+ let mut guard = this.data.lock();
+ if guard.powered {
+ return Err(EBUSY);
+ }
+
+ store_fn(&mut guard, value)
+}
+
+macro_rules! configfs_attribute {
+ (
+ $type:ty,
+ $id:literal,
+ show: |$show_this:ident, $show_page:ident| $show_block:expr,
+ store: |$store_this:ident, $store_page:ident| $store_block:expr
+ $(,)?
+ ) => {
+ #[vtable]
+ impl configfs::AttributeOperations<$id> for $type {
+ type Data = $type;
+
+ fn show($show_this: &$type, $show_page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+ $show_block
+ }
+
+ fn store($store_this: &$type, $store_page: &[u8]) -> Result {
+ $store_block
+ }
+ }
+ };
+}
+pub(crate) use configfs_attribute;
+
+// Specialized macro for simple boolean fields that just store kstrtobool_bytes result.
+macro_rules! configfs_simple_bool_field {
+ ($type:ty, $id:literal, $field:ident) => {
+ crate::configfs::macros::configfs_attribute!($type, $id,
+ show: |this, page| crate::configfs::macros::show_field(this.data.lock().$field, page),
+ store: |this, page|
+ crate::configfs::macros::store_with_power_check(this, page, |data, page| {
+ data.$field = kstrtobool_bytes(page)?;
+ Ok(())
+ })
+ );
+ };
+}
+pub(crate) use configfs_simple_bool_field;
+
+// Specialized macro for simple numeric fields that just parse and assign
+macro_rules! configfs_simple_field {
+ // Simple direct assignment
+ ($type:ty, $id:literal, $field:ident, $field_type:ty) => {
+ crate::configfs::macros::configfs_attribute!($type, $id,
+ show: |this, page| crate::configfs::macros::show_field(this.data.lock().$field, page),
+ store: |this, page| crate::configfs::macros::store_number_with_power_check(
+ this,
+ page,
+ |data, value: $field_type| {
+ data.$field = value;
+ Ok(())
+ }
+ )
+ );
+ };
+ // With infallible conversion expression (direct value)
+ ($type:ty, $id:literal, $field:ident, $field_type:ty, into $convert:expr) => {
+ crate::configfs::macros::configfs_attribute!($type, $id,
+ show: |this, page|
+ crate::configfs::macros::show_field(this.data.lock().$field, page),
+ store: |this, page| crate::configfs::macros::store_number_with_power_check(
+ this,
+ page,
+ |data, value: $field_type| {
+ data.$field = $convert(value);
+ Ok(())
+ }
+ )
+ );
+ };
+ // With check, no conversion
+ ($type:ty, $id:literal, $field:ident, $field_type:ty, check $check:expr) => {
+ crate::configfs::macros::configfs_attribute!($type, $id,
+ show: |this, page| crate::configfs::macros::show_field(this.data.lock().$field, page),
+ store: |this, page| crate::configfs::macros::store_number_with_power_check(
+ this,
+ page,
+ |data, value: $field_type| {
+ $check(value)?;
+ data.$field = value;
+ Ok(())
+ }
+ )
+ );
+ };
+}
+pub(crate) use configfs_simple_field;

--
2.51.2