diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b14da95787ffc5..6abacbdbe7ac46 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -368,6 +368,11 @@ jobs: grep '] rust_selftests: All tests passed. Congratulations!$' qemu-stdout.log grep '] rust_selftests: Rust self tests (exit)$' qemu-stdout.log + - run: | + grep '] rust_debugfs: Rust debugfs sample (init)$' qemu-stdout.log + grep '] rust_debugfs: Rust debugfs sample (exit)$' qemu-stdout.log + grep '^Debugfs file read count: 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..4d118aab65c28a 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_DEBUGFS=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..ad0c1464a4284f 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_DEBUGFS=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..4e057b35c01cfc 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_DEBUGFS=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index b20218794e90da..4989ac24c05832 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_DEBUGFS=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release-thinlto.config b/.github/workflows/kernel-arm64-release-thinlto.config index 25acb273a80ce7..71ea4ffaae643e 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_DEBUGFS=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 7ae05770df3ccb..c07a05261f3860 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_DEBUGFS=m # # arm64 Debugging diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index 163173416726b2..33b1571af2a536 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_DEBUGFS=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..5e38f7891158c2 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_DEBUGFS=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..5288ee1428d859 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_DEBUGFS=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..8956e0a33e8a7f 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_DEBUGFS=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..ccd62d3b18b505 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_DEBUGFS=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..fc89939c23c1e4 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_DEBUGFS=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..2d8601c1d39575 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_DEBUGFS=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..3bd5c36068ed03 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_DEBUGFS=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..323a5c81a491d2 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_debugfs.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_debugfs | busybox cut -d ' ' -f 1) +busybox mknod /dev/rust_debugfs0 c 10 $RUST_SEQ_MINOR +busybox cat /dev/rust_debugfs0 +busybox cat /dev/rust_debugfs0 +busybox cat /debugfs/rust_debugfs_debug/rust_debugfs +busybox rm /dev/rust_debugfs0 +busybox rmmod rust_debugfs.ko + busybox reboot -f diff --git a/.github/workflows/qemu-initramfs.desc b/.github/workflows/qemu-initramfs.desc index 62d121e6984409..db3e514e035a1f 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -15,6 +15,7 @@ file /rust_stack_probing.ko samples/rust/rust_stack_probing.ko 0755 file /rust_semaphore.ko samples/rust/rust_semaphore.ko 0755 0 0 file /rust_semaphore_c.ko samples/rust/rust_semaphore_c.ko 0755 0 0 file /rust_selftests.ko samples/rust/rust_selftests.ko 0755 0 0 +file /rust_debugfs.ko samples/rust/rust_debugfs.ko 0755 0 0 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 diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 3dcf0b8b4e932d..222f19a3a67922 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -713,35 +713,70 @@ static void __debugfs_file_removed(struct dentry *dentry) wait_for_completion(&fsd->active_users_drained); } -static void remove_one(struct dentry *victim) +void debugfs_remove_one_with_callback(struct dentry *victim, + void (*callback)(struct dentry *)) { + callback(victim); if (d_is_reg(victim)) __debugfs_file_removed(victim); simple_release_fs(&debugfs_mount, &debugfs_mount_count); } +EXPORT_SYMBOL_GPL(debugfs_remove_one_with_callback); + +static void empty_callback(struct dentry *dentry) {} + +static void remove_one(struct dentry *victim) +{ + debugfs_remove_one_with_callback(victim, empty_callback); +} /** - * debugfs_remove - recursively removes a directory + * debugfs_remove_with_callback - recursively removes a directory with an + * additional callback to be run on each dentry. This is intended to be used + * from Rust modules that need to pass in a Rust destructor to drop data in the + * dentry's. * @dentry: a pointer to a the dentry of the directory to be removed. If this * parameter is NULL or an error value, nothing will be done. + * @callback: a pointer to a callback which will be run on each dentry before + * it is removed. * * This function recursively removes a directory tree in debugfs that * was previously created with a call to another debugfs function * (like debugfs_create_file() or variants thereof.) * - * This function is required to be called in order for the file to be - * removed, no automatic cleanup of files will happen when a module is - * removed, you are responsible here. + * This function (or debugfs_remove) is required to be called in order for the + * file to be removed, no automatic cleanup of files will happen when a module + * is removed, you are responsible here. */ -void debugfs_remove(struct dentry *dentry) +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, remove_one); + 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 + * parameter is NULL or an error value, nothing will be done. + * + * This function recursively removes a directory tree in debugfs that + * was previously created with a call to another debugfs function + * (like debugfs_create_file() or variants thereof.) + * + * This function is required to be called in order for the file to be + * removed, no automatic cleanup of files will happen when a module is + * removed, you are responsible here. + */ +void debugfs_remove(struct dentry *dentry) +{ + debugfs_remove_with_callback(dentry, remove_one); +} EXPORT_SYMBOL_GPL(debugfs_remove); /** diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index c869f1e73d7558..62ded720dffd45 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h @@ -90,6 +90,10 @@ 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 *)); +void debugfs_remove_one_with_callback(struct dentry *victim, + 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..ca3a9f6c548043 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -10,6 +10,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..1cd8969c817bc7 --- /dev/null +++ b/rust/kernel/debugfs.rs @@ -0,0 +1,218 @@ +// 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::{PhantomData, Sync}, + ptr, +}; + +use crate::{ + bindings::{self, debugfs_remove_with_callback}, + error, + file::{OpenAdapter, Operations, OperationsVtable}, + 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 and removed on drop. If + /// a `parent` is given then the `parent` is responsible for removing the + /// directory. + 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>`. They both only create dentry's in debugfs. + // This makes it safe to call `remove_rust_dentry` on each `dentry` + // in `self.dentry`. + unsafe { debugfs_remove_with_callback(self.dentry, Some(remove_rust_dentry)) }; + } + } +} + +/// A `dentry` for a file in debugfs with a `T` stored in `i_private`. +pub struct DebugFsFile { + dentry: Option<*mut bindings::dentry>, + _t: 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 and it will be removed on drop. If a `parent` is provided then + /// the `parent` is responsible for removing this file. + /// + /// # Safety + /// + /// `fops` must be valid when opening an `inode` with a `Box>::into_pointer` that can be downcast to `T` stored in `i_private`. + 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) }, + _t: PhantomData, + }), + } + } +} + +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` in debugfs 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 `remove_rust_dentry` to be safely + // called on the `dentry`. + unsafe { debugfs_remove_with_callback(dentry, Some(remove_rust_dentry)) }; + } + } +} + +/// # 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) }; + } +} + +/// # Safety +/// `dentry` must be in debugfs and satify all the requirements for +/// `drop_i_private` to be safely called on it. +unsafe extern "C" fn remove_rust_dentry(dentry: *mut bindings::dentry) { + // SAFETY: The caller is responsible for ensuring that `drop_i_private` can + // be called on `dentry` and that the dentry is in debugfs. + unsafe { bindings::debugfs_remove_one_with_callback(dentry, Some(drop_i_private)) } +} + +impl OpenAdapter for DebugFsFile { + unsafe fn convert(inode: *mut bindings::inode, _file: *mut bindings::file) -> *const T { + let data: &dyn Any = + unsafe { > as PointerWrapper>::borrow((*inode).i_private) }.as_ref(); + let open_data: &T = match data.downcast_ref() { + Some(data) => data, + None => panic!(), + }; + open_data + } +} + +/// Create a file in `debugfs` under `parent`. If `parent` is `None` then the +/// folder will be created at the top level of `debugfs`. +pub fn debugfs_create( + name: &CStr, + parent: Option<&mut DebugFsDirectory>, + data: T::OpenData, +) -> Result> +where + T::OpenData: 'static, +{ + // SAFETY: The `OpenAdapter` implementation for `DebugFsFile` + // expects a the opened `inode` to have a `Box>` in `i_private` + // that can be downcast to `T::OpenData`. `data` has type `T::OpenData` so + // this satisfies the safety requirements on `DebugFsFile::create`. + unsafe { + DebugFsFile::create( + name, + parent, + data, + OperationsVtable::, T>::build(), + ) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index b55fe00761c2bb..fbda6b92f1ba79 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; diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 189c10ced6d4f7..e3369af1a55768 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -163,4 +163,16 @@ config SAMPLE_RUST_SELFTESTS If unsure, say N. +config SAMPLE_RUST_DEBUGFS + tristate "Debug fs" + depends on DEBUG_FS + help + This option builds the Rust debugfs sample. + + To compile this as a module, choose M here: + the module will be called rust_debugfs. + + If unsure, say N. + + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 420bcefeb08255..5e56fbc3cec5a8 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_DEBUGFS) += rust_debugfs.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs new file mode 100644 index 00000000000000..626721ce3b8b6a --- /dev/null +++ b/samples/rust/rust_debugfs.rs @@ -0,0 +1,124 @@ +// 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::{debugfs_create, DebugFsDirectory}, + file::{self, File}, + io_buffer::IoBufferWriter, + miscdev, + str::CString, + sync::{Mutex, Ref, RefBorrow, UniqueRef}, +}; + +module! { + type: RustMiscdev, + name: "rust_debugfs", + author: "Rust for Linux Contributors", + description: "Sample Rust miscellaneous device with a debugfs entry", + license: "GPL v2", +} + +#[derive(Clone, Copy)] +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 { + let mut inner = shared.inner.lock(); + inner.read_count += 1; + Ok(0) + } +} + +struct DebugFsToken; +#[vtable] +impl file::Operations for DebugFsToken { + type OpenData = Ref; + type Data = Ref; + + fn open(shared: &Ref, _file: &File) -> Result { + Ok(shared.clone()) + } + + fn read( + shared: RefBorrow<'_, SharedState>, + _: &File, + data: &mut impl IoBufferWriter, + offset: u64, + ) -> Result { + if offset != 0 { + return Ok(0); + } + + let read_count = shared.inner.lock().read_count; + let string = CString::try_from_fmt(fmt!("Debugfs file read count: {}\n", read_count))?; + data.write_slice(string.as_bytes())?; + Ok(string.len()) + } +} + +struct RustMiscdev { + _dev: Pin>>, + _debugfs_dir: DebugFsDirectory, +} + +impl kernel::Module for RustMiscdev { + fn init(name: &'static CStr, _module: &'static ThisModule) -> Result { + pr_info!("Rust debugfs 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}"))?; + let _debugfs_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 debugfs sample (exit)\n"); + } +}