| // 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::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"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"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_char_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.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, | |
| }) | |
| } | |
| } | |
| // 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: crate::str::as_char_ptr_in_const_context(name), | |
| 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>( | |
| /// ::kernel::module::this_module::<crate::LocalModule>(), | |
| /// &CONFIGURATION_ATTRS | |
| /// ); | |
| /// | |
| /// &CONFIGURATION_TPE | |
| /// } | |
| /// ``` | |
| #[allow(clippy::crate_in_macro_def)] | |
| #[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( | |
| $crate::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>( | |
| $crate::module::this_module::<crate::LocalModule>(), | |
| &[<$ data:upper _ATTRS >] | |
| ); | |
| )? | |
| $( | |
| static [< $data:upper _TPE >]: | |
| $crate::configfs::ItemType<$container, $data> = | |
| $crate::configfs::ItemType::<$container, $data>:: | |
| new_with_child_ctor::<N, $child>( | |
| $crate::module::this_module::<crate::LocalModule>(), | |
| &[<$ data:upper _ATTRS >] | |
| ); | |
| )? | |
| & [< $data:upper _TPE >] | |
| } | |
| } | |
| }; | |
| } | |
| pub use crate::configfs_attrs; |