Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2220327/?format=api
{ "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" ] }