* [PATCH v3] configfs: rust: add an API for adding default groups from C
@ 2026-11-11 13:53 Andreas Hindborg
0 siblings, 0 replies; only message in thread
From: Andreas Hindborg @ 2026-11-11 13:53 UTC (permalink / raw)
To: Boqun Feng, Jens Axboe, Miguel Ojeda, Gary Guo,
Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Breno Leitao, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan
Cc: linux-kernel, linux-block, rust-for-linux, Andreas Hindborg
Some C subsystems provide a feature to add configfs default groups to the
configfs hierarchy of other drivers or subsystems. Rust abstractions for
these subsystems will want a way to add these default groups via the
configfs Rust API. So add infrastructure to make this possible.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
Changes in v3:
- Acquire a reference on each default group before adding it to its parent
group. `configfs_add_default_group` does not acquire one, but
`configfs_remove_default_groups` releases one, so a default group reached
a zero reference count when its parent was dropped, while the caller
still owned it.
- Make `CDefaultGroup` an unsafe trait and document the requirements on
implementers.
- Add `UnattachedGroup`, a token that grants permission to attach a group
as a default group exactly once, and take that in `Group::new` instead of
an `Arc`. A `struct config_group` is linked into its parent through a
single `group_entry` field, so attaching one group to two parents would
corrupt both lists. The token cannot be cloned and `Group::new` consumes
it, so this is now rejected at compile time rather than left to the
caller.
- Allocate the group in `UnattachedGroup::new`, so that uniqueness follows
from construction. Obtain a handle to the group with `clone_arc` or
`into_arc`, named after the `ListArc` accessors.
- Initialize `data` before `group` in `Group::new`, so that no fallible
step runs after the default groups have been attached.
- Drop default groups from the list of unsupported features in the module
documentation.
- Link to v2: https://msgid.link/20260605-configfs-c-default-groups-v2-1-2e2e4919e402@kernel.org
Changes in v2:
- Drop the spurious `__` prefix on the symbol name of the
`configfs_remove_default_groups` Rust helper (Alice).
- Wrap the body of `Group::new` in `pin_init::pin_init_scope` so that
allocation failure from `KVec::push` is propagated with `?` instead
of panicking via `unwrap` (Danilo).
- Only build configfs rust helpers when configfs is enabled.
- Link to v1: https://msgid.link/20260215-configfs-c-default-groups-v1-1-e967daef6c36@kernel.org
---
drivers/block/rnull/configfs.rs | 1 +
rust/helpers/configfs.c | 20 +++++
rust/helpers/helpers.c | 1 +
rust/kernel/configfs.rs | 184 +++++++++++++++++++++++++++++++++++++---
samples/rust/rust_configfs.rs | 8 +-
5 files changed, 200 insertions(+), 14 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 34ef96fa125f0..2f5b722927e0d 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -88,6 +88,7 @@ fn make_group(
name: name.try_into()?,
}),
}),
+ core::iter::empty(),
))
}
}
diff --git a/rust/helpers/configfs.c b/rust/helpers/configfs.c
new file mode 100644
index 0000000000000..4c71c33211bf7
--- /dev/null
+++ b/rust/helpers/configfs.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/configfs.h>
+
+#ifdef CONFIG_CONFIGFS_FS
+
+__rust_helper void
+rust_helper_configfs_add_default_group(struct config_group *new_group,
+ struct config_group *group)
+{
+ configfs_add_default_group(new_group, group);
+}
+
+__rust_helper void
+rust_helper_configfs_remove_default_groups(struct config_group *group)
+{
+ configfs_remove_default_groups(group);
+}
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index d62c7be1ca7bd..ab8d38b431176 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -51,6 +51,7 @@
#include "build_bug.c"
#include "clk.c"
#include "completion.c"
+#include "configfs.c"
#include "cpu.c"
#include "cpufreq.c"
#include "cpumask.c"
diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs
index dbed72844b31c..a434fc269ef7c 100644
--- a/rust/kernel/configfs.rs
+++ b/rust/kernel/configfs.rs
@@ -11,7 +11,8 @@
//! - Items. All group children are groups.
//! - Symlink support.
//! - `disconnect_notify` hook.
-//! - Default groups.
+//! - Default groups, except for groups allocated and initialized by C code. See
+//! [`CDefaultGroup`] and [`UnattachedGroup`].
//!
//! See the [`rust_configfs.rs`] sample for a full example use of this module.
//!
@@ -110,6 +111,7 @@
//! [`rust_configfs.rs`]: srctree/samples/rust/rust_configfs.rs
use crate::alloc::flags;
+use crate::alloc::Flags;
use crate::container_of;
use crate::page::PAGE_SIZE;
use crate::prelude::*;
@@ -242,12 +244,27 @@ unsafe fn container_of(group: *const bindings::config_group) -> *const Self {
///
/// To add a subgroup to configfs, pass this type as `ctype` to
/// [`crate::configfs_attrs`] when creating a group in [`GroupOperations::make_group`].
-#[pin_data]
+#[pin_data(PinnedDrop)]
pub struct Group<Data> {
#[pin]
group: Opaque<bindings::config_group>,
#[pin]
data: Data,
+ default_groups: KVec<Arc<dyn CDefaultGroup>>,
+}
+
+#[pinned_drop]
+impl<Data> PinnedDrop for Group<Data> {
+ fn drop(self: Pin<&mut Self>) {
+ // SAFETY: We have exclusive access to `self` and we know the default groups are alive
+ // because we reference them through `self.default_groups`.
+ //
+ // `configfs_remove_default_groups` releases one reference on each default group. That is
+ // the reference `Self::new` acquired before adding the group, so the references owned by
+ // the `Arc` values in `self.default_groups` are not affected. Those are released when the
+ // field is dropped, which happens after this function returns.
+ unsafe { bindings::configfs_remove_default_groups(self.group.get()) };
+ }
}
// SAFETY: We do not provide any operations on `Group`.
@@ -262,26 +279,167 @@ impl<Data> Group<Data> {
///
/// When instantiated, the group will appear as a directory with the name
/// given by `name` and it will contain attributes specified by `item_type`.
+ ///
+ /// Each element of `default_groups` is added as a default group of the new group and will
+ /// appear as a subdirectory of it.
pub fn new(
name: CString,
item_type: &'static ItemType<Group<Data>, Data>,
data: impl PinInit<Data, Error>,
+ default_groups: impl IntoIterator<Item = UnattachedGroup<dyn CDefaultGroup>>,
) -> impl PinInit<Self, Error> {
- try_pin_init!(Self {
- group <- pin_init::init_zeroed().chain(|v: &mut Opaque<bindings::config_group>| {
- let place = v.get();
- let name = name.to_bytes_with_nul().as_ptr();
- // SAFETY: It is safe to initialize a group once it has been zeroed.
- unsafe {
- bindings::config_group_init_type_name(place, name.cast(), item_type.as_ptr())
- };
- Ok(())
- }),
- data <- data,
+ pin_init::pin_init_scope(move || {
+ // Transition the permission tokens into `Arc`s.
+ let mut dg: KVec<Arc<dyn CDefaultGroup>> = KVec::new();
+ for group in default_groups {
+ dg.push(group.into_arc(), GFP_KERNEL)?;
+ }
+
+ Ok(try_pin_init!(Self {
+ // `data` is initialized first because it is the only fallible step. Nothing may
+ // fail after the default groups below have been added to `group`, because the
+ // references acquired for them are only released by the
+ // `configfs_remove_default_groups` call in `PinnedDrop`, which does not run if
+ // initialization fails.
+ data <- data,
+ group <- pin_init::init_zeroed().chain(|v: &mut Opaque<bindings::config_group>| {
+ let place = v.get();
+ let name = name.to_bytes_with_nul().as_ptr();
+ // SAFETY: It is safe to initialize a group once it has been zeroed.
+ unsafe {
+ bindings::config_group_init_type_name(
+ place,
+ name.cast(),
+ item_type.as_ptr(),
+ )
+ };
+
+ for default_group in &dg {
+ let child = default_group.group_ptr();
+
+ // `configfs_add_default_group` does not acquire a reference on the group
+ // it adds, but `configfs_remove_default_groups` releases one. Acquire the
+ // reference that the removal in `PinnedDrop` will release, so that the
+ // reference owned by `default_group` stays intact.
+ //
+ // SAFETY: By the safety requirements of `CDefaultGroup`, `child` points to
+ // an initialized `struct config_group`, and it stays live because
+ // `default_group` keeps it alive.
+ unsafe { bindings::config_item_get(&raw mut (*child).cg_item) };
+
+ // SAFETY: As above, `child` points to an initialized
+ // `struct config_group` that stays live until `Self` is dropped, because
+ // `dg` is a field of `Self`.
+ //
+ // `child` is not already a default group of any group: each element of
+ // `dg` came from an `UnattachedGroup` consumed above, whose type
+ // invariants say so, and the only way to attach a group is to pass such a
+ // token to this function, so nothing can have attached it in between.
+ unsafe { bindings::configfs_add_default_group(child, place) }
+ }
+ Ok(())
+ }),
+ default_groups: dg,
+ }))
})
}
}
+/// A trait for default configfs groups added by C code.
+///
+/// Rust abstractions that work with C code that creates configfs groups can implement this trait to
+/// add the groups as default groups via the Rust configfs API.
+///
+/// Note that implementing this trait is not by itself enough to add a group as a default group of a
+/// [`Group`]. Construct the implementer with [`UnattachedGroup::new`] and pass the resulting
+/// [`UnattachedGroup`] to [`Group::new`].
+///
+/// # Safety
+///
+/// Implementers must ensure that:
+///
+/// - [`CDefaultGroup::group_ptr`] returns a pointer to a `struct config_group` that has been
+/// initialized by `config_group_init_type_name`, or by a C function that calls it.
+/// - Every call to [`CDefaultGroup::group_ptr`] on the same value returns the same pointer.
+/// - The pointee stays live, initialized and in place for as long as `self` is live.
+/// - The reference on the embedded `struct config_item` that was acquired when the group was
+/// initialized is owned by `self` and is released when `self` is dropped.
+/// - The implementer never adds the group as a default group of any group itself. Only
+/// [`Group::new`] may do that.
+pub unsafe trait CDefaultGroup {
+ /// Return a raw pointer to the group definition.
+ fn group_ptr(&self) -> *mut bindings::config_group;
+}
+
+/// A group that is not yet attached as a default group of a [`Group`].
+///
+/// This type is a token that grants permission to attach a group once. It cannot be cloned, and
+/// [`Group::new`] consumes it, so a group cannot be attached twice. Use
+/// [`UnattachedGroup::clone_arc`] to obtain a handle to the group, which can be cloned freely but
+/// cannot be used to attach the group.
+///
+/// [`Group::new`] takes an `UnattachedGroup<dyn CDefaultGroup>`, so that one [`Group`] can have
+/// default groups of different types. An `UnattachedGroup<T>` coerces to that type.
+///
+/// # Invariants
+///
+/// The `struct config_group` returned by [`CDefaultGroup::group_ptr`] on `self.0` is not a default
+/// group of any group.
+#[repr(transparent)]
+#[derive(core::marker::CoercePointee)]
+pub struct UnattachedGroup<T: ?Sized>(Arc<T>);
+
+impl<T: CDefaultGroup + 'static> UnattachedGroup<T> {
+ /// Create a group that can be attached as a default group of a [`Group`].
+ ///
+ /// The returned value grants permission to attach the group to exactly one [`Group`] via
+ /// [`Group::new`]. Use [`UnattachedGroup::clone_arc`] to obtain a handle to the group itself.
+ pub fn new<E>(init: impl PinInit<T, E>, flags: Flags) -> Result<Self>
+ where
+ Error: From<E>,
+ {
+ let group = Arc::pin_init(init, flags)?;
+
+ // INVARIANT: `group` was just created and this function holds the only reference to it, so
+ // by the safety requirements of `CDefaultGroup` the group it wraps is not a default group
+ // of any group. The reference on the embedded `struct config_item` is owned by the group
+ // itself, also by the safety requirements of `CDefaultGroup`.
+ //
+ // SAFETY: Same as above.
+ Ok(unsafe { Self::from_arc(group) })
+ }
+
+ /// Create an [`UnattachedGroup`] from an existing [`Arc`].
+ ///
+ /// # Safety
+ ///
+ /// The `struct config_group` returned by `group.group_ptr()` must not be a default group of any
+ /// group.
+ unsafe fn from_arc(group: Arc<T>) -> Self {
+ // INVARIANT: By the safety requirements of this function, the group is not a default group
+ // of any group.
+ Self(group)
+ }
+}
+
+impl<T: CDefaultGroup + ?Sized> UnattachedGroup<T> {
+ /// Clone an [`UnattachedGroup`] into an [`Arc`].
+ ///
+ /// The returned [`Arc`] can be cloned, and it keeps the group alive independently of the
+ /// [`Group`] that the group is attached to. It cannot be used to attach the group.
+ pub fn clone_arc(&self) -> Arc<T> {
+ self.0.clone()
+ }
+
+ /// Convert an [`UnattachedGroup`] into an [`Arc`].
+ ///
+ /// This gives up the permission to attach the group, so the group can no longer be attached as
+ /// a default group of a [`Group`].
+ pub fn into_arc(self) -> Arc<T> {
+ self.0
+ }
+}
+
// SAFETY: `Group<Data>` embeds a field of type `bindings::config_group`
// within the `group` field.
unsafe impl<Data> HasGroup<Data> for Group<Data> {
diff --git a/samples/rust/rust_configfs.rs b/samples/rust/rust_configfs.rs
index a1bd9db6010da..ada378bc331b1 100644
--- a/samples/rust/rust_configfs.rs
+++ b/samples/rust/rust_configfs.rs
@@ -83,7 +83,12 @@ fn make_group(&self, name: &CStr) -> Result<impl PinInit<configfs::Group<Child>,
],
};
- Ok(configfs::Group::new(name.try_into()?, tpe, Child::new()))
+ Ok(configfs::Group::new(
+ name.try_into()?,
+ tpe,
+ Child::new(),
+ core::iter::empty(),
+ ))
}
}
@@ -152,6 +157,7 @@ fn make_group(&self, name: &CStr) -> Result<impl PinInit<configfs::Group<GrandCh
name.try_into()?,
tpe,
GrandChild::new(),
+ core::iter::empty(),
))
}
}
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260215-configfs-c-default-groups-bdb0a44633a6
Best regards,
--
Andreas Hindborg <a.hindborg@kernel.org>
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-11 13:54 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 13:53 [PATCH v3] configfs: rust: add an API for adding default groups from C Andreas Hindborg
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®