Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2220257/?format=api
{ "id": 2220257, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2220257/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260406165120.166928-6-wenzhaoliao@ruc.edu.cn/", "project": { "id": 28, "url": "http://patchwork.ozlabs.org/api/1.1/projects/28/?format=api", "name": "Linux PCI development", "link_name": "linux-pci", "list_id": "linux-pci.vger.kernel.org", "list_email": "linux-pci@vger.kernel.org", "web_url": null, "scm_url": null, "webscm_url": null }, "msgid": "<20260406165120.166928-6-wenzhaoliao@ruc.edu.cn>", "date": "2026-04-06T16:51:19", "name": "[RFC,v3,5/6] rust: miscdevice: harden registration and safe file_operations invariants", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "2eef40f5f10e48daac8501948b790fcf6934f3a1", "submitter": { "id": 93071, "url": "http://patchwork.ozlabs.org/api/1.1/people/93071/?format=api", "name": "Wenzhao Liao", "email": "wenzhaoliao@ruc.edu.cn" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260406165120.166928-6-wenzhaoliao@ruc.edu.cn/mbox/", "series": [ { "id": 498880, "url": "http://patchwork.ozlabs.org/api/1.1/series/498880/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-pci/list/?series=498880", "date": "2026-04-06T16:51:15", "name": "Rust goldfish_address_space driver (ioctl-only subset)", "version": 3, "mbox": "http://patchwork.ozlabs.org/series/498880/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2220257/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2220257/checks/", "tags": {}, "headers": { "Return-Path": "\n <linux-pci+bounces-51977-incoming=patchwork.ozlabs.org@vger.kernel.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linux-pci@vger.kernel.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.a=rsa-sha256\n header.s=default header.b=YMMAcelD;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-pci+bounces-51977-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn\n header.b=\"YMMAcelD\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=210.79.60.129", "smtp.subspace.kernel.org;\n dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn", "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=ruc.edu.cn" ], "Received": [ "from sea.lore.kernel.org (sea.lore.kernel.org\n [IPv6:2600:3c0a:e001:db::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fqJvt3KrKz1xtJ\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 07 Apr 2026 05:16:10 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id AC9CB304501D\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 6 Apr 2026 19:14:24 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 98A01396595;\n\tMon, 6 Apr 2026 19:14:23 +0000 (UTC)", "from mail-m60129.netease.com (mail-m60129.netease.com\n [210.79.60.129])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 2BA9C396D2E;\n\tMon, 6 Apr 2026 19:14:18 +0000 (UTC)", "from lwz.tail698a0e.ts.net\n (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244])\n\tby smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbdf;\n\tTue, 7 Apr 2026 00:51:42 +0800 (GMT+08:00)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775502863; cv=none;\n b=ualTWTSzsM2NeeC8eB6haAhA6YBOQK65zTXnf+ZFTn/aEfY/3zl1KykhJG0gKjAaKQIF7XH8pS2mUee5fPOImQOGZbNqaUqS8mpzmwosoz/HuKEfyygbEBJyet5zy6EPe3jBXUkhCwix27BRCCP8FKMb2w6XUlRa86xWV7MnIQ8=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775502863; c=relaxed/simple;\n\tbh=ECGDkkNunsT7ol5JdVMZSZKKWi2mfnpcNCd1+Vi8iIQ=;\n\th=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:\n\t MIME-Version;\n b=H7bLKAyxtBjhdgCmw74vxS203O3afo7ukSLoe3ctF3qKUsoyTSNip2tbeEKASIimwgtmFVnoKyxZzqvDnnmI5CmrBMy4A8SqGv6P8adobtBctyo1Jj94giCKgdtEhqe7lbGA3NXkSsctrNaMJVLgnpWk3oRsTsVpybmLfteE3Fo=", "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn;\n spf=pass smtp.mailfrom=ruc.edu.cn;\n dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn\n header.b=YMMAcelD; arc=none smtp.client-ip=210.79.60.129", "From": "Wenzhao Liao <wenzhaoliao@ruc.edu.cn>", "To": "rust-for-linux@vger.kernel.org,\n\tlinux-pci@vger.kernel.org", "Cc": "ojeda@kernel.org,\n\tdakr@kernel.org,\n\tbhelgaas@google.com,\n\tkwilczynski@kernel.org,\n\tarnd@arndb.de,\n\tgregkh@linuxfoundation.org,\n\tlinux-kernel@vger.kernel.org,\n\tlinux-api@vger.kernel.org", "Subject": "[RFC PATCH v3 5/6] rust: miscdevice: harden registration and safe\n file_operations invariants", "Date": "Mon, 6 Apr 2026 12:51:19 -0400", "Message-Id": "<20260406165120.166928-6-wenzhaoliao@ruc.edu.cn>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20260406165120.166928-1-wenzhaoliao@ruc.edu.cn>", "References": "<cover.1775456181.git.wenzhaoliao@ruc.edu.cn>\n <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn>", "Precedence": "bulk", "X-Mailing-List": "linux-pci@vger.kernel.org", "List-Id": "<linux-pci.vger.kernel.org>", "List-Subscribe": "<mailto:linux-pci+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:linux-pci+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-HM-Tid": "0a9d63b50c3403a2kunm562d3b0d647e8b", "X-HM-MType": "10", "X-HM-Spam-Status": "e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly\n\ttZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVkaTElDVk1CHh9ITx5NS04dSlYeHw5VEwETFhoSFy\n\tQUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0\n\ttZBg++", "DKIM-Signature": "a=rsa-sha256;\n\tb=YMMAcelD5EoMn8NHp9n6qlxUOGKmMtUALBcVlOR799f6xXzFfv810WTv5EMVb/Afq0/YTUqVWRyhicvH0q6gU7aTwqXi3w2q2j5+otxhCSYTae2KW0WjqjYxig3FbwohbLIkTom03/0BKIDZT6EMG9iCT2DjBst+9RjUKxgFfgA=;\n c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1;\n\tbh=83Aos2kKrx0e0kDnA4rTmWci8dP3/t1joOgL0B9Gz+g=;\n\th=date:mime-version:subject:message-id:from;" }, "content": "Extend miscdevice registration with typed per-device data that open()\ncan read through a publication-safe context, and move raw\nfile_operations exposure behind an internal vtable boundary generated by\ndeclare_misc_device_fops!().\n\nThis keeps safe open() on pre-publication state, binds\nfile_operations.owner to THIS_MODULE for safe drivers, and keeps the\nprivate_data ownership protocol inside the abstraction instead of in\ndriver code. The goldfish driver uses the typed registration data to\npass its runtime into open() without raw casts or container traversal.\n\nSigned-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>\n---\n rust/kernel/miscdevice.rs | 409 +++++++++++++++++++++++--------\n samples/rust/rust_misc_device.rs | 9 +-\n 2 files changed, 306 insertions(+), 112 deletions(-)", "diff": "diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs\nindex c3c2052c9206..c2db81cd5da2 100644\n--- a/rust/kernel/miscdevice.rs\n+++ b/rust/kernel/miscdevice.rs\n@@ -9,7 +9,8 @@\n //! Reference: <https://www.kernel.org/doc/html/latest/driver-api/misc_devices.html>\n \n use crate::{\n- bindings,\n+ alloc::KBox,\n+ bindings, container_of,\n device::Device,\n error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR},\n ffi::{c_int, c_long, c_uint, c_ulong},\n@@ -18,9 +19,15 @@\n mm::virt::VmaNew,\n prelude::*,\n seq_file::SeqFile,\n- types::{ForeignOwnable, Opaque},\n+ sync::aref::ARef,\n+ types::{ForeignOwnable, Opaque, ScopeGuard},\n+};\n+use core::{\n+ marker::{PhantomData, PhantomPinned},\n+ pin::Pin,\n+ ptr::drop_in_place,\n+ sync::atomic::{AtomicBool, Ordering},\n };\n-use core::{marker::PhantomData, pin::Pin};\n \n /// Options for creating a misc device.\n #[derive(Copy, Clone)]\n@@ -31,94 +38,258 @@ pub struct MiscDeviceOptions {\n \n impl MiscDeviceOptions {\n /// Create a raw `struct miscdev` ready for registration.\n- pub const fn into_raw<T: MiscDevice>(self) -> bindings::miscdevice {\n+ pub fn into_raw<T: MiscDeviceVTable + 'static>(self) -> bindings::miscdevice {\n let mut result: bindings::miscdevice = pin_init::zeroed();\n result.minor = bindings::MISC_DYNAMIC_MINOR as ffi::c_int;\n result.name = crate::str::as_char_ptr_in_const_context(self.name);\n- result.fops = MiscdeviceVTable::<T>::build();\n+ result.fops = T::file_operations();\n result\n }\n }\n \n+/// Generates the `MiscDeviceVTable` implementation for a concrete miscdevice type.\n+///\n+/// Place this macro after `impl MiscDevice for ...`.\n+///\n+/// The generated implementation always binds `file_operations.owner` to the current module's\n+/// `THIS_MODULE`, so safe drivers cannot accidentally publish owner-less or foreign-owned misc\n+/// device callbacks.\n+#[macro_export]\n+macro_rules! declare_misc_device_fops {\n+ ($type:ty) => {\n+ // SAFETY: This implements the standard Rust miscdevice vtable generated by\n+ // `build_file_operations()`, which wires up owner/module pinning and the private-data\n+ // protocol enforced by this abstraction.\n+ unsafe impl $crate::miscdevice::MiscDeviceVTable for $type {\n+ fn file_operations() -> &'static $crate::bindings::file_operations {\n+ struct AssertSync<T>(T);\n+ // SAFETY: This wrapper is only used for immutable `file_operations` tables stored\n+ // in a `static`.\n+ unsafe impl<T> Sync for AssertSync<T> {}\n+\n+ static FOPS: AssertSync<$crate::bindings::file_operations> = AssertSync(\n+ $crate::miscdevice::build_file_operations::<$type>(THIS_MODULE.as_ptr()),\n+ );\n+\n+ &FOPS.0\n+ }\n+ }\n+ };\n+}\n+\n+#[repr(C)]\n+struct RegistrationBacking<T: MiscDevice + 'static> {\n+ misc: Opaque<bindings::miscdevice>,\n+ data: T::RegistrationData,\n+ owner: *const MiscDeviceRegistration<T>,\n+ registered: AtomicBool,\n+}\n+\n+struct OpenFile<T: MiscDevice + 'static> {\n+ data: *mut ffi::c_void,\n+ _t: PhantomData<T>,\n+}\n+\n+impl<T: MiscDevice + 'static> OpenFile<T> {\n+ fn empty() -> Self {\n+ Self {\n+ data: core::ptr::null_mut(),\n+ _t: PhantomData,\n+ }\n+ }\n+\n+ fn borrow(&self) -> <T::Ptr as ForeignOwnable>::Borrowed<'_> {\n+ // SAFETY: `self.data` comes from `T::Ptr::into_foreign()` and is only converted back in\n+ // `release`, after all borrows from this file operation callback have ended.\n+ unsafe { <T::Ptr as ForeignOwnable>::borrow(self.data) }\n+ }\n+}\n+\n /// A registration of a miscdevice.\n ///\n /// # Invariants\n ///\n-/// - `inner` contains a `struct miscdevice` that is registered using\n-/// `misc_register()`.\n-/// - This registration remains valid for the entire lifetime of the\n-/// [`MiscDeviceRegistration`] instance.\n-/// - Deregistration occurs exactly once in [`Drop`] via `misc_deregister()`.\n-/// - `inner` wraps a valid, pinned `miscdevice` created using\n+/// - `backing.misc` contains a valid `struct miscdevice` created using\n /// [`MiscDeviceOptions::into_raw`].\n-#[repr(transparent)]\n+/// - When `backing.registered` is `true`, `backing.misc` is registered using\n+/// `misc_register()`.\n+/// - Before `misc_register()` publishes `backing.misc`, every field reachable through the safe\n+/// open context (`backing.data` and `backing.owner`) is fully initialized.\n+/// - `backing.owner` points back to this wrapper for the entire time the miscdevice is registered.\n+/// - Deregistration occurs at most once, either via [`MiscDeviceRegistration::deregister`] or\n+/// [`Drop`].\n #[pin_data(PinnedDrop)]\n-pub struct MiscDeviceRegistration<T> {\n+pub struct MiscDeviceRegistration<T: MiscDevice + 'static> {\n+ backing: KBox<RegistrationBacking<T>>,\n #[pin]\n- inner: Opaque<bindings::miscdevice>,\n+ _pin: PhantomPinned,\n _t: PhantomData<T>,\n }\n \n // SAFETY: It is allowed to call `misc_deregister` on a different thread from where you called\n // `misc_register`.\n-unsafe impl<T> Send for MiscDeviceRegistration<T> {}\n+unsafe impl<T: MiscDevice> Send for MiscDeviceRegistration<T> {}\n // SAFETY: All `&self` methods on this type are written to ensure that it is safe to call them in\n // parallel.\n-unsafe impl<T> Sync for MiscDeviceRegistration<T> {}\n+unsafe impl<T: MiscDevice> Sync for MiscDeviceRegistration<T> {}\n \n-impl<T: MiscDevice> MiscDeviceRegistration<T> {\n+impl<T: MiscDevice + 'static> MiscDeviceRegistration<T> {\n /// Register a misc device.\n- pub fn register(opts: MiscDeviceOptions) -> impl PinInit<Self, Error> {\n- try_pin_init!(Self {\n- inner <- Opaque::try_ffi_init(move |slot: *mut bindings::miscdevice| {\n- // SAFETY: The initializer can write to the provided `slot`.\n- unsafe { slot.write(opts.into_raw::<T>()) };\n-\n- // SAFETY: We just wrote the misc device options to the slot. The miscdevice will\n- // get unregistered before `slot` is deallocated because the memory is pinned and\n- // the destructor of this type deallocates the memory.\n- // INVARIANT: If this returns `Ok(())`, then the `slot` will contain a registered\n- // misc device.\n- to_result(unsafe { bindings::misc_register(slot) })\n- }),\n- _t: PhantomData,\n- })\n+ pub fn register(opts: MiscDeviceOptions) -> impl PinInit<Self, Error>\n+ where\n+ T: MiscDevice<RegistrationData = ()> + MiscDeviceVTable,\n+ {\n+ Self::register_with_data(opts, ())\n+ }\n+\n+ /// Register a misc device together with driver-defined registration data.\n+ pub fn register_with_data(\n+ opts: MiscDeviceOptions,\n+ data: T::RegistrationData,\n+ ) -> impl PinInit<Self, Error>\n+ where\n+ T: MiscDeviceVTable,\n+ {\n+ let init = move |slot: *mut Self| {\n+ let backing = KBox::new(\n+ RegistrationBacking {\n+ misc: Opaque::new(opts.into_raw::<T>()),\n+ data,\n+ owner: slot.cast_const(),\n+ registered: AtomicBool::new(false),\n+ },\n+ GFP_KERNEL,\n+ )?;\n+\n+ // SAFETY: `slot` is valid for writes for the duration of this initializer.\n+ unsafe {\n+ slot.write(Self {\n+ backing,\n+ _pin: PhantomPinned,\n+ _t: PhantomData,\n+ })\n+ };\n+\n+ // SAFETY: `slot` points to the fully-initialized registration wrapper we just wrote\n+ // above.\n+ let this = unsafe { &mut *slot };\n+ // SAFETY: `this.as_raw()` points at the fully initialized `struct miscdevice`\n+ // contained in the heap-backed registration backing.\n+ let ret = to_result(unsafe { bindings::misc_register(this.as_raw()) });\n+ if let Err(err) = ret {\n+ // SAFETY: The wrapper was fully initialized above, so dropping it here correctly\n+ // releases the heap-backed registration backing.\n+ unsafe { drop_in_place(slot) };\n+ return Err(err);\n+ }\n+\n+ this.backing.registered.store(true, Ordering::Release);\n+ Ok(())\n+ };\n+\n+ // SAFETY:\n+ // - On success, the closure writes a fully-initialized `Self` into `slot` before making\n+ // the miscdevice visible via `misc_register()`. All state observable through the safe\n+ // open context is initialized before publication.\n+ // - On failure after the write, it drops the initialized value before returning.\n+ unsafe { pin_init::pin_init_from_closure(init) }\n+ }\n+\n+ /// Returns the registration wrapper for a raw `struct miscdevice` pointer.\n+ ///\n+ /// # Safety\n+ ///\n+ /// `misc` must point at the `misc` field of a live [`RegistrationBacking<T>`].\n+ unsafe fn from_raw_misc<'a>(misc: *mut bindings::miscdevice) -> &'a Self {\n+ // SAFETY: The caller guarantees that `misc` points at the `misc` field of a live\n+ // `RegistrationBacking<T>`, whose `owner` points back to the live registration wrapper.\n+ let backing =\n+ unsafe { &*container_of!(Opaque::cast_from(misc), RegistrationBacking<T>, misc) };\n+ // SAFETY: By the type invariant, `owner` points at the live wrapper that owns `backing`.\n+ unsafe { &*backing.owner }\n }\n \n /// Returns a raw pointer to the misc device.\n pub fn as_raw(&self) -> *mut bindings::miscdevice {\n- self.inner.get()\n+ self.backing.misc.get()\n+ }\n+\n+ /// Returns the registration data that was supplied at registration time.\n+ pub fn data(&self) -> &T::RegistrationData {\n+ &self.backing.data\n+ }\n+\n+ fn deregister_inner(backing: &RegistrationBacking<T>) {\n+ if backing.registered.swap(false, Ordering::AcqRel) {\n+ // SAFETY: `registered == true` guarantees that the miscdevice was successfully\n+ // registered and has not been deregistered yet.\n+ unsafe { bindings::misc_deregister(backing.misc.get()) };\n+ }\n }\n \n- /// Access the `this_device` field.\n- pub fn device(&self) -> &Device {\n- // SAFETY: This can only be called after a successful register(), which always\n- // initialises `this_device` with a valid device. Furthermore, the signature of this\n- // function tells the borrow-checker that the `&Device` reference must not outlive the\n- // `&MiscDeviceRegistration<T>` used to obtain it, so the last use of the reference must be\n- // before the underlying `struct miscdevice` is destroyed.\n- unsafe { Device::from_raw((*self.as_raw()).this_device) }\n+ /// Deregister this misc device if it is still registered.\n+ ///\n+ /// After this returns, the misc core will no longer route new opens to [`MiscDevice::open`].\n+ /// Existing open files keep their own pinned `file_operations` table and private data and must\n+ /// be drained by the driver before it tears down device-side resources that those file handles\n+ /// still own.\n+ pub fn deregister(&self) {\n+ Self::deregister_inner(&self.backing);\n }\n }\n \n #[pinned_drop]\n-impl<T> PinnedDrop for MiscDeviceRegistration<T> {\n+impl<T: MiscDevice + 'static> PinnedDrop for MiscDeviceRegistration<T> {\n fn drop(self: Pin<&mut Self>) {\n- // SAFETY: We know that the device is registered by the type invariants.\n- unsafe { bindings::misc_deregister(self.inner.get()) };\n+ let this = self.project();\n+ Self::deregister_inner(this.backing);\n+ }\n+}\n+\n+/// Publication-safe context passed to [`MiscDevice::open`].\n+pub struct MiscDeviceOpenContext<'a, T: MiscDevice + 'static> {\n+ registration: &'a MiscDeviceRegistration<T>,\n+ device: ARef<Device>,\n+}\n+\n+impl<'a, T: MiscDevice + 'static> MiscDeviceOpenContext<'a, T> {\n+ /// Returns the registration data supplied at registration time.\n+ pub fn data(&self) -> &T::RegistrationData {\n+ self.registration.data()\n+ }\n+\n+ /// Returns the class device backing this miscdevice open.\n+ pub fn device(&self) -> ARef<Device> {\n+ self.device.clone()\n }\n }\n \n+/// Internal trait that supplies the concrete `file_operations` table used for a Rust miscdevice.\n+///\n+/// # Safety\n+///\n+/// Implementations must return a stable `file_operations` table produced by this abstraction so\n+/// that owner/module pinning and the private-data protocol remain intact. Drivers should use\n+/// [`declare_misc_device_fops!`] instead of implementing this trait manually.\n+#[doc(hidden)]\n+pub unsafe trait MiscDeviceVTable: MiscDevice + 'static {\n+ /// Returns the `file_operations` table for this miscdevice implementation.\n+ fn file_operations() -> &'static bindings::file_operations;\n+}\n+\n /// Trait implemented by the private data of an open misc device.\n #[vtable]\n pub trait MiscDevice: Sized {\n /// What kind of pointer should `Self` be wrapped in.\n type Ptr: ForeignOwnable + Send + Sync;\n \n+ /// Driver-defined data stored in the miscdevice registration.\n+ type RegistrationData: Send + Sync + 'static;\n+\n /// Called when the misc device is opened.\n ///\n /// The returned pointer will be stored as the private data for the file.\n- fn open(_file: &File, _misc: &MiscDeviceRegistration<Self>) -> Result<Self::Ptr>;\n+ fn open(_file: &File, _ctx: &MiscDeviceOpenContext<'_, Self>) -> Result<Self::Ptr>;\n \n /// Called when the misc device is released.\n fn release(device: Self::Ptr, _file: &File) {\n@@ -195,7 +366,45 @@ fn show_fdinfo(\n /// A vtable for the file operations of a Rust miscdevice.\n struct MiscdeviceVTable<T: MiscDevice>(PhantomData<T>);\n \n-impl<T: MiscDevice> MiscdeviceVTable<T> {\n+impl<T: MiscDevice + 'static> MiscdeviceVTable<T> {\n+ const fn build(owner: *mut bindings::module) -> bindings::file_operations {\n+ bindings::file_operations {\n+ owner,\n+ open: Some(Self::open),\n+ release: Some(Self::release),\n+ mmap: if T::HAS_MMAP { Some(Self::mmap) } else { None },\n+ read_iter: if T::HAS_READ_ITER {\n+ Some(Self::read_iter)\n+ } else {\n+ None\n+ },\n+ write_iter: if T::HAS_WRITE_ITER {\n+ Some(Self::write_iter)\n+ } else {\n+ None\n+ },\n+ unlocked_ioctl: if T::HAS_IOCTL {\n+ Some(Self::ioctl)\n+ } else {\n+ None\n+ },\n+ #[cfg(CONFIG_COMPAT)]\n+ compat_ioctl: if T::HAS_COMPAT_IOCTL {\n+ Some(Self::compat_ioctl)\n+ } else if T::HAS_IOCTL {\n+ bindings::compat_ptr_ioctl\n+ } else {\n+ None\n+ },\n+ show_fdinfo: if T::HAS_SHOW_FDINFO {\n+ Some(Self::show_fdinfo)\n+ } else {\n+ None\n+ },\n+ ..pin_init::zeroed()\n+ }\n+ }\n+\n /// # Safety\n ///\n /// `file` and `inode` must be the file and inode for a file that is undergoing initialization.\n@@ -214,25 +423,38 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n // associated `struct miscdevice` before calling into this method. Furthermore,\n // `misc_open()` ensures that the miscdevice can't be unregistered and freed during this\n // call to `fops_open`.\n- let misc = unsafe { &*misc_ptr.cast::<MiscDeviceRegistration<T>>() };\n+ let misc = unsafe { MiscDeviceRegistration::<T>::from_raw_misc(misc_ptr.cast()) };\n \n // SAFETY:\n // * This underlying file is valid for (much longer than) the duration of `T::open`.\n // * There is no active fdget_pos region on the file on this thread.\n let file = unsafe { File::from_raw_file(raw_file) };\n \n- let ptr = match T::open(file, misc) {\n+ // SAFETY: `misc_open()` serializes with `misc_deregister()` via `misc_mtx`, so the class\n+ // device remains live for the duration of this callback. Taking an extra reference here\n+ // lets the safe open context own the device independently from later teardown.\n+ let ctx = MiscDeviceOpenContext {\n+ registration: misc,\n+ device: unsafe { Device::get_device((*misc.as_raw()).this_device) },\n+ };\n+\n+ let ptr = match T::open(file, &ctx) {\n Ok(ptr) => ptr,\n Err(err) => return err.to_errno(),\n };\n+ let ptr = ScopeGuard::new_with_data(ptr, |ptr| T::release(ptr, file));\n \n- // This overwrites the private data with the value specified by the user, changing the type\n- // of this file's private data. All future accesses to the private data is performed by\n- // other fops_* methods in this file, which all correctly cast the private data to the new\n- // type.\n+ let mut open_file = match KBox::new(OpenFile::<T>::empty(), GFP_KERNEL) {\n+ Ok(open_file) => open_file,\n+ Err(_) => return ENOMEM.to_errno(),\n+ };\n+ open_file.data = ptr.dismiss().into_foreign();\n+\n+ // This overwrites the private data with a small Rust-owned wrapper that keeps the module\n+ // pinned for the full file lifetime and owns the driver's foreign private data handle.\n //\n // SAFETY: The open call of a file can access the private data.\n- unsafe { (*raw_file).private_data = ptr.into_foreign() };\n+ unsafe { (*raw_file).private_data = open_file.into_foreign() };\n \n 0\n }\n@@ -243,14 +465,17 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n /// must be associated with a `MiscDeviceRegistration<T>`.\n unsafe extern \"C\" fn release(_inode: *mut bindings::inode, file: *mut bindings::file) -> c_int {\n // SAFETY: The release call of a file owns the private data.\n- let private = unsafe { (*file).private_data };\n- // SAFETY: The release call of a file owns the private data.\n- let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) };\n+ let open_file =\n+ unsafe { <KBox<OpenFile<T>> as ForeignOwnable>::from_foreign((*file).private_data) };\n+ let data = open_file.data;\n \n // SAFETY:\n // * The file is valid for the duration of this call.\n // * There is no active fdget_pos region on the file on this thread.\n- T::release(ptr, unsafe { File::from_raw_file(file) });\n+ T::release(\n+ unsafe { <T::Ptr as ForeignOwnable>::from_foreign(data) },\n+ unsafe { File::from_raw_file(file) },\n+ );\n \n 0\n }\n@@ -304,11 +529,9 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n vma: *mut bindings::vm_area_struct,\n ) -> c_int {\n // SAFETY: The mmap call of a file can access the private data.\n- let private = unsafe { (*file).private_data };\n- // SAFETY: This is a Rust Miscdevice, so we call `into_foreign` in `open` and\n- // `from_foreign` in `release`, and `fops_mmap` is guaranteed to be called between those\n- // two operations.\n- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private.cast()) };\n+ let open_file =\n+ unsafe { <KBox<OpenFile<T>> as ForeignOwnable>::borrow((*file).private_data) };\n+ let device = open_file.borrow();\n // SAFETY: The caller provides a vma that is undergoing initial VMA setup.\n let area = unsafe { VmaNew::from_raw(vma) };\n // SAFETY:\n@@ -327,9 +550,9 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`.\n unsafe extern \"C\" fn ioctl(file: *mut bindings::file, cmd: c_uint, arg: c_ulong) -> c_long {\n // SAFETY: The ioctl call of a file can access the private data.\n- let private = unsafe { (*file).private_data };\n- // SAFETY: Ioctl calls can borrow the private data of the file.\n- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };\n+ let open_file =\n+ unsafe { <KBox<OpenFile<T>> as ForeignOwnable>::borrow((*file).private_data) };\n+ let device = open_file.borrow();\n \n // SAFETY:\n // * The file is valid for the duration of this call.\n@@ -352,9 +575,9 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n arg: c_ulong,\n ) -> c_long {\n // SAFETY: The compat ioctl call of a file can access the private data.\n- let private = unsafe { (*file).private_data };\n- // SAFETY: Ioctl calls can borrow the private data of the file.\n- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };\n+ let open_file =\n+ unsafe { <KBox<OpenFile<T>> as ForeignOwnable>::borrow((*file).private_data) };\n+ let device = open_file.borrow();\n \n // SAFETY:\n // * The file is valid for the duration of this call.\n@@ -373,9 +596,9 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n /// - `seq_file` must be a valid `struct seq_file` that we can write to.\n unsafe extern \"C\" fn show_fdinfo(seq_file: *mut bindings::seq_file, file: *mut bindings::file) {\n // SAFETY: The release call of a file owns the private data.\n- let private = unsafe { (*file).private_data };\n- // SAFETY: Ioctl calls can borrow the private data of the file.\n- let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };\n+ let open_file =\n+ unsafe { <KBox<OpenFile<T>> as ForeignOwnable>::borrow((*file).private_data) };\n+ let device = open_file.borrow();\n // SAFETY:\n // * The file is valid for the duration of this call.\n // * There is no active fdget_pos region on the file on this thread.\n@@ -386,43 +609,11 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {\n \n T::show_fdinfo(device, m, file);\n }\n+}\n \n- const VTABLE: bindings::file_operations = bindings::file_operations {\n- open: Some(Self::open),\n- release: Some(Self::release),\n- mmap: if T::HAS_MMAP { Some(Self::mmap) } else { None },\n- read_iter: if T::HAS_READ_ITER {\n- Some(Self::read_iter)\n- } else {\n- None\n- },\n- write_iter: if T::HAS_WRITE_ITER {\n- Some(Self::write_iter)\n- } else {\n- None\n- },\n- unlocked_ioctl: if T::HAS_IOCTL {\n- Some(Self::ioctl)\n- } else {\n- None\n- },\n- #[cfg(CONFIG_COMPAT)]\n- compat_ioctl: if T::HAS_COMPAT_IOCTL {\n- Some(Self::compat_ioctl)\n- } else if T::HAS_IOCTL {\n- bindings::compat_ptr_ioctl\n- } else {\n- None\n- },\n- show_fdinfo: if T::HAS_SHOW_FDINFO {\n- Some(Self::show_fdinfo)\n- } else {\n- None\n- },\n- ..pin_init::zeroed()\n- };\n-\n- const fn build() -> &'static bindings::file_operations {\n- &Self::VTABLE\n- }\n+#[doc(hidden)]\n+pub const fn build_file_operations<T: MiscDevice + 'static>(\n+ owner: *mut bindings::module,\n+) -> bindings::file_operations {\n+ MiscdeviceVTable::<T>::build(owner)\n }\ndiff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs\nindex 87a1fe63533a..f2d7a98a5715 100644\n--- a/samples/rust/rust_misc_device.rs\n+++ b/samples/rust/rust_misc_device.rs\n@@ -100,7 +100,7 @@\n fs::{File, Kiocb},\n ioctl::{_IO, _IOC_SIZE, _IOR, _IOW},\n iov::{IovIterDest, IovIterSource},\n- miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},\n+ miscdevice::{MiscDevice, MiscDeviceOpenContext, MiscDeviceOptions, MiscDeviceRegistration},\n new_mutex,\n prelude::*,\n sync::{aref::ARef, Mutex},\n@@ -154,9 +154,10 @@ struct RustMiscDevice {\n #[vtable]\n impl MiscDevice for RustMiscDevice {\n type Ptr = Pin<KBox<Self>>;\n+ type RegistrationData = ();\n \n- fn open(_file: &File, misc: &MiscDeviceRegistration<Self>) -> Result<Pin<KBox<Self>>> {\n- let dev = ARef::from(misc.device());\n+ fn open(_file: &File, ctx: &MiscDeviceOpenContext<'_, Self>) -> Result<Pin<KBox<Self>>> {\n+ let dev = ctx.device();\n \n dev_info!(dev, \"Opening Rust Misc Device Sample\\n\");\n \n@@ -222,6 +223,8 @@ fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result\n }\n }\n \n+kernel::declare_misc_device_fops!(RustMiscDevice);\n+\n #[pinned_drop]\n impl PinnedDrop for RustMiscDevice {\n fn drop(self: Pin<&mut Self>) {\n", "prefixes": [ "RFC", "v3", "5/6" ] }