get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.1/patches/2220257/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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"
    ]
}