mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	 64fb810bce
			
		
	
	
		64fb810bce
		
	
	
	
	
		
			
			In the previous patch we added Opaque::cast_from() that performs the opposite operation to Opaque::raw_get(). For consistency with this naming, rename raw_get() to cast_from(). There are a few other options such as calling cast_from() something closer to raw_get() rather than renaming this method. However, I could not find a great naming scheme that works with raw_get(). The previous version of this patch used from_raw(), but functions of that name typically have a different signature, so that's not a great option. Suggested-by: Danilo Krummrich <dakr@kernel.org> Signed-off-by: Alice Ryhl <aliceryhl@google.com> Acked-by: Benno Lossin <lossin@kernel.org> Acked-by: Andreas Hindborg <a.hindborg@kernel.org> Acked-by: Boqun Feng <boqun.feng@gmail.com> Reviewed-by: Danilo Krummrich <dakr@kernel.org> Acked-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/r/20250624-opaque-from-raw-v2-2-e4da40bdc59c@google.com [ Removed `HrTimer::raw_get` change. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
		
			
				
	
	
		
			1041 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1041 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| 
 | |
| //! configfs interface: Userspace-driven Kernel Object Configuration
 | |
| //!
 | |
| //! configfs is an in-memory pseudo file system for configuration of kernel
 | |
| //! modules. Please see the [C documentation] for details and intended use of
 | |
| //! configfs.
 | |
| //!
 | |
| //! This module does not support the following configfs features:
 | |
| //!
 | |
| //! - Items. All group children are groups.
 | |
| //! - Symlink support.
 | |
| //! - `disconnect_notify` hook.
 | |
| //! - Default groups.
 | |
| //!
 | |
| //! See the [`rust_configfs.rs`] sample for a full example use of this module.
 | |
| //!
 | |
| //! C header: [`include/linux/configfs.h`](srctree/include/linux/configfs.h)
 | |
| //!
 | |
| //! # Examples
 | |
| //!
 | |
| //! ```ignore
 | |
| //! use kernel::alloc::flags;
 | |
| //! use kernel::c_str;
 | |
| //! use kernel::configfs_attrs;
 | |
| //! use kernel::configfs;
 | |
| //! use kernel::new_mutex;
 | |
| //! use kernel::page::PAGE_SIZE;
 | |
| //! use kernel::sync::Mutex;
 | |
| //! use kernel::ThisModule;
 | |
| //!
 | |
| //! #[pin_data]
 | |
| //! struct RustConfigfs {
 | |
| //!     #[pin]
 | |
| //!     config: configfs::Subsystem<Configuration>,
 | |
| //! }
 | |
| //!
 | |
| //! impl kernel::InPlaceModule for RustConfigfs {
 | |
| //!     fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
 | |
| //!         pr_info!("Rust configfs sample (init)\n");
 | |
| //!
 | |
| //!         let item_type = configfs_attrs! {
 | |
| //!             container: configfs::Subsystem<Configuration>,
 | |
| //!             data: Configuration,
 | |
| //!             attributes: [
 | |
| //!                 message: 0,
 | |
| //!                 bar: 1,
 | |
| //!             ],
 | |
| //!         };
 | |
| //!
 | |
| //!         try_pin_init!(Self {
 | |
| //!             config <- configfs::Subsystem::new(
 | |
| //!                 c_str!("rust_configfs"), item_type, Configuration::new()
 | |
| //!             ),
 | |
| //!         })
 | |
| //!     }
 | |
| //! }
 | |
| //!
 | |
| //! #[pin_data]
 | |
| //! struct Configuration {
 | |
| //!     message: &'static CStr,
 | |
| //!     #[pin]
 | |
| //!     bar: Mutex<(KBox<[u8; PAGE_SIZE]>, usize)>,
 | |
| //! }
 | |
| //!
 | |
| //! impl Configuration {
 | |
| //!     fn new() -> impl PinInit<Self, Error> {
 | |
| //!         try_pin_init!(Self {
 | |
| //!             message: c_str!("Hello World\n"),
 | |
| //!             bar <- new_mutex!((KBox::new([0; PAGE_SIZE], flags::GFP_KERNEL)?, 0)),
 | |
| //!         })
 | |
| //!     }
 | |
| //! }
 | |
| //!
 | |
| //! #[vtable]
 | |
| //! impl configfs::AttributeOperations<0> for Configuration {
 | |
| //!     type Data = Configuration;
 | |
| //!
 | |
| //!     fn show(container: &Configuration, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
 | |
| //!         pr_info!("Show message\n");
 | |
| //!         let data = container.message;
 | |
| //!         page[0..data.len()].copy_from_slice(data);
 | |
| //!         Ok(data.len())
 | |
| //!     }
 | |
| //! }
 | |
| //!
 | |
| //! #[vtable]
 | |
| //! impl configfs::AttributeOperations<1> for Configuration {
 | |
| //!     type Data = Configuration;
 | |
| //!
 | |
| //!     fn show(container: &Configuration, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
 | |
| //!         pr_info!("Show bar\n");
 | |
| //!         let guard = container.bar.lock();
 | |
| //!         let data = guard.0.as_slice();
 | |
| //!         let len = guard.1;
 | |
| //!         page[0..len].copy_from_slice(&data[0..len]);
 | |
| //!         Ok(len)
 | |
| //!     }
 | |
| //!
 | |
| //!     fn store(container: &Configuration, page: &[u8]) -> Result {
 | |
| //!         pr_info!("Store bar\n");
 | |
| //!         let mut guard = container.bar.lock();
 | |
| //!         guard.0[0..page.len()].copy_from_slice(page);
 | |
| //!         guard.1 = page.len();
 | |
| //!         Ok(())
 | |
| //!     }
 | |
| //! }
 | |
| //! ```
 | |
| //!
 | |
| //! [C documentation]: srctree/Documentation/filesystems/configfs.rst
 | |
| //! [`rust_configfs.rs`]: srctree/samples/rust/rust_configfs.rs
 | |
| 
 | |
| use crate::alloc::flags;
 | |
| use crate::container_of;
 | |
| use crate::page::PAGE_SIZE;
 | |
| use crate::prelude::*;
 | |
| use crate::str::CString;
 | |
| use crate::sync::Arc;
 | |
| use crate::sync::ArcBorrow;
 | |
| use crate::types::Opaque;
 | |
| use core::cell::UnsafeCell;
 | |
| use core::marker::PhantomData;
 | |
| 
 | |
| /// A configfs subsystem.
 | |
| ///
 | |
| /// This is the top level entrypoint for a configfs hierarchy. To register
 | |
| /// with configfs, embed a field of this type into your kernel module struct.
 | |
| #[pin_data(PinnedDrop)]
 | |
| pub struct Subsystem<Data> {
 | |
|     #[pin]
 | |
|     subsystem: Opaque<bindings::configfs_subsystem>,
 | |
|     #[pin]
 | |
|     data: Data,
 | |
| }
 | |
| 
 | |
| // SAFETY: We do not provide any operations on `Subsystem`.
 | |
| unsafe impl<Data> Sync for Subsystem<Data> {}
 | |
| 
 | |
| // SAFETY: Ownership of `Subsystem` can safely be transferred to other threads.
 | |
| unsafe impl<Data> Send for Subsystem<Data> {}
 | |
| 
 | |
| impl<Data> Subsystem<Data> {
 | |
|     /// Create an initializer for a [`Subsystem`].
 | |
|     ///
 | |
|     /// The subsystem will appear in configfs as a directory name given by
 | |
|     /// `name`. The attributes available in directory are specified by
 | |
|     /// `item_type`.
 | |
|     pub fn new(
 | |
|         name: &'static CStr,
 | |
|         item_type: &'static ItemType<Subsystem<Data>, Data>,
 | |
|         data: impl PinInit<Data, Error>,
 | |
|     ) -> impl PinInit<Self, Error> {
 | |
|         try_pin_init!(Self {
 | |
|             subsystem <- pin_init::init_zeroed().chain(
 | |
|                 |place: &mut Opaque<bindings::configfs_subsystem>| {
 | |
|                     // SAFETY: We initialized the required fields of `place.group` above.
 | |
|                     unsafe {
 | |
|                         bindings::config_group_init_type_name(
 | |
|                             &mut (*place.get()).su_group,
 | |
|                             name.as_ptr(),
 | |
|                             item_type.as_ptr(),
 | |
|                         )
 | |
|                     };
 | |
| 
 | |
|                     // SAFETY: `place.su_mutex` is valid for use as a mutex.
 | |
|                     unsafe {
 | |
|                         bindings::__mutex_init(
 | |
|                             &mut (*place.get()).su_mutex,
 | |
|                             kernel::optional_name!().as_char_ptr(),
 | |
|                             kernel::static_lock_class!().as_ptr(),
 | |
|                         )
 | |
|                     }
 | |
|                     Ok(())
 | |
|                 }
 | |
|             ),
 | |
|             data <- data,
 | |
|         })
 | |
|         .pin_chain(|this| {
 | |
|             crate::error::to_result(
 | |
|                 // SAFETY: We initialized `this.subsystem` according to C API contract above.
 | |
|                 unsafe { bindings::configfs_register_subsystem(this.subsystem.get()) },
 | |
|             )
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[pinned_drop]
 | |
| impl<Data> PinnedDrop for Subsystem<Data> {
 | |
|     fn drop(self: Pin<&mut Self>) {
 | |
|         // SAFETY: We registered `self.subsystem` in the initializer returned by `Self::new`.
 | |
|         unsafe { bindings::configfs_unregister_subsystem(self.subsystem.get()) };
 | |
|         // SAFETY: We initialized the mutex in `Subsystem::new`.
 | |
|         unsafe { bindings::mutex_destroy(&raw mut (*self.subsystem.get()).su_mutex) };
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Trait that allows offset calculations for structs that embed a
 | |
| /// `bindings::config_group`.
 | |
| ///
 | |
| /// Users of the configfs API should not need to implement this trait.
 | |
| ///
 | |
| /// # Safety
 | |
| ///
 | |
| /// - Implementers of this trait must embed a `bindings::config_group`.
 | |
| /// - Methods must be implemented according to method documentation.
 | |
| pub unsafe trait HasGroup<Data> {
 | |
|     /// Return the address of the `bindings::config_group` embedded in [`Self`].
 | |
|     ///
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// - `this` must be a valid allocation of at least the size of [`Self`].
 | |
|     unsafe fn group(this: *const Self) -> *const bindings::config_group;
 | |
| 
 | |
|     /// Return the address of the [`Self`] that `group` is embedded in.
 | |
|     ///
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// - `group` must point to the `bindings::config_group` that is embedded in
 | |
|     ///   [`Self`].
 | |
|     unsafe fn container_of(group: *const bindings::config_group) -> *const Self;
 | |
| }
 | |
| 
 | |
| // SAFETY: `Subsystem<Data>` embeds a field of type `bindings::config_group`
 | |
| // within the `subsystem` field.
 | |
| unsafe impl<Data> HasGroup<Data> for Subsystem<Data> {
 | |
|     unsafe fn group(this: *const Self) -> *const bindings::config_group {
 | |
|         // SAFETY: By impl and function safety requirement this projection is in bounds.
 | |
|         unsafe { &raw const (*(*this).subsystem.get()).su_group }
 | |
|     }
 | |
| 
 | |
|     unsafe fn container_of(group: *const bindings::config_group) -> *const Self {
 | |
|         // SAFETY: By impl and function safety requirement this projection is in bounds.
 | |
|         let c_subsys_ptr = unsafe { container_of!(group, bindings::configfs_subsystem, su_group) };
 | |
|         let opaque_ptr = c_subsys_ptr.cast::<Opaque<bindings::configfs_subsystem>>();
 | |
|         // SAFETY: By impl and function safety requirement, `opaque_ptr` and the
 | |
|         // pointer it returns, are within the same allocation.
 | |
|         unsafe { container_of!(opaque_ptr, Subsystem<Data>, subsystem) }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A configfs group.
 | |
| ///
 | |
| /// 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]
 | |
| pub struct Group<Data> {
 | |
|     #[pin]
 | |
|     group: Opaque<bindings::config_group>,
 | |
|     #[pin]
 | |
|     data: Data,
 | |
| }
 | |
| 
 | |
| impl<Data> Group<Data> {
 | |
|     /// Create an initializer for a new 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`.
 | |
|     pub fn new(
 | |
|         name: CString,
 | |
|         item_type: &'static ItemType<Group<Data>, Data>,
 | |
|         data: impl PinInit<Data, Error>,
 | |
|     ) -> 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.as_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,
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| // SAFETY: `Group<Data>` embeds a field of type `bindings::config_group`
 | |
| // within the `group` field.
 | |
| unsafe impl<Data> HasGroup<Data> for Group<Data> {
 | |
|     unsafe fn group(this: *const Self) -> *const bindings::config_group {
 | |
|         Opaque::cast_into(
 | |
|             // SAFETY: By impl and function safety requirements this field
 | |
|             // projection is within bounds of the allocation.
 | |
|             unsafe { &raw const (*this).group },
 | |
|         )
 | |
|     }
 | |
| 
 | |
|     unsafe fn container_of(group: *const bindings::config_group) -> *const Self {
 | |
|         let opaque_ptr = group.cast::<Opaque<bindings::config_group>>();
 | |
|         // SAFETY: By impl and function safety requirement, `opaque_ptr` and
 | |
|         // pointer it returns will be in the same allocation.
 | |
|         unsafe { container_of!(opaque_ptr, Self, group) }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// # Safety
 | |
| ///
 | |
| /// `this` must be a valid pointer.
 | |
| ///
 | |
| /// If `this` does not represent the root group of a configfs subsystem,
 | |
| /// `this` must be a pointer to a `bindings::config_group` embedded in a
 | |
| /// `Group<Parent>`.
 | |
| ///
 | |
| /// Otherwise, `this` must be a pointer to a `bindings::config_group` that
 | |
| /// is embedded in a `bindings::configfs_subsystem` that is embedded in a
 | |
| /// `Subsystem<Parent>`.
 | |
| unsafe fn get_group_data<'a, Parent>(this: *mut bindings::config_group) -> &'a Parent {
 | |
|     // SAFETY: `this` is a valid pointer.
 | |
|     let is_root = unsafe { (*this).cg_subsys.is_null() };
 | |
| 
 | |
|     if !is_root {
 | |
|         // SAFETY: By C API contact,`this` was returned from a call to
 | |
|         // `make_group`. The pointer is known to be embedded within a
 | |
|         // `Group<Parent>`.
 | |
|         unsafe { &(*Group::<Parent>::container_of(this)).data }
 | |
|     } else {
 | |
|         // SAFETY: By C API contract, `this` is a pointer to the
 | |
|         // `bindings::config_group` field within a `Subsystem<Parent>`.
 | |
|         unsafe { &(*Subsystem::container_of(this)).data }
 | |
|     }
 | |
| }
 | |
| 
 | |
| struct GroupOperationsVTable<Parent, Child>(PhantomData<(Parent, Child)>);
 | |
| 
 | |
| impl<Parent, Child> GroupOperationsVTable<Parent, Child>
 | |
| where
 | |
|     Parent: GroupOperations<Child = Child>,
 | |
|     Child: 'static,
 | |
| {
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// `this` must be a valid pointer.
 | |
|     ///
 | |
|     /// If `this` does not represent the root group of a configfs subsystem,
 | |
|     /// `this` must be a pointer to a `bindings::config_group` embedded in a
 | |
|     /// `Group<Parent>`.
 | |
|     ///
 | |
|     /// Otherwise, `this` must be a pointer to a `bindings::config_group` that
 | |
|     /// is embedded in a `bindings::configfs_subsystem` that is embedded in a
 | |
|     /// `Subsystem<Parent>`.
 | |
|     ///
 | |
|     /// `name` must point to a null terminated string.
 | |
|     unsafe extern "C" fn make_group(
 | |
|         this: *mut bindings::config_group,
 | |
|         name: *const kernel::ffi::c_char,
 | |
|     ) -> *mut bindings::config_group {
 | |
|         // SAFETY: By function safety requirements of this function, this call
 | |
|         // is safe.
 | |
|         let parent_data = unsafe { get_group_data(this) };
 | |
| 
 | |
|         let group_init = match Parent::make_group(
 | |
|             parent_data,
 | |
|             // SAFETY: By function safety requirements, name points to a null
 | |
|             // terminated string.
 | |
|             unsafe { CStr::from_char_ptr(name) },
 | |
|         ) {
 | |
|             Ok(init) => init,
 | |
|             Err(e) => return e.to_ptr(),
 | |
|         };
 | |
| 
 | |
|         let child_group = <Arc<Group<Child>> as InPlaceInit<Group<Child>>>::try_pin_init(
 | |
|             group_init,
 | |
|             flags::GFP_KERNEL,
 | |
|         );
 | |
| 
 | |
|         match child_group {
 | |
|             Ok(child_group) => {
 | |
|                 let child_group_ptr = child_group.into_raw();
 | |
|                 // SAFETY: We allocated the pointee of `child_ptr` above as a
 | |
|                 // `Group<Child>`.
 | |
|                 unsafe { Group::<Child>::group(child_group_ptr) }.cast_mut()
 | |
|             }
 | |
|             Err(e) => e.to_ptr(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// If `this` does not represent the root group of a configfs subsystem,
 | |
|     /// `this` must be a pointer to a `bindings::config_group` embedded in a
 | |
|     /// `Group<Parent>`.
 | |
|     ///
 | |
|     /// Otherwise, `this` must be a pointer to a `bindings::config_group` that
 | |
|     /// is embedded in a `bindings::configfs_subsystem` that is embedded in a
 | |
|     /// `Subsystem<Parent>`.
 | |
|     ///
 | |
|     /// `item` must point to a `bindings::config_item` within a
 | |
|     /// `bindings::config_group` within a `Group<Child>`.
 | |
|     unsafe extern "C" fn drop_item(
 | |
|         this: *mut bindings::config_group,
 | |
|         item: *mut bindings::config_item,
 | |
|     ) {
 | |
|         // SAFETY: By function safety requirements of this function, this call
 | |
|         // is safe.
 | |
|         let parent_data = unsafe { get_group_data(this) };
 | |
| 
 | |
|         // SAFETY: By function safety requirements, `item` is embedded in a
 | |
|         // `config_group`.
 | |
|         let c_child_group_ptr = unsafe { container_of!(item, bindings::config_group, cg_item) };
 | |
|         // SAFETY: By function safety requirements, `c_child_group_ptr` is
 | |
|         // embedded within a `Group<Child>`.
 | |
|         let r_child_group_ptr = unsafe { Group::<Child>::container_of(c_child_group_ptr) };
 | |
| 
 | |
|         if Parent::HAS_DROP_ITEM {
 | |
|             // SAFETY: We called `into_raw` to produce `r_child_group_ptr` in
 | |
|             // `make_group`.
 | |
|             let arc: Arc<Group<Child>> = unsafe { Arc::from_raw(r_child_group_ptr.cast_mut()) };
 | |
| 
 | |
|             Parent::drop_item(parent_data, arc.as_arc_borrow());
 | |
|             arc.into_raw();
 | |
|         }
 | |
| 
 | |
|         // SAFETY: By C API contract, we are required to drop a refcount on
 | |
|         // `item`.
 | |
|         unsafe { bindings::config_item_put(item) };
 | |
|     }
 | |
| 
 | |
|     const VTABLE: bindings::configfs_group_operations = bindings::configfs_group_operations {
 | |
|         make_item: None,
 | |
|         make_group: Some(Self::make_group),
 | |
|         disconnect_notify: None,
 | |
|         drop_item: Some(Self::drop_item),
 | |
|         is_visible: None,
 | |
|         is_bin_visible: None,
 | |
|     };
 | |
| 
 | |
|     const fn vtable_ptr() -> *const bindings::configfs_group_operations {
 | |
|         &Self::VTABLE
 | |
|     }
 | |
| }
 | |
| 
 | |
| struct ItemOperationsVTable<Container, Data>(PhantomData<(Container, Data)>);
 | |
| 
 | |
| impl<Data> ItemOperationsVTable<Group<Data>, Data>
 | |
| where
 | |
|     Data: 'static,
 | |
| {
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// `this` must be a pointer to a `bindings::config_group` embedded in a
 | |
|     /// `Group<Parent>`.
 | |
|     ///
 | |
|     /// This function will destroy the pointee of `this`. The pointee of `this`
 | |
|     /// must not be accessed after the function returns.
 | |
|     unsafe extern "C" fn release(this: *mut bindings::config_item) {
 | |
|         // SAFETY: By function safety requirements, `this` is embedded in a
 | |
|         // `config_group`.
 | |
|         let c_group_ptr = unsafe { kernel::container_of!(this, bindings::config_group, cg_item) };
 | |
|         // SAFETY: By function safety requirements, `c_group_ptr` is
 | |
|         // embedded within a `Group<Data>`.
 | |
|         let r_group_ptr = unsafe { Group::<Data>::container_of(c_group_ptr) };
 | |
| 
 | |
|         // SAFETY: We called `into_raw` on `r_group_ptr` in
 | |
|         // `make_group`.
 | |
|         let pin_self: Arc<Group<Data>> = unsafe { Arc::from_raw(r_group_ptr.cast_mut()) };
 | |
|         drop(pin_self);
 | |
|     }
 | |
| 
 | |
|     const VTABLE: bindings::configfs_item_operations = bindings::configfs_item_operations {
 | |
|         release: Some(Self::release),
 | |
|         allow_link: None,
 | |
|         drop_link: None,
 | |
|     };
 | |
| 
 | |
|     const fn vtable_ptr() -> *const bindings::configfs_item_operations {
 | |
|         &Self::VTABLE
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<Data> ItemOperationsVTable<Subsystem<Data>, Data> {
 | |
|     const VTABLE: bindings::configfs_item_operations = bindings::configfs_item_operations {
 | |
|         release: None,
 | |
|         allow_link: None,
 | |
|         drop_link: None,
 | |
|     };
 | |
| 
 | |
|     const fn vtable_ptr() -> *const bindings::configfs_item_operations {
 | |
|         &Self::VTABLE
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Operations implemented by configfs groups that can create subgroups.
 | |
| ///
 | |
| /// Implement this trait on structs that embed a [`Subsystem`] or a [`Group`].
 | |
| #[vtable]
 | |
| pub trait GroupOperations {
 | |
|     /// The child data object type.
 | |
|     ///
 | |
|     /// This group will create subgroups (subdirectories) backed by this kind of
 | |
|     /// object.
 | |
|     type Child: 'static;
 | |
| 
 | |
|     /// Creates a new subgroup.
 | |
|     ///
 | |
|     /// The kernel will call this method in response to `mkdir(2)` in the
 | |
|     /// directory representing `this`.
 | |
|     ///
 | |
|     /// To accept the request to create a group, implementations should
 | |
|     /// return an initializer of a `Group<Self::Child>`. To prevent creation,
 | |
|     /// return a suitable error.
 | |
|     fn make_group(&self, name: &CStr) -> Result<impl PinInit<Group<Self::Child>, Error>>;
 | |
| 
 | |
|     /// Prepares the group for removal from configfs.
 | |
|     ///
 | |
|     /// The kernel will call this method before the directory representing `_child` is removed from
 | |
|     /// configfs.
 | |
|     ///
 | |
|     /// Implementations can use this method to do house keeping before configfs drops its
 | |
|     /// reference to `Child`.
 | |
|     ///
 | |
|     /// NOTE: "drop" in the name of this function is not related to the Rust drop term. Rather, the
 | |
|     /// name is inherited from the callback name in the underlying C code.
 | |
|     fn drop_item(&self, _child: ArcBorrow<'_, Group<Self::Child>>) {
 | |
|         kernel::build_error!(kernel::error::VTABLE_DEFAULT_ERROR)
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A configfs attribute.
 | |
| ///
 | |
| /// An attribute appears as a file in configfs, inside a folder that represent
 | |
| /// the group that the attribute belongs to.
 | |
| #[repr(transparent)]
 | |
| pub struct Attribute<const ID: u64, O, Data> {
 | |
|     attribute: Opaque<bindings::configfs_attribute>,
 | |
|     _p: PhantomData<(O, Data)>,
 | |
| }
 | |
| 
 | |
| // SAFETY: We do not provide any operations on `Attribute`.
 | |
| unsafe impl<const ID: u64, O, Data> Sync for Attribute<ID, O, Data> {}
 | |
| 
 | |
| // SAFETY: Ownership of `Attribute` can safely be transferred to other threads.
 | |
| unsafe impl<const ID: u64, O, Data> Send for Attribute<ID, O, Data> {}
 | |
| 
 | |
| impl<const ID: u64, O, Data> Attribute<ID, O, Data>
 | |
| where
 | |
|     O: AttributeOperations<ID, Data = Data>,
 | |
| {
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// `item` must be embedded in a `bindings::config_group`.
 | |
|     ///
 | |
|     /// If `item` does not represent the root group of a configfs subsystem,
 | |
|     /// the group must be embedded in a `Group<Data>`.
 | |
|     ///
 | |
|     /// Otherwise, the group must be a embedded in a
 | |
|     /// `bindings::configfs_subsystem` that is embedded in a `Subsystem<Data>`.
 | |
|     ///
 | |
|     /// `page` must point to a writable buffer of size at least [`PAGE_SIZE`].
 | |
|     unsafe extern "C" fn show(
 | |
|         item: *mut bindings::config_item,
 | |
|         page: *mut kernel::ffi::c_char,
 | |
|     ) -> isize {
 | |
|         let c_group: *mut bindings::config_group =
 | |
|             // SAFETY: By function safety requirements, `item` is embedded in a
 | |
|             // `config_group`.
 | |
|             unsafe { container_of!(item, bindings::config_group, cg_item) };
 | |
| 
 | |
|         // SAFETY: The function safety requirements for this function satisfy
 | |
|         // the conditions for this call.
 | |
|         let data: &Data = unsafe { get_group_data(c_group) };
 | |
| 
 | |
|         // SAFETY: By function safety requirements, `page` is writable for `PAGE_SIZE`.
 | |
|         let ret = O::show(data, unsafe { &mut *(page.cast::<[u8; PAGE_SIZE]>()) });
 | |
| 
 | |
|         match ret {
 | |
|             Ok(size) => size as isize,
 | |
|             Err(err) => err.to_errno() as isize,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// `item` must be embedded in a `bindings::config_group`.
 | |
|     ///
 | |
|     /// If `item` does not represent the root group of a configfs subsystem,
 | |
|     /// the group must be embedded in a `Group<Data>`.
 | |
|     ///
 | |
|     /// Otherwise, the group must be a embedded in a
 | |
|     /// `bindings::configfs_subsystem` that is embedded in a `Subsystem<Data>`.
 | |
|     ///
 | |
|     /// `page` must point to a readable buffer of size at least `size`.
 | |
|     unsafe extern "C" fn store(
 | |
|         item: *mut bindings::config_item,
 | |
|         page: *const kernel::ffi::c_char,
 | |
|         size: usize,
 | |
|     ) -> isize {
 | |
|         let c_group: *mut bindings::config_group =
 | |
|         // SAFETY: By function safety requirements, `item` is embedded in a
 | |
|         // `config_group`.
 | |
|             unsafe { container_of!(item, bindings::config_group, cg_item) };
 | |
| 
 | |
|         // SAFETY: The function safety requirements for this function satisfy
 | |
|         // the conditions for this call.
 | |
|         let data: &Data = unsafe { get_group_data(c_group) };
 | |
| 
 | |
|         let ret = O::store(
 | |
|             data,
 | |
|             // SAFETY: By function safety requirements, `page` is readable
 | |
|             // for at least `size`.
 | |
|             unsafe { core::slice::from_raw_parts(page.cast(), size) },
 | |
|         );
 | |
| 
 | |
|         match ret {
 | |
|             Ok(()) => size as isize,
 | |
|             Err(err) => err.to_errno() as isize,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Create a new attribute.
 | |
|     ///
 | |
|     /// The attribute will appear as a file with name given by `name`.
 | |
|     pub const fn new(name: &'static CStr) -> Self {
 | |
|         Self {
 | |
|             attribute: Opaque::new(bindings::configfs_attribute {
 | |
|                 ca_name: name.as_char_ptr(),
 | |
|                 ca_owner: core::ptr::null_mut(),
 | |
|                 ca_mode: 0o660,
 | |
|                 show: Some(Self::show),
 | |
|                 store: if O::HAS_STORE {
 | |
|                     Some(Self::store)
 | |
|                 } else {
 | |
|                     None
 | |
|                 },
 | |
|             }),
 | |
|             _p: PhantomData,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Operations supported by an attribute.
 | |
| ///
 | |
| /// Implement this trait on type and pass that type as generic parameter when
 | |
| /// creating an [`Attribute`]. The type carrying the implementation serve no
 | |
| /// purpose other than specifying the attribute operations.
 | |
| ///
 | |
| /// This trait must be implemented on the `Data` type of for types that
 | |
| /// implement `HasGroup<Data>`. The trait must be implemented once for each
 | |
| /// attribute of the group. The constant type parameter `ID` maps the
 | |
| /// implementation to a specific `Attribute`. `ID` must be passed when declaring
 | |
| /// attributes via the [`kernel::configfs_attrs`] macro, to tie
 | |
| /// `AttributeOperations` implementations to concrete named attributes.
 | |
| #[vtable]
 | |
| pub trait AttributeOperations<const ID: u64 = 0> {
 | |
|     /// The type of the object that contains the field that is backing the
 | |
|     /// attribute for this operation.
 | |
|     type Data;
 | |
| 
 | |
|     /// Renders the value of an attribute.
 | |
|     ///
 | |
|     /// This function is called by the kernel to read the value of an attribute.
 | |
|     ///
 | |
|     /// Implementations should write the rendering of the attribute to `page`
 | |
|     /// and return the number of bytes written.
 | |
|     fn show(data: &Self::Data, page: &mut [u8; PAGE_SIZE]) -> Result<usize>;
 | |
| 
 | |
|     /// Stores the value of an attribute.
 | |
|     ///
 | |
|     /// This function is called by the kernel to update the value of an attribute.
 | |
|     ///
 | |
|     /// Implementations should parse the value from `page` and update internal
 | |
|     /// state to reflect the parsed value.
 | |
|     fn store(_data: &Self::Data, _page: &[u8]) -> Result {
 | |
|         kernel::build_error!(kernel::error::VTABLE_DEFAULT_ERROR)
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A list of attributes.
 | |
| ///
 | |
| /// This type is used to construct a new [`ItemType`]. It represents a list of
 | |
| /// [`Attribute`] that will appear in the directory representing a [`Group`].
 | |
| /// Users should not directly instantiate this type, rather they should use the
 | |
| /// [`kernel::configfs_attrs`] macro to declare a static set of attributes for a
 | |
| /// group.
 | |
| ///
 | |
| /// # Note
 | |
| ///
 | |
| /// Instances of this type are constructed statically at compile by the
 | |
| /// [`kernel::configfs_attrs`] macro.
 | |
| #[repr(transparent)]
 | |
| pub struct AttributeList<const N: usize, Data>(
 | |
|     /// Null terminated Array of pointers to [`Attribute`]. The type is [`c_void`]
 | |
|     /// to conform to the C API.
 | |
|     UnsafeCell<[*mut kernel::ffi::c_void; N]>,
 | |
|     PhantomData<Data>,
 | |
| );
 | |
| 
 | |
| // SAFETY: Ownership of `AttributeList` can safely be transferred to other threads.
 | |
| unsafe impl<const N: usize, Data> Send for AttributeList<N, Data> {}
 | |
| 
 | |
| // SAFETY: We do not provide any operations on `AttributeList` that need synchronization.
 | |
| unsafe impl<const N: usize, Data> Sync for AttributeList<N, Data> {}
 | |
| 
 | |
| impl<const N: usize, Data> AttributeList<N, Data> {
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// This function must only be called by the [`kernel::configfs_attrs`]
 | |
|     /// macro.
 | |
|     #[doc(hidden)]
 | |
|     pub const unsafe fn new() -> Self {
 | |
|         Self(UnsafeCell::new([core::ptr::null_mut(); N]), PhantomData)
 | |
|     }
 | |
| 
 | |
|     /// # Safety
 | |
|     ///
 | |
|     /// The caller must ensure that there are no other concurrent accesses to
 | |
|     /// `self`. That is, the caller has exclusive access to `self.`
 | |
|     #[doc(hidden)]
 | |
|     pub const unsafe fn add<const I: usize, const ID: u64, O>(
 | |
|         &'static self,
 | |
|         attribute: &'static Attribute<ID, O, Data>,
 | |
|     ) where
 | |
|         O: AttributeOperations<ID, Data = Data>,
 | |
|     {
 | |
|         // We need a space at the end of our list for a null terminator.
 | |
|         const { assert!(I < N - 1, "Invalid attribute index") };
 | |
| 
 | |
|         // SAFETY: By function safety requirements, we have exclusive access to
 | |
|         // `self` and the reference created below will be exclusive.
 | |
|         unsafe { (&mut *self.0.get())[I] = core::ptr::from_ref(attribute).cast_mut().cast() };
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A representation of the attributes that will appear in a [`Group`] or
 | |
| /// [`Subsystem`].
 | |
| ///
 | |
| /// Users should not directly instantiate objects of this type. Rather, they
 | |
| /// should use the [`kernel::configfs_attrs`] macro to statically declare the
 | |
| /// shape of a [`Group`] or [`Subsystem`].
 | |
| #[pin_data]
 | |
| pub struct ItemType<Container, Data> {
 | |
|     #[pin]
 | |
|     item_type: Opaque<bindings::config_item_type>,
 | |
|     _p: PhantomData<(Container, Data)>,
 | |
| }
 | |
| 
 | |
| // SAFETY: We do not provide any operations on `ItemType` that need synchronization.
 | |
| unsafe impl<Container, Data> Sync for ItemType<Container, Data> {}
 | |
| 
 | |
| // SAFETY: Ownership of `ItemType` can safely be transferred to other threads.
 | |
| unsafe impl<Container, Data> Send for ItemType<Container, Data> {}
 | |
| 
 | |
| macro_rules! impl_item_type {
 | |
|     ($tpe:ty) => {
 | |
|         impl<Data> ItemType<$tpe, Data> {
 | |
|             #[doc(hidden)]
 | |
|             pub const fn new_with_child_ctor<const N: usize, Child>(
 | |
|                 owner: &'static ThisModule,
 | |
|                 attributes: &'static AttributeList<N, Data>,
 | |
|             ) -> Self
 | |
|             where
 | |
|                 Data: GroupOperations<Child = Child>,
 | |
|                 Child: 'static,
 | |
|             {
 | |
|                 Self {
 | |
|                     item_type: Opaque::new(bindings::config_item_type {
 | |
|                         ct_owner: owner.as_ptr(),
 | |
|                         ct_group_ops: GroupOperationsVTable::<Data, Child>::vtable_ptr().cast_mut(),
 | |
|                         ct_item_ops: ItemOperationsVTable::<$tpe, Data>::vtable_ptr().cast_mut(),
 | |
|                         ct_attrs: core::ptr::from_ref(attributes).cast_mut().cast(),
 | |
|                         ct_bin_attrs: core::ptr::null_mut(),
 | |
|                     }),
 | |
|                     _p: PhantomData,
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             #[doc(hidden)]
 | |
|             pub const fn new<const N: usize>(
 | |
|                 owner: &'static ThisModule,
 | |
|                 attributes: &'static AttributeList<N, Data>,
 | |
|             ) -> Self {
 | |
|                 Self {
 | |
|                     item_type: Opaque::new(bindings::config_item_type {
 | |
|                         ct_owner: owner.as_ptr(),
 | |
|                         ct_group_ops: core::ptr::null_mut(),
 | |
|                         ct_item_ops: ItemOperationsVTable::<$tpe, Data>::vtable_ptr().cast_mut(),
 | |
|                         ct_attrs: core::ptr::from_ref(attributes).cast_mut().cast(),
 | |
|                         ct_bin_attrs: core::ptr::null_mut(),
 | |
|                     }),
 | |
|                     _p: PhantomData,
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| }
 | |
| 
 | |
| impl_item_type!(Subsystem<Data>);
 | |
| impl_item_type!(Group<Data>);
 | |
| 
 | |
| impl<Container, Data> ItemType<Container, Data> {
 | |
|     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(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 >]
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
| }
 |