From: "Alexandre Courbot" <acourbot@nvidia.com>
To: "Eliot Courtney" <ecourtney@nvidia.com>
Cc: "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>,
"John Hubbard" <jhubbard@nvidia.com>,
"Alistair Popple" <apopple@nvidia.com>,
"Timur Tabi" <ttabi@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: Fri, 06 Nov 2026 15:38:14 +0900 [thread overview]
Message-ID: <DMNX661YLSXA.3DZ2LVCDVD3GT@nvidia.com> (raw)
In-Reply-To: <DMN62G2CDG3N.ETT4GRFXWKKU@nvidia.com>
On Thu Nov 5, 2026 at 6:23 PM JST, Eliot Courtney wrote:
<...>
>> 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.
Oh yes, that simplifies things quite a bit, thanks!
<...>
>> +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)),
> }
> }
> ```
Although the subtraction from the `None` branch can never underflow, it
would still need a justifying comment and I was wondering whether we
could remove it entirely. I came with this alternative version which I
like slightly better, let me know if you see a problem with it:
// See if `slice_1` covers the whole message, take it entirely if it doesn't.
let slice_1 = slice_1
.split_at_checked(length)
.map(|split| split.0)
.unwrap_or(slice_1);
// `slice_2` is the remainder of the message not covered by `slice_1`.
let slice_2 = slice_2
.get(..length.saturating_sub(slice_1.len()))
.ok_or(EIO)?;
>
>> +
>> + 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?
Done.
>
>> +
>> + /// 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?
Fixed, thanks!
<...>
>> + /// 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.
Yeah, that's indeed a blind spot in the command queue design. Your
nested Result suggestion makes sense, so I'll introduce the idea as a
separate patch in the next revision.
>
> [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.
Done. TBH I am still not super happy with the way we are handling events
actually, but would like to have a bit more visibility about how we will
use GMCAPI to change things more fundamentally. The current way is not
perfect for gets the job done for now.
>
>> + // 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.
Indeed. This behavior will be gone in the next revision.
>
>> + 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.
I am not quite sure what you want to me do here (probably because I lack
the context about NvStatus). Can you be more specific?
prev parent reply other threads:[~2026-11-06 6:38 UTC|newest]
Thread overview: 12+ 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-03 0:08 ` sashiko-bot
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-03 0:14 ` sashiko-bot
2026-11-05 9:23 ` Eliot Courtney
2026-11-06 6:38 ` Alexandre Courbot [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=DMNX661YLSXA.3DZ2LVCDVD3GT@nvidia.com \
--to=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=dakr@kernel.org \
--cc=ecourtney@nvidia.com \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.