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 v5 12/14] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module
Date: Tue, 20 Oct 2026 19:08:33 +0900 [thread overview]
Message-ID: <20261020-cmdq-rpc-v5-12-dceeb8f360ec@nvidia.com> (raw)
In-Reply-To: <20261020-cmdq-rpc-v5-0-dceeb8f360ec@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 | 387 +------------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 415 +++++++++++++++++++++
.../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, 427 insertions(+), 385 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 54e87fd7158c..c5a2f08cda94 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,8 +29,8 @@
},
Mutex, //
},
+ time::Delta,
time::{
- Delta,
Instant,
Monotonic, //
},
@@ -39,8 +40,6 @@
},
};
-use continuation::SplitState;
-
use pin_init::pin_init_scope;
use crate::{
@@ -48,10 +47,8 @@
gsp::{
fw::{
GspMsgElement,
- MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
- RpcMessageHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -81,149 +78,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 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>;
-}
-
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
@@ -559,96 +413,10 @@ 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
- );
- }
- }
- }
-}
-
/// A message whose protocol layer has been parsed and validated.
enum GspParsedMessage<'a> {
- /// A [RPC message](RpcMessage).
- Rpc(RpcMessage<'a>),
+ /// A [RPC message](rpc::RpcMessage).
+ Rpc(rpc::RpcMessage<'a>),
}
impl GspParsedMessage<'_> {
@@ -731,73 +499,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.
///
@@ -871,36 +572,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 a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
@@ -970,7 +641,7 @@ fn consume_element<R>(
})?;
// RPC is the only type of message supported for now.
- let message = GspParsedMessage::Rpc(RpcMessage::parse(self.dev, message)?);
+ let message = GspParsedMessage::Rpc(rpc::RpcMessage::parse(self.dev, message)?);
let res = f(message);
@@ -1010,52 +681,6 @@ fn await_msg_with<R>(&mut self, f: impl Fn(GspParsedMessage<'_>) -> Result<R>) -
}
}
- /// 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.
- ///
- /// 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 f = |message: GspParsedMessage<'_>| {
- let GspParsedMessage::Rpc(rpc_message) = message;
- let function = rpc_message.header.function();
- 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_err(|e| e.into())
- .inspect(|_| {
- if !sbuffer.is_empty() {
- dev_warn!(
- self.dev,
- "GSP message {:?} has unprocessed data\n",
- M::FUNCTION
- );
- }
- })
- } else {
- rpc_message.log(self.dev);
-
- Err(ENOMSG)
- }
- };
-
- self.await_msg_with(f)
- }
-
/// Logs and consumes every message the queue holds.
///
/// # Errors
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..13baf4398632
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -0,0 +1,415 @@
+// 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::*,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use continuation::SplitState;
+
+use crate::{
+ gsp::{
+ cmdq::{
+ GspCommand,
+ NoReply, //
+ },
+ fw::{
+ MsgFunction,
+ RpcMessageHeader, //
+ },
+ },
+ sbuffer::SBufferIter,
+};
+
+use super::{
+ Cmdq,
+ CmdqInner,
+ CommandElement,
+ GspMessage,
+ GspParsedMessage, //
+};
+
+/// 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>;
+}
+
+/// 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.
+ ///
+ /// 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 f = |message: GspParsedMessage<'_>| {
+ let GspParsedMessage::Rpc(rpc_message) = message;
+ let function = rpc_message.header.function();
+ 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_err(|e| e.into())
+ .inspect(|_| {
+ if !sbuffer.is_empty() {
+ dev_warn!(
+ self.dev,
+ "GSP message {:?} has unprocessed data\n",
+ M::FUNCTION
+ );
+ }
+ })
+ } else {
+ rpc_message.log(self.dev);
+
+ Err(ENOMSG)
+ }
+ };
+
+ self.await_msg_with(f)
+ }
+}
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-20 10:09 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 10:08 [PATCH v5 00/14] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 01/14] gpu: nova-core: gsp: cmdq: remove stale comment Alexandre Courbot
2026-10-20 11:43 ` Eliot Courtney
2026-10-20 10:08 ` [PATCH v5 02/14] gpu: nova-core: gsp: cmdq: simplify framing error description Alexandre Courbot
2026-10-20 11:43 ` Eliot Courtney
2026-10-20 10:08 ` [PATCH v5 03/14] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 04/14] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 05/14] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 06/14] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 07/14] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 08/14] gpu: nova-core: gsp: cmdq: move RPC message logging to RpcMessage Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 09/14] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 10/14] gpu: nova-core: gsp: cmdq: extract await_msg_with from await_msg Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 11/14] gpu: nova-core: gsp: cmdq: use consume_element to implement drain Alexandre Courbot
2026-10-20 10:18 ` sashiko-bot
2026-10-20 14:27 ` Alexandre Courbot
2026-10-20 10:08 ` Alexandre Courbot [this message]
2026-10-20 10:15 ` [PATCH v5 12/14] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module sashiko-bot
2026-10-20 10:08 ` [PATCH v5 13/14] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-10-20 10:08 ` [PATCH v5 14/14] 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=20261020-cmdq-rpc-v5-12-dceeb8f360ec@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 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.