From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m128214.netease.com (mail-m128214.netease.com [103.209.128.214]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9ABA035F5E9; Mon, 6 Apr 2026 16:51:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=103.209.128.214 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775494303; cv=none; b=e81+kLR9+Tkxby+5D0LgP4dfNdtUA2rDG/0W7SVEiGD6xVAtQlUlNxpASYVhX9HvIsd8h7e1IGKH67asIqgH8Ed6YPA4z1FUUMo7jm9MxDWG9J8Ve/HjRaQXmnUD9VR0eR4l4q5qryBrEzm/GAIoa5q3fB29dCiQCHjGV7a06s0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775494303; c=relaxed/simple; bh=JGV9wifkkNiF1MU+26HGbW/+ymbUBkjqNWmmDG9ejug=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=bW/3SXJtPHfhCsVNaiUrsoMTPHSaDUGVXBv6CTjHb8YpvoiJYN/1d2zCREFlza3cWqnXd1Tto4+s0JjpoGcBthDATUnEK4L1jLoWKuLqph3dLYHeRgtmFTBov6xXHJUVzQvHxUI39fh3z4aKuIs0/a5IDn2ohLi62+GQI4KfDEk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=mr0jnYGJ; arc=none smtp.client-ip=103.209.128.214 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="mr0jnYGJ" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbdb; Tue, 7 Apr 2026 00:51:25 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 1/6] uapi: add goldfish_address_space userspace ABI header Date: Mon, 6 Apr 2026 12:51:15 -0400 Message-Id: <20260406165120.166928-2-wenzhaoliao@ruc.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> References: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b4ca8003a2kunm562d3b0d647e63 X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVkZSkgYVhoeS0pCQkJOS0geTFYeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0 tZBg++ DKIM-Signature: a=rsa-sha256; b=mr0jnYGJNn7jdQdmGx9snQukeD3Qle86rk2rjHNwRxzlq3z1QnTC5xPCsUs6ScNU2TzPr+pLNsIN/bPqVMJ0aW6R6lYp4m4VrAAZ/g1m48og8sJJnNcg94vYyd1Z9om1QI14x9/viFF0QyRbedvCJWQVQO79LFtSyIGorAbn7L0=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=2PkcM2WRjNh0KqsyA+Q2Us0R9IhzckDTKUqyovFSI74=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" The external goldfish address-space driver exposes its userspace contract through a dedicated header. Land the ioctl definitions in include/uapi/linux so the Rust driver can depend on an in-tree UAPI surface instead of carrying an external private header. This RFC intentionally narrows the first upstream step to the open/release/ioctl ABI subset. Userspace mmap and PING_WITH_DATA stay out of this series until they have their own review and validation story. Signed-off-by: Wenzhao Liao --- MAINTAINERS | 8 +++ include/uapi/linux/goldfish_address_space.h | 54 +++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 include/uapi/linux/goldfish_address_space.h diff --git a/MAINTAINERS b/MAINTAINERS index a62f6af55c3a..800b2fe0e648 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1882,6 +1882,14 @@ S: Supported F: Documentation/devicetree/bindings/interrupt-controller/google,goldfish-= pic.yaml F: drivers/irqchip/irq-goldfish-pic.c =20 +ANDROID GOLDFISH ADDRESS SPACE DRIVER +M: Wenzhao Liao +L: linux-kernel@vger.kernel.org +L: linux-pci@vger.kernel.org +L: rust-for-linux@vger.kernel.org +S: Maintained +F: include/uapi/linux/goldfish_address_space.h + ANDROID GOLDFISH RTC DRIVER M: Jiaxun Yang S: Supported diff --git a/include/uapi/linux/goldfish_address_space.h b/include/uapi/lin= ux/goldfish_address_space.h new file mode 100644 index 000000000000..b782d82f53df --- /dev/null +++ b/include/uapi/linux/goldfish_address_space.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _UAPI_LINUX_GOLDFISH_ADDRESS_SPACE_H +#define _UAPI_LINUX_GOLDFISH_ADDRESS_SPACE_H + +#include +#include + +#define GOLDFISH_ADDRESS_SPACE_DEVICE_NAME "goldfish_address_space" + +struct goldfish_address_space_allocate_block { + __u64 size; + __u64 offset; + __u64 phys_addr; +}; + +struct goldfish_address_space_ping { + __u64 offset; + __u64 size; + __u64 metadata; + __u32 version; + __u32 wait_fd; + __u32 wait_flags; + __u32 direction; +}; + +struct goldfish_address_space_claim_shared { + __u64 offset; + __u64 size; +}; + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC 'G' + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_OP(OP, T) \ + _IOWR(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, OP, T) + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK \ + GOLDFISH_ADDRESS_SPACE_IOCTL_OP(10, \ + struct goldfish_address_space_allocate_block) + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK \ + GOLDFISH_ADDRESS_SPACE_IOCTL_OP(11, __u64) + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_PING \ + GOLDFISH_ADDRESS_SPACE_IOCTL_OP(12, \ + struct goldfish_address_space_ping) + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED \ + GOLDFISH_ADDRESS_SPACE_IOCTL_OP(13, \ + struct goldfish_address_space_claim_shared) + +#define GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED \ + GOLDFISH_ADDRESS_SPACE_IOCTL_OP(14, __u64) + +#endif /* _UAPI_LINUX_GOLDFISH_ADDRESS_SPACE_H */ --=20 2.34.1 From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m83129.xmail.ntesmail.com (mail-m83129.xmail.ntesmail.com [156.224.83.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0FB5E27702D; Mon, 6 Apr 2026 17:06:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=156.224.83.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775495222; cv=none; b=MqZOVIlYyG0xl5AgpCtmayfqGf45tLkCc47NDsoeysPWN7FcY3jTs+Km5187++TV5NAXwZILA5RyxHQdOVdudqU7UNIjwLn3ckVPBEnrdTTU6fqspU9QZ/7u+Fb7ZDAYqGRxufESo7GlVlkjILLdDJRim6qmEU/LgtZF+iilYoA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775495222; c=relaxed/simple; bh=MyruzAKe4PZJ1OY6Kp5WYJMBUmMjeBtf1hWfM9tLod4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DJi5TdKxzW1OfLj8HvSX8wXl0MAEwEZ3IbbdRiFJnRKWFhtZtEsZXWHtQzI0m7jnNX7cEOscpBzbqAMxjRN24qJwcGXfoW6VJLCAJxG6vENTAEp5sdtZ8+mPMZkkuyaEc2BFps/Y5TrEA+WSeGKc/yE8xNAYV7C96fwL5msPPqk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=BdXgaili; arc=none smtp.client-ip=156.224.83.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="BdXgaili" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbdc; Tue, 7 Apr 2026 00:51:30 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 2/6] rust: bindings: expose goldfish address-space headers Date: Mon, 6 Apr 2026 12:51:16 -0400 Message-Id: <20260406165120.166928-3-wenzhaoliao@ruc.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> References: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b4daed03a2kunm562d3b0d647e6e X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVkaShpDVh5KHkpMT0JDQkxLSVYeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0 tZBg++ DKIM-Signature: a=rsa-sha256; b=BdXgailiNrsn8yC/dq8FY+M6JpXPpASFyXieM45D3hWn/cPVgh/uT2aYXrDL2PHSKx40AcROqNuhlFH7gWL1XsKcz2Unl1hh93gnRVceR384iXFdFyPno6MhLwTtGadoCSjrrLlZtlYZ7WCFPMJf4eJ9FrlDRPiZL1gavm9Ncn4=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=X16YJLGp7nPhHrC8mDgnpZbzv+DRjBcKHLYYvHcfrz0=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" Expose the UAPI header and the Linux I/O declarations needed by the Rust go= ldfish address-space driver. This keeps the driver-side code on typed Rust interfaces while still allowi= ng the binding and helper layers to see the header and memremap support req= uired by the abstraction patches that follow. Signed-off-by: Wenzhao Liao --- rust/bindings/bindings_helper.h | 1 + rust/uapi/uapi_helper.h | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 083cc44aa952..b0baff4c6349 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index 06d7d1a2e8da..ff19edab81da 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include --=20 2.34.1 From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m128214.netease.com (mail-m128214.netease.com [103.209.128.214]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2130F38C433; Mon, 6 Apr 2026 19:14:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=103.209.128.214 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775502858; cv=none; b=CNvCJpiD5ydppmTOhssG1SNtONC7xACZAwqRhcf/6GUWy5UVNI/Gg4QvEAaksVwCyYopSaS9wouRRxQjMDx9xnXHK6AHKT+dN2X//PSxPz/5HYR2NNZdP9DyhEpCUk1S2VlaUgd8ijjWvEKNvlGQg3AG7/fNdT9vRH/tnP0hVj4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775502858; c=relaxed/simple; bh=KnDk22NqfK2A5VmoRBMw4hkb6fV0LQGmL7Q7xj9UxpE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=qgDGGJyi1gut72bPIrcqyFuURJxV67xKp/NeQJCYUh/Fn3/LOKspIn0YuRSiftnzOfaUBo8/dPv0aY5eztM/OIS9czSuMOrpE6DI8QEkoj1W3N4shqfnUVwhqG6HyLLnApQKMUeg/qednaKtcrwBGTLyOsA7cV4fex/l4kdkokU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=cUnJcBHG; arc=none smtp.client-ip=103.209.128.214 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="cUnJcBHG" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbdd; Tue, 7 Apr 2026 00:51:34 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 3/6] rust: page: add helpers for page-backed ping state Date: Mon, 6 Apr 2026 12:51:17 -0400 Message-Id: <20260406165120.166928-4-wenzhaoliao@ruc.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> References: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b4eb5a03a2kunm562d3b0d647e7c X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVlCTRgaVkJOSh4eTksZSUsaSVYeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0 tZBg++ DKIM-Signature: a=rsa-sha256; b=cUnJcBHGiOx97/kgEgPi7Kd1EEfqAQImO7E/GBbnA46rDS77dufPinwtyGC8Gy8oWi02nrZ1qBHWhqJLQBRwmPUniLy/xWqWeBZg3pOWN0bSHsN5ppdXQhEqmCTuSnYJfyCZYvdkLgqwrP4jk1Dx3NNmJnAqJUVe1YKzkCJlMsY=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=kKwjlPbz8irjEUZ4cgjU35rxHOQIvjj9A2j4f+K6xtA=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" Add the minimal safe page helpers needed by the goldfish ping buffer: physi= cal address discovery plus bounded read, write, and zeroing operations. The driver uses these helpers to manage its per-file ping page entirely fro= m safe Rust while keeping the raw page mapping and pointer handling inside = the page abstraction. Signed-off-by: Wenzhao Liao --- rust/helpers/page.c | 5 +++++ rust/kernel/page.rs | 52 +++++++++++++++++++++++---------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/rust/helpers/page.c b/rust/helpers/page.c index f8463fbed2a2..05824bdc4fd8 100644 --- a/rust/helpers/page.c +++ b/rust/helpers/page.c @@ -20,6 +20,11 @@ __rust_helper void rust_helper_kunmap_local(const void *= addr) kunmap_local(addr); } =20 +__rust_helper phys_addr_t rust_helper_page_to_phys(struct page *page) +{ + return page_to_phys(page); +} + #ifndef NODE_NOT_IN_PAGE_FLAGS __rust_helper int rust_helper_page_to_nid(const struct page *page) { diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index adecb200c654..e8336d1bcc12 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -7,7 +7,7 @@ bindings, error::code::*, error::Result, - uaccess::UserSliceReader, + io::PhysAddr, }; use core::{ marker::PhantomData, @@ -198,6 +198,13 @@ pub fn nid(&self) -> i32 { unsafe { bindings::page_to_nid(self.as_ptr()) } } =20 + /// Returns the physical address of the start of this page. + #[inline] + pub fn phys_addr(&self) -> PhysAddr { + // SAFETY: `self.as_ptr()` is a live `struct page` owned by this `= Page`. + unsafe { bindings::page_to_phys(self.as_ptr()) } + } + /// Runs a piece of code with this page mapped to an address. /// /// The page is unmapped when this call returns. @@ -337,30 +344,25 @@ pub unsafe fn fill_zero_raw(&self, offset: usize, len= : usize) -> Result { }) } =20 - /// Copies data from userspace into this page. - /// - /// This method will perform bounds checks on the page offset. If `off= set .. offset+len` goes - /// outside of the page, then this call returns [`EINVAL`]. - /// - /// Like the other `UserSliceReader` methods, data races are allowed o= n the userspace address. - /// However, they are not allowed on the page you are copying into. - /// - /// # Safety - /// - /// Callers must ensure that this call does not race with a read or wr= ite to the same page that - /// overlaps with this write. - pub unsafe fn copy_from_user_slice_raw( - &self, - reader: &mut UserSliceReader, - offset: usize, - len: usize, - ) -> Result { - self.with_pointer_into_page(offset, len, move |dst| { - // SAFETY: If `with_pointer_into_page` calls into this closure= , then it has performed a - // bounds check and guarantees that `dst` is valid for `len` b= ytes. Furthermore, we have - // exclusive access to the slice since the caller guarantees t= hat there are no races. - reader.read_raw(unsafe { core::slice::from_raw_parts_mut(dst.c= ast(), len) }) - }) + /// Maps the page and reads from it into the given buffer. + pub fn read_slice(&self, dst: &mut [u8], offset: usize) -> Result { + // SAFETY: `dst` is a valid mutable slice for `dst.len()` bytes. S= afe Rust also prevents + // callers from obtaining a mutable reference to this `Page` while= this shared borrow + // exists, so concurrent writes through the safe API cannot overla= p with this read. + unsafe { self.read_raw(dst.as_mut_ptr(), offset, dst.len()) } + } + + /// Maps the page and writes the given buffer into it. + pub fn write_slice(&mut self, src: &[u8], offset: usize) -> Result { + // SAFETY: `src` is a valid immutable slice for `src.len()` bytes,= and `&mut self` + // guarantees unique access to the page through the safe API. + unsafe { self.write_raw(src.as_ptr(), offset, src.len()) } + } + + /// Zeroes a range within the page. + pub fn fill_zero(&mut self, offset: usize, len: usize) -> Result { + // SAFETY: `&mut self` guarantees unique access to the page throug= h the safe API. + unsafe { self.fill_zero_raw(offset, len) } } } =20 --=20 2.34.1 From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m81201.netease.com (mail-m81201.netease.com [47.88.81.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 59EC431985C; Mon, 6 Apr 2026 16:56:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=47.88.81.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775494623; cv=none; b=H7X7FeCUcMgLtwTnv6Bk/E+nEnyLy4Avm0TJxH4O3JgzU+Nmvi3+KJD3bSU7JPolE/E5lIrfLyG0ud6fKwbYTgTGTBzNH/vAPEkvQ3iiiytfy5+sVifbhaFFvSQHdEKmU+zxNY4NZIOLIw1wkRyN7aWKJ1luDU3LwIjcr17xI/g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775494623; c=relaxed/simple; bh=q24PgbY4dBpyVU6n1W0t5u+dpP3e1xtJm8tpu+KWqms=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ObnQq8iAxXfMNmLsnbmMO3d178m4jaq/cOiOzHHsltN34nVb+QTjKXjNUgmPs7XwJhQi+72CLgZrF09DkA1JR7lX9eGVr82/+BYopjAmObeqbiwz/wMVLA/8vY1gF+h1yLOM/YYP9Qq/9rnZf3LSu7RWdAU3r0msulNE94+Ojm8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=OIBZWDGJ; arc=none smtp.client-ip=47.88.81.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="OIBZWDGJ" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbde; Tue, 7 Apr 2026 00:51:38 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 4/6] rust: pci: add shared BAR memremap support Date: Mon, 6 Apr 2026 12:51:18 -0400 Message-Id: <20260406165120.166928-5-wenzhaoliao@ruc.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> References: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b4fbc603a2kunm562d3b0d647e84 X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVkaHRpPVhpJTE5DQh5ITx5IQlYeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSUhOQ0NVSktLVUtZBg ++ DKIM-Signature: a=rsa-sha256; b=OIBZWDGJEqJkkA2iSxbF45HhcYWe45TyqchoARpp4RJyzrduzeBQn2zcxBRGhqrfCfxacWKlk+upvfcQFJioeXB14moc1g1Pkc/b9IU1hq+JzmgP3QmODugKnf09WT0rgOozoN02KZiW/7vE+ZvPYyGHTrBBnIBM3ptkFRLAWIY=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=ZgC0mjQ5RQBGocLST6WuNwU3qiYPju/tIc008Tntz3c=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" Add a small Rust-owned abstraction for PCI BARs that back shared memory instead of register MMIO. The new SharedMemoryBar type owns both the BAR reservation and the memremap() lifetime, exposes the physical BAR start needed by the address-space ping path, and keeps the resource bookkeeping out of the Rust driver. The current RFC no longer exposes userspace mmap, but the driver still needs an owned shared-BAR reservation and the BAR's physical base for the ping path. Keeping the reservation/memremap() pairing in a Rust abstraction avoids pushing that lifetime bookkeeping back into driver code. Signed-off-by: Wenzhao Liao --- rust/kernel/pci.rs | 8 +++ rust/kernel/pci/id.rs | 2 +- rust/kernel/pci/io.rs | 112 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index af74ddff6114..4c63c931ffb2 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -47,6 +47,7 @@ ConfigSpaceSize, Extended, Normal, // + SharedMemoryBar, }; pub use self::irq::{ IrqType, @@ -458,6 +459,13 @@ pub fn set_master(&self) { // SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid = `struct pci_dev`. unsafe { bindings::pci_set_master(self.as_raw()) }; } + + /// Disable this PCI device. + #[inline] + pub fn disable_device(&self) { + // SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid = `struct pci_dev`. + unsafe { bindings::pci_disable_device(self.as_raw()) }; + } } =20 // SAFETY: `pci::Device` is a transparent wrapper of `struct pci_dev`. diff --git a/rust/kernel/pci/id.rs b/rust/kernel/pci/id.rs index 50005d176561..bd3cf17fd8de 100644 --- a/rust/kernel/pci/id.rs +++ b/rust/kernel/pci/id.rs @@ -156,7 +156,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Resul= t { impl Vendor { /// Create a Vendor from a raw 16-bit vendor ID. #[inline] - pub(super) fn from_raw(vendor_id: u16) -> Self { + pub const fn from_raw(vendor_id: u16) -> Self { Self(vendor_id) } =20 diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index fb6edab2aea7..89bf882b9634 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -7,6 +7,7 @@ bindings, device, devres::Devres, + ffi::{c_ulong, c_void}, io::{ io_define_read, io_define_write, @@ -17,11 +18,13 @@ MmioRaw, // }, prelude::*, - sync::aref::ARef, // + sync::aref::ARef, + types::ScopeGuard, }; use core::{ marker::PhantomData, ops::Deref, // + ptr::NonNull, }; =20 /// Represents the size of a PCI configuration space. @@ -285,6 +288,104 @@ fn deref(&self) -> &Self::Target { } } =20 +/// A cacheable shared-memory mapping of a PCI BAR created via `memremap()= `. +/// +/// This is intended for BARs that back shared memory rather than device r= egister MMIO. The +/// mapping owns both the underlying PCI region reservation and the `memre= map()` lifetime, so +/// driver code does not need to keep raw pointers or manually pair teardo= wn calls. +pub struct SharedMemoryBar { + pdev: ARef, + addr: NonNull, + phys_start: bindings::resource_size_t, + len: usize, + num: i32, +} + +// SAFETY: `SharedMemoryBar` owns a stable BAR reservation plus its `memre= map()` mapping. Moving +// the owner to another thread does not change the validity of the underly= ing PCI resource. +unsafe impl Send for SharedMemoryBar {} + +// SAFETY: Shared references only expose immutable metadata queries; the m= apped pointer itself is +// not exposed for dereferencing. +unsafe impl Sync for SharedMemoryBar {} + +impl SharedMemoryBar { + fn new(pdev: &Device, num: u32, name: &CStr) -> Result { + if !Bar::index_is_valid(num) { + return Err(EINVAL); + } + + let len =3D pdev.resource_len(num)?; + if len =3D=3D 0 { + return Err(ENXIO); + } + + let len =3D usize::try_from(len)?; + let phys_start =3D pdev.resource_start(num)?; + let num =3D i32::try_from(num)?; + + // SAFETY: + // - `pdev` is valid by the invariants of `Device`. + // - `num` is checked above. + // - `name` is a valid NUL-terminated string. + let ret =3D unsafe { bindings::pci_request_region(pdev.as_raw(), n= um, name.as_char_ptr()) }; + if ret !=3D 0 { + return Err(EBUSY); + } + + let release_region =3D ScopeGuard::new(|| { + // SAFETY: + // - `pdev` is still valid for the duration of this constructo= r. + // - `num` has just been successfully reserved. + unsafe { bindings::pci_release_region(pdev.as_raw(), num) }; + }); + + // SAFETY: + // - `phys_start`/`len` describe the BAR range we just reserved. + // - `MEMREMAP_WB` matches the external goldfish driver behaviour. + let addr =3D unsafe { bindings::memremap(phys_start, len, bindings= ::MEMREMAP_WB as c_ulong) }; + let addr =3D NonNull::new(addr.cast()).ok_or(ENOMEM)?; + + release_region.dismiss(); + + Ok(Self { + pdev: pdev.into(), + addr, + phys_start, + len, + num, + }) + } + + /// Returns the physical start address of the BAR. + #[inline] + pub fn phys_start(&self) -> bindings::resource_size_t { + self.phys_start + } + + /// Returns the BAR size in bytes. + #[inline] + pub fn len(&self) -> usize { + self.len + } + + fn release(&self) { + // SAFETY: + // - `self.addr` is a valid `memremap()` result owned by `self`. + // - `self.num` is the BAR region successfully reserved by `Self::= new`. + unsafe { + bindings::memunmap(self.addr.as_ptr().cast()); + bindings::pci_release_region(self.pdev.as_raw(), self.num); + } + } +} + +impl Drop for SharedMemoryBar { + fn drop(&mut self) { + self.release(); + } +} + impl Device { /// Maps an entire PCI BAR after performing a region-request on it. I/= O operation bound checks /// can be performed on compile time for offsets (plus the requested t= ype size) < SIZE. @@ -305,6 +406,15 @@ pub fn iomap_region<'a>( self.iomap_region_sized::<0>(bar, name) } =20 + /// Reserve and `memremap()` an entire PCI BAR as cacheable shared mem= ory. + pub fn memremap_bar<'a>( + &'a self, + bar: u32, + name: &'a CStr, + ) -> impl PinInit, Error> + 'a { + Devres::new(self.as_ref(), SharedMemoryBar::new(self, bar, name)) + } + /// Returns the size of configuration space. pub fn cfg_size(&self) -> ConfigSpaceSize { // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`. --=20 2.34.1 From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m60129.netease.com (mail-m60129.netease.com [210.79.60.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2BA9C396D2E; Mon, 6 Apr 2026 19:14:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.79.60.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775502863; cv=none; b=ualTWTSzsM2NeeC8eB6haAhA6YBOQK65zTXnf+ZFTn/aEfY/3zl1KykhJG0gKjAaKQIF7XH8pS2mUee5fPOImQOGZbNqaUqS8mpzmwosoz/HuKEfyygbEBJyet5zy6EPe3jBXUkhCwix27BRCCP8FKMb2w6XUlRa86xWV7MnIQ8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775502863; c=relaxed/simple; bh=ECGDkkNunsT7ol5JdVMZSZKKWi2mfnpcNCd1+Vi8iIQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=H7bLKAyxtBjhdgCmw74vxS203O3afo7ukSLoe3ctF3qKUsoyTSNip2tbeEKASIimwgtmFVnoKyxZzqvDnnmI5CmrBMy4A8SqGv6P8adobtBctyo1Jj94giCKgdtEhqe7lbGA3NXkSsctrNaMJVLgnpWk3oRsTsVpybmLfteE3Fo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=YMMAcelD; arc=none smtp.client-ip=210.79.60.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="YMMAcelD" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbdf; Tue, 7 Apr 2026 00:51:42 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 5/6] rust: miscdevice: harden registration and safe file_operations invariants Date: Mon, 6 Apr 2026 12:51:19 -0400 Message-Id: <20260406165120.166928-6-wenzhaoliao@ruc.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> References: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b50c3403a2kunm562d3b0d647e8b X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVkaTElDVk1CHh9ITx5NS04dSlYeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0 tZBg++ DKIM-Signature: a=rsa-sha256; b=YMMAcelD5EoMn8NHp9n6qlxUOGKmMtUALBcVlOR799f6xXzFfv810WTv5EMVb/Afq0/YTUqVWRyhicvH0q6gU7aTwqXi3w2q2j5+otxhCSYTae2KW0WjqjYxig3FbwohbLIkTom03/0BKIDZT6EMG9iCT2DjBst+9RjUKxgFfgA=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=83Aos2kKrx0e0kDnA4rTmWci8dP3/t1joOgL0B9Gz+g=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" Extend miscdevice registration with typed per-device data that open() can read through a publication-safe context, and move raw file_operations exposure behind an internal vtable boundary generated by declare_misc_device_fops!(). This keeps safe open() on pre-publication state, binds file_operations.owner to THIS_MODULE for safe drivers, and keeps the private_data ownership protocol inside the abstraction instead of in driver code. The goldfish driver uses the typed registration data to pass its runtime into open() without raw casts or container traversal. Signed-off-by: Wenzhao Liao --- rust/kernel/miscdevice.rs | 409 +++++++++++++++++++++++-------- samples/rust/rust_misc_device.rs | 9 +- 2 files changed, 306 insertions(+), 112 deletions(-) diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index c3c2052c9206..c2db81cd5da2 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -9,7 +9,8 @@ //! Reference: =20 use crate::{ - bindings, + alloc::KBox, + bindings, container_of, device::Device, error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR}, ffi::{c_int, c_long, c_uint, c_ulong}, @@ -18,9 +19,15 @@ mm::virt::VmaNew, prelude::*, seq_file::SeqFile, - types::{ForeignOwnable, Opaque}, + sync::aref::ARef, + types::{ForeignOwnable, Opaque, ScopeGuard}, +}; +use core::{ + marker::{PhantomData, PhantomPinned}, + pin::Pin, + ptr::drop_in_place, + sync::atomic::{AtomicBool, Ordering}, }; -use core::{marker::PhantomData, pin::Pin}; =20 /// Options for creating a misc device. #[derive(Copy, Clone)] @@ -31,94 +38,258 @@ pub struct MiscDeviceOptions { =20 impl MiscDeviceOptions { /// Create a raw `struct miscdev` ready for registration. - pub const fn into_raw(self) -> bindings::miscdevice { + pub fn into_raw(self) -> bindings::misc= device { let mut result: bindings::miscdevice =3D pin_init::zeroed(); result.minor =3D bindings::MISC_DYNAMIC_MINOR as ffi::c_int; result.name =3D crate::str::as_char_ptr_in_const_context(self.name= ); - result.fops =3D MiscdeviceVTable::::build(); + result.fops =3D T::file_operations(); result } } =20 +/// Generates the `MiscDeviceVTable` implementation for a concrete miscdev= ice type. +/// +/// Place this macro after `impl MiscDevice for ...`. +/// +/// The generated implementation always binds `file_operations.owner` to t= he current module's +/// `THIS_MODULE`, so safe drivers cannot accidentally publish owner-less = or foreign-owned misc +/// device callbacks. +#[macro_export] +macro_rules! declare_misc_device_fops { + ($type:ty) =3D> { + // SAFETY: This implements the standard Rust miscdevice vtable gen= erated by + // `build_file_operations()`, which wires up owner/module pinning = and the private-data + // protocol enforced by this abstraction. + unsafe impl $crate::miscdevice::MiscDeviceVTable for $type { + fn file_operations() -> &'static $crate::bindings::file_operat= ions { + struct AssertSync(T); + // SAFETY: This wrapper is only used for immutable `file_o= perations` tables stored + // in a `static`. + unsafe impl Sync for AssertSync {} + + static FOPS: AssertSync<$crate::bindings::file_operations>= =3D AssertSync( + $crate::miscdevice::build_file_operations::<$type>(THI= S_MODULE.as_ptr()), + ); + + &FOPS.0 + } + } + }; +} + +#[repr(C)] +struct RegistrationBacking { + misc: Opaque, + data: T::RegistrationData, + owner: *const MiscDeviceRegistration, + registered: AtomicBool, +} + +struct OpenFile { + data: *mut ffi::c_void, + _t: PhantomData, +} + +impl OpenFile { + fn empty() -> Self { + Self { + data: core::ptr::null_mut(), + _t: PhantomData, + } + } + + fn borrow(&self) -> ::Borrowed<'_> { + // SAFETY: `self.data` comes from `T::Ptr::into_foreign()` and is = only converted back in + // `release`, after all borrows from this file operation callback = have ended. + unsafe { ::borrow(self.data) } + } +} + /// A registration of a miscdevice. /// /// # Invariants /// -/// - `inner` contains a `struct miscdevice` that is registered using -/// `misc_register()`. -/// - This registration remains valid for the entire lifetime of the -/// [`MiscDeviceRegistration`] instance. -/// - Deregistration occurs exactly once in [`Drop`] via `misc_deregister(= )`. -/// - `inner` wraps a valid, pinned `miscdevice` created using +/// - `backing.misc` contains a valid `struct miscdevice` created using /// [`MiscDeviceOptions::into_raw`]. -#[repr(transparent)] +/// - When `backing.registered` is `true`, `backing.misc` is registered us= ing +/// `misc_register()`. +/// - Before `misc_register()` publishes `backing.misc`, every field reach= able through the safe +/// open context (`backing.data` and `backing.owner`) is fully initializ= ed. +/// - `backing.owner` points back to this wrapper for the entire time the = miscdevice is registered. +/// - Deregistration occurs at most once, either via [`MiscDeviceRegistrat= ion::deregister`] or +/// [`Drop`]. #[pin_data(PinnedDrop)] -pub struct MiscDeviceRegistration { +pub struct MiscDeviceRegistration { + backing: KBox>, #[pin] - inner: Opaque, + _pin: PhantomPinned, _t: PhantomData, } =20 // SAFETY: It is allowed to call `misc_deregister` on a different thread f= rom where you called // `misc_register`. -unsafe impl Send for MiscDeviceRegistration {} +unsafe impl Send for MiscDeviceRegistration {} // SAFETY: All `&self` methods on this type are written to ensure that it = is safe to call them in // parallel. -unsafe impl Sync for MiscDeviceRegistration {} +unsafe impl Sync for MiscDeviceRegistration {} =20 -impl MiscDeviceRegistration { +impl MiscDeviceRegistration { /// Register a misc device. - pub fn register(opts: MiscDeviceOptions) -> impl PinInit { - try_pin_init!(Self { - inner <- Opaque::try_ffi_init(move |slot: *mut bindings::miscd= evice| { - // SAFETY: The initializer can write to the provided `slot= `. - unsafe { slot.write(opts.into_raw::()) }; - - // SAFETY: We just wrote the misc device options to the sl= ot. The miscdevice will - // get unregistered before `slot` is deallocated because t= he memory is pinned and - // the destructor of this type deallocates the memory. - // INVARIANT: If this returns `Ok(())`, then the `slot` wi= ll contain a registered - // misc device. - to_result(unsafe { bindings::misc_register(slot) }) - }), - _t: PhantomData, - }) + pub fn register(opts: MiscDeviceOptions) -> impl PinInit + where + T: MiscDevice + MiscDeviceVTable, + { + Self::register_with_data(opts, ()) + } + + /// Register a misc device together with driver-defined registration d= ata. + pub fn register_with_data( + opts: MiscDeviceOptions, + data: T::RegistrationData, + ) -> impl PinInit + where + T: MiscDeviceVTable, + { + let init =3D move |slot: *mut Self| { + let backing =3D KBox::new( + RegistrationBacking { + misc: Opaque::new(opts.into_raw::()), + data, + owner: slot.cast_const(), + registered: AtomicBool::new(false), + }, + GFP_KERNEL, + )?; + + // SAFETY: `slot` is valid for writes for the duration of this= initializer. + unsafe { + slot.write(Self { + backing, + _pin: PhantomPinned, + _t: PhantomData, + }) + }; + + // SAFETY: `slot` points to the fully-initialized registration= wrapper we just wrote + // above. + let this =3D unsafe { &mut *slot }; + // SAFETY: `this.as_raw()` points at the fully initialized `st= ruct miscdevice` + // contained in the heap-backed registration backing. + let ret =3D to_result(unsafe { bindings::misc_register(this.as= _raw()) }); + if let Err(err) =3D ret { + // SAFETY: The wrapper was fully initialized above, so dro= pping it here correctly + // releases the heap-backed registration backing. + unsafe { drop_in_place(slot) }; + return Err(err); + } + + this.backing.registered.store(true, Ordering::Release); + Ok(()) + }; + + // SAFETY: + // - On success, the closure writes a fully-initialized `Self` int= o `slot` before making + // the miscdevice visible via `misc_register()`. All state obser= vable through the safe + // open context is initialized before publication. + // - On failure after the write, it drops the initialized value be= fore returning. + unsafe { pin_init::pin_init_from_closure(init) } + } + + /// Returns the registration wrapper for a raw `struct miscdevice` poi= nter. + /// + /// # Safety + /// + /// `misc` must point at the `misc` field of a live [`RegistrationBack= ing`]. + unsafe fn from_raw_misc<'a>(misc: *mut bindings::miscdevice) -> &'a Se= lf { + // SAFETY: The caller guarantees that `misc` points at the `misc` = field of a live + // `RegistrationBacking`, whose `owner` points back to the live= registration wrapper. + let backing =3D + unsafe { &*container_of!(Opaque::cast_from(misc), Registration= Backing, misc) }; + // SAFETY: By the type invariant, `owner` points at the live wrapp= er that owns `backing`. + unsafe { &*backing.owner } } =20 /// Returns a raw pointer to the misc device. pub fn as_raw(&self) -> *mut bindings::miscdevice { - self.inner.get() + self.backing.misc.get() + } + + /// Returns the registration data that was supplied at registration ti= me. + pub fn data(&self) -> &T::RegistrationData { + &self.backing.data + } + + fn deregister_inner(backing: &RegistrationBacking) { + if backing.registered.swap(false, Ordering::AcqRel) { + // SAFETY: `registered =3D=3D true` guarantees that the miscde= vice was successfully + // registered and has not been deregistered yet. + unsafe { bindings::misc_deregister(backing.misc.get()) }; + } } =20 - /// Access the `this_device` field. - pub fn device(&self) -> &Device { - // SAFETY: This can only be called after a successful register(), = which always - // initialises `this_device` with a valid device. Furthermore, the= signature of this - // function tells the borrow-checker that the `&Device` reference = must not outlive the - // `&MiscDeviceRegistration` used to obtain it, so the last use= of the reference must be - // before the underlying `struct miscdevice` is destroyed. - unsafe { Device::from_raw((*self.as_raw()).this_device) } + /// Deregister this misc device if it is still registered. + /// + /// After this returns, the misc core will no longer route new opens t= o [`MiscDevice::open`]. + /// Existing open files keep their own pinned `file_operations` table = and private data and must + /// be drained by the driver before it tears down device-side resource= s that those file handles + /// still own. + pub fn deregister(&self) { + Self::deregister_inner(&self.backing); } } =20 #[pinned_drop] -impl PinnedDrop for MiscDeviceRegistration { +impl PinnedDrop for MiscDeviceRegistration { fn drop(self: Pin<&mut Self>) { - // SAFETY: We know that the device is registered by the type invar= iants. - unsafe { bindings::misc_deregister(self.inner.get()) }; + let this =3D self.project(); + Self::deregister_inner(this.backing); + } +} + +/// Publication-safe context passed to [`MiscDevice::open`]. +pub struct MiscDeviceOpenContext<'a, T: MiscDevice + 'static> { + registration: &'a MiscDeviceRegistration, + device: ARef, +} + +impl<'a, T: MiscDevice + 'static> MiscDeviceOpenContext<'a, T> { + /// Returns the registration data supplied at registration time. + pub fn data(&self) -> &T::RegistrationData { + self.registration.data() + } + + /// Returns the class device backing this miscdevice open. + pub fn device(&self) -> ARef { + self.device.clone() } } =20 +/// Internal trait that supplies the concrete `file_operations` table used= for a Rust miscdevice. +/// +/// # Safety +/// +/// Implementations must return a stable `file_operations` table produced = by this abstraction so +/// that owner/module pinning and the private-data protocol remain intact.= Drivers should use +/// [`declare_misc_device_fops!`] instead of implementing this trait manua= lly. +#[doc(hidden)] +pub unsafe trait MiscDeviceVTable: MiscDevice + 'static { + /// Returns the `file_operations` table for this miscdevice implementa= tion. + fn file_operations() -> &'static bindings::file_operations; +} + /// Trait implemented by the private data of an open misc device. #[vtable] pub trait MiscDevice: Sized { /// What kind of pointer should `Self` be wrapped in. type Ptr: ForeignOwnable + Send + Sync; =20 + /// Driver-defined data stored in the miscdevice registration. + type RegistrationData: Send + Sync + 'static; + /// Called when the misc device is opened. /// /// The returned pointer will be stored as the private data for the fi= le. - fn open(_file: &File, _misc: &MiscDeviceRegistration) -> Result<= Self::Ptr>; + fn open(_file: &File, _ctx: &MiscDeviceOpenContext<'_, Self>) -> Resul= t; =20 /// Called when the misc device is released. fn release(device: Self::Ptr, _file: &File) { @@ -195,7 +366,45 @@ fn show_fdinfo( /// A vtable for the file operations of a Rust miscdevice. struct MiscdeviceVTable(PhantomData); =20 -impl MiscdeviceVTable { +impl MiscdeviceVTable { + const fn build(owner: *mut bindings::module) -> bindings::file_operati= ons { + bindings::file_operations { + owner, + open: Some(Self::open), + release: Some(Self::release), + mmap: if T::HAS_MMAP { Some(Self::mmap) } else { None }, + read_iter: if T::HAS_READ_ITER { + Some(Self::read_iter) + } else { + None + }, + write_iter: if T::HAS_WRITE_ITER { + Some(Self::write_iter) + } else { + None + }, + unlocked_ioctl: if T::HAS_IOCTL { + Some(Self::ioctl) + } else { + None + }, + #[cfg(CONFIG_COMPAT)] + compat_ioctl: if T::HAS_COMPAT_IOCTL { + Some(Self::compat_ioctl) + } else if T::HAS_IOCTL { + bindings::compat_ptr_ioctl + } else { + None + }, + show_fdinfo: if T::HAS_SHOW_FDINFO { + Some(Self::show_fdinfo) + } else { + None + }, + ..pin_init::zeroed() + } + } + /// # Safety /// /// `file` and `inode` must be the file and inode for a file that is u= ndergoing initialization. @@ -214,25 +423,38 @@ impl MiscdeviceVTable { // associated `struct miscdevice` before calling into this method.= Furthermore, // `misc_open()` ensures that the miscdevice can't be unregistered= and freed during this // call to `fops_open`. - let misc =3D unsafe { &*misc_ptr.cast::>= () }; + let misc =3D unsafe { MiscDeviceRegistration::::from_raw_misc(m= isc_ptr.cast()) }; =20 // SAFETY: // * This underlying file is valid for (much longer than) the dura= tion of `T::open`. // * There is no active fdget_pos region on the file on this threa= d. let file =3D unsafe { File::from_raw_file(raw_file) }; =20 - let ptr =3D match T::open(file, misc) { + // SAFETY: `misc_open()` serializes with `misc_deregister()` via `= misc_mtx`, so the class + // device remains live for the duration of this callback. Taking a= n extra reference here + // lets the safe open context own the device independently from la= ter teardown. + let ctx =3D MiscDeviceOpenContext { + registration: misc, + device: unsafe { Device::get_device((*misc.as_raw()).this_devi= ce) }, + }; + + let ptr =3D match T::open(file, &ctx) { Ok(ptr) =3D> ptr, Err(err) =3D> return err.to_errno(), }; + let ptr =3D ScopeGuard::new_with_data(ptr, |ptr| T::release(ptr, f= ile)); =20 - // This overwrites the private data with the value specified by th= e user, changing the type - // of this file's private data. All future accesses to the private= data is performed by - // other fops_* methods in this file, which all correctly cast the= private data to the new - // type. + let mut open_file =3D match KBox::new(OpenFile::::empty(), GFP_= KERNEL) { + Ok(open_file) =3D> open_file, + Err(_) =3D> return ENOMEM.to_errno(), + }; + open_file.data =3D ptr.dismiss().into_foreign(); + + // This overwrites the private data with a small Rust-owned wrappe= r that keeps the module + // pinned for the full file lifetime and owns the driver's foreign= private data handle. // // SAFETY: The open call of a file can access the private data. - unsafe { (*raw_file).private_data =3D ptr.into_foreign() }; + unsafe { (*raw_file).private_data =3D open_file.into_foreign() }; =20 0 } @@ -243,14 +465,17 @@ impl MiscdeviceVTable { /// must be associated with a `MiscDeviceRegistration`. unsafe extern "C" fn release(_inode: *mut bindings::inode, file: *mut = bindings::file) -> c_int { // SAFETY: The release call of a file owns the private data. - let private =3D unsafe { (*file).private_data }; - // SAFETY: The release call of a file owns the private data. - let ptr =3D unsafe { ::from_foreign(priv= ate) }; + let open_file =3D + unsafe { > as ForeignOwnable>::from_foreign((= *file).private_data) }; + let data =3D open_file.data; =20 // SAFETY: // * The file is valid for the duration of this call. // * There is no active fdget_pos region on the file on this threa= d. - T::release(ptr, unsafe { File::from_raw_file(file) }); + T::release( + unsafe { ::from_foreign(data) }, + unsafe { File::from_raw_file(file) }, + ); =20 0 } @@ -304,11 +529,9 @@ impl MiscdeviceVTable { vma: *mut bindings::vm_area_struct, ) -> c_int { // SAFETY: The mmap call of a file can access the private data. - let private =3D unsafe { (*file).private_data }; - // SAFETY: This is a Rust Miscdevice, so we call `into_foreign` in= `open` and - // `from_foreign` in `release`, and `fops_mmap` is guaranteed to b= e called between those - // two operations. - let device =3D unsafe { ::borrow(private= .cast()) }; + let open_file =3D + unsafe { > as ForeignOwnable>::borrow((*file)= .private_data) }; + let device =3D open_file.borrow(); // SAFETY: The caller provides a vma that is undergoing initial VM= A setup. let area =3D unsafe { VmaNew::from_raw(vma) }; // SAFETY: @@ -327,9 +550,9 @@ impl MiscdeviceVTable { /// `file` must be a valid file that is associated with a `MiscDeviceR= egistration`. unsafe extern "C" fn ioctl(file: *mut bindings::file, cmd: c_uint, arg= : c_ulong) -> c_long { // SAFETY: The ioctl call of a file can access the private data. - let private =3D unsafe { (*file).private_data }; - // SAFETY: Ioctl calls can borrow the private data of the file. - let device =3D unsafe { ::borrow(private= ) }; + let open_file =3D + unsafe { > as ForeignOwnable>::borrow((*file)= .private_data) }; + let device =3D open_file.borrow(); =20 // SAFETY: // * The file is valid for the duration of this call. @@ -352,9 +575,9 @@ impl MiscdeviceVTable { arg: c_ulong, ) -> c_long { // SAFETY: The compat ioctl call of a file can access the private = data. - let private =3D unsafe { (*file).private_data }; - // SAFETY: Ioctl calls can borrow the private data of the file. - let device =3D unsafe { ::borrow(private= ) }; + let open_file =3D + unsafe { > as ForeignOwnable>::borrow((*file)= .private_data) }; + let device =3D open_file.borrow(); =20 // SAFETY: // * The file is valid for the duration of this call. @@ -373,9 +596,9 @@ impl MiscdeviceVTable { /// - `seq_file` must be a valid `struct seq_file` that we can write t= o. unsafe extern "C" fn show_fdinfo(seq_file: *mut bindings::seq_file, fi= le: *mut bindings::file) { // SAFETY: The release call of a file owns the private data. - let private =3D unsafe { (*file).private_data }; - // SAFETY: Ioctl calls can borrow the private data of the file. - let device =3D unsafe { ::borrow(private= ) }; + let open_file =3D + unsafe { > as ForeignOwnable>::borrow((*file)= .private_data) }; + let device =3D open_file.borrow(); // SAFETY: // * The file is valid for the duration of this call. // * There is no active fdget_pos region on the file on this threa= d. @@ -386,43 +609,11 @@ impl MiscdeviceVTable { =20 T::show_fdinfo(device, m, file); } +} =20 - const VTABLE: bindings::file_operations =3D bindings::file_operations { - open: Some(Self::open), - release: Some(Self::release), - mmap: if T::HAS_MMAP { Some(Self::mmap) } else { None }, - read_iter: if T::HAS_READ_ITER { - Some(Self::read_iter) - } else { - None - }, - write_iter: if T::HAS_WRITE_ITER { - Some(Self::write_iter) - } else { - None - }, - unlocked_ioctl: if T::HAS_IOCTL { - Some(Self::ioctl) - } else { - None - }, - #[cfg(CONFIG_COMPAT)] - compat_ioctl: if T::HAS_COMPAT_IOCTL { - Some(Self::compat_ioctl) - } else if T::HAS_IOCTL { - bindings::compat_ptr_ioctl - } else { - None - }, - show_fdinfo: if T::HAS_SHOW_FDINFO { - Some(Self::show_fdinfo) - } else { - None - }, - ..pin_init::zeroed() - }; - - const fn build() -> &'static bindings::file_operations { - &Self::VTABLE - } +#[doc(hidden)] +pub const fn build_file_operations( + owner: *mut bindings::module, +) -> bindings::file_operations { + MiscdeviceVTable::::build(owner) } diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_devi= ce.rs index 87a1fe63533a..f2d7a98a5715 100644 --- a/samples/rust/rust_misc_device.rs +++ b/samples/rust/rust_misc_device.rs @@ -100,7 +100,7 @@ fs::{File, Kiocb}, ioctl::{_IO, _IOC_SIZE, _IOR, _IOW}, iov::{IovIterDest, IovIterSource}, - miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration}, + miscdevice::{MiscDevice, MiscDeviceOpenContext, MiscDeviceOptions, Mis= cDeviceRegistration}, new_mutex, prelude::*, sync::{aref::ARef, Mutex}, @@ -154,9 +154,10 @@ struct RustMiscDevice { #[vtable] impl MiscDevice for RustMiscDevice { type Ptr =3D Pin>; + type RegistrationData =3D (); =20 - fn open(_file: &File, misc: &MiscDeviceRegistration) -> Result>> { - let dev =3D ARef::from(misc.device()); + fn open(_file: &File, ctx: &MiscDeviceOpenContext<'_, Self>) -> Result= >> { + let dev =3D ctx.device(); =20 dev_info!(dev, "Opening Rust Misc Device Sample\n"); =20 @@ -222,6 +223,8 @@ fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u= 32, arg: usize) -> Result } } =20 +kernel::declare_misc_device_fops!(RustMiscDevice); + #[pinned_drop] impl PinnedDrop for RustMiscDevice { fn drop(self: Pin<&mut Self>) { --=20 2.34.1 From nobody Sun Jun 21 06:31:16 2026 Received: from mail-m81141.netease.com (mail-m81141.netease.com [47.88.81.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 10C862DCF55; Mon, 6 Apr 2026 22:34:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=47.88.81.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775514901; cv=none; b=cpb2YSDDafKd4zAzlKtRN+0+VXO6zaOucmfAAnu911v/wAul8ER7FdRIGHDqyKDELuQEVNVF+uqF7+6rKNFf7yGcfuOwiAyWEfEAVBm4lk/7E1NL1gBhxjgIW7YqRWbN4ONIqcLmNFLITZq21YCkZO8LkuqzbxUcs0xqwAHT0TQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775514901; c=relaxed/simple; bh=dRHAE3I/SWqaGNIaJvkyVijNPIt2j+9ekFM0wXRPM/M=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=gqWIHRqq5luBN+jT9js04syEZ7aYfFJ5XctDNc5bdfu8yDakCUUVpaU7GPo9c5KzPKplGHgh+HO5Tvw3k8fLy62AbVvbG9zC0lWc/+zf5X/rBNyVwWvR9LggdDLcB6PVveobztLkH/hRisG+aXPcfnItUzgZN/LrLPboESkaCtU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn; spf=pass smtp.mailfrom=ruc.edu.cn; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b=XBBJMEfR; arc=none smtp.client-ip=47.88.81.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ruc.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ruc.edu.cn header.i=@ruc.edu.cn header.b="XBBJMEfR" Received: from lwz.tail698a0e.ts.net (gy-adaptive-ssl-proxy-1-entmail-virt204.gy.ntes [36.112.3.244]) by smtp.qiye.163.com (Hmail) with ESMTP id 39aaefbe1; Tue, 7 Apr 2026 00:51:46 +0800 (GMT+08:00) From: Wenzhao Liao To: rust-for-linux@vger.kernel.org, linux-pci@vger.kernel.org Cc: ojeda@kernel.org, dakr@kernel.org, bhelgaas@google.com, kwilczynski@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org Subject: [RFC PATCH v3 6/6] platform/goldfish: add Rust goldfish_address_space 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: <20260406165120.166928-1-wenzhaoliao@ruc.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-HM-Tid: 0a9d63b51ca203a2kunm562d3b0d647e93 X-HM-MType: 10 X-HM-Spam-Status: e1kfGhgUHx5ZQUpXWQgPGg8OCBgUHx5ZQUlOS1dZFg8aDwILHllBWSg2Ly tZV1koWUFITzdXWS1ZQUlXWQ8JGhUIEh9ZQVlCHhpLVk9CTBkYHkMYQxhLH1YeHw5VEwETFhoSFy QUDg9ZV1kYEgtZQVlITVVKSklVSFVJT09ZV1kWGg8SFR0UWUFZT0tIVUpLSEpOTE5VSktLVUpCS0 tZBg++ DKIM-Signature: a=rsa-sha256; b=XBBJMEfRtlBC9iBF8DurdyoNTlCN1ZcrjeNulVRYg+NL3Ai5nwGU2kBWCXkUXHYfzKyJ8LGvc295BphjGd938chplOh95R5+ipVjHScfIzRYr+Xvv/mKgXam/sm27HuSGs1XyKxTzdKV8niGE3NMFaxSsjdPzAbxBGtmzTrl7vk=; c=relaxed/relaxed; s=default; d=ruc.edu.cn; v=1; bh=ED5Mu0ieMlJgd4INk+b0oEYvkKv8bjy1kvcUcApERQs=; h=date:mime-version:subject:message-id:from; Content-Type: text/plain; charset="utf-8" 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 --- 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@vger.kernel.org L: linux-pci@vger.kernel.org L: rust-for-linux@vger.kernel.org S: Maintained +F: drivers/platform/goldfish/goldfish_address_space.rs F: include/uapi/linux/goldfish_address_space.h +K: \bGOLDFISH_ADDRESS_SPACE\b =20 ANDROID GOLDFISH RTC DRIVER M: Jiaxun Yang 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. =20 +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) +=3D goldfish_pipe.o +obj-$(CONFIG_GOLDFISH_ADDRESS_SPACE) +=3D 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, Mis= cDeviceRegistration}, + 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 =3D 0; +const GOLDFISH_AS_AREA_BAR: u32 =3D 1; +const GOLDFISH_AS_VENDOR_ID: u32 =3D 0x607d; +const GOLDFISH_AS_DEVICE_ID: u32 =3D 0xf153; +const GOLDFISH_AS_SUPPORTED_REVISION: u8 =3D 1; +const GOLDFISH_AS_INVALID_HANDLE: u32 =3D u32::MAX; + +const GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC: u32 =3D uapi::GOLDFISH_ADDRESS_S= PACE_IOCTL_MAGIC as u32; +const GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK: u32 =3D + ioctl::_IOWR::(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC,= 10); +const GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK: u32 =3D + ioctl::_IOWR::(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 11); +const GOLDFISH_ADDRESS_SPACE_IOCTL_PING: u32 =3D + ioctl::_IOWR::(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 12); +const GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED: u32 =3D + ioctl::_IOWR::(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 1= 3); +const GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED: u32 =3D + ioctl::_IOWR::(GOLDFISH_ADDRESS_SPACE_IOCTL_MAGIC, 14); + +struct Registers; + +impl Registers { + const COMMAND: usize =3D 0; + const STATUS: usize =3D 4; + const GUEST_PAGE_SIZE: usize =3D 8; + const BLOCK_SIZE_LOW: usize =3D 12; + const BLOCK_SIZE_HIGH: usize =3D 16; + const BLOCK_OFFSET_LOW: usize =3D 20; + const BLOCK_OFFSET_HIGH: usize =3D 24; + const PING: usize =3D 28; + const PING_INFO_ADDR_LOW: usize =3D 32; + const PING_INFO_ADDR_HIGH: usize =3D 36; + const HANDLE: usize =3D 40; + const PHYS_START_LOW: usize =3D 44; + const PHYS_START_HIGH: usize =3D 48; + const END: usize =3D 56; +} + +#[repr(u32)] +#[derive(Clone, Copy)] +enum CommandId { + AllocateBlock =3D 1, + DeallocateBlock =3D 2, + GenHandle =3D 3, + DestroyHandle =3D 4, + TellPingInfoAddr =3D 5, +} + +type ControlBar =3D pci::Bar<{ Registers::END }>; + +#[derive(Clone, Copy)] +struct Block { + offset: u64, + size: u64, +} + +struct BlockSet { + blocks: KVVec, +} + +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 { + let index =3D self + .blocks + .iter() + .position(|block| block.offset =3D=3D offset) + .ok_or(ENXIO)?; + self.blocks.remove(index).map_err(|_| EINVAL) + } + + fn iter(&self) -> impl Iterator + '_ { + self.blocks.iter().copied() + } + + fn clear(&mut self) { + let _ =3D self.take_all(); + } + + fn take_all(&mut self) -> KVVec { + let mut blocks =3D 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 =3D 48; + + fn encode(self) -> [u8; Self::ENCODED_LEN] { + let mut bytes =3D [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 =3D 24; + + fn encode(self) -> [u8; Self::ENCODED_LEN] { + let mut bytes =3D [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 =3D 40; + + fn encode(self) -> [u8; Self::ENCODED_LEN] { + let mut bytes =3D [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 =3D 16; + + fn encode(self) -> [u8; Self::ENCODED_LEN] { + let mut bytes =3D [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 { + let mut page =3D 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 { + let shared_phys_start =3D 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: Phy= sAddr) -> Result { + let header =3D 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 =3D [0u8; PingInfoHeader::ENCODED_LEN]; + self.page.read_slice(&mut bytes, 0)?; + let header =3D PingInfoHeader::decode(&bytes); + request.offset =3D header.offset; + request.size =3D header.size; + request.metadata =3D header.metadata; + request.version =3D header.version; + request.wait_fd =3D header.wait_fd; + request.wait_flags =3D header.wait_flags; + request.direction =3D header.direction; + Ok(()) + } +} + +#[pin_data] +struct DeviceRuntime { + #[pin] + control_bar: Devres, + #[pin] + shared_bar: Devres, + #[pin] + registers_lock: Mutex<()>, + #[pin] + lifecycle: Mutex, + #[pin] + lifecycle_idle: CondVar, +} + +struct RuntimeLifecycleState { + accepting_new_ops: bool, + active_ops: usize, + live_files: KVVec>, +} + +struct RuntimeOpGuard { + runtime: Arc, +} + +impl Drop for RuntimeOpGuard { + fn drop(&mut self) { + let mut state =3D self.runtime.lifecycle.lock(); + state.active_ops -=3D 1; + self.runtime.notify_if_idle(&state); + } +} + +impl DeviceRuntime { + fn new(pdev: &pci::Device) -> Result> { + 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/lif= ecycle_idle"), + }), + GFP_KERNEL, + ) + } + + fn notify_if_idle(&self, state: &RuntimeLifecycleState) { + if !state.accepting_new_ops && state.active_ops =3D=3D 0 { + self.lifecycle_idle.notify_all(); + } + } + + fn begin_operation(self: &Arc) -> Result { + let mut state =3D self.lifecycle.lock(); + if !state.accepting_new_ops { + return Err(ENODEV); + } + + state.active_ops =3D state.active_ops.checked_add(1).ok_or(EBUSY)?; + drop(state); + + Ok(RuntimeOpGuard { + runtime: self.clone(), + }) + } + + fn register_live_file(&self, file: Arc) -> Result { + let mut state =3D 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) { + let mut state =3D self.lifecycle.lock(); + let Some(index) =3D state + .live_files + .iter() + .position(|entry| Arc::ptr_eq(entry, file)) + else { + return; + }; + + if let Ok(entry) =3D state.live_files.remove(index) { + drop(entry); + } + } + + fn shutdown(&self) { + let mut state =3D self.lifecycle.lock(); + // `unbind()` removes miscdevice reachability before calling `shut= down()`. 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 d= escriptors. + state.accepting_new_ops =3D false; + + while state.active_ops !=3D 0 { + self.lifecycle_idle.wait(&mut state); + } + + let mut live_files =3D 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 + '_> { + self.control_bar.try_access().ok_or(ENXIO) + } + + fn shared_bar(&self) -> Result + '_> { + self.shared_bar.try_access().ok_or(ENXIO) + } + + fn run_command_locked(control: &ControlBar, command: CommandId) -> Res= ult { + control.write32(command as u32, Registers::COMMAND); + + let status =3D i32::try_from(control.read32(Registers::STATUS)).ma= p_err(|_| EIO)?; + if status =3D=3D 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: usi= ze, 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: usiz= e) -> u64 { + u64::from(control.read32(low_offset)) | (u64::from(control.read32(= high_offset)) << 32) + } + + fn program_host_visible_state(&self) -> Result { + let control =3D self.control_bar()?; + let shared =3D self.shared_bar()?; + let phys_start =3D 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 { + Ok(self.shared_bar()?.phys_start()) + } + + fn generate_handle(&self) -> Result { + let _guard =3D self.registers_lock.lock(); + let control =3D 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 =3D control.read32(Registers::HANDLE); + if handle =3D=3D GOLDFISH_AS_INVALID_HANDLE { + return Err(EINVAL); + } + + Ok(handle) + } + + fn tell_ping_info_addr(&self, handle: u32, ping_info_phys: PhysAddr) -= > Result { + let _guard =3D self.registers_lock.lock(); + let control =3D self.control_bar()?; + let ping_info_phys =3D u64::try_from(ping_info_phys).map_err(|_| E= OVERFLOW)?; + + 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 t= he echoed physical + // address rather than through the status register. + Self::issue_command_locked(&control, CommandId::TellPingInfoAddr); + + let returned =3D Self::read_u64( + &control, + Registers::PING_INFO_ADDR_LOW, + Registers::PING_INFO_ADDR_HIGH, + ); + if returned !=3D ping_info_phys { + return Err(EINVAL); + } + + Ok(()) + } + + fn destroy_handle(&self, handle: u32) -> Result { + let _guard =3D self.registers_lock.lock(); + let control =3D self.control_bar()?; + control.write32(handle, Registers::HANDLE); + Self::issue_command_locked(&control, CommandId::DestroyHandle); + Ok(()) + } + + fn allocate_block(&self, size: u64) -> Result { + let _guard =3D self.registers_lock.lock(); + let control =3D 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 =3D self.registers_lock.lock(); + let control =3D 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 =3D self.registers_lock.lock(); + self.control_bar()?.write32(handle, Registers::PING); + Ok(()) + } + + fn cleanup_file_resources(&self, handle: u32, blocks: I) + where + I: IntoIterator, + { + // `unbind()` revokes live files before `disable_device()`, so bot= h the shutdown path and a + // concurrent `release()` may still legitimately touch the BAR her= e. + if let Err(err) =3D 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) =3D self.deallocate_block(block.offset) { + pr_warn!( + "goldfish_address_space: deallocate block 0x{:x} faile= d: {}\n", + block.offset, + err.to_errno() + ); + } + } + } +} + +struct FileResources { + handle: Option, + 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, + #[pin] + ping: Mutex, + #[pin] + resources: Mutex, +} + +impl FileState { + fn new(runtime: Arc, handle: u32, ping: PingState) -> R= esult> { + 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 { + let base =3D 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 { + let block =3D self.runtime.allocate_block(request.size)?; + let mut resources =3D self.resources.lock(); + if resources.handle.is_none() { + drop(resources); + let _ =3D self.runtime.deallocate_block(block.offset); + return Err(ENODEV); + } + + if let Err(err) =3D resources.allocated_blocks.insert(block) { + drop(resources); + let _ =3D self.runtime.deallocate_block(block.offset); + return Err(err); + } + + request.size =3D block.size; + request.offset =3D block.offset; + request.phys_addr =3D self.shared_phys_addr(block.offset)?; + Ok(request) + } + + fn deallocate_block(self: ArcBorrow<'_, Self>, offset: u64) -> Result { + let mut resources =3D self.resources.lock(); + if resources.handle.is_none() { + return Err(ENODEV); + } + + if !resources + .allocated_blocks + .iter() + .any(|block| block.offset =3D=3D offset) + { + return Err(ENXIO); + } + + self.runtime.deallocate_block(offset)?; + let _ =3D resources.allocated_blocks.remove(offset)?; + Ok(()) + } + + fn claim_shared( + self: ArcBorrow<'_, Self>, + request: ClaimSharedIoctl, + ) -> Result { + let mut resources =3D 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 =3D 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 { + let handle =3D self.resources.lock().handle.ok_or(ENODEV)?; + let mut ping =3D 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 =3D self.resources.lock(); + let Some(handle) =3D resources.handle.take() else { + return; + }; + + self.runtime + .cleanup_file_resources(handle, resources.allocated_blocks.ite= r()); + resources.allocated_blocks.clear(); + resources.shared_blocks.clear(); + } + + fn revoke_for_shutdown(&self) { + self.cleanup_resources(); + } + + fn release(self: Arc) { + self.cleanup_resources(); + self.runtime.unregister_live_file(&self); + } +} + +#[pin_data] +struct GoldfishAddressSpaceDriver { + runtime: Arc, + #[pin] + misc: MiscDeviceRegistration, +} + +struct GoldfishAddressSpaceMisc; + +#[vtable] +impl MiscDevice for GoldfishAddressSpaceMisc { + type Ptr =3D Arc; + type RegistrationData =3D Arc; + + fn open(_file: &File, ctx: &MiscDeviceOpenContext<'_, Self>) -> Result= { + let runtime =3D ctx.data().clone(); + let _op =3D runtime.begin_operation()?; + let ping =3D PingState::new()?; + let handle =3D runtime.generate_handle()?; + let cleanup =3D ScopeGuard::new_with_data((runtime.clone(), handle= ), |(runtime, handle)| { + let _ =3D runtime.destroy_handle(handle); + }); + + runtime.tell_ping_info_addr(handle, ping.phys_addr())?; + let state =3D FileState::new(runtime.clone(), handle, ping)?; + cleanup.dismiss(); + + // Publish the file as a live shutdown owner before returning it t= o the miscdevice core. + if let Err(err) =3D 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 { + let _op =3D device.runtime.begin_operation()?; + match cmd { + GOLDFISH_ADDRESS_SPACE_IOCTL_ALLOCATE_BLOCK =3D> { + let data =3D UserSlice::new(UserPtr::from_addr(arg), Alloc= ateBlockIoctl::ENCODED_LEN); + let (mut reader, mut writer) =3D data.reader_writer(); + let mut bytes =3D [0u8; AllocateBlockIoctl::ENCODED_LEN]; + reader.read_slice(&mut bytes)?; + let request =3D AllocateBlockIoctl::decode(&bytes); + let response =3D device.allocate_block(request)?; + writer.write_slice(&response.encode())?; + Ok(0) + } + GOLDFISH_ADDRESS_SPACE_IOCTL_DEALLOCATE_BLOCK =3D> { + let mut reader =3D UserSlice::new(UserPtr::from_addr(arg),= size_of::()).reader(); + device.deallocate_block(reader.read::()?)?; + Ok(0) + } + GOLDFISH_ADDRESS_SPACE_IOCTL_PING =3D> { + let data =3D UserSlice::new(UserPtr::from_addr(arg), PingI= octl::ENCODED_LEN); + let (mut reader, mut writer) =3D data.reader_writer(); + let mut bytes =3D [0u8; PingIoctl::ENCODED_LEN]; + reader.read_slice(&mut bytes)?; + let request =3D PingIoctl::decode(&bytes); + let response =3D device.ping(request)?; + writer.write_slice(&response.encode())?; + Ok(0) + } + GOLDFISH_ADDRESS_SPACE_IOCTL_CLAIM_SHARED =3D> { + let data =3D UserSlice::new(UserPtr::from_addr(arg), Claim= SharedIoctl::ENCODED_LEN); + let (mut reader, mut writer) =3D data.reader_writer(); + let mut bytes =3D [0u8; ClaimSharedIoctl::ENCODED_LEN]; + reader.read_slice(&mut bytes)?; + let request =3D ClaimSharedIoctl::decode(&bytes); + let response =3D device.claim_shared(request)?; + writer.write_slice(&response.encode())?; + Ok(0) + } + GOLDFISH_ADDRESS_SPACE_IOCTL_UNCLAIM_SHARED =3D> { + let mut reader =3D UserSlice::new(UserPtr::from_addr(arg),= size_of::()).reader(); + device.unclaim_shared(reader.read::()?)?; + Ok(0) + } + _ =3D> Err(ENOTTY), + } + } + + #[cfg(CONFIG_COMPAT)] + fn compat_ioctl( + device: ArcBorrow<'_, FileState>, + file: &File, + cmd: u32, + arg: usize, + ) -> Result { + Self::ioctl(device, file, cmd, arg) + } +} + +kernel::declare_misc_device_fops!(GoldfishAddressSpaceMisc); + +kernel::pci_device_table!( + PCI_TABLE, + MODULE_PCI_TABLE, + ::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 =3D (); + + const ID_TABLE: pci::IdTable =3D &PCI_TABLE; + + fn probe(pdev: &pci::Device, _id_info: &Self::IdInfo) -> impl Pi= nInit { + pin_init::pin_init_scope(move || { + if pdev.revision_id() !=3D GOLDFISH_AS_SUPPORTED_REVISION { + return Err(ENODEV); + } + + pdev.enable_device_mem()?; + let enable_guard =3D ScopeGuard::new(|| pdev.disable_device()); + + let runtime =3D DeviceRuntime::new(pdev)?; + runtime.program_host_visible_state()?; + + let driver =3D 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, this: Pin<&Self>) { + let this =3D 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-l= ive 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", +} --=20 2.34.1