From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794405257; b=ZLTdIz3wzbAPxFbawQneWFrtW4GcTYOODnOTrrkTRRi0IDeEbhS0IV8iFt8RnXIEYLHi ljmNrVbLAwlEWxesbiLjifkoV0hXnTZWEb+cravzgMDOnmKTbfS+9hHM3RY/iYVBR2OGt AQC31GqNQYrBeKv7C2HkU2PXyjeEh0to7O5NEUO8Xni9ZcwRgFFDF8/XOVx6wpmeY6yZy OWF6UcDVexJ7o3OctpOq6bIOxU4UzTwN2DqGl8UVHFooJaWKW5oyX7ArITa/HWXp0iSP1 SiwKtrZow+Osht3PCvqNPug4u5453l+1qwqpuu2vmqBR6szUu9tKk56my8j6H3kbUDA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794405257; h=Received:DKIM-Signature:From:Date:Subject:MIME-Version:Content-Type: Content-Transfer-Encoding:Message-Id:X-B4-Tracking:X-Change-ID:To:Cc: X-Mailer:X-Developer-Signature:X-Developer-Key; bh=dLCsOhTudAlUPTNU0R3bdVAeTFalW+MHycbb2NS9OIE=; b=OjqCHKYVNKIbab68flzLxCBgy/SBcllWEpFtkrO5zvH2skoFkgJ+uLAJ6uPc5Oubl4K+ SF7focUZhsQ4MOluAnmMmLKr7rlmNoLXqISSjKxD5wRWyU1exRAAAiJyP2gwndlsgMTzH u6gdRYIE8f0PLlYoWlsgm1A1tBxuH5Jtw2coK/Rv/cgLIErpo2zN4xrQOpz76Ss234PHJ 5X5Z4fGtyV/7XUc3mP98YXKHQToiLoHov4HvhaDu3XJQcGIYXip1vH5+wXiPn4VIGr5To p//fLfCjRAL+KLIBVct59wS9ZdBgQukA3QJ6IAROSde83sOduGfdaS5YMBUi/K4UcRA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=M8ve62MO; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 574BE606A32A; Wed, 11 Nov 2026 13:54:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=M8ve62MO Received: by smtp.kernel.org (Postfix) with ESMTPSA id 439981F00A06; Wed, 11 Nov 2026 13:54:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1794405256; bh=dLCsOhTudAlUPTNU0R3bdVAeTFalW+MHycbb2NS9OIE=; h=From:Date:Subject:To:Cc; b=M8ve62MOyP3NNSzIDuBUmNbyc+uf8zljbFd5IajHPI+Cru57NzSaFwsRksbsD9ylB ClW2Q7TT8957iWdUDWoCgsL/+sM1P/eLJZr6PYkYFit3t7cqur4E4iFnEoWAJarrRt xf1bqHrf13MzZkKA+lf1NOXfWLWTkIHhubBagJ/UDnYXwqBYWWZoIcsbOCYoFZT8o7 b6Rs+fpqQYinlQLpJOian5A1HvESuqpMTnNRnbllmrV/Ak6iVbMaEgGCBuk8X9hwwf 1Kl69JXfLk62qexpFVhaB2h3joh1F+Vg+tkmeCCPaq5/YgPx0euOP/EokNir9w1/sV 9NAkWvVhmOUIA== From: Andreas Hindborg Date: Wed, 11 Nov 2026 14:53:52 +0100 Subject: [PATCH v3] configfs: rust: add an API for adding default groups from C X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261111-configfs-c-default-groups-v3-1-aec19389272e@kernel.org> X-B4-Tracking: v=1; b=H4sIAG9z9GoC/43NTQ7CIBCG4as0rMXwJ6auvIdxQWGgxAYaaImm6 d2l3Rg3xuX7JfPMgjIkDxldmgUlKD77GGrwQ4N0r4ID7E1txAiThNET1jFY72zGGhuwah4m7FK cx4w70xElhORcSVTvxwTWP3f7dq/d+zzF9NpfFbqt/6iFYoqhlWejwErN5fUBKcBwjMmhjS3sQ 0nyk2KVYsBAtLQFQdgXta7rG5qZa9wOAQAA X-Change-ID: 20260215-configfs-c-default-groups-bdb0a44633a6 To: Boqun Feng , Jens Axboe , Miguel Ojeda , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Alice Ryhl , Trevor Gross , Danilo Krummrich , Breno Leitao , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= Cc: linux-kernel@vger.kernel.org, linux-block@vger.kernel.org, rust-for-linux@vger.kernel.org, Andreas Hindborg X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=15792; i=a.hindborg@kernel.org; h=from:subject:message-id; bh=M/dcZL/nm44/hZuDmi7hVupFoq3kXmerJ+/j5Q+G9nc=; b=owEBbQKS/ZANAwAKAfpQKQiqxb3QAcsmYgBq9HNxp9uQhGvTVvGMVyWPy/OqK3iqjU6bCfd9O obB9VcTsc6JAjMEAAEKAB0WIQRXitnI2WZ2JirAaob6UCkIqsW90AUCavRzcQAKCRD6UCkIqsW9 0Gp0EACMQqUEoQ3bNAYwj/JeiIii3fdOFt55Kwl+2z7Ejv9yQ8UdQXAuPhniXHb5ROEHub1Y8d+ G/KhnLZnUYPoRl+nSLKbAgDjdc0yHWL3sLKOaIclgyZCrMaTAOWWwmENn9834WJfPVKFhfE50/1 2rNhVXQfTsk1G9/CtIYxG6OzfyNXUtBgwBoeroEwonTNtXCHsPtIuk9PklO0d2r/roZTuE6ZXpI CCBoCuMmztdrJqLx3LxIcIsluN/8XJ/7cxxDn9bXMLgz10Ehhpv8fUH8bsgpQSKUKsYtpAffkaZ ERgmanJHu1aZP59JvbzztU1ApsRaX0HqcXOnEGjY0pq40ArjAZ+14PtQA6mrlfynz5BJwQ2P0Sr c09Z9c57+HHnUX3uGLP+XYQf4NQulTeBcxl9ufeO/UeFzgQDbgUqU9fOtE3Xh4ng84nlyMdnQ56 irwe+x46uZR9CYHd/mwpspsJpRm40X4ecLSSJRjSBse4+aBQQFx4TrlAm5QtvfLaAUyukC+REBm zPpm9biSf52TcbHEK2e6PAf4TEqPs7vn9M4Bzv7HKfha4RJQTjQIBDM8vF8bGplcHRaXozdESn+ ZXeDu5LS4Z7Ouuf9aANazM/lmzlcurg+nGhz9wCyUrp2wXjAO1amjW1oSbEWc8wtI06hq8QPbYy 1oFCiwPzt/jocqw== X-Developer-Key: i=a.hindborg@kernel.org; a=openpgp; fpr=3108C10F46872E248D1FB221376EB100563EF7A7 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 --- 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 + +#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 { #[pin] group: Opaque, #[pin] data: Data, + default_groups: KVec>, +} + +#[pinned_drop] +impl PinnedDrop for Group { + 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 Group { /// /// 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, Data>, data: impl PinInit, + default_groups: impl IntoIterator>, ) -> impl PinInit { - try_pin_init!(Self { - group <- pin_init::init_zeroed().chain(|v: &mut Opaque| { - 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> = 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| { + 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`, so that one [`Group`] can have +/// default groups of different types. An `UnattachedGroup` 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(Arc); + +impl UnattachedGroup { + /// 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(init: impl PinInit, flags: Flags) -> Result + where + Error: From, + { + 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) -> Self { + // INVARIANT: By the safety requirements of this function, the group is not a default group + // of any group. + Self(group) + } +} + +impl UnattachedGroup { + /// 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 { + 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 { + self.0 + } +} + // SAFETY: `Group` embeds a field of type `bindings::config_group` // within the `group` field. unsafe impl HasGroup for Group { 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, ], }; - 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