xref: /linux/samples/rust/rust_debugfs_scoped.rs (revision 07fdad3a93756b872da7b53647715c48d0f4a2d0)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 // Copyright (C) 2025 Google LLC.
4 
5 //! Sample DebugFS exporting platform driver that demonstrates the use of
6 //! `Scope::dir` to create a variety of files without the need to separately
7 //! track them all.
8 
9 use core::sync::atomic::AtomicUsize;
10 use kernel::debugfs::{Dir, Scope};
11 use kernel::prelude::*;
12 use kernel::sync::Mutex;
13 use kernel::{c_str, new_mutex, str::CString};
14 
15 module! {
16     type: RustScopedDebugFs,
17     name: "rust_debugfs_scoped",
18     authors: ["Matthew Maurer"],
19     description: "Rust Scoped DebugFS usage sample",
20     license: "GPL",
21 }
22 
23 fn remove_file_write(
24     mod_data: &ModuleData,
25     reader: &mut kernel::uaccess::UserSliceReader,
26 ) -> Result {
27     let mut buf = [0u8; 128];
28     if reader.len() >= buf.len() {
29         return Err(EINVAL);
30     }
31     let n = reader.len();
32     reader.read_slice(&mut buf[..n])?;
33 
34     let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim();
35     let nul_idx = s.len();
36     buf[nul_idx] = 0;
37     let to_remove = CStr::from_bytes_with_nul(&buf[..nul_idx + 1]).map_err(|_| EINVAL)?;
38     mod_data
39         .devices
40         .lock()
41         .retain(|device| device.name.as_bytes() != to_remove.as_bytes());
42     Ok(())
43 }
44 
45 fn create_file_write(
46     mod_data: &ModuleData,
47     reader: &mut kernel::uaccess::UserSliceReader,
48 ) -> Result {
49     let mut buf = [0u8; 128];
50     if reader.len() > buf.len() {
51         return Err(EINVAL);
52     }
53     let n = reader.len();
54     reader.read_slice(&mut buf[..n])?;
55 
56     let mut nums = KVec::new();
57 
58     let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim();
59     let mut items = s.split_whitespace();
60     let name_str = items.next().ok_or(EINVAL)?;
61     let name = CString::try_from_fmt(fmt!("{name_str}"))?;
62     let file_name = CString::try_from_fmt(fmt!("{name_str}"))?;
63     for sub in items {
64         nums.push(
65             AtomicUsize::new(sub.parse().map_err(|_| EINVAL)?),
66             GFP_KERNEL,
67         )?;
68     }
69 
70     let scope = KBox::pin_init(
71         mod_data
72             .device_dir
73             .scope(DeviceData { name, nums }, &file_name, |dev_data, dir| {
74                 for (idx, val) in dev_data.nums.iter().enumerate() {
75                     let Ok(name) = CString::try_from_fmt(fmt!("{idx}")) else {
76                         return;
77                     };
78                     dir.read_write_file(&name, val);
79                 }
80             }),
81         GFP_KERNEL,
82     )?;
83     (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?;
84 
85     Ok(())
86 }
87 
88 struct RustScopedDebugFs {
89     _data: Pin<KBox<Scope<ModuleData>>>,
90 }
91 
92 #[pin_data]
93 struct ModuleData {
94     device_dir: Dir,
95     #[pin]
96     devices: Mutex<KVec<Pin<KBox<Scope<DeviceData>>>>>,
97 }
98 
99 impl ModuleData {
100     fn init(device_dir: Dir) -> impl PinInit<Self> {
101         pin_init! {
102             Self {
103                 device_dir: device_dir,
104                 devices <- new_mutex!(KVec::new())
105             }
106         }
107     }
108 }
109 
110 struct DeviceData {
111     name: CString,
112     nums: KVec<AtomicUsize>,
113 }
114 
115 fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ {
116     base_dir.scope(
117         ModuleData::init(dyn_dirs),
118         c_str!("control"),
119         |data, dir| {
120             dir.write_only_callback_file(c_str!("create"), data, &create_file_write);
121             dir.write_only_callback_file(c_str!("remove"), data, &remove_file_write);
122         },
123     )
124 }
125 
126 impl kernel::Module for RustScopedDebugFs {
127     fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
128         let base_dir = Dir::new(c_str!("rust_scoped_debugfs"));
129         let dyn_dirs = base_dir.subdir(c_str!("dynamic"));
130         Ok(Self {
131             _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?,
132         })
133     }
134 }
135