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