get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 2220327,
    "url": "http://patchwork.ozlabs.org/api/1.1/patches/2220327/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/linux-pci/patch/20260406165120.166928-7-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-7-wenzhaoliao@ruc.edu.cn>",
    "date": "2026-04-06T16:51:20",
    "name": "[RFC,v3,6/6] platform/goldfish: add Rust goldfish_address_space driver",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "df934e4ba0801f67a67ffca775e08e3b361d2bfb",
    "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-7-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/2220327/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2220327/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "\n <linux-pci+bounces-51980-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=XBBJMEfR;\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-51980-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=\"XBBJMEfR\"",
            "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=47.88.81.141",
            "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 4fqPKt6rQzz1yGM\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 07 Apr 2026 08:35:30 +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 1DDDE3040220\n\tfor <incoming@patchwork.ozlabs.org>; Mon,  6 Apr 2026 22:35:03 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id AEB9839DBC0;\n\tMon,  6 Apr 2026 22:35:01 +0000 (UTC)",
            "from mail-m81141.netease.com (mail-m81141.netease.com\n [47.88.81.141])\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 10C862DCF55;\n\tMon,  6 Apr 2026 22:34:52 +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 39aaefbe1;\n\tTue, 7 Apr 2026 00:51:46 +0800 (GMT+08:00)"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775514901; cv=none;\n b=cpb2YSDDafKd4zAzlKtRN+0+VXO6zaOucmfAAnu911v/wAul8ER7FdRIGHDqyKDELuQEVNVF+uqF7+6rKNFf7yGcfuOwiAyWEfEAVBm4lk/7E1NL1gBhxjgIW7YqRWbN4ONIqcLmNFLITZq21YCkZO8LkuqzbxUcs0xqwAHT0TQ=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775514901; c=relaxed/simple;\n\tbh=dRHAE3I/SWqaGNIaJvkyVijNPIt2j+9ekFM0wXRPM/M=;\n\th=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:\n\t MIME-Version;\n b=gqWIHRqq5luBN+jT9js04syEZ7aYfFJ5XctDNc5bdfu8yDakCUUVpaU7GPo9c5KzPKplGHgh+HO5Tvw3k8fLy62AbVvbG9zC0lWc/+zf5X/rBNyVwWvR9LggdDLcB6PVveobztLkH/hRisG+aXPcfnItUzgZN/LrLPboESkaCtU=",
        "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=XBBJMEfR; arc=none smtp.client-ip=47.88.81.141",
        "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 6/6] platform/goldfish: add Rust goldfish_address_space\n driver",
        "Date": "Mon,  6 Apr 2026 12:51:20 -0400",
        "Message-Id": "<20260406165120.166928-7-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": "0a9d63b51ca203a2kunm562d3b0d647e93",
        "X-HM-MType": "10",
        "X-HM-Spam-Status": "e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly\n\ttZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVlCHhpLVk9CTBkYHkMYQxhLH1YeHw5VEwETFhoSFy\n\tQUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0\n\ttZBg++",
        "DKIM-Signature": "a=rsa-sha256;\n\tb=XBBJMEfRtlBC9iBF8DurdyoNTlCN1ZcrjeNulVRYg+NL3Ai5nwGU2kBWCXkUXHYfzKyJ8LGvc295BphjGd938chplOh95R5+ipVjHScfIzRYr+Xvv/mKgXam/sm27HuSGs1XyKxTzdKV8niGE3NMFaxSsjdPzAbxBGtmzTrl7vk=;\n c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1;\n\tbh=ED5Mu0ieMlJgd4INk+b0oEYvkKv8bjy1kvcUcApERQs=;\n\th=date:mime-version:subject:message-id:from;"
    },
    "content": "Add a Rust implementation of the goldfish address-space driver and wire\nit into drivers/platform/goldfish.\n\nThis RFC intentionally scopes the driver to the open/release/ioctl ABI\nsubset; userspace mmap is not part of this series. The driver keeps all\nunsafe and bindings-facing work inside the Rust abstraction layers,\ncarries #![forbid(unsafe_code)] in the driver crate, and uses typed\nmiscdevice registration data plus SharedMemoryBar to stay on the safe\nside of those abstractions.\n\nOn teardown, unbind first deregisters the miscdevice, then drains\nalready-running operations and revokes live file-owned device state\nbefore disabling the PCI function. Probe also pairs\nenable_device_mem() with a ScopeGuard so mid-probe failures cannot leak\nan enabled device.\n\nSigned-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>\n---\n MAINTAINERS                                   |   2 +\n drivers/platform/goldfish/Kconfig             |  11 +\n drivers/platform/goldfish/Makefile            |   1 +\n .../goldfish/goldfish_address_space.rs        | 917 ++++++++++++++++++\n 4 files changed, 931 insertions(+)\n create mode 100644 drivers/platform/goldfish/goldfish_address_space.rs",
    "diff": "diff --git a/MAINTAINERS b/MAINTAINERS\nindex 800b2fe0e648..0a9193854f1b 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -1888,7 +1888,9 @@ L:\tlinux-kernel@vger.kernel.org\n L:\tlinux-pci@vger.kernel.org\n L:\trust-for-linux@vger.kernel.org\n S:\tMaintained\n+F:\tdrivers/platform/goldfish/goldfish_address_space.rs\n F:\tinclude/uapi/linux/goldfish_address_space.h\n+K:\t\\bGOLDFISH_ADDRESS_SPACE\\b\n \n ANDROID GOLDFISH RTC DRIVER\n M:\tJiaxun Yang <jiaxun.yang@flygoat.com>\ndiff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig\nindex 03ca5bf19f98..58ccf5a757bd 100644\n--- a/drivers/platform/goldfish/Kconfig\n+++ b/drivers/platform/goldfish/Kconfig\n@@ -17,4 +17,15 @@ config GOLDFISH_PIPE\n \t  This is a virtual device to drive the QEMU pipe interface used by\n \t  the Goldfish Android Virtual Device.\n \n+config GOLDFISH_ADDRESS_SPACE\n+\ttristate \"Goldfish address space driver in Rust\"\n+\tdepends on PCI && RUST && MMU\n+\thelp\n+\t  Adds a Rust implementation of the Goldfish address space driver\n+\t  used by the Android Goldfish emulator.\n+\n+\t  This implementation uses typed Rust abstractions for PCI resource\n+\t  setup, miscdevice registration, page-backed ping state, and the\n+\t  userspace ioctl interface.\n+\n endif # GOLDFISH\ndiff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile\nindex 76ba1d571896..17f67c223e95 100644\n--- a/drivers/platform/goldfish/Makefile\n+++ b/drivers/platform/goldfish/Makefile\n@@ -3,3 +3,4 @@\n # Makefile for Goldfish platform specific drivers\n #\n obj-$(CONFIG_GOLDFISH_PIPE)\t+= goldfish_pipe.o\n+obj-$(CONFIG_GOLDFISH_ADDRESS_SPACE) += goldfish_address_space.o\ndiff --git a/drivers/platform/goldfish/goldfish_address_space.rs b/drivers/platform/goldfish/goldfish_address_space.rs\nnew file mode 100644\nindex 000000000000..7742c76ea7fc\n--- /dev/null\n+++ b/drivers/platform/goldfish/goldfish_address_space.rs\n@@ -0,0 +1,917 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+//! Rust Goldfish address space driver.\n+\n+#![forbid(unsafe_code)]\n+\n+use core::{mem::size_of, pin::Pin};\n+use kernel::{\n+    alloc::KVVec,\n+    device::Core,\n+    devres::Devres,\n+    error::Error,\n+    fs::File,\n+    io::{Io, PhysAddr},\n+    ioctl,\n+    miscdevice::{MiscDevice, MiscDeviceOpenContext, MiscDeviceOptions, MiscDeviceRegistration},\n+    new_condvar, new_mutex,\n+    page::{Page, PAGE_SIZE},\n+    pci,\n+    prelude::*,\n+    sync::{Arc, ArcBorrow, CondVar, Mutex},\n+    types::ScopeGuard,\n+    uaccess::{UserPtr, UserSlice},\n+    uapi,\n+};\n+\n+const GOLDFISH_AS_CONTROL_BAR: u32 = 0;\n+const GOLDFISH_AS_AREA_BAR: u32 = 1;\n+const GOLDFISH_AS_VENDOR_ID: u32 = 0x607d;\n+const GOLDFISH_AS_DEVICE_ID: u32 = 0xf153;\n+const GOLDFISH_AS_SUPPORTED_REVISION: u8 = 1;\n+const GOLDFISH_AS_INVALID_HANDLE: u32 = u32::MAX;\n+\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC: u32 = uapi::GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC as u32;\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK: u32 =\n+    ioctl::_IOWR::<AllocateBlockIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 10);\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK: u32 =\n+    ioctl::_IOWR::<u64>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 11);\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_PING: u32 =\n+    ioctl::_IOWR::<PingIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 12);\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED: u32 =\n+    ioctl::_IOWR::<ClaimSharedIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 13);\n+const GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED: u32 =\n+    ioctl::_IOWR::<u64>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 14);\n+\n+struct Registers;\n+\n+impl Registers {\n+    const COMMAND: usize = 0;\n+    const STATUS: usize = 4;\n+    const GUEST_PAGE_SIZE: usize = 8;\n+    const BLOCK_SIZE_LOW: usize = 12;\n+    const BLOCK_SIZE_HIGH: usize = 16;\n+    const BLOCK_OFFSET_LOW: usize = 20;\n+    const BLOCK_OFFSET_HIGH: usize = 24;\n+    const PING: usize = 28;\n+    const PING_INFO_ADDR_LOW: usize = 32;\n+    const PING_INFO_ADDR_HIGH: usize = 36;\n+    const HANDLE: usize = 40;\n+    const PHYS_START_LOW: usize = 44;\n+    const PHYS_START_HIGH: usize = 48;\n+    const END: usize = 56;\n+}\n+\n+#[repr(u32)]\n+#[derive(Clone, Copy)]\n+enum CommandId {\n+    AllocateBlock = 1,\n+    DeallocateBlock = 2,\n+    GenHandle = 3,\n+    DestroyHandle = 4,\n+    TellPingInfoAddr = 5,\n+}\n+\n+type ControlBar = pci::Bar<{ Registers::END }>;\n+\n+#[derive(Clone, Copy)]\n+struct Block {\n+    offset: u64,\n+    size: u64,\n+}\n+\n+struct BlockSet {\n+    blocks: KVVec<Block>,\n+}\n+\n+impl BlockSet {\n+    fn new() -> Self {\n+        Self {\n+            blocks: KVVec::new(),\n+        }\n+    }\n+\n+    fn insert(&mut self, block: Block) -> Result {\n+        self.blocks.push(block, GFP_KERNEL)?;\n+        Ok(())\n+    }\n+\n+    fn remove(&mut self, offset: u64) -> Result<Block> {\n+        let index = self\n+            .blocks\n+            .iter()\n+            .position(|block| block.offset == offset)\n+            .ok_or(ENXIO)?;\n+        self.blocks.remove(index).map_err(|_| EINVAL)\n+    }\n+\n+    fn iter(&self) -> impl Iterator<Item = Block> + '_ {\n+        self.blocks.iter().copied()\n+    }\n+\n+    fn clear(&mut self) {\n+        let _ = self.take_all();\n+    }\n+\n+    fn take_all(&mut self) -> KVVec<Block> {\n+        let mut blocks = KVVec::new();\n+        core::mem::swap(&mut blocks, &mut self.blocks);\n+        blocks\n+    }\n+}\n+\n+#[derive(Clone, Copy, Default)]\n+struct PingInfoHeader {\n+    offset: u64,\n+    size: u64,\n+    metadata: u64,\n+    version: u32,\n+    wait_fd: u32,\n+    wait_flags: u32,\n+    direction: u32,\n+    data_size: u64,\n+}\n+\n+impl PingInfoHeader {\n+    const ENCODED_LEN: usize = 48;\n+\n+    fn encode(self) -> [u8; Self::ENCODED_LEN] {\n+        let mut bytes = [0u8; Self::ENCODED_LEN];\n+\n+        bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());\n+        bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());\n+        bytes[16..24].copy_from_slice(&self.metadata.to_ne_bytes());\n+        bytes[24..28].copy_from_slice(&self.version.to_ne_bytes());\n+        bytes[28..32].copy_from_slice(&self.wait_fd.to_ne_bytes());\n+        bytes[32..36].copy_from_slice(&self.wait_flags.to_ne_bytes());\n+        bytes[36..40].copy_from_slice(&self.direction.to_ne_bytes());\n+        bytes[40..48].copy_from_slice(&self.data_size.to_ne_bytes());\n+\n+        bytes\n+    }\n+\n+    fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {\n+        Self {\n+            offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),\n+            size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),\n+            metadata: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),\n+            version: u32::from_ne_bytes(bytes[24..28].try_into().unwrap()),\n+            wait_fd: u32::from_ne_bytes(bytes[28..32].try_into().unwrap()),\n+            wait_flags: u32::from_ne_bytes(bytes[32..36].try_into().unwrap()),\n+            direction: u32::from_ne_bytes(bytes[36..40].try_into().unwrap()),\n+            data_size: u64::from_ne_bytes(bytes[40..48].try_into().unwrap()),\n+        }\n+    }\n+}\n+\n+#[repr(C)]\n+#[derive(Clone, Copy, Default)]\n+struct AllocateBlockIoctl {\n+    size: u64,\n+    offset: u64,\n+    phys_addr: u64,\n+}\n+\n+impl AllocateBlockIoctl {\n+    const ENCODED_LEN: usize = 24;\n+\n+    fn encode(self) -> [u8; Self::ENCODED_LEN] {\n+        let mut bytes = [0u8; Self::ENCODED_LEN];\n+        bytes[0..8].copy_from_slice(&self.size.to_ne_bytes());\n+        bytes[8..16].copy_from_slice(&self.offset.to_ne_bytes());\n+        bytes[16..24].copy_from_slice(&self.phys_addr.to_ne_bytes());\n+        bytes\n+    }\n+\n+    fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {\n+        Self {\n+            size: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),\n+            offset: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),\n+            phys_addr: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),\n+        }\n+    }\n+}\n+\n+#[repr(C)]\n+#[derive(Clone, Copy, Default)]\n+struct PingIoctl {\n+    offset: u64,\n+    size: u64,\n+    metadata: u64,\n+    version: u32,\n+    wait_fd: u32,\n+    wait_flags: u32,\n+    direction: u32,\n+}\n+\n+impl PingIoctl {\n+    const ENCODED_LEN: usize = 40;\n+\n+    fn encode(self) -> [u8; Self::ENCODED_LEN] {\n+        let mut bytes = [0u8; Self::ENCODED_LEN];\n+        bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());\n+        bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());\n+        bytes[16..24].copy_from_slice(&self.metadata.to_ne_bytes());\n+        bytes[24..28].copy_from_slice(&self.version.to_ne_bytes());\n+        bytes[28..32].copy_from_slice(&self.wait_fd.to_ne_bytes());\n+        bytes[32..36].copy_from_slice(&self.wait_flags.to_ne_bytes());\n+        bytes[36..40].copy_from_slice(&self.direction.to_ne_bytes());\n+        bytes\n+    }\n+\n+    fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {\n+        Self {\n+            offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),\n+            size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),\n+            metadata: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),\n+            version: u32::from_ne_bytes(bytes[24..28].try_into().unwrap()),\n+            wait_fd: u32::from_ne_bytes(bytes[28..32].try_into().unwrap()),\n+            wait_flags: u32::from_ne_bytes(bytes[32..36].try_into().unwrap()),\n+            direction: u32::from_ne_bytes(bytes[36..40].try_into().unwrap()),\n+        }\n+    }\n+}\n+\n+#[repr(C)]\n+#[derive(Clone, Copy, Default)]\n+struct ClaimSharedIoctl {\n+    offset: u64,\n+    size: u64,\n+}\n+\n+impl ClaimSharedIoctl {\n+    const ENCODED_LEN: usize = 16;\n+\n+    fn encode(self) -> [u8; Self::ENCODED_LEN] {\n+        let mut bytes = [0u8; Self::ENCODED_LEN];\n+        bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());\n+        bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());\n+        bytes\n+    }\n+\n+    fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {\n+        Self {\n+            offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),\n+            size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),\n+        }\n+    }\n+}\n+\n+struct PingState {\n+    page: Page,\n+}\n+\n+impl PingState {\n+    fn new() -> Result<Self> {\n+        let mut page = Page::alloc_page(GFP_KERNEL)?;\n+        page.fill_zero(0, PAGE_SIZE)?;\n+        Ok(Self { page })\n+    }\n+\n+    fn phys_addr(&self) -> PhysAddr {\n+        self.page.phys_addr()\n+    }\n+\n+    fn shared_offset(offset: u64, shared_phys_start: PhysAddr) -> Result<u64> {\n+        let shared_phys_start = u64::try_from(shared_phys_start).map_err(|_| EOVERFLOW)?;\n+        offset.checked_add(shared_phys_start).ok_or(EOVERFLOW)\n+    }\n+\n+    fn prepare_ping(&mut self, request: &PingIoctl, shared_phys_start: PhysAddr) -> Result {\n+        let header = PingInfoHeader {\n+            offset: Self::shared_offset(request.offset, shared_phys_start)?,\n+            size: request.size,\n+            metadata: request.metadata,\n+            version: request.version,\n+            wait_fd: request.wait_fd,\n+            wait_flags: request.wait_flags,\n+            direction: request.direction,\n+            data_size: 0,\n+        };\n+\n+        self.page.fill_zero(0, PAGE_SIZE)?;\n+        self.page.write_slice(&header.encode(), 0)\n+    }\n+\n+    fn finish_ping(&self, request: &mut PingIoctl) -> Result {\n+        let mut bytes = [0u8; PingInfoHeader::ENCODED_LEN];\n+        self.page.read_slice(&mut bytes, 0)?;\n+        let header = PingInfoHeader::decode(&bytes);\n+        request.offset = header.offset;\n+        request.size = header.size;\n+        request.metadata = header.metadata;\n+        request.version = header.version;\n+        request.wait_fd = header.wait_fd;\n+        request.wait_flags = header.wait_flags;\n+        request.direction = header.direction;\n+        Ok(())\n+    }\n+}\n+\n+#[pin_data]\n+struct DeviceRuntime {\n+    #[pin]\n+    control_bar: Devres<ControlBar>,\n+    #[pin]\n+    shared_bar: Devres<pci::SharedMemoryBar>,\n+    #[pin]\n+    registers_lock: Mutex<()>,\n+    #[pin]\n+    lifecycle: Mutex<RuntimeLifecycleState>,\n+    #[pin]\n+    lifecycle_idle: CondVar,\n+}\n+\n+struct RuntimeLifecycleState {\n+    accepting_new_ops: bool,\n+    active_ops: usize,\n+    live_files: KVVec<Arc<FileState>>,\n+}\n+\n+struct RuntimeOpGuard {\n+    runtime: Arc<DeviceRuntime>,\n+}\n+\n+impl Drop for RuntimeOpGuard {\n+    fn drop(&mut self) {\n+        let mut state = self.runtime.lifecycle.lock();\n+        state.active_ops -= 1;\n+        self.runtime.notify_if_idle(&state);\n+    }\n+}\n+\n+impl DeviceRuntime {\n+    fn new(pdev: &pci::Device<Core>) -> Result<Arc<Self>> {\n+        Arc::pin_init(\n+            try_pin_init!(Self {\n+                control_bar <- pdev.iomap_region_sized::<{ Registers::END }>(\n+                    GOLDFISH_AS_CONTROL_BAR,\n+                    c\"goldfish_address_space/control\",\n+                ),\n+                shared_bar <- pdev.memremap_bar(\n+                    GOLDFISH_AS_AREA_BAR,\n+                    c\"goldfish_address_space/area\",\n+                ),\n+                registers_lock <- new_mutex!(()),\n+                lifecycle <- new_mutex!(RuntimeLifecycleState {\n+                    accepting_new_ops: true,\n+                    active_ops: 0,\n+                    live_files: KVVec::new(),\n+                }),\n+                lifecycle_idle <- new_condvar!(\"goldfish_address_space/lifecycle_idle\"),\n+            }),\n+            GFP_KERNEL,\n+        )\n+    }\n+\n+    fn notify_if_idle(&self, state: &RuntimeLifecycleState) {\n+        if !state.accepting_new_ops && state.active_ops == 0 {\n+            self.lifecycle_idle.notify_all();\n+        }\n+    }\n+\n+    fn begin_operation(self: &Arc<Self>) -> Result<RuntimeOpGuard> {\n+        let mut state = self.lifecycle.lock();\n+        if !state.accepting_new_ops {\n+            return Err(ENODEV);\n+        }\n+\n+        state.active_ops = state.active_ops.checked_add(1).ok_or(EBUSY)?;\n+        drop(state);\n+\n+        Ok(RuntimeOpGuard {\n+            runtime: self.clone(),\n+        })\n+    }\n+\n+    fn register_live_file(&self, file: Arc<FileState>) -> Result {\n+        let mut state = self.lifecycle.lock();\n+        if !state.accepting_new_ops {\n+            return Err(ENODEV);\n+        }\n+\n+        state.live_files.push(file, GFP_KERNEL)?;\n+        Ok(())\n+    }\n+\n+    fn unregister_live_file(&self, file: &Arc<FileState>) {\n+        let mut state = self.lifecycle.lock();\n+        let Some(index) = state\n+            .live_files\n+            .iter()\n+            .position(|entry| Arc::ptr_eq(entry, file))\n+        else {\n+            return;\n+        };\n+\n+        if let Ok(entry) = state.live_files.remove(index) {\n+            drop(entry);\n+        }\n+    }\n+\n+    fn shutdown(&self) {\n+        let mut state = self.lifecycle.lock();\n+        // `unbind()` removes miscdevice reachability before calling `shutdown()`. After that we\n+        // only need to wait for already-entered syscalls to finish; live files are revoked below,\n+        // so remove is no longer bounded by userspace deciding to close descriptors.\n+        state.accepting_new_ops = false;\n+\n+        while state.active_ops != 0 {\n+            self.lifecycle_idle.wait(&mut state);\n+        }\n+\n+        let mut live_files = KVVec::new();\n+        core::mem::swap(&mut live_files, &mut state.live_files);\n+        drop(state);\n+\n+        for file in &live_files {\n+            file.revoke_for_shutdown();\n+        }\n+    }\n+\n+    fn control_bar(&self) -> Result<impl core::ops::Deref<Target = ControlBar> + '_> {\n+        self.control_bar.try_access().ok_or(ENXIO)\n+    }\n+\n+    fn shared_bar(&self) -> Result<impl core::ops::Deref<Target = pci::SharedMemoryBar> + '_> {\n+        self.shared_bar.try_access().ok_or(ENXIO)\n+    }\n+\n+    fn run_command_locked(control: &ControlBar, command: CommandId) -> Result {\n+        control.write32(command as u32, Registers::COMMAND);\n+\n+        let status = i32::try_from(control.read32(Registers::STATUS)).map_err(|_| EIO)?;\n+        if status == 0 {\n+            Ok(())\n+        } else {\n+            Err(Error::from_errno(-status))\n+        }\n+    }\n+\n+    fn issue_command_locked(control: &ControlBar, command: CommandId) {\n+        control.write32(command as u32, Registers::COMMAND);\n+    }\n+\n+    fn write_u64(control: &ControlBar, low_offset: usize, high_offset: usize, value: u64) {\n+        control.write32(value as u32, low_offset);\n+        control.write32((value >> 32) as u32, high_offset);\n+    }\n+\n+    fn read_u64(control: &ControlBar, low_offset: usize, high_offset: usize) -> u64 {\n+        u64::from(control.read32(low_offset)) | (u64::from(control.read32(high_offset)) << 32)\n+    }\n+\n+    fn program_host_visible_state(&self) -> Result {\n+        let control = self.control_bar()?;\n+        let shared = self.shared_bar()?;\n+        let phys_start = u64::try_from(shared.phys_start()).map_err(|_| EOVERFLOW)?;\n+\n+        control.write32(PAGE_SIZE as u32, Registers::GUEST_PAGE_SIZE);\n+        Self::write_u64(\n+            &control,\n+            Registers::PHYS_START_LOW,\n+            Registers::PHYS_START_HIGH,\n+            phys_start,\n+        );\n+\n+        Ok(())\n+    }\n+\n+    fn shared_phys_start(&self) -> Result<PhysAddr> {\n+        Ok(self.shared_bar()?.phys_start())\n+    }\n+\n+    fn generate_handle(&self) -> Result<u32> {\n+        let _guard = self.registers_lock.lock();\n+        let control = self.control_bar()?;\n+\n+        // The external C driver does not gate `GEN_HANDLE` on the status register and instead\n+        // validates completion by reading back the handle.\n+        Self::issue_command_locked(&control, CommandId::GenHandle);\n+\n+        let handle = control.read32(Registers::HANDLE);\n+        if handle == GOLDFISH_AS_INVALID_HANDLE {\n+            return Err(EINVAL);\n+        }\n+\n+        Ok(handle)\n+    }\n+\n+    fn tell_ping_info_addr(&self, handle: u32, ping_info_phys: PhysAddr) -> Result {\n+        let _guard = self.registers_lock.lock();\n+        let control = self.control_bar()?;\n+        let ping_info_phys = u64::try_from(ping_info_phys).map_err(|_| EOVERFLOW)?;\n+\n+        control.write32(handle, Registers::HANDLE);\n+        Self::write_u64(\n+            &control,\n+            Registers::PING_INFO_ADDR_LOW,\n+            Registers::PING_INFO_ADDR_HIGH,\n+            ping_info_phys,\n+        );\n+        // The external C driver validates `TELL_PING_INFO_ADDR` through the echoed physical\n+        // address rather than through the status register.\n+        Self::issue_command_locked(&control, CommandId::TellPingInfoAddr);\n+\n+        let returned = Self::read_u64(\n+            &control,\n+            Registers::PING_INFO_ADDR_LOW,\n+            Registers::PING_INFO_ADDR_HIGH,\n+        );\n+        if returned != ping_info_phys {\n+            return Err(EINVAL);\n+        }\n+\n+        Ok(())\n+    }\n+\n+    fn destroy_handle(&self, handle: u32) -> Result {\n+        let _guard = self.registers_lock.lock();\n+        let control = self.control_bar()?;\n+        control.write32(handle, Registers::HANDLE);\n+        Self::issue_command_locked(&control, CommandId::DestroyHandle);\n+        Ok(())\n+    }\n+\n+    fn allocate_block(&self, size: u64) -> Result<Block> {\n+        let _guard = self.registers_lock.lock();\n+        let control = self.control_bar()?;\n+\n+        Self::write_u64(\n+            &control,\n+            Registers::BLOCK_SIZE_LOW,\n+            Registers::BLOCK_SIZE_HIGH,\n+            size,\n+        );\n+        Self::run_command_locked(&control, CommandId::AllocateBlock)?;\n+\n+        Ok(Block {\n+            offset: Self::read_u64(\n+                &control,\n+                Registers::BLOCK_OFFSET_LOW,\n+                Registers::BLOCK_OFFSET_HIGH,\n+            ),\n+            size: Self::read_u64(\n+                &control,\n+                Registers::BLOCK_SIZE_LOW,\n+                Registers::BLOCK_SIZE_HIGH,\n+            ),\n+        })\n+    }\n+\n+    fn deallocate_block(&self, offset: u64) -> Result {\n+        let _guard = self.registers_lock.lock();\n+        let control = self.control_bar()?;\n+        Self::write_u64(\n+            &control,\n+            Registers::BLOCK_OFFSET_LOW,\n+            Registers::BLOCK_OFFSET_HIGH,\n+            offset,\n+        );\n+        Self::run_command_locked(&control, CommandId::DeallocateBlock)\n+    }\n+\n+    fn ping(&self, handle: u32) -> Result {\n+        let _guard = self.registers_lock.lock();\n+        self.control_bar()?.write32(handle, Registers::PING);\n+        Ok(())\n+    }\n+\n+    fn cleanup_file_resources<I>(&self, handle: u32, blocks: I)\n+    where\n+        I: IntoIterator<Item = Block>,\n+    {\n+        // `unbind()` revokes live files before `disable_device()`, so both the shutdown path and a\n+        // concurrent `release()` may still legitimately touch the BAR here.\n+        if let Err(err) = self.destroy_handle(handle) {\n+            pr_warn!(\n+                \"goldfish_address_space: destroy handle {} failed: {}\\n\",\n+                handle,\n+                err.to_errno()\n+            );\n+        }\n+\n+        for block in blocks {\n+            if let Err(err) = self.deallocate_block(block.offset) {\n+                pr_warn!(\n+                    \"goldfish_address_space: deallocate block 0x{:x} failed: {}\\n\",\n+                    block.offset,\n+                    err.to_errno()\n+                );\n+            }\n+        }\n+    }\n+}\n+\n+struct FileResources {\n+    handle: Option<u32>,\n+    allocated_blocks: BlockSet,\n+    shared_blocks: BlockSet,\n+}\n+\n+impl FileResources {\n+    fn new(handle: u32) -> Self {\n+        Self {\n+            handle: Some(handle),\n+            allocated_blocks: BlockSet::new(),\n+            shared_blocks: BlockSet::new(),\n+        }\n+    }\n+}\n+\n+#[pin_data]\n+struct FileState {\n+    runtime: Arc<DeviceRuntime>,\n+    #[pin]\n+    ping: Mutex<PingState>,\n+    #[pin]\n+    resources: Mutex<FileResources>,\n+}\n+\n+impl FileState {\n+    fn new(runtime: Arc<DeviceRuntime>, handle: u32, ping: PingState) -> Result<Arc<Self>> {\n+        Arc::pin_init(\n+            try_pin_init!(Self {\n+                runtime: runtime,\n+                ping <- new_mutex!(ping),\n+                resources <- new_mutex!(FileResources::new(handle)),\n+            }),\n+            GFP_KERNEL,\n+        )\n+    }\n+\n+    fn shared_phys_addr(&self, offset: u64) -> Result<u64> {\n+        let base = u64::try_from(self.runtime.shared_phys_start()?).map_err(|_| EOVERFLOW)?;\n+        base.checked_add(offset).ok_or(EOVERFLOW)\n+    }\n+\n+    fn allocate_block(\n+        self: ArcBorrow<'_, Self>,\n+        mut request: AllocateBlockIoctl,\n+    ) -> Result<AllocateBlockIoctl> {\n+        let block = self.runtime.allocate_block(request.size)?;\n+        let mut resources = self.resources.lock();\n+        if resources.handle.is_none() {\n+            drop(resources);\n+            let _ = self.runtime.deallocate_block(block.offset);\n+            return Err(ENODEV);\n+        }\n+\n+        if let Err(err) = resources.allocated_blocks.insert(block) {\n+            drop(resources);\n+            let _ = self.runtime.deallocate_block(block.offset);\n+            return Err(err);\n+        }\n+\n+        request.size = block.size;\n+        request.offset = block.offset;\n+        request.phys_addr = self.shared_phys_addr(block.offset)?;\n+        Ok(request)\n+    }\n+\n+    fn deallocate_block(self: ArcBorrow<'_, Self>, offset: u64) -> Result {\n+        let mut resources = self.resources.lock();\n+        if resources.handle.is_none() {\n+            return Err(ENODEV);\n+        }\n+\n+        if !resources\n+            .allocated_blocks\n+            .iter()\n+            .any(|block| block.offset == offset)\n+        {\n+            return Err(ENXIO);\n+        }\n+\n+        self.runtime.deallocate_block(offset)?;\n+        let _ = resources.allocated_blocks.remove(offset)?;\n+        Ok(())\n+    }\n+\n+    fn claim_shared(\n+        self: ArcBorrow<'_, Self>,\n+        request: ClaimSharedIoctl,\n+    ) -> Result<ClaimSharedIoctl> {\n+        let mut resources = self.resources.lock();\n+        if resources.handle.is_none() {\n+            return Err(ENODEV);\n+        }\n+\n+        resources.shared_blocks.insert(Block {\n+            offset: request.offset,\n+            size: request.size,\n+        })?;\n+        Ok(request)\n+    }\n+\n+    fn unclaim_shared(self: ArcBorrow<'_, Self>, offset: u64) -> Result {\n+        let mut resources = self.resources.lock();\n+        if resources.handle.is_none() {\n+            return Err(ENODEV);\n+        }\n+\n+        resources.shared_blocks.remove(offset)?;\n+        Ok(())\n+    }\n+\n+    fn ping(self: ArcBorrow<'_, Self>, mut request: PingIoctl) -> Result<PingIoctl> {\n+        let handle = self.resources.lock().handle.ok_or(ENODEV)?;\n+        let mut ping = self.ping.lock();\n+        ping.prepare_ping(&request, self.runtime.shared_phys_start()?)?;\n+        self.runtime.ping(handle)?;\n+        ping.finish_ping(&mut request)?;\n+        Ok(request)\n+    }\n+\n+    fn cleanup_resources(&self) {\n+        let mut resources = self.resources.lock();\n+        let Some(handle) = resources.handle.take() else {\n+            return;\n+        };\n+\n+        self.runtime\n+            .cleanup_file_resources(handle, resources.allocated_blocks.iter());\n+        resources.allocated_blocks.clear();\n+        resources.shared_blocks.clear();\n+    }\n+\n+    fn revoke_for_shutdown(&self) {\n+        self.cleanup_resources();\n+    }\n+\n+    fn release(self: Arc<Self>) {\n+        self.cleanup_resources();\n+        self.runtime.unregister_live_file(&self);\n+    }\n+}\n+\n+#[pin_data]\n+struct GoldfishAddressSpaceDriver {\n+    runtime: Arc<DeviceRuntime>,\n+    #[pin]\n+    misc: MiscDeviceRegistration<GoldfishAddressSpaceMisc>,\n+}\n+\n+struct GoldfishAddressSpaceMisc;\n+\n+#[vtable]\n+impl MiscDevice for GoldfishAddressSpaceMisc {\n+    type Ptr = Arc<FileState>;\n+    type RegistrationData = Arc<DeviceRuntime>;\n+\n+    fn open(_file: &File, ctx: &MiscDeviceOpenContext<'_, Self>) -> Result<Self::Ptr> {\n+        let runtime = ctx.data().clone();\n+        let _op = runtime.begin_operation()?;\n+        let ping = PingState::new()?;\n+        let handle = runtime.generate_handle()?;\n+        let cleanup = ScopeGuard::new_with_data((runtime.clone(), handle), |(runtime, handle)| {\n+            let _ = runtime.destroy_handle(handle);\n+        });\n+\n+        runtime.tell_ping_info_addr(handle, ping.phys_addr())?;\n+        let state = FileState::new(runtime.clone(), handle, ping)?;\n+        cleanup.dismiss();\n+\n+        // Publish the file as a live shutdown owner before returning it to the miscdevice core.\n+        if let Err(err) = runtime.register_live_file(state.clone()) {\n+            state.release();\n+            return Err(err);\n+        }\n+\n+        Ok(state)\n+    }\n+\n+    fn release(device: Self::Ptr, _file: &File) {\n+        device.release();\n+    }\n+\n+    fn ioctl(\n+        device: ArcBorrow<'_, FileState>,\n+        _file: &File,\n+        cmd: u32,\n+        arg: usize,\n+    ) -> Result<isize> {\n+        let _op = device.runtime.begin_operation()?;\n+        match cmd {\n+            GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK => {\n+                let data = UserSlice::new(UserPtr::from_addr(arg), AllocateBlockIoctl::ENCODED_LEN);\n+                let (mut reader, mut writer) = data.reader_writer();\n+                let mut bytes = [0u8; AllocateBlockIoctl::ENCODED_LEN];\n+                reader.read_slice(&mut bytes)?;\n+                let request = AllocateBlockIoctl::decode(&bytes);\n+                let response = device.allocate_block(request)?;\n+                writer.write_slice(&response.encode())?;\n+                Ok(0)\n+            }\n+            GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK => {\n+                let mut reader = UserSlice::new(UserPtr::from_addr(arg), size_of::<u64>()).reader();\n+                device.deallocate_block(reader.read::<u64>()?)?;\n+                Ok(0)\n+            }\n+            GOLDFISH_ADDRESS_SPACE_IOCTL_PING => {\n+                let data = UserSlice::new(UserPtr::from_addr(arg), PingIoctl::ENCODED_LEN);\n+                let (mut reader, mut writer) = data.reader_writer();\n+                let mut bytes = [0u8; PingIoctl::ENCODED_LEN];\n+                reader.read_slice(&mut bytes)?;\n+                let request = PingIoctl::decode(&bytes);\n+                let response = device.ping(request)?;\n+                writer.write_slice(&response.encode())?;\n+                Ok(0)\n+            }\n+            GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED => {\n+                let data = UserSlice::new(UserPtr::from_addr(arg), ClaimSharedIoctl::ENCODED_LEN);\n+                let (mut reader, mut writer) = data.reader_writer();\n+                let mut bytes = [0u8; ClaimSharedIoctl::ENCODED_LEN];\n+                reader.read_slice(&mut bytes)?;\n+                let request = ClaimSharedIoctl::decode(&bytes);\n+                let response = device.claim_shared(request)?;\n+                writer.write_slice(&response.encode())?;\n+                Ok(0)\n+            }\n+            GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED => {\n+                let mut reader = UserSlice::new(UserPtr::from_addr(arg), size_of::<u64>()).reader();\n+                device.unclaim_shared(reader.read::<u64>()?)?;\n+                Ok(0)\n+            }\n+            _ => Err(ENOTTY),\n+        }\n+    }\n+\n+    #[cfg(CONFIG_COMPAT)]\n+    fn compat_ioctl(\n+        device: ArcBorrow<'_, FileState>,\n+        file: &File,\n+        cmd: u32,\n+        arg: usize,\n+    ) -> Result<isize> {\n+        Self::ioctl(device, file, cmd, arg)\n+    }\n+}\n+\n+kernel::declare_misc_device_fops!(GoldfishAddressSpaceMisc);\n+\n+kernel::pci_device_table!(\n+    PCI_TABLE,\n+    MODULE_PCI_TABLE,\n+    <GoldfishAddressSpaceDriver as pci::Driver>::IdInfo,\n+    [(\n+        pci::DeviceId::from_id(\n+            pci::Vendor::from_raw(GOLDFISH_AS_VENDOR_ID as u16),\n+            GOLDFISH_AS_DEVICE_ID,\n+        ),\n+        (),\n+    )]\n+);\n+\n+impl pci::Driver for GoldfishAddressSpaceDriver {\n+    type IdInfo = ();\n+\n+    const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;\n+\n+    fn probe(pdev: &pci::Device<Core>, _id_info: &Self::IdInfo) -> impl PinInit<Self, Error> {\n+        pin_init::pin_init_scope(move || {\n+            if pdev.revision_id() != GOLDFISH_AS_SUPPORTED_REVISION {\n+                return Err(ENODEV);\n+            }\n+\n+            pdev.enable_device_mem()?;\n+            let enable_guard = ScopeGuard::new(|| pdev.disable_device());\n+\n+            let runtime = DeviceRuntime::new(pdev)?;\n+            runtime.program_host_visible_state()?;\n+\n+            let driver = try_pin_init!(Self {\n+                runtime: runtime.clone(),\n+                misc <- MiscDeviceRegistration::register_with_data(\n+                    MiscDeviceOptions {\n+                        name: c\"goldfish_address_space\",\n+                    },\n+                    runtime.clone(),\n+                ),\n+            });\n+            enable_guard.dismiss();\n+\n+            Ok(driver)\n+        })\n+    }\n+\n+    fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) {\n+        let this = this.get_ref();\n+        // 1. Stop new miscdevice opens from reaching the driver.\n+        this.misc.deregister();\n+        // 2. Wait for already-running syscalls, then revoke every still-live file's device-side\n+        //    state before the PCI function disappears.\n+        this.runtime.shutdown();\n+        // 3. Only then disable the PCI function, so post-shutdown release never needs to touch a\n+        //    disabled device.\n+        pdev.disable_device();\n+    }\n+}\n+\n+kernel::module_pci_driver! {\n+    type: GoldfishAddressSpaceDriver,\n+    name: \"goldfish_address_space\",\n+    authors: [\"Wenzhao Liao\"],\n+    description: \"Rust Goldfish address space driver\",\n+    license: \"GPL v2\",\n+}\n",
    "prefixes": [
        "RFC",
        "v3",
        "6/6"
    ]
}