1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (C) 2025 Google LLC. 3 4 //! DebugFS Abstraction 5 //! 6 //! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h) 7 8 // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful. 9 #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] 10 11 use crate::prelude::*; 12 use crate::str::CStr; 13 #[cfg(CONFIG_DEBUG_FS)] 14 use crate::sync::Arc; 15 use core::marker::PhantomPinned; 16 use core::ops::Deref; 17 18 mod traits; 19 pub use traits::{Reader, Writer}; 20 21 mod file_ops; 22 use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile}; 23 #[cfg(CONFIG_DEBUG_FS)] 24 mod entry; 25 #[cfg(CONFIG_DEBUG_FS)] 26 use entry::Entry; 27 28 /// Owning handle to a DebugFS directory. 29 /// 30 /// The directory in the filesystem represented by [`Dir`] will be removed when handle has been 31 /// dropped *and* all children have been removed. 32 // If we have a parent, we hold a reference to it in the `Entry`. This prevents the `dentry` 33 // we point to from being cleaned up if our parent `Dir`/`Entry` is dropped before us. 34 // 35 // The `None` option indicates that the `Arc` could not be allocated, so our children would not be 36 // able to refer to us. In this case, we need to silently fail. All future child directories/files 37 // will silently fail as well. 38 #[derive(Clone)] 39 pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry>>); 40 41 impl Dir { 42 /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root. 43 fn create(name: &CStr, parent: Option<&Dir>) -> Self { 44 #[cfg(CONFIG_DEBUG_FS)] 45 { 46 let parent_entry = match parent { 47 // If the parent couldn't be allocated, just early-return 48 Some(Dir(None)) => return Self(None), 49 Some(Dir(Some(entry))) => Some(entry.clone()), 50 None => None, 51 }; 52 Self( 53 // If Arc creation fails, the `Entry` will be dropped, so the directory will be 54 // cleaned up. 55 Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(), 56 ) 57 } 58 #[cfg(not(CONFIG_DEBUG_FS))] 59 Self() 60 } 61 62 /// Creates a DebugFS file which will own the data produced by the initializer provided in 63 /// `data`. 64 fn create_file<'a, T, E: 'a>( 65 &'a self, 66 name: &'a CStr, 67 data: impl PinInit<T, E> + 'a, 68 file_ops: &'static FileOps<T>, 69 ) -> impl PinInit<File<T>, E> + 'a 70 where 71 T: Sync + 'static, 72 { 73 let scope = Scope::<T>::new(data, move |data| { 74 #[cfg(CONFIG_DEBUG_FS)] 75 if let Some(parent) = &self.0 { 76 // SAFETY: Because data derives from a scope, and our entry will be dropped before 77 // the data is dropped, it is guaranteed to outlive the entry we return. 78 unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) } 79 } else { 80 Entry::empty() 81 } 82 }); 83 try_pin_init! { 84 File { 85 scope <- scope 86 } ? E 87 } 88 } 89 90 /// Create a new directory in DebugFS at the root. 91 /// 92 /// # Examples 93 /// 94 /// ``` 95 /// # use kernel::c_str; 96 /// # use kernel::debugfs::Dir; 97 /// let debugfs = Dir::new(c_str!("parent")); 98 /// ``` 99 pub fn new(name: &CStr) -> Self { 100 Dir::create(name, None) 101 } 102 103 /// Creates a subdirectory within this directory. 104 /// 105 /// # Examples 106 /// 107 /// ``` 108 /// # use kernel::c_str; 109 /// # use kernel::debugfs::Dir; 110 /// let parent = Dir::new(c_str!("parent")); 111 /// let child = parent.subdir(c_str!("child")); 112 /// ``` 113 pub fn subdir(&self, name: &CStr) -> Self { 114 Dir::create(name, Some(self)) 115 } 116 117 /// Creates a read-only file in this directory. 118 /// 119 /// The file's contents are produced by invoking [`Writer::write`] on the value initialized by 120 /// `data`. 121 /// 122 /// # Examples 123 /// 124 /// ``` 125 /// # use kernel::c_str; 126 /// # use kernel::debugfs::Dir; 127 /// # use kernel::prelude::*; 128 /// # let dir = Dir::new(c_str!("my_debugfs_dir")); 129 /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?; 130 /// // "my_debugfs_dir/foo" now contains the number 200. 131 /// // The file is removed when `file` is dropped. 132 /// # Ok::<(), Error>(()) 133 /// ``` 134 pub fn read_only_file<'a, T, E: 'a>( 135 &'a self, 136 name: &'a CStr, 137 data: impl PinInit<T, E> + 'a, 138 ) -> impl PinInit<File<T>, E> + 'a 139 where 140 T: Writer + Send + Sync + 'static, 141 { 142 let file_ops = &<T as ReadFile<_>>::FILE_OPS; 143 self.create_file(name, data, file_ops) 144 } 145 146 /// Creates a read-write file in this directory. 147 /// 148 /// Reading the file uses the [`Writer`] implementation. 149 /// Writing to the file uses the [`Reader`] implementation. 150 pub fn read_write_file<'a, T, E: 'a>( 151 &'a self, 152 name: &'a CStr, 153 data: impl PinInit<T, E> + 'a, 154 ) -> impl PinInit<File<T>, E> + 'a 155 where 156 T: Writer + Reader + Send + Sync + 'static, 157 { 158 let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS; 159 self.create_file(name, data, file_ops) 160 } 161 162 /// Creates a write-only file in this directory. 163 /// 164 /// The file owns its backing data. Writing to the file uses the [`Reader`] 165 /// implementation. 166 /// 167 /// The file is removed when the returned [`File`] is dropped. 168 pub fn write_only_file<'a, T, E: 'a>( 169 &'a self, 170 name: &'a CStr, 171 data: impl PinInit<T, E> + 'a, 172 ) -> impl PinInit<File<T>, E> + 'a 173 where 174 T: Reader + Send + Sync + 'static, 175 { 176 self.create_file(name, data, &T::FILE_OPS) 177 } 178 } 179 180 #[pin_data] 181 /// Handle to a DebugFS scope, which ensures that attached `data` will outlive the provided 182 /// [`Entry`] without moving. 183 /// Currently, this is used to back [`File`] so that its `read` and/or `write` implementations 184 /// can assume that their backing data is still alive. 185 struct Scope<T> { 186 // This order is load-bearing for drops - `_entry` must be dropped before `data`. 187 #[cfg(CONFIG_DEBUG_FS)] 188 _entry: Entry, 189 #[pin] 190 data: T, 191 // Even if `T` is `Unpin`, we still can't allow it to be moved. 192 #[pin] 193 _pin: PhantomPinned, 194 } 195 196 #[pin_data] 197 /// Handle to a DebugFS file, owning its backing data. 198 /// 199 /// When dropped, the DebugFS file will be removed and the attached data will be dropped. 200 pub struct File<T> { 201 #[pin] 202 scope: Scope<T>, 203 } 204 205 #[cfg(not(CONFIG_DEBUG_FS))] 206 impl<'b, T: 'b> Scope<T> { 207 fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b 208 where 209 F: for<'a> FnOnce(&'a T) + 'b, 210 { 211 try_pin_init! { 212 Self { 213 data <- data, 214 _pin: PhantomPinned 215 } ? E 216 } 217 .pin_chain(|scope| { 218 init(&scope.data); 219 Ok(()) 220 }) 221 } 222 } 223 224 #[cfg(CONFIG_DEBUG_FS)] 225 impl<'b, T: 'b> Scope<T> { 226 fn entry_mut(self: Pin<&mut Self>) -> &mut Entry { 227 // SAFETY: _entry is not structurally pinned. 228 unsafe { &mut Pin::into_inner_unchecked(self)._entry } 229 } 230 231 fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b 232 where 233 F: for<'a> FnOnce(&'a T) -> Entry + 'b, 234 { 235 try_pin_init! { 236 Self { 237 _entry: Entry::empty(), 238 data <- data, 239 _pin: PhantomPinned 240 } ? E 241 } 242 .pin_chain(|scope| { 243 *scope.entry_mut() = init(&scope.data); 244 Ok(()) 245 }) 246 } 247 } 248 249 impl<T> Deref for Scope<T> { 250 type Target = T; 251 fn deref(&self) -> &T { 252 &self.data 253 } 254 } 255 256 impl<T> Deref for File<T> { 257 type Target = T; 258 fn deref(&self) -> &T { 259 &self.scope 260 } 261 } 262