diff --git a/.github/workflows/busybox.config b/.github/workflows/busybox.config index c4175a522a3a60..18693ef72beda0 100644 --- a/.github/workflows/busybox.config +++ b/.github/workflows/busybox.config @@ -196,7 +196,7 @@ CONFIG_GZIP_FAST=0 # Coreutils # # CONFIG_BASENAME is not set -# CONFIG_CAT is not set +CONFIG_CAT=y # CONFIG_FEATURE_CATN is not set # CONFIG_FEATURE_CATV is not set # CONFIG_CHGRP is not set @@ -263,9 +263,9 @@ CONFIG_GZIP_FAST=0 # CONFIG_SHA512SUM is not set # CONFIG_SHA3SUM is not set # CONFIG_FEATURE_MD5_SHA1_SUM_CHECK is not set -# CONFIG_MKDIR is not set +CONFIG_MKDIR=y # CONFIG_MKFIFO is not set -# CONFIG_MKNOD is not set +CONFIG_MKNOD=y # CONFIG_MKTEMP is not set # CONFIG_MV is not set # CONFIG_NICE is not set @@ -280,7 +280,7 @@ CONFIG_GZIP_FAST=0 # CONFIG_READLINK is not set # CONFIG_FEATURE_READLINK_FOLLOW is not set # CONFIG_REALPATH is not set -# CONFIG_RM is not set +CONFIG_RM=y # CONFIG_RMDIR is not set # CONFIG_SEQ is not set # CONFIG_SHRED is not set @@ -635,7 +635,7 @@ CONFIG_DEFAULT_DEPMOD_FILE="modules.dep" # CONFIG_MKSWAP is not set # CONFIG_FEATURE_MKSWAP_UUID is not set # CONFIG_MORE is not set -# CONFIG_MOUNT is not set +CONFIG_MOUNT=y # CONFIG_FEATURE_MOUNT_FAKE is not set # CONFIG_FEATURE_MOUNT_VERBOSE is not set # CONFIG_FEATURE_MOUNT_HELPERS is not set diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c616881090154b..79721bb0ebfb98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,6 +11,7 @@ jobs: timeout-minutes: 20 strategy: + fail-fast: false matrix: arch: [arm, arm64, ppc64le, riscv64, x86_64] toolchain: [gcc, clang, llvm] @@ -360,6 +361,12 @@ jobs: grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (init)$' qemu-stdout.log grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (exit)$' qemu-stdout.log + - run: | + grep '] rust_seq_file: Rust seq_file sample (init)$' qemu-stdout.log + grep '] rust_seq_file: Rust seq_file sample (exit)$' qemu-stdout.log + grep 'rust_seq_file read log: 1' qemu-stdout.log + grep 'rust_seq_file read log: 2' qemu-stdout.log + # Report - run: | cat ${{ env.BUILD_DIR }}.config diff --git a/.github/workflows/kernel-arm-debug.config b/.github/workflows/kernel-arm-debug.config index 8fe33e2c0e167d..256eff9fcee553 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1780,6 +1780,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm-release.config b/.github/workflows/kernel-arm-release.config index 7f2f8a9ac9b0d1..476fa41ad70145 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1704,6 +1704,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm64-debug-thinlto.config b/.github/workflows/kernel-arm64-debug-thinlto.config index bbeecdf025ba42..51e2d86ad4fdd1 100644 --- a/.github/workflows/kernel-arm64-debug-thinlto.config +++ b/.github/workflows/kernel-arm64-debug-thinlto.config @@ -1437,6 +1437,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index 9fa042b1e1dbc5..c8345fb779343d 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1432,6 +1432,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release-thinlto.config b/.github/workflows/kernel-arm64-release-thinlto.config index 30057ad8889c32..d9b6cb86f51ee1 100644 --- a/.github/workflows/kernel-arm64-release-thinlto.config +++ b/.github/workflows/kernel-arm64-release-thinlto.config @@ -1216,7 +1216,10 @@ CONFIG_FRAME_POINTER=y # Generic Kernel Debugging Instruments # # CONFIG_MAGIC_SYSRQ is not set -# CONFIG_DEBUG_FS is not set +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y # CONFIG_KGDB is not set CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y @@ -1355,6 +1358,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 4fc6d8e140accf..7e25e135c37f2d 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1350,6 +1350,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index e55f709d006b13..eb8b1184d2be8f 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1492,6 +1492,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-ppc64le-release.config b/.github/workflows/kernel-ppc64le-release.config index ac094bd0eba335..6b04e03c67b708 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1454,6 +1454,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-riscv64-debug.config b/.github/workflows/kernel-riscv64-debug.config index 040942c451a96a..d6998e17867c5e 100644 --- a/.github/workflows/kernel-riscv64-debug.config +++ b/.github/workflows/kernel-riscv64-debug.config @@ -1122,7 +1122,10 @@ CONFIG_FRAME_POINTER=y # Generic Kernel Debugging Instruments # # CONFIG_MAGIC_SYSRQ is not set -# CONFIG_DEBUG_FS is not set +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y CONFIG_HAVE_ARCH_KGDB_QXFER_PKT=y # CONFIG_KGDB is not set @@ -1286,6 +1289,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-riscv64-release.config b/.github/workflows/kernel-riscv64-release.config index 242153977e16d7..7cee90953a7fe4 100644 --- a/.github/workflows/kernel-riscv64-release.config +++ b/.github/workflows/kernel-riscv64-release.config @@ -1110,7 +1110,10 @@ CONFIG_FRAME_POINTER=y # Generic Kernel Debugging Instruments # # CONFIG_MAGIC_SYSRQ is not set -# CONFIG_DEBUG_FS is not set +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y CONFIG_HAVE_ARCH_KGDB_QXFER_PKT=y # CONFIG_UBSAN is not set @@ -1202,6 +1205,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-x86_64-debug-thinlto.config b/.github/workflows/kernel-x86_64-debug-thinlto.config index 2eee24e6910bdb..19be7f94f8dbb2 100644 --- a/.github/workflows/kernel-x86_64-debug-thinlto.config +++ b/.github/workflows/kernel-x86_64-debug-thinlto.config @@ -1222,9 +1222,9 @@ CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 CONFIG_MAGIC_SYSRQ_SERIAL=y CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE="" CONFIG_DEBUG_FS=y -# CONFIG_DEBUG_FS_ALLOW_ALL is not set +CONFIG_DEBUG_FS_ALLOW_ALL=y # CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set -CONFIG_DEBUG_FS_ALLOW_NONE=y +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y CONFIG_KGDB=y CONFIG_KGDB_HONOUR_BLOCKLIST=y @@ -1441,6 +1441,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-debug.config b/.github/workflows/kernel-x86_64-debug.config index 0d882c5d8f5a81..2319e182cf6d88 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1217,9 +1217,9 @@ CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 CONFIG_MAGIC_SYSRQ_SERIAL=y CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE="" CONFIG_DEBUG_FS=y -# CONFIG_DEBUG_FS_ALLOW_ALL is not set +CONFIG_DEBUG_FS_ALLOW_ALL=y # CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set -CONFIG_DEBUG_FS_ALLOW_NONE=y +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y CONFIG_KGDB=y CONFIG_KGDB_HONOUR_BLOCKLIST=y @@ -1444,6 +1444,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-release-thinlto.config b/.github/workflows/kernel-x86_64-release-thinlto.config index b4534eb809b2b5..9be9f252389872 100644 --- a/.github/workflows/kernel-x86_64-release-thinlto.config +++ b/.github/workflows/kernel-x86_64-release-thinlto.config @@ -1289,7 +1289,10 @@ CONFIG_STACK_VALIDATION=y # Generic Kernel Debugging Instruments # # CONFIG_MAGIC_SYSRQ is not set -# CONFIG_DEBUG_FS is not set +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_DISALLOW_MOUNT is not set +# CONFIG_DEBUG_FS_ALLOW_NONE is not set CONFIG_HAVE_ARCH_KGDB=y CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y # CONFIG_UBSAN is not set @@ -1397,6 +1400,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-release.config b/.github/workflows/kernel-x86_64-release.config index dab8ac918dc953..6e079e12f2bc91 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1392,6 +1392,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 078de0887c6dba..dcb8eb7af4e0e9 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -37,4 +37,17 @@ busybox insmod rust_module_parameters_loadable_custom.ko \ busybox rmmod rust_module_parameters_loadable_default.ko busybox rmmod rust_module_parameters_loadable_custom.ko +busybox insmod rust_seq_file.ko +busybox mkdir proc +busybox mount -t proc proc /proc +busybox mkdir debugfs +busybox mount -t debugfs debugfs /debugfs +export RUST_SEQ_MINOR=$(busybox cat /proc/misc | busybox grep rust_seq_file | busybox cut -d ' ' -f 1) +busybox mknod /dev/rust_seq_file0 c 10 $RUST_SEQ_MINOR +busybox cat /dev/rust_seq_file0 +busybox cat /dev/rust_seq_file0 +busybox cat /debugfs/rust_seq_file +busybox rm /dev/rust_seq_file0 +busybox rmmod rust_seq_file.ko + busybox reboot -f diff --git a/.github/workflows/qemu-initramfs.desc b/.github/workflows/qemu-initramfs.desc index 0268b475dde56d..827da30c07f1b6 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -5,7 +5,6 @@ file /bin/busybox busybox 0755 0 0 slink /bin/sh /bin/busybox 0755 0 0 file /init .github/workflows/qemu-init.sh 0755 0 0 -file /rust_minimal.ko samples/rust/rust_minimal.ko 0755 0 0 file /rust_print.ko samples/rust/rust_print.ko 0755 0 0 file /rust_module_parameters.ko samples/rust/rust_module_parameters.ko 0755 0 0 file /rust_sync.ko samples/rust/rust_sync.ko 0755 0 0 @@ -17,3 +16,5 @@ file /rust_semaphore_c.ko samples/rust/rust_semaphore_c.ko 0755 file /rust_module_parameters_loadable_default.ko samples/rust/rust_module_parameters_loadable_default.ko 0755 0 0 file /rust_module_parameters_loadable_custom.ko samples/rust/rust_module_parameters_loadable_custom.ko 0755 0 0 +file /rust_minimal.ko samples/rust/rust_minimal.ko 0755 0 0 +file /rust_seq_file.ko samples/rust/rust_seq_file.ko 0755 0 0 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..6e3e33b3a98240 --- /dev/null +++ b/rust/kernel/debugfs.rs @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! TODOABK: finish doc + +use core::{ + marker::{PhantomData, Sync}, + ptr, +}; + +use crate::{bindings, c_types, error, str::CStr, types::PointerWrapper, Result}; + +/// An `inode` in debugfs with a `T` stored in `i_private`. +pub(crate) struct DebugFsDirEntry { + dentry: *mut bindings::dentry, + data: *mut c_types::c_void, + _wrapper: PhantomData, +} + +// SAFETY: There are methods available on [`DebugFsDirEntry`] so a thread can't +// actually do anything with a `&DebugFsDirEntry`. This makes it is safe to +// share across threads. +unsafe impl Sync for DebugFsDirEntry {} + +impl DebugFsDirEntry { + /// Create a file in `debugfs`. + /// + /// # Safety + /// + /// `fops` must be valid when opening an `inode` with `data::into_pointer` + /// stored in `i_private`. + pub(crate) unsafe fn create_file( + name: &CStr, + data: T, + fops: &'static bindings::file_operations, + ) -> Result { + let name = name.as_char_ptr(); + let data = data.into_pointer() as *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, ptr::null_mut(), 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(DebugFsDirEntry { + dentry, + data, + _wrapper: PhantomData, + }), + } + } +} + +impl Drop for DebugFsDirEntry { + fn drop(&mut self) { + // SAFETY: Calling a C function. `dentry` must have been created by a + // call to [`DebugFsDirEntry::create_file`] which always returns a valid + // `dentry`. + unsafe { + bindings::debugfs_remove(self.dentry); + } + // SAFETY: `self.data` was created by a call to `T::into_pointer` in + // [`DebugFsDirEntry::create_file`]. + 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..2612162379c0d8 --- /dev/null +++ b/rust/kernel/seq_file.rs @@ -0,0 +1,327 @@ +// 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 `/proc` 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::{self, Display}, + marker::PhantomData, + ptr, +}; + +use crate::{ + bindings, c_str, c_types, + debugfs::DebugFsDirEntry, + str::{CStr, CString}, + 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 constructed the vtable. + 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, + }; +} + +/// A file in [`debugfs`] which was created using a `seq_file`. +pub struct SeqFileDebugFsDirEntry { + _debugfs_entry: DebugFsDirEntry, +} + +/// Create a `seq_file` in [`debugfs`]. +pub fn debugfs_create_file( + name: fmt::Arguments<'_>, + data: T::OpenData, +) -> Result> { + let name = CString::try_from_fmt(name)?; + // 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`. + let debugfs_entry = unsafe { + DebugFsDirEntry::create_file(&name, data, &SeqFileOperationsVTable::::VTABLE) + }?; + Ok(SeqFileDebugFsDirEntry { + _debugfs_entry: debugfs_entry, + }) +} 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..bd4b8b3793f03d --- /dev/null +++ b/samples/rust/rust_seq_file.rs @@ -0,0 +1,150 @@ +// 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::{ + file::File, + file_operations::FileOperations, + io_buffer::IoBufferWriter, + miscdev, seq_file, + seq_file::SeqFileDebugFsDirEntry, + 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: SeqFileDebugFsDirEntry, +} + +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 debugfs = seq_file::debugfs_create_file(fmt!("{name}"), state.clone())?; + + Ok(RustMiscdev { + _dev: miscdev::Registration::new_pinned(fmt!("{name}"), state)?, + _debugfs: debugfs, + }) + } +} + +impl Drop for RustMiscdev { + fn drop(&mut self) { + pr_info!("Rust seq_file sample (exit)\n"); + } +}