* [PATCH] gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors
@ 2026-03-09 22:53 Danilo Krummrich
2026-03-10 1:44 ` Claude review: " Claude Code Review Bot
2026-03-10 1:44 ` Claude Code Review Bot
0 siblings, 2 replies; 3+ messages in thread
From: Danilo Krummrich @ 2026-03-09 22:53 UTC (permalink / raw)
To: acourbot, aliceryhl
Cc: nouveau, dri-devel, rust-for-linux, linux-kernel,
Danilo Krummrich, Gary Guo
The DmaGspMem pointer accessor methods (gsp_write_ptr, gsp_read_ptr,
cpu_read_ptr, cpu_write_ptr, advance_cpu_read_ptr,
advance_cpu_write_ptr) dereference a raw pointer to DMA memory, creating
an intermediate reference before calling volatile read/write methods.
This is undefined behavior since DMA memory can be concurrently modified
by the device.
Fix this by moving the implementations into a gsp_mem module in fw.rs
that uses the dma_read!() / dma_write!() macros, making the original
methods on DmaGspMem thin forwarding wrappers.
An alternative approach would have been to wrap the shared memory in
Opaque, but that would have required even more unsafe code.
Since the gsp_mem module lives in fw.rs (to access firmware-specific
binding field names), GspMem, Msgq, DmaGspMem and their relevant fields
are temporarily widened to pub(in crate::gsp). This will be reverted
once IoView projections are available.
Cc: Gary Guo <gary@garyguo.net>
Closes: https://lore.kernel.org/nouveau/DGUT14ILG35P.1UMNRKU93JUM1@kernel.org/
Fixes: 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 71 +++++----------------
drivers/gpu/nova-core/gsp/fw.rs | 101 ++++++++++++++++++++----------
2 files changed, 84 insertions(+), 88 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index ae54708c38eb..00a4062a47dc 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -2,11 +2,7 @@
use core::{
cmp,
- mem,
- sync::atomic::{
- fence,
- Ordering, //
- }, //
+ mem, //
};
use kernel::{
@@ -146,30 +142,32 @@ struct MsgqData {
#[repr(C)]
// There is no struct defined for this in the open-gpu-kernel-source headers.
// Instead it is defined by code in `GspMsgQueuesInit()`.
-struct Msgq {
+// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
+pub(in crate::gsp) struct Msgq {
/// Header for sending messages, including the write pointer.
- tx: MsgqTxHeader,
+ pub(in crate::gsp) tx: MsgqTxHeader,
/// Header for receiving messages, including the read pointer.
- rx: MsgqRxHeader,
+ pub(in crate::gsp) rx: MsgqRxHeader,
/// The message queue proper.
msgq: MsgqData,
}
/// Structure shared between the driver and the GSP and containing the command and message queues.
#[repr(C)]
-struct GspMem {
+// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
+pub(in crate::gsp) struct GspMem {
/// Self-mapping page table entries.
ptes: PteArray<{ GSP_PAGE_SIZE / size_of::<u64>() }>,
/// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
/// write and read pointers that the CPU updates.
///
/// This member is read-only for the GSP.
- cpuq: Msgq,
+ pub(in crate::gsp) cpuq: Msgq,
/// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
/// write and read pointers that the GSP updates.
///
/// This member is read-only for the driver.
- gspq: Msgq,
+ pub(in crate::gsp) gspq: Msgq,
}
// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
@@ -321,12 +319,7 @@ fn allocate_command(&mut self, size: usize) -> Result<GspCommand<'_>> {
//
// - The returned value is between `0` and `MSGQ_NUM_PAGES`.
fn gsp_write_ptr(&self) -> u32 {
- let gsp_mem = self.0.start_ptr();
-
- // SAFETY:
- // - The 'CoherentAllocation' contains at least one object.
- // - By the invariants of `CoherentAllocation` the pointer is valid.
- (unsafe { (*gsp_mem).gspq.tx.write_ptr() } % MSGQ_NUM_PAGES)
+ super::fw::gsp_mem::gsp_write_ptr(&self.0)
}
// Returns the index of the memory page the GSP will read the next command from.
@@ -335,12 +328,7 @@ fn gsp_write_ptr(&self) -> u32 {
//
// - The returned value is between `0` and `MSGQ_NUM_PAGES`.
fn gsp_read_ptr(&self) -> u32 {
- let gsp_mem = self.0.start_ptr();
-
- // SAFETY:
- // - The 'CoherentAllocation' contains at least one object.
- // - By the invariants of `CoherentAllocation` the pointer is valid.
- (unsafe { (*gsp_mem).gspq.rx.read_ptr() } % MSGQ_NUM_PAGES)
+ super::fw::gsp_mem::gsp_read_ptr(&self.0)
}
// Returns the index of the memory page the CPU can read the next message from.
@@ -349,27 +337,12 @@ fn gsp_read_ptr(&self) -> u32 {
//
// - The returned value is between `0` and `MSGQ_NUM_PAGES`.
fn cpu_read_ptr(&self) -> u32 {
- let gsp_mem = self.0.start_ptr();
-
- // SAFETY:
- // - The ['CoherentAllocation'] contains at least one object.
- // - By the invariants of CoherentAllocation the pointer is valid.
- (unsafe { (*gsp_mem).cpuq.rx.read_ptr() } % MSGQ_NUM_PAGES)
+ super::fw::gsp_mem::cpu_read_ptr(&self.0)
}
// Informs the GSP that it can send `elem_count` new pages into the message queue.
fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
- let rptr = self.cpu_read_ptr().wrapping_add(elem_count) % MSGQ_NUM_PAGES;
-
- // Ensure read pointer is properly ordered.
- fence(Ordering::SeqCst);
-
- let gsp_mem = self.0.start_ptr_mut();
-
- // SAFETY:
- // - The 'CoherentAllocation' contains at least one object.
- // - By the invariants of `CoherentAllocation` the pointer is valid.
- unsafe { (*gsp_mem).cpuq.rx.set_read_ptr(rptr) };
+ super::fw::gsp_mem::advance_cpu_read_ptr(&self.0, elem_count)
}
// Returns the index of the memory page the CPU can write the next command to.
@@ -378,26 +351,12 @@ fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
//
// - The returned value is between `0` and `MSGQ_NUM_PAGES`.
fn cpu_write_ptr(&self) -> u32 {
- let gsp_mem = self.0.start_ptr();
-
- // SAFETY:
- // - The 'CoherentAllocation' contains at least one object.
- // - By the invariants of `CoherentAllocation` the pointer is valid.
- (unsafe { (*gsp_mem).cpuq.tx.write_ptr() } % MSGQ_NUM_PAGES)
+ super::fw::gsp_mem::cpu_write_ptr(&self.0)
}
// Informs the GSP that it can process `elem_count` new pages from the command queue.
fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
- let wptr = self.cpu_write_ptr().wrapping_add(elem_count) & MSGQ_NUM_PAGES;
- let gsp_mem = self.0.start_ptr_mut();
-
- // SAFETY:
- // - The 'CoherentAllocation' contains at least one object.
- // - By the invariants of `CoherentAllocation` the pointer is valid.
- unsafe { (*gsp_mem).cpuq.tx.set_write_ptr(wptr) };
-
- // Ensure all command data is visible before triggering the GSP read.
- fence(Ordering::SeqCst);
+ super::fw::gsp_mem::advance_cpu_write_ptr(&self.0, elem_count)
}
}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 83ff91614e36..ca03d497246d 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -40,6 +40,75 @@
},
};
+// TODO: Replace with `IoView` projections once available; the `unwrap()` calls go away once we
+// switch to the new `dma::Coherent` API.
+pub(in crate::gsp) mod gsp_mem {
+ use core::sync::atomic::{
+ fence,
+ Ordering, //
+ };
+
+ use kernel::{
+ dma::CoherentAllocation,
+ dma_read,
+ dma_write,
+ prelude::*, //
+ };
+
+ use crate::gsp::cmdq::{
+ GspMem,
+ MSGQ_NUM_PAGES, //
+ };
+
+ pub(in crate::gsp) fn gsp_write_ptr(qs: &CoherentAllocation<GspMem>) -> u32 {
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result<u32> { Ok(dma_read!(qs, [0]?.gspq.tx.0.writePtr) % MSGQ_NUM_PAGES) }().unwrap()
+ }
+
+ pub(in crate::gsp) fn gsp_read_ptr(qs: &CoherentAllocation<GspMem>) -> u32 {
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result<u32> { Ok(dma_read!(qs, [0]?.gspq.rx.0.readPtr) % MSGQ_NUM_PAGES) }().unwrap()
+ }
+
+ pub(in crate::gsp) fn cpu_read_ptr(qs: &CoherentAllocation<GspMem>) -> u32 {
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result<u32> { Ok(dma_read!(qs, [0]?.cpuq.rx.0.readPtr) % MSGQ_NUM_PAGES) }().unwrap()
+ }
+
+ pub(in crate::gsp) fn advance_cpu_read_ptr(qs: &CoherentAllocation<GspMem>, count: u32) {
+ let rptr = cpu_read_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES;
+
+ // Ensure read pointer is properly ordered.
+ fence(Ordering::SeqCst);
+
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result {
+ dma_write!(qs, [0]?.cpuq.rx.0.readPtr, rptr);
+ Ok(())
+ }()
+ .unwrap()
+ }
+
+ pub(in crate::gsp) fn cpu_write_ptr(qs: &CoherentAllocation<GspMem>) -> u32 {
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result<u32> { Ok(dma_read!(qs, [0]?.cpuq.tx.0.writePtr) % MSGQ_NUM_PAGES) }().unwrap()
+ }
+
+ pub(in crate::gsp) fn advance_cpu_write_ptr(qs: &CoherentAllocation<GspMem>, count: u32) {
+ let wptr = cpu_write_ptr(qs).wrapping_add(count) & MSGQ_NUM_PAGES;
+
+ // PANIC: A `dma::CoherentAllocation` always contains at least one element.
+ || -> Result {
+ dma_write!(qs, [0]?.cpuq.tx.0.writePtr, wptr);
+ Ok(())
+ }()
+ .unwrap();
+
+ // Ensure all command data is visible before triggering the GSP read.
+ fence(Ordering::SeqCst);
+ }
+}
+
/// Empty type to group methods related to heap parameters for running the GSP firmware.
enum GspFwHeapParams {}
@@ -708,22 +777,6 @@ pub(crate) fn new(msgq_size: u32, rx_hdr_offset: u32, msg_count: u32) -> Self {
entryOff: num::usize_into_u32::<GSP_PAGE_SIZE>(),
})
}
-
- /// Returns the value of the write pointer for this queue.
- pub(crate) fn write_ptr(&self) -> u32 {
- let ptr = core::ptr::from_ref(&self.0.writePtr);
-
- // SAFETY: `ptr` is a valid pointer to a `u32`.
- unsafe { ptr.read_volatile() }
- }
-
- /// Sets the value of the write pointer for this queue.
- pub(crate) fn set_write_ptr(&mut self, val: u32) {
- let ptr = core::ptr::from_mut(&mut self.0.writePtr);
-
- // SAFETY: `ptr` is a valid pointer to a `u32`.
- unsafe { ptr.write_volatile(val) }
- }
}
// SAFETY: Padding is explicit and does not contain uninitialized data.
@@ -739,22 +792,6 @@ impl MsgqRxHeader {
pub(crate) fn new() -> Self {
Self(Default::default())
}
-
- /// Returns the value of the read pointer for this queue.
- pub(crate) fn read_ptr(&self) -> u32 {
- let ptr = core::ptr::from_ref(&self.0.readPtr);
-
- // SAFETY: `ptr` is a valid pointer to a `u32`.
- unsafe { ptr.read_volatile() }
- }
-
- /// Sets the value of the read pointer for this queue.
- pub(crate) fn set_read_ptr(&mut self, val: u32) {
- let ptr = core::ptr::from_mut(&mut self.0.readPtr);
-
- // SAFETY: `ptr` is a valid pointer to a `u32`.
- unsafe { ptr.write_volatile(val) }
- }
}
// SAFETY: Padding is explicit and does not contain uninitialized data.
base-commit: 4da879a0d3fd170a70994b73baa554c6913918b5
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Claude review: gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors
2026-03-09 22:53 [PATCH] gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors Danilo Krummrich
@ 2026-03-10 1:44 ` Claude Code Review Bot
2026-03-10 1:44 ` Claude Code Review Bot
1 sibling, 0 replies; 3+ messages in thread
From: Claude Code Review Bot @ 2026-03-10 1:44 UTC (permalink / raw)
To: dri-devel-reviews
Overall Series Review
Subject: gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors
Author: Danilo Krummrich <dakr@kernel.org>
Patches: 1
Reviewed: 2026-03-10T11:44:19.954576
---
This is a single-patch fix for undefined behavior in the nova-core GSP driver's DMA memory pointer accessors. The core problem is well-identified: the old code created intermediate Rust references (`&self`, `&mut self`) to DMA memory that can be concurrently modified by the device, which is UB in Rust's memory model. The fix moves the pointer accesses to use `dma_read!`/`dma_write!` macros which perform volatile access without creating intermediate references.
The approach is reasonable as a stopgap until IoView projections are available. The code is clean and well-commented about its temporary nature. However, there is one pre-existing bug that was faithfully carried over and should be addressed.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 3+ messages in thread
* Claude review: gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors
2026-03-09 22:53 [PATCH] gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors Danilo Krummrich
2026-03-10 1:44 ` Claude review: " Claude Code Review Bot
@ 2026-03-10 1:44 ` Claude Code Review Bot
1 sibling, 0 replies; 3+ messages in thread
From: Claude Code Review Bot @ 2026-03-10 1:44 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
**The UB fix itself is correct and well-motivated.** The old `write_ptr()` / `read_ptr()` methods took `&self` / `&mut self`, creating Rust references to memory that the GSP hardware can concurrently modify, violating Rust's aliasing guarantees. Using `dma_read!` / `dma_write!` avoids this.
**Bug carried over — `&` vs `%` inconsistency:**
In `advance_cpu_write_ptr` (fw.rs, new line 98):
```rust
let wptr = cpu_write_ptr(qs).wrapping_add(count) & MSGQ_NUM_PAGES;
```
But in `advance_cpu_read_ptr` (fw.rs, new line 79):
```rust
let rptr = cpu_read_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES;
```
`MSGQ_NUM_PAGES` is `0x3f` (63). `& 0x3f` is equivalent to `% 64`, not `% 63`. So these two functions use different modular arithmetic ranges: `advance_cpu_read_ptr` produces values in `[0, 62]` while `advance_cpu_write_ptr` produces values in `[0, 63]`. This was present in the original code (the diff shows the old `advance_cpu_write_ptr` also used `&`), but since this patch is a refactor touching exactly these functions, it would be a good opportunity to either:
- Fix it to `%` if the intent is modulo-63, or
- Document why `&` is intentionally different here.
Given that the invariant comments on `cpu_write_ptr` say "The returned value is between `0` and `MSGQ_NUM_PAGES`" and the `MsgqData` array is sized as `MSGQ_NUM_PAGES` elements (0 to 62), using `& 0x3f` could produce index 63 which would be out of bounds.
**Shared vs mutable reference change:**
`advance_cpu_read_ptr` and `advance_cpu_write_ptr` now take `&CoherentAllocation<GspMem>` (shared reference) instead of the original `&mut self` path which would have obtained a mutable pointer via `start_ptr_mut()`. This is fine because:
- The `dma_write!` macro handles the volatile write without needing `&mut`
- The callers on `DmaGspMem` still take `&mut self`, maintaining exclusive access at the API boundary
**Closure-based unwrap pattern:**
The `|| -> Result<u32> { Ok(dma_read!(...)) }().unwrap()` pattern is unusual but understandable — it's needed because `dma_read!` uses the `?` operator internally, requiring a `Result`-returning context. The `// PANIC` comments correctly justify why the unwrap is safe. This is acknowledged as temporary until the new `dma::Coherent` API.
**Visibility widening is appropriate** — the `pub(in crate::gsp)` scope is narrow enough and clearly documented with TODO comments for reversion.
**Minor nit:** The fence placement in `advance_cpu_read_ptr` (line 82) comes *after* computing `rptr` but *before* writing it. This matches the original. However, the comment says "Ensure read pointer is properly ordered" — it might be clearer to say it ensures prior reads of queue data are complete before the pointer update becomes visible. This is a pre-existing comment clarity issue, not introduced by this patch.
**Overall: the patch is a correct fix for the UB issue.** The `&` vs `%` inconsistency in `advance_cpu_write_ptr` should be investigated and resolved, either in this patch or as a follow-up.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-03-10 1:44 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 22:53 [PATCH] gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors Danilo Krummrich
2026-03-10 1:44 ` Claude review: " Claude Code Review Bot
2026-03-10 1:44 ` Claude Code Review Bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox