1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
// 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<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<AtomicUsize>,
}
fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ {
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<Self> {
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)?,
})
}
}
|