From: Alice Ryhl <aliceryhl@google.com>
To: Arnd Bergmann <arnd@arndb.de>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Danilo Krummrich <dakr@kernel.org>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
"Alice Ryhl" <aliceryhl@google.com>
Subject: [PATCH v2] rust: miscdevice: add registration data to MiscDevice
Date: Tue, 13 Oct 2026 12:29:02 +0000 [thread overview]
Message-ID: <20261013-miscdevice-data-v2-1-d727496ed705@google.com> (raw)
Currently when fds for a miscdevice are opened, the only "global" data
they are given access to is the MiscDeviceRegistration value. However,
this value doesn't let you store any user-provided data, so there is no
way for different fds from the same miscdevice to interact with each
other. Thus, let the user specify a type to be stored in the
MiscDeviceRegistration in which the user can store whichever data they
would like.
The intended user of this patch is Rust course material. MiscDevice is a
nice and relatively easy to use API for someone's first driver, and
being able to persist data from fd to fd allows the student to interact
with their driver using 'cat' and 'echo', even though each call opens a
new fd.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
Changes in v2:
- Store `T` directly in `MiscDeviceRegistration<T>` instead of adding a `RegistrationData` associated type.
- Make `MiscDeviceRegistration::register` `unsafe` (requiring `Drop` to run) so `T` does not need to be `'static`.
- Rename `MiscDevice::Ptr` to `MiscDevice::File`.
- Use `container_of!` and `Opaque::cast_from` in `MiscdeviceVTable::open`.
- Rebase on `char-misc-next`.
- Link to v1: https://lore.kernel.org/r/20260807-miscdevice-data-v1-1-43c4233e0b03@google.com
---
rust/kernel/miscdevice.rs | 100 ++++++++++++++++++++++++++-------------
samples/rust/rust_misc_device.rs | 29 ++++++++----
2 files changed, 87 insertions(+), 42 deletions(-)
diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs
index 8d4b719bd83f..ecc142a1b7ea 100644
--- a/rust/kernel/miscdevice.rs
+++ b/rust/kernel/miscdevice.rs
@@ -10,6 +10,7 @@
use crate::{
bindings,
+ container_of,
device::Device,
error::{
to_result,
@@ -32,7 +33,10 @@
Opaque, //
}, //
};
-use core::marker::PhantomData;
+use core::{
+ marker::PhantomData,
+ ops::Deref, //
+};
/// Options for creating a misc device.
#[derive(Copy, Clone)]
@@ -63,37 +67,56 @@ pub const fn into_raw<T: MiscDevice>(self) -> bindings::miscdevice {
/// - Deregistration occurs exactly once in [`Drop`] via `misc_deregister()`.
/// - `inner` wraps a valid, pinned `miscdevice` created using
/// [`MiscDeviceOptions::into_raw`].
-#[repr(transparent)]
+#[repr(C)]
#[pin_data(PinnedDrop)]
pub struct MiscDeviceRegistration<T> {
#[pin]
inner: Opaque<bindings::miscdevice>,
- _t: PhantomData<T>,
+ #[pin]
+ data: T,
}
// SAFETY: It is allowed to call `misc_deregister` on a different thread from where you called
-// `misc_register`.
-unsafe impl<T> Send for MiscDeviceRegistration<T> {}
+// `misc_register`, and `T` is `Send`.
+unsafe impl<T: Send> Send for MiscDeviceRegistration<T> {}
// SAFETY: All `&self` methods on this type are written to ensure that it is safe to call them in
-// parallel.
-unsafe impl<T> Sync for MiscDeviceRegistration<T> {}
+// parallel, and `T` is `Sync`.
+unsafe impl<T: Sync> Sync for MiscDeviceRegistration<T> {}
impl<T: MiscDevice> MiscDeviceRegistration<T> {
/// Register a misc device.
- pub fn register(opts: MiscDeviceOptions) -> impl PinInit<Self, Error> {
+ ///
+ /// # Safety
+ ///
+ /// The caller must not `mem::forget()` the returned [`MiscDeviceRegistration`] or otherwise
+ /// prevent its [`Drop`] implementation from running.
+ pub unsafe fn register<'a>(
+ opts: MiscDeviceOptions,
+ data: impl PinInit<T, Error> + 'a,
+ ) -> impl PinInit<Self, Error> + 'a
+ where
+ T: 'a,
+ {
try_pin_init!(Self {
+ data <- data,
inner <- Opaque::try_ffi_init(move |slot: *mut bindings::miscdevice| {
// SAFETY: The initializer can write to the provided `slot`.
unsafe { slot.write(opts.into_raw::<T>()) };
// SAFETY: We just wrote the misc device options to the slot. The miscdevice will
// get unregistered before `slot` is deallocated because the memory is pinned and
- // the destructor of this type deallocates the memory.
+ // the caller guarantees that the destructor of this type will run.
+ //
+ // The `data` field is already initialized and is only dropped after
+ // `misc_deregister` in `PinnedDrop::drop`. Since `T: Sync` and the caller
+ // guarantees that `Drop` will run before `T` becomes invalid, it is sound for the
+ // `open` callback to access `&MiscDeviceRegistration<T>` until `misc_deregister`
+ // returns.
+ //
// INVARIANT: If this returns `Ok(())`, then the `slot` will contain a registered
// misc device.
to_result(unsafe { bindings::misc_register(slot) })
}),
- _t: PhantomData,
})
}
@@ -113,6 +136,14 @@ pub fn device(&self) -> &Device {
}
}
+impl<T> Deref for MiscDeviceRegistration<T> {
+ type Target = T;
+ #[inline]
+ fn deref(&self) -> &T {
+ &self.data
+ }
+}
+
#[pinned_drop]
impl<T> PinnedDrop for MiscDeviceRegistration<T> {
fn drop(self: Pin<&mut Self>) {
@@ -121,19 +152,23 @@ fn drop(self: Pin<&mut Self>) {
}
}
-/// Trait implemented by the private data of an open misc device.
+/// Trait implemented by the data stored in a [`MiscDeviceRegistration`].
#[vtable]
-pub trait MiscDevice: Sized {
- /// What kind of pointer should `Self` be wrapped in.
- type Ptr: ForeignOwnable + Send + Sync;
+pub trait MiscDevice: Sized + Sync {
+ /// The type of the data stored in each file for this miscdevice.
+ ///
+ /// This type must be pointer-sized, so it will usually be a [`KBox`] or [`Arc`].
+ ///
+ /// [`Arc`]: crate::sync::Arc
+ type File: ForeignOwnable + Send + Sync + 'static;
/// Called when the misc device is opened.
///
/// The returned pointer will be stored as the private data for the file.
- fn open(_file: &File, _misc: &MiscDeviceRegistration<Self>) -> Result<Self::Ptr>;
+ fn open(_file: &File, _misc: &MiscDeviceRegistration<Self>) -> Result<Self::File>;
/// Called when the misc device is released.
- fn release(device: Self::Ptr, _file: &File) {
+ fn release(device: Self::File, _file: &File) {
drop(device);
}
@@ -146,7 +181,7 @@ fn release(device: Self::Ptr, _file: &File) {
/// error, the kernel will complete initialization of the VMA according to the properties of
/// `vma`.
fn mmap(
- _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _device: <Self::File as ForeignOwnable>::Borrowed<'_>,
_file: &File,
_vma: &VmaNew,
) -> Result {
@@ -154,12 +189,12 @@ fn mmap(
}
/// Read from this miscdevice.
- fn read_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterDest<'_>) -> Result<usize> {
+ fn read_iter(_kiocb: Kiocb<'_, Self::File>, _iov: &mut IovIterDest<'_>) -> Result<usize> {
build_error!(VTABLE_DEFAULT_ERROR)
}
/// Write to this miscdevice.
- fn write_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterSource<'_>) -> Result<usize> {
+ fn write_iter(_kiocb: Kiocb<'_, Self::File>, _iov: &mut IovIterSource<'_>) -> Result<usize> {
build_error!(VTABLE_DEFAULT_ERROR)
}
@@ -169,7 +204,7 @@ fn write_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterSource<'_>) -> Res
///
/// [`kernel::ioctl`]: mod@crate::ioctl
fn ioctl(
- _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _device: <Self::File as ForeignOwnable>::Borrowed<'_>,
_file: &File,
_cmd: u32,
_arg: usize,
@@ -186,7 +221,7 @@ fn ioctl(
/// provided, then `compat_ptr_ioctl` will be used instead.
#[cfg(CONFIG_COMPAT)]
fn compat_ioctl(
- _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _device: <Self::File as ForeignOwnable>::Borrowed<'_>,
_file: &File,
_cmd: u32,
_arg: usize,
@@ -196,7 +231,7 @@ fn compat_ioctl(
/// Show info for this fd.
fn show_fdinfo(
- _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _device: <Self::File as ForeignOwnable>::Borrowed<'_>,
_m: &SeqFile,
_file: &File,
) {
@@ -220,13 +255,14 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
}
// SAFETY: The open call of a file can access the private data.
- let misc_ptr = unsafe { (*raw_file).private_data };
+ let misc_ptr = Opaque::cast_from(unsafe { (*raw_file).private_data }.cast());
// SAFETY: This is a miscdevice, so `misc_open()` set the private data to a pointer to the
- // associated `struct miscdevice` before calling into this method. Furthermore,
- // `misc_open()` ensures that the miscdevice can't be unregistered and freed during this
- // call to `fops_open`.
- let misc = unsafe { &*misc_ptr.cast::<MiscDeviceRegistration<T>>() };
+ // associated `struct miscdevice` (which is the `inner` field of a valid
+ // `MiscDeviceRegistration<T>`) before calling into this method. Furthermore, `misc_open()`
+ // ensures that the miscdevice can't be unregistered and freed during this call to
+ // `fops_open`, and `T: Sync` allows sharing `&MiscDeviceRegistration<T>` across threads.
+ let misc = unsafe { &*container_of!(misc_ptr, MiscDeviceRegistration<T>, inner) };
// SAFETY:
// * This underlying file is valid for (much longer than) the duration of `T::open`.
@@ -257,7 +293,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
// SAFETY: The release call of a file owns the private data.
let private = unsafe { (*file).private_data };
// SAFETY: The release call of a file owns the private data.
- let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) };
+ let ptr = unsafe { <T::File as ForeignOwnable>::from_foreign(private) };
// SAFETY:
// * The file is valid for the duration of this call.
@@ -320,7 +356,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
// SAFETY: This is a Rust Miscdevice, so we call `into_foreign` in `open` and
// `from_foreign` in `release`, and `fops_mmap` is guaranteed to be called between those
// two operations.
- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private.cast()) };
+ let device = unsafe { <T::File as ForeignOwnable>::borrow(private) };
// SAFETY: The caller provides a vma that is undergoing initial VMA setup.
let area = unsafe { VmaNew::from_raw(vma) };
// SAFETY:
@@ -341,7 +377,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
// SAFETY: The ioctl call of a file can access the private data.
let private = unsafe { (*file).private_data };
// SAFETY: Ioctl calls can borrow the private data of the file.
- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ let device = unsafe { <T::File as ForeignOwnable>::borrow(private) };
// SAFETY:
// * The file is valid for the duration of this call.
@@ -366,7 +402,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
// SAFETY: The compat ioctl call of a file can access the private data.
let private = unsafe { (*file).private_data };
// SAFETY: Ioctl calls can borrow the private data of the file.
- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ let device = unsafe { <T::File as ForeignOwnable>::borrow(private) };
// SAFETY:
// * The file is valid for the duration of this call.
@@ -387,7 +423,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
// SAFETY: The release call of a file owns the private data.
let private = unsafe { (*file).private_data };
// SAFETY: Ioctl calls can borrow the private data of the file.
- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ let device = unsafe { <T::File as ForeignOwnable>::borrow(private) };
// SAFETY:
// * The file is valid for the duration of this call.
// * There is no active fdget_pos region on the file on this thread.
diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs
index 41e26c825060..f5b2b00b933a 100644
--- a/samples/rust/rust_misc_device.rs
+++ b/samples/rust/rust_misc_device.rs
@@ -156,7 +156,11 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
};
try_pin_init!(Self {
- _miscdev <- MiscDeviceRegistration::register(options),
+ // SAFETY: `_miscdev` is stored in `RustMiscDeviceModule` and dropped when the module
+ // is unloaded; `mem::forget()` is never called on it.
+ _miscdev <- unsafe {
+ MiscDeviceRegistration::register(options, try_pin_init!(RustMiscDevice {}))
+ },
})
}
}
@@ -166,8 +170,13 @@ struct Inner {
buffer: KVVec<u8>,
}
+/// Data shared by all open files for the same instance of this miscdevice.
+#[pin_data]
+struct RustMiscDevice {}
+
+/// Data that is stored per file for this miscdevice.
#[pin_data(PinnedDrop)]
-struct RustMiscDevice {
+struct RustMiscDeviceFile {
#[pin]
inner: Mutex<Inner>,
dev: ARef<Device>,
@@ -175,16 +184,16 @@ struct RustMiscDevice {
#[vtable]
impl MiscDevice for RustMiscDevice {
- type Ptr = Pin<KBox<Self>>;
+ type File = Pin<KBox<RustMiscDeviceFile>>;
- fn open(_file: &File, misc: &MiscDeviceRegistration<Self>) -> Result<Pin<KBox<Self>>> {
+ fn open(_file: &File, misc: &MiscDeviceRegistration<Self>) -> Result<Self::File> {
let dev = ARef::from(misc.device());
dev_info!(dev, "Opening Rust Misc Device Sample\n");
KBox::try_pin_init(
try_pin_init! {
- RustMiscDevice {
+ RustMiscDeviceFile {
inner <- new_mutex!(Inner {
value: 0_i32,
buffer: KVVec::new(),
@@ -196,7 +205,7 @@ fn open(_file: &File, misc: &MiscDeviceRegistration<Self>) -> Result<Pin<KBox<Se
)
}
- fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterDest<'_>) -> Result<usize> {
+ fn read_iter(mut kiocb: Kiocb<'_, Self::File>, iov: &mut IovIterDest<'_>) -> Result<usize> {
let me = kiocb.file();
dev_info!(me.dev, "Reading from Rust Misc Device Sample\n");
@@ -207,7 +216,7 @@ fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterDest<'_>) -> Resu
Ok(read)
}
- fn write_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterSource<'_>) -> Result<usize> {
+ fn write_iter(mut kiocb: Kiocb<'_, Self::File>, iov: &mut IovIterSource<'_>) -> Result<usize> {
let me = kiocb.file();
dev_info!(me.dev, "Writing to Rust Misc Device Sample\n");
@@ -223,7 +232,7 @@ fn write_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterSource<'_>) -> R
Ok(len)
}
- fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result<isize> {
+ fn ioctl(me: Pin<&RustMiscDeviceFile>, _file: &File, cmd: u32, arg: usize) -> Result<isize> {
dev_info!(me.dev, "IOCTLing Rust Misc Device Sample\n");
// Treat the ioctl argument as a user pointer.
@@ -245,13 +254,13 @@ fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result
}
#[pinned_drop]
-impl PinnedDrop for RustMiscDevice {
+impl PinnedDrop for RustMiscDeviceFile {
fn drop(self: Pin<&mut Self>) {
dev_info!(self.dev, "Exiting the Rust Misc Device Sample\n");
}
}
-impl RustMiscDevice {
+impl RustMiscDeviceFile {
fn set_value(&self, mut reader: UserSliceReader) -> Result<isize> {
let new_value = reader.read::<i32>()?;
let mut guard = self.inner.lock();
---
base-commit: 79e5797d243a466138f641932f81dc69f796060c
change-id: 20260807-miscdevice-data-71c727e8b2e5
Best regards,
--
Alice Ryhl <aliceryhl@google.com>
reply other threads:[~2026-10-13 12:29 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013-miscdevice-data-v2-1-d727496ed705@google.com \
--to=aliceryhl@google.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=arnd@arndb.de \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox