// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. //! Sample DebugFS exporting platform driver that demonstrates the use of //! `Scope::dir` to create a variety of files without the need to separately //! track them all. use core::sync::atomic::AtomicUsize; use kernel::debugfs::{Dir, Scope}; use kernel::prelude::*; use kernel::sync::Mutex; use kernel::{c_str, new_mutex, str::CString}; module! { type: RustScopedDebugFs, name: "rust_debugfs_scoped", authors: ["Matthew Maurer"], description: "Rust Scoped DebugFS usage sample", license: "GPL", } fn remove_file_write( mod_data: &ModuleData, reader: &mut kernel::uaccess::UserSliceReader, ) -> Result { let mut buf = [0u8; 128]; if reader.len() >= buf.len() { return Err(EINVAL); } let n = reader.len(); reader.read_slice(&mut buf[..n])?; let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim(); let nul_idx = s.len(); buf[nul_idx] = 0; let to_remove = CStr::from_bytes_with_nul(&buf[..nul_idx + 1]).map_err(|_| EINVAL)?; mod_data .devices .lock() .retain(|device| device.name.as_bytes() != to_remove.as_bytes()); Ok(()) } fn create_file_write( mod_data: &ModuleData, reader: &mut kernel::uaccess::UserSliceReader, ) -> Result { let mut buf = [0u8; 128]; if reader.len() > buf.len() { return Err(EINVAL); } let n = reader.len(); reader.read_slice(&mut buf[..n])?; let mut nums = KVec::new(); let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?.trim(); let mut items = s.split_whitespace(); let name_str = items.next().ok_or(EINVAL)?; let name = CString::try_from_fmt(fmt!("{name_str}"))?; let file_name = CString::try_from_fmt(fmt!("{name_str}"))?; for sub in items { nums.push( AtomicUsize::new(sub.parse().map_err(|_| EINVAL)?), GFP_KERNEL, )?; } let scope = KBox::pin_init( mod_data .device_dir .scope(DeviceData { name, nums }, &file_name, |dev_data, dir| { for (idx, val) in dev_data.nums.iter().enumerate() { let Ok(name) = CString::try_from_fmt(fmt!("{idx}")) else { return; }; dir.read_write_file(&name, val); } }), GFP_KERNEL, )?; (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?; Ok(()) } struct RustScopedDebugFs { _data: Pin>>, } #[pin_data] struct ModuleData { device_dir: Dir, #[pin] devices: Mutex>>>>, } impl ModuleData { fn init(device_dir: Dir) -> impl PinInit { pin_init! { Self { device_dir: device_dir, devices <- new_mutex!(KVec::new()) } } } } struct DeviceData { name: CString, nums: KVec, } fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit> + '_ { base_dir.scope( ModuleData::init(dyn_dirs), c_str!("control"), |data, dir| { dir.write_only_callback_file(c_str!("create"), data, &create_file_write); dir.write_only_callback_file(c_str!("remove"), data, &remove_file_write); }, ) } impl kernel::Module for RustScopedDebugFs { fn init(_module: &'static kernel::ThisModule) -> Result { let base_dir = Dir::new(c_str!("rust_scoped_debugfs")); let dyn_dirs = base_dir.subdir(c_str!("dynamic")); Ok(Self { _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?, }) } }