From: Alexandre Courbot <acourbot@nvidia.com>
To: John Hubbard <jhubbard@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: Alistair Popple <apopple@nvidia.com>,
Timur Tabi <ttabi@nvidia.com>,
Eliot Courtney <ecourtney@nvidia.com>,
Zhi Wang <zhiw@nvidia.com>,
nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org,
linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Alexandre Courbot <acourbot@nvidia.com>
Subject: [PATCH v4 08/10] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module
Date: Fri, 09 Oct 2026 20:54:04 +0900 [thread overview]
Message-ID: <20261009-cmdq-rpc-v4-8-c9ab8de1d3f2@nvidia.com> (raw)
In-Reply-To: <20261009-cmdq-rpc-v4-0-c9ab8de1d3f2@nvidia.com>
Move the types and code related to sending and receiving RPC commands
and messages into the `rpc` sub-module. The methods are still declared
as members of `CmdqInner` and `Cmdq`; only they are now in their own
sub-module.
This is a pure move commit, with no functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 412 +------------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 443 +++++++++++++++++++++
.../nova-core/gsp/cmdq/{ => rpc}/continuation.rs | 0
drivers/gpu/nova-core/gsp/commands.rs | 6 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 4 +-
5 files changed, 454 insertions(+), 411 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 65e1a0b6bb44..c7deb1ae58f4 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
-mod continuation;
+pub(super) mod rpc;
use core::mem;
@@ -28,19 +29,13 @@
},
Mutex, //
},
- time::{
- Delta,
- Instant,
- Monotonic, //
- },
+ time::Delta,
transmute::{
AsBytes,
FromBytes, //
},
};
-use continuation::SplitState;
-
use pin_init::pin_init_scope;
use crate::{
@@ -48,10 +43,8 @@
gsp::{
fw::{
GspMsgElement,
- MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
- RpcMessageHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -81,124 +74,6 @@ trait CommandElement {
fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result;
}
-/// Trait implemented by types representing a 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 {
- /// Function identifying this command to the GSP.
- const FUNCTION: MsgFunction;
-
- /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
- type Command: FromBytes + AsBytes;
-
- /// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
- /// have a reply.
- type Reply;
-
- /// 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
- /// [`GspMsgElement`] header.
- fn size(&self) -> usize {
- size_of::<Self::Command>() + self.variable_payload_len()
- }
-}
-
-/// Wrapper type for sending a RPC command as a command queue element.
-///
-/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket
-/// implementation as it would conflict with other future command types.
-struct RpcCommandElement<M>(M);
-
-impl<M> CommandElement for RpcCommandElement<M>
-where
- M: CommandToGsp,
- Error: From<M::InitError>,
-{
- fn size(&self) -> usize {
- self.0.size()
- }
-
- fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
- let command = &self.0;
- let size_in_bytes = command.size();
- // Extract area for the command itself. The GSP message 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 (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
- let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
- // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
- // initializer fails.
- unsafe {
- pin_init::raw_try_init(
- core::ptr::from_mut(dst.header.rpc_header_mut()),
- rpc_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 RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
- seq,
- M::FUNCTION,
- size_in_bytes,
- );
-
- Ok(())
- }
-}
-
/// Trait implemented by types that can be received as single command queue elements.
///
/// The command queue validates the element header before calling `read()` to interpret the
@@ -208,70 +83,6 @@ trait MessageElement: Sized {
fn read(dev: &device::Device, message: GspMessage<'_>) -> Result<Self>;
}
-/// Trait representing messages received from the GSP.
-///
-/// A reply that [`Cmdq::send_command`] waits for, or an event that [`Cmdq::await_msg`] waits for.
-/// The receiver matches a message's function code against [`Self::FUNCTION`] and decodes the
-/// message with [`Self::read`].
-pub(crate) trait MessageFromGsp: Sized {
- /// Function identifying this message from the GSP.
- const FUNCTION: MsgFunction;
-
- /// Error type returned by [`MessageFromGsp::read`].
- type InitError;
-
- /// Type containing the raw message to be read from the message queue.
- type Message: FromBytes;
-
- /// Method reading the message from the message queue and returning it.
- ///
- /// From a `Self::Message` and a [`SBufferIter`], constructs an instance of `Self` and returns
- /// it.
- fn read(
- msg: &Self::Message,
- sbuffer: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError>;
-}
-
-/// Wrapper type for receiving a RPC message from a command queue element.
-///
-/// [`MessageElement`] cannot be directly implemented for all [`MessageFromGsp`] with a blanket
-/// implementation as it would conflict with other future message types.
-struct RpcMessageElement<M>(M);
-
-impl<M> MessageElement for RpcMessageElement<M>
-where
- M: MessageFromGsp,
- Error: From<M::InitError>,
-{
- fn read(dev: &device::Device, message: GspMessage<'_>) -> Result<Self> {
- let rpc_message = RpcMessage::parse(dev, message)?;
- let function = rpc_message.header.function();
-
- // An early return here would leave the read pointer on this message.
- let result = if matches!(function, Ok(f) if f == M::FUNCTION) {
- let (cmd, contents_1) =
- M::Message::from_bytes_prefix(rpc_message.contents.0).ok_or(EIO)?;
- let mut sbuffer = SBufferIter::new_reader([contents_1, rpc_message.contents.1]);
-
- M::read(cmd, &mut sbuffer)
- .map(Self)
- .map_err(|e| e.into())
- .inspect(|_| {
- if !sbuffer.is_empty() {
- dev_warn!(dev, "GSP message {:?} has unprocessed data\n", M::FUNCTION);
- }
- })
- } else {
- rpc_message.log(dev);
-
- Err(ENOMSG)
- };
-
- result
- }
-}
-
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
@@ -607,92 +418,6 @@ struct GspMessage<'a> {
contents: (&'a [u8], &'a [u8]),
}
-/// A RPC message ready to be processed from the message queue.
-struct RpcMessage<'a> {
- // Reference to the RPC header of the message.
- header: &'a RpcMessageHeader,
- // Slices to the contents of the message. The second slice is empty unless the message loops
- // over the message queue.
- contents: (&'a [u8], &'a [u8]),
-}
-
-impl<'a> RpcMessage<'a> {
- /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
- /// to the RPC payload.
- ///
- /// # Errors
- ///
- /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
- fn parse(dev: &device::Device, element: GspMessage<'a>) -> Result<RpcMessage<'a>> {
- let GspMessage {
- header,
- contents: (slice_1, slice_2),
- } = element;
-
- let rpc_header = header.rpc_header();
- let payload_length = rpc_header.rpc_length();
-
- dev_dbg!(
- dev,
- "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- rpc_header.sequence(),
- rpc_header.function(),
- payload_length,
- );
-
- // Check that the driver read area is large enough for the message.
- if slice_1.len() + slice_2.len() < payload_length {
- 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_length {
- // PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
- (slice_1.split_at(payload_length).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_length`.
- slice_2.split_at(payload_length - slice_1.len()).0,
- )
- };
-
- Ok(RpcMessage {
- header: rpc_header,
- contents: (slice_1, slice_2),
- })
- }
-
- /// Logs an event, meaning a message that no caller was waiting for.
- ///
- /// An OS error or robust-channel record is logged at error level and an unknown function code
- /// at warning level. Other events are not logged.
- fn log(&self, dev: &device::Device) {
- let function = self.header.function();
- let seq = self.header.sequence();
-
- match function {
- Ok(MsgFunction::OsErrorLog) => {
- dev_err!(dev, "GSP reported an OS error (seq {})\n", seq);
- }
- Ok(MsgFunction::RcTriggered) => {
- dev_err!(dev, "GSP triggered robust-channel recovery (seq {})\n", seq);
- }
- // Nothing to do for the remaining known function codes.
- Ok(_) => {}
- Err(raw) => {
- dev_warn!(
- dev,
- "unknown GSP message function {:#x} (seq {})\n",
- raw,
- seq
- );
- }
- }
- }
-}
-
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
@@ -764,73 +489,6 @@ fn notify_gsp(bar: Bar0<'_>) {
bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
}
- /// Sends `command` to the GSP and waits for the reply.
- ///
- /// Events that arrive before the reply are logged and consumed.
- ///
- /// The queue is locked for the entire send+receive cycle to ensure that no other command can
- /// be interleaved.
- ///
- /// # Errors
- ///
- /// - `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 [`CommandToGsp::init_variable_payload`] method.
- ///
- /// Error codes returned by the command and reply initializers are propagated as-is.
- pub(crate) fn send_command<M>(&self, command: M) -> Result<M::Reply>
- where
- M: CommandToGsp,
- M::Reply: MessageFromGsp,
- Error: From<M::InitError>,
- Error: From<<M::Reply as MessageFromGsp>::InitError>,
- {
- let mut inner = self.inner.lock();
- inner.send_command(command)?;
-
- inner.await_msg()
- }
-
- /// Sends `command` to the GSP without waiting for a reply.
- ///
- /// # Errors
- ///
- /// - `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 initializers are propagated as-is.
- pub(crate) fn send_command_no_wait<M>(&self, command: M) -> Result
- where
- M: CommandToGsp<Reply = NoReply>,
- Error: From<M::InitError>,
- {
- self.inner.lock().send_command(command)
- }
-
- /// Waits for an unsolicited GSP event of type `M`. Events that arrive before it are logged and
- /// consumed.
- ///
- /// The queue mutex is held for the whole wait, up to [`Self::RECEIVE_TIMEOUT`], so no other
- /// caller can send a command or consume an event meanwhile.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if the event does not arrive within [`Self::RECEIVE_TIMEOUT`] of the call,
- /// however many other events arrive while waiting.
- /// - `EIO` if a message fails framing or checksum validation.
- ///
- /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- 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>,
- {
- self.inner.lock().await_msg()
- }
-
/// Logs and consumes every message the GSP has already posted, and returns without waiting for
/// more.
///
@@ -904,36 +562,6 @@ fn send_command_element<E: CommandElement>(&mut self, command: E) -> Result {
Ok(())
}
- /// Sends `command` to the GSP.
- ///
- /// The command may be split into multiple messages if it is large.
- ///
- /// # Errors
- ///
- /// - `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 initializers are propagated as-is.
- fn send_command<M>(&mut self, command: M) -> Result
- where
- M: CommandToGsp,
- Error: From<M::InitError>,
- {
- match SplitState::new(command)? {
- SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)),
- SplitState::Split(command, mut continuations) => {
- self.send_command_element(RpcCommandElement(command))?;
-
- while let Some(continuation) = continuations.next() {
- self.send_command_element(RpcCommandElement(continuation))?;
- }
-
- Ok(())
- }
- }
- }
-
/// Wait for the next element to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
@@ -1001,37 +629,6 @@ fn consume_element<M: MessageElement>(&mut self, timeout: Delta) -> Result<M> {
result
}
- /// Receives a message of type `M`, waiting up to [`Cmdq::RECEIVE_TIMEOUT`] from the call.
- ///
- /// Any other message that arrives first is logged as an event and does not extend the
- /// deadline.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if no message of type `M` arrives before the deadline, however many other
- /// messages arrive while waiting.
- /// - `EIO` if a message fails framing or checksum validation (see [`Self::wait_for_msg`]).
- ///
- /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- fn await_msg<M: MessageFromGsp>(&mut 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() + Cmdq::RECEIVE_TIMEOUT;
- loop {
- let remaining = deadline - Instant::<Monotonic>::now();
- if remaining.is_negative() {
- break Err(ETIMEDOUT);
- }
- match self.consume_element::<RpcMessageElement<M>>(remaining) {
- Ok(msg) => break Ok(msg.0),
- Err(ENOMSG) => continue,
- Err(e) => break Err(e),
- }
- }
- }
-
/// Logs and consumes every message the queue holds.
///
/// # Errors
@@ -1049,7 +646,8 @@ fn drain(&mut self) -> Result {
EIO
})?;
- RpcMessage::parse(self.dev, msg)?.log(self.dev);
+ // RPC is the only type of message supported, so dispatch the message there.
+ rpc::RpcMessage::parse(self.dev, msg)?.log(self.dev);
self.gsp_mem.advance_cpu_read_ptr(pages);
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
new file mode 100644
index 000000000000..39fc3a9e61fe
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Support for the RPC message type on the GSP command queue.
+
+mod continuation;
+
+use kernel::{
+ device,
+ prelude::*,
+ time::{
+ Instant,
+ Monotonic, //
+ },
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use continuation::SplitState;
+
+use crate::{
+ gsp::{
+ cmdq::{
+ GspCommand,
+ NoReply, //
+ },
+ fw::{
+ MsgFunction,
+ RpcMessageHeader, //
+ },
+ },
+ sbuffer::SBufferIter,
+};
+
+use super::{
+ Cmdq,
+ CmdqInner,
+ CommandElement,
+ GspMessage,
+ MessageElement, //
+};
+
+/// Trait implemented by types representing a 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 {
+ /// Function identifying this command to the GSP.
+ const FUNCTION: MsgFunction;
+
+ /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
+ type Command: FromBytes + AsBytes;
+
+ /// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
+ /// have a reply.
+ type Reply;
+
+ /// 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
+ /// [`GspMsgElement`] header.
+ fn size(&self) -> usize {
+ size_of::<Self::Command>() + self.variable_payload_len()
+ }
+}
+
+/// Wrapper type for sending a RPC command as a command queue element.
+///
+/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket
+/// implementation as it would conflict with other future command types.
+struct RpcCommandElement<M>(M);
+
+impl<M> CommandElement for RpcCommandElement<M>
+where
+ M: CommandToGsp,
+ Error: From<M::InitError>,
+{
+ fn size(&self) -> usize {
+ self.0.size()
+ }
+
+ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
+ let command = &self.0;
+ let size_in_bytes = command.size();
+ // Extract area for the command itself. The GSP message 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 (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
+ let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
+ // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_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 RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
+ seq,
+ M::FUNCTION,
+ size_in_bytes,
+ );
+
+ Ok(())
+ }
+}
+
+/// Trait representing messages received from the GSP.
+///
+/// A reply that [`Cmdq::send_command`] waits for, or an event that [`Cmdq::await_msg`] waits for.
+/// The receiver matches a message's function code against [`Self::FUNCTION`] and decodes the
+/// message with [`Self::read`].
+pub(crate) trait MessageFromGsp: Sized {
+ /// Function identifying this message from the GSP.
+ const FUNCTION: MsgFunction;
+
+ /// Error type returned by [`MessageFromGsp::read`].
+ type InitError;
+
+ /// Type containing the raw message to be read from the message queue.
+ type Message: FromBytes;
+
+ /// Method reading the message from the message queue and returning it.
+ ///
+ /// From a `Self::Message` and a [`SBufferIter`], constructs an instance of `Self` and returns
+ /// it.
+ fn read(
+ msg: &Self::Message,
+ sbuffer: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError>;
+}
+
+/// Wrapper type for receiving a RPC message from a command queue element.
+///
+/// [`MessageElement`] cannot be directly implemented for all [`MessageFromGsp`] with a blanket
+/// implementation as it would conflict with other future message types.
+struct RpcMessageElement<M>(M);
+
+impl<M> MessageElement for RpcMessageElement<M>
+where
+ M: MessageFromGsp,
+ Error: From<M::InitError>,
+{
+ fn read(dev: &device::Device, message: GspMessage<'_>) -> Result<Self> {
+ let rpc_message = RpcMessage::parse(dev, message)?;
+ let function = rpc_message.header.function();
+
+ // An early return here would leave the read pointer on this message.
+ let result = if matches!(function, Ok(f) if f == M::FUNCTION) {
+ let (cmd, contents_1) =
+ M::Message::from_bytes_prefix(rpc_message.contents.0).ok_or(EIO)?;
+ let mut sbuffer = SBufferIter::new_reader([contents_1, rpc_message.contents.1]);
+
+ M::read(cmd, &mut sbuffer)
+ .map(Self)
+ .map_err(|e| e.into())
+ .inspect(|_| {
+ if !sbuffer.is_empty() {
+ dev_warn!(dev, "GSP message {:?} has unprocessed data\n", M::FUNCTION);
+ }
+ })
+ } else {
+ rpc_message.log(dev);
+
+ Err(ENOMSG)
+ };
+
+ result
+ }
+}
+
+/// A RPC message ready to be processed from the message queue.
+pub(super) struct RpcMessage<'a> {
+ // Reference to the RPC header of the message.
+ header: &'a RpcMessageHeader,
+ // Slices to the contents of the message. The second slice is empty unless the message loops
+ // over the message queue.
+ contents: (&'a [u8], &'a [u8]),
+}
+
+impl<'a> RpcMessage<'a> {
+ /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
+ /// to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ pub(super) fn parse(dev: &device::Device, element: GspMessage<'a>) -> Result<RpcMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.rpc_length();
+
+ dev_dbg!(
+ dev,
+ "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
+ rpc_header.sequence(),
+ rpc_header.function(),
+ payload_length,
+ );
+
+ // Check that the driver read area is large enough for the message.
+ if slice_1.len() + slice_2.len() < payload_length {
+ 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_length {
+ // PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
+ (slice_1.split_at(payload_length).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_length`.
+ slice_2.split_at(payload_length - slice_1.len()).0,
+ )
+ };
+
+ Ok(RpcMessage {
+ header: rpc_header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ /// Logs an event, meaning a message that no caller was waiting for.
+ ///
+ /// An OS error or robust-channel record is logged at error level and an unknown function code
+ /// at warning level. Other events are not logged.
+ pub(super) fn log(&self, dev: &device::Device) {
+ let function = self.header.function();
+ let seq = self.header.sequence();
+
+ match function {
+ Ok(MsgFunction::OsErrorLog) => {
+ dev_err!(dev, "GSP reported an OS error (seq {})\n", seq);
+ }
+ Ok(MsgFunction::RcTriggered) => {
+ dev_err!(dev, "GSP triggered robust-channel recovery (seq {})\n", seq);
+ }
+ // Nothing to do for the remaining known function codes.
+ Ok(_) => {}
+ Err(raw) => {
+ dev_warn!(
+ dev,
+ "unknown GSP message function {:#x} (seq {})\n",
+ raw,
+ seq
+ );
+ }
+ }
+ }
+}
+
+impl Cmdq<'_> {
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// Events that arrive before the reply are logged and consumed.
+ ///
+ /// The queue is locked for the entire send+receive cycle to ensure that no other command can
+ /// be interleaved.
+ ///
+ /// # Errors
+ ///
+ /// - `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 [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command and reply initializers are propagated as-is.
+ pub(crate) fn send_command<M>(&self, command: M) -> Result<M::Reply>
+ where
+ M: CommandToGsp,
+ M::Reply: MessageFromGsp,
+ Error: From<M::InitError>,
+ Error: From<<M::Reply as MessageFromGsp>::InitError>,
+ {
+ let mut inner = self.inner.lock();
+ inner.send_command(command)?;
+
+ inner.await_msg()
+ }
+
+ /// Sends `command` to the GSP without waiting for a reply.
+ ///
+ /// # Errors
+ ///
+ /// - `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 initializers are propagated as-is.
+ pub(crate) fn send_command_no_wait<M>(&self, command: M) -> Result
+ where
+ M: CommandToGsp<Reply = NoReply>,
+ Error: From<M::InitError>,
+ {
+ self.inner.lock().send_command(command)
+ }
+
+ /// Waits for an unsolicited GSP event of type `M`. Events that arrive before it are logged and
+ /// consumed.
+ ///
+ /// The queue mutex is held for the whole wait, up to [`Self::RECEIVE_TIMEOUT`], so no other
+ /// caller can send a command or consume an event meanwhile.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if the event does not arrive within [`Self::RECEIVE_TIMEOUT`] of the call,
+ /// however many other events arrive while waiting.
+ /// - `EIO` if a message fails framing or checksum validation.
+ ///
+ /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
+ 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>,
+ {
+ self.inner.lock().await_msg()
+ }
+}
+
+impl CmdqInner<'_> {
+ /// Sends `command` to the GSP.
+ ///
+ /// The command may be split into multiple messages if it is large.
+ ///
+ /// # Errors
+ ///
+ /// - `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 initializers are propagated as-is.
+ fn send_command<M>(&mut self, command: M) -> Result
+ where
+ M: CommandToGsp,
+ Error: From<M::InitError>,
+ {
+ match SplitState::new(command)? {
+ SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)),
+ SplitState::Split(command, mut continuations) => {
+ self.send_command_element(RpcCommandElement(command))?;
+
+ while let Some(continuation) = continuations.next() {
+ self.send_command_element(RpcCommandElement(continuation))?;
+ }
+
+ Ok(())
+ }
+ }
+ }
+
+ /// Receives a message of type `M`, waiting up to [`Cmdq::RECEIVE_TIMEOUT`] from the call.
+ ///
+ /// Any other message that arrives first is logged as an event and does not extend the
+ /// deadline.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if no message of type `M` arrives before the deadline, however many other
+ /// messages arrive while waiting.
+ /// - `EIO` if a message fails framing or checksum validation (see [`Self::wait_for_msg`]).
+ ///
+ /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
+ fn await_msg<M: MessageFromGsp>(&mut 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() + Cmdq::RECEIVE_TIMEOUT;
+ loop {
+ let remaining = deadline - Instant::<Monotonic>::now();
+ if remaining.is_negative() {
+ break Err(ETIMEDOUT);
+ }
+ match self.consume_element::<RpcMessageElement<M>>(remaining) {
+ Ok(msg) => break Ok(msg.0),
+ Err(ENOMSG) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc/continuation.rs
similarity index 100%
rename from drivers/gpu/nova-core/gsp/cmdq/continuation.rs
rename to drivers/gpu/nova-core/gsp/cmdq/rpc/continuation.rs
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index cdbb13674f08..ceac0b0f9423 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -23,9 +23,11 @@
gpu::Chipset,
gsp::{
cmdq::{
+ rpc::{
+ CommandToGsp,
+ MessageFromGsp, //
+ },
Cmdq,
- CommandToGsp,
- MessageFromGsp,
NoReply, //
},
fw::{
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index 250adc9fe74f..9f5ab08def2c 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -28,8 +28,8 @@
},
gsp::{
cmdq::{
- Cmdq,
- MessageFromGsp, //
+ rpc::MessageFromGsp,
+ Cmdq, //
},
fw,
GspBootContext,
--
2.56.0
next prev parent reply other threads:[~2026-10-09 11:55 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 11:53 [PATCH v4 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-10-09 11:53 ` [PATCH v4 01/10] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-10-09 11:53 ` [PATCH v4 02/10] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
2026-10-09 11:53 ` [PATCH v4 03/10] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
2026-10-09 11:54 ` [PATCH v4 04/10] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
2026-10-09 11:54 ` [PATCH v4 05/10] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path Alexandre Courbot
2026-10-09 11:54 ` [PATCH v4 06/10] gpu: nova-core: gsp: cmdq: move RPC message logging to RpcMessage Alexandre Courbot
2026-10-09 11:54 ` [PATCH v4 07/10] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
2026-10-09 11:54 ` Alexandre Courbot [this message]
2026-10-09 11:54 ` [PATCH v4 09/10] gpu: nova-core: gsp: move the RPC commands into a sub-module Alexandre Courbot
2026-10-09 11:54 ` [PATCH v4 10/10] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
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=20261009-cmdq-rpc-v4-8-c9ab8de1d3f2@nvidia.com \
--to=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®