From: "Eliot Courtney" <ecourtney@nvidia.com>
To: "Alexandre Courbot" <acourbot@nvidia.com>,
"Danilo Krummrich" <dakr@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Benno Lossin" <lossin@kernel.org>, "Gary Guo" <gary@garyguo.net>
Cc: "John Hubbard" <jhubbard@nvidia.com>,
"Alistair Popple" <apopple@nvidia.com>,
"Timur Tabi" <ttabi@nvidia.com>,
"Eliot Courtney" <ecourtney@nvidia.com>,
"Zhi Wang" <zhiw@nvidia.com>, <linux-kernel@vger.kernel.org>,
<nova-gpu@lists.linux.dev>, <rust-for-linux@vger.kernel.org>
Subject: Re: [PATCH 4/4] gpu: nova-core: gsp: cmdq: add the GMC receive path
Date: Thu, 05 Nov 2026 18:23:52 +0900 [thread overview]
Message-ID: <DMN62G2CDG3N.ETT4GRFXWKKU@nvidia.com> (raw)
In-Reply-To: <20261103-r000_rebase_on_cmdq-rpc-v1-4-189f9ff3bfd2@nvidia.com>
On Tue Nov 3, 2026 at 9:02 AM JST, Alexandre Courbot wrote:
> From: John Hubbard <jhubbard@nvidia.com>
>
> 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@nvidia.com>
> Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> 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;
What about removing "COMMAND" here, and have `await_gmc_reply` take the
CommandToGsp type, returning C::Reply (where C::Reply: MessageFromGsp)?
That removes needing to define a bidirectional mapping which could
become out of sync. Also, if this is going to be used for "an event not
bound to any command" then it's better not to specify `COMMAND` imo.
> +
> + /// 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,
> + )
> + };
Consider sharing a helper for this (with the RPC code which does the same thing).
The split_at_checked also IMO simplifies the code.
```
fn message_prefix<'a>(
(s1, s2): (&'a [u8], &'a [u8]),
len: usize,
) -> Option<(&'a [u8], &'a [u8])> {
match s1.split_at_checked(len) {
Some((p, _)) => Some((p, &[])),
None => s2.get(..len - s1.len()).map(|rest| (s1, rest)),
}
}
```
> +
> + 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
> + }
> + }
What about calling this "reply_status" since it returns an optional
reply status?
> +
> + /// 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
[`GmcCommand::init_variable_payload`] =>
[`CommandToGsp::init_variable_payload`]? Can you check the doclinks?
> + /// 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>,
Currently, we have this flow:
await_gmc_reply -> await_msg_with -> consume_element -> closure
`consume_element` returns Result<R>. The outer Result is a transport
level result. The inner R is a gmc/rpc level object, and it has the
contract that ENOMSG means drain this element. In `await_gmc_reply` we
provide the closure which is doing this gmc/rpc level job, and also
asking the `MessageFromGsp` to decode itself plus getting the nvstatus.
I think that `await_gmc_reply` should return Result<Result<R>> (and
Result<Result<R, NvStatus>> in the future, or even Result<Result<R,
NvStatus>, CmdqError>). For example, if we add an NvStatus enum and have
it convertible to ernno, then later someone adds a conversion to ENOMSG,
it could trigger this. And semantically, there is a difference between a
successfully received reply that had a failed status vs a failed/borked
reply. The RPC code also has this issue where it doesn't distinguish
between a successfully received reply which conveys a failed status vs
borked reply (actually, we don't even check the status for RPC, which
isn't good).
It's also worth considering adding a custom error type enum that has
Drain or something instead of using ENOMSG - then we can encode drain in
the type system and make it impossible for decoder/encoder
implementations to affect cmdq control flow.
Another reason to do this is that the later vGPU code [1] (see
`CeUtilsAllocError`) needs to distinguish this case - explicitly failed
command vs transport failure.
[1]: https://lore.kernel.org/all/6fbf38e3622a135c662443155513976898b1ab68.1790580105.git.zhiw@nvidia.com/
> + {
> + 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) {
It's possible if we hit a timeout earlier, to get an old reply here
(`is_response` is true but one or both of the sequence number or command
id doesn't match). In that case IMO we should log and return Err(ENOMSG)
rather than calling `on_event`, because it feels odd for `on_event` to
receive things with `is_response` true.
> + // Message is not the reply to our command, log it.
Comment says it'll log, but it only calls "on_event".
> + None => {
> + on_event(&gmc_message)?;
It's probably worth a note that if `on_event` returns Err(ENOMSG) then
it'll skip (drain) that event. So Ok(()) and Err(ENOMSG) are equivalent
here, which is a bit odd.
> + return Err(ENOMSG);
> + }
> + // Reply has an error status.
> + Some(status @ 1..) => {
Ok for now, but maybe a TODO to add the NvStatus enum? I sent a patch
adding it before [2]. It will probably be more important to delineate
the transport vs message vs content layer errors if we do this.
[2]: https://lore.kernel.org/all/20260418-rmcontrol-v4-2-fda8c76dbb95@nvidia.com/
prev parent reply other threads:[~2026-11-05 9:24 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 0:02 [PATCH 0/4] gpu: nova-core: gsp: add support for GMC messages Alexandre Courbot
2026-11-03 0:02 ` [PATCH 1/4] gpu: nova-core: gsp: fw: add the r000 bindings for the GMC API header Alexandre Courbot
2026-11-04 9:21 ` Eliot Courtney
2026-11-03 0:02 ` [PATCH 2/4] gpu: nova-core: gsp: add " Alexandre Courbot
2026-11-04 9:34 ` Eliot Courtney
2026-11-03 0:02 ` [PATCH 3/4] gpu: nova-core: gsp: cmdq: add the GMC send path Alexandre Courbot
2026-11-05 1:07 ` Eliot Courtney
2026-11-03 0:02 ` [PATCH 4/4] gpu: nova-core: gsp: cmdq: add the GMC receive path Alexandre Courbot
2026-11-05 9:23 ` Eliot Courtney [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DMN62G2CDG3N.ETT4GRFXWKKU@nvidia.com \
--to=ecourtney@nvidia.com \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=ttabi@nvidia.com \
--cc=zhiw@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox