Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/rust/kernel/debugfs.rs
29266 views
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 crate::uaccess::UserSliceReader;
16
use core::fmt;
17
use core::marker::PhantomData;
18
use core::marker::PhantomPinned;
19
#[cfg(CONFIG_DEBUG_FS)]
20
use core::mem::ManuallyDrop;
21
use core::ops::Deref;
22
23
mod traits;
24
pub use traits::{Reader, Writer};
25
26
mod callback_adapters;
27
use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter};
28
mod file_ops;
29
use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
30
#[cfg(CONFIG_DEBUG_FS)]
31
mod entry;
32
#[cfg(CONFIG_DEBUG_FS)]
33
use entry::Entry;
34
35
/// Owning handle to a DebugFS directory.
36
///
37
/// The directory in the filesystem represented by [`Dir`] will be removed when handle has been
38
/// dropped *and* all children have been removed.
39
// If we have a parent, we hold a reference to it in the `Entry`. This prevents the `dentry`
40
// we point to from being cleaned up if our parent `Dir`/`Entry` is dropped before us.
41
//
42
// The `None` option indicates that the `Arc` could not be allocated, so our children would not be
43
// able to refer to us. In this case, we need to silently fail. All future child directories/files
44
// will silently fail as well.
45
#[derive(Clone)]
46
pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry<'static>>>);
47
48
impl Dir {
49
/// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
50
fn create(name: &CStr, parent: Option<&Dir>) -> Self {
51
#[cfg(CONFIG_DEBUG_FS)]
52
{
53
let parent_entry = match parent {
54
// If the parent couldn't be allocated, just early-return
55
Some(Dir(None)) => return Self(None),
56
Some(Dir(Some(entry))) => Some(entry.clone()),
57
None => None,
58
};
59
Self(
60
// If Arc creation fails, the `Entry` will be dropped, so the directory will be
61
// cleaned up.
62
Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(),
63
)
64
}
65
#[cfg(not(CONFIG_DEBUG_FS))]
66
Self()
67
}
68
69
/// Creates a DebugFS file which will own the data produced by the initializer provided in
70
/// `data`.
71
fn create_file<'a, T, E: 'a>(
72
&'a self,
73
name: &'a CStr,
74
data: impl PinInit<T, E> + 'a,
75
file_ops: &'static FileOps<T>,
76
) -> impl PinInit<File<T>, E> + 'a
77
where
78
T: Sync + 'static,
79
{
80
let scope = Scope::<T>::new(data, move |data| {
81
#[cfg(CONFIG_DEBUG_FS)]
82
if let Some(parent) = &self.0 {
83
// SAFETY: Because data derives from a scope, and our entry will be dropped before
84
// the data is dropped, it is guaranteed to outlive the entry we return.
85
unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }
86
} else {
87
Entry::empty()
88
}
89
});
90
try_pin_init! {
91
File {
92
scope <- scope
93
} ? E
94
}
95
}
96
97
/// Create a new directory in DebugFS at the root.
98
///
99
/// # Examples
100
///
101
/// ```
102
/// # use kernel::c_str;
103
/// # use kernel::debugfs::Dir;
104
/// let debugfs = Dir::new(c_str!("parent"));
105
/// ```
106
pub fn new(name: &CStr) -> Self {
107
Dir::create(name, None)
108
}
109
110
/// Creates a subdirectory within this directory.
111
///
112
/// # Examples
113
///
114
/// ```
115
/// # use kernel::c_str;
116
/// # use kernel::debugfs::Dir;
117
/// let parent = Dir::new(c_str!("parent"));
118
/// let child = parent.subdir(c_str!("child"));
119
/// ```
120
pub fn subdir(&self, name: &CStr) -> Self {
121
Dir::create(name, Some(self))
122
}
123
124
/// Creates a read-only file in this directory.
125
///
126
/// The file's contents are produced by invoking [`Writer::write`] on the value initialized by
127
/// `data`.
128
///
129
/// # Examples
130
///
131
/// ```
132
/// # use kernel::c_str;
133
/// # use kernel::debugfs::Dir;
134
/// # use kernel::prelude::*;
135
/// # let dir = Dir::new(c_str!("my_debugfs_dir"));
136
/// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;
137
/// // "my_debugfs_dir/foo" now contains the number 200.
138
/// // The file is removed when `file` is dropped.
139
/// # Ok::<(), Error>(())
140
/// ```
141
pub fn read_only_file<'a, T, E: 'a>(
142
&'a self,
143
name: &'a CStr,
144
data: impl PinInit<T, E> + 'a,
145
) -> impl PinInit<File<T>, E> + 'a
146
where
147
T: Writer + Send + Sync + 'static,
148
{
149
let file_ops = &<T as ReadFile<_>>::FILE_OPS;
150
self.create_file(name, data, file_ops)
151
}
152
153
/// Creates a read-only file in this directory, with contents from a callback.
154
///
155
/// `f` must be a function item or a non-capturing closure.
156
/// This is statically asserted and not a safety requirement.
157
///
158
/// # Examples
159
///
160
/// ```
161
/// # use core::sync::atomic::{AtomicU32, Ordering};
162
/// # use kernel::c_str;
163
/// # use kernel::debugfs::Dir;
164
/// # use kernel::prelude::*;
165
/// # let dir = Dir::new(c_str!("foo"));
166
/// let file = KBox::pin_init(
167
/// dir.read_callback_file(c_str!("bar"),
168
/// AtomicU32::new(3),
169
/// &|val, f| {
170
/// let out = val.load(Ordering::Relaxed);
171
/// writeln!(f, "{out:#010x}")
172
/// }),
173
/// GFP_KERNEL)?;
174
/// // Reading "foo/bar" will show "0x00000003".
175
/// file.store(10, Ordering::Relaxed);
176
/// // Reading "foo/bar" will now show "0x0000000a".
177
/// # Ok::<(), Error>(())
178
/// ```
179
pub fn read_callback_file<'a, T, E: 'a, F>(
180
&'a self,
181
name: &'a CStr,
182
data: impl PinInit<T, E> + 'a,
183
_f: &'static F,
184
) -> impl PinInit<File<T>, E> + 'a
185
where
186
T: Send + Sync + 'static,
187
F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
188
{
189
let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt();
190
self.create_file(name, data, file_ops)
191
}
192
193
/// Creates a read-write file in this directory.
194
///
195
/// Reading the file uses the [`Writer`] implementation.
196
/// Writing to the file uses the [`Reader`] implementation.
197
pub fn read_write_file<'a, T, E: 'a>(
198
&'a self,
199
name: &'a CStr,
200
data: impl PinInit<T, E> + 'a,
201
) -> impl PinInit<File<T>, E> + 'a
202
where
203
T: Writer + Reader + Send + Sync + 'static,
204
{
205
let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;
206
self.create_file(name, data, file_ops)
207
}
208
209
/// Creates a read-write file in this directory, with logic from callbacks.
210
///
211
/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
212
///
213
/// `f` and `w` must be function items or non-capturing closures.
214
/// This is statically asserted and not a safety requirement.
215
pub fn read_write_callback_file<'a, T, E: 'a, F, W>(
216
&'a self,
217
name: &'a CStr,
218
data: impl PinInit<T, E> + 'a,
219
_f: &'static F,
220
_w: &'static W,
221
) -> impl PinInit<File<T>, E> + 'a
222
where
223
T: Send + Sync + 'static,
224
F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
225
W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,
226
{
227
let file_ops =
228
<WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS
229
.adapt()
230
.adapt();
231
self.create_file(name, data, file_ops)
232
}
233
234
/// Creates a write-only file in this directory.
235
///
236
/// The file owns its backing data. Writing to the file uses the [`Reader`]
237
/// implementation.
238
///
239
/// The file is removed when the returned [`File`] is dropped.
240
pub fn write_only_file<'a, T, E: 'a>(
241
&'a self,
242
name: &'a CStr,
243
data: impl PinInit<T, E> + 'a,
244
) -> impl PinInit<File<T>, E> + 'a
245
where
246
T: Reader + Send + Sync + 'static,
247
{
248
self.create_file(name, data, &T::FILE_OPS)
249
}
250
251
/// Creates a write-only file in this directory, with write logic from a callback.
252
///
253
/// `w` must be a function item or a non-capturing closure.
254
/// This is statically asserted and not a safety requirement.
255
pub fn write_callback_file<'a, T, E: 'a, W>(
256
&'a self,
257
name: &'a CStr,
258
data: impl PinInit<T, E> + 'a,
259
_w: &'static W,
260
) -> impl PinInit<File<T>, E> + 'a
261
where
262
T: Send + Sync + 'static,
263
W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,
264
{
265
let file_ops = <WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS
266
.adapt()
267
.adapt();
268
self.create_file(name, data, file_ops)
269
}
270
271
// While this function is safe, it is intentionally not public because it's a bit of a
272
// footgun.
273
//
274
// Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate
275
// time, a `ScopedDir` with a `Dir` parent will never be deleted.
276
fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {
277
#[cfg(CONFIG_DEBUG_FS)]
278
{
279
let parent_entry = match &self.0 {
280
None => return ScopedDir::empty(),
281
Some(entry) => entry.clone(),
282
};
283
ScopedDir {
284
entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),
285
_phantom: PhantomData,
286
}
287
}
288
#[cfg(not(CONFIG_DEBUG_FS))]
289
ScopedDir::empty()
290
}
291
292
/// Creates a new scope, which is a directory associated with some data `T`.
293
///
294
/// The created directory will be a subdirectory of `self`. The `init` closure is called to
295
/// populate the directory with files and subdirectories. These files can reference the data
296
/// stored in the scope.
297
///
298
/// The entire directory tree created within the scope will be removed when the returned
299
/// `Scope` handle is dropped.
300
pub fn scope<'a, T: 'a, E: 'a, F>(
301
&'a self,
302
data: impl PinInit<T, E> + 'a,
303
name: &'a CStr,
304
init: F,
305
) -> impl PinInit<Scope<T>, E> + 'a
306
where
307
F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,
308
{
309
Scope::new(data, |data| {
310
let scoped = self.scoped_dir(name);
311
init(data, &scoped);
312
scoped.into_entry()
313
})
314
}
315
}
316
317
#[pin_data]
318
/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry
319
/// without moving.
320
///
321
/// This is internally used to back [`File`], and used in the API to represent the attachment
322
/// of a directory lifetime to a data structure which may be jointly accessed by a number of
323
/// different files.
324
///
325
/// When dropped, a `Scope` will remove all directories and files in the filesystem backed by the
326
/// attached data structure prior to releasing the attached data.
327
pub struct Scope<T> {
328
// This order is load-bearing for drops - `_entry` must be dropped before `data`.
329
#[cfg(CONFIG_DEBUG_FS)]
330
_entry: Entry<'static>,
331
#[pin]
332
data: T,
333
// Even if `T` is `Unpin`, we still can't allow it to be moved.
334
#[pin]
335
_pin: PhantomPinned,
336
}
337
338
#[pin_data]
339
/// Handle to a DebugFS file, owning its backing data.
340
///
341
/// When dropped, the DebugFS file will be removed and the attached data will be dropped.
342
pub struct File<T> {
343
#[pin]
344
scope: Scope<T>,
345
}
346
347
#[cfg(not(CONFIG_DEBUG_FS))]
348
impl<'b, T: 'b> Scope<T> {
349
fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
350
where
351
F: for<'a> FnOnce(&'a T) + 'b,
352
{
353
try_pin_init! {
354
Self {
355
data <- data,
356
_pin: PhantomPinned
357
} ? E
358
}
359
.pin_chain(|scope| {
360
init(&scope.data);
361
Ok(())
362
})
363
}
364
}
365
366
#[cfg(CONFIG_DEBUG_FS)]
367
impl<'b, T: 'b> Scope<T> {
368
fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {
369
// SAFETY: _entry is not structurally pinned.
370
unsafe { &mut Pin::into_inner_unchecked(self)._entry }
371
}
372
373
fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
374
where
375
F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b,
376
{
377
try_pin_init! {
378
Self {
379
_entry: Entry::empty(),
380
data <- data,
381
_pin: PhantomPinned
382
} ? E
383
}
384
.pin_chain(|scope| {
385
*scope.entry_mut() = init(&scope.data);
386
Ok(())
387
})
388
}
389
}
390
391
impl<'a, T: 'a> Scope<T> {
392
/// Creates a new scope, which is a directory at the root of the debugfs filesystem,
393
/// associated with some data `T`.
394
///
395
/// The `init` closure is called to populate the directory with files and subdirectories. These
396
/// files can reference the data stored in the scope.
397
///
398
/// The entire directory tree created within the scope will be removed when the returned
399
/// `Scope` handle is dropped.
400
pub fn dir<E: 'a, F>(
401
data: impl PinInit<T, E> + 'a,
402
name: &'a CStr,
403
init: F,
404
) -> impl PinInit<Self, E> + 'a
405
where
406
F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,
407
{
408
Scope::new(data, |data| {
409
let scoped = ScopedDir::new(name);
410
init(data, &scoped);
411
scoped.into_entry()
412
})
413
}
414
}
415
416
impl<T> Deref for Scope<T> {
417
type Target = T;
418
fn deref(&self) -> &T {
419
&self.data
420
}
421
}
422
423
impl<T> Deref for File<T> {
424
type Target = T;
425
fn deref(&self) -> &T {
426
&self.scope
427
}
428
}
429
430
/// A handle to a directory which will live at most `'dir`, accessing data that will live for at
431
/// least `'data`.
432
///
433
/// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping
434
/// the `Scope` that created it.
435
pub struct ScopedDir<'data, 'dir> {
436
#[cfg(CONFIG_DEBUG_FS)]
437
entry: ManuallyDrop<Entry<'dir>>,
438
_phantom: PhantomData<fn(&'data ()) -> &'dir ()>,
439
}
440
441
impl<'data, 'dir> ScopedDir<'data, 'dir> {
442
/// Creates a subdirectory inside this `ScopedDir`.
443
///
444
/// The returned directory handle cannot outlive this one.
445
pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2> {
446
#[cfg(not(CONFIG_DEBUG_FS))]
447
let _ = name;
448
ScopedDir {
449
#[cfg(CONFIG_DEBUG_FS)]
450
entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))),
451
_phantom: PhantomData,
452
}
453
}
454
455
fn create_file<T: Sync>(&self, name: &CStr, data: &'data T, vtable: &'static FileOps<T>) {
456
#[cfg(CONFIG_DEBUG_FS)]
457
core::mem::forget(Entry::file(name, &self.entry, data, vtable));
458
}
459
460
/// Creates a read-only file in this directory.
461
///
462
/// The file's contents are produced by invoking [`Writer::write`].
463
///
464
/// This function does not produce an owning handle to the file. The created
465
/// file is removed when the [`Scope`] that this directory belongs
466
/// to is dropped.
467
pub fn read_only_file<T: Writer + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {
468
self.create_file(name, data, &T::FILE_OPS)
469
}
470
471
/// Creates a read-only file in this directory, with contents from a callback.
472
///
473
/// The file contents are generated by calling `f` with `data`.
474
///
475
///
476
/// `f` must be a function item or a non-capturing closure.
477
/// This is statically asserted and not a safety requirement.
478
///
479
/// This function does not produce an owning handle to the file. The created
480
/// file is removed when the [`Scope`] that this directory belongs
481
/// to is dropped.
482
pub fn read_callback_file<T, F>(&self, name: &CStr, data: &'data T, _f: &'static F)
483
where
484
T: Send + Sync + 'static,
485
F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
486
{
487
let vtable = <FormatAdapter<T, F> as ReadFile<_>>::FILE_OPS.adapt();
488
self.create_file(name, data, vtable)
489
}
490
491
/// Creates a read-write file in this directory.
492
///
493
/// Reading the file uses the [`Writer`] implementation on `data`. Writing to the file uses
494
/// the [`Reader`] implementation on `data`.
495
///
496
/// This function does not produce an owning handle to the file. The created
497
/// file is removed when the [`Scope`] that this directory belongs
498
/// to is dropped.
499
pub fn read_write_file<T: Writer + Reader + Send + Sync + 'static>(
500
&self,
501
name: &CStr,
502
data: &'data T,
503
) {
504
let vtable = &<T as ReadWriteFile<_>>::FILE_OPS;
505
self.create_file(name, data, vtable)
506
}
507
508
/// Creates a read-write file in this directory, with logic from callbacks.
509
///
510
/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
511
///
512
/// `f` and `w` must be function items or non-capturing closures.
513
/// This is statically asserted and not a safety requirement.
514
///
515
/// This function does not produce an owning handle to the file. The created
516
/// file is removed when the [`Scope`] that this directory belongs
517
/// to is dropped.
518
pub fn read_write_callback_file<T, F, W>(
519
&self,
520
name: &CStr,
521
data: &'data T,
522
_f: &'static F,
523
_w: &'static W,
524
) where
525
T: Send + Sync + 'static,
526
F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
527
W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,
528
{
529
let vtable = <WritableAdapter<FormatAdapter<T, F>, W> as ReadWriteFile<_>>::FILE_OPS
530
.adapt()
531
.adapt();
532
self.create_file(name, data, vtable)
533
}
534
535
/// Creates a write-only file in this directory.
536
///
537
/// Writing to the file uses the [`Reader`] implementation on `data`.
538
///
539
/// This function does not produce an owning handle to the file. The created
540
/// file is removed when the [`Scope`] that this directory belongs
541
/// to is dropped.
542
pub fn write_only_file<T: Reader + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {
543
let vtable = &<T as WriteFile<_>>::FILE_OPS;
544
self.create_file(name, data, vtable)
545
}
546
547
/// Creates a write-only file in this directory, with write logic from a callback.
548
///
549
/// Writing to the file is handled by `w`.
550
///
551
/// `w` 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 created
555
/// file is removed when the [`Scope`] that this directory belongs
556
/// to is dropped.
557
pub fn write_only_callback_file<T, W>(&self, name: &CStr, data: &'data T, _w: &'static W)
558
where
559
T: Send + Sync + 'static,
560
W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync,
561
{
562
let vtable = &<WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS
563
.adapt()
564
.adapt();
565
self.create_file(name, data, vtable)
566
}
567
568
fn empty() -> Self {
569
ScopedDir {
570
#[cfg(CONFIG_DEBUG_FS)]
571
entry: ManuallyDrop::new(Entry::empty()),
572
_phantom: PhantomData,
573
}
574
}
575
#[cfg(CONFIG_DEBUG_FS)]
576
fn into_entry(self) -> Entry<'dir> {
577
ManuallyDrop::into_inner(self.entry)
578
}
579
#[cfg(not(CONFIG_DEBUG_FS))]
580
fn into_entry(self) {}
581
}
582
583
impl<'data> ScopedDir<'data, 'static> {
584
// This is safe, but intentionally not exported due to footgun status. A ScopedDir with no
585
// parent will never be released by default, and needs to have its entry extracted and used
586
// somewhere.
587
fn new(name: &CStr) -> ScopedDir<'data, 'static> {
588
ScopedDir {
589
#[cfg(CONFIG_DEBUG_FS)]
590
entry: ManuallyDrop::new(Entry::dir(name, None)),
591
_phantom: PhantomData,
592
}
593
}
594
}
595
596