diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index a79f3f398b9369..475e3e2112d7c1 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -2,10 +2,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs new file mode 100644 index 00000000000000..d44f6590af60b9 --- /dev/null +++ b/rust/kernel/debugfs.rs @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust implementation of `debugfs`. +//! +//! This module allows Rust kernel modules to create directories and files in +//! `/debugfs`. +//! +//! C header: [`include/linux/debugfs.h`](../../../include/linux/debugfs.h) +//! +//! Reference: + +use alloc::{boxed::Box, vec::Vec}; +use core::{ + marker::{PhantomData, Sync}, + ptr, +}; + +use crate::{bindings, c_types, error, str::CStr, types::PointerWrapper, Result}; + +/// An `inode` for a directory in debugfs. +pub struct DebugFsDirectory { + dentry: *mut bindings::dentry, + has_parent: bool, + file_children: Vec>, + dir_children: Vec, +} + +// SAFETY: There are no public functions that take a shared [`DebugFsDirectory`] +// reference and all its fields are private so a thread can't actually do +// anything with a `&DebugFsDirectory`. This makes it is safe to share across +// threads. [`DebufFsDirectory::create`] does take a `&DebugFsDirectory`, but it +// is only publicly accessible through [`DebufFsDirectory::create_with_parent`] +// which requires the reference to be mutable. +unsafe impl Sync for DebugFsDirectory {} + +impl DebugFsDirectory { + /// Create a new directory in `debugfs` under `parent`. If `parent` is + /// `None`, it will be created at the `debugfs` root. The directory will be + /// recursively removed on drop. + fn create(name: &CStr, parent: Option<&DebugFsDirectory>) -> Result { + let name = name.as_char_ptr(); + let parent_ptr = match parent { + Some(dir) => dir.dentry, + None => ptr::null_mut(), + }; + // SAFETY: Calling a C function. `name` is a valid null-terminated + // string because it came from a [`CStr`] and `parent` is either null or + // valid because it came from a [`DebugFsDirectory`]. + let dentry = + error::from_kernel_err_ptr(unsafe { bindings::debugfs_create_dir(name, parent_ptr) })?; + Ok(DebugFsDirectory { + dentry, + has_parent: parent.is_some(), + file_children: Vec::new(), + dir_children: Vec::new(), + }) + } + + /// Create a new directory in `debugfs` under `parent`. The directory will + /// be removed when the parent is dropped. + pub fn create_with_parent<'name, 'parent>( + name: &'name CStr, + parent: &'parent mut DebugFsDirectory, + ) -> Result<&'parent mut DebugFsDirectory> { + let result = DebugFsDirectory::create(name, Some(parent))?; + parent.dir_children.try_push(result)?; + let index = parent.dir_children.len() - 1; + Ok(&mut parent.dir_children[index]) + } + + /// Create a new directory at the toplevel of `debugfs`. + /// The directory will be recursively removed on drop. + pub fn create_toplevel(name: &CStr) -> Result { + DebugFsDirectory::create(name, None) + } +} + +impl Drop for DebugFsDirectory { + fn drop(&mut self) { + // If this entry has a parent, we don't need to worry about removal + // because the parent will remove its children when dropped. Otherwise + // we need to clean up. + if !self.has_parent { + // SAFETY: Calling a C function. `dentry` must have been created by + // a call to [`DebugFsDirectory::create`] which always returns a + // valid `dentry`. There is no parent, so the `dentry` couldn't have + // been removed and must still be valid. + unsafe { + bindings::debugfs_remove(self.dentry); + } + } + } +} + +/// An `inode` for a file in debugfs with a `T` stored in `i_private`. +pub struct DebugFsFile { + dentry: *mut bindings::dentry, + data: *mut c_types::c_void, + has_parent: bool, + _wrapper: PhantomData, +} + +// SAFETY: There are no public methods available on [`DebugFsFile`] so a thread +// can't actually do anything with a `&DebugFsFile`. This makes it is safe to +// share across threads. +unsafe impl Sync for DebugFsFile {} + +impl DebugFsFile { + /// Create a file in the `debugfs` directory under `parent`. If `parent` is + /// `None` then the file will be created at the root of the `debugfs` + /// directory. + /// + /// # Safety + /// + /// `fops` must be valid when opening an `inode` with `data::into_pointer` + /// stored in `i_private`. + unsafe fn create( + name: &CStr, + parent: Option<&DebugFsDirectory>, + data: T, + fops: &'static bindings::file_operations, + ) -> Result> { + let name = name.as_char_ptr(); + let data = data.into_pointer() as *mut _; + let parent = parent.map(|p| p.dentry).unwrap_or_else(ptr::null_mut); + // SAFETY: Calling a C function. `name` will be a valid null-terminated + // string because it came from a [`CStr`]. The caller guarantees that + // `fops` is valid for an inode with `data` in `i_private`. + let dentry_ptr = error::from_kernel_err_ptr(unsafe { + bindings::debugfs_create_file(name, 0, parent, data, fops) + }); + match dentry_ptr { + Err(err) => { + // SAFETY: `data` was created by `T::into_pointer` just above. + drop(unsafe { T::from_pointer(data) }); + Err(err) + } + Ok(dentry) => Ok(DebugFsFile { + dentry, + data, + has_parent: true, + _wrapper: PhantomData, + }), + } + } + + /// Create a file in the `debugfs` directory under `parent`. + /// + /// # Safety + /// + /// `fops` must be valid when opening an `inode` with `data::into_pointer` + /// stored in `i_private`. + pub(crate) unsafe fn create_with_parent( + name: &CStr, + parent: &mut DebugFsDirectory, + data: T, + fops: &'static bindings::file_operations, + ) -> Result<()> { + // SAFETY: The caller must ensure the safety conditions on `create` are + // met. + let file: DebugFsFile = unsafe { Self::create(name, Some(parent), data, fops) }?; + let boxed: Box = Box::try_new(file)?; + parent.file_children.try_push(boxed)?; + Ok(()) + } + + /// Create a file at the top level of the `debugfs` directory. + /// + /// # Safety + /// + /// `fops` must be valid when opening an `inode` with `data::into_pointer` + /// stored in `i_private`. + pub(crate) unsafe fn create_toplevel( + name: &CStr, + data: T, + fops: &'static bindings::file_operations, + ) -> Result> { + // SAFETY: The caller must ensure the safety conditions on `create` are + // met. + unsafe { Self::create(name, None, data, fops) } + } +} + +impl Drop for DebugFsFile { + fn drop(&mut self) { + // If there is a parent then the parent is responsible for dropping the + // dentries. + if !self.has_parent { + // SAFETY: Calling a C function. `dentry` must have been created by + // a call to [`DebugFsFile::create`] which always returns a valid + // `dentry`. Since there is no parent that can remove the `dentry` + // it must still exist. + unsafe { bindings::debugfs_remove(self.dentry) }; + } + // SAFETY: `self.data` was created by a call to `T::into_pointer` in + // [`DebugFsFile::create`]. + unsafe { T::from_pointer(self.data) }; + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 2209f779fa99b6..b79f72b8c2d454 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -48,6 +48,8 @@ pub mod chrdev; #[cfg(CONFIG_COMMON_CLK)] pub mod clk; pub mod cred; +#[cfg(CONFIG_DEBUG_FS)] +pub mod debugfs; pub mod device; pub mod driver; mod error; @@ -75,6 +77,8 @@ mod build_assert; pub mod prelude; pub mod print; pub mod random; +#[cfg(CONFIG_DEBUG_FS)] +pub mod seq_file; mod static_assert; #[doc(hidden)] pub mod std_vendor; diff --git a/rust/kernel/seq_file.rs b/rust/kernel/seq_file.rs new file mode 100644 index 00000000000000..2d5bd85b7c7d9b --- /dev/null +++ b/rust/kernel/seq_file.rs @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Trait for defining `seq_file`s. +//! +//! This module allows Rust devices to implement [`struct seq_operations`] and +//! and create a file under `/debugfs` based on that implementation. +//! +//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h) +//! +//! Reference: + +// Currently this module is only usable through debugfs. +#![cfg(CONFIG_DEBUG_FS)] + +use core::{fmt::Display, marker::PhantomData, ptr}; + +use crate::{ + bindings, c_str, c_types, + debugfs::{DebugFsDirectory, DebugFsFile}, + str::CStr, + types::PointerWrapper, + Result, +}; + +/// Rust equivalent of the [`seq_operations`] interface on the C side. +/// +/// # Example +/// +/// ```rust,no_run +/// use kernel::prelude::*; +/// use kernel::{ +/// seq_file, +/// sync::{Mutex, Ref, RefBorrow}, +/// }; +/// +/// use kernel::{Error, Result, seq_file}; +/// struct SharedStateInner { +/// token_count: usize, +/// } +/// +/// struct SharedState { +/// inner: Mutex, +/// } +/// +/// struct Token +/// +/// impl seq_file::SeqOperations for Token { +/// type OpenData = Ref; +/// type DataWrapper = Ref; +/// type IteratorWrapper = Box<(usize, usize)>; +/// type Item = usize; +/// +/// fn open<'a>(open_data: RefBorrow<'a, SharedState>) -> Result> { +/// Ok(open_data.into()) +/// } +/// +/// fn start<'a>(data: RefBorrow<'a, SharedState>) -> Option { +/// let total = data.inner.lock().token_count; +/// Box::try_new((total, 1)).ok() +/// } +/// +/// fn next(iterator: &mut Self::IteratorWrapper) -> bool { +/// let total = iterator.0; +/// let current = iterator.1; +/// if total == current { +/// false +/// } else { +/// iterator.1 += 1; +/// true +/// } +/// } +/// +/// fn current(iterator: &Self::IteratorWrapper) -> core::option::Option { +/// let total = iterator.0; +/// let current = iterator.1; +/// if total >= current { +/// Some(current) +/// } else { +/// None +/// } +/// } +/// } +/// ``` +/// +/// [`seq_operations`]: ../../../include/linux/seq_file.h +pub trait SeqOperations { + /// Data stored in the seq_file's inode and accesible each time it is opened + /// in [`SeqOperations::open`]. + type OpenData: PointerWrapper + Sync; + + /// Data stored in an opened seq_file. + type DataWrapper: PointerWrapper; + + /// Data stored in a seq_file as it is being iterated through. + type IteratorWrapper: PointerWrapper; + + /// Type produced on iteration. + type Item: Display; + + /// Called when the seq_file is opened. + fn open<'a>( + open_data: ::Borrowed<'a>, + ) -> Result; + + /// Called once on each execution of fops->read or fops->read_iter. + fn start<'a>( + data: ::Borrowed<'a>, + ) -> Option; + + /// Moves the iterator to the next item. Iteration will stop if this returns + /// `false`. + fn next(iterator: &mut Self::IteratorWrapper) -> bool; + + /// Returns the current item. Items can be skipped by returning `None`. + fn current<'a>( + iterator: ::Borrowed<'a>, + ) -> Option; +} + +extern "C" fn stop_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) { + if !v.is_null() { + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `T::IteratorWrapper::into_pointer`. + drop(unsafe { T::IteratorWrapper::from_pointer(v) }) + } +} + +extern "C" fn next_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + if v.is_null() { + return ptr::null_mut(); + } + + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `T::IteratorWrapper::into_pointer`. + // We already checked for he null pointer case above. + let mut iterator = unsafe { T::IteratorWrapper::from_pointer(v) }; + + // SAFETY: The caller guarantees tha `pos` is a valid pointer to an + // `loff_t` and expects this function to mutate the value. + unsafe { + *pos += 1; + } + + if !T::next(&mut iterator) { + ptr::null_mut() + } else { + T::IteratorWrapper::into_pointer(iterator) as *mut _ + } +} + +extern "C" fn show_callback( + m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) -> c_types::c_int { + const FORMAT: &CStr = c_str!("%pA"); + if v.is_null() { + return 0; + } + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer or + // pointer generated by `T::IteratorWrapper::into_pointer`. We checked for + // null pointers above. + let iterator = unsafe { T::IteratorWrapper::borrow(v) }; + if let Some(item) = T::current(iterator) { + // SAFETY: Calling a C function. `FORMAT` is null terminated because it + // comes from a `CStr`. + unsafe { + bindings::seq_printf( + m, + (FORMAT as *const _) as *const _, + (&format_args!("{}", item) as *const _) as *const _, + ); + } + } + 0 +} + +extern "C" fn start_callback( + m: *mut bindings::seq_file, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + // SAFETY: This function is only called on the private_data of a file that + // was opened with [`SeqFileOperationsVTable::::open_callback`]. That + // function stores a pointer generated with T::DataWrapper::into_pointer in + // m->private. + let data_wrapper = unsafe { T::DataWrapper::borrow((*m).private) }; + let iterator = T::start(data_wrapper); + // SAFETY: The caller guarantees that `pos` points to a valid `loff_t`. + let pos = unsafe { *pos }; + match iterator { + Some(mut wrapper) => { + for _ in 0..pos { + if !T::next(&mut wrapper) { + return ptr::null_mut(); + } + } + T::IteratorWrapper::into_pointer(wrapper) as *mut _ + } + None => ptr::null_mut(), + } +} + +pub(crate) struct SeqFileOperationsVTable(PhantomData); + +impl<'a, T, D: 'a> SeqFileOperationsVTable +where + T: SeqOperations, + D: PointerWrapper, +{ + const SEQ_VTABLE: bindings::seq_operations = bindings::seq_operations { + start: Some(start_callback::), + stop: Some(stop_callback::), + next: Some(next_callback::), + show: Some(show_callback::), + }; + + /// # Safety + /// + /// The `inode->i_private` must have been created by `T::OpenData::into_pointer`. + unsafe extern "C" fn open_callback( + inode: *mut bindings::inode, + file: *mut bindings::file, + ) -> c_types::c_int { + // SAFETY: Calling a C function. Caller ensures that `file` is a valid + // file pointer and we've statically ensured the vtable is valid. + let result = unsafe { + bindings::seq_open( + file, + &Self::SEQ_VTABLE as *const bindings::seq_operations as *mut _, + ) + }; + if result != 0 { + return result; + } + + // SAFETY: Safety condition on this function requires that `i_private` + // was created by `T::OpenData::into_pointer`. + let open_data = unsafe { T::OpenData::borrow((*inode).i_private) }; + + let data_wrapper = match T::open(open_data) { + Ok(data) => data, + Err(err) => return err.to_kernel_errno(), + }; + + let data_pointer = data_wrapper.into_pointer() as *mut c_types::c_void; + // SAFETY: The caller guarantees that `file` is a valid `file` pointer + // and `seq_open` stores a valid `seq_file` pointer in + // `file->private_data`. + unsafe { + (*((*file).private_data as *mut bindings::seq_file)).private = + data_pointer as *mut c_types::c_void + }; + result + } + + const VTABLE: bindings::file_operations = bindings::file_operations { + open: Some(Self::open_callback), + release: Some(bindings::seq_release), + read: Some(bindings::seq_read), + read_iter: Some(bindings::seq_read_iter), + llseek: Some(bindings::seq_lseek), + + check_flags: None, + compat_ioctl: None, + copy_file_range: None, + fallocate: None, + fadvise: None, + fasync: None, + flock: None, + flush: None, + fsync: None, + get_unmapped_area: None, + iterate: None, + iterate_shared: None, + iopoll: None, + lock: None, + mmap: None, + mmap_supported_flags: 0, + owner: ptr::null_mut(), + poll: None, + remap_file_range: None, + sendpage: None, + setlease: None, + show_fdinfo: None, + splice_read: None, + splice_write: None, + unlocked_ioctl: None, + write: None, + write_iter: None, + }; +} + +/// Create a `seq_file` in [`debugfs`] under the `parent` directory. +pub fn debugfs_create_with_parent( + name: &CStr, + parent: &mut DebugFsDirectory, + data: T::OpenData, +) -> Result<()> +where + ::OpenData: 'static, +{ + // SAFETY: The `open` field of the vtable is valid iff the opened `inode` + // has `i_private` generated by `T::OpenData::into_pointer`. `create_file` + // is safe to call only if the vtable is valid when used on an inode with + // `data::into_pointer` stored in `i_private`. + unsafe { + DebugFsFile::create_with_parent(name, parent, data, &SeqFileOperationsVTable::::VTABLE) + } +} + +/// Create a `seq_file` at the top level of [`debugfs`]. +pub fn debugfs_create_toplevel( + name: &CStr, + data: T::OpenData, +) -> Result> +where + ::OpenData: 'static, +{ + // SAFETY: The `open` field of the vtable is valid iff the opened `inode` + // has `i_private` generated by `T::OpenData::into_pointer`. `create_file` + // is safe to call only if the vtable is valid when used on an inode with + // `data::into_pointer` stored in `i_private`. + Ok(unsafe { DebugFsFile::create_toplevel(name, data, &SeqFileOperationsVTable::::VTABLE) }?) +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index e234be7c341ca1..a2eb45153d8e78 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -120,4 +120,15 @@ config SAMPLE_RUST_PLATFORM If unsure, say N. +config SAMPLE_RUST_SEQ_FILE + tristate "Seq file" + depends on DEBUG_FS + help + This option builds the Rust seq_file sample. + + To compile this as a module, choose M here: + the module will be called rust_seq_file. + + If unsure, say N. + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 96ffeb7d334ac6..c54ea9968d052f 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o obj-$(CONFIG_SAMPLE_RUST_RANDOM) += rust_random.o obj-$(CONFIG_SAMPLE_RUST_PLATFORM) += rust_platform.o +obj-$(CONFIG_SAMPLE_RUST_SEQ_FILE) += rust_seq_file.o diff --git a/samples/rust/rust_seq_file.rs b/samples/rust/rust_seq_file.rs new file mode 100644 index 00000000000000..1726c43c7ab340 --- /dev/null +++ b/samples/rust/rust_seq_file.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust misc device that reports debug information with a seq_file entry in +//! debugfs. + +use kernel::prelude::*; +use kernel::{ + debugfs::DebugFsDirectory, + file::File, + file_operations::FileOperations, + io_buffer::IoBufferWriter, + miscdev, seq_file, + str::CString, + sync::{Mutex, Ref, RefBorrow, UniqueRef}, +}; + +module! { + type: RustMiscdev, + name: b"rust_seq_file", + author: b"Rust for Linux Contributors", + description: b"Sample Rust miscellaneous device with a debugfs entry using seq_file", + license: b"GPL v2", +} + +struct SharedStateInner { + read_count: usize, +} + +struct SharedState { + inner: Mutex, +} + +impl SharedState { + fn try_new() -> Result> { + let mut state = Pin::from(UniqueRef::try_new(Self { + // SAFETY: `mutex_init!` is called below. + inner: unsafe { Mutex::new(SharedStateInner { read_count: 0 }) }, + })?); + + // SAFETY: `inner` is pinned when `state` is. + let pinned = unsafe { state.as_mut().map_unchecked_mut(|s| &mut s.inner) }; + kernel::mutex_init!(pinned, "SharedState::inner"); + + Ok(state.into()) + } +} + +struct Token; +impl FileOperations for Token { + type Wrapper = Ref; + type OpenData = Ref; + + kernel::declare_file_operations!(read, write); + + fn open(shared: &Ref, _file: &File) -> Result { + Ok(shared.clone()) + } + + fn read( + shared: RefBorrow<'_, SharedState>, + _: &File, + data: &mut impl IoBufferWriter, + offset: u64, + ) -> Result { + // Succeed if the caller doesn't provide a buffer or if not at the start. + if data.is_empty() || offset != 0 { + return Ok(0); + } + + { + let mut inner = shared.inner.lock(); + inner.read_count += 1; + } + + // Write a one-byte 1 to the reader. + data.write_slice(&[b'a'; 1])?; + Ok(1) + } +} + +struct Log { + read_id: usize, +} + +impl core::fmt::Display for Log { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "rust_seq_file read log: {}", self.read_id) + } +} + +impl seq_file::SeqOperations for Token { + type OpenData = Ref; + type DataWrapper = Ref; + type IteratorWrapper = Box<(usize, usize)>; + type Item = Log; + + fn open<'a>(open_data: RefBorrow<'a, SharedState>) -> Result> { + Ok(open_data.into()) + } + + fn start<'a>(data: RefBorrow<'a, SharedState>) -> Option { + let total = data.inner.lock().read_count; + Box::try_new((total, 1)).ok() + } + + fn next(iterator: &mut Self::IteratorWrapper) -> bool { + let total = iterator.0; + let current = iterator.1; + if total == current { + false + } else { + iterator.1 += 1; + true + } + } + + fn current(iterator: &(usize, usize)) -> core::option::Option { + let total = iterator.0; + let current = iterator.1; + if total >= current { + Some(Log { read_id: current }) + } else { + None + } + } +} + +struct RustMiscdev { + _dev: Pin>>, + _debugfs_dir: DebugFsDirectory, +} + +impl KernelModule for RustMiscdev { + fn init(name: &'static CStr, _module: &'static ThisModule) -> Result { + pr_info!("Rust seq_file sample (init)\n"); + + let state = SharedState::try_new()?; + let dir_name = CString::try_from_fmt(fmt!("{name}_debug"))?; + let mut debugfs_dir = DebugFsDirectory::create_toplevel(&dir_name)?; + let file_name = CString::try_from_fmt(fmt!("{name}"))?; + seq_file::debugfs_create_with_parent::(&file_name, &mut debugfs_dir, state.clone())?; + + Ok(RustMiscdev { + _dev: miscdev::Registration::new_pinned(fmt!("{name}"), state)?, + _debugfs_dir: debugfs_dir, + }) + } +} + +impl Drop for RustMiscdev { + fn drop(&mut self) { + pr_info!("Rust seq_file sample (exit)\n"); + } +}