// 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) //! //! # Example //! //! ```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, //! } //! //! impl kernel::InPlaceModule for RustConfigfs { //! fn init(_module: &'static ThisModule) -> impl PinInit { //! pr_info!("Rust configfs sample (init)\n"); //! //! let item_type = configfs_attrs! { //! container: configfs::Subsystem, //! 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 { //! 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 { //! 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 { //! 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 { #[pin] subsystem: Opaque, #[pin] data: Data, } // SAFETY: We do not provide any operations on `Subsystem`. unsafe impl Sync for Subsystem {} // SAFETY: Ownership of `Subsystem` can safely be transferred to other threads. unsafe impl Send for Subsystem {} impl Subsystem { /// 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, Data>, data: impl PinInit, ) -> impl PinInit { try_pin_init!(Self { subsystem <- pin_init::zeroed().chain( |place: &mut Opaque| { // 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 PinnedDrop for Subsystem { 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 { /// 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` embeds a field of type `bindings::config_group` // within the `subsystem` field. unsafe impl HasGroup for Subsystem { 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::>(); // 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, 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 { #[pin] group: Opaque, #[pin] data: Data, } impl Group { /// 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, Data>, data: impl PinInit, ) -> impl PinInit { try_pin_init!(Self { group <- pin_init::zeroed().chain(|v: &mut Opaque| { 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` embeds a field of type `bindings::config_group` // within the `group` field. unsafe impl HasGroup for Group { unsafe fn group(this: *const Self) -> *const bindings::config_group { Opaque::raw_get( // 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::>(); // 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`. /// /// 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`. 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`. unsafe { &(*Group::::container_of(this)).data } } else { // SAFETY: By C API contract, `this` is a pointer to the // `bindings::config_group` field within a `Subsystem`. unsafe { &(*Subsystem::container_of(this)).data } } } struct GroupOperationsVTable(PhantomData<(Parent, Child)>); impl GroupOperationsVTable where Parent: GroupOperations, 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`. /// /// 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`. /// /// `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 = > as InPlaceInit>>::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`. unsafe { Group::::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`. /// /// 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`. /// /// `item` must point to a `bindings::config_item` within a /// `bindings::config_group` within a `Group`. 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`. let r_child_group_ptr = unsafe { Group::::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> = 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 as *const bindings::configfs_group_operations } } struct ItemOperationsVTable(PhantomData<(Container, Data)>); impl ItemOperationsVTable, Data> where Data: 'static, { /// # Safety /// /// `this` must be a pointer to a `bindings::config_group` embedded in a /// `Group`. /// /// 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`. let r_group_ptr = unsafe { Group::::container_of(c_group_ptr) }; // SAFETY: We called `into_raw` on `r_group_ptr` in // `make_group`. let pin_self: Arc> = 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 as *const bindings::configfs_item_operations } } impl ItemOperationsVTable, 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 as *const bindings::configfs_item_operations } } /// 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`. To prevent creation, /// return a suitable error. fn make_group(&self, name: &CStr) -> Result, 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>) { 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 { attribute: Opaque, _p: PhantomData<(O, Data)>, } // SAFETY: We do not provide any operations on `Attribute`. unsafe impl Sync for Attribute {} // SAFETY: Ownership of `Attribute` can safely be transferred to other threads. unsafe impl Send for Attribute {} impl Attribute where O: AttributeOperations, { /// # 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`. /// /// Otherwise, the group must be a embedded in a /// `bindings::configfs_subsystem` that is embedded in a `Subsystem`. /// /// `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) }.cast_mut(); // 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 as *mut [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`. /// /// Otherwise, the group must be a embedded in a /// `bindings::configfs_subsystem` that is embedded in a `Subsystem`. /// /// `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) }.cast_mut(); // 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`. 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 { /// 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; /// 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( /// 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, ); // SAFETY: Ownership of `AttributeList` can safely be transferred to other threads. unsafe impl Send for AttributeList {} // SAFETY: We do not provide any operations on `AttributeList` that need synchronization. unsafe impl Sync for AttributeList {} impl AttributeList { /// # 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( &'static self, attribute: &'static Attribute, ) where O: AttributeOperations, { // 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] = (attribute as *const 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 { #[pin] item_type: Opaque, _p: PhantomData<(Container, Data)>, } // SAFETY: We do not provide any operations on `ItemType` that need synchronization. unsafe impl Sync for ItemType {} // SAFETY: Ownership of `ItemType` can safely be transferred to other threads. unsafe impl Send for ItemType {} macro_rules! impl_item_type { ($tpe:ty) => { impl ItemType<$tpe, Data> { #[doc(hidden)] pub const fn new_with_child_ctor( owner: &'static ThisModule, attributes: &'static AttributeList, ) -> Self where Data: GroupOperations, Child: 'static, { Self { item_type: Opaque::new(bindings::config_item_type { ct_owner: owner.as_ptr(), ct_group_ops: GroupOperationsVTable::::vtable_ptr().cast_mut(), ct_item_ops: ItemOperationsVTable::<$tpe, Data>::vtable_ptr().cast_mut(), ct_attrs: (attributes as *const AttributeList) .cast_mut() .cast(), ct_bin_attrs: core::ptr::null_mut(), }), _p: PhantomData, } } #[doc(hidden)] pub const fn new( owner: &'static ThisModule, attributes: &'static AttributeList, ) -> 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: (attributes as *const AttributeList) .cast_mut() .cast(), ct_bin_attrs: core::ptr::null_mut(), }), _p: PhantomData, } } } }; } impl_item_type!(Subsystem); impl_item_type!(Group); impl ItemType { 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, /// 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 = /// unsafe { kernel::configfs::AttributeList::new() }; /// /// { /// const N: usize = 0usize; /// unsafe { CONFIGURATION_ATTRS.add::(&CONFIGURATION_MESSAGE_ATTR) }; /// } /// /// { /// const N: usize = (1usize + 0usize); /// unsafe { CONFIGURATION_ATTRS.add::(&CONFIGURATION_BAR_ATTR) }; /// } /// /// static CONFIGURATION_TPE: /// kernel::configfs::ItemType ,Configuration> /// = kernel::configfs::ItemType::< /// configfs::Subsystem, /// Configuration /// >::new_with_child_ctor::( /// &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::(&[< $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 = 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::( &THIS_MODULE, &[<$ data:upper _ATTRS >] ); )? $( static [< $data:upper _TPE >]: $crate::configfs::ItemType<$container, $data> = $crate::configfs::ItemType::<$container, $data>:: new_with_child_ctor::( &THIS_MODULE, &[<$ data:upper _ATTRS >] ); )? & [< $data:upper _TPE >] } } }; }