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