* [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types
@ 2026-09-27 13:46 Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
` (8 more replies)
0 siblings, 9 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 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`.
[1] https://lore.kernel.org/all/20260918010719.1176945-1-jhubbard@nvidia.com/
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Changes in v2:
- Fix checksum on receive incorrectly using the whole slots allocated
to the message instead of its actual length. (Sashiko)
- Drop patch 1 to avoid having to validate the `elemCount` header
member against edge cases. (Sashiko)
- Fix incorrect layer separation in receive path, where the CPU read
pointer was advanced in the message layer.
- Fix doclinks pointing to old function names. (Sashiko)
- Link to v1: https://patch.msgid.link/20260927-cmdq-rpc-v1-0-822db5af910e@nvidia.com
---
Alexandre Courbot (9):
gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command
gpu: nova-core: gsp: cmdq: split the transport part of the send path
gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module
gpu: nova-core: gsp: cmdq: split the transport part of the receive path
gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module
gpu: nova-core: gsp: move the RPC commands into a sub-module
gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods
drivers/gpu/nova-core/api.rs | 2 +-
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/cmdq.rs | 352 +++----------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 378 +++++++++++++++++++++
.../nova-core/gsp/cmdq/{ => rpc}/continuation.rs | 0
drivers/gpu/nova-core/gsp/commands.rs | 335 +-----------------
drivers/gpu/nova-core/gsp/commands/rpc.rs | 339 ++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 95 ++++--
drivers/gpu/nova-core/gsp/sequencer.rs | 6 +-
11 files changed, 836 insertions(+), 689 deletions(-)
---
base-commit: 10a6623a24a85708650efad7be15182289403cd7
change-id: 20260927-cmdq-rpc-ed5f850b207e
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 4:25 ` Eliot Courtney
2026-09-27 13:46 ` [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
` (7 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 | 24 +++++++++---------------
1 file changed, 9 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index a5595da23407..d293d28b0967 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -768,6 +768,15 @@ 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 after truncating the message to its exact length.
+ if Cmdq::calculate_checksum(
+ SBufferIter::new_reader([header.as_bytes(), slice_1, slice_2]).take(header.length()),
+ ) != 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 +805,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] 25+ messages in thread
* [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 4:43 ` Eliot Courtney
2026-09-27 13:46 ` [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
` (6 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 | 95 +++++++++++++++++++++++++--------------
2 files changed, 78 insertions(+), 42 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d293d28b0967..3a8548a51259 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.
@@ -777,16 +783,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);
@@ -833,7 +840,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..b12034db7857 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,28 @@ 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 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 total length of the message, message and RPC headers included.
+ ///
+ /// Note: this method is technically a layering violation as it is transport-layer code
+ /// accessing message-layer data. It only exists because it is necessary to accurately compute
+ /// the checksum upon receiving a message from the GSP.
pub(crate) fn length(&self) -> usize {
- size_of::<Self>() + self.payload_length()
+ size_of::<Self>() + self.rpc_header().length()
}
- // 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] 25+ messages in thread
* [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 4:52 ` Eliot Courtney
2026-09-27 13:46 ` [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
` (5 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 3a8548a51259..07e8e32c3d57 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] 25+ messages in thread
* [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (2 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 5:16 ` Eliot Courtney
2026-09-27 13:46 ` [PATCH v2 5/9] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module Alexandre Courbot
` (4 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 07e8e32c3d57..b6d50b0bd039 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] 25+ messages in thread
* [PATCH v2 5/9] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (3 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
` (3 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 b6d50b0bd039..640afe2e29cb 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 59d7d7fb15e8..03bd0d881934 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] 25+ messages in thread
* [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (4 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 5/9] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 3:19 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 7/9] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module Alexandre Courbot
` (2 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 introduce
`consume_element`, a transport-level method which runs a closure on the
next element before advancing the CPU read pointer past it, and
`parse_rpc_message`, which validates the RPC layer. This sets things up
for moving the RPC code into its own module, leaving the transport
agnostic of the message type.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 91 ++++++++++++++++++++++++++++-----------
1 file changed, 65 insertions(+), 26 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 640afe2e29cb..169ef0865339 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -411,7 +411,7 @@ struct GspCommand<'a> {
/// A message ready to be processed from the message queue.
///
-/// This is the type returned by [`CmdqInner::wait_for_msg`].
+/// This is the type returned by [`CmdqInner::wait_for_element`].
struct GspMessage<'a> {
// Reference to the header of the message.
header: &'a GspMsgElement,
@@ -566,7 +566,7 @@ fn send_command_element(
Ok(())
}
- /// Wait for a message to become available on the message queue.
+ /// Wait for the next element to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
/// beyond the advertised length in its [`GspMsgElement`].
@@ -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()),
@@ -606,18 +606,65 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
return Err(EIO);
}
+ Ok(GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ /// Wait for the next element on the message queue, pass it to `process_element`, and advances
+ /// the read pointer past it.
+ ///
+ /// The read pointer advances regardless of whether `process_element` succeeds or not.
+ ///
+ /// # Errors
+ ///
+ /// Errors from [`Self::wait_for_element`] and from `process_element` are propagated as-is.
+ fn consume_element<R>(
+ &mut self,
+ timeout: Delta,
+ process_element: impl FnOnce(GspMessage<'_>) -> Result<R>,
+ ) -> Result<R> {
+ let (elem_count, result) = {
+ let message = self.wait_for_element(timeout)?;
+
+ (
+ u32::try_from(message.header.length().div_ceil(GSP_PAGE_SIZE))?,
+ process_element(message),
+ )
+ };
+
+ self.gsp_mem.advance_cpu_read_ptr(elem_count);
+
+ result
+ }
+
+ /// Validate the RPC layer of `element` and trim its contents down to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ fn parse_rpc_message<'a>(
+ dev: &device::Device,
+ element: GspMessage<'a>,
+ ) -> Result<GspMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
let rpc_header = header.rpc_header();
let payload_length = rpc_header.length();
dev_dbg!(
- &self.dev,
+ 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.
+ // Check that the element is large enough for the message.
if slice_1.len() + slice_2.len() < payload_length {
return Err(EIO);
}
@@ -646,7 +693,8 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
/// 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.
+ /// The read pointer is always advanced past the message, regardless of whether it matched or
+ /// could be parsed.
///
/// # Errors
///
@@ -662,12 +710,16 @@ fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
// 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)?;
+ let dev = self.dev;
+
+ self.consume_element(timeout, |element| {
+ let message = Self::parse_rpc_message(dev, element)?;
+ let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
+
+ if function != M::FUNCTION {
+ return Err(ERANGE);
+ }
- // 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]);
@@ -675,22 +727,9 @@ fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
.map_err(|e| e.into())
.inspect(|_| {
if !sbuffer.is_empty() {
- dev_warn!(
- &self.dev,
- "GSP message {:?} has unprocessed data\n",
- function
- );
+ dev_warn!(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(u32::try_from(
- message.header.length().div_ceil(GSP_PAGE_SIZE),
- )?);
-
- result
+ })
}
}
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 7/9] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (5 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
8 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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 | 129 --------------------------------
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(+), 134 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 169ef0865339..16e40a52fa57 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`].
@@ -638,98 +603,4 @@ fn consume_element<R>(
result
}
-
- /// Validate the RPC layer of `element` and trim its contents down to the RPC payload.
- ///
- /// # Errors
- ///
- /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
- fn parse_rpc_message<'a>(
- dev: &device::Device,
- element: GspMessage<'a>,
- ) -> Result<GspMessage<'a>> {
- let GspMessage {
- header,
- contents: (slice_1, slice_2),
- } = element;
-
- let rpc_header = header.rpc_header();
- let payload_length = rpc_header.length();
-
- dev_dbg!(
- dev,
- "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- rpc_header.sequence(),
- rpc_header.function(),
- payload_length,
- );
-
- // Check that the element 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 or
- /// could be parsed.
- ///
- /// # 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 dev = self.dev;
-
- self.consume_element(timeout, |element| {
- let message = Self::parse_rpc_message(dev, element)?;
- let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
-
- if function != M::FUNCTION {
- return Err(ERANGE);
- }
-
- 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!(dev, "GSP message {:?} has unprocessed data\n", function);
- }
- })
- })
- }
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index 3bafe456efd6..a0e1e7e20ac3 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -6,7 +6,9 @@
mod continuation;
use kernel::{
+ device,
prelude::*,
+ time::Delta,
transmute::{
AsBytes,
FromBytes, //
@@ -35,7 +37,7 @@
use super::{
Cmdq,
CmdqInner,
- MessageFromGsp, //
+ GspMessage, //
};
/// Trait implemented by types representing a command to send to the GSP.
@@ -100,6 +102,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 +216,100 @@ fn send_command<M>(&mut self, command: M) -> Result
}
}
}
+
+ /// Validate the RPC layer of `element` and trim its contents down to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ fn parse_rpc_message<'a>(
+ dev: &device::Device,
+ element: GspMessage<'a>,
+ ) -> Result<GspMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.length();
+
+ dev_dbg!(
+ dev,
+ "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
+ rpc_header.sequence(),
+ rpc_header.function(),
+ payload_length,
+ );
+
+ // Check that the element 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 or
+ /// could be parsed.
+ ///
+ /// # 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 dev = self.dev;
+
+ self.consume_element(timeout, |element| {
+ let message = Self::parse_rpc_message(dev, element)?;
+ let function = message.header.rpc_header().function().map_err(|_| EINVAL)?;
+
+ if function != M::FUNCTION {
+ return Err(ERANGE);
+ }
+
+ 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!(dev, "GSP message {:?} has unprocessed data\n", function);
+ }
+ })
+ })
+ }
}
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 03bd0d881934..25a5a8d33d64 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] 25+ messages in thread
* [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (6 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 7/9] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 5:25 ` Eliot Courtney
2026-09-28 9:20 ` Zhi Wang
2026-09-27 13:46 ` [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
8 siblings, 2 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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/api.rs | 2 +-
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 | 337 +----------------------------
drivers/gpu/nova-core/gsp/commands/rpc.rs | 339 ++++++++++++++++++++++++++++++
6 files changed, 350 insertions(+), 346 deletions(-)
diff --git a/drivers/gpu/nova-core/api.rs b/drivers/gpu/nova-core/api.rs
index f02c6c7c7e51..84a15265e849 100644
--- a/drivers/gpu/nova-core/api.rs
+++ b/drivers/gpu/nova-core/api.rs
@@ -16,7 +16,7 @@
pub use crate::gpu::Spec;
use crate::gpu::Gpu;
-use crate::gsp::commands::GetGspStaticInfoReply;
+use crate::gsp::commands::rpc::GetGspStaticInfoReply;
/// API handle for the auxiliary bus child drivers to interact with nova-core.
pub struct NovaCoreApi<'bound> {
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index fb6f8a86a503..2a3bd619acf6 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 25a5a8d33d64..5f1c944678be 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -1,339 +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 struct GetGspStaticInfoReply {
- gpu_name: [u8; 64],
- gpu_short_name: [u8; 64],
- /// The 16-byte SHA-1 based GPU identifier (GID) reported by GSP-RM.
- pub gpu_gid: [u8; 16],
- /// 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(),
- gpu_short_name: msg.gpu_short_name_str(),
- gpu_gid: msg.gpu_gid(),
- bar1_pde_base: msg.bar1_pde_base(),
- usable_fb_regions,
- total_fb_end,
- })
- }
-}
-
-/// Error type for [`GetGspStaticInfoReply::gpu_name`].
-#[derive(Debug)]
-pub enum GpuNameError {
- /// The GPU name string does not contain a null terminator.
- NoNullTerminator(FromBytesUntilNulError),
-
- /// The GPU name string contains invalid UTF-8.
- 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 fn gpu_name(&self) -> Result<&str, GpuNameError> {
- CStr::from_bytes_until_nul(&self.gpu_name)
- .map_err(GpuNameError::NoNullTerminator)?
- .to_str()
- .map_err(GpuNameError::InvalidUtf8)
- }
-
- /// Returns the short 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 fn gpu_short_name(&self) -> core::result::Result<&str, GpuNameError> {
- CStr::from_bytes_until_nul(&self.gpu_short_name)
- .map_err(GpuNameError::NoNullTerminator)?
- .to_str()
- .map_err(GpuNameError::InvalidUtf8)
- }
-
- /// Returns the total usable VRAM size in bytes, i.e. the summed lengths of all usable FB
- /// regions.
- pub fn vram_size(&self) -> u64 {
- self.usable_fb_regions.iter().fold(0, |size, region| {
- size.saturating_add(region.end - region.start)
- })
- }
-}
-
-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..0176ac79fb09
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/commands/rpc.rs
@@ -0,0 +1,339 @@
+// 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 struct GetGspStaticInfoReply {
+ gpu_name: [u8; 64],
+ gpu_short_name: [u8; 64],
+ /// The 16-byte SHA-1 based GPU identifier (GID) reported by GSP-RM.
+ pub gpu_gid: [u8; 16],
+ /// 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(),
+ gpu_short_name: msg.gpu_short_name_str(),
+ gpu_gid: msg.gpu_gid(),
+ bar1_pde_base: msg.bar1_pde_base(),
+ usable_fb_regions,
+ total_fb_end,
+ })
+ }
+}
+
+/// Error type for [`GetGspStaticInfoReply::gpu_name`].
+#[derive(Debug)]
+pub enum GpuNameError {
+ /// The GPU name string does not contain a null terminator.
+ NoNullTerminator(FromBytesUntilNulError),
+
+ /// The GPU name string contains invalid UTF-8.
+ 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 fn gpu_name(&self) -> Result<&str, GpuNameError> {
+ CStr::from_bytes_until_nul(&self.gpu_name)
+ .map_err(GpuNameError::NoNullTerminator)?
+ .to_str()
+ .map_err(GpuNameError::InvalidUtf8)
+ }
+
+ /// Returns the short 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 fn gpu_short_name(&self) -> core::result::Result<&str, GpuNameError> {
+ CStr::from_bytes_until_nul(&self.gpu_short_name)
+ .map_err(GpuNameError::NoNullTerminator)?
+ .to_str()
+ .map_err(GpuNameError::InvalidUtf8)
+ }
+
+ /// Returns the total usable VRAM size in bytes, i.e. the summed lengths of all usable FB
+ /// regions.
+ pub fn vram_size(&self) -> u64 {
+ self.usable_fb_regions.iter().fold(0, |size, region| {
+ size.saturating_add(region.end - region.start)
+ })
+ }
+}
+
+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] 25+ messages in thread
* [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (7 preceding siblings ...)
2026-09-27 13:46 ` [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
@ 2026-09-27 13:46 ` Alexandre Courbot
2026-09-28 5:32 ` Eliot Courtney
8 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-27 13:46 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.rs | 2 +-
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 +-
6 files changed, 22 insertions(+), 22 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.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 16e40a52fa57..4fe23a33aa8b 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -58,7 +58,7 @@
use super::regs;
/// Marker type representing the absence of a reply for a command. Commands using this as their
-/// reply type are sent using [`Cmdq::send_command_no_wait`].
+/// reply type are sent using [`Cmdq::send_rpc_command_no_wait`].
pub(crate) struct NoReply;
/// Number of GSP pages making the [`Msgq`].
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index a0e1e7e20ac3..a8bf3d6317f8 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -104,7 +104,7 @@ 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.
+/// This trait tells [`Cmdq::receive_rpc_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;
@@ -136,7 +136,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`.
@@ -197,19 +197,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(())
@@ -283,7 +283,7 @@ fn parse_rpc_message<'a>(
/// - `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>,
@@ -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>
+ /// See [`CmdqInner::receive_rpc_msg`] for details.
+ 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 0176ac79fb09..a218453d6bef 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] 25+ messages in thread
* Re: [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path
2026-09-27 13:46 ` [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
@ 2026-09-28 3:19 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 3:19 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 10:46 PM JST, Alexandre Courbot wrote:
> `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 introduce
> `consume_element`, a transport-level method which runs a closure on the
> next element before advancing the CPU read pointer past it, and
> `parse_rpc_message`, which validates the RPC layer. This sets things up
> for moving the RPC code into its own module, leaving the transport
> agnostic of the message type.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> drivers/gpu/nova-core/gsp/cmdq.rs | 91 ++++++++++++++++++++++++++++-----------
> 1 file changed, 65 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
> index 640afe2e29cb..169ef0865339 100644
> --- a/drivers/gpu/nova-core/gsp/cmdq.rs
> +++ b/drivers/gpu/nova-core/gsp/cmdq.rs
> @@ -411,7 +411,7 @@ struct GspCommand<'a> {
>
> /// A message ready to be processed from the message queue.
> ///
> -/// This is the type returned by [`CmdqInner::wait_for_msg`].
> +/// This is the type returned by [`CmdqInner::wait_for_element`].
> struct GspMessage<'a> {
> // Reference to the header of the message.
> header: &'a GspMsgElement,
> @@ -566,7 +566,7 @@ fn send_command_element(
> Ok(())
> }
>
> - /// Wait for a message to become available on the message queue.
> + /// Wait for the next element to become available on the message queue.
> ///
> /// This works purely at the transport layer and does not interpret or validate the message
> /// beyond the advertised length in its [`GspMsgElement`].
> @@ -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()),
> @@ -606,18 +606,65 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
> return Err(EIO);
> }
>
> + Ok(GspMessage {
> + header,
> + contents: (slice_1, slice_2),
> + })
> + }
> +
> + /// Wait for the next element on the message queue, pass it to `process_element`, and advances
> + /// the read pointer past it.
> + ///
> + /// The read pointer advances regardless of whether `process_element` succeeds or not.
> + ///
> + /// # Errors
> + ///
> + /// Errors from [`Self::wait_for_element`] and from `process_element` are propagated as-is.
> + fn consume_element<R>(
> + &mut self,
> + timeout: Delta,
> + process_element: impl FnOnce(GspMessage<'_>) -> Result<R>,
> + ) -> Result<R> {
> + let (elem_count, result) = {
> + let message = self.wait_for_element(timeout)?;
> +
> + (
> + u32::try_from(message.header.length().div_ceil(GSP_PAGE_SIZE))?,
> + process_element(message),
> + )
> + };
> +
> + self.gsp_mem.advance_cpu_read_ptr(elem_count);
> +
> + result
> + }
> +
> + /// Validate the RPC layer of `element` and trim its contents down to the RPC payload.
> + ///
> + /// # Errors
> + ///
> + /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
> + fn parse_rpc_message<'a>(
> + dev: &device::Device,
> + element: GspMessage<'a>,
> + ) -> Result<GspMessage<'a>> {
Since this trims the payload, we should return a different type
(`RpcMessage`?) with a field referring to the RPC header, otherwise we
risk getting confused as to which headers are in the payload or not.
Not re-sending just for that, but wanted to flag this for v3.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
2026-09-27 13:46 ` [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
@ 2026-09-28 4:25 ` Eliot Courtney
2026-09-28 6:24 ` Alexandre Courbot
0 siblings, 1 reply; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 4:25 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> 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>
> ---
N.B. this means that checking the length of the message (which uses the
payload length) almost always gets replaced with a bad checksum error.
But since we can't distinguish between corrupted data and something
wrong with the lengths, we can't do anything about it anyway so lgtm.
It also means we don't know which message had the bad checksum, but
again I think this fine too.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
2026-09-27 13:46 ` [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
@ 2026-09-28 4:43 ` Eliot Courtney
2026-09-28 6:22 ` Alexandre Courbot
0 siblings, 1 reply; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 4:43 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> 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 | 95 +++++++++++++++++++++++++--------------
> 2 files changed, 78 insertions(+), 42 deletions(-)
>
> diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
> index d293d28b0967..3a8548a51259 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())?;
> }
nit: I think the style is to have separate unsafe blocks for each call
with separate justifications.
>
> @@ -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.
> @@ -777,16 +783,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);
> @@ -833,7 +840,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..b12034db7857 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 {}
nit: these impls are not used
> +
> +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>())
> + }
Suggest calling this `payload_length` because now we have to `lengths`,
one which is the length of the entire thing (On GspMsgElement), and
this, which is just the payload. optional nit: rename
GspMsgElement::length to frame_length.
> +
> + /// 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.
nit: Update or remove argument list?
> - 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,28 @@ 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 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 total length of the message, message and RPC headers included.
> + ///
> + /// Note: this method is technically a layering violation as it is transport-layer code
> + /// accessing message-layer data. It only exists because it is necessary to accurately compute
> + /// the checksum upon receiving a message from the GSP.
This is also used in `receive_msg` for `advance_cpu_read_ptr`, not just
for checksum. Also, I wouldn't necessarily describe this as a layering
violation. The transport needs to know the size of each frame being
sent, it just so happens that the data that contains that is at a weird
offset in the transport layer message (i.e. in the RPC header). It's a
nit but I would move the comment from on the function to inside saying
that > it's weird but the transport message size needs to look in
message layer data to know the length; Because it's very natural for a
transport layer to need to know the size of its frames.
With comments fixed or refuted:
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command
2026-09-27 13:46 ` [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
@ 2026-09-28 4:52 ` Eliot Courtney
0 siblings, 0 replies; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 4:52 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> `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 3a8548a51259..07e8e32c3d57 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.
nit: `msg_header` not used here
> + unsafe {
> + pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
> + }
> +
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-27 13:46 ` [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
@ 2026-09-28 5:16 ` Eliot Courtney
2026-09-28 6:19 ` Alexandre Courbot
0 siblings, 1 reply; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 5:16 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> 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 07e8e32c3d57..b6d50b0bd039 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;
nit: this local reads noisily to me
> +
> // 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();
I suspect if we pass dev and seq into the closure below, we can
further split the RPC layer from the transport layer. That means the
patches after this won't have to impl on CmdqInner for e.g.
`send_single_command` and instead we can just define a trait that writes
in the message layer data (so send_single_command could e.g. instead
take an impl RpcCommandWriter or whatever, and we can impl
RpcCommandWriter for anything that impls CommandToGsp, inserting the
message layer protocol stuff in there).
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-27 13:46 ` [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
@ 2026-09-28 5:25 ` Eliot Courtney
2026-09-28 9:20 ` Zhi Wang
1 sibling, 0 replies; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 5:25 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> 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>
> ---
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods
2026-09-27 13:46 ` [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
@ 2026-09-28 5:32 ` Eliot Courtney
0 siblings, 0 replies; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 5:32 UTC (permalink / raw)
To: Alexandre Courbot, 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, dri-devel
On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
> 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>
> ---
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-28 5:16 ` Eliot Courtney
@ 2026-09-28 6:19 ` Alexandre Courbot
2026-09-28 6:40 ` Eliot Courtney
0 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 6:19 UTC (permalink / raw)
To: Eliot Courtney
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Zhi Wang, nova-gpu, dri-devel, linux-kernel,
rust-for-linux, dri-devel
On Mon Sep 28, 2026 at 2:16 PM JST, Eliot Courtney wrote:
> On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
>> 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 07e8e32c3d57..b6d50b0bd039 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;
>
> nit: this local reads noisily to me
>
>> +
>> // 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();
>
> I suspect if we pass dev and seq into the closure below, we can
> further split the RPC layer from the transport layer. That means the
> patches after this won't have to impl on CmdqInner for e.g.
> `send_single_command` and instead we can just define a trait that writes
> in the message layer data (so send_single_command could e.g. instead
> take an impl RpcCommandWriter or whatever, and we can impl
> RpcCommandWriter for anything that impls CommandToGsp, inserting the
> message layer protocol stuff in there).
That sounds like a much better design indeed - I was contemplating
introducing traits and thought it might be better to keep things simple
and revisit until r000 is completed, but it probably won't be limiting
us in any way, and removes the unneeded mirror methods in `CmdqInner`.
The same pattern can probably also be applied on the receiving side, so
I'll try and do it there as well.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
2026-09-28 4:43 ` Eliot Courtney
@ 2026-09-28 6:22 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 6:22 UTC (permalink / raw)
To: Eliot Courtney
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Zhi Wang, nova-gpu, dri-devel, linux-kernel,
rust-for-linux, dri-devel
On Mon Sep 28, 2026 at 1:43 PM JST, Eliot Courtney wrote:
> On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
>> 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 | 95 +++++++++++++++++++++++++--------------
>> 2 files changed, 78 insertions(+), 42 deletions(-)
>>
>> diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
>> index d293d28b0967..3a8548a51259 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())?;
>> }
>
> nit: I think the style is to have separate unsafe blocks for each call
> with separate justifications.
>
>>
>> @@ -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.
>> @@ -777,16 +783,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);
>> @@ -833,7 +840,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..b12034db7857 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 {}
>
> nit: these impls are not used
>
>> +
>> +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>())
>> + }
>
> Suggest calling this `payload_length` because now we have to `lengths`,
> one which is the length of the entire thing (On GspMsgElement), and
> this, which is just the payload. optional nit: rename
> GspMsgElement::length to frame_length.
>
>> +
>> + /// 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.
>
> nit: Update or remove argument list?
>
>> - 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,28 @@ 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 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 total length of the message, message and RPC headers included.
>> + ///
>> + /// Note: this method is technically a layering violation as it is transport-layer code
>> + /// accessing message-layer data. It only exists because it is necessary to accurately compute
>> + /// the checksum upon receiving a message from the GSP.
>
> This is also used in `receive_msg` for `advance_cpu_read_ptr`, not just
> for checksum.
Ah yeah, now that I've removed patch 1 that is indeed the case.
> Also, I wouldn't necessarily describe this as a layering
> violation. The transport needs to know the size of each frame being
> sent, it just so happens that the data that contains that is at a weird
> offset in the transport layer message (i.e. in the RPC header). It's a
> nit but I would move the comment from on the function to inside saying
> that > it's weird but the transport message size needs to look in
> message layer data to know the length; Because it's very natural for a
> transport layer to need to know the size of its frames.
As long as there is only one message type this does work yes. r000 has a
proper size field in its transport header (and no checksum at all) so
this will be removed eventually, so I'll move that comment inside the
method as suggested.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
2026-09-28 4:25 ` Eliot Courtney
@ 2026-09-28 6:24 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 6:24 UTC (permalink / raw)
To: Eliot Courtney
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Zhi Wang, nova-gpu, dri-devel, linux-kernel,
rust-for-linux, dri-devel
On Mon Sep 28, 2026 at 1:25 PM JST, Eliot Courtney wrote:
> On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
>> 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>
>> ---
>
> N.B. this means that checking the length of the message (which uses the
> payload length) almost always gets replaced with a bad checksum error.
> But since we can't distinguish between corrupted data and something
> wrong with the lengths, we can't do anything about it anyway so lgtm.
> It also means we don't know which message had the bad checksum, but
> again I think this fine too.
Thankfully r000 gets rid of the checksum (which indeed is not very
critical when messages are sent through shared memory...), so that part
will eventually disappear. I just wanted to get the layering right to
make the split in the next patches easier.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-28 6:19 ` Alexandre Courbot
@ 2026-09-28 6:40 ` Eliot Courtney
2026-09-28 11:35 ` Alexandre Courbot
0 siblings, 1 reply; 25+ messages in thread
From: Eliot Courtney @ 2026-09-28 6:40 UTC (permalink / raw)
To: Alexandre Courbot, Eliot Courtney
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Zhi Wang, nova-gpu, dri-devel, linux-kernel,
rust-for-linux, dri-devel
On Mon Sep 28, 2026 at 3:19 PM JST, Alexandre Courbot wrote:
> On Mon Sep 28, 2026 at 2:16 PM JST, Eliot Courtney wrote:
>> On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
>>> 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 07e8e32c3d57..b6d50b0bd039 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;
>>
>> nit: this local reads noisily to me
>>
>>> +
>>> // 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();
>>
>> I suspect if we pass dev and seq into the closure below, we can
>> further split the RPC layer from the transport layer. That means the
>> patches after this won't have to impl on CmdqInner for e.g.
>> `send_single_command` and instead we can just define a trait that writes
>> in the message layer data (so send_single_command could e.g. instead
>> take an impl RpcCommandWriter or whatever, and we can impl
>> RpcCommandWriter for anything that impls CommandToGsp, inserting the
>> message layer protocol stuff in there).
>
> That sounds like a much better design indeed - I was contemplating
> introducing traits and thought it might be better to keep things simple
> and revisit until r000 is completed, but it probably won't be limiting
> us in any way, and removes the unneeded mirror methods in `CmdqInner`.
>
> The same pattern can probably also be applied on the receiving side, so
> I'll try and do it there as well.
Yerp I think it can be. I also don't think it's bad to keep the trait
around even after we remove r570. If we are passing in closures anyway
to cmdq to do message layer stuff, that's kinda like an unnamed trait
anyway. Thanks!
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-27 13:46 ` [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-09-28 5:25 ` Eliot Courtney
@ 2026-09-28 9:20 ` Zhi Wang
2026-09-28 11:40 ` Alexandre Courbot
1 sibling, 1 reply; 25+ messages in thread
From: Zhi Wang @ 2026-09-28 9:20 UTC (permalink / raw)
To: Alexandre Courbot
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Eliot Courtney, nova-gpu, dri-devel, linux-kernel,
rust-for-linux
On Sun, 27 Sep 2026 22:46:25 +0900
Alexandre Courbot <acourbot@nvidia.com> wrote:
> 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.
>
Hi Alex:
What would be the plan for commands.rs in the future after the movement?
I was adopting the similar code structures (fw.rs/command.rs) for vGPU
manager's RPCs and GSP plugin RPCs, e.g. having RPC typed code and
function handler(wrapper)s, it would be nice that I can align with the
idea accordingly.
Z.
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> drivers/gpu/nova-core/api.rs | 2 +-
> 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 | 337
> +----------------------------
> drivers/gpu/nova-core/gsp/commands/rpc.rs | 339
> ++++++++++++++++++++++++++++++ 6 files changed, 350 insertions(+),
> 346 deletions(-)
>
> diff --git a/drivers/gpu/nova-core/api.rs
> b/drivers/gpu/nova-core/api.rs index f02c6c7c7e51..84a15265e849 100644
> --- a/drivers/gpu/nova-core/api.rs
> +++ b/drivers/gpu/nova-core/api.rs
> @@ -16,7 +16,7 @@
> pub use crate::gpu::Spec;
>
> use crate::gpu::Gpu;
> -use crate::gsp::commands::GetGspStaticInfoReply;
> +use crate::gsp::commands::rpc::GetGspStaticInfoReply;
>
> /// API handle for the auxiliary bus child drivers to interact with
> nova-core. pub struct NovaCoreApi<'bound> {
> diff --git a/drivers/gpu/nova-core/gpu.rs
> b/drivers/gpu/nova-core/gpu.rs index fb6f8a86a503..2a3bd619acf6 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
> 25a5a8d33d64..5f1c944678be 100644 ---
> a/drivers/gpu/nova-core/gsp/commands.rs +++
> b/drivers/gpu/nova-core/gsp/commands.rs @@ -1,339 +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 struct GetGspStaticInfoReply {
> - gpu_name: [u8; 64],
> - gpu_short_name: [u8; 64],
> - /// The 16-byte SHA-1 based GPU identifier (GID) reported by
> GSP-RM.
> - pub gpu_gid: [u8; 16],
> - /// 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(),
> - gpu_short_name: msg.gpu_short_name_str(),
> - gpu_gid: msg.gpu_gid(),
> - bar1_pde_base: msg.bar1_pde_base(),
> - usable_fb_regions,
> - total_fb_end,
> - })
> - }
> -}
> -
> -/// Error type for [`GetGspStaticInfoReply::gpu_name`].
> -#[derive(Debug)]
> -pub enum GpuNameError {
> - /// The GPU name string does not contain a null terminator.
> - NoNullTerminator(FromBytesUntilNulError),
> -
> - /// The GPU name string contains invalid UTF-8.
> - 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 fn gpu_name(&self) -> Result<&str, GpuNameError> {
> - CStr::from_bytes_until_nul(&self.gpu_name)
> - .map_err(GpuNameError::NoNullTerminator)?
> - .to_str()
> - .map_err(GpuNameError::InvalidUtf8)
> - }
> -
> - /// Returns the short 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 fn gpu_short_name(&self) -> core::result::Result<&str,
> GpuNameError> {
> - CStr::from_bytes_until_nul(&self.gpu_short_name)
> - .map_err(GpuNameError::NoNullTerminator)?
> - .to_str()
> - .map_err(GpuNameError::InvalidUtf8)
> - }
> -
> - /// Returns the total usable VRAM size in bytes, i.e. the summed
> lengths of all usable FB
> - /// regions.
> - pub fn vram_size(&self) -> u64 {
> - self.usable_fb_regions.iter().fold(0, |size, region| {
> - size.saturating_add(region.end - region.start)
> - })
> - }
> -}
> -
> -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..0176ac79fb09
> --- /dev/null
> +++ b/drivers/gpu/nova-core/gsp/commands/rpc.rs
> @@ -0,0 +1,339 @@
> +// 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 struct GetGspStaticInfoReply {
> + gpu_name: [u8; 64],
> + gpu_short_name: [u8; 64],
> + /// The 16-byte SHA-1 based GPU identifier (GID) reported by
> GSP-RM.
> + pub gpu_gid: [u8; 16],
> + /// 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(),
> + gpu_short_name: msg.gpu_short_name_str(),
> + gpu_gid: msg.gpu_gid(),
> + bar1_pde_base: msg.bar1_pde_base(),
> + usable_fb_regions,
> + total_fb_end,
> + })
> + }
> +}
> +
> +/// Error type for [`GetGspStaticInfoReply::gpu_name`].
> +#[derive(Debug)]
> +pub enum GpuNameError {
> + /// The GPU name string does not contain a null terminator.
> + NoNullTerminator(FromBytesUntilNulError),
> +
> + /// The GPU name string contains invalid UTF-8.
> + 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 fn gpu_name(&self) -> Result<&str, GpuNameError> {
> + CStr::from_bytes_until_nul(&self.gpu_name)
> + .map_err(GpuNameError::NoNullTerminator)?
> + .to_str()
> + .map_err(GpuNameError::InvalidUtf8)
> + }
> +
> + /// Returns the short 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 fn gpu_short_name(&self) -> core::result::Result<&str,
> GpuNameError> {
> + CStr::from_bytes_until_nul(&self.gpu_short_name)
> + .map_err(GpuNameError::NoNullTerminator)?
> + .to_str()
> + .map_err(GpuNameError::InvalidUtf8)
> + }
> +
> + /// Returns the total usable VRAM size in bytes, i.e. the summed
> lengths of all usable FB
> + /// regions.
> + pub fn vram_size(&self) -> u64 {
> + self.usable_fb_regions.iter().fold(0, |size, region| {
> + size.saturating_add(region.end - region.start)
> + })
> + }
> +}
> +
> +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)
> + }
> +}
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-28 6:40 ` Eliot Courtney
@ 2026-09-28 11:35 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 11:35 UTC (permalink / raw)
To: Eliot Courtney
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Zhi Wang, nova-gpu, dri-devel, linux-kernel,
rust-for-linux, dri-devel
On Mon Sep 28, 2026 at 3:40 PM JST, Eliot Courtney wrote:
> On Mon Sep 28, 2026 at 3:19 PM JST, Alexandre Courbot wrote:
>> On Mon Sep 28, 2026 at 2:16 PM JST, Eliot Courtney wrote:
>>> On Sun Sep 27, 2026 at 10:46 PM JST, Alexandre Courbot wrote:
>>>> 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 07e8e32c3d57..b6d50b0bd039 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;
>>>
>>> nit: this local reads noisily to me
>>>
>>>> +
>>>> // 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();
>>>
>>> I suspect if we pass dev and seq into the closure below, we can
>>> further split the RPC layer from the transport layer. That means the
>>> patches after this won't have to impl on CmdqInner for e.g.
>>> `send_single_command` and instead we can just define a trait that writes
>>> in the message layer data (so send_single_command could e.g. instead
>>> take an impl RpcCommandWriter or whatever, and we can impl
>>> RpcCommandWriter for anything that impls CommandToGsp, inserting the
>>> message layer protocol stuff in there).
>>
>> That sounds like a much better design indeed - I was contemplating
>> introducing traits and thought it might be better to keep things simple
>> and revisit until r000 is completed, but it probably won't be limiting
>> us in any way, and removes the unneeded mirror methods in `CmdqInner`.
>>
>> The same pattern can probably also be applied on the receiving side, so
>> I'll try and do it there as well.
>
> Yerp I think it can be. I also don't think it's bad to keep the trait
> around even after we remove r570. If we are passing in closures anyway
> to cmdq to do message layer stuff, that's kinda like an unnamed trait
> anyway. Thanks!
So it came to a surprise to me, but RPC will stay even after r570 is
removed (although it will only be used with vGPU IIUC) - that's actually
the main motivation for this design. So the trait will have more than
one implementor even after the switch to r000.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-28 9:20 ` Zhi Wang
@ 2026-09-28 11:40 ` Alexandre Courbot
2026-09-28 15:11 ` Zhi Wang
0 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-09-28 11:40 UTC (permalink / raw)
To: Zhi Wang
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Eliot Courtney, nova-gpu, dri-devel, linux-kernel,
rust-for-linux
On Mon Sep 28, 2026 at 6:20 PM JST, Zhi Wang wrote:
> On Sun, 27 Sep 2026 22:46:25 +0900
> Alexandre Courbot <acourbot@nvidia.com> wrote:
>
>> 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.
>>
>
> Hi Alex:
>
> What would be the plan for commands.rs in the future after the movement?
> I was adopting the similar code structures (fw.rs/command.rs) for vGPU
> manager's RPCs and GSP plugin RPCs, e.g. having RPC typed code and
> function handler(wrapper)s, it would be nice that I can align with the
> idea accordingly.
Can you point me to the the structure you have if it is posted? (sorry,
too many series in-flight and I cannot find where it is ^_^;)
If you add vGPU RPC commands then I guess somewhere like
`gsp/commands/rpc/vgpu.rs` would be a good place for them. But let's
wait until this series is approved before settling on a design - moving
code around is easy anyway.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-28 11:40 ` Alexandre Courbot
@ 2026-09-28 15:11 ` Zhi Wang
0 siblings, 0 replies; 25+ messages in thread
From: Zhi Wang @ 2026-09-28 15:11 UTC (permalink / raw)
To: Alexandre Courbot
Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo, Alistair Popple,
Timur Tabi, Eliot Courtney, nova-gpu, dri-devel, linux-kernel,
rust-for-linux
On Mon, 28 Sep 2026 20:40:08 +0900
"Alexandre Courbot" <acourbot@nvidia.com> wrote:
> On Mon Sep 28, 2026 at 6:20 PM JST, Zhi Wang wrote:
> > On Sun, 27 Sep 2026 22:46:25 +0900
> > Alexandre Courbot <acourbot@nvidia.com> wrote:
> >
> >> 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.
> >>
> >
> > Hi Alex:
> >
> > What would be the plan for commands.rs in the future after the
> > movement? I was adopting the similar code structures
> > (fw.rs/command.rs) for vGPU manager's RPCs and GSP plugin RPCs,
> > e.g. having RPC typed code and function handler(wrapper)s, it would
> > be nice that I can align with the idea accordingly.
>
> Can you point me to the the structure you have if it is posted?
> (sorry, too many series in-flight and I cannot find where it is ^_^;)
>
No worry.
I am trying to maintain vGPU-related RPCs and GSP plugin RPCs in below
two files. Basically the maintaining schema is similar to current
nova-core fw.rs and commands.rs.
IMO, nova-core and vGPU managers bindings should stay together, but
there can be quite many vGPU RPCs. Would it be a good idea to maintain
them together with nova-core's fw.rs and commands.rs?
https://github.com/zhiwang-nvidia/nova-core/blob/zhi/nova-vgpu-20260928/drivers/gpu/nova-core/vgpu/fw.rs
https://github.com/zhiwang-nvidia/nova-core/blob/zhi/nova-vgpu-20260928/drivers/gpu/nova-core/vgpu/commands.rs
> If you add vGPU RPC commands then I guess somewhere like
> `gsp/commands/rpc/vgpu.rs` would be a good place for them. But let's
> wait until this series is approved before settling on a design -
> moving code around is easy anyway.
>
Sure.
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-09-28 15:11 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-27 13:46 [PATCH v2 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-09-28 4:25 ` Eliot Courtney
2026-09-28 6:24 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
2026-09-28 4:43 ` Eliot Courtney
2026-09-28 6:22 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
2026-09-28 4:52 ` Eliot Courtney
2026-09-27 13:46 ` [PATCH v2 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
2026-09-28 5:16 ` Eliot Courtney
2026-09-28 6:19 ` Alexandre Courbot
2026-09-28 6:40 ` Eliot Courtney
2026-09-28 11:35 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 5/9] gpu: nova-core: gsp: cmdq: move the RPC send code into a sub-module Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path Alexandre Courbot
2026-09-28 3:19 ` Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 7/9] gpu: nova-core: gsp: cmdq: move the RPC receive code into a sub-module Alexandre Courbot
2026-09-27 13:46 ` [PATCH v2 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-09-28 5:25 ` Eliot Courtney
2026-09-28 9:20 ` Zhi Wang
2026-09-28 11:40 ` Alexandre Courbot
2026-09-28 15:11 ` Zhi Wang
2026-09-27 13:46 ` [PATCH v2 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
2026-09-28 5:32 ` Eliot Courtney
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®