// 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::fmt;11use crate::prelude::*;12use crate::str::CStr;13#[cfg(CONFIG_DEBUG_FS)]14use crate::sync::Arc;15use crate::uaccess::UserSliceReader;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::{BinaryReader, BinaryReaderMut, BinaryWriter, Reader, Writer};2425mod callback_adapters;26use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter};27mod file_ops;28use file_ops::{29BinaryReadFile, BinaryReadWriteFile, BinaryWriteFile, FileOps, ReadFile, ReadWriteFile,30WriteFile,31};32#[cfg(CONFIG_DEBUG_FS)]33mod entry;34#[cfg(CONFIG_DEBUG_FS)]35use entry::Entry;3637/// Owning handle to a DebugFS directory.38///39/// The directory in the filesystem represented by [`Dir`] will be removed when handle has been40/// dropped *and* all children have been removed.41// If we have a parent, we hold a reference to it in the `Entry`. This prevents the `dentry`42// we point to from being cleaned up if our parent `Dir`/`Entry` is dropped before us.43//44// The `None` option indicates that the `Arc` could not be allocated, so our children would not be45// able to refer to us. In this case, we need to silently fail. All future child directories/files46// will silently fail as well.47#[derive(Clone)]48pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry<'static>>>);4950impl Dir {51/// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.52fn create(name: &CStr, parent: Option<&Dir>) -> Self {53#[cfg(CONFIG_DEBUG_FS)]54{55let parent_entry = match parent {56// If the parent couldn't be allocated, just early-return57Some(Dir(None)) => return Self(None),58Some(Dir(Some(entry))) => Some(entry.clone()),59None => None,60};61Self(62// If Arc creation fails, the `Entry` will be dropped, so the directory will be63// cleaned up.64Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(),65)66}67#[cfg(not(CONFIG_DEBUG_FS))]68Self()69}7071/// Creates a DebugFS file which will own the data produced by the initializer provided in72/// `data`.73fn create_file<'a, T, E: 'a>(74&'a self,75name: &'a CStr,76data: impl PinInit<T, E> + 'a,77file_ops: &'static FileOps<T>,78) -> impl PinInit<File<T>, E> + 'a79where80T: Sync + 'static,81{82let scope = Scope::<T>::new(data, move |data| {83#[cfg(CONFIG_DEBUG_FS)]84if let Some(parent) = &self.0 {85// SAFETY: Because data derives from a scope, and our entry will be dropped before86// the data is dropped, it is guaranteed to outlive the entry we return.87unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }88} else {89Entry::empty()90}91});92try_pin_init! {93File {94scope <- scope95} ? E96}97}9899/// Create a new directory in DebugFS at the root.100///101/// # Examples102///103/// ```104/// # use kernel::c_str;105/// # use kernel::debugfs::Dir;106/// let debugfs = Dir::new(c_str!("parent"));107/// ```108pub fn new(name: &CStr) -> Self {109Dir::create(name, None)110}111112/// Creates a subdirectory within this directory.113///114/// # Examples115///116/// ```117/// # use kernel::c_str;118/// # use kernel::debugfs::Dir;119/// let parent = Dir::new(c_str!("parent"));120/// let child = parent.subdir(c_str!("child"));121/// ```122pub fn subdir(&self, name: &CStr) -> Self {123Dir::create(name, Some(self))124}125126/// Creates a read-only file in this directory.127///128/// The file's contents are produced by invoking [`Writer::write`] on the value initialized by129/// `data`.130///131/// # Examples132///133/// ```134/// # use kernel::c_str;135/// # use kernel::debugfs::Dir;136/// # use kernel::prelude::*;137/// # let dir = Dir::new(c_str!("my_debugfs_dir"));138/// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;139/// // "my_debugfs_dir/foo" now contains the number 200.140/// // The file is removed when `file` is dropped.141/// # Ok::<(), Error>(())142/// ```143pub fn read_only_file<'a, T, E: 'a>(144&'a self,145name: &'a CStr,146data: impl PinInit<T, E> + 'a,147) -> impl PinInit<File<T>, E> + 'a148where149T: Writer + Send + Sync + 'static,150{151let file_ops = &<T as ReadFile<_>>::FILE_OPS;152self.create_file(name, data, file_ops)153}154155/// Creates a read-only binary file in this directory.156///157/// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`] on the value158/// initialized by `data`.159///160/// # Examples161///162/// ```163/// # use kernel::c_str;164/// # use kernel::debugfs::Dir;165/// # use kernel::prelude::*;166/// # let dir = Dir::new(c_str!("my_debugfs_dir"));167/// let file = KBox::pin_init(dir.read_binary_file(c_str!("foo"), [0x1, 0x2]), GFP_KERNEL)?;168/// # Ok::<(), Error>(())169/// ```170pub fn read_binary_file<'a, T, E: 'a>(171&'a self,172name: &'a CStr,173data: impl PinInit<T, E> + 'a,174) -> impl PinInit<File<T>, E> + 'a175where176T: BinaryWriter + Send + Sync + 'static,177{178self.create_file(name, data, &T::FILE_OPS)179}180181/// Creates a read-only file in this directory, with contents from a callback.182///183/// `f` must be a function item or a non-capturing closure.184/// This is statically asserted and not a safety requirement.185///186/// # Examples187///188/// ```189/// # use core::sync::atomic::{AtomicU32, Ordering};190/// # use kernel::c_str;191/// # use kernel::debugfs::Dir;192/// # use kernel::prelude::*;193/// # let dir = Dir::new(c_str!("foo"));194/// let file = KBox::pin_init(195/// dir.read_callback_file(c_str!("bar"),196/// AtomicU32::new(3),197/// &|val, f| {198/// let out = val.load(Ordering::Relaxed);199/// writeln!(f, "{out:#010x}")200/// }),201/// GFP_KERNEL)?;202/// // Reading "foo/bar" will show "0x00000003".203/// file.store(10, Ordering::Relaxed);204/// // Reading "foo/bar" will now show "0x0000000a".205/// # Ok::<(), Error>(())206/// ```207pub fn read_callback_file<'a, T, E: 'a, F>(208&'a self,209name: &'a CStr,210data: impl PinInit<T, E> + 'a,211_f: &'static F,212) -> impl PinInit<File<T>, E> + 'a213where214T: Send + Sync + 'static,215F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,216{217let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt();218self.create_file(name, data, file_ops)219}220221/// Creates a read-write file in this directory.222///223/// Reading the file uses the [`Writer`] implementation.224/// Writing to the file uses the [`Reader`] implementation.225pub fn read_write_file<'a, T, E: 'a>(226&'a self,227name: &'a CStr,228data: impl PinInit<T, E> + 'a,229) -> impl PinInit<File<T>, E> + 'a230where231T: Writer + Reader + Send + Sync + 'static,232{233let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;234self.create_file(name, data, file_ops)235}236237/// Creates a read-write binary file in this directory.238///239/// Reading the file uses the [`BinaryWriter`] implementation.240/// Writing to the file uses the [`BinaryReader`] implementation.241pub fn read_write_binary_file<'a, T, E: 'a>(242&'a self,243name: &'a CStr,244data: impl PinInit<T, E> + 'a,245) -> impl PinInit<File<T>, E> + 'a246where247T: BinaryWriter + BinaryReader + Send + Sync + 'static,248{249let file_ops = &<T as BinaryReadWriteFile<_>>::FILE_OPS;250self.create_file(name, data, file_ops)251}252253/// Creates a read-write file in this directory, with logic from callbacks.254///255/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.256///257/// `f` and `w` must be function items or non-capturing closures.258/// This is statically asserted and not a safety requirement.259pub fn read_write_callback_file<'a, T, E: 'a, F, W>(260&'a self,261name: &'a CStr,262data: impl PinInit<T, E> + 'a,263_f: &'static F,264_w: &'static W,265) -> impl PinInit<File<T>, E> + 'a266where267T: Send + Sync + 'static,268F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,269W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,270{271let file_ops =272<WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS273.adapt()274.adapt();275self.create_file(name, data, file_ops)276}277278/// Creates a write-only file in this directory.279///280/// The file owns its backing data. Writing to the file uses the [`Reader`]281/// implementation.282///283/// The file is removed when the returned [`File`] is dropped.284pub fn write_only_file<'a, T, E: 'a>(285&'a self,286name: &'a CStr,287data: impl PinInit<T, E> + 'a,288) -> impl PinInit<File<T>, E> + 'a289where290T: Reader + Send + Sync + 'static,291{292self.create_file(name, data, &T::FILE_OPS)293}294295/// Creates a write-only binary file in this directory.296///297/// The file owns its backing data. Writing to the file uses the [`BinaryReader`]298/// implementation.299///300/// The file is removed when the returned [`File`] is dropped.301pub fn write_binary_file<'a, T, E: 'a>(302&'a self,303name: &'a CStr,304data: impl PinInit<T, E> + 'a,305) -> impl PinInit<File<T>, E> + 'a306where307T: BinaryReader + Send + Sync + 'static,308{309self.create_file(name, data, &T::FILE_OPS)310}311312/// Creates a write-only file in this directory, with write logic from a callback.313///314/// `w` must be a function item or a non-capturing closure.315/// This is statically asserted and not a safety requirement.316pub fn write_callback_file<'a, T, E: 'a, W>(317&'a self,318name: &'a CStr,319data: impl PinInit<T, E> + 'a,320_w: &'static W,321) -> impl PinInit<File<T>, E> + 'a322where323T: Send + Sync + 'static,324W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,325{326let file_ops = <WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS327.adapt()328.adapt();329self.create_file(name, data, file_ops)330}331332// While this function is safe, it is intentionally not public because it's a bit of a333// footgun.334//335// Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate336// time, a `ScopedDir` with a `Dir` parent will never be deleted.337fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {338#[cfg(CONFIG_DEBUG_FS)]339{340let parent_entry = match &self.0 {341None => return ScopedDir::empty(),342Some(entry) => entry.clone(),343};344ScopedDir {345entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),346_phantom: PhantomData,347}348}349#[cfg(not(CONFIG_DEBUG_FS))]350ScopedDir::empty()351}352353/// Creates a new scope, which is a directory associated with some data `T`.354///355/// The created directory will be a subdirectory of `self`. The `init` closure is called to356/// populate the directory with files and subdirectories. These files can reference the data357/// stored in the scope.358///359/// The entire directory tree created within the scope will be removed when the returned360/// `Scope` handle is dropped.361pub fn scope<'a, T: 'a, E: 'a, F>(362&'a self,363data: impl PinInit<T, E> + 'a,364name: &'a CStr,365init: F,366) -> impl PinInit<Scope<T>, E> + 'a367where368F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,369{370Scope::new(data, |data| {371let scoped = self.scoped_dir(name);372init(data, &scoped);373scoped.into_entry()374})375}376}377378#[pin_data]379/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry380/// without moving.381///382/// This is internally used to back [`File`], and used in the API to represent the attachment383/// of a directory lifetime to a data structure which may be jointly accessed by a number of384/// different files.385///386/// When dropped, a `Scope` will remove all directories and files in the filesystem backed by the387/// attached data structure prior to releasing the attached data.388pub struct Scope<T> {389// This order is load-bearing for drops - `_entry` must be dropped before `data`.390#[cfg(CONFIG_DEBUG_FS)]391_entry: Entry<'static>,392#[pin]393data: T,394// Even if `T` is `Unpin`, we still can't allow it to be moved.395#[pin]396_pin: PhantomPinned,397}398399#[pin_data]400/// Handle to a DebugFS file, owning its backing data.401///402/// When dropped, the DebugFS file will be removed and the attached data will be dropped.403pub struct File<T> {404#[pin]405scope: Scope<T>,406}407408#[cfg(not(CONFIG_DEBUG_FS))]409impl<'b, T: 'b> Scope<T> {410fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b411where412F: for<'a> FnOnce(&'a T) + 'b,413{414try_pin_init! {415Self {416data <- data,417_pin: PhantomPinned418} ? E419}420.pin_chain(|scope| {421init(&scope.data);422Ok(())423})424}425}426427#[cfg(CONFIG_DEBUG_FS)]428impl<'b, T: 'b> Scope<T> {429fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {430// SAFETY: _entry is not structurally pinned.431unsafe { &mut Pin::into_inner_unchecked(self)._entry }432}433434fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b435where436F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b,437{438try_pin_init! {439Self {440_entry: Entry::empty(),441data <- data,442_pin: PhantomPinned443} ? E444}445.pin_chain(|scope| {446*scope.entry_mut() = init(&scope.data);447Ok(())448})449}450}451452impl<'a, T: 'a> Scope<T> {453/// Creates a new scope, which is a directory at the root of the debugfs filesystem,454/// associated with some data `T`.455///456/// The `init` closure is called to populate the directory with files and subdirectories. These457/// files can reference the data stored in the scope.458///459/// The entire directory tree created within the scope will be removed when the returned460/// `Scope` handle is dropped.461pub fn dir<E: 'a, F>(462data: impl PinInit<T, E> + 'a,463name: &'a CStr,464init: F,465) -> impl PinInit<Self, E> + 'a466where467F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,468{469Scope::new(data, |data| {470let scoped = ScopedDir::new(name);471init(data, &scoped);472scoped.into_entry()473})474}475}476477impl<T> Deref for Scope<T> {478type Target = T;479fn deref(&self) -> &T {480&self.data481}482}483484impl<T> Deref for File<T> {485type Target = T;486fn deref(&self) -> &T {487&self.scope488}489}490491/// A handle to a directory which will live at most `'dir`, accessing data that will live for at492/// least `'data`.493///494/// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping495/// the `Scope` that created it.496pub struct ScopedDir<'data, 'dir> {497#[cfg(CONFIG_DEBUG_FS)]498entry: ManuallyDrop<Entry<'dir>>,499_phantom: PhantomData<fn(&'data ()) -> &'dir ()>,500}501502impl<'data, 'dir> ScopedDir<'data, 'dir> {503/// Creates a subdirectory inside this `ScopedDir`.504///505/// The returned directory handle cannot outlive this one.506pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2> {507#[cfg(not(CONFIG_DEBUG_FS))]508let _ = name;509ScopedDir {510#[cfg(CONFIG_DEBUG_FS)]511entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))),512_phantom: PhantomData,513}514}515516fn create_file<T: Sync>(&self, name: &CStr, data: &'data T, vtable: &'static FileOps<T>) {517#[cfg(CONFIG_DEBUG_FS)]518core::mem::forget(Entry::file(name, &self.entry, data, vtable));519}520521/// Creates a read-only file in this directory.522///523/// The file's contents are produced by invoking [`Writer::write`].524///525/// This function does not produce an owning handle to the file. The created526/// file is removed when the [`Scope`] that this directory belongs527/// to is dropped.528pub fn read_only_file<T: Writer + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {529self.create_file(name, data, &T::FILE_OPS)530}531532/// Creates a read-only binary file in this directory.533///534/// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`].535///536/// This function does not produce an owning handle to the file. The created file is removed537/// when the [`Scope`] that this directory belongs to is dropped.538pub fn read_binary_file<T: BinaryWriter + Send + Sync + 'static>(539&self,540name: &CStr,541data: &'data T,542) {543self.create_file(name, data, &T::FILE_OPS)544}545546/// Creates a read-only file in this directory, with contents from a callback.547///548/// The file contents are generated by calling `f` with `data`.549///550///551/// `f` must be a function item or a non-capturing closure.552/// This is statically asserted and not a safety requirement.553///554/// This function does not produce an owning handle to the file. The created555/// file is removed when the [`Scope`] that this directory belongs556/// to is dropped.557pub fn read_callback_file<T, F>(&self, name: &CStr, data: &'data T, _f: &'static F)558where559T: Send + Sync + 'static,560F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,561{562let vtable = <FormatAdapter<T, F> as ReadFile<_>>::FILE_OPS.adapt();563self.create_file(name, data, vtable)564}565566/// Creates a read-write file in this directory.567///568/// Reading the file uses the [`Writer`] implementation on `data`. Writing to the file uses569/// the [`Reader`] implementation on `data`.570///571/// This function does not produce an owning handle to the file. The created572/// file is removed when the [`Scope`] that this directory belongs573/// to is dropped.574pub fn read_write_file<T: Writer + Reader + Send + Sync + 'static>(575&self,576name: &CStr,577data: &'data T,578) {579let vtable = &<T as ReadWriteFile<_>>::FILE_OPS;580self.create_file(name, data, vtable)581}582583/// Creates a read-write binary file in this directory.584///585/// Reading the file uses the [`BinaryWriter`] implementation on `data`. Writing to the file586/// uses the [`BinaryReader`] implementation on `data`.587///588/// This function does not produce an owning handle to the file. The created file is removed589/// when the [`Scope`] that this directory belongs to is dropped.590pub fn read_write_binary_file<T: BinaryWriter + BinaryReader + Send + Sync + 'static>(591&self,592name: &CStr,593data: &'data T,594) {595let vtable = &<T as BinaryReadWriteFile<_>>::FILE_OPS;596self.create_file(name, data, vtable)597}598599/// Creates a read-write file in this directory, with logic from callbacks.600///601/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.602///603/// `f` and `w` must be function items or non-capturing closures.604/// This is statically asserted and not a safety requirement.605///606/// This function does not produce an owning handle to the file. The created607/// file is removed when the [`Scope`] that this directory belongs608/// to is dropped.609pub fn read_write_callback_file<T, F, W>(610&self,611name: &CStr,612data: &'data T,613_f: &'static F,614_w: &'static W,615) where616T: Send + Sync + 'static,617F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,618W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,619{620let vtable = <WritableAdapter<FormatAdapter<T, F>, W> as ReadWriteFile<_>>::FILE_OPS621.adapt()622.adapt();623self.create_file(name, data, vtable)624}625626/// Creates a write-only file in this directory.627///628/// Writing to the file uses the [`Reader`] implementation on `data`.629///630/// This function does not produce an owning handle to the file. The created631/// file is removed when the [`Scope`] that this directory belongs632/// to is dropped.633pub fn write_only_file<T: Reader + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {634let vtable = &<T as WriteFile<_>>::FILE_OPS;635self.create_file(name, data, vtable)636}637638/// Creates a write-only binary file in this directory.639///640/// Writing to the file uses the [`BinaryReader`] implementation on `data`.641///642/// This function does not produce an owning handle to the file. The created file is removed643/// when the [`Scope`] that this directory belongs to is dropped.644pub fn write_binary_file<T: BinaryReader + Send + Sync + 'static>(645&self,646name: &CStr,647data: &'data T,648) {649self.create_file(name, data, &T::FILE_OPS)650}651652/// Creates a write-only file in this directory, with write logic from a callback.653///654/// Writing to the file is handled by `w`.655///656/// `w` must be a function item or a non-capturing closure.657/// This is statically asserted and not a safety requirement.658///659/// This function does not produce an owning handle to the file. The created660/// file is removed when the [`Scope`] that this directory belongs661/// to is dropped.662pub fn write_only_callback_file<T, W>(&self, name: &CStr, data: &'data T, _w: &'static W)663where664T: Send + Sync + 'static,665W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,666{667let vtable = &<WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS668.adapt()669.adapt();670self.create_file(name, data, vtable)671}672673fn empty() -> Self {674ScopedDir {675#[cfg(CONFIG_DEBUG_FS)]676entry: ManuallyDrop::new(Entry::empty()),677_phantom: PhantomData,678}679}680#[cfg(CONFIG_DEBUG_FS)]681fn into_entry(self) -> Entry<'dir> {682ManuallyDrop::into_inner(self.entry)683}684#[cfg(not(CONFIG_DEBUG_FS))]685fn into_entry(self) {}686}687688impl<'data> ScopedDir<'data, 'static> {689// This is safe, but intentionally not exported due to footgun status. A ScopedDir with no690// parent will never be released by default, and needs to have its entry extracted and used691// somewhere.692fn new(name: &CStr) -> ScopedDir<'data, 'static> {693ScopedDir {694#[cfg(CONFIG_DEBUG_FS)]695entry: ManuallyDrop::new(Entry::dir(name, None)),696_phantom: PhantomData,697}698}699}700701702