// 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 kernel::{ debugfs::{ Dir, Scope, // }, new_mutex, prelude::*, sizes::*, str::CString, sync::{ atomic::Atomic, Mutex, // },}; 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.to_bytes() != to_remove.to_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( Atomic::<usize>::new(sub.parse().map_err(|_| EINVAL)?), GFP_KERNEL, )?; } let blob = KBox::pin_init(new_mutex!([0x42; SZ_4K]), GFP_KERNEL)?; let scope = KBox::pin_init( mod_data.device_dir.scope( DeviceData { name, nums, blob }, &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); } dir.read_write_binary_file(c"blob", &dev_data.blob); }, ), GFP_KERNEL, )?; (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?; Ok(())} struct RustScopedDebugFs { _data: Pin<KBox<Scope<ModuleData>>>,} #[pin_data]struct ModuleData { device_dir: Dir, #[pin] devices: Mutex<KVec<Pin<KBox<Scope<DeviceData>>>>>,} impl ModuleData { fn init(device_dir: Dir) -> impl PinInit<Self> { pin_init! { Self { device_dir: device_dir, devices <- new_mutex!(KVec::new()) } } }} struct DeviceData { name: CString, nums: KVec<Atomic<usize>>, blob: Pin<KBox<Mutex<[u8; SZ_4K]>>>,} fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ { base_dir.scope(ModuleData::init(dyn_dirs), c"control", |data, dir| { dir.write_only_callback_file(c"create", data, &create_file_write); dir.write_only_callback_file(c"remove", data, &remove_file_write); })} impl kernel::Module for RustScopedDebugFs { fn init(_module: &'static kernel::ThisModule) -> Result<Self> { let base_dir = Dir::new(c"rust_scoped_debugfs"); let dyn_dirs = base_dir.subdir(c"dynamic"); Ok(Self { _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?, }) }}
// 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 kernel::{
debugfs::{
Dir,
Scope, //
},
new_mutex,
prelude::*,
sizes::*,
str::CString,
sync::{
atomic::Atomic,
Mutex, //
},
};
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.to_bytes() != to_remove.to_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(
Atomic::<usize>::new(sub.parse().map_err(|_| EINVAL)?),
GFP_KERNEL,
)?;
}
let blob = KBox::pin_init(new_mutex!([0x42; SZ_4K]), GFP_KERNEL)?;
let scope = KBox::pin_init(
mod_data.device_dir.scope(
DeviceData { name, nums, blob },
&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);
}
dir.read_write_binary_file(c"blob", &dev_data.blob);
},
),
GFP_KERNEL,
)?;
(*mod_data.devices.lock()).push(scope, GFP_KERNEL)?;
Ok(())
}
struct RustScopedDebugFs {
_data: Pin<KBox<Scope<ModuleData>>>,
}
#[pin_data]
struct ModuleData {
device_dir: Dir,
#[pin]
devices: Mutex<KVec<Pin<KBox<Scope<DeviceData>>>>>,
}
impl ModuleData {
fn init(device_dir: Dir) -> impl PinInit<Self> {
pin_init! {
Self {
device_dir: device_dir,
devices <- new_mutex!(KVec::new())
}
}
}
}
struct DeviceData {
name: CString,
nums: KVec<Atomic<usize>>,
blob: Pin<KBox<Mutex<[u8; SZ_4K]>>>,
}
fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ {
base_dir.scope(ModuleData::init(dyn_dirs), c"control", |data, dir| {
dir.write_only_callback_file(c"create", data, &create_file_write);
dir.write_only_callback_file(c"remove", data, &remove_file_write);
})
}
impl kernel::Module for RustScopedDebugFs {
fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
let base_dir = Dir::new(c"rust_scoped_debugfs");
let dyn_dirs = base_dir.subdir(c"dynamic");
Ok(Self {
_data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?,
})
}
}