[PATCH 13/17] gpu: nova-core: bound a GSP wait by a single deadline

From: John Hubbard

Date: Fri Aug 07 2026 - 23:15:00 EST


The GSP posts unsolicited events on the same queue it posts replies on,
so a caller waiting for one message dispatches 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.

Compute one absolute deadline when the wait begins and pass the time
remaining to each read, so the whole wait is bounded however many events
arrive first.

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 | 52 ++++++++++++++++++++++----
drivers/gpu/nova-core/gsp/commands.rs | 8 +---
drivers/gpu/nova-core/gsp/sequencer.rs | 8 +---
3 files changed, 46 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index fc4c229b8b9a..76d51155c49f 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -30,7 +30,11 @@
aref::ARef,
Mutex, //
},
- time::Delta,
+ time::{
+ Delta,
+ Instant,
+ Monotonic, //
+ },
transmute::{
AsBytes,
FromBytes, //
@@ -558,8 +562,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 are
+ /// dispatched while waiting.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
@@ -574,8 +579,13 @@ pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Rep
let mut inner = self.inner.lock();
let expected_seq = inner.send_command(bar, command)?;

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

- /// Receive a message from the GSP.
+ /// Receive a message from the GSP, matching on the function code alone.
///
- /// Matches on the function code alone, for a caller awaiting an unsolicited GSP event rather
- /// than a reply to a command. See [`CmdqInner::receive_msg`].
- pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ /// Returns `ERANGE` if the message that arrives is not of type `M`. See
+ /// [`CmdqInner::receive_msg`].
+ fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> 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, None)
}
+
+ /// Waits for an unsolicited GSP event of type `M`, dispatching any other event that arrives
+ /// first.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if the event does not arrive within [`Self::RECEIVE_TIMEOUT`] of the call,
+ /// however many other events are dispatched while waiting.
+ 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>,
+ {
+ let deadline = Instant::<Monotonic>::now() + Self::RECEIVE_TIMEOUT;
+ loop {
+ let remaining = deadline - Instant::<Monotonic>::now();
+ if remaining.is_negative() {
+ break Err(ETIMEDOUT);
+ }
+ match self.receive_msg::<M>(remaining) {
+ Ok(msg) => break Ok(msg),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
}

/// Inner mutex protected state of [`Cmdq`].
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