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