[RFC PATCH v3 6/6] platform/goldfish: add Rust goldfish_address_space driver
From: Wenzhao Liao
Date: Mon Apr 06 2026 - 18:35:09 EST
Add a Rust implementation of the goldfish address-space driver and wire
it into drivers/platform/goldfish.
This RFC intentionally scopes the driver to the open/release/ioctl ABI
subset; userspace mmap is not part of this series. The driver keeps all
unsafe and bindings-facing work inside the Rust abstraction layers,
carries #![forbid(unsafe_code)] in the driver crate, and uses typed
miscdevice registration data plus SharedMemoryBar to stay on the safe
side of those abstractions.
On teardown, unbind first deregisters the miscdevice, then drains
already-running operations and revokes live file-owned device state
before disabling the PCI function. Probe also pairs
enable_device_mem() with a ScopeGuard so mid-probe failures cannot leak
an enabled device.
Signed-off-by: Wenzhao Liao <wenzhaoliao@xxxxxxxxxx>
---
MAINTAINERS | 2 +
drivers/platform/goldfish/Kconfig | 11 +
drivers/platform/goldfish/Makefile | 1 +
.../goldfish/goldfish_address_space.rs | 917 ++++++++++++++++++
4 files changed, 931 insertions(+)
create mode 100644 drivers/platform/goldfish/goldfish_address_space.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 800b2fe0e648..0a9193854f1b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1888,7 +1888,9 @@ L: linux-kernel@xxxxxxxxxxxxxxx
L: linux-pci@xxxxxxxxxxxxxxx
L: rust-for-linux@xxxxxxxxxxxxxxx
S: Maintained
+F: drivers/platform/goldfish/goldfish_address_space.rs
F: include/uapi/linux/goldfish_address_space.h
+K: \bGOLDFISH_ADDRESS_SPACE\b
ANDROID GOLDFISH RTC DRIVER
M: Jiaxun Yang <jiaxun.yang@xxxxxxxxxxx>
diff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig
index 03ca5bf19f98..58ccf5a757bd 100644
--- a/drivers/platform/goldfish/Kconfig
+++ b/drivers/platform/goldfish/Kconfig
@@ -17,4 +17,15 @@ config GOLDFISH_PIPE
This is a virtual device to drive the QEMU pipe interface used by
the Goldfish Android Virtual Device.
+config GOLDFISH_ADDRESS_SPACE
+ tristate "Goldfish address space driver in Rust"
+ depends on PCI && RUST && MMU
+ help
+ Adds a Rust implementation of the Goldfish address space driver
+ used by the Android Goldfish emulator.
+
+ This implementation uses typed Rust abstractions for PCI resource
+ setup, miscdevice registration, page-backed ping state, and the
+ userspace ioctl interface.
+
endif # GOLDFISH
diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile
index 76ba1d571896..17f67c223e95 100644
--- a/drivers/platform/goldfish/Makefile
+++ b/drivers/platform/goldfish/Makefile
@@ -3,3 +3,4 @@
# Makefile for Goldfish platform specific drivers
#
obj-$(CONFIG_GOLDFISH_PIPE) += goldfish_pipe.o
+obj-$(CONFIG_GOLDFISH_ADDRESS_SPACE) += goldfish_address_space.o
diff --git a/drivers/platform/goldfish/goldfish_address_space.rs b/drivers/platform/goldfish/goldfish_address_space.rs
new file mode 100644
index 000000000000..7742c76ea7fc
--- /dev/null
+++ b/drivers/platform/goldfish/goldfish_address_space.rs
@@ -0,0 +1,917 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Goldfish address space driver.
+
+#![forbid(unsafe_code)]
+
+use core::{mem::size_of, pin::Pin};
+use kernel::{
+ alloc::KVVec,
+ device::Core,
+ devres::Devres,
+ error::Error,
+ fs::File,
+ io::{Io, PhysAddr},
+ ioctl,
+ miscdevice::{MiscDevice, MiscDeviceOpenContext, MiscDeviceOptions, MiscDeviceRegistration},
+ new_condvar, new_mutex,
+ page::{Page, PAGE_SIZE},
+ pci,
+ prelude::*,
+ sync::{Arc, ArcBorrow, CondVar, Mutex},
+ types::ScopeGuard,
+ uaccess::{UserPtr, UserSlice},
+ uapi,
+};
+
+const GOLDFISH_AS_CONTROL_BAR: u32 = 0;
+const GOLDFISH_AS_AREA_BAR: u32 = 1;
+const GOLDFISH_AS_VENDOR_ID: u32 = 0x607d;
+const GOLDFISH_AS_DEVICE_ID: u32 = 0xf153;
+const GOLDFISH_AS_SUPPORTED_REVISION: u8 = 1;
+const GOLDFISH_AS_INVALID_HANDLE: u32 = u32::MAX;
+
+const GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC: u32 = uapi::GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC as u32;
+const GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK: u32 =
+ ioctl::_IOWR::<AllocateBlockIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 10);
+const GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK: u32 =
+ ioctl::_IOWR::<u64>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 11);
+const GOLDFISH_ADDRESS_SPACE_IOCTL_PING: u32 =
+ ioctl::_IOWR::<PingIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 12);
+const GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED: u32 =
+ ioctl::_IOWR::<ClaimSharedIoctl>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 13);
+const GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED: u32 =
+ ioctl::_IOWR::<u64>(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 14);
+
+struct Registers;
+
+impl Registers {
+ const COMMAND: usize = 0;
+ const STATUS: usize = 4;
+ const GUEST_PAGE_SIZE: usize = 8;
+ const BLOCK_SIZE_LOW: usize = 12;
+ const BLOCK_SIZE_HIGH: usize = 16;
+ const BLOCK_OFFSET_LOW: usize = 20;
+ const BLOCK_OFFSET_HIGH: usize = 24;
+ const PING: usize = 28;
+ const PING_INFO_ADDR_LOW: usize = 32;
+ const PING_INFO_ADDR_HIGH: usize = 36;
+ const HANDLE: usize = 40;
+ const PHYS_START_LOW: usize = 44;
+ const PHYS_START_HIGH: usize = 48;
+ const END: usize = 56;
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy)]
+enum CommandId {
+ AllocateBlock = 1,
+ DeallocateBlock = 2,
+ GenHandle = 3,
+ DestroyHandle = 4,
+ TellPingInfoAddr = 5,
+}
+
+type ControlBar = pci::Bar<{ Registers::END }>;
+
+#[derive(Clone, Copy)]
+struct Block {
+ offset: u64,
+ size: u64,
+}
+
+struct BlockSet {
+ blocks: KVVec<Block>,
+}
+
+impl BlockSet {
+ fn new() -> Self {
+ Self {
+ blocks: KVVec::new(),
+ }
+ }
+
+ fn insert(&mut self, block: Block) -> Result {
+ self.blocks.push(block, GFP_KERNEL)?;
+ Ok(())
+ }
+
+ fn remove(&mut self, offset: u64) -> Result<Block> {
+ let index = self
+ .blocks
+ .iter()
+ .position(|block| block.offset == offset)
+ .ok_or(ENXIO)?;
+ self.blocks.remove(index).map_err(|_| EINVAL)
+ }
+
+ fn iter(&self) -> impl Iterator<Item = Block> + '_ {
+ self.blocks.iter().copied()
+ }
+
+ fn clear(&mut self) {
+ let _ = self.take_all();
+ }
+
+ fn take_all(&mut self) -> KVVec<Block> {
+ let mut blocks = KVVec::new();
+ core::mem::swap(&mut blocks, &mut self.blocks);
+ blocks
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+struct PingInfoHeader {
+ offset: u64,
+ size: u64,
+ metadata: u64,
+ version: u32,
+ wait_fd: u32,
+ wait_flags: u32,
+ direction: u32,
+ data_size: u64,
+}
+
+impl PingInfoHeader {
+ const ENCODED_LEN: usize = 48;
+
+ fn encode(self) -> [u8; Self::ENCODED_LEN] {
+ let mut bytes = [0u8; Self::ENCODED_LEN];
+
+ bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());
+ bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());
+ bytes[16..24].copy_from_slice(&self.metadata.to_ne_bytes());
+ bytes[24..28].copy_from_slice(&self.version.to_ne_bytes());
+ bytes[28..32].copy_from_slice(&self.wait_fd.to_ne_bytes());
+ bytes[32..36].copy_from_slice(&self.wait_flags.to_ne_bytes());
+ bytes[36..40].copy_from_slice(&self.direction.to_ne_bytes());
+ bytes[40..48].copy_from_slice(&self.data_size.to_ne_bytes());
+
+ bytes
+ }
+
+ fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {
+ Self {
+ offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),
+ size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),
+ metadata: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),
+ version: u32::from_ne_bytes(bytes[24..28].try_into().unwrap()),
+ wait_fd: u32::from_ne_bytes(bytes[28..32].try_into().unwrap()),
+ wait_flags: u32::from_ne_bytes(bytes[32..36].try_into().unwrap()),
+ direction: u32::from_ne_bytes(bytes[36..40].try_into().unwrap()),
+ data_size: u64::from_ne_bytes(bytes[40..48].try_into().unwrap()),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+struct AllocateBlockIoctl {
+ size: u64,
+ offset: u64,
+ phys_addr: u64,
+}
+
+impl AllocateBlockIoctl {
+ const ENCODED_LEN: usize = 24;
+
+ fn encode(self) -> [u8; Self::ENCODED_LEN] {
+ let mut bytes = [0u8; Self::ENCODED_LEN];
+ bytes[0..8].copy_from_slice(&self.size.to_ne_bytes());
+ bytes[8..16].copy_from_slice(&self.offset.to_ne_bytes());
+ bytes[16..24].copy_from_slice(&self.phys_addr.to_ne_bytes());
+ bytes
+ }
+
+ fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {
+ Self {
+ size: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),
+ offset: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),
+ phys_addr: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+struct PingIoctl {
+ offset: u64,
+ size: u64,
+ metadata: u64,
+ version: u32,
+ wait_fd: u32,
+ wait_flags: u32,
+ direction: u32,
+}
+
+impl PingIoctl {
+ const ENCODED_LEN: usize = 40;
+
+ fn encode(self) -> [u8; Self::ENCODED_LEN] {
+ let mut bytes = [0u8; Self::ENCODED_LEN];
+ bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());
+ bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());
+ bytes[16..24].copy_from_slice(&self.metadata.to_ne_bytes());
+ bytes[24..28].copy_from_slice(&self.version.to_ne_bytes());
+ bytes[28..32].copy_from_slice(&self.wait_fd.to_ne_bytes());
+ bytes[32..36].copy_from_slice(&self.wait_flags.to_ne_bytes());
+ bytes[36..40].copy_from_slice(&self.direction.to_ne_bytes());
+ bytes
+ }
+
+ fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {
+ Self {
+ offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),
+ size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),
+ metadata: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),
+ version: u32::from_ne_bytes(bytes[24..28].try_into().unwrap()),
+ wait_fd: u32::from_ne_bytes(bytes[28..32].try_into().unwrap()),
+ wait_flags: u32::from_ne_bytes(bytes[32..36].try_into().unwrap()),
+ direction: u32::from_ne_bytes(bytes[36..40].try_into().unwrap()),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+struct ClaimSharedIoctl {
+ offset: u64,
+ size: u64,
+}
+
+impl ClaimSharedIoctl {
+ const ENCODED_LEN: usize = 16;
+
+ fn encode(self) -> [u8; Self::ENCODED_LEN] {
+ let mut bytes = [0u8; Self::ENCODED_LEN];
+ bytes[0..8].copy_from_slice(&self.offset.to_ne_bytes());
+ bytes[8..16].copy_from_slice(&self.size.to_ne_bytes());
+ bytes
+ }
+
+ fn decode(bytes: &[u8; Self::ENCODED_LEN]) -> Self {
+ Self {
+ offset: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),
+ size: u64::from_ne_bytes(bytes[8..16].try_into().unwrap()),
+ }
+ }
+}
+
+struct PingState {
+ page: Page,
+}
+
+impl PingState {
+ fn new() -> Result<Self> {
+ let mut page = Page::alloc_page(GFP_KERNEL)?;
+ page.fill_zero(0, PAGE_SIZE)?;
+ Ok(Self { page })
+ }
+
+ fn phys_addr(&self) -> PhysAddr {
+ self.page.phys_addr()
+ }
+
+ fn shared_offset(offset: u64, shared_phys_start: PhysAddr) -> Result<u64> {
+ let shared_phys_start = u64::try_from(shared_phys_start).map_err(|_| EOVERFLOW)?;
+ offset.checked_add(shared_phys_start).ok_or(EOVERFLOW)
+ }
+
+ fn prepare_ping(&mut self, request: &PingIoctl, shared_phys_start: PhysAddr) -> Result {
+ let header = PingInfoHeader {
+ offset: Self::shared_offset(request.offset, shared_phys_start)?,
+ size: request.size,
+ metadata: request.metadata,
+ version: request.version,
+ wait_fd: request.wait_fd,
+ wait_flags: request.wait_flags,
+ direction: request.direction,
+ data_size: 0,
+ };
+
+ self.page.fill_zero(0, PAGE_SIZE)?;
+ self.page.write_slice(&header.encode(), 0)
+ }
+
+ fn finish_ping(&self, request: &mut PingIoctl) -> Result {
+ let mut bytes = [0u8; PingInfoHeader::ENCODED_LEN];
+ self.page.read_slice(&mut bytes, 0)?;
+ let header = PingInfoHeader::decode(&bytes);
+ request.offset = header.offset;
+ request.size = header.size;
+ request.metadata = header.metadata;
+ request.version = header.version;
+ request.wait_fd = header.wait_fd;
+ request.wait_flags = header.wait_flags;
+ request.direction = header.direction;
+ Ok(())
+ }
+}
+
+#[pin_data]
+struct DeviceRuntime {
+ #[pin]
+ control_bar: Devres<ControlBar>,
+ #[pin]
+ shared_bar: Devres<pci::SharedMemoryBar>,
+ #[pin]
+ registers_lock: Mutex<()>,
+ #[pin]
+ lifecycle: Mutex<RuntimeLifecycleState>,
+ #[pin]
+ lifecycle_idle: CondVar,
+}
+
+struct RuntimeLifecycleState {
+ accepting_new_ops: bool,
+ active_ops: usize,
+ live_files: KVVec<Arc<FileState>>,
+}
+
+struct RuntimeOpGuard {
+ runtime: Arc<DeviceRuntime>,
+}
+
+impl Drop for RuntimeOpGuard {
+ fn drop(&mut self) {
+ let mut state = self.runtime.lifecycle.lock();
+ state.active_ops -= 1;
+ self.runtime.notify_if_idle(&state);
+ }
+}
+
+impl DeviceRuntime {
+ fn new(pdev: &pci::Device<Core>) -> Result<Arc<Self>> {
+ Arc::pin_init(
+ try_pin_init!(Self {
+ control_bar <- pdev.iomap_region_sized::<{ Registers::END }>(
+ GOLDFISH_AS_CONTROL_BAR,
+ c"goldfish_address_space/control",
+ ),
+ shared_bar <- pdev.memremap_bar(
+ GOLDFISH_AS_AREA_BAR,
+ c"goldfish_address_space/area",
+ ),
+ registers_lock <- new_mutex!(()),
+ lifecycle <- new_mutex!(RuntimeLifecycleState {
+ accepting_new_ops: true,
+ active_ops: 0,
+ live_files: KVVec::new(),
+ }),
+ lifecycle_idle <- new_condvar!("goldfish_address_space/lifecycle_idle"),
+ }),
+ GFP_KERNEL,
+ )
+ }
+
+ fn notify_if_idle(&self, state: &RuntimeLifecycleState) {
+ if !state.accepting_new_ops && state.active_ops == 0 {
+ self.lifecycle_idle.notify_all();
+ }
+ }
+
+ fn begin_operation(self: &Arc<Self>) -> Result<RuntimeOpGuard> {
+ let mut state = self.lifecycle.lock();
+ if !state.accepting_new_ops {
+ return Err(ENODEV);
+ }
+
+ state.active_ops = state.active_ops.checked_add(1).ok_or(EBUSY)?;
+ drop(state);
+
+ Ok(RuntimeOpGuard {
+ runtime: self.clone(),
+ })
+ }
+
+ fn register_live_file(&self, file: Arc<FileState>) -> Result {
+ let mut state = self.lifecycle.lock();
+ if !state.accepting_new_ops {
+ return Err(ENODEV);
+ }
+
+ state.live_files.push(file, GFP_KERNEL)?;
+ Ok(())
+ }
+
+ fn unregister_live_file(&self, file: &Arc<FileState>) {
+ let mut state = self.lifecycle.lock();
+ let Some(index) = state
+ .live_files
+ .iter()
+ .position(|entry| Arc::ptr_eq(entry, file))
+ else {
+ return;
+ };
+
+ if let Ok(entry) = state.live_files.remove(index) {
+ drop(entry);
+ }
+ }
+
+ fn shutdown(&self) {
+ let mut state = self.lifecycle.lock();
+ // `unbind()` removes miscdevice reachability before calling `shutdown()`. After that we
+ // only need to wait for already-entered syscalls to finish; live files are revoked below,
+ // so remove is no longer bounded by userspace deciding to close descriptors.
+ state.accepting_new_ops = false;
+
+ while state.active_ops != 0 {
+ self.lifecycle_idle.wait(&mut state);
+ }
+
+ let mut live_files = KVVec::new();
+ core::mem::swap(&mut live_files, &mut state.live_files);
+ drop(state);
+
+ for file in &live_files {
+ file.revoke_for_shutdown();
+ }
+ }
+
+ fn control_bar(&self) -> Result<impl core::ops::Deref<Target = ControlBar> + '_> {
+ self.control_bar.try_access().ok_or(ENXIO)
+ }
+
+ fn shared_bar(&self) -> Result<impl core::ops::Deref<Target = pci::SharedMemoryBar> + '_> {
+ self.shared_bar.try_access().ok_or(ENXIO)
+ }
+
+ fn run_command_locked(control: &ControlBar, command: CommandId) -> Result {
+ control.write32(command as u32, Registers::COMMAND);
+
+ let status = i32::try_from(control.read32(Registers::STATUS)).map_err(|_| EIO)?;
+ if status == 0 {
+ Ok(())
+ } else {
+ Err(Error::from_errno(-status))
+ }
+ }
+
+ fn issue_command_locked(control: &ControlBar, command: CommandId) {
+ control.write32(command as u32, Registers::COMMAND);
+ }
+
+ fn write_u64(control: &ControlBar, low_offset: usize, high_offset: usize, value: u64) {
+ control.write32(value as u32, low_offset);
+ control.write32((value >> 32) as u32, high_offset);
+ }
+
+ fn read_u64(control: &ControlBar, low_offset: usize, high_offset: usize) -> u64 {
+ u64::from(control.read32(low_offset)) | (u64::from(control.read32(high_offset)) << 32)
+ }
+
+ fn program_host_visible_state(&self) -> Result {
+ let control = self.control_bar()?;
+ let shared = self.shared_bar()?;
+ let phys_start = u64::try_from(shared.phys_start()).map_err(|_| EOVERFLOW)?;
+
+ control.write32(PAGE_SIZE as u32, Registers::GUEST_PAGE_SIZE);
+ Self::write_u64(
+ &control,
+ Registers::PHYS_START_LOW,
+ Registers::PHYS_START_HIGH,
+ phys_start,
+ );
+
+ Ok(())
+ }
+
+ fn shared_phys_start(&self) -> Result<PhysAddr> {
+ Ok(self.shared_bar()?.phys_start())
+ }
+
+ fn generate_handle(&self) -> Result<u32> {
+ let _guard = self.registers_lock.lock();
+ let control = self.control_bar()?;
+
+ // The external C driver does not gate `GEN_HANDLE` on the status register and instead
+ // validates completion by reading back the handle.
+ Self::issue_command_locked(&control, CommandId::GenHandle);
+
+ let handle = control.read32(Registers::HANDLE);
+ if handle == GOLDFISH_AS_INVALID_HANDLE {
+ return Err(EINVAL);
+ }
+
+ Ok(handle)
+ }
+
+ fn tell_ping_info_addr(&self, handle: u32, ping_info_phys: PhysAddr) -> Result {
+ let _guard = self.registers_lock.lock();
+ let control = self.control_bar()?;
+ let ping_info_phys = u64::try_from(ping_info_phys).map_err(|_| EOVERFLOW)?;
+
+ control.write32(handle, Registers::HANDLE);
+ Self::write_u64(
+ &control,
+ Registers::PING_INFO_ADDR_LOW,
+ Registers::PING_INFO_ADDR_HIGH,
+ ping_info_phys,
+ );
+ // The external C driver validates `TELL_PING_INFO_ADDR` through the echoed physical
+ // address rather than through the status register.
+ Self::issue_command_locked(&control, CommandId::TellPingInfoAddr);
+
+ let returned = Self::read_u64(
+ &control,
+ Registers::PING_INFO_ADDR_LOW,
+ Registers::PING_INFO_ADDR_HIGH,
+ );
+ if returned != ping_info_phys {
+ return Err(EINVAL);
+ }
+
+ Ok(())
+ }
+
+ fn destroy_handle(&self, handle: u32) -> Result {
+ let _guard = self.registers_lock.lock();
+ let control = self.control_bar()?;
+ control.write32(handle, Registers::HANDLE);
+ Self::issue_command_locked(&control, CommandId::DestroyHandle);
+ Ok(())
+ }
+
+ fn allocate_block(&self, size: u64) -> Result<Block> {
+ let _guard = self.registers_lock.lock();
+ let control = self.control_bar()?;
+
+ Self::write_u64(
+ &control,
+ Registers::BLOCK_SIZE_LOW,
+ Registers::BLOCK_SIZE_HIGH,
+ size,
+ );
+ Self::run_command_locked(&control, CommandId::AllocateBlock)?;
+
+ Ok(Block {
+ offset: Self::read_u64(
+ &control,
+ Registers::BLOCK_OFFSET_LOW,
+ Registers::BLOCK_OFFSET_HIGH,
+ ),
+ size: Self::read_u64(
+ &control,
+ Registers::BLOCK_SIZE_LOW,
+ Registers::BLOCK_SIZE_HIGH,
+ ),
+ })
+ }
+
+ fn deallocate_block(&self, offset: u64) -> Result {
+ let _guard = self.registers_lock.lock();
+ let control = self.control_bar()?;
+ Self::write_u64(
+ &control,
+ Registers::BLOCK_OFFSET_LOW,
+ Registers::BLOCK_OFFSET_HIGH,
+ offset,
+ );
+ Self::run_command_locked(&control, CommandId::DeallocateBlock)
+ }
+
+ fn ping(&self, handle: u32) -> Result {
+ let _guard = self.registers_lock.lock();
+ self.control_bar()?.write32(handle, Registers::PING);
+ Ok(())
+ }
+
+ fn cleanup_file_resources<I>(&self, handle: u32, blocks: I)
+ where
+ I: IntoIterator<Item = Block>,
+ {
+ // `unbind()` revokes live files before `disable_device()`, so both the shutdown path and a
+ // concurrent `release()` may still legitimately touch the BAR here.
+ if let Err(err) = self.destroy_handle(handle) {
+ pr_warn!(
+ "goldfish_address_space: destroy handle {} failed: {}\n",
+ handle,
+ err.to_errno()
+ );
+ }
+
+ for block in blocks {
+ if let Err(err) = self.deallocate_block(block.offset) {
+ pr_warn!(
+ "goldfish_address_space: deallocate block 0x{:x} failed: {}\n",
+ block.offset,
+ err.to_errno()
+ );
+ }
+ }
+ }
+}
+
+struct FileResources {
+ handle: Option<u32>,
+ allocated_blocks: BlockSet,
+ shared_blocks: BlockSet,
+}
+
+impl FileResources {
+ fn new(handle: u32) -> Self {
+ Self {
+ handle: Some(handle),
+ allocated_blocks: BlockSet::new(),
+ shared_blocks: BlockSet::new(),
+ }
+ }
+}
+
+#[pin_data]
+struct FileState {
+ runtime: Arc<DeviceRuntime>,
+ #[pin]
+ ping: Mutex<PingState>,
+ #[pin]
+ resources: Mutex<FileResources>,
+}
+
+impl FileState {
+ fn new(runtime: Arc<DeviceRuntime>, handle: u32, ping: PingState) -> Result<Arc<Self>> {
+ Arc::pin_init(
+ try_pin_init!(Self {
+ runtime: runtime,
+ ping <- new_mutex!(ping),
+ resources <- new_mutex!(FileResources::new(handle)),
+ }),
+ GFP_KERNEL,
+ )
+ }
+
+ fn shared_phys_addr(&self, offset: u64) -> Result<u64> {
+ let base = u64::try_from(self.runtime.shared_phys_start()?).map_err(|_| EOVERFLOW)?;
+ base.checked_add(offset).ok_or(EOVERFLOW)
+ }
+
+ fn allocate_block(
+ self: ArcBorrow<'_, Self>,
+ mut request: AllocateBlockIoctl,
+ ) -> Result<AllocateBlockIoctl> {
+ let block = self.runtime.allocate_block(request.size)?;
+ let mut resources = self.resources.lock();
+ if resources.handle.is_none() {
+ drop(resources);
+ let _ = self.runtime.deallocate_block(block.offset);
+ return Err(ENODEV);
+ }
+
+ if let Err(err) = resources.allocated_blocks.insert(block) {
+ drop(resources);
+ let _ = self.runtime.deallocate_block(block.offset);
+ return Err(err);
+ }
+
+ request.size = block.size;
+ request.offset = block.offset;
+ request.phys_addr = self.shared_phys_addr(block.offset)?;
+ Ok(request)
+ }
+
+ fn deallocate_block(self: ArcBorrow<'_, Self>, offset: u64) -> Result {
+ let mut resources = self.resources.lock();
+ if resources.handle.is_none() {
+ return Err(ENODEV);
+ }
+
+ if !resources
+ .allocated_blocks
+ .iter()
+ .any(|block| block.offset == offset)
+ {
+ return Err(ENXIO);
+ }
+
+ self.runtime.deallocate_block(offset)?;
+ let _ = resources.allocated_blocks.remove(offset)?;
+ Ok(())
+ }
+
+ fn claim_shared(
+ self: ArcBorrow<'_, Self>,
+ request: ClaimSharedIoctl,
+ ) -> Result<ClaimSharedIoctl> {
+ let mut resources = self.resources.lock();
+ if resources.handle.is_none() {
+ return Err(ENODEV);
+ }
+
+ resources.shared_blocks.insert(Block {
+ offset: request.offset,
+ size: request.size,
+ })?;
+ Ok(request)
+ }
+
+ fn unclaim_shared(self: ArcBorrow<'_, Self>, offset: u64) -> Result {
+ let mut resources = self.resources.lock();
+ if resources.handle.is_none() {
+ return Err(ENODEV);
+ }
+
+ resources.shared_blocks.remove(offset)?;
+ Ok(())
+ }
+
+ fn ping(self: ArcBorrow<'_, Self>, mut request: PingIoctl) -> Result<PingIoctl> {
+ let handle = self.resources.lock().handle.ok_or(ENODEV)?;
+ let mut ping = self.ping.lock();
+ ping.prepare_ping(&request, self.runtime.shared_phys_start()?)?;
+ self.runtime.ping(handle)?;
+ ping.finish_ping(&mut request)?;
+ Ok(request)
+ }
+
+ fn cleanup_resources(&self) {
+ let mut resources = self.resources.lock();
+ let Some(handle) = resources.handle.take() else {
+ return;
+ };
+
+ self.runtime
+ .cleanup_file_resources(handle, resources.allocated_blocks.iter());
+ resources.allocated_blocks.clear();
+ resources.shared_blocks.clear();
+ }
+
+ fn revoke_for_shutdown(&self) {
+ self.cleanup_resources();
+ }
+
+ fn release(self: Arc<Self>) {
+ self.cleanup_resources();
+ self.runtime.unregister_live_file(&self);
+ }
+}
+
+#[pin_data]
+struct GoldfishAddressSpaceDriver {
+ runtime: Arc<DeviceRuntime>,
+ #[pin]
+ misc: MiscDeviceRegistration<GoldfishAddressSpaceMisc>,
+}
+
+struct GoldfishAddressSpaceMisc;
+
+#[vtable]
+impl MiscDevice for GoldfishAddressSpaceMisc {
+ type Ptr = Arc<FileState>;
+ type RegistrationData = Arc<DeviceRuntime>;
+
+ fn open(_file: &File, ctx: &MiscDeviceOpenContext<'_, Self>) -> Result<Self::Ptr> {
+ let runtime = ctx.data().clone();
+ let _op = runtime.begin_operation()?;
+ let ping = PingState::new()?;
+ let handle = runtime.generate_handle()?;
+ let cleanup = ScopeGuard::new_with_data((runtime.clone(), handle), |(runtime, handle)| {
+ let _ = runtime.destroy_handle(handle);
+ });
+
+ runtime.tell_ping_info_addr(handle, ping.phys_addr())?;
+ let state = FileState::new(runtime.clone(), handle, ping)?;
+ cleanup.dismiss();
+
+ // Publish the file as a live shutdown owner before returning it to the miscdevice core.
+ if let Err(err) = runtime.register_live_file(state.clone()) {
+ state.release();
+ return Err(err);
+ }
+
+ Ok(state)
+ }
+
+ fn release(device: Self::Ptr, _file: &File) {
+ device.release();
+ }
+
+ fn ioctl(
+ device: ArcBorrow<'_, FileState>,
+ _file: &File,
+ cmd: u32,
+ arg: usize,
+ ) -> Result<isize> {
+ let _op = device.runtime.begin_operation()?;
+ match cmd {
+ GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK => {
+ let data = UserSlice::new(UserPtr::from_addr(arg), AllocateBlockIoctl::ENCODED_LEN);
+ let (mut reader, mut writer) = data.reader_writer();
+ let mut bytes = [0u8; AllocateBlockIoctl::ENCODED_LEN];
+ reader.read_slice(&mut bytes)?;
+ let request = AllocateBlockIoctl::decode(&bytes);
+ let response = device.allocate_block(request)?;
+ writer.write_slice(&response.encode())?;
+ Ok(0)
+ }
+ GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK => {
+ let mut reader = UserSlice::new(UserPtr::from_addr(arg), size_of::<u64>()).reader();
+ device.deallocate_block(reader.read::<u64>()?)?;
+ Ok(0)
+ }
+ GOLDFISH_ADDRESS_SPACE_IOCTL_PING => {
+ let data = UserSlice::new(UserPtr::from_addr(arg), PingIoctl::ENCODED_LEN);
+ let (mut reader, mut writer) = data.reader_writer();
+ let mut bytes = [0u8; PingIoctl::ENCODED_LEN];
+ reader.read_slice(&mut bytes)?;
+ let request = PingIoctl::decode(&bytes);
+ let response = device.ping(request)?;
+ writer.write_slice(&response.encode())?;
+ Ok(0)
+ }
+ GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED => {
+ let data = UserSlice::new(UserPtr::from_addr(arg), ClaimSharedIoctl::ENCODED_LEN);
+ let (mut reader, mut writer) = data.reader_writer();
+ let mut bytes = [0u8; ClaimSharedIoctl::ENCODED_LEN];
+ reader.read_slice(&mut bytes)?;
+ let request = ClaimSharedIoctl::decode(&bytes);
+ let response = device.claim_shared(request)?;
+ writer.write_slice(&response.encode())?;
+ Ok(0)
+ }
+ GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED => {
+ let mut reader = UserSlice::new(UserPtr::from_addr(arg), size_of::<u64>()).reader();
+ device.unclaim_shared(reader.read::<u64>()?)?;
+ Ok(0)
+ }
+ _ => Err(ENOTTY),
+ }
+ }
+
+ #[cfg(CONFIG_COMPAT)]
+ fn compat_ioctl(
+ device: ArcBorrow<'_, FileState>,
+ file: &File,
+ cmd: u32,
+ arg: usize,
+ ) -> Result<isize> {
+ Self::ioctl(device, file, cmd, arg)
+ }
+}
+
+kernel::declare_misc_device_fops!(GoldfishAddressSpaceMisc);
+
+kernel::pci_device_table!(
+ PCI_TABLE,
+ MODULE_PCI_TABLE,
+ <GoldfishAddressSpaceDriver as pci::Driver>::IdInfo,
+ [(
+ pci::DeviceId::from_id(
+ pci::Vendor::from_raw(GOLDFISH_AS_VENDOR_ID as u16),
+ GOLDFISH_AS_DEVICE_ID,
+ ),
+ (),
+ )]
+);
+
+impl pci::Driver for GoldfishAddressSpaceDriver {
+ type IdInfo = ();
+
+ const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
+
+ fn probe(pdev: &pci::Device<Core>, _id_info: &Self::IdInfo) -> impl PinInit<Self, Error> {
+ pin_init::pin_init_scope(move || {
+ if pdev.revision_id() != GOLDFISH_AS_SUPPORTED_REVISION {
+ return Err(ENODEV);
+ }
+
+ pdev.enable_device_mem()?;
+ let enable_guard = ScopeGuard::new(|| pdev.disable_device());
+
+ let runtime = DeviceRuntime::new(pdev)?;
+ runtime.program_host_visible_state()?;
+
+ let driver = try_pin_init!(Self {
+ runtime: runtime.clone(),
+ misc <- MiscDeviceRegistration::register_with_data(
+ MiscDeviceOptions {
+ name: c"goldfish_address_space",
+ },
+ runtime.clone(),
+ ),
+ });
+ enable_guard.dismiss();
+
+ Ok(driver)
+ })
+ }
+
+ fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) {
+ let this = this.get_ref();
+ // 1. Stop new miscdevice opens from reaching the driver.
+ this.misc.deregister();
+ // 2. Wait for already-running syscalls, then revoke every still-live file's device-side
+ // state before the PCI function disappears.
+ this.runtime.shutdown();
+ // 3. Only then disable the PCI function, so post-shutdown release never needs to touch a
+ // disabled device.
+ pdev.disable_device();
+ }
+}
+
+kernel::module_pci_driver! {
+ type: GoldfishAddressSpaceDriver,
+ name: "goldfish_address_space",
+ authors: ["Wenzhao Liao"],
+ description: "Rust Goldfish address space driver",
+ license: "GPL v2",
+}
--
2.34.1