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