[PATCH 4/4] gpu: nova-core: gsp: cmdq: add the GMC receive path
From: Alexandre Courbot
Date: Mon Nov 02 2026 - 19:03:47 EST
From: John Hubbard <jhubbard@xxxxxxxxxx>
GSP-RM answers a GMC request with a GMC message whose command word
carries the response flag and the id of the request, and whose sequence
number is that of the request. It also raises GMC events, named by a
command id. During boot, the load-and-execute events arrive while the
driver is waiting for the GSP_INIT reply, and GSP-RM only answers once
they have been handled.
Add the receive side of the gmc sub-module, mirroring the
already-existing rpc sub-module.
The transport cannot produce GMC messages yet: the r570 queue element
header does not carry the type of the message it transports, so the Gmc
variant of GspParsedMessage is only constructed once the switch to r000
makes the transport dispatch on the NVDM type. The RPC receive path and
drain() account for it by logging unclaimed GMC messages.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
Co-developed-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
Signed-off-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 7 ++
drivers/gpu/nova-core/gsp/cmdq/gmc.rs | 230 +++++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 8 +-
drivers/gpu/nova-core/gsp/fw.rs | 1 -
4 files changed, 243 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 629ab8d35f87..a1fa7a3b4079 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -420,6 +420,12 @@ struct GspMessage<'a> {
enum GspParsedMessage<'a> {
/// A [RPC message](rpc::RpcMessage).
Rpc(rpc::RpcMessage<'a>),
+ /// A [GMC message](gmc::GmcMessage).
+ //
+ // The transport header of the r570 firmware carries no message type, so this variant cannot
+ // be constructed before the switch to r000.
+ #[expect(dead_code)]
+ Gmc(gmc::GmcMessage<'a>),
}
impl GspParsedMessage<'_> {
@@ -427,6 +433,7 @@ impl GspParsedMessage<'_> {
fn log(&self, dev: &device::Device) {
match self {
GspParsedMessage::Rpc(rpc) => rpc.log(dev),
+ GspParsedMessage::Gmc(gmc) => gmc.log(dev),
}
}
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/gmc.rs b/drivers/gpu/nova-core/gsp/cmdq/gmc.rs
index b035f30479a8..9adba5cbcd5c 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/gmc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/gmc.rs
@@ -29,7 +29,9 @@
use super::{
Cmdq,
CmdqInner,
- CommandElement, //
+ CommandElement,
+ GspMessage,
+ GspParsedMessage, //
};
/// Trait implemented by types representing a GMC command to send to the GSP.
@@ -150,7 +152,167 @@ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Res
}
}
+/// Trait representing GMC messages received from the GSP.
+///
+/// These can be either replies to sent commands that expect a reply, or an event not bound to any
+/// command.
+pub(crate) trait MessageFromGsp: Sized {
+ /// Id of the command this is the reply to.
+ const COMMAND: GmcCommandId;
+
+ /// Error type returned by [`MessageFromGsp::read`].
+ type InitError;
+
+ /// Method reading the reply from the payload of the message and returning it.
+ fn read(
+ payload: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError>;
+}
+
+/// A GMC message ready to be processed from the message queue.
+pub(crate) struct GmcMessage<'a> {
+ // Reference to the GMC API header of the message.
+ header: &'a GmcApiHeader,
+ // Slices to the payload of the message. The second slice is empty unless the message loops
+ // over the message queue.
+ contents: (&'a [u8], &'a [u8]),
+}
+
+impl<'a> GmcMessage<'a> {
+ /// Validate the GMC layer of `element` and returns its GMC API header and its contents trimmed
+ /// down to the GMC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the GMC API header, or than the payload length
+ /// advertised by the GMC API header.
+ pub(super) fn parse(dev: &device::Device, element: GspMessage<'a>) -> Result<GmcMessage<'a>> {
+ let GspMessage {
+ contents: (slice_1, slice_2),
+ ..
+ } = element;
+
+ // The GSP message header and the GMC API header together are guaranteed to fit entirely
+ // into a single page, so it's ok to only look at `slice_1` here.
+ let (gmc_header, slice_1) = GmcApiHeader::from_bytes_prefix(slice_1).ok_or(EIO)?;
+ let payload_size = gmc_header.payload_size();
+
+ dev_dbg!(
+ dev,
+ "GSP GMC: receive: seq# {}, command={:?}, length=0x{:x}\n",
+ gmc_header.sequence(),
+ gmc_header.command(),
+ payload_size,
+ );
+
+ // Check that the driver read area is large enough for the message.
+ if slice_1.len() + slice_2.len() < payload_size {
+ return Err(EIO);
+ }
+
+ // Cut the message slices down to the actual length of the message.
+ let (slice_1, slice_2) = if slice_1.len() > payload_size {
+ // PANIC: we checked above that `slice_1` is at least as long as `payload_size`.
+ (slice_1.split_at(payload_size).0, &slice_2[0..0])
+ } else {
+ (
+ slice_1,
+ // PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
+ // large as `payload_size`.
+ slice_2.split_at(payload_size - slice_1.len()).0,
+ )
+ };
+
+ Ok(GmcMessage {
+ header: gmc_header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ /// Returns the GMC API header of the message.
+ pub(crate) fn header(&self) -> &'a GmcApiHeader {
+ self.header
+ }
+
+ /// Returns the payload of the message.
+ ///
+ /// The second slice is empty unless the message loops over the message queue.
+ pub(crate) fn payload(&self) -> (&'a [u8], &'a [u8]) {
+ self.contents
+ }
+
+ /// Returns the status code of this message if it is the reply to `command` sent with sequence
+ /// number `seq`, `None` otherwise.
+ fn is_reply_to(&self, command: GmcCommandId, seq: u32) -> Option<u32> {
+ if matches!(self.header.command().id(), Ok(id) if id == command)
+ && self.header.sequence() == u64::from(seq)
+ {
+ self.header.status()
+ } else {
+ None
+ }
+ }
+
+ /// Logs an event, meaning a message that no caller was waiting for.
+ ///
+ /// A reply that nobody claimed (e.g. one that arrived after its request timed out) and an
+ /// unknown command id are logged at warning level. Other events are not logged.
+ pub(super) fn log(&self, dev: &device::Device) {
+ let command = self.header.command();
+ let seq = self.header.sequence();
+
+ match command.id() {
+ Ok(id) if command.is_response() => {
+ dev_warn!(dev, "unclaimed GSP GMC reply to {:?} (seq {})\n", id, seq);
+ }
+ // Nothing to do for the remaining known command ids.
+ Ok(_) => {}
+ Err(_) => {
+ dev_warn!(dev, "unknown GSP GMC command {:?} (seq {})\n", command, seq);
+ }
+ }
+ }
+}
+
impl Cmdq<'_> {
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// GMC events that arrive before the reply are passed to `on_event`, and RPC messages are
+ /// logged and consumed. The boot sequence relies on this to handle the load-and-execute
+ /// events that GSP-RM raises before it answers `GSP_INIT`.
+ ///
+ /// The queue is locked for the entire send+receive cycle, including the calls to `on_event`,
+ /// to ensure that no other command can be interleaved. `on_event` must thus not use the queue.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command does not fit in a single command queue element.
+ /// - `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 [`GmcCommand::init_variable_payload`] method, or if the reply carries
+ /// a status other than `NV_OK`.
+ ///
+ /// Error codes returned by the command and reply initializers, and by `on_event`, are
+ /// propagated as-is.
+ pub(crate) fn send_gmc_command<C>(
+ &self,
+ command: C,
+ on_event: impl Fn(&GmcMessage<'_>) -> Result,
+ ) -> Result<C::Reply>
+ where
+ C: CommandToGsp,
+ C::Reply: MessageFromGsp,
+ Error: From<C::InitError>,
+ Error: From<<C::Reply as MessageFromGsp>::InitError>,
+ {
+ let mut inner = self.inner.lock();
+ let seq = inner.send_gmc_command(command)?;
+
+ inner.await_gmc_reply(seq, on_event)
+ }
+
/// Sends `command` to the GSP without waiting for a reply.
///
/// # Errors
@@ -191,4 +353,70 @@ fn send_gmc_command<C>(&mut self, command: C) -> Result<u32>
{
self.send_command_element(GmcCommandElement(command))
}
+
+ /// Receives the reply of type `R` to the command sent with sequence number `seq`, waiting up
+ /// to [`Cmdq::RECEIVE_TIMEOUT`] from the call.
+ ///
+ /// Any GMC event that arrives first is passed to `on_event`, and any RPC message is logged.
+ /// Neither extends the deadline.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if the reply does not arrive before the deadline, however many other messages
+ /// arrive while waiting.
+ /// - `EIO` if a message fails framing or checksum validation, or if the reply carries a status
+ /// other than `NV_OK`.
+ ///
+ /// Error codes returned by [`MessageFromGsp::read`] and by `on_event` are propagated as-is.
+ fn await_gmc_reply<R: MessageFromGsp>(
+ &mut self,
+ seq: u32,
+ on_event: impl Fn(&GmcMessage<'_>) -> Result,
+ ) -> Result<R>
+ where
+ // This allows all error types, including `Infallible`, to be used for `R::InitError`.
+ Error: From<R::InitError>,
+ {
+ let dev = self.dev;
+
+ self.await_msg_with(|message| {
+ let gmc_message = match message {
+ GspParsedMessage::Gmc(gmc_message) => gmc_message,
+ _ => {
+ message.log(dev);
+ return Err(ENOMSG);
+ }
+ };
+
+ match gmc_message.is_reply_to(R::COMMAND, seq) {
+ // Message is not the reply to our command, log it.
+ None => {
+ on_event(&gmc_message)?;
+ return Err(ENOMSG);
+ }
+ // Reply has an error status.
+ Some(status @ 1..) => {
+ dev_err!(
+ dev,
+ "GSP GMC: command {:?} failed with status {:#x}\n",
+ R::COMMAND,
+ status
+ );
+
+ return Err(EIO);
+ }
+ // Reply indicates the command has succeeded.
+ Some(0) => (),
+ }
+
+ let mut sbuffer =
+ SBufferIter::new_reader([gmc_message.contents.0, gmc_message.contents.1]);
+
+ R::read(&mut sbuffer).map_err(|e| e.into()).inspect(|_| {
+ if !sbuffer.is_empty() {
+ dev_warn!(dev, "GSP GMC reply {:?} has unprocessed data\n", R::COMMAND);
+ }
+ })
+ })
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index 36cba7c28d3e..de3734daa99e 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -387,7 +387,13 @@ fn await_rpc_msg<M: MessageFromGsp>(&mut self) -> Result<M>
Error: From<M::InitError>,
{
let f = |message: GspParsedMessage<'_>| {
- let GspParsedMessage::Rpc(rpc_message) = message;
+ let rpc_message = match message {
+ GspParsedMessage::Rpc(rpc_message) => rpc_message,
+ _ => {
+ message.log(self.dev);
+ return Err(ENOMSG);
+ }
+ };
let function = rpc_message.header.function();
if matches!(function, Ok(f) if f == M::FUNCTION) {
let (cmd, contents_1) =
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 367c8b5e3443..de61afd60403 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -935,7 +935,6 @@ pub(crate) struct GmcCommandWord(u32) {
/// In a queue element, it follows the queue element header and precedes the payload of the
/// command.
#[repr(transparent)]
-#[expect(dead_code)]
pub(crate) struct GmcApiHeader {
inner: r000_00::GMCAPI_HEADER,
}
--
2.56.0