// SPDX-License-Identifier: GPL-2.01// Copyright (C) 2025 Google LLC.23//! DebugFS Abstraction4//!5//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)67// When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful.8#![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))]910use crate::prelude::*;11use crate::str::CStr;12#[cfg(CONFIG_DEBUG_FS)]13use crate::sync::Arc;14use crate::uaccess::UserSliceReader;15use core::fmt;16use core::marker::PhantomData;17use core::marker::PhantomPinned;18#[cfg(CONFIG_DEBUG_FS)]19use core::mem::ManuallyDrop;20use core::ops::Deref;2122mod traits;23pub use traits::{Reader, Writer};2425mod callback_adapters;26use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter};27mod file_ops;28use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};29#[cfg(CONFIG_DEBUG_FS)]30mod entry;31#[cfg(CONFIG_DEBUG_FS)]32use entry::Entry;3334/// Owning handle to a DebugFS directory.35///36/// The directory in the filesystem represented by [`Dir`] will be removed when handle has been37/// dropped *and* all children have been removed.38// If we have a parent, we hold a reference to it in the `Entry`. This prevents the `dentry`39// we point to from being cleaned up if our parent `Dir`/`Entry` is dropped before us.40//41// The `None` option indicates that the `Arc` could not be allocated, so our children would not be42// able to refer to us. In this case, we need to silently fail. All future child directories/files43// will silently fail as well.44#[derive(Clone)]45pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry<'static>>>);4647impl Dir {48/// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.49fn create(name: &CStr, parent: Option<&Dir>) -> Self {50#[cfg(CONFIG_DEBUG_FS)]51{52let parent_entry = match parent {53// If the parent couldn't be allocated, just early-return54Some(Dir(None)) => return Self(None),55Some(Dir(Some(entry))) => Some(entry.clone()),56None => None,57};58Self(59// If Arc creation fails, the `Entry` will be dropped, so the directory will be60// cleaned up.61Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(),62)63}64#[cfg(not(CONFIG_DEBUG_FS))]65Self()66}6768/// Creates a DebugFS file which will own the data produced by the initializer provided in69/// `data`.70fn create_file<'a, T, E: 'a>(71&'a self,72name: &'a CStr,73data: impl PinInit<T, E> + 'a,74file_ops: &'static FileOps<T>,75) -> impl PinInit<File<T>, E> + 'a76where77T: Sync + 'static,78{79let scope = Scope::<T>::new(data, move |data| {80#[cfg(CONFIG_DEBUG_FS)]81if let Some(parent) = &self.0 {82// SAFETY: Because data derives from a scope, and our entry will be dropped before83// the data is dropped, it is guaranteed to outlive the entry we return.84unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }85} else {86Entry::empty()87}88});89try_pin_init! {90File {91scope <- scope92} ? E93}94}9596/// Create a new directory in DebugFS at the root.97///98/// # Examples99///100/// ```101/// # use kernel::c_str;102/// # use kernel::debugfs::Dir;103/// let debugfs = Dir::new(c_str!("parent"));104/// ```105pub fn new(name: &CStr) -> Self {106Dir::create(name, None)107}108109/// Creates a subdirectory within this directory.110///111/// # Examples112///113/// ```114/// # use kernel::c_str;115/// # use kernel::debugfs::Dir;116/// let parent = Dir::new(c_str!("parent"));117/// let child = parent.subdir(c_str!("child"));118/// ```119pub fn subdir(&self, name: &CStr) -> Self {120Dir::create(name, Some(self))121}122123/// Creates a read-only file in this directory.124///125/// The file's contents are produced by invoking [`Writer::write`] on the value initialized by126/// `data`.127///128/// # Examples129///130/// ```131/// # use kernel::c_str;132/// # use kernel::debugfs::Dir;133/// # use kernel::prelude::*;134/// # let dir = Dir::new(c_str!("my_debugfs_dir"));135/// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;136/// // "my_debugfs_dir/foo" now contains the number 200.137/// // The file is removed when `file` is dropped.138/// # Ok::<(), Error>(())139/// ```140pub fn read_only_file<'a, T, E: 'a>(141&'a self,142name: &'a CStr,143data: impl PinInit<T, E> + 'a,144) -> impl PinInit<File<T>, E> + 'a145where146T: Writer + Send + Sync + 'static,147{148let file_ops = &<T as ReadFile<_>>::FILE_OPS;149self.create_file(name, data, file_ops)150}151152/// Creates a read-only file in this directory, with contents from a callback.153///154/// `f` must be a function item or a non-capturing closure.155/// This is statically asserted and not a safety requirement.156///157/// # Examples158///159/// ```160/// # use core::sync::atomic::{AtomicU32, Ordering};161/// # use kernel::c_str;162/// # use kernel::debugfs::Dir;163/// # use kernel::prelude::*;164/// # let dir = Dir::new(c_str!("foo"));165/// let file = KBox::pin_init(166/// dir.read_callback_file(c_str!("bar"),167/// AtomicU32::new(3),168/// &|val, f| {169/// let out = val.load(Ordering::Relaxed);170/// writeln!(f, "{out:#010x}")171/// }),172/// GFP_KERNEL)?;173/// // Reading "foo/bar" will show "0x00000003".174/// file.store(10, Ordering::Relaxed);175/// // Reading "foo/bar" will now show "0x0000000a".176/// # Ok::<(), Error>(())177/// ```178pub fn read_callback_file<'a, T, E: 'a, F>(179&'a self,180name: &'a CStr,181data: impl PinInit<T, E> + 'a,182_f: &'static F,183) -> impl PinInit<File<T>, E> + 'a184where185T: Send + Sync + 'static,186F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,187{188let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt();189self.create_file(name, data, file_ops)190}191192/// Creates a read-write file in this directory.193///194/// Reading the file uses the [`Writer`] implementation.195/// Writing to the file uses the [`Reader`] implementation.196pub fn read_write_file<'a, T, E: 'a>(197&'a self,198name: &'a CStr,199data: impl PinInit<T, E> + 'a,200) -> impl PinInit<File<T>, E> + 'a201where202T: Writer + Reader + Send + Sync + 'static,203{204let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;205self.create_file(name, data, file_ops)206}207208/// Creates a read-write file in this directory, with logic from callbacks.209///210/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.211///212/// `f` and `w` must be function items or non-capturing closures.213/// This is statically asserted and not a safety requirement.214pub fn read_write_callback_file<'a, T, E: 'a, F, W>(215&'a self,216name: &'a CStr,217data: impl PinInit<T, E> + 'a,218_f: &'static F,219_w: &'static W,220) -> impl PinInit<File<T>, E> + 'a221where222T: Send + Sync + 'static,223F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,224W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,225{226let file_ops =227<WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS228.adapt()229.adapt();230self.create_file(name, data, file_ops)231}232233/// Creates a write-only file in this directory.234///235/// The file owns its backing data. Writing to the file uses the [`Reader`]236/// implementation.237///238/// The file is removed when the returned [`File`] is dropped.239pub fn write_only_file<'a, T, E: 'a>(240&'a self,241name: &'a CStr,242data: impl PinInit<T, E> + 'a,243) -> impl PinInit<File<T>, E> + 'a244where245T: Reader + Send + Sync + 'static,246{247self.create_file(name, data, &T::FILE_OPS)248}249250/// Creates a write-only file in this directory, with write logic from a callback.251///252/// `w` must be a function item or a non-capturing closure.253/// This is statically asserted and not a safety requirement.254pub fn write_callback_file<'a, T, E: 'a, W>(255&'a self,256name: &'a CStr,257data: impl PinInit<T, E> + 'a,258_w: &'static W,259) -> impl PinInit<File<T>, E> + 'a260where261T: Send + Sync + 'static,262W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,263{264let file_ops = <WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS265.adapt()266.adapt();267self.create_file(name, data, file_ops)268}269270// While this function is safe, it is intentionally not public because it's a bit of a271// footgun.272//273// Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate274// time, a `ScopedDir` with a `Dir` parent will never be deleted.275fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {276#[cfg(CONFIG_DEBUG_FS)]277{278let parent_entry = match &self.0 {279None => return ScopedDir::empty(),280Some(entry) => entry.clone(),281};282ScopedDir {283entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),284_phantom: PhantomData,285}286}287#[cfg(not(CONFIG_DEBUG_FS))]288ScopedDir::empty()289}290291/// Creates a new scope, which is a directory associated with some data `T`.292///293/// The created directory will be a subdirectory of `self`. The `init` closure is called to294/// populate the directory with files and subdirectories. These files can reference the data295/// stored in the scope.296///297/// The entire directory tree created within the scope will be removed when the returned298/// `Scope` handle is dropped.299pub fn scope<'a, T: 'a, E: 'a, F>(300&'a self,301data: impl PinInit<T, E> + 'a,302name: &'a CStr,303init: F,304) -> impl PinInit<Scope<T>, E> + 'a305where306F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,307{308Scope::new(data, |data| {309let scoped = self.scoped_dir(name);310init(data, &scoped);311scoped.into_entry()312})313}314}315316#[pin_data]317/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry318/// without moving.319///320/// This is internally used to back [`File`], and used in the API to represent the attachment321/// of a directory lifetime to a data structure which may be jointly accessed by a number of322/// different files.323///324/// When dropped, a `Scope` will remove all directories and files in the filesystem backed by the325/// attached data structure prior to releasing the attached data.326pub struct Scope<T> {327// This order is load-bearing for drops - `_entry` must be dropped before `data`.328#[cfg(CONFIG_DEBUG_FS)]329_entry: Entry<'static>,330#[pin]331data: T,332// Even if `T` is `Unpin`, we still can't allow it to be moved.333#[pin]334_pin: PhantomPinned,335}336337#[pin_data]338/// Handle to a DebugFS file, owning its backing data.339///340/// When dropped, the DebugFS file will be removed and the attached data will be dropped.341pub struct File<T> {342#[pin]343scope: Scope<T>,344}345346#[cfg(not(CONFIG_DEBUG_FS))]347impl<'b, T: 'b> Scope<T> {348fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b349where350F: for<'a> FnOnce(&'a T) + 'b,351{352try_pin_init! {353Self {354data <- data,355_pin: PhantomPinned356} ? E357}358.pin_chain(|scope| {359init(&scope.data);360Ok(())361})362}363}364365#[cfg(CONFIG_DEBUG_FS)]366impl<'b, T: 'b> Scope<T> {367fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {368// SAFETY: _entry is not structurally pinned.369unsafe { &mut Pin::into_inner_unchecked(self)._entry }370}371372fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b373where374F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b,375{376try_pin_init! {377Self {378_entry: Entry::empty(),379data <- data,380_pin: PhantomPinned381} ? E382}383.pin_chain(|scope| {384*scope.entry_mut() = init(&scope.data);385Ok(())386})387}388}389390impl<'a, T: 'a> Scope<T> {391/// Creates a new scope, which is a directory at the root of the debugfs filesystem,392/// associated with some data `T`.393///394/// The `init` closure is called to populate the directory with files and subdirectories. These395/// files can reference the data stored in the scope.396///397/// The entire directory tree created within the scope will be removed when the returned398/// `Scope` handle is dropped.399pub fn dir<E: 'a, F>(400data: impl PinInit<T, E> + 'a,401name: &'a CStr,402init: F,403) -> impl PinInit<Self, E> + 'a404where405F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,406{407Scope::new(data, |data| {408let scoped = ScopedDir::new(name);409init(data, &scoped);410scoped.into_entry()411})412}413}414415impl<T> Deref for Scope<T> {416type Target = T;417fn deref(&self) -> &T {418&self.data419}420}421422impl<T> Deref for File<T> {423type Target = T;424fn deref(&self) -> &T {425&self.scope426}427}428429/// A handle to a directory which will live at most `'dir`, accessing data that will live for at430/// least `'data`.431///432/// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping433/// the `Scope` that created it.434pub struct ScopedDir<'data, 'dir> {435#[cfg(CONFIG_DEBUG_FS)]436entry: ManuallyDrop<Entry<'dir>>,437_phantom: PhantomData<fn(&'data ()) -> &'dir ()>,438}439440impl<'data, 'dir> ScopedDir<'data, 'dir> {441/// Creates a subdirectory inside this `ScopedDir`.442///443/// The returned directory handle cannot outlive this one.444pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2> {445#[cfg(not(CONFIG_DEBUG_FS))]446let _ = name;447ScopedDir {448#[cfg(CONFIG_DEBUG_FS)]449entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))),450_phantom: PhantomData,451}452}453454fn create_file<T: Sync>(&self, name: &CStr, data: &'data T, vtable: &'static FileOps<T>) {455#[cfg(CONFIG_DEBUG_FS)]456core::mem::forget(Entry::file(name, &self.entry, data, vtable));457}458459/// Creates a read-only file in this directory.460///461/// The file's contents are produced by invoking [`Writer::write`].462///463/// This function does not produce an owning handle to the file. The created464/// file is removed when the [`Scope`] that this directory belongs465/// to is dropped.466pub fn read_only_file<T: Writer + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {467self.create_file(name, data, &T::FILE_OPS)468}469470/// Creates a read-only file in this directory, with contents from a callback.471///472/// The file contents are generated by calling `f` with `data`.473///474///475/// `f` must be a function item or a non-capturing closure.476/// This is statically asserted and not a safety requirement.477///478/// This function does not produce an owning handle to the file. The created479/// file is removed when the [`Scope`] that this directory belongs480/// to is dropped.481pub fn read_callback_file<T, F>(&self, name: &CStr, data: &'data T, _f: &'static F)482where483T: Send + Sync + 'static,484F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,485{486let vtable = <FormatAdapter<T, F> as ReadFile<_>>::FILE_OPS.adapt();487self.create_file(name, data, vtable)488}489490/// Creates a read-write file in this directory.491///492/// Reading the file uses the [`Writer`] implementation on `data`. Writing to the file uses493/// the [`Reader`] implementation on `data`.494///495/// This function does not produce an owning handle to the file. The created496/// file is removed when the [`Scope`] that this directory belongs497/// to is dropped.498pub fn read_write_file<T: Writer + Reader + Send + Sync + 'static>(499&self,500name: &CStr,501data: &'data T,502) {503let vtable = &<T as ReadWriteFile<_>>::FILE_OPS;504self.create_file(name, data, vtable)505}506507/// Creates a read-write file in this directory, with logic from callbacks.508///509/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.510///511/// `f` and `w` must be function items or non-capturing closures.512/// This is statically asserted and not a safety requirement.513///514/// This function does not produce an owning handle to the file. The created515/// file is removed when the [`Scope`] that this directory belongs516/// to is dropped.517pub fn read_write_callback_file<T, F, W>(518&self,519name: &CStr,520data: &'data T,521_f: &'static F,522_w: &'static W,523) where524T: Send + Sync + 'static,525F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,526W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,527{528let vtable = <WritableAdapter<FormatAdapter<T, F>, W> as ReadWriteFile<_>>::FILE_OPS529.adapt()530.adapt();531self.create_file(name, data, vtable)532}533534/// Creates a write-only file in this directory.535///536/// Writing to the file uses the [`Reader`] implementation on `data`.537///538/// This function does not produce an owning handle to the file. The created539/// file is removed when the [`Scope`] that this directory belongs540/// to is dropped.541pub fn write_only_file<T: Reader + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {542let vtable = &<T as WriteFile<_>>::FILE_OPS;543self.create_file(name, data, vtable)544}545546/// Creates a write-only file in this directory, with write logic from a callback.547///548/// Writing to the file is handled by `w`.549///550/// `w` must be a function item or a non-capturing closure.551/// This is statically asserted and not a safety requirement.552///553/// This function does not produce an owning handle to the file. The created554/// file is removed when the [`Scope`] that this directory belongs555/// to is dropped.556pub fn write_only_callback_file<T, W>(&self, name: &CStr, data: &'data T, _w: &'static W)557where558T: Send + Sync + 'static,559W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,560{561let vtable = &<WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS562.adapt()563.adapt();564self.create_file(name, data, vtable)565}566567fn empty() -> Self {568ScopedDir {569#[cfg(CONFIG_DEBUG_FS)]570entry: ManuallyDrop::new(Entry::empty()),571_phantom: PhantomData,572}573}574#[cfg(CONFIG_DEBUG_FS)]575fn into_entry(self) -> Entry<'dir> {576ManuallyDrop::into_inner(self.entry)577}578#[cfg(not(CONFIG_DEBUG_FS))]579fn into_entry(self) {}580}581582impl<'data> ScopedDir<'data, 'static> {583// This is safe, but intentionally not exported due to footgun status. A ScopedDir with no584// parent will never be released by default, and needs to have its entry extracted and used585// somewhere.586fn new(name: &CStr) -> ScopedDir<'data, 'static> {587ScopedDir {588#[cfg(CONFIG_DEBUG_FS)]589entry: ManuallyDrop::new(Entry::dir(name, None)),590_phantom: PhantomData,591}592}593}594595596