From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0C5B8E9B36F for ; Mon, 2 Mar 2026 13:03:15 +0000 (UTC) Received: from kara.freedesktop.org (unknown [131.252.210.166]) by gabe.freedesktop.org (Postfix) with ESMTPS id DC68310E4DF; Mon, 2 Mar 2026 13:03:12 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.b="AMS5jFNm"; dkim-atps=neutral Received: from kara.freedesktop.org (localhost [127.0.0.1]) by kara.freedesktop.org (Postfix) with ESMTP id 427E544D60; Mon, 2 Mar 2026 12:52:57 +0000 (UTC) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=lists.freedesktop.org; s=20240201; t=1772455977; b=UoH/OtcI8FE5OGykXXNkiniWsG5G4ZYzIEIqDCYCcU/AFS49yJ0IpUICaogYcyND4Bzlg yKgQLf89gdhCxWdoOCedEevIWb7yNf8+OnCwuycmGTEmlSj8S2DPUT2rxaNYYdAHEQvIhS0 XSgy7ZZ+mWzpjN4cKLP03K6WgTVGtkD9pzbIbcG33D/M/WvpSPEB1L+9DS6xU999A74vJWH CcADzzwhE3lYMjrKaMn/ePtaCW7w6zZRI4nGJSoaSoMns2pXjnP7cUMtHzOTR1EJaM6dtGM /lT3cTP7AVC4xqcod4oc2XkKuGTkCjoKBUuP+Eg6vmYLRp4dI5VPP018UlmA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=lists.freedesktop.org; s=20240201; t=1772455977; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=mdgDHQWZuy/mYEzDbVuE6TL/tamLyNwfdM8VbAJu4w0=; b=GEuaIa5doM0x5v3C25cH5FHTd9mILCciswMbUiwAGYYAz0Y3LQhvbYPc5bUSuZ42Z0DiP Q5hjoV4xuFprKyJEtrmED0KxsVReuwvtA5Rer/L943C+FKdEs66DSPNxQucAi91qVAJxwdm LlroysyyOAKBM67vj4k8Daecrcvh4vuVnoM57dxR5mj9+J5JrKLCppaoXcTwoefnmMchPhw Nboivx6DzWKg+x8yjIGmX3TWouVv5fJjyRnOJMYoi5/6xZIdei3OLr2uT6GCBMqadBgqkVn ZUd8K4ikbcCAQBACJ+DZcTagZj6GDeat/uK8sqwOzrD72lryHk4bpKx1nImg== ARC-Authentication-Results: i=1; mail.freedesktop.org; dkim=pass header.d=kernel.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=kernel.org policy.dmarc=quarantine Authentication-Results: mail.freedesktop.org; dkim=pass header.d=kernel.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=kernel.org policy.dmarc=quarantine Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by kara.freedesktop.org (Postfix) with ESMTPS id 206A14032A for ; Mon, 2 Mar 2026 12:52:55 +0000 (UTC) Received: from tor.source.kernel.org (tor.source.kernel.org [172.105.4.254]) by gabe.freedesktop.org (Postfix) with ESMTPS id 70D3D10E4D9; Mon, 2 Mar 2026 13:03:10 +0000 (UTC) Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by tor.source.kernel.org (Postfix) with ESMTP id 8FF6760008; Mon, 2 Mar 2026 13:03:09 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id AF322C2BC86; Mon, 2 Mar 2026 13:03:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1772456589; bh=S0Hfuvgw1f1fWJyBUEnC4qY6gqep6JwxreV/pS9o6tY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:Reply-To:From; b=AMS5jFNmaIDvcShdrkSUUj+6imuRBgCV85qsH5hkem5OT4jfK6OcY3dSo4J0RfmcY fDJ+JQ1lhbNJC1S5oLQU4I583t3BBAmDIiFUCQ9f9TvFRlpOKwtGSbQll/PpA0pIQp zruJIPMFLWf+sXk/3TRm9BUKG3tKfUDYuD7xQ4hLRDqRoEUKQ+9Yk9ta8mIpvnuAs1 n6Owu7dr/f3bC9neL8SmTK5zy2cHAQcwbpxtHhBBqZq8TKA2GRLus+6F6KBIu8Gms0 zrV78Jr1wjJgmpUR4R7HMJKymY7QPKVOqYRyHjSPXVxuctlM5oq/dgXdYWh6zkIQf0 zDocNv1eF1AKg== From: Gary Guo To: Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Alexandre Courbot , David Airlie , Simona Vetter , Abdiel Janulgue , Daniel Almeida , Robin Murphy Subject: [PATCH v3 2/2] rust: dma: use pointer projection infra for `dma_{read,write}` macro Date: Mon, 2 Mar 2026 13:02:20 +0000 Message-ID: <20260302130223.134058-3-gary@kernel.org> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20260302130223.134058-1-gary@kernel.org> References: <20260302130223.134058-1-gary@kernel.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: 7PMQZS2WGWOARU5INCPLY4RHI4PILC5H X-Message-ID-Hash: 7PMQZS2WGWOARU5INCPLY4RHI4PILC5H X-MailFrom: gary@kernel.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, driver-core@lists.linux.dev X-Mailman-Version: 3.3.8 Precedence: list Reply-To: Gary Guo List-Id: Nouveau development list Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Gary Guo Current `dma_read!`, `dma_write!` macros also use a custom `addr_of!()`-based implementation for projecting pointers, which has soundness issue as it relies on absence of `Deref` implementation on types. It also has a soundness issue where it does not protect against unaligned fields (when `#[repr(packed)]` is used) so it can generate misaligned accesses. This commit migrates them to use the general pointer projection infrastructure, which handles these cases correctly. As part of migration, the macro is updated to have an improved surface syntax. The current macro have dma_read!(a.b.c[d].e.f) to mean `a.b.c` is a DMA coherent allocation and it should project into it with `[d].e.f` and do a read, which is confusing as it makes the indexing operator integral to the macro (so it will break if you have an array of `CoherentAllocation`, for example). This also is problematic as we would like to generalize `CoherentAllocation` from just slices to arbitrary types. Make the macro expects `dma_read!(path.to.dma, .path.inside.dma)` as the canonical syntax. The index operator is no longer special and is just one type of projection (in additional to field projection). Similarly, make `dma_write!(path.to.dma, .path.inside.dma, value)` become the canonical syntax for writing. Another issue of the current macro is that it is always fallible. This makes sense with existing design of `CoherentAllocation`, but once we support fixed size arrays with `CoherentAllocation`, it is desirable to have the ability to perform infallible indexing as well, e.g. doing a `[0]` index of `[Foo; 2]` is okay and can be checked at build-time, so forcing falliblity is non-ideal. To capture this, the macro is changed to use `[idx]` as infallible projection and `[idx]?` as fallible index projection (those syntax are part of the general projection infra). A benefit of this is that while individual indexing operation may fail, the overall read/write operation is not fallible. Fixes: ad2907b4e308 ("rust: add dma coherent allocator abstraction") Signed-off-by: Gary Guo --- drivers/gpu/nova-core/gsp.rs | 14 ++-- drivers/gpu/nova-core/gsp/boot.rs | 2 +- drivers/gpu/nova-core/gsp/cmdq.rs | 10 ++- rust/kernel/dma.rs | 114 +++++++++++++----------------- samples/rust/rust_dma.rs | 30 ++++---- 5 files changed, 81 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs index 174feaca0a6b..25cd48514c77 100644 --- a/drivers/gpu/nova-core/gsp.rs +++ b/drivers/gpu/nova-core/gsp.rs @@ -143,14 +143,14 @@ pub(crate) fn new(pdev: &pci::Device) -> impl PinInit::alloc_coherent(dev, 1, GFP_KERNEL | __GFP_ZERO)?; - dma_write!(wpr_meta[0] = GspFwWprMeta::new(&gsp_fw, &fb_layout))?; + dma_write!(wpr_meta, [0]?, GspFwWprMeta::new(&gsp_fw, &fb_layout)); self.cmdq .send_command(bar, commands::SetSystemInfo::new(pdev))?; diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs index 87dbbd6d1be9..0056bfbf0a44 100644 --- a/drivers/gpu/nova-core/gsp/cmdq.rs +++ b/drivers/gpu/nova-core/gsp/cmdq.rs @@ -202,9 +202,13 @@ fn new(dev: &device::Device) -> Result { let gsp_mem = CoherentAllocation::::alloc_coherent(dev, 1, GFP_KERNEL | __GFP_ZERO)?; - dma_write!(gsp_mem[0].ptes = PteArray::new(gsp_mem.dma_handle())?)?; - dma_write!(gsp_mem[0].cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES))?; - dma_write!(gsp_mem[0].cpuq.rx = MsgqRxHeader::new())?; + dma_write!(gsp_mem, [0]?.ptes, PteArray::new(gsp_mem.dma_handle())?); + dma_write!( + gsp_mem, + [0]?.cpuq.tx, + MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES) + ); + dma_write!(gsp_mem, [0]?.cpuq.rx, MsgqRxHeader::new()); Ok(Self(gsp_mem)) } diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 909d56fd5118..07fa4912ea78 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -461,6 +461,19 @@ pub fn size(&self) -> usize { self.count * core::mem::size_of::() } + /// Returns the raw pointer to the allocated region in the CPU's virtual address space. + #[inline] + pub fn as_ptr(&self) -> *const [T] { + core::ptr::slice_from_raw_parts(self.cpu_addr.as_ptr(), self.count) + } + + /// Returns the raw pointer to the allocated region in the CPU's virtual address space as + /// a mutable pointer. + #[inline] + pub fn as_mut_ptr(&self) -> *mut [T] { + core::ptr::slice_from_raw_parts_mut(self.cpu_addr.as_ptr(), self.count) + } + /// Returns the base address to the allocated region in the CPU's virtual address space. pub fn start_ptr(&self) -> *const T { self.cpu_addr.as_ptr() @@ -581,23 +594,6 @@ pub unsafe fn write(&mut self, src: &[T], offset: usize) -> Result { Ok(()) } - /// Returns a pointer to an element from the region with bounds checking. `offset` is in - /// units of `T`, not the number of bytes. - /// - /// Public but hidden since it should only be used from [`dma_read`] and [`dma_write`] macros. - #[doc(hidden)] - pub fn item_from_index(&self, offset: usize) -> Result<*mut T> { - if offset >= self.count { - return Err(EINVAL); - } - // SAFETY: - // - The pointer is valid due to type invariant on `CoherentAllocation` - // and we've just checked that the range and index is within bounds. - // - `offset` can't overflow since it is smaller than `self.count` and we've checked - // that `self.count` won't overflow early in the constructor. - Ok(unsafe { self.cpu_addr.as_ptr().add(offset) }) - } - /// Reads the value of `field` and ensures that its type is [`FromBytes`]. /// /// # Safety @@ -670,6 +666,9 @@ unsafe impl Send for CoherentAllocation {} /// Reads a field of an item from an allocated region of structs. /// +/// The syntax is of form `kernel::dma_read!(dma, proj)` where `dma` is an expression to an +/// [`CoherentAllocation`] and `proj` is a [projection specification](kernel::project_pointer!). +/// /// # Examples /// /// ``` @@ -684,36 +683,29 @@ unsafe impl Send for CoherentAllocation {} /// unsafe impl kernel::transmute::AsBytes for MyStruct{}; /// /// # fn test(alloc: &kernel::dma::CoherentAllocation) -> Result { -/// let whole = kernel::dma_read!(alloc[2]); -/// let field = kernel::dma_read!(alloc[1].field); +/// let whole = kernel::dma_read!(alloc, [2]?); +/// let field = kernel::dma_read!(alloc, [1]?.field); /// # Ok::<(), Error>(()) } /// ``` #[macro_export] macro_rules! dma_read { - ($dma:expr, $idx: expr, $($field:tt)*) => {{ - (|| -> ::core::result::Result<_, $crate::error::Error> { - let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?; - // SAFETY: `item_from_index` ensures that `item` is always a valid pointer and can be - // dereferenced. The compiler also further validates the expression on whether `field` - // is a member of `item` when expanded by the macro. - unsafe { - let ptr_field = ::core::ptr::addr_of!((*item) $($field)*); - ::core::result::Result::Ok( - $crate::dma::CoherentAllocation::field_read(&$dma, ptr_field) - ) - } - })() + ($dma:expr, $($proj:tt)*) => {{ + let dma = &$dma; + let ptr = $crate::project_pointer!( + $crate::dma::CoherentAllocation::as_ptr(dma), $($proj)* + ); + // SAFETY: pointer created by projection is within DMA region. + unsafe { $crate::dma::CoherentAllocation::field_read(dma, ptr) } }}; - ($dma:ident [ $idx:expr ] $($field:tt)* ) => { - $crate::dma_read!($dma, $idx, $($field)*) - }; - ($($dma:ident).* [ $idx:expr ] $($field:tt)* ) => { - $crate::dma_read!($($dma).*, $idx, $($field)*) - }; } /// Writes to a field of an item from an allocated region of structs. /// +/// The syntax is of form `kernel::dma_write!(dma, proj, val)` where `dma` is an expression to an +/// [`CoherentAllocation`] and `proj` is a [projection specification](kernel::project_pointer!), +/// and `val` is the value to be written to the projected location. +/// +/// /// # Examples /// /// ``` @@ -728,37 +720,31 @@ macro_rules! dma_read { /// unsafe impl kernel::transmute::AsBytes for MyStruct{}; /// /// # fn test(alloc: &kernel::dma::CoherentAllocation) -> Result { -/// kernel::dma_write!(alloc[2].member = 0xf); -/// kernel::dma_write!(alloc[1] = MyStruct { member: 0xf }); +/// kernel::dma_write!(alloc, [2]?.member, 0xf); +/// kernel::dma_write!(alloc, [1]?, MyStruct { member: 0xf }); /// # Ok::<(), Error>(()) } /// ``` #[macro_export] macro_rules! dma_write { - ($dma:ident [ $idx:expr ] $($field:tt)*) => {{ - $crate::dma_write!($dma, $idx, $($field)*) - }}; - ($($dma:ident).* [ $idx:expr ] $($field:tt)* ) => {{ - $crate::dma_write!($($dma).*, $idx, $($field)*) + (@parse [$dma:expr] [$($proj:tt)*] [, $val:expr]) => {{ + let dma = &$dma; + let ptr = $crate::project_pointer!( + mut $crate::dma::CoherentAllocation::as_mut_ptr(dma), $($proj)* + ); + let val = $val; + // SAFETY: pointer created by projection is within DMA region. + unsafe { $crate::dma::CoherentAllocation::field_write(dma, ptr, val) } }}; - ($dma:expr, $idx: expr, = $val:expr) => { - (|| -> ::core::result::Result<_, $crate::error::Error> { - let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?; - // SAFETY: `item_from_index` ensures that `item` is always a valid item. - unsafe { $crate::dma::CoherentAllocation::field_write(&$dma, item, $val) } - ::core::result::Result::Ok(()) - })() + (@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => { + $crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*]) + }; + (@parse [$dma:expr] [$($proj:tt)*] [[$index:expr]? $($rest:tt)*]) => { + $crate::dma_write!(@parse [$dma] [$($proj)* [$index]?] [$($rest)*]) + }; + (@parse [$dma:expr] [$($proj:tt)*] [[$index:expr] $($rest:tt)*]) => { + $crate::dma_write!(@parse [$dma] [$($proj)* [$index]] [$($rest)*]) }; - ($dma:expr, $idx: expr, $(.$field:ident)* = $val:expr) => { - (|| -> ::core::result::Result<_, $crate::error::Error> { - let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?; - // SAFETY: `item_from_index` ensures that `item` is always a valid pointer and can be - // dereferenced. The compiler also further validates the expression on whether `field` - // is a member of `item` when expanded by the macro. - unsafe { - let ptr_field = ::core::ptr::addr_of_mut!((*item) $(.$field)*); - $crate::dma::CoherentAllocation::field_write(&$dma, ptr_field, $val) - } - ::core::result::Result::Ok(()) - })() + ($dma:expr, $($rest:tt)*) => { + $crate::dma_write!(@parse [$dma] [] [$($rest)*]) }; } diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 9c45851c876e..ce39b5545097 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -68,7 +68,7 @@ fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit, _info: &Self::IdInfo) -> impl PinInit Result { + for (i, value) in TEST_VALUES.into_iter().enumerate() { + let val0 = kernel::dma_read!(self.ca, [i]?.h); + let val1 = kernel::dma_read!(self.ca, [i]?.b); + + assert_eq!(val0, value.0); + assert_eq!(val1, value.1); + } + + Ok(()) + } +} + #[pinned_drop] impl PinnedDrop for DmaSampleDriver { fn drop(self: Pin<&mut Self>) { dev_info!(self.pdev, "Unload DMA test driver.\n"); - for (i, value) in TEST_VALUES.into_iter().enumerate() { - let val0 = kernel::dma_read!(self.ca[i].h); - let val1 = kernel::dma_read!(self.ca[i].b); - assert!(val0.is_ok()); - assert!(val1.is_ok()); - - if let Ok(val0) = val0 { - assert_eq!(val0, value.0); - } - if let Ok(val1) = val1 { - assert_eq!(val1, value.1); - } - } + assert!(self.check_dma().is_ok()); for (i, entry) in self.sgt.iter().enumerate() { dev_info!( -- 2.51.2