mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-21 23:16:50 +08:00
gpu: nova-core: gsp: fix UB in DmaGspMem pointer accessors
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 and their relevant fields are
temporarily widened to pub(super). 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: 75f6b1de81 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://patch.msgid.link/20260309225408.27714-1-dakr@kernel.org
[ Use pub(super) where possible; replace bitwise-and with modulo
operator analogous to [1]. - Danilo ]
Link: https://lore.kernel.org/all/20260129-nova-core-cmdq1-v3-1-2ede85493a27@nvidia.com/ [1]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
@@ -2,11 +2,7 @@
|
||||
|
||||
use core::{
|
||||
cmp,
|
||||
mem,
|
||||
sync::atomic::{
|
||||
fence,
|
||||
Ordering, //
|
||||
}, //
|
||||
mem, //
|
||||
};
|
||||
|
||||
use kernel::{
|
||||
@@ -146,30 +142,32 @@ static_assert!(align_of::<MsgqData>() == GSP_PAGE_SIZE);
|
||||
#[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(super) struct Msgq {
|
||||
/// Header for sending messages, including the write pointer.
|
||||
tx: MsgqTxHeader,
|
||||
pub(super) tx: MsgqTxHeader,
|
||||
/// Header for receiving messages, including the read pointer.
|
||||
rx: MsgqRxHeader,
|
||||
pub(super) 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(super) struct GspMem {
|
||||
/// Self-mapping page table entries.
|
||||
ptes: PteArray<{ Self::PTE_ARRAY_SIZE }>,
|
||||
/// 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(super) 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(super) gspq: Msgq,
|
||||
}
|
||||
|
||||
impl GspMem {
|
||||
@@ -331,12 +329,7 @@ impl DmaGspMem {
|
||||
//
|
||||
// - 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.
|
||||
@@ -345,12 +338,7 @@ impl DmaGspMem {
|
||||
//
|
||||
// - 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.
|
||||
@@ -359,27 +347,12 @@ impl DmaGspMem {
|
||||
//
|
||||
// - 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.
|
||||
@@ -388,26 +361,12 @@ impl DmaGspMem {
|
||||
//
|
||||
// - 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,75 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Replace with `IoView` projections once available; the `unwrap()` calls go away once we
|
||||
// switch to the new `dma::Coherent` API.
|
||||
pub(super) 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 @@ impl MsgqTxHeader {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user