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