* [PATCH 01/10] gpu: nova-core: gsp: cmdq: use element_count to advance queue pointer
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 02/10] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
The transport layer of the command queue works in whole page units (aka.
slots) - the size in bytes of the message is only relevant to the
message layer.
After queueing a message, we computed the number of slots to advance the
queue's write pointer by from the message size in bytes, borrowing
information from a layer we are not supposed to access while also
re-computing a number we already have since the number of slots of the
message is in the transport-level header.
Therefore, use the correct information from our layer. This is necessary
in order to extract the message layer code into its own module.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index a5595da23407..9285fd796f9d 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -863,9 +863,8 @@ fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
};
// Advance the read pointer past this message.
- self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
- message.header.length().div_ceil(GSP_PAGE_SIZE),
- )?);
+ self.gsp_mem
+ .advance_cpu_read_ptr(message.header.element_count());
result
}
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 02/10] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-27 11:14 ` [PATCH 01/10] gpu: nova-core: gsp: cmdq: use element_count to advance queue pointer Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 03/10] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
The checksum validation error message of `wait_for_msg` prints the
message's sequence number before validating the checksum.
But the checksum is part of the transport layer, and it not validating
indicates a corruption that could very well be in the RPC message
header, meaning the value of this field cannot be trusted.
Furthermore, the transport layer is not supposed to know the kind of
message it transports, and it accessing the RPC header is a layering
violation.
Thus, move the checksum validation before we start looking at the
message header, and drop that information from the error message.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 9285fd796f9d..08a49450bca0 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -768,6 +768,17 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
// Extract the `GspMsgElement`.
let (header, slice_1) = GspMsgElement::from_bytes_prefix(slice_1).ok_or(EIO)?;
+ // Validate checksum.
+ if Cmdq::calculate_checksum(SBufferIter::new_reader([
+ header.as_bytes(),
+ slice_1,
+ slice_2,
+ ])) != 0
+ {
+ dev_err!(&self.dev, "GSP receive: bad checksum\n");
+ return Err(EIO);
+ }
+
dev_dbg!(
&self.dev,
"GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
@@ -796,21 +807,6 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
)
};
- // Validate checksum.
- if Cmdq::calculate_checksum(SBufferIter::new_reader([
- header.as_bytes(),
- slice_1,
- slice_2,
- ])) != 0
- {
- dev_err!(
- &self.dev,
- "GSP RPC: receive: Call {} - bad checksum\n",
- header.sequence()
- );
- return Err(EIO);
- }
-
Ok(GspMessage {
header,
contents: (slice_1, slice_2),
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 03/10] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-27 11:14 ` [PATCH 01/10] gpu: nova-core: gsp: cmdq: use element_count to advance queue pointer Alexandre Courbot
2026-09-27 11:14 ` [PATCH 02/10] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 04/10] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
So far, the GSP command queue transport and message layer code were
intertwined, a design issue that goes as deep as the types themselves:
the generated bindings for `GspMsgElement` even include the RPC header
at its end.
This makes it difficult to introduce the new GMC message type; thus this
patch works around these limitations to make the RPC message header more
explicit and allow it to be eventually handled by a different layer.
The `RpcMessageHeader` wrapping type is introduced following the same
model as `GspMsgElement`, and can be obtained from the latter. The
methods of `GspMsgElement` that actually query the RPC header are moved
to `RpcMessageHeader`.
Regarding initialization, `GspMsgElement` leaves the RPC header zeroed,
and the command queue code is now responsible for initializing it in a
separate call.
The only functional change is that the RPC debug messages now display
the size of the RPC payload instead of the whole message including its
headers, as they are technically part of the message layer. This metric
is arguably more useful as the headers have successfully been parsed by
the time we can print these messages.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 25 +++++++----
drivers/gpu/nova-core/gsp/fw.rs | 90 ++++++++++++++++++++++++---------------
2 files changed, 71 insertions(+), 44 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 08a49450bca0..8e7e6609ebf1 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -50,6 +50,7 @@
MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
+ RpcMessageHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -664,11 +665,16 @@ fn send_single_command<M>(&mut self, command: M) -> Result
let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
// Fill the header and command in-place.
- let msg_element = GspMsgElement::init(self.seq, size_in_bytes, M::FUNCTION);
+ let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
+ let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
// SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
// fails.
unsafe {
- pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element)?;
+ pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
}
@@ -694,7 +700,7 @@ fn send_single_command<M>(&mut self, command: M) -> Result
"GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
self.seq,
M::FUNCTION,
- dst.header.length(),
+ size_in_bytes,
);
// All set - update the write pointer and inform the GSP of the new command.
@@ -779,16 +785,17 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
return Err(EIO);
}
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.length();
+
dev_dbg!(
&self.dev,
"GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- header.sequence(),
- header.function(),
- header.length(),
+ rpc_header.sequence(),
+ rpc_header.function(),
+ payload_length,
);
- let payload_length = header.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);
@@ -835,7 +842,7 @@ fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
Error: From<M::InitError>,
{
let message = self.wait_for_msg(timeout)?;
- let function = message.header.function().map_err(|_| EINVAL)?;
+ let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
// Extract the message. Store the result as we want to advance the read pointer even in
// case of failure.
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 918a7ae809eb..1cfa7838ac11 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -781,11 +781,25 @@ fn new() -> Self {
}
}
-impl bindings::rpc_message_header_v {
- fn init(cmd_size: usize, function: MsgFunction) -> impl Init<Self, Error> {
- type RpcMessageHeader = bindings::rpc_message_header_v;
+#[repr(transparent)]
+pub(crate) struct RpcMessageHeader {
+ inner: bindings::rpc_message_header_v,
+}
- try_init!(RpcMessageHeader {
+// SAFETY: Padding is explicit and does not contain uninitialized data.
+unsafe impl AsBytes for RpcMessageHeader {}
+
+// SAFETY: This struct only contains integer types for which all bit patterns
+// are valid.
+unsafe impl FromBytes for RpcMessageHeader {}
+
+impl RpcMessageHeader {
+ /// Creates a new RPC header.
+ ///
+ /// `cmd_size` is the size in bytes of the payload. `function` is the RPC function of the
+ /// message.
+ pub(crate) fn init(cmd_size: usize, function: MsgFunction) -> impl Init<Self, Error> {
+ let init_inner = try_init!(bindings::rpc_message_header_v {
header_version: MsgHeaderVersion::new().into(),
signature: bindings::NV_VGPU_MSG_SIGNATURE_VALID,
function: function.into(),
@@ -796,8 +810,32 @@ fn init(cmd_size: usize, function: MsgFunction) -> impl Init<Self, Error> {
rpc_result: 0xffffffff,
rpc_result_private: 0xffffffff,
..Zeroable::init_zeroed()
+ });
+
+ try_init!(RpcMessageHeader {
+ inner <- init_inner,
})
}
+
+ /// Returns the length of the RPC's payload, not including the header.
+ pub(crate) fn length(&self) -> usize {
+ // `length` includes the length of the RPC message header.
+ num::u32_as_usize(self.inner.length).saturating_sub(size_of::<Self>())
+ }
+
+ /// Returns the sequence number of the message.
+ pub(crate) fn sequence(&self) -> u32 {
+ self.inner.sequence
+ }
+
+ /// Returns the function of the message, if it is valid, or the invalid function number as an
+ /// error.
+ pub(crate) fn function(&self) -> Result<MsgFunction, u32> {
+ self.inner
+ .function
+ .try_into()
+ .map_err(|_| self.inner.function)
+ }
}
/// GSP Message Element.
@@ -811,17 +849,15 @@ pub(crate) struct GspMsgElement {
impl GspMsgElement {
/// Creates a new message element.
///
+ /// The RPC header is left initialized to zero and must be initialized separately using e.g.
+ /// [`Self::rpc_header_mut`].
+ ///
/// # Arguments
///
/// * `sequence` - Sequence number of the message.
/// * `cmd_size` - Size of the command (not including the message element), in bytes.
/// * `function` - Function of the message.
- pub(crate) fn init(
- sequence: u32,
- cmd_size: usize,
- function: MsgFunction,
- ) -> impl Init<Self, Error> {
- type RpcMessageHeader = bindings::rpc_message_header_v;
+ pub(crate) fn init(sequence: u32, cmd_size: usize) -> impl Init<Self, Error> {
type InnerGspMsgElement = bindings::GSP_MSG_QUEUE_ELEMENT;
let init_inner = try_init!(InnerGspMsgElement {
seqNum: sequence,
@@ -831,7 +867,6 @@ pub(crate) fn init(
.div_ceil(GSP_PAGE_SIZE)
.try_into()
.map_err(|_| EOVERFLOW)?,
- rpc <- RpcMessageHeader::init(cmd_size, function),
..Zeroable::init_zeroed()
});
@@ -848,34 +883,19 @@ pub(crate) fn set_checksum(&mut self, checksum: u32) {
self.inner.checkSum = checksum;
}
- /// Returns the length of the message's payload.
- pub(crate) fn payload_length(&self) -> usize {
- // `rpc.length` includes the length of the RPC message header.
- num::u32_as_usize(self.inner.rpc.length)
- .saturating_sub(size_of::<bindings::rpc_message_header_v>())
+ /// Returns a reference to the RPC header within the message element.
+ pub(crate) fn rpc_header(&self) -> &RpcMessageHeader {
+ // SAFETY: transparent type.
+ unsafe { core::mem::transmute(&self.inner.rpc) }
}
- /// Returns the total length of the message, message and RPC headers included.
- pub(crate) fn length(&self) -> usize {
- size_of::<Self>() + self.payload_length()
+ /// Returns a mutable reference to the RPC header within the message element.
+ pub(crate) fn rpc_header_mut(&mut self) -> &mut RpcMessageHeader {
+ // SAFETY: `RpcMessageHeader` is a transparent wrapper for the type of `inner.rpc`.
+ unsafe { core::mem::transmute(&mut self.inner.rpc) }
}
- // Returns the sequence number of the message.
- pub(crate) fn sequence(&self) -> u32 {
- self.inner.rpc.sequence
- }
-
- // Returns the function of the message, if it is valid, or the invalid function number as an
- // error.
- pub(crate) fn function(&self) -> Result<MsgFunction, u32> {
- self.inner
- .rpc
- .function
- .try_into()
- .map_err(|_| self.inner.rpc.function)
- }
-
- // Returns the number of elements (i.e. memory pages) used by this message.
+ /// Returns the number of elements (i.e. memory pages) used by this message.
pub(crate) fn element_count(&self) -> u32 {
self.inner.elemCount
}
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 04/10] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (2 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 03/10] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 05/10] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
`send_single_command` handles both the transport and message layers of
the command, intertwining the logic of the two.
Reorder the code so the message layer logic is within the same
contiguous block of code, so it can easily be moved.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 8e7e6609ebf1..f0e4f8f5228d 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -659,18 +659,21 @@ fn send_single_command<M>(&mut self, command: M) -> Result
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
+ // Fill the header.
+ let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
+ // SAFETY: `msg_header` is a valid reference, and not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
+ }
+
// 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)?;
-
- // Fill the header and command in-place.
- let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
- // SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
- // fails.
+ // SAFETY: `rpc_header_mut()` and `cmd` are valid references, and not touched if the
+ // initializer fails.
unsafe {
- pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
pin_init::raw_try_init(
core::ptr::from_mut(dst.header.rpc_header_mut()),
rpc_header_init,
@@ -687,14 +690,6 @@ fn send_single_command<M>(&mut self, command: M) -> Result
}
drop(sbuffer);
- // Compute checksum now that the whole message is ready.
- dst.header
- .set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
- dst.header.as_bytes(),
- dst.contents.0,
- dst.contents.1,
- ])));
-
dev_dbg!(
&self.dev,
"GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
@@ -703,6 +698,14 @@ fn send_single_command<M>(&mut self, command: M) -> Result
size_in_bytes,
);
+ // Compute checksum now that the whole message is ready.
+ dst.header
+ .set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
+ dst.header.as_bytes(),
+ dst.contents.0,
+ dst.contents.1,
+ ])));
+
// All set - update the write pointer and inform the GSP of the new command.
let elem_count = dst.header.element_count();
self.seq += 1;
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 05/10] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (3 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 04/10] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 06/10] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module Alexandre Courbot
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
Move the transport part of `send_single_command` into
`send_command_element`, which allocates the queue slots, writes the
element header, calls a closure to fill the remainder of the command,
then computes the checksum, advances the write pointer and rings the
doorbell.
The RPC part of `send_single_command` (writing the RPC header and the
command payload) is passed as a closure, unchanged apart from its
indentation. This sets things up for moving the RPC code into its own
sub-module, leaving the transport agnostic of the message type.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 118 ++++++++++++++++++++++++--------------
1 file changed, 74 insertions(+), 44 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index f0e4f8f5228d..602824236f64 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -638,65 +638,35 @@ impl CmdqInner<'_> {
/// Timeout for waiting for space on the command queue.
const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1);
- /// Sends `command` to the GSP, without splitting it.
+ /// Allocates enough send slots to store a command of `sizes_in_bytes` length, initialize them
+ /// using `command_init`, and send the command to the GSP.
///
/// # Errors
///
/// - `EMSGSIZE` if the command exceeds the maximum queue element size.
/// - `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_single_command<M>(&mut self, command: M) -> Result
- where
- M: CommandToGsp,
- // This allows all error types, including `Infallible`, to be used for `M::InitError`.
- Error: From<M::InitError>,
- {
- let size_in_bytes = command.size();
- let dst = self
+ /// Error codes returned by `command_init` are returned as-is.
+ fn send_command_element(
+ &mut self,
+ size_in_bytes: usize,
+ command_init: impl FnOnce(&mut GspCommand<'_>) -> Result,
+ ) -> Result {
+ let mut dst = self
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
+ let seq = self.seq;
+
// Fill the header.
- let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
+ let msg_element_init = GspMsgElement::init(seq, size_in_bytes);
// SAFETY: `msg_header` is a valid reference, and not touched if the initializer fails.
unsafe {
pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
}
- // 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: `rpc_header_mut()` and `cmd` are valid references, and not touched if the
- // initializer fails.
- unsafe {
- pin_init::raw_try_init(
- core::ptr::from_mut(dst.header.rpc_header_mut()),
- rpc_header_init,
- )?;
- 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);
- }
- drop(sbuffer);
-
- dev_dbg!(
- &self.dev,
- "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
- self.seq,
- M::FUNCTION,
- size_in_bytes,
- );
+ // Initialize the message payload.
+ command_init(&mut dst)?;
// Compute checksum now that the whole message is ready.
dst.header
@@ -715,6 +685,66 @@ fn send_single_command<M>(&mut self, command: M) -> Result
Ok(())
}
+ /// Sends `command` to the GSP, without splitting it.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
+ /// - `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_single_command<M>(&mut self, command: M) -> Result
+ where
+ M: CommandToGsp,
+ // This allows all error types, including `Infallible`, to be used for `M::InitError`.
+ Error: From<M::InitError>,
+ {
+ let dev = self.dev;
+ let seq = self.seq;
+ let size_in_bytes = command.size();
+
+ let init = |dst: &mut GspCommand<'_>| {
+ // 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: `rpc_header_mut()` and `cmd` are valid references, and not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
+ 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(())
+ };
+
+ self.send_command_element(size_in_bytes, init)
+ }
+
/// Sends `command` to the GSP.
///
/// The command may be split into multiple messages if it is large.
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 06/10] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (4 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 05/10] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 07/10] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
Move the types and code related to sending a RPC command 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>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 214 +-----------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 248 +++++++++++++++++++++
.../nova-core/gsp/cmdq/{ => rpc}/continuation.rs | 0
drivers/gpu/nova-core/gsp/commands.rs | 2 +-
4 files changed, 251 insertions(+), 213 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 602824236f64..2d22ae45e990 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(crate) mod rpc;
use core::mem;
@@ -35,11 +36,6 @@
},
};
-use continuation::{
- ContinuationRecord,
- SplitState, //
-};
-
use pin_init::pin_init_scope;
use crate::{
@@ -50,7 +46,6 @@
MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
- RpcMessageHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -67,68 +62,6 @@
/// reply type are sent using [`Cmdq::send_command_no_wait`].
pub(crate) struct NoReply;
-/// 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()
- }
-}
-
/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
@@ -558,58 +491,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.
- ///
- /// Messages with non-matching function codes are silently consumed until the expected reply
- /// arrives.
- ///
- /// 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 is
- /// not received 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 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)?;
-
- loop {
- match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
- Ok(reply) => break Ok(reply),
- Err(ERANGE) => continue,
- Err(e) => break Err(e),
- }
- }
- }
-
- /// 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)
- }
-
/// Receive a message from the GSP.
///
/// See [`CmdqInner::receive_msg`] for details.
@@ -685,97 +566,6 @@ fn send_command_element(
Ok(())
}
- /// Sends `command` to the GSP, without splitting it.
- ///
- /// # Errors
- ///
- /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
- /// - `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_single_command<M>(&mut self, command: M) -> Result
- where
- M: CommandToGsp,
- // This allows all error types, including `Infallible`, to be used for `M::InitError`.
- Error: From<M::InitError>,
- {
- let dev = self.dev;
- let seq = self.seq;
- let size_in_bytes = command.size();
-
- let init = |dst: &mut GspCommand<'_>| {
- // 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: `rpc_header_mut()` and `cmd` are valid references, and not touched if the
- // initializer fails.
- unsafe {
- pin_init::raw_try_init(
- core::ptr::from_mut(dst.header.rpc_header_mut()),
- rpc_header_init,
- )?;
- 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(())
- };
-
- self.send_command_element(size_in_bytes, init)
- }
-
- /// 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_single_command(command),
- SplitState::Split(command, mut continuations) => {
- self.send_single_command(command)?;
-
- while let Some(continuation) = continuations.next() {
- // Turbofish needed because the compiler cannot infer M here.
- self.send_single_command::<ContinuationRecord<'_>>(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
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..3bafe456efd6
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -0,0 +1,248 @@
+// 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::{
+ prelude::*,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use continuation::{
+ ContinuationRecord,
+ SplitState, //
+};
+
+use crate::{
+ gsp::{
+ cmdq::{
+ GspCommand,
+ NoReply, //
+ },
+ fw::{
+ MsgFunction,
+ RpcMessageHeader, //
+ },
+ },
+ sbuffer::SBufferIter,
+};
+
+use super::{
+ Cmdq,
+ CmdqInner,
+ MessageFromGsp, //
+};
+
+/// 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()
+ }
+}
+
+impl CmdqInner<'_> {
+ /// Sends `command` to the GSP, without splitting it.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
+ /// - `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_single_command<M>(&mut self, command: M) -> Result
+ where
+ M: CommandToGsp,
+ // This allows all error types, including `Infallible`, to be used for `M::InitError`.
+ Error: From<M::InitError>,
+ {
+ let dev = self.dev;
+ let seq = self.seq;
+ let size_in_bytes = command.size();
+
+ let init = |dst: &mut GspCommand<'_>| {
+ // 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: `rpc_header_mut()` and `cmd` are valid references, and not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
+ 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(())
+ };
+
+ self.send_command_element(size_in_bytes, init)
+ }
+
+ /// 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_single_command(command),
+ SplitState::Split(command, mut continuations) => {
+ self.send_single_command(command)?;
+
+ while let Some(continuation) = continuations.next() {
+ // Turbofish needed because the compiler cannot infer M here.
+ self.send_single_command::<ContinuationRecord<'_>>(continuation)?;
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+impl Cmdq<'_> {
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// Messages with non-matching function codes are silently consumed until the expected reply
+ /// arrives.
+ ///
+ /// 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 is
+ /// not received 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 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)?;
+
+ loop {
+ match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
+
+ /// 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)
+ }
+}
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 e087c9e8c35c..4322a4d554f9 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -23,8 +23,8 @@
gpu::Chipset,
gsp::{
cmdq::{
+ rpc::CommandToGsp,
Cmdq,
- CommandToGsp,
MessageFromGsp,
NoReply, //
},
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 07/10] gpu: nova-core: gsp: cmdq: split the transport part of the receive path
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (5 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 06/10] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 08/10] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module Alexandre Courbot
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
`wait_for_msg` mixes two layers: the transport layer which polls the
queue, extracts the element header and validates the checksum, and the
RPC layer which reads the RPC header and trims the payload slices to the
length advertised by the RPC header.
Move the transport layer into `wait_for_element`, and make
`wait_for_msg` call it before validating the RPC layer. This sets things
up for moving the RPC code into its own module, leaving the transport
agnostic of the message type.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 2d22ae45e990..d50a2a594f82 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -584,7 +584,7 @@ fn send_command_element(
/// message queue.
///
/// Error codes returned by the message constructor are propagated as-is.
- fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
+ fn wait_for_element(&self, timeout: Delta) -> Result<GspMessage<'_>> {
// Wait for a message to arrive from the GSP.
let (slice_1, slice_2) = read_poll_timeout(
|| Ok(self.gsp_mem.driver_read_area()),
@@ -608,6 +608,18 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
return Err(EIO);
}
+ Ok(GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = self.wait_for_element(timeout)?;
+
let rpc_header = header.rpc_header();
let payload_length = rpc_header.length();
@@ -619,6 +631,8 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
payload_length,
);
+ // Validate the RPC layer before returning the message.
+
// Check that the driver read area is large enough for the message.
if slice_1.len() + slice_2.len() < payload_length {
return Err(EIO);
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 08/10] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (6 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 07/10] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 09/10] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
Move the types and code related to receiving a RPC message 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>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 130 --------------------------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 132 ++++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/commands.rs | 6 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 4 +-
4 files changed, 137 insertions(+), 135 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d50a2a594f82..ea395c70324a 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -43,7 +43,6 @@
gsp::{
fw::{
GspMsgElement,
- MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
@@ -62,29 +61,6 @@
/// reply type are sent using [`Cmdq::send_command_no_wait`].
pub(crate) struct NoReply;
-/// Trait representing messages received from the GSP.
-///
-/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
-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;
@@ -490,17 +466,6 @@ fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
fn notify_gsp(bar: Bar0<'_>) {
bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
}
-
- /// Receive a message from the GSP.
- ///
- /// See [`CmdqInner::receive_msg`] for details.
- pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
- where
- // This allows all error types, including `Infallible`, to be used for `M::InitError`.
- Error: From<M::InitError>,
- {
- self.inner.lock().receive_msg(timeout)
- }
}
/// Inner mutex protected state of [`Cmdq`].
@@ -613,99 +578,4 @@ fn wait_for_element(&self, timeout: Delta) -> Result<GspMessage<'_>> {
contents: (slice_1, slice_2),
})
}
-
- fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
- let GspMessage {
- header,
- contents: (slice_1, slice_2),
- } = self.wait_for_element(timeout)?;
-
- let rpc_header = header.rpc_header();
- let payload_length = rpc_header.length();
-
- dev_dbg!(
- &self.dev,
- "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- rpc_header.sequence(),
- rpc_header.function(),
- payload_length,
- );
-
- // Validate the RPC layer before returning the message.
-
- // 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(GspMessage {
- header,
- contents: (slice_1, slice_2),
- })
- }
-
- /// Receive a message from the GSP.
- ///
- /// The expected message type is specified using the `M` generic parameter. If the pending
- /// message has a different function code, `ERANGE` is returned and the message is consumed.
- ///
- /// The read pointer is always advanced past the message, regardless of whether it matched.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
- /// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
- /// message queue.
- /// - `EINVAL` if the function code of the message was not recognized.
- /// - `ERANGE` if the message had a recognized but non-matching function code.
- ///
- /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
- where
- // This allows all error types, including `Infallible`, to be used for `M::InitError`.
- Error: From<M::InitError>,
- {
- let message = self.wait_for_msg(timeout)?;
- let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
-
- // Extract the message. Store the result as we want to advance the read pointer even in
- // case of failure.
- let result = if function == M::FUNCTION {
- let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
- let mut sbuffer = SBufferIter::new_reader([contents_1, 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",
- function
- );
- }
- })
- } else {
- Err(ERANGE)
- };
-
- // Advance the read pointer past this message.
- self.gsp_mem
- .advance_cpu_read_ptr(message.header.element_count());
-
- result
- }
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index 3bafe456efd6..335f94c4d2aa 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -7,6 +7,7 @@
use kernel::{
prelude::*,
+ time::Delta,
transmute::{
AsBytes,
FromBytes, //
@@ -35,7 +36,7 @@
use super::{
Cmdq,
CmdqInner,
- MessageFromGsp, //
+ GspMessage, //
};
/// Trait implemented by types representing a command to send to the GSP.
@@ -100,6 +101,29 @@ fn size(&self) -> usize {
}
}
+/// Trait representing messages received from the GSP.
+///
+/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
+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>;
+}
+
impl CmdqInner<'_> {
/// Sends `command` to the GSP, without splitting it.
///
@@ -191,6 +215,101 @@ fn send_command<M>(&mut self, command: M) -> Result
}
}
}
+
+ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = self.wait_for_element(timeout)?;
+
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.length();
+
+ dev_dbg!(
+ &self.dev,
+ "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
+ rpc_header.sequence(),
+ rpc_header.function(),
+ payload_length,
+ );
+
+ // Validate the RPC layer before returning the message.
+
+ // 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(GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ /// Receive a message from the GSP.
+ ///
+ /// The expected message type is specified using the `M` generic parameter. If the pending
+ /// message has a different function code, `ERANGE` is returned and the message is consumed.
+ ///
+ /// The read pointer is always advanced past the message, regardless of whether it matched.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
+ /// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
+ /// message queue.
+ /// - `EINVAL` if the function code of the message was not recognized.
+ /// - `ERANGE` if the message had a recognized but non-matching function code.
+ ///
+ /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
+ fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
+ where
+ // This allows all error types, including `Infallible`, to be used for `M::InitError`.
+ Error: From<M::InitError>,
+ {
+ let message = self.wait_for_msg(timeout)?;
+ let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
+
+ // Extract the message. Store the result as we want to advance the read pointer even in
+ // case of failure.
+ let result = if function == M::FUNCTION {
+ let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
+ let mut sbuffer = SBufferIter::new_reader([contents_1, 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",
+ function
+ );
+ }
+ })
+ } else {
+ Err(ERANGE)
+ };
+
+ // Advance the read pointer past this message.
+ self.gsp_mem
+ .advance_cpu_read_ptr(message.header.element_count());
+
+ result
+ }
}
impl Cmdq<'_> {
@@ -245,4 +364,15 @@ pub(crate) fn send_command_no_wait<M>(&self, command: M) -> Result
{
self.inner.lock().send_command(command)
}
+
+ /// Receive a message from the GSP.
+ ///
+ /// See [`CmdqInner::receive_msg`] for details.
+ pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ where
+ // This allows all error types, including `Infallible`, to be used for `M::InitError`.
+ Error: From<M::InitError>,
+ {
+ self.inner.lock().receive_msg(timeout)
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 4322a4d554f9..81bd457a6eb6 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,
+ rpc::{
+ CommandToGsp,
+ MessageFromGsp, //
+ },
Cmdq,
- MessageFromGsp,
NoReply, //
},
fw::{
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index dae34c11eb05..ebf13867746f 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.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 09/10] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (7 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 08/10] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 11:14 ` [PATCH 10/10] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
2026-09-27 13:35 ` [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
Move the types and code related to RPC commands into the
`rpc` sub-module, and update their users to reference them from their
new location.
This is a pure move commit, with no functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gpu.rs | 2 +-
drivers/gpu/nova-core/gsp.rs | 4 +-
drivers/gpu/nova-core/gsp/boot.rs | 12 +-
drivers/gpu/nova-core/gsp/commands.rs | 314 +----------------------------
drivers/gpu/nova-core/gsp/commands/rpc.rs | 316 ++++++++++++++++++++++++++++++
5 files changed, 326 insertions(+), 322 deletions(-)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 8f30186f10ba..b6c691361895 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -34,7 +34,7 @@
fsp::Fsp,
gsp::{
self,
- commands::GetGspStaticInfoReply,
+ commands::rpc::GetGspStaticInfoReply,
Gsp,
GspBootContext, //
},
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index dda58095f40b..75a3ba7d50f4 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -220,8 +220,8 @@ pub(crate) fn new(
}
/// Query the GSP for the static GPU information.
- pub(crate) fn get_static_info(&self) -> Result<commands::GetGspStaticInfoReply> {
- self.cmdq.send_command(commands::GetGspStaticInfo)
+ pub(crate) fn get_static_info(&self) -> Result<commands::rpc::GetGspStaticInfoReply> {
+ self.cmdq.send_command(commands::rpc::GetGspStaticInfo)
}
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 4fb1b69ac9d5..8e446ad1eac2 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -43,9 +43,9 @@ pub(crate) fn boot(
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset), GFP_KERNEL)?;
self.cmdq
- .send_command_no_wait(commands::SetSystemInfo::new(pdev, chipset))?;
+ .send_command_no_wait(commands::rpc::SetSystemInfo::new(pdev, chipset))?;
self.cmdq
- .send_command_no_wait(commands::SetRegistry::new(ctx.vgpu.state())?)?;
+ .send_command_no_wait(commands::rpc::SetRegistry::new(ctx.vgpu.state())?)?;
// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_bundle = hal.boot(&self, &mut ctx, &gsp_fw)?.or_else(|| {
@@ -79,7 +79,7 @@ pub(crate) fn boot(
hal.post_boot(&self, ctx, &gsp_fw)?;
// Wait until GSP is fully initialized.
- commands::wait_gsp_init_done(&self.cmdq)?;
+ commands::rpc::wait_gsp_init_done(&self.cmdq)?;
Ok(unload_guard.dismiss().1)
}
@@ -88,10 +88,10 @@ pub(crate) fn boot(
fn shutdown_gsp(
cmdq: &Cmdq<'_>,
gsp_falcon: &Falcon<'_, Gsp>,
- mode: commands::PowerStateLevel,
+ mode: commands::rpc::PowerStateLevel,
) -> Result {
// Command to shut the GSP down.
- cmdq.send_command(commands::UnloadingGuestDriver::new(mode))?;
+ cmdq.send_command(commands::rpc::UnloadingGuestDriver::new(mode))?;
// Wait until GSP signals it is suspended.
const LIBOS_INTERRUPT_PROCESSOR_SUSPENDED: u32 = bits::bit_u32(31);
@@ -118,7 +118,7 @@ pub(crate) fn unload(
let mut res = Self::shutdown_gsp(
&self.cmdq,
ctx.gsp_falcon,
- commands::PowerStateLevel::Level0,
+ commands::rpc::PowerStateLevel::Level0,
)
.inspect_err(|e| dev_err!(dev, "GSP shutdown failed: {:?}\n", e));
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 81bd457a6eb6..5f1c944678be 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -1,316 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
-use core::{
- array,
- convert::Infallible,
- ffi::FromBytesUntilNulError,
- ops::Range,
- str::Utf8Error, //
-};
-
-use kernel::{
- device,
- pci,
- prelude::*,
- transmute::{
- AsBytes,
- FromBytes, //
- }, //
-};
-
-use crate::{
- gpu::Chipset,
- gsp::{
- cmdq::{
- rpc::{
- CommandToGsp,
- MessageFromGsp, //
- },
- Cmdq,
- NoReply, //
- },
- fw::{
- self,
- MsgFunction, //
- },
- },
- sbuffer::SBufferIter,
- vgpu::VgpuState, //
-};
-
-/// The `GspSetSystemInfo` command.
-pub(crate) struct SetSystemInfo<'a> {
- pdev: &'a pci::Device<device::Bound>,
- chipset: Chipset,
-}
-
-impl<'a> SetSystemInfo<'a> {
- /// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`.
- pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Self {
- Self { pdev, chipset }
- }
-}
-
-impl<'a> CommandToGsp for SetSystemInfo<'a> {
- const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
- type Command = fw::commands::GspSetSystemInfo;
- type Reply = NoReply;
- type InitError = Error;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- Self::Command::init(self.pdev, self.chipset)
- }
-}
-
-struct RegistryEntry {
- key: &'static str,
- value: u32,
-}
-
-/// The `SetRegistry` command.
-pub(crate) struct SetRegistry {
- entries: KVec<RegistryEntry>,
-}
-
-impl SetRegistry {
- /// Creates a new `SetRegistry` command, using a set of hardcoded entries.
- pub(crate) fn new(vgpu_state: VgpuState) -> Result<Self> {
- let mut entries = KVec::new();
-
- // RMSecBusResetEnable - enables PCI secondary bus reset
- entries.push(
- RegistryEntry {
- key: "RMSecBusResetEnable",
- value: 1,
- },
- GFP_KERNEL,
- )?;
-
- // RMForcePcieConfigSave - forces GSP-RM to preserve PCI configuration registers on
- // any PCI reset.
- entries.push(
- RegistryEntry {
- key: "RMForcePcieConfigSave",
- value: 1,
- },
- GFP_KERNEL,
- )?;
-
- // RMDevidCheckIgnore - allows GSP-RM to boot even if the PCI dev ID is not found
- // in the internal product name database.
- entries.push(
- RegistryEntry {
- key: "RMDevidCheckIgnore",
- value: 1,
- },
- GFP_KERNEL,
- )?;
-
- if matches!(vgpu_state, VgpuState::Enabled { .. }) {
- // RMSetSriovMode - required when vGPU is enabled.
- entries.push(
- RegistryEntry {
- key: "RMSetSriovMode",
- value: 1,
- },
- GFP_KERNEL,
- )?;
- }
-
- Ok(Self { entries })
- }
-}
-
-impl CommandToGsp for SetRegistry {
- const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
- type Command = fw::commands::PackedRegistryTable;
- type Reply = NoReply;
- type InitError = Infallible;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- Self::Command::init(self.entries.len() as u32, self.size() as u32)
- }
-
- fn variable_payload_len(&self) -> usize {
- let mut key_size = 0;
- for entry in self.entries.iter() {
- key_size += entry.key.len() + 1; // +1 for NULL terminator
- }
- self.entries.len() * size_of::<fw::commands::PackedRegistryEntry>() + key_size
- }
-
- fn init_variable_payload(
- &self,
- dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
- ) -> Result {
- let string_data_start_offset = size_of::<Self::Command>()
- + self.entries.len() * size_of::<fw::commands::PackedRegistryEntry>();
-
- // Array for string data.
- let mut string_data = KVec::new();
-
- for entry in self.entries.iter() {
- dst.write_all(
- fw::commands::PackedRegistryEntry::new(
- (string_data_start_offset + string_data.len()) as u32,
- entry.value,
- )
- .as_bytes(),
- )?;
-
- let key_bytes = entry.key.as_bytes();
- string_data.extend_from_slice(key_bytes, GFP_KERNEL)?;
- string_data.push(0, GFP_KERNEL)?;
- }
-
- dst.write_all(string_data.as_slice())
- }
-}
-
-/// Message type for GSP initialization done notification.
-struct GspInitDone;
-
-// SAFETY: `GspInitDone` is a zero-sized type with no bytes, therefore it
-// trivially has no uninitialized bytes.
-unsafe impl FromBytes for GspInitDone {}
-
-impl MessageFromGsp for GspInitDone {
- const FUNCTION: MsgFunction = MsgFunction::GspInitDone;
- type InitError = Infallible;
- type Message = ();
-
- fn read(
- _msg: &Self::Message,
- _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- Ok(GspInitDone)
- }
-}
-
-/// Waits for GSP initialization to complete.
-pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq<'_>) -> Result {
- loop {
- match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
- Ok(_) => break Ok(()),
- Err(ERANGE) => continue,
- Err(e) => break Err(e),
- }
- }
-}
-
-/// The `GetGspStaticInfo` command.
-pub(crate) struct GetGspStaticInfo;
-
-impl CommandToGsp for GetGspStaticInfo {
- const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Command = fw::commands::GspStaticConfigInfo;
- type Reply = GetGspStaticInfoReply;
- type InitError = Infallible;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- Self::Command::init_zeroed()
- }
-}
-
-/// The reply from the GSP to the [`GetGspStaticInfo`] command.
-pub(crate) struct GetGspStaticInfoReply {
- gpu_name: [u8; 64],
- /// BAR1 Page Directory Entry base address.
- pub(crate) bar1_pde_base: u64,
- /// Usable FB (VRAM) regions for driver memory allocation.
- pub(crate) usable_fb_regions: KVec<Range<u64>>,
- /// Exclusive end of the FB physical address space.
- pub(crate) total_fb_end: u64,
-}
-
-impl MessageFromGsp for GetGspStaticInfoReply {
- const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Message = fw::commands::GspStaticConfigInfo;
- type InitError = Error;
-
- fn read(
- msg: &Self::Message,
- _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- let mut usable_fb_regions = KVec::new();
- for region in msg.usable_fb_regions() {
- usable_fb_regions.push(region, GFP_KERNEL)?;
- }
- let total_fb_end = msg.total_fb_end().ok_or(EINVAL)?;
-
- Ok(GetGspStaticInfoReply {
- gpu_name: msg.gpu_name_str(),
- bar1_pde_base: msg.bar1_pde_base(),
- usable_fb_regions,
- total_fb_end,
- })
- }
-}
-
-/// Error type for [`GetGspStaticInfoReply::gpu_name`].
-#[derive(Debug)]
-pub(crate) enum GpuNameError {
- /// The GPU name string does not contain a null terminator.
- NoNullTerminator(FromBytesUntilNulError),
-
- /// The GPU name string contains invalid UTF-8.
- #[expect(dead_code)]
- InvalidUtf8(Utf8Error),
-}
-
-impl GetGspStaticInfoReply {
- /// Returns the name of the GPU as a string.
- ///
- /// Returns an error if the string given by the GSP does not contain a null terminator or
- /// contains invalid UTF-8.
- pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
- CStr::from_bytes_until_nul(&self.gpu_name)
- .map_err(GpuNameError::NoNullTerminator)?
- .to_str()
- .map_err(GpuNameError::InvalidUtf8)
- }
-}
-
-pub(crate) use fw::commands::PowerStateLevel;
-
-/// The `UnloadingGuestDriver` command, used to shut down the GSP.
-///
-/// Only used within the `gsp` module.
-pub(super) struct UnloadingGuestDriver {
- level: PowerStateLevel,
-}
-
-impl UnloadingGuestDriver {
- /// Creates a new `UnloadingGuestDriver` command for the given [`PowerStateLevel`].
- pub(super) fn new(level: PowerStateLevel) -> Self {
- Self { level }
- }
-}
-
-impl CommandToGsp for UnloadingGuestDriver {
- const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
- type Command = fw::commands::UnloadingGuestDriver;
- type Reply = UnloadingGuestDriverReply;
- type InitError = Infallible;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- fw::commands::UnloadingGuestDriver::new(self.level)
- }
-}
-
-/// The reply from the GSP to the [`UnloadingGuestDriver`] command.
-pub(super) struct UnloadingGuestDriverReply;
-
-impl MessageFromGsp for UnloadingGuestDriverReply {
- const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
- type InitError = Infallible;
- type Message = ();
-
- fn read(
- _msg: &Self::Message,
- _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- Ok(UnloadingGuestDriverReply)
- }
-}
+pub(crate) mod rpc;
diff --git a/drivers/gpu/nova-core/gsp/commands/rpc.rs b/drivers/gpu/nova-core/gsp/commands/rpc.rs
new file mode 100644
index 000000000000..a3262cc3e0e9
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/commands/rpc.rs
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+use core::{
+ array,
+ convert::Infallible,
+ ffi::FromBytesUntilNulError,
+ ops::Range,
+ str::Utf8Error, //
+};
+
+use kernel::{
+ device,
+ pci,
+ prelude::*,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ }, //
+};
+
+use crate::{
+ gpu::Chipset,
+ gsp::{
+ cmdq::{
+ rpc::{
+ CommandToGsp,
+ MessageFromGsp, //
+ },
+ Cmdq,
+ NoReply, //
+ },
+ fw::{
+ self,
+ MsgFunction, //
+ },
+ },
+ sbuffer::SBufferIter,
+ vgpu::VgpuState, //
+};
+
+/// The `GspSetSystemInfo` command.
+pub(crate) struct SetSystemInfo<'a> {
+ pdev: &'a pci::Device<device::Bound>,
+ chipset: Chipset,
+}
+
+impl<'a> SetSystemInfo<'a> {
+ /// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`.
+ pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Self {
+ Self { pdev, chipset }
+ }
+}
+
+impl<'a> CommandToGsp for SetSystemInfo<'a> {
+ const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
+ type Command = fw::commands::GspSetSystemInfo;
+ type Reply = NoReply;
+ type InitError = Error;
+
+ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
+ Self::Command::init(self.pdev, self.chipset)
+ }
+}
+
+struct RegistryEntry {
+ key: &'static str,
+ value: u32,
+}
+
+/// The `SetRegistry` command.
+pub(crate) struct SetRegistry {
+ entries: KVec<RegistryEntry>,
+}
+
+impl SetRegistry {
+ /// Creates a new `SetRegistry` command, using a set of hardcoded entries.
+ pub(crate) fn new(vgpu_state: VgpuState) -> Result<Self> {
+ let mut entries = KVec::new();
+
+ // RMSecBusResetEnable - enables PCI secondary bus reset
+ entries.push(
+ RegistryEntry {
+ key: "RMSecBusResetEnable",
+ value: 1,
+ },
+ GFP_KERNEL,
+ )?;
+
+ // RMForcePcieConfigSave - forces GSP-RM to preserve PCI configuration registers on
+ // any PCI reset.
+ entries.push(
+ RegistryEntry {
+ key: "RMForcePcieConfigSave",
+ value: 1,
+ },
+ GFP_KERNEL,
+ )?;
+
+ // RMDevidCheckIgnore - allows GSP-RM to boot even if the PCI dev ID is not found
+ // in the internal product name database.
+ entries.push(
+ RegistryEntry {
+ key: "RMDevidCheckIgnore",
+ value: 1,
+ },
+ GFP_KERNEL,
+ )?;
+
+ if matches!(vgpu_state, VgpuState::Enabled { .. }) {
+ // RMSetSriovMode - required when vGPU is enabled.
+ entries.push(
+ RegistryEntry {
+ key: "RMSetSriovMode",
+ value: 1,
+ },
+ GFP_KERNEL,
+ )?;
+ }
+
+ Ok(Self { entries })
+ }
+}
+
+impl CommandToGsp for SetRegistry {
+ const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
+ type Command = fw::commands::PackedRegistryTable;
+ type Reply = NoReply;
+ type InitError = Infallible;
+
+ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
+ Self::Command::init(self.entries.len() as u32, self.size() as u32)
+ }
+
+ fn variable_payload_len(&self) -> usize {
+ let mut key_size = 0;
+ for entry in self.entries.iter() {
+ key_size += entry.key.len() + 1; // +1 for NULL terminator
+ }
+ self.entries.len() * size_of::<fw::commands::PackedRegistryEntry>() + key_size
+ }
+
+ fn init_variable_payload(
+ &self,
+ dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
+ ) -> Result {
+ let string_data_start_offset = size_of::<Self::Command>()
+ + self.entries.len() * size_of::<fw::commands::PackedRegistryEntry>();
+
+ // Array for string data.
+ let mut string_data = KVec::new();
+
+ for entry in self.entries.iter() {
+ dst.write_all(
+ fw::commands::PackedRegistryEntry::new(
+ (string_data_start_offset + string_data.len()) as u32,
+ entry.value,
+ )
+ .as_bytes(),
+ )?;
+
+ let key_bytes = entry.key.as_bytes();
+ string_data.extend_from_slice(key_bytes, GFP_KERNEL)?;
+ string_data.push(0, GFP_KERNEL)?;
+ }
+
+ dst.write_all(string_data.as_slice())
+ }
+}
+
+/// Message type for GSP initialization done notification.
+struct GspInitDone;
+
+// SAFETY: `GspInitDone` is a zero-sized type with no bytes, therefore it
+// trivially has no uninitialized bytes.
+unsafe impl FromBytes for GspInitDone {}
+
+impl MessageFromGsp for GspInitDone {
+ const FUNCTION: MsgFunction = MsgFunction::GspInitDone;
+ type InitError = Infallible;
+ type Message = ();
+
+ fn read(
+ _msg: &Self::Message,
+ _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError> {
+ Ok(GspInitDone)
+ }
+}
+
+/// Waits for GSP initialization to complete.
+pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq<'_>) -> Result {
+ loop {
+ match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
+ Ok(_) => break Ok(()),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+}
+
+/// The `GetGspStaticInfo` command.
+pub(crate) struct GetGspStaticInfo;
+
+impl CommandToGsp for GetGspStaticInfo {
+ const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
+ type Command = fw::commands::GspStaticConfigInfo;
+ type Reply = GetGspStaticInfoReply;
+ type InitError = Infallible;
+
+ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
+ Self::Command::init_zeroed()
+ }
+}
+
+/// The reply from the GSP to the [`GetGspStaticInfo`] command.
+pub(crate) struct GetGspStaticInfoReply {
+ gpu_name: [u8; 64],
+ /// BAR1 Page Directory Entry base address.
+ pub(crate) bar1_pde_base: u64,
+ /// Usable FB (VRAM) regions for driver memory allocation.
+ pub(crate) usable_fb_regions: KVec<Range<u64>>,
+ /// Exclusive end of the FB physical address space.
+ pub(crate) total_fb_end: u64,
+}
+
+impl MessageFromGsp for GetGspStaticInfoReply {
+ const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
+ type Message = fw::commands::GspStaticConfigInfo;
+ type InitError = Error;
+
+ fn read(
+ msg: &Self::Message,
+ _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError> {
+ let mut usable_fb_regions = KVec::new();
+ for region in msg.usable_fb_regions() {
+ usable_fb_regions.push(region, GFP_KERNEL)?;
+ }
+ let total_fb_end = msg.total_fb_end().ok_or(EINVAL)?;
+
+ Ok(GetGspStaticInfoReply {
+ gpu_name: msg.gpu_name_str(),
+ bar1_pde_base: msg.bar1_pde_base(),
+ usable_fb_regions,
+ total_fb_end,
+ })
+ }
+}
+
+/// Error type for [`GetGspStaticInfoReply::gpu_name`].
+#[derive(Debug)]
+pub(crate) enum GpuNameError {
+ /// The GPU name string does not contain a null terminator.
+ NoNullTerminator(FromBytesUntilNulError),
+
+ /// The GPU name string contains invalid UTF-8.
+ #[expect(dead_code)]
+ InvalidUtf8(Utf8Error),
+}
+
+impl GetGspStaticInfoReply {
+ /// Returns the name of the GPU as a string.
+ ///
+ /// Returns an error if the string given by the GSP does not contain a null terminator or
+ /// contains invalid UTF-8.
+ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
+ CStr::from_bytes_until_nul(&self.gpu_name)
+ .map_err(GpuNameError::NoNullTerminator)?
+ .to_str()
+ .map_err(GpuNameError::InvalidUtf8)
+ }
+}
+
+pub(crate) use fw::commands::PowerStateLevel;
+
+/// The `UnloadingGuestDriver` command, used to shut down the GSP.
+///
+/// Only used within the `gsp` module.
+pub(crate) struct UnloadingGuestDriver {
+ level: PowerStateLevel,
+}
+
+impl UnloadingGuestDriver {
+ /// Creates a new `UnloadingGuestDriver` command for the given [`PowerStateLevel`].
+ pub(crate) fn new(level: PowerStateLevel) -> Self {
+ Self { level }
+ }
+}
+
+impl CommandToGsp for UnloadingGuestDriver {
+ const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
+ type Command = fw::commands::UnloadingGuestDriver;
+ type Reply = UnloadingGuestDriverReply;
+ type InitError = Infallible;
+
+ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
+ fw::commands::UnloadingGuestDriver::new(self.level)
+ }
+}
+
+/// The reply from the GSP to the [`UnloadingGuestDriver`] command.
+pub(crate) struct UnloadingGuestDriverReply;
+
+impl MessageFromGsp for UnloadingGuestDriverReply {
+ const FUNCTION: MsgFunction = MsgFunction::UnloadingGuestDriver;
+ type InitError = Infallible;
+ type Message = ();
+
+ fn read(
+ _msg: &Self::Message,
+ _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
+ ) -> Result<Self, Self::InitError> {
+ Ok(UnloadingGuestDriverReply)
+ }
+}
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 10/10] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (8 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 09/10] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
@ 2026-09-27 11:14 ` Alexandre Courbot
2026-09-27 13:35 ` [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 11:14 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
Since we are going to introduce a second type of message, add `rpc` to
the names of the relevant command queue methods so we can distinguish
between the two.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 2 +-
drivers/gpu/nova-core/gsp/boot.rs | 6 +++---
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 30 +++++++++++++++---------------
drivers/gpu/nova-core/gsp/commands/rpc.rs | 2 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 2 +-
5 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 75a3ba7d50f4..ed7eef21e8f9 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -221,7 +221,7 @@ pub(crate) fn new(
/// Query the GSP for the static GPU information.
pub(crate) fn get_static_info(&self) -> Result<commands::rpc::GetGspStaticInfoReply> {
- self.cmdq.send_command(commands::rpc::GetGspStaticInfo)
+ self.cmdq.send_rpc_command(commands::rpc::GetGspStaticInfo)
}
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 8e446ad1eac2..0d0b07fafd4f 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -43,9 +43,9 @@ pub(crate) fn boot(
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset), GFP_KERNEL)?;
self.cmdq
- .send_command_no_wait(commands::rpc::SetSystemInfo::new(pdev, chipset))?;
+ .send_rpc_command_no_wait(commands::rpc::SetSystemInfo::new(pdev, chipset))?;
self.cmdq
- .send_command_no_wait(commands::rpc::SetRegistry::new(ctx.vgpu.state())?)?;
+ .send_rpc_command_no_wait(commands::rpc::SetRegistry::new(ctx.vgpu.state())?)?;
// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_bundle = hal.boot(&self, &mut ctx, &gsp_fw)?.or_else(|| {
@@ -91,7 +91,7 @@ fn shutdown_gsp(
mode: commands::rpc::PowerStateLevel,
) -> Result {
// Command to shut the GSP down.
- cmdq.send_command(commands::rpc::UnloadingGuestDriver::new(mode))?;
+ cmdq.send_rpc_command(commands::rpc::UnloadingGuestDriver::new(mode))?;
// Wait until GSP signals it is suspended.
const LIBOS_INTERRUPT_PROCESSOR_SUSPENDED: u32 = bits::bit_u32(31);
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index 335f94c4d2aa..81f8c79b1bb5 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -135,7 +135,7 @@ impl CmdqInner<'_> {
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
- fn send_single_command<M>(&mut self, command: M) -> Result
+ fn send_single_rpc_command<M>(&mut self, command: M) -> Result
where
M: CommandToGsp,
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
@@ -196,19 +196,19 @@ fn send_single_command<M>(&mut self, command: M) -> Result
/// 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
+ fn send_rpc_command<M>(&mut self, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
{
match SplitState::new(command)? {
- SplitState::Single(command) => self.send_single_command(command),
+ SplitState::Single(command) => self.send_single_rpc_command(command),
SplitState::Split(command, mut continuations) => {
- self.send_single_command(command)?;
+ self.send_single_rpc_command(command)?;
while let Some(continuation) = continuations.next() {
// Turbofish needed because the compiler cannot infer M here.
- self.send_single_command::<ContinuationRecord<'_>>(continuation)?;
+ self.send_single_rpc_command::<ContinuationRecord<'_>>(continuation)?;
}
Ok(())
@@ -216,7 +216,7 @@ fn send_command<M>(&mut self, command: M) -> Result
}
}
- fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
+ fn wait_for_rpc_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
let GspMessage {
header,
contents: (slice_1, slice_2),
@@ -275,12 +275,12 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
/// - `ERANGE` if the message had a recognized but non-matching function code.
///
/// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
+ fn receive_rpc_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
- let message = self.wait_for_msg(timeout)?;
+ let message = self.wait_for_rpc_msg(timeout)?;
let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
// Extract the message. Store the result as we want to advance the read pointer even in
@@ -329,7 +329,7 @@ impl Cmdq<'_> {
/// 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>
+ pub(crate) fn send_rpc_command<M>(&self, command: M) -> Result<M::Reply>
where
M: CommandToGsp,
M::Reply: MessageFromGsp,
@@ -337,10 +337,10 @@ pub(crate) fn send_command<M>(&self, command: M) -> Result<M::Reply>
Error: From<<M::Reply as MessageFromGsp>::InitError>,
{
let mut inner = self.inner.lock();
- inner.send_command(command)?;
+ inner.send_rpc_command(command)?;
loop {
- match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ match inner.receive_rpc_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
@@ -357,22 +357,22 @@ pub(crate) fn send_command<M>(&self, command: M) -> Result<M::Reply>
/// 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
+ pub(crate) fn send_rpc_command_no_wait<M>(&self, command: M) -> Result
where
M: CommandToGsp<Reply = NoReply>,
Error: From<M::InitError>,
{
- self.inner.lock().send_command(command)
+ self.inner.lock().send_rpc_command(command)
}
/// Receive a message from the GSP.
///
/// See [`CmdqInner::receive_msg`] for details.
- pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ pub(crate) fn receive_rpc_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
- self.inner.lock().receive_msg(timeout)
+ self.inner.lock().receive_rpc_msg(timeout)
}
}
diff --git a/drivers/gpu/nova-core/gsp/commands/rpc.rs b/drivers/gpu/nova-core/gsp/commands/rpc.rs
index a3262cc3e0e9..ad4f466427c0 100644
--- a/drivers/gpu/nova-core/gsp/commands/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/commands/rpc.rs
@@ -191,7 +191,7 @@ fn read(
/// Waits for GSP initialization to complete.
pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq<'_>) -> Result {
loop {
- match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
+ match cmdq.receive_rpc_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
Ok(_) => break Ok(()),
Err(ERANGE) => continue,
Err(e) => break Err(e),
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index ebf13867746f..ab3237ed64d6 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -344,7 +344,7 @@ pub(crate) fn run(
bootloader_app_version: u32,
) -> Result {
let seq_info = loop {
- match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
+ match cmdq.receive_rpc_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
Ok(seq_info) => break seq_info,
Err(ERANGE) => continue,
Err(e) => return Err(e),
--
2.55.0
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types
2026-09-27 11:14 [PATCH 00/10] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (9 preceding siblings ...)
2026-09-27 11:14 ` [PATCH 10/10] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
@ 2026-09-27 13:35 ` Alexandre Courbot
10 siblings, 0 replies; 12+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:35 UTC (permalink / raw)
To: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: Alistair Popple, Timur Tabi, Eliot Courtney, Zhi Wang, nova-gpu,
dri-devel, linux-kernel, rust-for-linux, Alexandre Courbot
On Sun Sep 27, 2026 at 8:14 PM JST, Alexandre Courbot wrote:
> The upcoming r000 firmware support [1] introduces a new type of command
> named GMC alongside the existing and already supported RPC command type.
>
> Since RPC was the only command type supported so far, it was embedded
> into the lowest command queue level: the message header actually
> includes the RPC header and the code considers them as one.
>
> This makes the r000/GMC support difficult to land without a refactor,
> with the result of transport layer code being duplicated between the two
> message types, and no clear separation between the RPC and GMC code
> which are mixed together in the command queue. Merging the code that way
> would introduce quite some technical debt.
>
> Thus, this series prepares a proper landing ground for a new message
> type by extracting all the RPC layer code and moving it into its own
> sub-modules, making the `cmdq` module completely agnostic of the type of
> message it transports. By the end of the series, the only mention of RPC
> in `cmdq.rs` is for the `rpc` sub-module.
>
> With the transport and message layers properly separated, support for
> GMC can be added into sibling modules of `rpc`, and the transport layer
> can be converted once when doing the switch to r000.
>
> This moves quite a bit of code around, but most of it is moved verbatim.
> No significant functional change is intended.
>
> For the r000 series, this essentially means the following:
>
> - Its patch 7 making `allocate_command` generic can be dropped as the
> transport layer makes no assumption about the message type.
> - Patches adding GMC support will do it in dedicated `gmc` sub-modules,
> following the model set by RPC. These `gmc` sub-modules will start as
> `dead_code` as they are being built.
> - The "switch to r000" patch will essentially operate on the transport
> layer level, i.e. `cmdq.rs`.
> - There should be no need for extra post-switch RPC fixing, as the
> transport layer code will already be shared with GMC.
>
> Overall, this should make the r000 series much easier to review and
> eventually land, while not creating new technical debt.
>
> This series is based on `drm-rust-next`.
Sashiko has found a couple of serious issues, and I've found another I
had overlooked, so please ignore this revision. I will send v2 shortly.
^ permalink raw reply [flat|nested] 12+ messages in thread