diff options
Diffstat (limited to 'rust/kernel/debugfs.rs')
-rw-r--r-- | rust/kernel/debugfs.rs | 148 |
1 files changed, 147 insertions, 1 deletions
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 65be71600b8e..988aacdadbfa 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -8,12 +8,18 @@ // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful. #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] -#[cfg(CONFIG_DEBUG_FS)] use crate::prelude::*; use crate::str::CStr; #[cfg(CONFIG_DEBUG_FS)] use crate::sync::Arc; +use core::marker::PhantomPinned; +use core::ops::Deref; + +mod traits; +pub use traits::Writer; +mod file_ops; +use file_ops::{FileOps, ReadFile}; #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] @@ -53,6 +59,34 @@ impl Dir { Self() } + /// Creates a DebugFS file which will own the data produced by the initializer provided in + /// `data`. + fn create_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit<T, E> + 'a, + file_ops: &'static FileOps<T>, + ) -> impl PinInit<File<T>, E> + 'a + where + T: Sync + 'static, + { + let scope = Scope::<T>::new(data, move |data| { + #[cfg(CONFIG_DEBUG_FS)] + if let Some(parent) = &self.0 { + // SAFETY: Because data derives from a scope, and our entry will be dropped before + // the data is dropped, it is guaranteed to outlive the entry we return. + unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) } + } else { + Entry::empty() + } + }); + try_pin_init! { + File { + scope <- scope + } ? E + } + } + /// Create a new directory in DebugFS at the root. /// /// # Examples @@ -79,4 +113,116 @@ impl Dir { pub fn subdir(&self, name: &CStr) -> Self { Dir::create(name, Some(self)) } + + /// Creates a read-only file in this directory. + /// + /// The file's contents are produced by invoking [`Writer::write`] on the value initialized by + /// `data`. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// # use kernel::prelude::*; + /// # let dir = Dir::new(c_str!("my_debugfs_dir")); + /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?; + /// // "my_debugfs_dir/foo" now contains the number 200. + /// // The file is removed when `file` is dropped. + /// # Ok::<(), Error>(()) + /// ``` + pub fn read_only_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit<T, E> + 'a, + ) -> impl PinInit<File<T>, E> + 'a + where + T: Writer + Send + Sync + 'static, + { + let file_ops = &<T as ReadFile<_>>::FILE_OPS; + self.create_file(name, data, file_ops) + } +} + +#[pin_data] +/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the provided +/// [`Entry`] without moving. +/// Currently, this is used to back [`File`] so that its `read` and/or `write` implementations +/// can assume that their backing data is still alive. +struct Scope<T> { + // This order is load-bearing for drops - `_entry` must be dropped before `data`. + #[cfg(CONFIG_DEBUG_FS)] + _entry: Entry, + #[pin] + data: T, + // Even if `T` is `Unpin`, we still can't allow it to be moved. + #[pin] + _pin: PhantomPinned, +} + +#[pin_data] +/// Handle to a DebugFS file, owning its backing data. +/// +/// When dropped, the DebugFS file will be removed and the attached data will be dropped. +pub struct File<T> { + #[pin] + scope: Scope<T>, +} + +#[cfg(not(CONFIG_DEBUG_FS))] +impl<'b, T: 'b> Scope<T> { + fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b + where + F: for<'a> FnOnce(&'a T) + 'b, + { + try_pin_init! { + Self { + data <- data, + _pin: PhantomPinned + } ? E + } + .pin_chain(|scope| { + init(&scope.data); + Ok(()) + }) + } +} + +#[cfg(CONFIG_DEBUG_FS)] +impl<'b, T: 'b> Scope<T> { + fn entry_mut(self: Pin<&mut Self>) -> &mut Entry { + // SAFETY: _entry is not structurally pinned. + unsafe { &mut Pin::into_inner_unchecked(self)._entry } + } + + fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b + where + F: for<'a> FnOnce(&'a T) -> Entry + 'b, + { + try_pin_init! { + Self { + _entry: Entry::empty(), + data <- data, + _pin: PhantomPinned + } ? E + } + .pin_chain(|scope| { + *scope.entry_mut() = init(&scope.data); + Ok(()) + }) + } +} + +impl<T> Deref for Scope<T> { + type Target = T; + fn deref(&self) -> &T { + &self.data + } +} + +impl<T> Deref for File<T> { + type Target = T; + fn deref(&self) -> &T { + &self.scope + } } |