Skip to contentSkip to conversation

rust: DebugFS Bindings

PATCH Superseded Matthew Maurer 6 participants 49 messages Jun 27, 2025 – Jul 3, 2025

Subsystems (2)

v8 of 11 Superseded Latest is v11 →

lore
Share & tools
lore
#0 lore raw

v8 cover rust: DebugFS Bindings

This series provides safe DebugFS bindings for Rust, with a sample module using them.

---
Changes in v8:
- Switched from casts to core::from_{ref, mut} in type change
- Link to v7: https://lore.kernel.org/r/[email protected]

Changes in v7:
- Rewrote entry::Entry -> Entry
- Use c_int and c_void from kernel prelude rather than core
- Removed unnecessary display_open cast
- Switched from Deref + an explicit box to ForeignOwnable for
attaching owned data.
- Made &'static and &'static mut implement ForeignOwnable
- Swapped "driver" to "module" in sample code
- Link to v6: https://lore.kernel.org/r/[email protected]

Changes in v6:
- Replaced explicit lifetimes with children keeping their parents alive.
- Added support for attaching owned data.
- Removed recomendation to only keep root handles and handles you want
to delete around.
- Refactored some code into separate files to improve clarity.
- Link to v5: https://lore.kernel.org/r/[email protected]

Changes in v5:
- Made Dir + File wrappers around Entry
- All functions return owning handles. To discard without drop, use forget. To keep a handle without drop, use ManuallyDrop.
- Fixed bugs around not(CONFIG_DEBUG_FS)
- Removed unnecessary addr_of!
- Link to v4: https://lore.kernel.org/r/[email protected]

Changes in v4:
- Remove SubDir, replace with type-level constant.
- Add lifetime to Dir to prevent subdirectories and files from outliving their parents and triggering an Oops when accessed.
- Split unsafe blocks with two calls into two blocks
- Access private field through direct pointer dereference, avoiding creation of a reference to it.
- Notably not changed - owning/non-owning handle defaults. The best read
I had from the thread was to continue with this mode, but I'm willing
to change if need be.
- Comment changes
- More comment markdown
- Remove scopes from examples
- Put as_ptr properties into a # Guarantees section.
- Link to v3: https://lore.kernel.org/r/[email protected]

Changes in v3:
- Split Dir into Dir/SubDir/File to improve API.
- Add "." to end of all comments.
- Convert INVARIANT to # Invariants on types.
- Add backticks everywhere I found variables/types in my comments.
- Promoted invariant comment to doc comment.
- Extended sample commenting to make it clearer what is happening.
- Link to v2: https://lore.kernel.org/r/[email protected]

Changes in v2:
- Drop support for builder / pinned bindings in initial series
- Remove ARef usage to abstract the dentry nature of handles
- Remove error handling to discourage users from caring whether DebugFS
is enabled.
- Support CONFIG_DEBUG_FS=n while leaving the API available
- Fixed mistaken decimal/octal mixup
- Doc/comment cleanup
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Matthew Maurer (6):
rust: debugfs: Bind DebugFS directory creation
rust: debugfs: Bind file creation for long-lived Display
rust: types: Support &'static and &'static mut ForeignOwnable
rust: debugfs: Support arbitrary owned backing for File
rust: debugfs: Support format hooks
rust: samples: Add debugfs sample

 MAINTAINERS                         |   3 +
 rust/bindings/bindings_helper.h     |   1 +
 rust/kernel/debugfs.rs              | 268 ++++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/display_file.rs | 155 +++++++++++++++++++++
 rust/kernel/debugfs/entry.rs        |  66 +++++++++
 rust/kernel/lib.rs                  |   1 +
 rust/kernel/types.rs                |  58 ++++++++
 samples/rust/Kconfig                |  11 ++
 samples/rust/Makefile               |   1 +
 samples/rust/rust_debugfs.rs        |  76 ++++++++++
 10 files changed, 640 insertions(+)

---
base-commit: bd243cb5d922cc6343007a3f3918c8d1970541d2
change-id: 20250428-debugfs-rust-3cd5c97eb7d1

Best regards,

Signature · 1 line
Matthew Maurer <[email protected]>
#1 lore raw

PATCH v8 1/6 rust: debugfs: Bind DebugFS directory creation

Support creating DebugFS directories and subdirectories. Similar to the original DebugFS API, errors are hidden.

Directories persist until their handle and the handles of any child objects have been dropped.

---
 MAINTAINERS                     |  2 +
 rust/bindings/bindings_helper.h |  1 +
 rust/kernel/debugfs.rs          | 90 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/entry.rs    | 58 ++++++++++++++++++++++++++
 rust/kernel/lib.rs              |  1 +
 5 files changed, 152 insertions(+)
MAINTAINERS +2 -0
index c3f7fbd0d67afe8376ea84bc17d70e9fa4b89bf6..551e3a6a16d9051a2d58a77614c458287da2bdcc 100644
@@ -7366,6 +7366,8 @@ F:	include/linux/kobj*
 F:	include/linux/property.h
 F:	include/linux/sysfs.h
 F:	lib/kobj*
+F:	rust/kernel/debugfs.rs
+F:	rust/kernel/debugfs/
 F:	rust/kernel/device.rs
 F:	rust/kernel/device_id.rs
 F:	rust/kernel/devres.rs
rust/bindings/bindings_helper.h +1 -0
index 8cbb660e2ec218021d16e6e0144acf6f4d7cca13..655d88b8e7fe717190ccfb7b8173e84213bf8331 100644
@@ -45,6 +45,7 @@
 #include <linux/cpufreq.h>
 #include <linux/cpumask.h>
 #include <linux/cred.h>
+#include <linux/debugfs.h>
 #include <linux/device/faux.h>
 #include <linux/dma-mapping.h>
 #include <linux/errname.h>
rust/kernel/debugfs.rs new file +90 -0

90 lines not on this page · raw message

rust/kernel/debugfs/entry.rs new file +58 -0

58 lines not on this page · raw message

rust/kernel/lib.rs +1 -0
index 6b4774b2b1c37f4da1866e993be6230bc6715841..43f40b1baa9717ea71e4586042e9e6979491ad37 100644
@@ -66,6 +66,7 @@
 pub mod cpufreq;
 pub mod cpumask;
 pub mod cred;
+pub mod debugfs;
 pub mod device;
 pub mod device_id;
 pub mod devres;
Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
#2 lore raw

PATCH v8 2/6 rust: debugfs: Bind file creation for long-lived Display

In reply to Matthew Maurer

Allows creation of files for references that live forever and lack metadata through the Display implementation.

The Display implementation is used because seq_printf needs to route through %pA, which in turn routes through Arguments.

---
 rust/kernel/debugfs.rs              | 62 +++++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/display_file.rs | 61 ++++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/entry.rs        |  8 +++++
 3 files changed, 131 insertions(+)
rust/kernel/debugfs.rs +62 -0
index 2359bd11cd664fb9f7206f8fe38f758dc43d2cb8..1f20d85da56fcb89476552feefc9d97fab43cc04 100644
@@ -10,7 +10,10 @@
 use crate::str::CStr;
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
+use core::fmt::Display;
 
+#[cfg(CONFIG_DEBUG_FS)]
+mod display_file;
 #[cfg(CONFIG_DEBUG_FS)]
 mod entry;
 #[cfg(CONFIG_DEBUG_FS)]
@@ -59,6 +62,43 @@ fn create(_name: &CStr, _parent: Option<&Dir>) -> Self {
         Self()
     }
 
+    #[cfg(CONFIG_DEBUG_FS)]
+    fn create_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+        let Some(parent) = &self.0 else {
+            return File {
+                _entry: Entry::empty(),
+            };
+        };
+        // SAFETY:
+        // * `name` is a NUL-terminated C string, living across the call, by `CStr` invariant.
+        // * `parent` is a live `dentry` since we have a reference to it.
+        // * `vtable` is all stock `seq_file` implementations except for `open`.
+        //   `open`'s only requirement beyond what is provided to all open functions is that the
+        //   inode's data pointer must point to a `T` that will outlive it, which we know because
+        //   we have a static reference.
+        let ptr = unsafe {
+            bindings::debugfs_create_file_full(
+                name.as_char_ptr(),
+                0o444,
+                parent.as_ptr(),
+                data as *const _ as *mut _,
+                core::ptr::null(),
+                &<T as display_file::DisplayFile>::VTABLE,
+            )
+        };
+
+        // SAFETY: `debugfs_create_file_full` either returns an error code or a legal
+        // dentry pointer, so `Entry::new` is safe to call here.
+        let entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
+
+        File { _entry: entry }
+    }
+
+    #[cfg(not(CONFIG_DEBUG_FS))]
+    fn create_file<T: Display + Sized>(&self, _name: &CStr, _data: &'static T) -> File {
+        File {}
+    }
+
     /// Create a DebugFS subdirectory.
     ///
     /// Subdirectory handles cannot outlive the directory handle they were created from.
@@ -75,6 +115,22 @@ pub fn subdir(&self, name: &CStr) -> Self {
         Dir::create(name, Some(self))
     }
 
+    /// Create a file in a DebugFS directory with the provided name, and contents from invoking
+    /// [`Display::fmt`] on the provided reference.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// let dir = Dir::new(c_str!("my_debugfs_dir"));
+    /// dir.display_file(c_str!("foo"), &200);
+    /// // "my_debugfs_dir/foo" now contains the number 200.
+    /// ```
+    pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+        self.create_file(name, data)
+    }
+
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
@@ -88,3 +144,9 @@ pub fn new(name: &CStr) -> Self {
         Dir::create(name, None)
     }
 }
+
+/// Handle to a DebugFS file.
+pub struct File {
+    #[cfg(CONFIG_DEBUG_FS)]
+    _entry: Entry,
+}
rust/kernel/debugfs/display_file.rs new file +61 -0

61 lines not on this page · raw message

rust/kernel/debugfs/entry.rs +8 -0
index ae0e2c4e1d58e878ebb081a71e4ac0f4a7d99b91..2baaf31c326c3071b92b5bc37552907fa1102246 100644
@@ -38,6 +38,14 @@ pub(crate) unsafe fn new(entry: *mut bindings::dentry, parent: Option<Arc<Entry>
         }
     }
 
+    /// Constructs a placeholder DebugFS [`Entry`].
+    pub(crate) fn empty() -> Self {
+        Self {
+            entry: core::ptr::null_mut(),
+            _parent: None,
+        }
+    }
+
     /// Returns the pointer representation of the DebugFS directory.
     ///
     /// # Guarantees
Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
#3 lore raw

PATCH v8 3/6 rust: types: Support &'static and &'static mut ForeignOwnable

In reply to Matthew Maurer

These types live forever and do not require cleanup, so they can serve as ForeignOwnable.

---
 rust/kernel/types.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)
rust/kernel/types.rs +58 -0
index 22985b6f69820d6df8ff3aae0bf815fad36a9d92..0a2b15cd05f91c69ef9c678555b845a23c19b82c 100644
@@ -142,6 +142,64 @@ unsafe fn borrow<'a>(_: *mut Self::PointedTo) -> Self::Borrowed<'a> {}
     unsafe fn borrow_mut<'a>(_: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {}
 }
 
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static T {
+    type PointedTo = T;
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut Self::PointedTo {
+        core::ptr::from_ref(self).cast_mut()
+    }
+
+    unsafe fn from_foreign(foreign: *mut Self::PointedTo) -> Self {
+        // SAFETY: from_foreign has stricter restrictions than borrow
+        unsafe { Self::borrow(foreign) }
+    }
+
+    unsafe fn borrow<'a>(foreign: *mut Self::PointedTo) -> Self::Borrowed<'a> {
+        // SAFETY: We know the original reference lived forever, so we can convert it back
+        unsafe { &*foreign }
+    }
+
+    unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {
+        // SAFETY: borrow_mut has stricter restrictions than borrow
+        unsafe { Self::borrow(foreign) }
+    }
+}
+
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static mut T {
+    type PointedTo = T;
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a mut T;
+
+    fn into_foreign(self) -> *mut Self::PointedTo {
+        core::ptr::from_mut(self)
+    }
+
+    unsafe fn from_foreign(foreign: *mut Self::PointedTo) -> Self {
+        // SAFETY: from_foreign has stricter restrictions than `borrow_mut`
+        unsafe { Self::borrow_mut(foreign) }
+    }
+
+    unsafe fn borrow<'a>(foreign: *mut Self::PointedTo) -> Self::Borrowed<'a> {
+        // SAFETY: We know the original reference lived forever, and the requirements on the
+        // function indicate that `from_foreign` and `borrow_mut` will not happen concurrently, so
+        // we can do a shared borrow.
+        unsafe { &*foreign }
+    }
+
+    unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {
+        // SAFETY: We know the original reference lived forever, and the requirements on the
+        // function indicate that no other borrows will happen concurrently, so we can do a
+        // unique borrow.
+        unsafe { &mut *foreign }
+    }
+}
+
 /// Runs a cleanup function/closure when dropped.
 ///
 /// The [`ScopeGuard::dismiss`] function prevents the cleanup function from running.
Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
#4 lore raw

Re: [PATCH v8 3/6] rust: types: Support &'static and &'static mut ForeignOwnable

Quoted 24 lines · Matthew Maurer

These types live forever and do not require cleanup, so they can serve as ForeignOwnable.

---
 rust/kernel/types.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)
rust/kernel/types.rs +7 -0
index 22985b6f69820d6df8ff3aae0bf815fad36a9d92..0a2b15cd05f91c69ef9c678555b845a23c19b82c 100644
@@ -142,6 +142,64 @@ unsafe fn borrow<'a>(_: *mut Self::PointedTo) -> Self::Borrowed<'a> {}
     unsafe fn borrow_mut<'a>(_: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {}
 }
 
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static T {
+    type PointedTo = T;
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+

...

rust/kernel/types.rs

Quoted 6 lines
rust/kernel/types.rs +6 -0
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static mut T {
+    type PointedTo = T;
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a mut T;

Just fyi, depending on what hits mainline first, this patch series or

https://lore.kernel.org/rust-for-linux/[email protected]/

the latter might need something like [1].

Dirk

[1]

(quoted diff) +0 -0

b/rust/kernel/debugfs/display_file.rs
index b38675a90e1b..9cea3bd633dc 100644

rust/kernel/debugfs/display_file.rs +0 -0
@@ -102,6 +102,7 @@ pub(crate) struct BorrowedAdapter<'a, D:

ForeignOwnable, F> {
// SAFETY: We delegate to D's implementation of ForeignOwnable, so
into_foreign produced aligned
// pointers.
unsafe impl<D: ForeignOwnable, F> ForeignOwnable for FormatAdapter<D, F> {

+    type Target = D;
     type PointedTo = D::PointedTo;
     type Borrowed<'a> = BorrowedAdapter<'a, D, F>;
     type BorrowedMut<'a> = Self::Borrowed<'a>;
rust/kernel/types.rs +0 -0
index ffbd750e1eda..c2d87e077c8f 100644
@@ -149,6 +149,7 @@ unsafe fn borrow_mut<'a>(_: *mut Self::PointedTo) ->

Self::BorrowedMut<'a> {}
// SAFETY: The into_foreign function derives its pointer from a
reference, so it is correctly
// aligned.
unsafe impl<T: 'static> ForeignOwnable for &'static T {

+    type Target = T;
     type PointedTo = T;
     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a T;

@@ -176,6 +177,7 @@ unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a>
// SAFETY: The into_foreign function derives its pointer from a
reference, so it is correctly
// aligned.
unsafe impl<T: 'static> ForeignOwnable for &'static mut T {

+    type Target = T;
     type PointedTo = T;
     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a mut T;
#6 lore raw

PATCH v8 4/6 rust: debugfs: Support arbitrary owned backing for File

In reply to Matthew Maurer

This allows Files to be backed by ForeignOwnable rather than just &'static T. This means that dynamically allocated objects can be attached to Files without needing to take extra steps to create a pinned reference that's guaranteed to live long enough.

---
 rust/kernel/debugfs.rs              | 99 +++++++++++++++++++++++++++++++------
 rust/kernel/debugfs/display_file.rs | 49 +++++++++++-------
 2 files changed, 115 insertions(+), 33 deletions(-)
rust/kernel/debugfs.rs +85 -14
index 1f20d85da56fcb89476552feefc9d97fab43cc04..929e55ee5629f6888edf29997b9ed77d274e11c8 100644
@@ -5,11 +5,11 @@
 //!
 //! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
 
-#[cfg(CONFIG_DEBUG_FS)]
-use crate::prelude::GFP_KERNEL;
+use crate::prelude::*;
 use crate::str::CStr;
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
+use crate::types::ForeignOwnable;
 use core::fmt::Display;
 
 #[cfg(CONFIG_DEBUG_FS)]
@@ -63,40 +63,52 @@ fn create(_name: &CStr, _parent: Option<&Dir>) -> Self {
     }
 
     #[cfg(CONFIG_DEBUG_FS)]
-    fn create_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+    fn create_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        let mut file = File {
+            _entry: Entry::empty(),
+            _foreign: ForeignHolder::new(data),
+        };
+
         let Some(parent) = &self.0 else {
-            return File {
-                _entry: Entry::empty(),
-            };
+            return file;
         };
+
         // SAFETY:
         // * `name` is a NUL-terminated C string, living across the call, by `CStr` invariant.
         // * `parent` is a live `dentry` since we have a reference to it.
         // * `vtable` is all stock `seq_file` implementations except for `open`.
         //   `open`'s only requirement beyond what is provided to all open functions is that the
         //   inode's data pointer must point to a `T` that will outlive it, which we know because
-        //   we have a static reference.
+        //   we have an owning `D` in the `File`, and we tear down the file during `Drop`.
         let ptr = unsafe {
             bindings::debugfs_create_file_full(
                 name.as_char_ptr(),
                 0o444,
                 parent.as_ptr(),
-                data as *const _ as *mut _,
+                file._foreign.data,
                 core::ptr::null(),
-                &<T as display_file::DisplayFile>::VTABLE,
+                &<D as display_file::DisplayFile>::VTABLE,
             )
         };
 
         // SAFETY: `debugfs_create_file_full` either returns an error code or a legal
         // dentry pointer, so `Entry::new` is safe to call here.
-        let entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
+        file._entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
 
-        File { _entry: entry }
+        file
     }
 
     #[cfg(not(CONFIG_DEBUG_FS))]
-    fn create_file<T: Display + Sized>(&self, _name: &CStr, _data: &'static T) -> File {
-        File {}
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
     }
 
     /// Create a DebugFS subdirectory.
@@ -127,7 +139,21 @@ pub fn subdir(&self, name: &CStr) -> Self {
     /// dir.display_file(c_str!("foo"), &200);
     /// // "my_debugfs_dir/foo" now contains the number 200.
     /// ```
-    pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// # use kernel::prelude::*;
+    /// let val = KBox::new(300, GFP_KERNEL)?;
+    /// let dir = Dir::new(c_str!("my_debugfs_dir"));
+    /// dir.display_file(c_str!("foo"), val);
+    /// // "my_debugfs_dir/foo" now contains the number 300.
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn display_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
         self.create_file(name, data)
     }
 
@@ -147,6 +173,51 @@ pub fn new(name: &CStr) -> Self {
 
 /// Handle to a DebugFS file.
 pub struct File {
+    // This order is load-bearing for drops - `_entry` must be dropped before `_foreign`
     #[cfg(CONFIG_DEBUG_FS)]
     _entry: Entry,
+    _foreign: ForeignHolder,
+}
+
+struct ForeignHolder {
+    data: *mut c_void,
+    drop_hook: unsafe fn(*mut c_void),
+}
+
+// SAFETY: We only construct `ForeignHolder` using a pointer from a `ForeignOwnable` which
+// is also `Sync`.
+unsafe impl Sync for ForeignHolder {}
+// SAFETY: We only construct `ForeignHolder` using a pointer from a `ForeignOwnable` which
+// is also `Send`.
+unsafe impl Send for ForeignHolder {}
+
+/// Helper function to drop a `D`-typed foreign ownable from its foreign representation, useful for
+/// cases where you want the type erased.
+/// # Safety
+/// * The foreign pointer passed in must have come from `D`'s `ForeignOwnable::into_foreign`
+/// * There must be no outstanding `ForeignOwnable::borrow{,mut}`
+/// * The pointer must not have been `ForeignOwnable::from_foreign`'d
+unsafe fn drop_helper<D: ForeignOwnable>(foreign: *mut c_void) {
+    // SAFETY: By safetydocs, we meet the requirements for `from_foreign`
+    drop(unsafe { D::from_foreign(foreign as _) })
+}
+
+impl ForeignHolder {
+    fn new<D: ForeignOwnable>(data: D) -> Self {
+        Self {
+            data: data.into_foreign() as _,
+            drop_hook: drop_helper::<D>,
+        }
+    }
+}
+
+impl Drop for ForeignHolder {
+    fn drop(&mut self) {
+        // SAFETY: `drop_hook` corresponds to the original `ForeignOwnable` instance's `drop`.
+        // This is only used in the case of `File`, so the only place borrows occur is through the
+        // DebugFS file owned by `_entry`. Since `_entry` occurs earlier in the struct, it will be
+        // dropped first, so no borrows will be ongoing. We know no `from_foreign` has occurred
+        // because this pointer is not exposed anywhere that is called.
+        unsafe { (self.drop_hook)(self.data) }
+    }
 }
rust/kernel/debugfs/display_file.rs +30 -19

87 lines not on this page · raw message

Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
maintainer
#7 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

rust/kernel/debugfs.rs fn create(_name: &CStr, _parent: Option<&Dir>) -> Self { · line 112

Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
      }

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

#8 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 11 lines · Danilo Krummrich

rust/kernel/debugfs.rs fn create(_name: &CStr, _parent: Option<&Dir>) -> Self { · line 112

Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
      }
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.
2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

#9 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 16 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich

rust/kernel/debugfs.rs fn create(_name: &CStr, _parent: Option<&Dir>) -> Self { · line 112

Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
      }
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

Quoted 6 lines

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.
2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

If it helps, effectively what I'm doing is adding a dyn* ForeignOwnable, but dyn* isn't available, so writing this without ForeignHolder would involve an unnecessary Box to hide the dyn.

maintainer
#10 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Matthew Maurer
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

#11 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 27 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

Quoted 6 lines

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

If you want a .data() function, I can add it in, but I don't think it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

maintainer
#12 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

	let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 4 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the

Quoted 8 lines

data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

- Danilo

#13 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
rust/kernel/debugfs.rs +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

	let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 4 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the

Quoted 8 lines

data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

And debugfs is NOT for performance things, but really, how bad could it be? :)

thanks,

greg k-h

maintainer
#14 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 98 lines · Greg Kroah-Hartman
Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
(quoted diff) +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

	let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 4 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the

Quoted 8 lines

data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

Quoted 5 lines

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

I think it technically works, but it imposes semantics on drivers that we shouldn't do; see the example below.

Quoted 2 lines

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

With the current API we would need this:

	struct GPU {
	   fw: Arc<Firmware>,
	   fw_file: debugfs::File,
	}

and then I would initialize it the following way:

	let fw = Arc::new(Firmware::new(), GFP_KERNEL)?;
	let fw_file = dir.create_file("firmware", fw.clone());

	fw.do_something();

This is bad, because now my Firmware instance in GPU needs to be reference counted, even though it should *never* out-live the GPU instance. This is error prone.

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

#15 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 120 lines · Danilo Krummrich
Quoted 98 lines · Greg Kroah-Hartman
Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
(quoted diff) +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

	let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 4 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the

Quoted 8 lines

data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

Quoted 5 lines

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

I think it technically works, but it imposes semantics on drivers that we shouldn't do; see the example below.

Quoted 2 lines

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

With the current API we would need this:

	struct GPU {
	   fw: Arc<Firmware>,
	   fw_file: debugfs::File,
	}

and then I would initialize it the following way:

Quoted 8 lines
	let fw = Arc::new(Firmware::new(), GFP_KERNEL)?;
	let fw_file = dir.create_file("firmware", fw.clone());

	fw.do_something();

This is bad, because now my Firmware instance in GPU needs to be reference counted, even though it should *never* out-live the GPU instance. This is error prone.

Agreed, AND you just created a new fw structure that you really didn't need, wasting memory.

Quoted 17 lines

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

thanks,

greg k-h

maintainer
#16 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 124 lines · Greg Kroah-Hartman
Quoted 123 lines · Danilo Krummrich
Quoted 98 lines · Greg Kroah-Hartman
Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
(quoted diff) +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

	let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 4 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the

Quoted 8 lines

data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

Quoted 5 lines

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

I think it technically works, but it imposes semantics on drivers that we shouldn't do; see the example below.

Quoted 2 lines

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

With the current API we would need this:

	struct GPU {
	   fw: Arc<Firmware>,
	   fw_file: debugfs::File,
	}

and then I would initialize it the following way:

	let fw = Arc::new(Firmware::new(), GFP_KERNEL)?;
	let fw_file = dir.create_file("firmware", fw.clone());
Quoted 8 lines
Quoted 5 lines
	fw.do_something();

This is bad, because now my Firmware instance in GPU needs to be reference counted, even though it should *never* out-live the GPU instance. This is error prone.

Agreed, AND you just created a new fw structure that you really didn't need, wasting memory.

In case you refer to fw.clone(), since fw is an Arc<Firmware>, clone() just creates a new reference count to the same object.

Quoted 17 lines
Quoted 17 lines

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.
Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

	impl Dir {
	   pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
	      pin_init!(Self {
	         data <- data,
	         ...
	      })
	   }
	}

This allows us to do things like:

	#[pin_data]
	struct Firmware {
	   #[pin]
	   minor: debugfs::File<u32>,
	   #[pin]
	   major: debugfs::File<u32>,
	   #[pin]
	   buffer: debugfs::File<[u8]>,
	}

	impl Firmware {
	   pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
	      pin_init!(Self {
	         minor <- dir.create_file("minor", 1),
	         major <- dir.create_file("major", 2),
	         buffer <- dir.create_file("buffer", buffer),
	      })
	   }
	}

	// This is the only allocation we need.
	let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

	let file = dir.create_file("firmware", Firmware::new());
	let file = KBox::pin_init(file, GFP_KERNEL)?;

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

#17 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 95 lines · Danilo Krummrich
Quoted 93 lines · Greg Kroah-Hartman
Quoted 92 lines · Danilo Krummrich
Quoted 91 lines · Greg Kroah-Hartman
Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
(quoted diff) +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

        let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 12 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

The part that is not straightforward is that the primary purpose of T may not be "to be exposed via DebugFS", and leaves us in the unusual world where DebugFS (or at least our bindings to it) are load-bearing in builds that have DebugFS disabled. It can work, but it definitely seems confusing and is privileging DebugFS holding a reference to a data structure over other things holding a reference to it.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

I have a version of this that works with my initial driver, and keep intending to revise it to work on top of the new one, but I keep getting requests for API changes before I get around to reworking it ;)

The way I intended my ForeignOwnable variant of this to work was that I would have a *single* struct that contained all the scanned data. For the socinfo module, we know how big this could be, so we could put this as a static in the module, in which case we'd have &'static MyInfo. If we didn't want to do that or didn't know, we'd produce an Arc<MyInfo>. Then, for each file, we'd create a file that passed in either my_arc_info.clone() or my_info if we had the static reference, with the functions that do the printing distinguishing the behavior of each file.

Doing the file-owns-the-specific-field version *could* work, but means that the debugfs tree code would be interleaved with the calls to parsing code, or that we'd need two copies of the struct, an initial one with all the parsed data, and a follow-up one that has has them all wrapped in File.

Quoted 16 lines
Quoted 16 lines
Quoted 16 lines

I think it technically works, but it imposes semantics on drivers that we shouldn't do; see the example below.

Quoted 2 lines

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

With the current API we would need this:

    struct GPU {
       fw: Arc<Firmware>,
       fw_file: debugfs::File,
    }

and then I would initialize it the following way:

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
    let fw = Arc::new(Firmware::new(), GFP_KERNEL)?;
    let fw_file = dir.create_file("firmware", fw.clone());

    fw.do_something();

This is bad, because now my Firmware instance in GPU needs to be reference counted, even though it should *never* out-live the GPU instance. This is error prone.

If Firmware outliving the GPU instance causes a bug, not just a resource leak, I'd argue that you should be passing a narrower handle to your debugfs file. If it just causes a resource leak, then I'd argue that if you leak a resource (the debugfs file itself), then leaking a resource is not unexpected.

For the narrower handle example, consider e.g.

```
struct Firmware {
  info: Arc<FirmwareInfo>,
  my_handle: Handle // Some handle whose drop is load bearing
}
// ...
let fw: Firmware = build_fw()?;
let fw_file = fw.info.clone();
fw.do_something();
```
Quoted 30 lines
Quoted 3 lines

Agreed, AND you just created a new fw structure that you really didn't need, wasting memory.

In case you refer to fw.clone(), since fw is an Arc<Firmware>, clone() just creates a new reference count to the same object.

Quoted 23 lines
Quoted 17 lines

Instead this should just be:

    struct GPU {
       fw: debugfs::File<Firmware>,
    }

and then I would initialize it the following way:

    let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
    let file = dir.create_file("firmware", fw);

    // debugfs::File<Firmware> dereferences to Firmware
    file.do_something();

    // Access to fw is prevented by the compiler, since it has been moved
    // into file.
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

This seems odd to me because with this model the DebugFS file itself becomes deeply involved with the ownership of the devices or other data. There are a few issues I think this creates:
1. It discourages file-per-field, because you can no longer have the DebugFS file uniquely own something, which you're now trying to encourage. This seems likely to result in people having debugfs files that render out structured data, which I think we generally don't want?
2. If I have a module and want to add a new DebugFS file to it, the change will edit the core structures of the drivers rather than just adding an auxiliary field. This is partially remedied if we make File<D> implement Deref<Target=D>. It will still either incur churn or imprecision around commonly derived traits like equality and debug. If I have #[derive(PartialEq)] on my struct including DeviceData, and then I wrap that into a File<DeviceData>, we'll get stuck with a quandry:
a. Should files x == y if the underlying private data is equal?
This has the benefit of preserving the behavior of the derive, but means that distinct files may compare equal if they have the same contents.
b. Should files x == y only be true if the underlying *file* is
the same? This seems logical for *file* equality, but will break equality on the field or structures including it.
This will also be an incompatible change for anyone already using an
explicit .deref() or an explicit impl Deref<Target = T> method to work with the field, because while deref coercion will automatically chain, impl Deref<Target = T> *cannot* automatically chain.
3. If you create an object specifically for debugging, then it will *have* to be created and kept around even if DebugFS is disabled, because we can't tell whether the user is relying on its existence. (My current implementation keeps it around in that case as well, but that's not guaranteed or required - we could cause it to just immediately drop data that someone tried to attach.) This means that rather than relying on the API to determine whether the information is needed, the modules will need to check the config if they want to get rid of it.

Quoted 21 lines

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

        impl Dir {
           pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
              pin_init!(Self {
                 data <- data,
                 ...
              })
           }
        }

This allows us to do things like:

Quoted 8 lines
        #[pin_data]
        struct Firmware {
           #[pin]
           minor: debugfs::File<u32>,
           #[pin]
           major: debugfs::File<u32>,
           #[pin]
           buffer: debugfs::File<[u8]>,

Nit: This can't work, because a &[u8] is double-width, and we only get to send one pointer into the file. We'd need to add a requirement T: Sized if we really wanted to have File embed the data.

Quoted 15 lines
        }

        impl Firmware {
           pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
              pin_init!(Self {
                 minor <- dir.create_file("minor", 1),
                 major <- dir.create_file("major", 2),
                 buffer <- dir.create_file("buffer", buffer),
              })
           }
        }

        // This is the only allocation we need.
        let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 7 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

If it's easy to use the model you described earlier where the sole owner of the data is the debugfs file, then this is mostly fine, because to use the old version, you'd already have it live in a ForeignOwnable, which most likely means its own allocation. However, if you're already forced into an environment where the objects you're embedding into a file are reference counted, you now have an extra allocation required for every File. Because the File has to be able to respond to data(), we also won't have these Files truncate to zero size in the no debugfs scenario.

This means that if you want a File to render an object that is reference counted, and they're dynamically created and destroyed, you will now be required to incur an allocation per file, even when DebugFS is disabled.

Quoted 13 lines
        struct GPU {
           fw: debugfs::File<Firmware>,
        }

        let file = dir.create_file("firmware", Firmware::new());
        let file = KBox::pin_init(file, GFP_KERNEL)?;

        // debugfs::File<Firmware> dereferences to Firmware
        file.do_something();

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

maintainer
#18 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 103 lines · Matthew Maurer
Quoted 95 lines · Danilo Krummrich
Quoted 93 lines · Greg Kroah-Hartman
Quoted 92 lines · Danilo Krummrich
Quoted 91 lines · Greg Kroah-Hartman
Quoted 90 lines · Danilo Krummrich
Quoted 32 lines · Matthew Maurer
Quoted 31 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer
Quoted 11 lines · Danilo Krummrich
Quoted 8 lines · Matthew Maurer
(quoted diff) +7 -0
+    fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+    where
+        for<'a> D::Borrowed<'a>: Display,
+    {
+        File {
+            _foreign: ForeignHolder::new(data),
+        }
       }
Quoted 5 lines
Quoted 2 lines

What's the motivation for the ForeignHolder abstraction? Why not just make it File<D> and store data directly?

1. A File<D> can't be held in collection data structures as easily unless all your files contain the *same* backing type.

That sounds reasonable.

Quoted 4 lines

2. None of the APIs or potential APIs for File care about which type it's wrapping, nor are they supposed to. If nothing you can do with a File is different depending on the backing type, making it polymorphic is just needlessly confusing.

What if I want to access file.data() and do something with the data? Then I'd necessarily need to put my data in an Arc and reference count it to still be able to access it.

Quoted 8 lines
Quoted 2 lines

That doesn't seem like a reasonable requirement to be able to access data exposed via debugfs.

pub fn data(&self) -> D would go against my understanding of Greg's request for DebugFS files to not really support anything other than delete. I was even considering making D not be retained in the disabled debugfs case, but left it in for now for so that the lifecycles wouldn't change.

Well, that's because the C side does not have anything else. But the C side has no type system that deals with ownership:

In C you just stuff a pointer of your private data into debugfs_create_file() without any implication of ownership. debugfs has a pointer, the driver has a pointer. The question of the ownership semantics is not answered by the API, but by the implementation of the driver.

The Rust API is different, and it's even implied by the name of the trait you expect the data to implement: ForeignOwnable.

The File *owns* the data, either entirely or a reference count of the data.

If the *only* way to access the data the File now owns is by making it reference counted, it:

1) Is additional overhead imposed on users.

2) It has implications on the ownership design of your driver. Once something is reference counted, you loose the guarantee the something can't out-live some event.

I don't want that people have to stuff their data structures into Arc (i.e. reference count them), even though that's not necessary. It makes it easy to make mistakes. Things like:

        let foo = bar.clone();

can easily be missed in reviews, whereas some contributor falsely changing a KBox to an Arc is much harder to miss.

Quoted 1 line

If you want a .data() function, I can add it in,

I think it could even be an implementation of Deref.

Quoted 12 lines

but I don't think
it'll improve flexibility in most cases. If you want to do something with the data and it's not in an Arc / behind a handle of some kind, you'll need something providing threadsafe interior mutability in the data structure. If that's a lock, then I have a hard time believing that Arc<Mutex<T>>(or if it's a global, a &'static Mutex<T>, which is why I added that in the stack) is so much more expensive than Box<Mutex<T>> that it's worth a more complex API. If it's an atomic, e.g. Arc<AtomicU8>, then I can see the benefit to having Box<AtomicU8> over that, but it still seems so slim that I think the simpler "File is just a handle to how long the file stays alive, it doesn't let you do anything else" API makes sense.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

I don't really see what is complicated about File<T> -- it's a File and it owns data of type T that is exposed via debugfs. Seems pretty straight forward to me.

Maybe the performance cost is not a huge argument here, but maintainability in terms of clarity about ownership and lifetime of an object as explained above clearly is.

Quoted 7 lines

The part that is not straightforward is that the primary purpose of T may not be "to be exposed via DebugFS", and leaves us in the unusual world where DebugFS (or at least our bindings to it) are load-bearing in builds that have DebugFS disabled. It can work, but it definitely seems confusing and is privileging DebugFS holding a reference to a data structure over other things holding a reference to it.

The purpose of File<T> is that with an instance of File<T> you can access the T. This is totally normal. You give the file ownership of some T and you still want to be able to access this T the File instance has ownership of.

It's not load-bearing at all in builds that have debugfs disabled; there is zero overhead. When compiled without debugfs File<T>'s only member is the data itself. In fact, it's even slighly less, since the T can be a KBox<U> rather than an Arc<U>.

Quoted 22 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

I'm agreeing here. As one of the primary users of this api is going to be a "soc info" module, like drivers/soc/qcom/socinfo.c, I tried to make an example driver to emulate that file with just a local structure, but the reference counting and access logic just didn't seem to work out properly. Odds are I'm doing something stupid though...

I have a version of this that works with my initial driver, and keep intending to revise it to work on top of the new one, but I keep getting requests for API changes before I get around to reworking it ;)

The way I intended my ForeignOwnable variant of this to work was that I would have a *single* struct that contained all the scanned data. For the socinfo module, we know how big this could be, so we could put this as a static in the module, in which case we'd have &'static MyInfo. If we didn't want to do that or didn't know, we'd produce an Arc<MyInfo>. Then, for each file, we'd create a file that passed in either my_arc_info.clone() or my_info if we had the static reference, with the functions that do the printing distinguishing the behavior of each file.

Quoted 5 lines

Doing the file-owns-the-specific-field version *could* work, but means that the debugfs tree code would be interleaved with the calls to parsing code, or that we'd need two copies of the struct, an initial one with all the parsed data, and a follow-up one that has has them all wrapped in File.

Why can't you do the latter right away?

Quoted 32 lines
Quoted 16 lines
Quoted 16 lines
Quoted 16 lines

I think it technically works, but it imposes semantics on drivers that we shouldn't do; see the example below.

Quoted 2 lines

So a file callback IS going to want to have access to the data of type T that is exposed somehow.

With the current API we would need this:

    struct GPU {
       fw: Arc<Firmware>,
       fw_file: debugfs::File,
    }

and then I would initialize it the following way:

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
    let fw = Arc::new(Firmware::new(), GFP_KERNEL)?;
    let fw_file = dir.create_file("firmware", fw.clone());

    fw.do_something();

This is bad, because now my Firmware instance in GPU needs to be reference counted, even though it should *never* out-live the GPU instance. This is error prone.

If Firmware outliving the GPU instance causes a bug, not just a resource leak, I'd argue that you should be passing a narrower handle to your debugfs file. If it just causes a resource leak, then I'd argue that if you leak a resource (the debugfs file itself), then leaking a resource is not unexpected.

For the narrower handle example, consider e.g.

Quoted 10 lines
```
struct Firmware {
  info: Arc<FirmwareInfo>,
  my_handle: Handle // Some handle whose drop is load bearing
}
// ...
let fw: Firmware = build_fw()?;
let fw_file = fw.info.clone();
fw.do_something();
```

Sorry, but that really looks like a bad workaround. Now I have an additional allocation and a reference counted FirmwareInfo instance for no reason, other than debugfs imposing a bad API on users. :(

In more complicated cases, this could be lots of additional allocations and reference counted structures just because of debugfs.

Quoted 27 lines
Quoted 27 lines
Quoted 3 lines

Agreed, AND you just created a new fw structure that you really didn't need, wasting memory.

In case you refer to fw.clone(), since fw is an Arc<Firmware>, clone() just creates a new reference count to the same object.

Quoted 20 lines
Quoted 19 lines

Instead this should just be:

    struct GPU {
       fw: debugfs::File<Firmware>,
    }

and then I would initialize it the following way:

    let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
    let file = dir.create_file("firmware", fw);

    // debugfs::File<Firmware> dereferences to Firmware
    file.do_something();

    // Access to fw is prevented by the compiler, since it has been moved
    // into file.

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

Quoted 7 lines
Quoted 3 lines
Quoted 3 lines

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

This seems odd to me because with this model the DebugFS file itself becomes deeply involved with the ownership of the devices or other data.

This is already the case with this version on the mailing list. You're passing *ForeignOwnable* data to the File, which the File instance subsequently owns.

There is zero difference in ownership, the difference is that File<T> allows you to access the data it owns, whereas just File does not, which makes it necessary to use an Arc.

Quoted 6 lines

There are a few issues I think this creates:
1. It discourages file-per-field, because you can no longer have the DebugFS file uniquely own something, which you're now trying to encourage. This seems likely to result in people having debugfs files that render out structured data, which I think we generally don't want?

That's what I proposed the pin-init solution below for.

Quoted 4 lines

2. If I have a module and want to add a new DebugFS file to it, the change will edit the core structures of the drivers rather than just adding an auxiliary field. This is partially remedied if we make File<D> implement Deref<Target=D>.

So, you're arguing that changing

	struct Foo {
	   bar: Bar,
	}

to

	struct Foo {
	   bar: Arc<Bar>,
	   bar_file: debugfs::File,
	}

is better than

	struct Foo {
	   bar: debugfs::File<Bar>,
	}

correct? If so, I really have to disagree.

Quoted 4 lines

It will still either incur
churn or imprecision around commonly derived traits like equality and debug. If I have #[derive(PartialEq)] on my struct including DeviceData, and then I wrap that into a File<DeviceData>, we'll

Quoted 8 lines

get stuck with a quandry:
a. Should files x == y if the underlying private data is equal?
This has the benefit of preserving the behavior of the derive, but means that distinct files may compare equal if they have the same contents.
b. Should files x == y only be true if the underlying *file* is
the same? This seems logical for *file* equality, but will break equality on the field or structures including it.

Why would you even compare two File instances? If your T implements PartialEq, and you want to compare it with some other T just use the ForeignOwnable::Borrowed you get from dereferencing the File.

Quoted 4 lines

This will also be an incompatible change for anyone already using an
explicit .deref() or an explicit impl Deref<Target = T> method to work with the field, because while deref coercion will automatically chain, impl Deref<Target = T> *cannot* automatically chain.

Seems like a pretty minor concern, but if you want you can still do the Arc dance in your driver, no?

Quoted 9 lines

3. If you create an object specifically for debugging, then it will *have* to be created and kept around even if DebugFS is disabled, because we can't tell whether the user is relying on its existence. (My current implementation keeps it around in that case as well, but that's not guaranteed or required - we could cause it to just immediately drop data that someone tried to attach.) This means that rather than relying on the API to determine whether the information is needed, the modules will need to check the config if they want to get rid of it.

This is only half the truth, since this is *only* the case if you don't need to access the data you move into the File, since otherwise you have to keep an Arc around.

Quoted 30 lines
Quoted 21 lines

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

        impl Dir {
           pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
              pin_init!(Self {
                 data <- data,
                 ...
              })
           }
        }

This allows us to do things like:

Quoted 8 lines
        #[pin_data]
        struct Firmware {
           #[pin]
           minor: debugfs::File<u32>,
           #[pin]
           major: debugfs::File<u32>,
           #[pin]
           buffer: debugfs::File<[u8]>,
Quoted 3 lines

Nit: This can't work, because a &[u8] is double-width, and we only get to send one pointer into the file. We'd need to add a requirement T: Sized if we really wanted to have File embed the data.

Indeed, that was a bad example, let's say Vec<u8> then. :)

Quoted 36 lines
Quoted 16 lines
        }

        impl Firmware {
           pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
              pin_init!(Self {
                 minor <- dir.create_file("minor", 1),
                 major <- dir.create_file("major", 2),
                 buffer <- dir.create_file("buffer", buffer),
              })
           }
        }

        // This is the only allocation we need.
        let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 7 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

Quoted 28 lines

If it's easy to use the model you described earlier where the sole owner of the data is the debugfs file, then this is mostly fine, because to use the old version, you'd already have it live in a ForeignOwnable, which most likely means its own allocation. However, if you're already forced into an environment where the objects you're embedding into a file are reference counted, you now have an extra allocation required for every File. Because the File has to be able to respond to data(), we also won't have these Files truncate to zero size in the no debugfs scenario.

This means that if you want a File to render an object that is reference counted, and they're dynamically created and destroyed, you will now be required to incur an allocation per file, even when DebugFS is disabled.

Quoted 13 lines
        struct GPU {
           fw: debugfs::File<Firmware>,
        }

        let file = dir.create_file("firmware", Firmware::new());
        let file = KBox::pin_init(file, GFP_KERNEL)?;

        // debugfs::File<Firmware> dereferences to Firmware
        file.do_something();

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

reviewer
#19 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 57 lines · Danilo Krummrich
Quoted 34 lines · Matthew Maurer
Quoted 13 lines · Danilo Krummrich
        impl Firmware {
           pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
              pin_init!(Self {
                 minor <- dir.create_file("minor", 1),
                 major <- dir.create_file("major", 2),
                 buffer <- dir.create_file("buffer", buffer),
              })
           }
        }

        // This is the only allocation we need.
        let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 7 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}
Quoted 5 lines

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

    enum SegmentedVec<T> {
        Cons(Segment<T>, KBox<SegmentedVec<T>>)
        Nul,
    }

    struct Segment<T> {
        elements: [T; 16]
    }

or make the segments variable-sized and grow them accordingly.

---
Cheers,
Benno

maintainer
#20 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 82 lines · Benno Lossin
Quoted 57 lines · Danilo Krummrich
Quoted 34 lines · Matthew Maurer
Quoted 13 lines · Danilo Krummrich
         impl Firmware {
            pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
               pin_init!(Self {
                  minor <- dir.create_file("minor", 1),
                  major <- dir.create_file("major", 2),
                  buffer <- dir.create_file("buffer", buffer),
               })
            }
         }

         // This is the only allocation we need.
         let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 7 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}
Quoted 5 lines

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }

     struct Segment<T> {
         elements: [T; 16]
     }
Quoted 1 line

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

reviewer
#21 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 79 lines · Danilo Krummrich
Quoted 78 lines · Benno Lossin
Quoted 57 lines · Danilo Krummrich
Quoted 34 lines · Matthew Maurer
Quoted 13 lines · Danilo Krummrich
         impl Firmware {
            pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
               pin_init!(Self {
                  minor <- dir.create_file("minor", 1),
                  major <- dir.create_file("major", 2),
                  buffer <- dir.create_file("buffer", buffer),
               })
            }
         }

         // This is the only allocation we need.
         let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 7 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}
Quoted 5 lines

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }
Quoted 7 lines
Quoted 5 lines
     struct Segment<T> {
         elements: [T; 16]
     }

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

Haha I didn't know this already existed in the kernel :) Yeah then we should make XArray work for this use-case.

---
Cheers,
Benno

reviewer
#22 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 62 lines · Benno Lossin
Quoted 61 lines · Danilo Krummrich
Quoted 60 lines · Benno Lossin
Quoted 35 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}
Quoted 5 lines

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }

     struct Segment<T> {
         elements: [T; 16]
     }
Quoted 6 lines
Quoted 3 lines
Quoted 1 line

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

Haha I didn't know this already existed in the kernel :) Yeah then we should make XArray work for this use-case.

Ah wait, I meant for SegmentedVec<T> to store multiple T in the same allocation, so it would only grow sometimes and amortize the allocations just like Vec. It seems to me that XArray only stores pointers, so you have to box everything (which we're trying to avoid IIUC).

---
Cheers,
Benno

maintainer
#23 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 67 lines · Benno Lossin
Quoted 66 lines · Benno Lossin
Quoted 64 lines · Danilo Krummrich
Quoted 60 lines · Benno Lossin
Quoted 35 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

	struct Client {
	   id: File<ClientId>,
	   data: File<ClientData>,
	   ...
	}
Quoted 5 lines

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }

     struct Segment<T> {
         elements: [T; 16]
     }
Quoted 1 line

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

Quoted 7 lines
Quoted 2 lines

Haha I didn't know this already existed in the kernel :) Yeah then we should make XArray work for this use-case.

Ah wait, I meant for SegmentedVec<T> to store multiple T in the same allocation, so it would only grow sometimes and amortize the allocations just like Vec. It seems to me that XArray only stores pointers, so you have to box everything (which we're trying to avoid IIUC).

Yes, that sounds good. And if the potential number of Client instances can get pretty large Vec isn't a good choice anyways. If we really get a ton of clients, they should be allocated with a kmem_cache and stored in an XArray, maple tree, etc.

#24 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 77 lines · Danilo Krummrich
Quoted 67 lines · Benno Lossin
Quoted 66 lines · Benno Lossin
Quoted 64 lines · Danilo Krummrich
Quoted 60 lines · Benno Lossin
Quoted 33 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

 struct Client {
    id: File<ClientId>,
    data: File<ClientData>,
    ...
Quoted 7 lines

}

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }

     struct Segment<T> {
         elements: [T; 16]
     }
Quoted 1 line

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

Quoted 7 lines
Quoted 2 lines

Haha I didn't know this already existed in the kernel :) Yeah then we should make XArray work for this use-case.

Ah wait, I meant for SegmentedVec<T> to store multiple T in the same allocation, so it would only grow sometimes and amortize the allocations just like Vec. It seems to me that XArray only stores pointers, so you have to box everything (which we're trying to avoid IIUC).

Quoted 4 lines

Yes, that sounds good. And if the potential number of Client instances can get pretty large Vec isn't a good choice anyways. If we really get a ton of clients, they should be allocated with a kmem_cache and stored in an XArray, maple tree, etc.

OK. I've outlined why I disagree, but it sounds like consensus here is that:
1. You want a File<T> that implements Deref to expose a &T.
2. To enable T to take on values which are not ForeignOwnable, you want me to make create_file return a PinInit<File<T>> (We will also grow a T: Sized bound here.)

You are aware that:
1. A File can no longer be placed in a normal Rust collection, you will either need to box it or use a special collection kind.
2. Adding or removing DebugFS support for some data in a driver may cause more noise (code edits non-local to the debug logic) than expected.

The one thing I still don't see a consensus on:

What do you want me to do about standard traits for File? If we're intending it to be heavily used throughout structs, we'll effectively break #[derive] if I don't add support. For example, if we had

```
#[derive(Debug, PartialEq)]
struct FooAttrs {
  name: String,
  size: usize,
}
// In my current patch world someone adds `File`, it's by putting
`FooAttrs` into an `Arc` and passing that into `create_file`, no
modifications to the structure are made.
```
before, we have an obvious semantics for this. If someone adds `File`
with the new API, we get
```
#[derive(Debug, PartialEq)]
#[pin_data]
struct FooAttrs {
  #[pin]
  name: File<String>,
  #[pin]
  size: File<usize>,
}
```

This means that for the #[derive] to keep working, File needs to implement these traits. Do we want it to:
A. Purely delegate, so making this sort of change keeps the same behavior of derived traits
B. Do what makes sense for a File, so e.g. Debug might print 'File {
data: "name_of_foo" }', and PartialEq might test for equality of the dentry if not an error

I'm guessing you want A, but I've been through so many API reworks at this point that I wanted to ask in case there's disagreement here.

maintainer
#25 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 88 lines · Matthew Maurer
Quoted 76 lines · Danilo Krummrich
Quoted 67 lines · Benno Lossin
Quoted 66 lines · Benno Lossin
Quoted 64 lines · Danilo Krummrich
Quoted 60 lines · Benno Lossin
Quoted 33 lines · Danilo Krummrich
Quoted 12 lines · Matthew Maurer

If we implement *only* pinned files, we run into an additional problem
- you can't easily extend a pinned vector. This means that you cannot have dynamically created devices unless you're willing to put every new File into its own Box, because you aren't allowed to move any of the previously allocated Files for a resize.

Where previously you would have had

```
debug_files: Vec<File>
```
Quoted 5 lines

you would now have

```
debug_files: Vec<PinBox<File<T>>>
```

Stuffing single File instances into a Vec seems like the wrong thing to do.

Instead you may have instances of some data structure that is created dynamically in your driver that you want to expose through debugfs.

Let's say you have (userspace) clients that can be registered arbitrarily, then you want a Vec<Client>, which contains the client instances. In order to provide information about the Client in debugfs you then have the client embed things as discussed above.

 struct Client {
    id: File<ClientId>,
    data: File<ClientData>,
    ...
Quoted 7 lines

}

I think that makes much more sense than keeping a Vec<Arc<Client>> *and* a Vec<File> separately. Also, note that with the above, your Client instances don't need to be reference counted anymore.

I think this addresses the concerns below.

You still have the issue that Client now needs to be pinned and the vector can't be resized. But if you know that it's bounded, then we could just make Pin<Vec<T>> work as expected (not relocating the underlying allocation by not exposing push, only push_within_capacity).

We also could have a SegmentedVec<T> that doesn't move elements. Essentially it is

     enum SegmentedVec<T> {
         Cons(Segment<T>, KBox<SegmentedVec<T>>)
         Nul,
     }

     struct Segment<T> {
         elements: [T; 16]
     }
Quoted 1 line

or make the segments variable-sized and grow them accordingly.

That sounds a lot like the perfect application for XArray. :)

Quoted 7 lines
Quoted 2 lines

Haha I didn't know this already existed in the kernel :) Yeah then we should make XArray work for this use-case.

Ah wait, I meant for SegmentedVec<T> to store multiple T in the same allocation, so it would only grow sometimes and amortize the allocations just like Vec. It seems to me that XArray only stores pointers, so you have to box everything (which we're trying to avoid IIUC).

Quoted 4 lines

Yes, that sounds good. And if the potential number of Client instances can get pretty large Vec isn't a good choice anyways. If we really get a ton of clients, they should be allocated with a kmem_cache and stored in an XArray, maple tree, etc.

OK. I've outlined why I disagree, but it sounds like consensus here is that:
1. You want a File<T> that implements Deref to expose a &T.
2. To enable T to take on values which are not ForeignOwnable, you want me to make create_file return a PinInit<File<T>> (We will also grow a T: Sized bound here.)

Quoted 3 lines

You are aware that:
1. A File can no longer be placed in a normal Rust collection, you will either need to box it or use a special collection kind.

I don't see why we would want to do that. I can see why we would want to keep a Vec of some "payload" structure, e.g. some Client structure. But even then, using a simple resizable array is rarely a good choice.

IMHO there is just no point in optimizing for this case.

If there is anything specific you have in mind, it would be good to share what that is.

In drivers we have per device allocations, e.g. the driver's private data, the private data of a class device registration, the private data of a work items, the private data if an IRQ, etc.

And for cases where we create instances of some struct dynamically, we usually just go with a new allocation for each new instance and store it in lists, tree structures, XArray, etc.

Especially, if there can be lots of instances of the same structure, you don't want to re-allocate all the time and copy things around, but use a kmem_cache instead.

Quoted 3 lines

2. Adding or removing DebugFS support for some data in a driver may cause more noise (code edits non-local to the debug logic) than expected.

I don't agree with that -- forcing a reference count for something that doesn't need to be reference counted is clearly more "noise" that a few trivial changes, such as:

- foo: Foo,
+ foo: debugfs::File<Foo>,

Quoted 36 lines

The one thing I still don't see a consensus on:

What do you want me to do about standard traits for File? If we're intending it to be heavily used throughout structs, we'll effectively break #[derive] if I don't add support. For example, if we had

```
#[derive(Debug, PartialEq)]
struct FooAttrs {
  name: String,
  size: usize,
}
// In my current patch world someone adds `File`, it's by putting
`FooAttrs` into an `Arc` and passing that into `create_file`, no
modifications to the structure are made.
```
before, we have an obvious semantics for this. If someone adds `File`
with the new API, we get
```
#[derive(Debug, PartialEq)]
#[pin_data]
struct FooAttrs {
  #[pin]
  name: File<String>,
  #[pin]
  size: File<usize>,
}
```

This means that for the #[derive] to keep working, File needs to implement these traits. Do we want it to:
A. Purely delegate, so making this sort of change keeps the same behavior of derived traits
B. Do what makes sense for a File, so e.g. Debug might print 'File {
data: "name_of_foo" }', and PartialEq might test for equality of the dentry if not an error

Quoted 2 lines

I'm guessing you want A, but I've been through so many API reworks at this point that I wanted to ask in case there's disagreement here.

I understand that -- and thanks for your work on this, I really appreciate it!

Regarding the question: Yes, I'm clearly all for A, I don't think there's much value in B.

reviewer
#26 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Danilo Krummrich
Quoted 36 lines · Danilo Krummrich

I think we can add an additional API like this:

	impl Dir {
	   pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
	      pin_init!(Self {
	         data <- data,
	         ...
	      })
	   }
	}

This allows us to do things like:

	#[pin_data]
	struct Firmware {
	   #[pin]
	   minor: debugfs::File<u32>,
	   #[pin]
	   major: debugfs::File<u32>,
	   #[pin]
	   buffer: debugfs::File<[u8]>,
	}

	impl Firmware {
	   pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
	      pin_init!(Self {
	         minor <- dir.create_file("minor", 1),
	         major <- dir.create_file("major", 2),
	         buffer <- dir.create_file("buffer", buffer),
	      })
	   }
	}

	// This is the only allocation we need.
	let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;
Quoted 4 lines

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Yes! This is a textbook example of how to use pin-init in API design!

Quoted 13 lines

Actually, we can also implement *only this*, since with this my previous example would just become this:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

	let file = dir.create_file("firmware", Firmware::new());
	let file = KBox::pin_init(file, GFP_KERNEL)?;

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Yeah I think it's a good idea to do that too.

---
Cheers,
Benno

reviewer
#27 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Danilo Krummrich
Quoted 87 lines · Danilo Krummrich
Quoted 18 lines · Greg Kroah-Hartman
Quoted 17 lines · Danilo Krummrich

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.
Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

	impl Dir {
	   pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
	      pin_init!(Self {
	         data <- data,
	         ...
	      })
	   }
	}

This allows us to do things like:

	#[pin_data]
	struct Firmware {
	   #[pin]
	   minor: debugfs::File<u32>,
	   #[pin]
	   major: debugfs::File<u32>,
	   #[pin]
	   buffer: debugfs::File<[u8]>,
	}

	impl Firmware {
	   pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
	      pin_init!(Self {
	         minor <- dir.create_file("minor", 1),
	         major <- dir.create_file("major", 2),
	         buffer <- dir.create_file("buffer", buffer),
	      })
	   }
	}

	// This is the only allocation we need.
	let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

	let file = dir.create_file("firmware", Firmware::new());
	let file = KBox::pin_init(file, GFP_KERNEL)?;

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Does this actually work in practice for anything except immutable data? I mean, let's take Rust Binder as an example and lets say that I want to expose a directory for each Process object with some of the fields exposed. Let's just simplify Rust Binder a bit and only include some of the fields:

#[pin_data]

struct Process {
    task: ARef<Task>,
    #[pin]
    inner: SpinLock<ProcessInner>,
}
pub(crate) struct ProcessInner {
    threads: RBTree<i32, Arc<Thread>>,
    nodes: RBTree<u64, DArc<Node>>,
    requested_thread_count: u32,
    max_threads: u32,
    started_thread_count: u32,
}

Rust Binder already does expose some debugging data through a file system, though it doesn't do so using debugfs. It exposes a lot of data, but among them are the pid, the number of threads and nodes, as well as the values of requested_thread_count, started_thread_count, and max_threads.

Now, we run into problem number one: pinning is not supported inside mutexes. But let's say we solved that and we could do this:

#[pin_data]
struct Process {
task: File<ARef<Task>>, // prints the pid
#[pin]
inner: SpinLock<ProcessInner>,
}

pub(crate) struct ProcessInner {
threads: File<RBTree<i32, Arc<Thread>>>, // prints the count
nodes: File<RBTree<u64, DArc<Node>>>, // prints the count
requested_thread_count: File<u32>,
max_threads: File<u32>,
started_thread_count: File<u32>,
}

However, this still doesn't work! Debugfs may get triggered at any time and need to read these fields, and there's no way for it to take the spinlock with the above design - it doesn't know where the spinlock is. For the integers I guess we could make them atomic to allow reading them in parallel with mutation, but that option is not available for the red/black trees.

What is the intended solution in this case? If the argument is that this is a rare case, then keep in mind that this is a real-world example of debugging information that we actually expose today in a real driver. With Matt's current approach, it's relatively easy - just store a bunch of File<Arc<Process>> instances somewhere and define each one to take the mutex and print the relevant value.

Alice

reviewer
#28 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 143 lines · Alice Ryhl
Quoted 87 lines · Danilo Krummrich
Quoted 18 lines · Greg Kroah-Hartman
Quoted 17 lines · Danilo Krummrich

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.
Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

	impl Dir {
	   pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
	      pin_init!(Self {
	         data <- data,
	         ...
	      })
	   }
	}

This allows us to do things like:

	#[pin_data]
	struct Firmware {
	   #[pin]
	   minor: debugfs::File<u32>,
	   #[pin]
	   major: debugfs::File<u32>,
	   #[pin]
	   buffer: debugfs::File<[u8]>,
	}

	impl Firmware {
	   pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
	      pin_init!(Self {
	         minor <- dir.create_file("minor", 1),
	         major <- dir.create_file("major", 2),
	         buffer <- dir.create_file("buffer", buffer),
	      })
	   }
	}

	// This is the only allocation we need.
	let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

	let file = dir.create_file("firmware", Firmware::new());
	let file = KBox::pin_init(file, GFP_KERNEL)?;

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Does this actually work in practice for anything except immutable data? I mean, let's take Rust Binder as an example and lets say that I want to expose a directory for each Process object with some of the fields exposed. Let's just simplify Rust Binder a bit and only include some of the fields:

#[pin_data]

struct Process {
    task: ARef<Task>,
    #[pin]
    inner: SpinLock<ProcessInner>,
}
pub(crate) struct ProcessInner {
    threads: RBTree<i32, Arc<Thread>>,
    nodes: RBTree<u64, DArc<Node>>,
    requested_thread_count: u32,
    max_threads: u32,
    started_thread_count: u32,
}

Rust Binder already does expose some debugging data through a file system, though it doesn't do so using debugfs. It exposes a lot of data, but among them are the pid, the number of threads and nodes, as well as the values of requested_thread_count, started_thread_count, and max_threads.

Now, we run into problem number one: pinning is not supported inside mutexes. But let's say we solved that and we could do this:

#[pin_data]
struct Process {
task: File<ARef<Task>>, // prints the pid
#[pin]
inner: SpinLock<ProcessInner>,
}

pub(crate) struct ProcessInner {
threads: File<RBTree<i32, Arc<Thread>>>, // prints the count
nodes: File<RBTree<u64, DArc<Node>>>, // prints the count
requested_thread_count: File<u32>,
max_threads: File<u32>,
started_thread_count: File<u32>,
}

However, this still doesn't work! Debugfs may get triggered at any time and need to read these fields, and there's no way for it to take the spinlock with the above design - it doesn't know where the spinlock is. For the integers I guess we could make them atomic to allow reading them in parallel with mutation, but that option is not available for the red/black trees.

Quoted 6 lines

What is the intended solution in this case? If the argument is that this is a rare case, then keep in mind that this is a real-world example of debugging information that we actually expose today in a real driver. With Matt's current approach, it's relatively easy - just store a bunch of File<Arc<Process>> instances somewhere and define each one to take the mutex and print the relevant value.

How would your example look like with the current approach? IIUC, it also wouldn't work, because the debugfs data can't be mutated?

---
Cheers,
Benno

reviewer
#29 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 152 lines · Benno Lossin
Quoted 143 lines · Alice Ryhl
Quoted 87 lines · Danilo Krummrich
Quoted 18 lines · Greg Kroah-Hartman
Quoted 17 lines · Danilo Krummrich

Instead this should just be:

struct GPU {
fw: debugfs::File<Firmware>,
}

and then I would initialize it the following way:

let fw = KBox::new(Firmware::new(), GFP_KERNEL)?; let file = dir.create_file("firmware", fw);

// debugfs::File<Firmware> dereferences to Firmware file.do_something();

// Access to fw is prevented by the compiler, since it has been moved // into file.

Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

     impl Dir {
        pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
           pin_init!(Self {
              data <- data,
              ...
           })
        }
     }

This allows us to do things like:

     #[pin_data]
     struct Firmware {
        #[pin]
        minor: debugfs::File<u32>,
        #[pin]
        major: debugfs::File<u32>,
        #[pin]
        buffer: debugfs::File<[u8]>,
     }

     impl Firmware {
        pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
           pin_init!(Self {
              minor <- dir.create_file("minor", 1),
              major <- dir.create_file("major", 2),
              buffer <- dir.create_file("buffer", buffer),
           })
        }
     }

     // This is the only allocation we need.
     let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

     struct GPU {
        fw: debugfs::File<Firmware>,
     }

     let file = dir.create_file("firmware", Firmware::new());
     let file = KBox::pin_init(file, GFP_KERNEL)?;

     // debugfs::File<Firmware> dereferences to Firmware
     file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Does this actually work in practice for anything except immutable data? I mean, let's take Rust Binder as an example and lets say that I want to expose a directory for each Process object with some of the fields exposed. Let's just simplify Rust Binder a bit and only include some of the fields:

#[pin_data]

struct Process {
    task: ARef<Task>,
    #[pin]
    inner: SpinLock<ProcessInner>,
}
pub(crate) struct ProcessInner {
    threads: RBTree<i32, Arc<Thread>>,
    nodes: RBTree<u64, DArc<Node>>,
    requested_thread_count: u32,
    max_threads: u32,
    started_thread_count: u32,
}

Rust Binder already does expose some debugging data through a file system, though it doesn't do so using debugfs. It exposes a lot of data, but among them are the pid, the number of threads and nodes, as well as the values of requested_thread_count, started_thread_count, and max_threads.

Now, we run into problem number one: pinning is not supported inside mutexes. But let's say we solved that and we could do this:

#[pin_data]
struct Process {
task: File<ARef<Task>>, // prints the pid
#[pin]
inner: SpinLock<ProcessInner>,
}

pub(crate) struct ProcessInner {
threads: File<RBTree<i32, Arc<Thread>>>, // prints the count
nodes: File<RBTree<u64, DArc<Node>>>, // prints the count
requested_thread_count: File<u32>,
max_threads: File<u32>,
started_thread_count: File<u32>,
}

However, this still doesn't work! Debugfs may get triggered at any time and need to read these fields, and there's no way for it to take the spinlock with the above design - it doesn't know where the spinlock is. For the integers I guess we could make them atomic to allow reading them in parallel with mutation, but that option is not available for the red/black trees.

Quoted 6 lines

What is the intended solution in this case? If the argument is that this is a rare case, then keep in mind that this is a real-world example of debugging information that we actually expose today in a real driver. With Matt's current approach, it's relatively easy - just store a bunch of File<Arc<Process>> instances somewhere and define each one to take the mutex and print the relevant value.

Quoted 2 lines

How would your example look like with the current approach? IIUC, it also wouldn't work, because the debugfs data can't be mutated?

I would store a bunch of File<Arc<Process>> instances somewhere. Each one has a closure that takes the spinlock and prints the appropriate value.

Alice

#30 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 153 lines · Alice Ryhl
Quoted 152 lines · Benno Lossin
Quoted 149 lines · Alice Ryhl
Quoted 87 lines · Danilo Krummrich
Quoted 18 lines · Greg Kroah-Hartman
Quoted 17 lines · Danilo Krummrich

Instead this should just be:

struct GPU {
fw: debugfs::File<Firmware>,
}

and then I would initialize it the following way:

let fw = KBox::new(Firmware::new(), GFP_KERNEL)?; let file = dir.create_file("firmware", fw);

// debugfs::File<Firmware> dereferences to Firmware file.do_something();

// Access to fw is prevented by the compiler, since it has been moved // into file.

Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

     impl Dir {
        pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
           pin_init!(Self {
              data <- data,
              ...
           })
        }
     }

This allows us to do things like:

     #[pin_data]
     struct Firmware {
        #[pin]
        minor: debugfs::File<u32>,
        #[pin]
        major: debugfs::File<u32>,
        #[pin]
        buffer: debugfs::File<[u8]>,
     }

     impl Firmware {
        pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
           pin_init!(Self {
              minor <- dir.create_file("minor", 1),
              major <- dir.create_file("major", 2),
              buffer <- dir.create_file("buffer", buffer),
           })
        }
     }

     // This is the only allocation we need.
     let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

     struct GPU {
        fw: debugfs::File<Firmware>,
     }

     let file = dir.create_file("firmware", Firmware::new());
     let file = KBox::pin_init(file, GFP_KERNEL)?;

     // debugfs::File<Firmware> dereferences to Firmware
     file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Does this actually work in practice for anything except immutable data? I mean, let's take Rust Binder as an example and lets say that I want to expose a directory for each Process object with some of the fields exposed. Let's just simplify Rust Binder a bit and only include some of the fields:

#[pin_data]

struct Process {
    task: ARef<Task>,
    #[pin]
    inner: SpinLock<ProcessInner>,
}
pub(crate) struct ProcessInner {
    threads: RBTree<i32, Arc<Thread>>,
    nodes: RBTree<u64, DArc<Node>>,
    requested_thread_count: u32,
    max_threads: u32,
    started_thread_count: u32,
}

Rust Binder already does expose some debugging data through a file system, though it doesn't do so using debugfs. It exposes a lot of data, but among them are the pid, the number of threads and nodes, as well as the values of requested_thread_count, started_thread_count, and max_threads.

Now, we run into problem number one: pinning is not supported inside mutexes. But let's say we solved that and we could do this:

#[pin_data]
struct Process {
task: File<ARef<Task>>, // prints the pid
#[pin]
inner: SpinLock<ProcessInner>,
}

pub(crate) struct ProcessInner {
threads: File<RBTree<i32, Arc<Thread>>>, // prints the count
nodes: File<RBTree<u64, DArc<Node>>>, // prints the count
requested_thread_count: File<u32>,
max_threads: File<u32>,
started_thread_count: File<u32>,
}

However, this still doesn't work! Debugfs may get triggered at any time and need to read these fields, and there's no way for it to take the spinlock with the above design - it doesn't know where the spinlock is. For the integers I guess we could make them atomic to allow reading them in parallel with mutation, but that option is not available for the red/black trees.

What is the intended solution in this case? If the argument is that this is a rare case, then keep in mind that this is a real-world example of debugging information that we actually expose today in a real driver. With Matt's current approach, it's relatively easy - just store a bunch of File<Arc<Process>> instances somewhere and define each one to take the mutex and print the relevant value.

Quoted 6 lines
Quoted 2 lines

How would your example look like with the current approach? IIUC, it also wouldn't work, because the debugfs data can't be mutated?

I would store a bunch of File<Arc<Process>> instances somewhere. Each one has a closure that takes the spinlock and prints the appropriate value.

Ok, I think we need to see some "real" examples here of the api in use before figuring it out further as I'm totally confused :)

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly. We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

So how about a platform driver that exposes values read from a platform device (i.e. a soc info driver), that also includes a local-to-the-device data structure that can be locked and modified? That should cover all the use cases that I can think of at the moment.

thanks,

greg k-h

maintainer
#31 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 4 lines · Greg Kroah-Hartman

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly. We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well, that's possible with both approaches. Data behind a lock becomes mutable once you grabbed the lock. That's the same in both cases.

The difference is that with the pin-init approach I propose you can't have what Alice sketched up. And I think it's even desirable that you can't do it.

Let's me explain the difference on a simplified example, based on Alice' example.

ForeignOwnable API
------------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: SpinLock<ProcessInner>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here we have to create an Arc<Process> (let's call it process) and create files from it.

	let file_threads = dir.create_file("threads", process);
	let file_max_threads = dir.create_file("max_threads", process);

In the file system callback of both of these, we now have an Arc<Process>, hence we can access:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

and in the other file:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

Pin-Init API
------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: File<SpinLock<ProcessInner>>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here Process does not need to be within an Arc and no separate file instances need to be kept around, that happens already within the constructor of Process:

	pin_init!(Process {
	   inner <- dir.create_file("process_inner", ...),
	   [...]
	})

The file itself has a reference to SpinLock<ProcessInner>, hence we can access:

	let guard = inner.lock();

	read_or_write(guard.threads)
	read_or_write(guard.max_threads)

The difference is that with the ForeignOwnable API it was possible to have separate files for threads and max_threads.

While with the pin-init one we either have to have a single file exposing ProcessInner (which is what I did above) or protect threads and max_threads with separate locks (of course max_threads could also just be an atomic).

(If you like I can sketch up this case as well.)

At a first glance this seems like an undesirable limitation, but I argue that this is a good thing.

The reason I think so is what I also explained in [1], but let me adjust it a bit for this reply:

threads and max_threads being protected by the same lock means that they are in a certain relationship to each other. Meaning that they only really make sense looking at them atomically.

So I argue it does not make sense to expose those values to userspace through separate files.

For instance:

	$ cat max_threads && cat threads
	$ 5
	$ 10

This way you may read 5 max_threads, but 10 actual threads, because things may have changed in between the two cat commands.

However, if instead, they are exposed through a single file, we get an atomic view of them, such that the semantic relationship between them is preserved.

For instance:

	$ cat process_info
	$ threads: 2
	$ max_threads: 10

So, what I'm trying to say is, I think it's a good thing if fields that are protected by the same lock can't be exposed through separate files, because it means that the fields only make sense in the context of each other.

Or saying it the other way around, if it makes sense to expose fields through separate files, it means they're unrelated to each other and hence should be protected with separate locks, rather than a common one.

IMHO it's even a good thing beyond the scope of debugfs, because it forces developers to really think about organizing structures properly, e.g. in a way that only fields that really belong behind a certain lock are placed behind this lock.

Quoted 4 lines

So how about a platform driver that exposes values read from a platform device (i.e. a soc info driver), that also includes a local-to-the-device data structure that can be locked and modified? That should cover all the use cases that I can think of at the moment.

Yes, I also really like to have that.

But, again, both approaches can do this. It's just that I really discourage the one that forces us to have an Arc instance on structures exposed through debugfs, since this messes with the driver's lifetime and ownership architecture in a bad way.

#32 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 110 lines · Danilo Krummrich
Quoted 4 lines · Greg Kroah-Hartman

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly. We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well, that's possible with both approaches. Data behind a lock becomes mutable once you grabbed the lock. That's the same in both cases.

The difference is that with the pin-init approach I propose you can't have what Alice sketched up. And I think it's even desirable that you can't do it.

Let's me explain the difference on a simplified example, based on Alice' example.

ForeignOwnable API
------------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: SpinLock<ProcessInner>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here we have to create an Arc<Process> (let's call it process) and create files from it.

	let file_threads = dir.create_file("threads", process);
	let file_max_threads = dir.create_file("max_threads", process);

In the file system callback of both of these, we now have an Arc<Process>, hence we can access:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

and in the other file:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

Pin-Init API
------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: File<SpinLock<ProcessInner>>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here Process does not need to be within an Arc and no separate file instances need to be kept around, that happens already within the constructor of Process:

	pin_init!(Process {
	   inner <- dir.create_file("process_inner", ...),
	   [...]
	})

The file itself has a reference to SpinLock<ProcessInner>, hence we can access:

	let guard = inner.lock();

	read_or_write(guard.threads)
	read_or_write(guard.max_threads)

The difference is that with the ForeignOwnable API it was possible to have separate files for threads and max_threads.

While with the pin-init one we either have to have a single file exposing ProcessInner (which is what I did above) or protect threads and max_threads with separate locks (of course max_threads could also just be an atomic).

(If you like I can sketch up this case as well.)

At a first glance this seems like an undesirable limitation, but I argue that this is a good thing.

The reason I think so is what I also explained in [1], but let me adjust it a bit for this reply:

threads and max_threads being protected by the same lock means that they are in a certain relationship to each other. Meaning that they only really make sense looking at them atomically.

So I argue it does not make sense to expose those values to userspace through separate files.

For instance:

	$ cat max_threads && cat threads
	$ 5
	$ 10

This way you may read 5 max_threads, but 10 actual threads, because things may have changed in between the two cat commands.

Quoted 8 lines

However, if instead, they are exposed through a single file, we get an atomic view of them, such that the semantic relationship between them is preserved.

For instance:

	$ cat process_info
	$ threads: 2
	$ max_threads: 10

I think you mean to write:

 	$ cat process_info
 	threads: 2
 	max_threads: 10

right?

Quoted 18 lines

So, what I'm trying to say is, I think it's a good thing if fields that are protected by the same lock can't be exposed through separate files, because it means that the fields only make sense in the context of each other.

Or saying it the other way around, if it makes sense to expose fields through separate files, it means they're unrelated to each other and hence should be protected with separate locks, rather than a common one.

IMHO it's even a good thing beyond the scope of debugfs, because it forces developers to really think about organizing structures properly, e.g. in a way that only fields that really belong behind a certain lock are placed behind this lock.

Quoted 4 lines

So how about a platform driver that exposes values read from a platform device (i.e. a soc info driver), that also includes a local-to-the-device data structure that can be locked and modified? That should cover all the use cases that I can think of at the moment.

Quoted 7 lines

Yes, I also really like to have that.

But, again, both approaches can do this. It's just that I really discourage the one that forces us to have an Arc instance on structures exposed through debugfs, since this messes with the driver's lifetime and ownership architecture in a bad way.

Thanks for the long descriptions, that's great to help out here. I'll wait for the next patch series with a real example to show my ignorance of rust some more :)

thanks,

greg k-h

maintainer
#33 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 125 lines · Greg Kroah-Hartman
Quoted 110 lines · Danilo Krummrich
Quoted 4 lines · Greg Kroah-Hartman

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly. We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well, that's possible with both approaches. Data behind a lock becomes mutable once you grabbed the lock. That's the same in both cases.

The difference is that with the pin-init approach I propose you can't have what Alice sketched up. And I think it's even desirable that you can't do it.

Let's me explain the difference on a simplified example, based on Alice' example.

ForeignOwnable API
------------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: SpinLock<ProcessInner>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here we have to create an Arc<Process> (let's call it process) and create files from it.

	let file_threads = dir.create_file("threads", process);
	let file_max_threads = dir.create_file("max_threads", process);

In the file system callback of both of these, we now have an Arc<Process>, hence we can access:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

and in the other file:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

Pin-Init API
------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: File<SpinLock<ProcessInner>>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here Process does not need to be within an Arc and no separate file instances need to be kept around, that happens already within the constructor of Process:

	pin_init!(Process {
	   inner <- dir.create_file("process_inner", ...),
	   [...]
	})

The file itself has a reference to SpinLock<ProcessInner>, hence we can access:

	let guard = inner.lock();

	read_or_write(guard.threads)
	read_or_write(guard.max_threads)

The difference is that with the ForeignOwnable API it was possible to have separate files for threads and max_threads.

While with the pin-init one we either have to have a single file exposing ProcessInner (which is what I did above) or protect threads and max_threads with separate locks (of course max_threads could also just be an atomic).

(If you like I can sketch up this case as well.)

At a first glance this seems like an undesirable limitation, but I argue that this is a good thing.

The reason I think so is what I also explained in [1], but let me adjust it a bit for this reply:

threads and max_threads being protected by the same lock means that they are in a certain relationship to each other. Meaning that they only really make sense looking at them atomically.

So I argue it does not make sense to expose those values to userspace through separate files.

For instance:

	$ cat max_threads && cat threads
	$ 5
	$ 10

This way you may read 5 max_threads, but 10 actual threads, because things may have changed in between the two cat commands.

Quoted 8 lines

However, if instead, they are exposed through a single file, we get an atomic view of them, such that the semantic relationship between them is preserved.

For instance:

	$ cat process_info
	$ threads: 2
	$ max_threads: 10

I think you mean to write:

(quoted diff) +0 -0
 	$ cat process_info
 	threads: 2
 	max_threads: 10
Quoted 1 line

right?

Yes, indeed. :)

reviewer
#34 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Danilo Krummrich
Quoted 3 lines · Danilo Krummrich

So, what I'm trying to say is, I think it's a good thing if fields that are protected by the same lock can't be exposed through separate files, because it means that the fields only make sense in the context of each other.

I think that even the pin-init API can have multiple files for different elements of the locked structure, you just need to nest File:

    #[pin_data]
    struct Process {
        task: ARef<Task>,
        #[pin]
        inner: File<File<SpinLock<ProcessInner>>>,
    }
    
    pub(crate) struct ProcessInner {
        threads: RBTree<i32, Arc<Thread>>,
        max_threads: u32,
    }

Now you can do:

    pin_init!(Process {
        inner <- dir.create_file(
            "threads",
            dir.create_file("max_threads", new_spinlock!(...)),
        ),
        // ...
    })

But I'd say this will at least raise eyebrows for the reviewers, which is good, since it catches the footgun.

---
Cheers,
Benno

maintainer
#35 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 30 lines · Benno Lossin
Quoted 3 lines · Danilo Krummrich

So, what I'm trying to say is, I think it's a good thing if fields that are protected by the same lock can't be exposed through separate files, because it means that the fields only make sense in the context of each other.

I think that even the pin-init API can have multiple files for different elements of the locked structure, you just need to nest File:

    #[pin_data]
    struct Process {
        task: ARef<Task>,
        #[pin]
        inner: File<File<SpinLock<ProcessInner>>>,
    }
    
    pub(crate) struct ProcessInner {
        threads: RBTree<i32, Arc<Thread>>,
        max_threads: u32,
    }

Now you can do:

    pin_init!(Process {
        inner <- dir.create_file(
            "threads",
            dir.create_file("max_threads", new_spinlock!(...)),
        ),
        // ...
    })
Quoted 2 lines

But I'd say this will at least raise eyebrows for the reviewers, which is good, since it catches the footgun.

Heh! I didn't even think of this trick. I agree, this should at least raise some eyebrows. :)

reviewer
#36 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Danilo Krummrich
Quoted 43 lines · Danilo Krummrich
Quoted 4 lines · Greg Kroah-Hartman

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly. We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well, that's possible with both approaches. Data behind a lock becomes mutable once you grabbed the lock. That's the same in both cases.

The difference is that with the pin-init approach I propose you can't have what Alice sketched up. And I think it's even desirable that you can't do it.

Let's me explain the difference on a simplified example, based on Alice' example.

ForeignOwnable API
------------------

	#[pin_data]
	struct Process {
	    task: ARef<Task>,
	    #[pin]
	    inner: SpinLock<ProcessInner>,
	}
	
	pub(crate) struct ProcessInner {
	    threads: RBTree<i32, Arc<Thread>>,
	    max_threads: u32,
	}

Here we have to create an Arc<Process> (let's call it process) and create files from it.

	let file_threads = dir.create_file("threads", process);
	let file_max_threads = dir.create_file("max_threads", process);

In the file system callback of both of these, we now have an Arc<Process>, hence we can access:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);
Quoted 5 lines

and in the other file:

	let guard = process.inner.lock();

	read_or_write(guard.max_threads);

Where do you actually specify this callback? At the moment, the API asks for Display and thus it can only read values?

---
Cheers,
Benno

reviewer
#37 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 2 lines · Benno Lossin

Where do you actually specify this callback? At the moment, the API asks for Display and thus it can only read values?

A later patch in this series adds closure support.

Alice

reviewer
#38 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Greg Kroah-Hartman
Quoted 8 lines · Greg Kroah-Hartman
Quoted 7 lines · Alice Ryhl
Quoted 2 lines · Benno Lossin

How would your example look like with the current approach? IIUC, it also wouldn't work, because the debugfs data can't be mutated?

I would store a bunch of File<Arc<Process>> instances somewhere. Each one has a closure that takes the spinlock and prints the appropriate value.

But you could also do that with the pin-init design?

Quoted 2 lines

Ok, I think we need to see some "real" examples here of the api in use before figuring it out further as I'm totally confused :)

Agreed :)

Quoted 2 lines

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly.

To me this seems orthogonal to storing the value in-place or in a ForeignOwnable.

Quoted 2 lines

We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well that changes things a lot IMO... How does the C side usually handle synchronization here? Does the driver decide that the structure exposed to debugfs is locked with eg a spinlock and then in the debugfs callback they just lock the same one?

---
Cheers,
Benno

Quoted 8 lines

So how about a platform driver that exposes values read from a platform device (i.e. a soc info driver), that also includes a local-to-the-device data structure that can be locked and modified? That should cover all the use cases that I can think of at the moment.

thanks,

greg k-h

#39 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

Quoted 23 lines · Benno Lossin
Quoted 8 lines · Greg Kroah-Hartman
Quoted 7 lines · Alice Ryhl
Quoted 2 lines · Benno Lossin

How would your example look like with the current approach? IIUC, it also wouldn't work, because the debugfs data can't be mutated?

I would store a bunch of File<Arc<Process>> instances somewhere. Each one has a closure that takes the spinlock and prints the appropriate value.

But you could also do that with the pin-init design?

Quoted 2 lines

Ok, I think we need to see some "real" examples here of the api in use before figuring it out further as I'm totally confused :)

Agreed :)

Quoted 2 lines

Yes, we need to be able to have a debugfs file callback handle a mutable structure in order to lock things correctly.

To me this seems orthogonal to storing the value in-place or in a ForeignOwnable.

Quoted 7 lines
Quoted 2 lines

We also need to have it be mutable so that it can MODIFY the value (everyone seems to forget that debugfs allows that...)

Well that changes things a lot IMO... How does the C side usually handle synchronization here? Does the driver decide that the structure exposed to debugfs is locked with eg a spinlock and then in the debugfs callback they just lock the same one?

It does whatever it feels like, so both are valid uses.

Sometimes a lock is involved, sometimes not. Remember this is debugfs, the only rule for it is "there are no rules except it requires root permissions to access".

thanks,

greg k-h

maintainer
#40 lore raw

Re: [PATCH v8 4/6] rust: debugfs: Support arbitrary owned backing for File

In reply to Alice Ryhl
Quoted 143 lines · Alice Ryhl
Quoted 87 lines · Danilo Krummrich
Quoted 18 lines · Greg Kroah-Hartman
Quoted 17 lines · Danilo Krummrich

Instead this should just be:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

and then I would initialize it the following way:

	let fw = KBox::new(Firmware::new(), GFP_KERNEL)?;
	let file = dir.create_file("firmware", fw);

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();

	// Access to fw is prevented by the compiler, since it has been moved
	// into file.
Quoted 6 lines
Quoted 2 lines

This is much better, since now I have the guarantee that my Firmare instance can't out-live the GPU instance.

That's better, yes, but how would multiple files for the same "structure" work here? Like a debugfs-file-per-field of a structure that we often have?

That is a very good question and I thought about this as well, because with only the current API this would require us to have more and more dynamic allocations if we want to have a more fine grained filesystem representations of structures.

The idea I have for this is to use pin-init, which we do in quite some other places as well.

I think we can add an additional API like this:

	impl Dir {
	   pub fn create_file<T>(&self, data: impl PinInit<T>) -> impl PinInit<Self> {
	      pin_init!(Self {
	         data <- data,
	         ...
	      })
	   }
	}

This allows us to do things like:

	#[pin_data]
	struct Firmware {
	   #[pin]
	   minor: debugfs::File<u32>,
	   #[pin]
	   major: debugfs::File<u32>,
	   #[pin]
	   buffer: debugfs::File<[u8]>,
	}

	impl Firmware {
	   pub fn new(&dir: debugfs::Dir, buffer: [u8]) -> impl PinInit<Self> {
	      pin_init!(Self {
	         minor <- dir.create_file("minor", 1),
	         major <- dir.create_file("major", 2),
	         buffer <- dir.create_file("buffer", buffer),
	      })
	   }
	}

	// This is the only allocation we need.
	let fw = KBox::pin_init(Firmware::new(...), GFP_KERNEL)?;

With this everything is now in a single allocation and since we're using pin-init, Dir::create_file() can safely store pointers of the corresponding data in debugfs_create_file(), since this structure is guaranteed to be pinned in memory.

Actually, we can also implement *only this*, since with this my previous example would just become this:

	struct GPU {
	   fw: debugfs::File<Firmware>,
	}

	let file = dir.create_file("firmware", Firmware::new());
	let file = KBox::pin_init(file, GFP_KERNEL)?;

	// debugfs::File<Firmware> dereferences to Firmware
	file.do_something();
Quoted 2 lines

Given that, I think we should change things to use pin-init right away for the debugfs::File API.

Does this actually work in practice for anything except immutable data? I mean, let's take Rust Binder as an example and lets say that I want to expose a directory for each Process object with some of the fields exposed. Let's just simplify Rust Binder a bit and only include some of the fields:

#[pin_data]

struct Process {
    task: ARef<Task>,
    #[pin]
    inner: SpinLock<ProcessInner>,
}
pub(crate) struct ProcessInner {
    threads: RBTree<i32, Arc<Thread>>,
    nodes: RBTree<u64, DArc<Node>>,
    requested_thread_count: u32,
    max_threads: u32,
    started_thread_count: u32,
}

Rust Binder already does expose some debugging data through a file system, though it doesn't do so using debugfs. It exposes a lot of data, but among them are the pid, the number of threads and nodes, as well as the values of requested_thread_count, started_thread_count, and max_threads.

Now, we run into problem number one: pinning is not supported inside mutexes. But let's say we solved that and we could do this:

#[pin_data]
struct Process {
task: File<ARef<Task>>, // prints the pid
#[pin]
inner: SpinLock<ProcessInner>,
}

pub(crate) struct ProcessInner {
threads: File<RBTree<i32, Arc<Thread>>>, // prints the count
nodes: File<RBTree<u64, DArc<Node>>>, // prints the count
requested_thread_count: File<u32>,
max_threads: File<u32>,
started_thread_count: File<u32>,
}

However, this still doesn't work! Debugfs may get triggered at any time and need to read these fields, and there's no way for it to take the spinlock with the above design - it doesn't know where the spinlock is. For the integers I guess we could make them atomic to allow reading them in parallel with mutation, but that option is not available for the red/black trees.

Quoted 3 lines

What is the intended solution in this case? If the argument is that this is a rare case, then keep in mind that this is a real-world example of debugging information that we actually expose today in a real driver.

I'd like to question the decision of having a separate File for each member of ProcessInner in this case a bit:

Given the decision to protect all members of ProcessInner with the same lock, I assume that they are in a certain relationship to each other, such that it is either necessary or (at least) makes the most sense to keep them synchronized with each other.

In other words, you probably don't want that, for instance, changes on requested_thread_count can race with threads, or max_threads, etc.

So the question I want to raise is, given that, does it make sense to expose those values to userspace unsychronized to each other, i.e. through separate files?

For instance:

	$ cat max_threads && cat threads
	$ 5
	$ 10

This way you may read 5 max_threads, but 10 actual threads, because things have changed in between the two cat commands.

Or is it more that you want to expose them all at once, such that reading those values from debugfs you can be sure that they were *all* captured under the same lock at the same time and are in a valid relationship to each other?

For instance:

	$ cat process_info
	$ threads: 2
	$ nodes: 2
	$ requested_thread_count: 5
	$ max_threads: 10
	$ started_thread_count: 1

(I don't know if the numbers make sense semantically, but you get the idea.)

So, what I'm trying to say is, I think that usually we want to expose data protected with the same lock as a whole and otherwise protect data that is independant from other data with separate locks.

- Danilo

#41 lore raw

PATCH v8 5/6 rust: debugfs: Support format hooks

In reply to Matthew Maurer

Rather than always using Display, allow hooking arbitrary functions to arbitrary files. Display technically has the expressiveness to do this, but requires a new type be declared for every different way to render things, which can be very clumsy.

---
 rust/kernel/debugfs.rs              | 45 ++++++++++++++++++++
 rust/kernel/debugfs/display_file.rs | 85 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 129 insertions(+), 1 deletion(-)
rust/kernel/debugfs.rs +45 -0
index 929e55ee5629f6888edf29997b9ed77d274e11c8..d74b599e8534536b10502e6db8c6f3197f7ab4a5 100644
@@ -10,7 +10,9 @@
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
 use crate::types::ForeignOwnable;
+use core::fmt;
 use core::fmt::Display;
+use core::ops::Deref;
 
 #[cfg(CONFIG_DEBUG_FS)]
 mod display_file;
@@ -157,6 +159,49 @@ pub fn display_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D
         self.create_file(name, data)
     }
 
+    /// Create a file in a DebugFS directory with the provided name, and contents from invoking `f`
+    /// on the provided reference.
+    ///
+    /// `f` must be a function item or a non-capturing closure, or this will fail to compile.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use core::sync::atomic::{AtomicU32, Ordering};
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// let dir = Dir::new(c_str!("foo"));
+    /// static MY_ATOMIC: AtomicU32 = AtomicU32::new(3);
+    /// let file = dir.fmt_file(c_str!("bar"), &MY_ATOMIC, &|val, f| {
+    ///   let out = val.load(Ordering::Relaxed);
+    ///   writeln!(f, "{out:#010x}")
+    /// });
+    /// MY_ATOMIC.store(10, Ordering::Relaxed);
+    /// ```
+    pub fn fmt_file<
+        D: ForeignOwnable + Send + Sync,
+        T,
+        F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+    >(
+        &self,
+        name: &CStr,
+        data: D,
+        f: &'static F,
+    ) -> File
+    where
+        for<'a> D::Borrowed<'a>: Deref<Target = T>,
+    {
+        #[cfg(CONFIG_DEBUG_FS)]
+        let data_adapted = display_file::FormatAdapter::new(data, f);
+        #[cfg(not(CONFIG_DEBUG_FS))]
+        let data_adapted = {
+            // Mark used
+            let (_, _) = (data, f);
+            &0
+        };
+        self.display_file(name, data_adapted)
+    }
+
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
rust/kernel/debugfs/display_file.rs +84 -1

94 lines not on this page · raw message

Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
#42 lore raw

PATCH v8 6/6 rust: samples: Add debugfs sample

In reply to Matthew Maurer

Provides an example of using the Rust DebugFS bindings.

---
 MAINTAINERS                  |  1 +
 samples/rust/Kconfig         | 11 +++++++
 samples/rust/Makefile        |  1 +
 samples/rust/rust_debugfs.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+)
MAINTAINERS +1 -0
index 551e3a6a16d9051a2d58a77614c458287da2bdcc..f55c99cb2907655d109cb1fa248cdec803b5ce9a 100644
@@ -7374,6 +7374,7 @@ F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 F:	rust/kernel/faux.rs
 F:	rust/kernel/platform.rs
+F:	samples/rust/rust_debugfs.rs
 F:	samples/rust/rust_driver_platform.rs
 F:	samples/rust/rust_driver_faux.rs
 
samples/rust/Kconfig +11 -0
index 7f7371a004ee0a8f67dca99c836596709a70c4fa..01101db41ae31b08a86d048cdd27da8ef9bb23a2 100644
@@ -62,6 +62,17 @@ config SAMPLE_RUST_DMA
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DEBUGFS
+	tristate "DebugFS Test Module"
+	depends on DEBUG_FS
+	help
+	  This option builds the Rust DebugFS Test module sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_debugfs.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_DRIVER_PCI
 	tristate "PCI Driver"
 	depends on PCI
samples/rust/Makefile +1 -0
index bd2faad63b4f3befe7d1ed5139fe25c7a8b6d7f6..61276222a99f8cc6d7f84c26d0533b30815ebadd 100644
@@ -4,6 +4,7 @@ ccflags-y += -I$(src)				# needed for trace events
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE)		+= rust_misc_device.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
+obj-$(CONFIG_SAMPLE_RUST_DEBUGFS)		+= rust_debugfs.o
 obj-$(CONFIG_SAMPLE_RUST_DMA)			+= rust_dma.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
samples/rust/rust_debugfs.rs new file +76 -0

76 lines not on this page · raw message

Signature · 1 line
2.50.0.727.gbf7dc18ff4-goog
#43 lore raw

Re: [PATCH v8 6/6] rust: samples: Add debugfs sample

samples/rust/rust_debugfs.rs line 60

Quoted 5 lines · Matthew Maurer
samples/rust/rust_debugfs.rs new file +5 -0
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });

While cute, is this really going to be the way to describe all "custom" debugfs function callbacks? No other way to point to a function itself instead? Look at "fun" debugfs functions like qh_lines() in drivers/usb/host/ehci-dbg.c that is dumping tons of data out. Putting that inline here is going to be a bit ackward :)

So can you show an example of a "traditional" debugfs file output with multiple lines that is dealing with a dynamically allocated device that is associated with the module (not the static example you showed here), as that's going to be the real way this is used, not with static variables.

thanks,

greg k-h

#44 lore raw

Re: [PATCH v8 6/6] rust: samples: Add debugfs sample

Quoted 8 lines · Greg Kroah-Hartman

samples/rust/rust_debugfs.rs line 60

Quoted 5 lines · Matthew Maurer
samples/rust/rust_debugfs.rs new file +5 -0
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });
Quoted 5 lines

While cute, is this really going to be the way to describe all "custom" debugfs function callbacks? No other way to point to a function itself instead? Look at "fun" debugfs functions like qh_lines() in drivers/usb/host/ehci-dbg.c that is dumping tons of data out. Putting that inline here is going to be a bit ackward :)

Good news, function pointers are legal to pass in here as well already, I can add that usage to make it clear.

Quoted 6 lines

So can you show an example of a "traditional" debugfs file output with multiple lines that is dealing with a dynamically allocated device that is associated with the module (not the static example you showed here), as that's going to be the real way this is used, not with static variables.

Sure, do we want to:
* Finish creating the driver struct early in init, then call dynamic .create(&str) or .destroy(&str) .modify(&str) type things on it in init to show how it would work
* Actually wire up an input source to drive create/destroy/modify dynamically (e.g. I could implement a miscdevice) - if you want this one, do you have a preference on where I get my input signal from?

Quoted 4 lines

thanks,

greg k-h

maintainer
#45 lore raw

Re: [PATCH v8 6/6] rust: samples: Add debugfs sample

Quoted 26 lines · Matthew Maurer
Quoted 8 lines · Greg Kroah-Hartman

samples/rust/rust_debugfs.rs line 60

Quoted 5 lines · Matthew Maurer
samples/rust/rust_debugfs.rs new file +5 -0
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });
Quoted 5 lines

While cute, is this really going to be the way to describe all "custom" debugfs function callbacks? No other way to point to a function itself instead? Look at "fun" debugfs functions like qh_lines() in drivers/usb/host/ehci-dbg.c that is dumping tons of data out. Putting that inline here is going to be a bit ackward :)

Good news, function pointers are legal to pass in here as well already, I can add that usage to make it clear.

Quoted 6 lines

So can you show an example of a "traditional" debugfs file output with multiple lines that is dealing with a dynamically allocated device that is associated with the module (not the static example you showed here), as that's going to be the real way this is used, not with static variables.

Quoted 7 lines

Sure, do we want to:
* Finish creating the driver struct early in init, then call dynamic .create(&str) or .destroy(&str) .modify(&str) type things on it in init to show how it would work
* Actually wire up an input source to drive create/destroy/modify dynamically (e.g. I could implement a miscdevice) - if you want this one, do you have a preference on where I get my input signal from?

I think the idea was to show how it works in a real driver context, e.g. a platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a miscdevice registered from a module, which is a rather rare use-case.

If you rebase on the latest driver-core-next, you can write a platform driver with an ACPI ID table, which can easily probed by passing -acpitable file=ssdt.aml to qemu, i.e. no need to mess with OF.

#46 lore raw

Re: [PATCH v8 6/6] rust: samples: Add debugfs sample

Quoted 36 lines · Danilo Krummrich
Quoted 26 lines · Matthew Maurer
Quoted 8 lines · Greg Kroah-Hartman

samples/rust/rust_debugfs.rs line 60

Quoted 5 lines · Matthew Maurer
samples/rust/rust_debugfs.rs new file +5 -0
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });
Quoted 5 lines

While cute, is this really going to be the way to describe all "custom" debugfs function callbacks? No other way to point to a function itself instead? Look at "fun" debugfs functions like qh_lines() in drivers/usb/host/ehci-dbg.c that is dumping tons of data out. Putting that inline here is going to be a bit ackward :)

Good news, function pointers are legal to pass in here as well already, I can add that usage to make it clear.

Quoted 6 lines

So can you show an example of a "traditional" debugfs file output with multiple lines that is dealing with a dynamically allocated device that is associated with the module (not the static example you showed here), as that's going to be the real way this is used, not with static variables.

Quoted 7 lines

Sure, do we want to:
* Finish creating the driver struct early in init, then call dynamic .create(&str) or .destroy(&str) .modify(&str) type things on it in init to show how it would work
* Actually wire up an input source to drive create/destroy/modify dynamically (e.g. I could implement a miscdevice) - if you want this one, do you have a preference on where I get my input signal from?

Quoted 7 lines

I think the idea was to show how it works in a real driver context, e.g. a platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a miscdevice registered from a module, which is a rather rare use-case.

If you rebase on the latest driver-core-next, you can write a platform driver with an ACPI ID table, which can easily probed by passing -acpitable file=ssdt.aml to qemu, i.e. no need to mess with OF.

I'm confused as to how registering as a platform driver would result in an input source that would let me trigger the creation/destruction of DebugFS files. I need some kind of input stream to do that. Is there some input stream that's available to a platform driver that I'm missing, or are you suggesting that the input stream would effectively be the probe's id_info field? If I did that, wouldn't I still have a static arrangement of DebugFS files in my driver struct?

I could have misunderstood, but I don't think that's what Greg is asking for - I think he wants to see how at a data structure level, I can handle creating and destroying DebugFS files that correspond to some kind of object being created and destroyed, rather than just having a static list of slots in my driver struct for keeping them alive.

maintainer
#47 lore raw

Re: [PATCH v8 6/6] rust: samples: Add debugfs sample

Quoted 45 lines · Matthew Maurer
Quoted 36 lines · Danilo Krummrich
Quoted 26 lines · Matthew Maurer
Quoted 8 lines · Greg Kroah-Hartman

samples/rust/rust_debugfs.rs line 60

Quoted 5 lines · Matthew Maurer
samples/rust/rust_debugfs.rs new file +5 -0
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });
Quoted 5 lines

While cute, is this really going to be the way to describe all "custom" debugfs function callbacks? No other way to point to a function itself instead? Look at "fun" debugfs functions like qh_lines() in drivers/usb/host/ehci-dbg.c that is dumping tons of data out. Putting that inline here is going to be a bit ackward :)

Good news, function pointers are legal to pass in here as well already, I can add that usage to make it clear.

Quoted 6 lines

So can you show an example of a "traditional" debugfs file output with multiple lines that is dealing with a dynamically allocated device that is associated with the module (not the static example you showed here), as that's going to be the real way this is used, not with static variables.

Quoted 7 lines

Sure, do we want to:
* Finish creating the driver struct early in init, then call dynamic .create(&str) or .destroy(&str) .modify(&str) type things on it in init to show how it would work
* Actually wire up an input source to drive create/destroy/modify dynamically (e.g. I could implement a miscdevice) - if you want this one, do you have a preference on where I get my input signal from?

Quoted 7 lines

I think the idea was to show how it works in a real driver context, e.g. a platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a miscdevice registered from a module, which is a rather rare use-case.

If you rebase on the latest driver-core-next, you can write a platform driver with an ACPI ID table, which can easily probed by passing -acpitable file=ssdt.aml to qemu, i.e. no need to mess with OF.

Quoted 7 lines

I'm confused as to how registering as a platform driver would result in an input source that would let me trigger the creation/destruction of DebugFS files. I need some kind of input stream to do that. Is there some input stream that's available to a platform driver that I'm missing, or are you suggesting that the input stream would effectively be the probe's id_info field? If I did that, wouldn't I still have a static arrangement of DebugFS files in my driver struct?

If it's about having some dynamic input stream, creating an example with a platform driver clearly doesn't help by itself.

But that wasn't my understanding. My understanding was that the request is to show it in a driver context, where you won't get away with statics. :)

But that's maybe because *I* would like to focus more on this case, because it's the common one.

Quoted 6 lines

I could have misunderstood, but I don't think that's what Greg is asking for - I think he wants to see how at a data structure level, I can handle creating and destroying DebugFS files that correspond to some kind of object being created and destroyed, rather than just having a static list of slots in my driver struct for keeping them alive.

If that's the request, you could simply create a function that returns a Vec with some random entries and then you just iterate over it in Driver::probe() or Module::init()?

I don't know if that case is too interesting, since conceptually it doesn't make a huge difference to the case where you have a single instance, i.e.

Current
-------

	struct Bar {
	   a: A,
	   b: B,
	}

	// Single.
	struct Foo {
	   bar: Arc<Bar>,
	   bar_file: File,
	}

	// Multiple.
	struct Foo {
	  bars: Vec<Arc<Bar>>
	  bar_files: Vec<File>, // contains a File for each field for every Bar
	}

Proposed (pin-init)
-------------------

	struct Bar {
	   a: File<A>,
	   b: File<B>,
	}

	// Single.
	struct Foo {
	   bar: Bar,
	}

	// Multiple.
	struct Foo {
	   bar: Vec<Bar>
	}