[PATCH v3 10/14] gpu: nova-core: bound a GSP wait by a single deadline

From: John Hubbard

Date: Wed Sep 02 2026 - 23:19:19 EST


The GSP posts unsolicited events on the same queue it posts replies on,
so a caller waiting for one message logs whatever else arrives first and
reads again.

Each of those reads started a fresh five-second timeout, so a steady
stream of events extended the wait without bound. The wait also released
the queue lock between reads, so a command sent from another thread
could consume the awaited event and leave the waiter to time out.

Compute one absolute deadline when the wait begins and pass the time
remaining to each read, and hold the queue lock across the whole wait,
so the wait is bounded however many events arrive first and no other
caller can take the event it waits for.

GSP boot waits for two unsolicited events. Move that loop into a helper
so both take the same bound.

Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 50 +++++++++++++++++++++-----
drivers/gpu/nova-core/gsp/commands.rs | 8 +----
drivers/gpu/nova-core/gsp/sequencer.rs | 8 +----
3 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index ce4d6a111e68..a0f995faaee5 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -33,7 +33,11 @@
},
Mutex, //
},
- time::Delta,
+ time::{
+ Delta,
+ Instant,
+ Monotonic, //
+ },
transmute::{
AsBytes,
FromBytes, //
@@ -569,8 +573,9 @@ fn notify_gsp(bar: Bar0<'_>) {
///
/// # Errors
///
- /// - `ETIMEDOUT` if space does not become available to send the command, or if the reply is
- /// not received within the timeout.
+ /// - `ETIMEDOUT` if space does not become available to send the command, or if the reply does
+ /// not arrive within [`Self::RECEIVE_TIMEOUT`] of the send, however many events arrive
+ /// while waiting.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
@@ -585,8 +590,13 @@ pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Rep
let mut inner = self.inner.lock();
inner.send_command(bar, command)?;

+ let deadline = Instant::<Monotonic>::now() + Self::RECEIVE_TIMEOUT;
loop {
- match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ let remaining = deadline - Instant::<Monotonic>::now();
+ if remaining.is_negative() {
+ break Err(ETIMEDOUT);
+ }
+ match inner.receive_msg::<M::Reply>(remaining) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
@@ -611,15 +621,39 @@ pub(crate) fn send_command_no_wait<M>(&self, bar: Bar0<'_>, command: M) -> Resul
self.inner.lock().send_command(bar, command)
}

- /// Receive a message from the GSP.
+ /// Waits for an unsolicited GSP event of type `M`, logging any other event that arrives
+ /// first.
+ ///
+ /// The queue is locked for the whole wait, for up to [`Self::RECEIVE_TIMEOUT`], so a
+ /// concurrent command cannot consume the awaited event.
///
- /// See [`CmdqInner::receive_msg`] for details.
- pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if the event does not arrive within [`Self::RECEIVE_TIMEOUT`] of the call,
+ /// however many other events arrive while waiting.
+ /// - `EIO` if the queue is poisoned or a message fails framing or checksum validation (see
+ /// [`CmdqInner::wait_for_msg`]).
+ ///
+ /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
+ pub(crate) fn await_msg<M: MessageFromGsp>(&self) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
- self.inner.lock().receive_msg(timeout)
+ let mut inner = self.inner.lock();
+
+ let deadline = Instant::<Monotonic>::now() + Self::RECEIVE_TIMEOUT;
+ loop {
+ let remaining = deadline - Instant::<Monotonic>::now();
+ if remaining.is_negative() {
+ break Err(ETIMEDOUT);
+ }
+ match inner.receive_msg::<M>(remaining) {
+ Ok(msg) => break Ok(msg),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
}
}

diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index ffc25fd8c47b..61fe93db9e7e 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -188,13 +188,7 @@ fn read(

/// Waits for GSP initialization to complete.
pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
- loop {
- match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
- Ok(_) => break Ok(()),
- Err(ERANGE) => continue,
- Err(e) => break Err(e),
- }
- }
+ cmdq.await_msg::<GspInitDone>().map(|_| ())
}

/// The `GetGspStaticInfo` command.
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index bcad1421953a..e2f1da129d8f 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -343,13 +343,7 @@ pub(crate) fn run(
libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
bootloader_app_version: u32,
) -> Result {
- let seq_info = loop {
- match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
- Ok(seq_info) => break seq_info,
- Err(ERANGE) => continue,
- Err(e) => return Err(e),
- }
- };
+ let seq_info = cmdq.await_msg::<GspSequence>()?;

let sequencer = GspSequencer {
bar: ctx.bar,
--
2.55.0