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