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