xref: /linux/samples/rust/rust_debugfs_scoped.rs (revision 59e6295fac26b8e85c1ea859cdd89fa1e47519d7)
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(
79         new_mutex!(pin_init::init_array_from_fn(|_| 0x42)),
80         GFP_KERNEL,
81     )?;
82 
83     let scope = KBox::pin_init(
84         mod_data.device_dir.scope(
85             DeviceData { name, nums, blob },
86             &file_name,
87             |dev_data, dir| {
88                 for (idx, val) in dev_data.nums.iter().enumerate() {
89                     let Ok(name) = CString::try_from_fmt(fmt!("{idx}")) else {
90                         return;
91                     };
92                     dir.read_write_file(&name, val);
93                 }
94                 dir.read_write_binary_file(c"blob", &dev_data.blob);
95             },
96         ),
97         GFP_KERNEL,
98     )?;
99     (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?;
100 
101     Ok(())
102 }
103 
104 struct RustScopedDebugFs {
105     _data: Pin<KBox<Scope<ModuleData>>>,
106 }
107 
108 #[pin_data]
109 struct ModuleData {
110     device_dir: Dir,
111     #[pin]
112     devices: Mutex<KVec<Pin<KBox<Scope<DeviceData>>>>>,
113 }
114 
115 impl ModuleData {
116     fn init(device_dir: Dir) -> impl PinInit<Self> {
117         pin_init! {
118             Self {
119                 device_dir: device_dir,
120                 devices <- new_mutex!(KVec::new())
121             }
122         }
123     }
124 }
125 
126 struct DeviceData {
127     name: CString,
128     nums: KVec<Atomic<usize>>,
129     blob: Pin<KBox<Mutex<[u8; SZ_4K]>>>,
130 }
131 
132 fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ {
133     base_dir.scope(ModuleData::init(dyn_dirs), c"control", |data, dir| {
134         dir.write_only_callback_file(c"create", data, &create_file_write);
135         dir.write_only_callback_file(c"remove", data, &remove_file_write);
136     })
137 }
138 
139 impl kernel::Module for RustScopedDebugFs {
140     fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
141         let base_dir = Dir::new(c"rust_scoped_debugfs");
142         let dyn_dirs = base_dir.subdir(c"dynamic");
143         Ok(Self {
144             _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?,
145         })
146     }
147 }
148