[PATCH 3/4] gpu: nova-core: gsp: cmdq: add the GMC send path

From: Alexandre Courbot

Date: Mon Nov 02 2026 - 19:08:44 EST


From: John Hubbard <jhubbard@xxxxxxxxxx>

The r000 boot protocol has the driver send GSP_INIT, the request that
carries the driver's configuration to GSP-RM, as a GMC (GPU Management
Controller) API command. GMC and RPC commands share the command queue
and its sequence counter. A GMC command always fits in one element, as
the GMC API has no continuation records.

Add the gmc sub-module of the command queue, sibling of rpc, traits and
types definitions mirroring those of rpc and adding support for GMC messages.

The module has no user yet, and is built as dead code until the switch
to r000.

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 | 4 +
drivers/gpu/nova-core/gsp/cmdq/gmc.rs | 194 ++++++++++++++++++++++++++++++++++
2 files changed, 198 insertions(+)

diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index c8b103a0785d..629ab8d35f87 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

+// The transport header of the r570 firmware carries no message type, so nothing can produce or
+// consume a GMC message before the switch to r000. Until then the module is built as dead code.
+#[expect(dead_code)]
+pub(super) mod gmc;
pub(super) mod rpc;

use core::mem;
diff --git a/drivers/gpu/nova-core/gsp/cmdq/gmc.rs b/drivers/gpu/nova-core/gsp/cmdq/gmc.rs
new file mode 100644
index 000000000000..b035f30479a8
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/cmdq/gmc.rs
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Support for the GMC (GPU Management Controller) API message type on the GSP command queue.
+
+use kernel::{
+ device,
+ prelude::*,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use crate::{
+ gsp::{
+ cmdq::{
+ GspCommand,
+ NoReply, //
+ },
+ fw::{
+ GmcApiHeader,
+ GmcCommandId, //
+ },
+ },
+ sbuffer::SBufferIter,
+};
+
+use super::{
+ Cmdq,
+ CmdqInner,
+ CommandElement, //
+};
+
+/// Trait implemented by types representing a GMC command to send to the GSP.
+///
+/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
+/// a given command.
+///
+/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
+/// into the space reserved for it in the command queue buffer.
+///
+/// Some commands may be followed by a variable-length payload. For these, the
+/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
+/// defined as well.
+pub(crate) trait CommandToGsp {
+ /// Id identifying this command to the GSP.
+ const COMMAND: GmcCommandId;
+
+ /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer right
+ /// after the GMC API header.
+ type Command: FromBytes + AsBytes;
+
+ /// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't have a
+ /// reply.
+ type Reply;
+
+ /// Largest reply this command accepts, in bytes. Zero for commands that the GSP does not
+ /// answer.
+ const MAX_REPLY_SIZE: u32;
+
+ /// Error type returned by [`CommandToGsp::init`].
+ type InitError;
+
+ /// In-place command initializer responsible for filling the command in the command queue
+ /// buffer.
+ fn init(&self) -> impl Init<Self::Command, Self::InitError>;
+
+ /// Size of the variable-length payload following the command structure generated by
+ /// [`CommandToGsp::init`].
+ ///
+ /// Most commands don't have a variable-length payload, so this is zero by default.
+ fn variable_payload_len(&self) -> usize {
+ 0
+ }
+
+ /// Method initializing the variable-length payload.
+ ///
+ /// The command buffer is circular, which means that we may need to jump back to its beginning
+ /// while in the middle of a command. For this reason, the variable-length payload is
+ /// initialized using a [`SBufferIter`].
+ ///
+ /// This method will receive a buffer of the length returned by
+ /// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
+ /// unwritten space will lead to an error.
+ ///
+ /// Most commands don't have a variable-length payload, so this does nothing by default.
+ fn init_variable_payload(
+ &self,
+ _dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
+ ) -> Result {
+ Ok(())
+ }
+
+ /// Total size of the command (including its variable-length payload) without the GMC API
+ /// header, i.e. the payload size advertised by that header.
+ fn size(&self) -> usize {
+ size_of::<Self::Command>() + self.variable_payload_len()
+ }
+}
+
+/// Wrapper type for sending a GMC command as a command queue element.
+struct GmcCommandElement<C>(C);
+
+impl<C> CommandElement for GmcCommandElement<C>
+where
+ C: CommandToGsp,
+ Error: From<C::InitError>,
+{
+ fn size(&self) -> usize {
+ size_of::<GmcApiHeader>() + self.0.size()
+ }
+
+ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
+ let command = &self.0;
+ let payload_size = command.size();
+ // Extract the areas for the GMC API header and the command itself. The GSP message
+ // header, the GMC API header and the command header together are guaranteed to fit
+ // entirely into a single page, so it's ok to only look at `dst.contents.0` here.
+ let (header, contents) = GmcApiHeader::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
+ let (cmd, payload_1) = C::Command::from_bytes_mut_prefix(contents).ok_or(EIO)?;
+ let header_init =
+ GmcApiHeader::init(C::COMMAND, u64::from(seq), payload_size, C::MAX_REPLY_SIZE);
+ // SAFETY: `header` is a valid reference, and is not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(header), header_init)?;
+ }
+ // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
+ }
+
+ // Fill the variable-length payload, which may be empty.
+ let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
+ command.init_variable_payload(&mut sbuffer)?;
+
+ if !sbuffer.is_empty() {
+ return Err(EIO);
+ }
+
+ dev_dbg!(
+ dev,
+ "GSP GMC: send: seq# {}, command={:?}, length=0x{:x}\n",
+ seq,
+ C::COMMAND,
+ payload_size,
+ );
+
+ Ok(())
+ }
+}
+
+impl Cmdq<'_> {
+ /// Sends `command` to the GSP without waiting for a reply.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command does not fit in a single command queue element.
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializer are propagated as-is.
+ pub(crate) fn send_gmc_command_no_wait<C>(&self, command: C) -> Result
+ where
+ C: CommandToGsp<Reply = NoReply>,
+ Error: From<C::InitError>,
+ {
+ self.inner.lock().send_gmc_command(command).map(|_| ())
+ }
+}
+
+impl CmdqInner<'_> {
+ /// Sends `command` to the GSP, and returns the sequence number of its element.
+ ///
+ /// Unlike RPC commands, GMC commands are never split: a command that does not fit in a single
+ /// element is an error.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command does not fit in a single command queue element.
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializer are propagated as-is.
+ fn send_gmc_command<C>(&mut self, command: C) -> Result<u32>
+ where
+ C: CommandToGsp,
+ Error: From<C::InitError>,
+ {
+ self.send_command_element(GmcCommandElement(command))
+ }
+}

--
2.56.0