diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b14da95787ffc5..36875868873926 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -368,6 +368,12 @@ jobs: grep '] rust_selftests: All tests passed. Congratulations!$' qemu-stdout.log grep '] rust_selftests: Rust self tests (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 83d8b504a09b97..d7fbb2e73a626a 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1782,6 +1782,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 2bbbbb524b0dca..7da58f7a7dc87a 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1706,6 +1706,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 685c8e0982efe1..3c4a046031eb74 100644 --- a/.github/workflows/kernel-arm64-debug-thinlto.config +++ b/.github/workflows/kernel-arm64-debug-thinlto.config @@ -1074,7 +1074,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1439,6 +1439,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 b20218794e90da..a1d660d3bf2994 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1069,7 +1069,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1434,6 +1434,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 25acb273a80ce7..173419948199ad 100644 --- a/.github/workflows/kernel-arm64-release-thinlto.config +++ b/.github/workflows/kernel-arm64-release-thinlto.config @@ -1069,7 +1069,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -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 @@ -1357,6 +1360,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 7ae05770df3ccb..da2607627fbc61 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1064,7 +1064,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1211,7 +1211,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 @@ -1352,6 +1355,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 163173416726b2..913d7b96450761 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1310,9 +1310,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 @@ -1494,6 +1494,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 1f9419ceec23cf..6a7290146f5dd5 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1366,7 +1366,10 @@ CONFIG_FRAME_WARN=2048 # 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 @@ -1456,6 +1459,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 0c39089a35ef68..7d434e20bd8bdf 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 @@ -1288,6 +1291,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 7748fa052c3147..2b46f7b38eff49 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 @@ -1204,6 +1207,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 a4db37edef2a48..1a7be80d62c37a 100644 --- a/.github/workflows/kernel-x86_64-debug-thinlto.config +++ b/.github/workflows/kernel-x86_64-debug-thinlto.config @@ -1064,7 +1064,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set CONFIG_KERNFS=y CONFIG_SYSFS=y @@ -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 @@ -1443,6 +1443,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 635eecc7ec72d6..0e91001a25e193 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1059,7 +1059,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set CONFIG_KERNFS=y CONFIG_SYSFS=y @@ -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 @@ -1446,6 +1446,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 0e29107cc253b0..82147d8cd556a8 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 @@ -1399,6 +1402,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 3da338aa731fc7..b73f9dc9798d3f 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1284,7 +1284,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 @@ -1394,6 +1397,7 @@ CONFIG_SAMPLE_RUST_SEMAPHORE_C=m CONFIG_SAMPLE_RUST_RANDOM=m CONFIG_SAMPLE_RUST_HOSTPROGS=y CONFIG_SAMPLE_RUST_SELFTESTS=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 62f6d470523105..a95a6d9e2acfc1 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -40,4 +40,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_debug/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 62d121e6984409..d048751f8be318 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -18,3 +18,4 @@ file /rust_selftests.ko samples/rust/rust_selftests.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_seq_file.ko samples/rust/rust_seq_file.ko 0755 0 0 diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 3dcf0b8b4e932d..4888146b3020d0 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -720,6 +720,18 @@ static void remove_one(struct dentry *victim) simple_release_fs(&debugfs_mount, &debugfs_mount_count); } +void debugfs_remove_with_callback(struct dentry *dentry, + void (*callback)(struct dentry *)) +{ + if (IS_ERR_OR_NULL(dentry)) + return; + + simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); + simple_recursive_removal(dentry, callback); + simple_release_fs(&debugfs_mount, &debugfs_mount_count); +} +EXPORT_SYMBOL_GPL(debugfs_remove_with_callback); + /** * debugfs_remove - recursively removes a directory * @dentry: a pointer to a the dentry of the directory to be removed. If this diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index c869f1e73d7558..0a96c0e0828eec 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h @@ -90,6 +90,8 @@ struct dentry *debugfs_create_automount(const char *name, void debugfs_remove(struct dentry *dentry); #define debugfs_remove_recursive debugfs_remove +void debugfs_remove_with_callback(struct dentry *dentry, + void (*callback)(struct dentry *)); const struct file_operations *debugfs_real_fops(const struct file *filp); diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 284793085d5530..df10e1b8f29f12 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #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..a927c3e27b6b8a --- /dev/null +++ b/rust/kernel/debugfs.rs @@ -0,0 +1,164 @@ +// 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; +use core::{any::Any, marker::Sync, ptr}; + +use crate::{ + bindings::{self, debugfs_remove_with_callback}, + error, + str::CStr, + types::PointerWrapper, + Result, +}; + +/// An `dentry` for a directory in debugfs. +pub struct DebugFsDirectory { + dentry: *mut bindings::dentry, + has_parent: bool, +} + +// 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. +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. + pub fn create(name: &CStr, parent: Option<&mut DebugFsDirectory>) -> Result { + let name = name.as_char_ptr(); + let has_parent = parent.is_some(); + let parent_ptr = parent.map(|p| p.dentry).unwrap_or_else(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 }) + } +} + +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. + // + // This `dentry` and every `dentry` in it was created with either + // `DebugFsDirectory::create` or `DebugFsFile::create`. Both + // functions guarantee that the created `dentry` has a valide + // `inode` and the `inode`'s `i_private` field will be either null + // or come from calling `PointerWrapper::into_pointer` on a + // `Box>`. This makes it safe to call `drop_i_private` + // on each `dentry` in `self.dentry`. + unsafe { debugfs_remove_with_callback(self.dentry, Some(drop_i_private)) }; + } + } +} + +/// A `dentry` for a file in debugfs with a `T` stored in `i_private`. +pub struct DebugFsFile { + dentry: Option<*mut bindings::dentry>, +} + +// 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 a `Box>::into_pointer` that can be downcast to `T` stored in `i_private`. + #[allow(dead_code)] // Remove when a caller is implemented. + pub(crate) unsafe fn create( + name: &CStr, + parent: Option<&mut DebugFsDirectory>, + data: T, + fops: &'static bindings::file_operations, + ) -> Result { + let has_parent = parent.is_some(); + let name = name.as_char_ptr(); + let boxed1: Box = Box::try_new(data)?; + let boxed2 = Box::try_new(boxed1)?; + let data = PointerWrapper::into_pointer(boxed2) 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 a `Box>::into_pointer` + // that can be downcast to `T` stored 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 calling + // `PointerWrapper::into_pointer` on a `Box>` just + // above. + let _: Box> = unsafe { PointerWrapper::from_pointer(data) }; + Err(err) + } + Ok(dentry) => Ok(DebugFsFile { + dentry: if has_parent { None } else { Some(dentry) }, + }), + } + } +} + +impl Drop for DebugFsFile { + fn drop(&mut self) { + // If there is no dentry then this file has a parent `DebugFsDirectory` + // which is responsible for removal. + if let Some(dentry) = self.dentry { + // 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. + // + // A `DebugFsFile` is created by calling `debugfs_create_file` + // (which always creates a valid `dentry` with a valid `d_inode` + // field) and passing in a pointer coming from a `Box>` + // which gets put in the `inode`'s `i_private` field. This is + // sufficient for `drop_i_private` to be safely called on the + // `dentry`. + unsafe { debugfs_remove_with_callback(dentry, Some(drop_i_private)) }; + } + } +} + +/// # Safety +/// `dentry` must be a valid `bindings::dentry` with a valid `d_inode` field. In +/// addition, the `i_private` field of `d_inode` must be either a null pointer +/// or one created by calling `PointerWrapper::into_pointer` on a `Box>`. +unsafe extern "C" fn drop_i_private(dentry: *mut bindings::dentry) { + // SAFETY: Caller guarantees that `dentry->d_inode` can be dereferenced. + let i_private = unsafe { (*(*dentry).d_inode).i_private }; + // SAFETY: Caller guarantees that `dentry->d_inode->i_private` is either + // null, or generated by calling `PointerWrapper::into_pointer` on a + // `Box>`. + if !i_private.is_null() { + let _: Box> = unsafe { PointerWrapper::from_pointer(i_private) }; + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index b55fe00761c2bb..492848967f49fc 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -49,6 +49,8 @@ pub mod chrdev; #[cfg(CONFIG_COMMON_CLK)] pub mod clk; pub mod cred; +#[cfg(CONFIG_DEBUG_FS)] +pub mod debugfs; pub mod delay; pub mod device; pub mod driver; @@ -83,6 +85,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..41c19dd138a1a1 --- /dev/null +++ b/rust/kernel/seq_file.rs @@ -0,0 +1,323 @@ +// 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: + +use alloc::boxed::Box; + +use core::{ + any::Any, + ffi::{c_int, c_void}, + fmt::Display, + marker::PhantomData, + ptr, +}; + +use crate::{ + bindings, c_str, + debugfs::{DebugFsDirectory, DebugFsFile}, + prelude::*, + 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::{ +/// Error, +/// Result, +/// seq_file, +/// sync::{Mutex, Ref, RefBorrow}, +/// }; +/// +/// 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(open_data: &Ref) -> Result> { +/// Ok(open_data.clone()) +/// } +/// +/// fn start(data: RefBorrow<'_, 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((total, current): &(usize, usize)) -> core::option::Option { +/// 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: Any; + + /// 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(open_data: &Self::OpenData) -> Result; + + /// Called once on each execution of fops->read or fops->read_iter. + fn start( + data: ::Borrowed<'_>, + ) -> 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( + iterator: ::Borrowed<'_>, + ) -> Option; +} + +extern "C" fn stop_callback(_m: *mut bindings::seq_file, v: *mut 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_void, + pos: *mut bindings::loff_t, +) -> *mut 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_void) -> 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_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 `>>::into_pointer` on a a `T::OpenData`. + unsafe extern "C" fn open_callback( + inode: *mut bindings::inode, + file: *mut bindings::file, + ) -> 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 `>>::into_pointer` and that the + // resulting value can be downcast to `T::OpenData`. + let data = + unsafe { > as PointerWrapper>::borrow((*inode).i_private) }.as_ref(); + let open_data = match data.downcast_ref() { + Some(data) => data, + None => return EINVAL.to_kernel_errno(), + }; + + 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_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_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, + uring_cmd: None, + write: None, + write_iter: None, + }; +} + +/// Create a `seq_file` in [`debugfs`](`crate::debugfs`) under the `parent` +/// directory. If no `parent` is given, the file will be created at the top +/// level of `debugfs`. +pub fn debugfs_create( + name: &CStr, + parent: Option<&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(name, parent, data, &SeqFileOperationsVTable::::VTABLE) } +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 861b35318b4d6e..f30cbbe4fa5c33 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -162,4 +162,15 @@ config SAMPLE_RUST_SELFTESTS 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 420bcefeb08255..9fd3a307f5a1db 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -15,5 +15,6 @@ obj-$(CONFIG_SAMPLE_RUST_NETFILTER) += rust_netfilter.o obj-$(CONFIG_SAMPLE_RUST_ECHO_SERVER) += rust_echo_server.o obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o +obj-$(CONFIG_SAMPLE_RUST_SEQ_FILE) += rust_seq_file.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_seq_file.rs b/samples/rust/rust_seq_file.rs new file mode 100644 index 00000000000000..dda745c15310c9 --- /dev/null +++ b/samples/rust/rust_seq_file.rs @@ -0,0 +1,152 @@ +// 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::{self, File}, + 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; +#[vtable] +impl file::Operations for Token { + type Data = Ref; + type OpenData = Ref; + + 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(open_data: &Ref) -> Result> { + Ok(open_data.clone()) + } + + fn start(data: RefBorrow<'_, 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 kernel::Module 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(&dir_name, None)?; + let file_name = CString::try_from_fmt(fmt!("{name}"))?; + seq_file::debugfs_create::(&file_name, Some(&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"); + } +}