* [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types
@ 2026-09-30 14:55 Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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 v3:
- Use dedicated `RpcMessage` type to distinguish parsed RPC message from
the transport-level `GspMessage`.
- Introduce traits for sending and receiving elements on the command
queue (thanks Eliot!).
- Split unsafe blocks, drop unused impls, and other fixes on patch 2.
(thanks Eliot!)
- Squash the two move commits into a single one.
- Link to v2: https://patch.msgid.link/20260927-cmdq-rpc-v2-0-c3f66ae73be4@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: split RPC parsing part of the receive path
gpu: nova-core: gsp: cmdq: split the transport part of the receive path
gpu: nova-core: gsp: cmdq: move the RPC 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 | 351 +++----------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 380 +++++++++++++++++++++
.../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 | 88 +++--
drivers/gpu/nova-core/gsp/sequencer.rs | 6 +-
11 files changed, 834 insertions(+), 685 deletions(-)
---
base-commit: 10a6623a24a85708650efad7be15182289403cd7
change-id: 20260927-cmdq-rpc-ed5f850b207e
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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>
Reviewed-by: Eliot Courtney <ecourtney@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] 10+ messages in thread
* [PATCH v3 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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>
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 35 +++++++++++-----
drivers/gpu/nova-core/gsp/fw.rs | 88 ++++++++++++++++++++++++---------------
2 files changed, 78 insertions(+), 45 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d293d28b0967..b40d45bf0a92 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,22 @@ 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);
- // SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
- // fails.
+ let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
+ let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
+ // SAFETY: `dst.header` is a valid reference, and is not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?;
+ }
+ // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
+ }
+ // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
unsafe {
- pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element)?;
pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
}
@@ -694,7 +706,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 +789,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 +846,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..58ae82dab5e5 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -781,11 +781,18 @@ 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 {
+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 +803,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 +842,14 @@ 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 +859,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 +875,27 @@ 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.
pub(crate) fn length(&self) -> usize {
- size_of::<Self>() + self.payload_length()
+ // Note: this is transport-layer code accessing message-layer data, which is only ok here
+ // because RPC is the only kind of message we handle, and its header can (with a stretch) be
+ // considered part of the transport header.
+ 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] 10+ messages in thread
* [PATCH v3 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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>
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index b40d45bf0a92..bda79f23626d 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -659,18 +659,18 @@ fn send_single_command<M>(&mut self, command: M) -> Result
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
- // 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.
+ // Fill the header.
let msg_element_init = GspMsgElement::init(self.seq, size_in_bytes);
- let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
// SAFETY: `dst.header` is a valid reference, and is 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: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
// initializer fails.
unsafe {
@@ -693,14 +693,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",
@@ -709,6 +701,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] 10+ messages in thread
* [PATCH v3 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (2 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 5/9] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path Alexandre Courbot
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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
Introduce the `CommandElement` trait and `RpcCommandElement` wrapper
type to define how messages (independently of their type) are sent
through the transport layer. `send_single_command` becomes
`send_command_element`, which allocates the queue slots, writes the
element header, and delegates the writing of the message itself to the
implementation of `CommandElement` before computing the checksum,
advancing the write pointer and ringing the doorbell.
The RPC part of `send_single_command` (writing the RPC header and the
command payload) is now part of the `CommandElement` implementation.
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.
Suggested-by: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 133 +++++++++++++++++++++++---------------
1 file changed, 80 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index bda79f23626d..d8a7716fc500 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -35,10 +35,7 @@
},
};
-use continuation::{
- ContinuationRecord,
- SplitState, //
-};
+use continuation::SplitState;
use pin_init::pin_init_scope;
@@ -67,6 +64,19 @@
/// reply type are sent using [`Cmdq::send_command_no_wait`].
pub(crate) struct NoReply;
+/// Trait implemented by types that can be sent as a single command queue element.
+///
+/// The command queue allocates `size()` bytes after the `GspMsgElement` header and calls `write()`
+/// to fill them.
+trait CommandElement {
+ /// Size in bytes of the element, not including the `GspMsgElement` header.
+ fn size(&self) -> usize;
+
+ /// Writes the contents of the command into `dst`. `dev` is the queue's device (to be used for
+ /// logging), `seq` is the sequence number of the element.
+ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result;
+}
+
/// Trait implemented by types representing a command to send to the GSP.
///
/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
@@ -129,6 +139,62 @@ fn size(&self) -> usize {
}
}
+/// Wrapper type for sending a RPC command as a command queue element.
+///
+/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket
+/// implementation as it would conflict with other future command types.
+struct RpcCommandElement<M>(M);
+
+impl<M> CommandElement for RpcCommandElement<M>
+where
+ M: CommandToGsp,
+ Error: From<M::InitError>,
+{
+ fn size(&self) -> usize {
+ self.0.size()
+ }
+
+ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
+ let command = &self.0;
+ let size_in_bytes = command.size();
+ // Extract area for the command itself. The GSP message header and the command header
+ // together are guaranteed to fit entirely into a single page, so it's ok to only look
+ // at `dst.contents.0` here.
+ let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
+ let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
+ // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
+ }
+ // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
+ }
+
+ // Fill the variable-length payload, which may be empty.
+ let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
+ command.init_variable_payload(&mut sbuffer)?;
+
+ if !sbuffer.is_empty() {
+ return Err(EIO);
+ }
+
+ dev_dbg!(
+ dev,
+ "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
+ seq,
+ M::FUNCTION,
+ size_in_bytes,
+ );
+
+ Ok(())
+ }
+}
+
/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
@@ -638,24 +704,18 @@ 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.
+ /// Allocate enough send slots to store `command`, initialize them using
+ /// [`CommandElement::write`], 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>,
- {
+ /// Error codes returned by [`CommandElement::write`] are returned as-is.
+ fn send_command_element<E: CommandElement>(&mut self, command: E) -> Result {
let size_in_bytes = command.size();
- let dst = self
+ let mut dst = self
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
@@ -666,40 +726,8 @@ fn send_single_command<M>(&mut self, command: M) -> Result
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: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
- // initializer fails.
- unsafe {
- pin_init::raw_try_init(
- core::ptr::from_mut(dst.header.rpc_header_mut()),
- rpc_header_init,
- )?;
- }
- // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
- unsafe {
- pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
- }
-
- // Fill the variable-length payload, which may be empty.
- let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
- command.init_variable_payload(&mut sbuffer)?;
-
- if !sbuffer.is_empty() {
- return Err(EIO);
- }
- drop(sbuffer);
-
- dev_dbg!(
- &self.dev,
- "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
- self.seq,
- M::FUNCTION,
- size_in_bytes,
- );
+ // Write the message payload.
+ command.write(self.dev, self.seq, &mut dst)?;
// Compute checksum now that the whole message is ready.
dst.header
@@ -735,13 +763,12 @@ fn send_command<M>(&mut self, command: M) -> Result
Error: From<M::InitError>,
{
match SplitState::new(command)? {
- SplitState::Single(command) => self.send_single_command(command),
+ SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)),
SplitState::Split(command, mut continuations) => {
- self.send_single_command(command)?;
+ self.send_command_element(RpcCommandElement(command))?;
while let Some(continuation) = continuations.next() {
- // Turbofish needed because the compiler cannot infer M here.
- self.send_single_command::<ContinuationRecord<'_>>(continuation)?;
+ self.send_command_element(RpcCommandElement(continuation))?;
}
Ok(())
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 5/9] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (3 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 6/9] gpu: nova-core: gsp: cmdq: split the transport " Alexandre Courbot
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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 RPC parsing should not be done in the same method as the transport
header parsing. Split it out and introduce a `RpcMessage` type to
indicate messages that have been successfully parsed as RPC. This makes
the code easier to move around into its new destination.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 37 +++++++++++++++++++++++++++++++++----
1 file changed, 33 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d8a7716fc500..b8a9e02b76fe 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -553,6 +553,17 @@ struct GspMessage<'a> {
contents: (&'a [u8], &'a [u8]),
}
+/// A RPC message ready to be processed from the message queue.
+///
+/// This is the type returned by [`CmdqInner::parse_rpc_message`].
+struct RpcMessage<'a> {
+ // Reference to the RPC header of the message.
+ header: &'a RpcMessageHeader,
+ // Slices to the contents of the message. The second slice is zero unless the message loops
+ // over the message queue.
+ contents: (&'a [u8], &'a [u8]),
+}
+
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
@@ -794,7 +805,7 @@ fn send_command<M>(&mut self, command: M) -> Result
/// 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_msg(&self, timeout: Delta) -> Result<RpcMessage<'_>> {
// Wait for a message to arrive from the GSP.
let (slice_1, slice_2) = read_poll_timeout(
|| Ok(self.gsp_mem.driver_read_area()),
@@ -816,6 +827,24 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
return Err(EIO);
}
+ self.parse_rpc_message(GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ })
+ }
+
+ /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
+ /// to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ fn parse_rpc_message<'a>(&self, element: GspMessage<'a>) -> Result<RpcMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
let rpc_header = header.rpc_header();
let payload_length = rpc_header.length();
@@ -845,8 +874,8 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
)
};
- Ok(GspMessage {
- header,
+ Ok(RpcMessage {
+ header: rpc_header,
contents: (slice_1, slice_2),
})
}
@@ -873,7 +902,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.rpc_header().function().map_err(|_| EINVAL)?;
+ let function = message.header.function().map_err(|_| EINVAL)?;
// Extract the message. Store the result as we want to advance the read pointer even in
// case of failure.
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 6/9] gpu: nova-core: gsp: cmdq: split the transport part of the receive path
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (4 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 5/9] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 7/9] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module Alexandre Courbot
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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 reads the message's
contents using an implementation of the `MessageElement` trait 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 | 219 +++++++++++++++++++++-----------------
1 file changed, 119 insertions(+), 100 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index b8a9e02b76fe..07036972dbec 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -195,6 +195,15 @@ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Res
}
}
+/// Trait implemented by types that can be received as single command queue elements.
+///
+/// The command queue validates the element header before calling `read()` to interpret the
+/// contents.
+trait MessageElement: Sized {
+ /// Tries to read `Self` from `element`. `dev` is the queue's device, to be used for logging.
+ fn read(dev: &device::Device, element: GspMessage<'_>) -> Result<Self>;
+}
+
/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
@@ -218,6 +227,94 @@ fn read(
) -> Result<Self, Self::InitError>;
}
+/// Wrapper type for receiving a RPC message from a command queue element.
+///
+/// [`MessageElement`] cannot be directly implemented for all [`MessageFromGsp`] with a blanket
+/// implementation as it would conflict with other future message types.
+struct RpcMessageElement<M>(M);
+
+impl<M> RpcMessageElement<M>
+where
+ M: MessageFromGsp,
+{
+ /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
+ /// to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ fn parse_rpc_message<'a>(
+ dev: &device::Device,
+ element: GspMessage<'a>,
+ ) -> Result<RpcMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.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(RpcMessage {
+ header: rpc_header,
+ contents: (slice_1, slice_2),
+ })
+ }
+}
+
+impl<M> MessageElement for RpcMessageElement<M>
+where
+ M: MessageFromGsp,
+ Error: From<M::InitError>,
+{
+ fn read(dev: &device::Device, element: GspMessage<'_>) -> Result<Self> {
+ let message = Self::parse_rpc_message(dev, element)?;
+ let function = message.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(Self)
+ .map_err(|e| e.into())
+ .inspect(|_| {
+ if !sbuffer.is_empty() {
+ dev_warn!(dev, "GSP message {:?} has unprocessed data\n", function);
+ }
+ })
+ }
+}
+
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
@@ -544,7 +641,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,
@@ -662,8 +759,8 @@ pub(crate) fn send_command<M>(&self, command: M) -> Result<M::Reply>
inner.send_command(command)?;
loop {
- match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
- Ok(reply) => break Ok(reply),
+ match inner.consume_element::<RpcMessageElement<M::Reply>>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply.0),
Err(ERANGE) => continue,
Err(e) => break Err(e),
}
@@ -689,13 +786,16 @@ pub(crate) fn send_command_no_wait<M>(&self, command: M) -> Result
/// Receive a message from the GSP.
///
- /// See [`CmdqInner::receive_msg`] for details.
+ /// See [`CmdqInner::consume_element`] 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)
+ self.inner
+ .lock()
+ .consume_element::<RpcMessageElement<M>>(timeout)
+ .map(|m| m.0)
}
}
@@ -787,7 +887,7 @@ fn send_command<M>(&mut self, command: M) -> Result
}
}
- /// 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`].
@@ -805,7 +905,7 @@ fn send_command<M>(&mut self, command: M) -> Result
/// message queue.
///
/// Error codes returned by the message constructor are propagated as-is.
- fn wait_for_msg(&self, timeout: Delta) -> Result<RpcMessage<'_>> {
+ 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()),
@@ -827,108 +927,27 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<RpcMessage<'_>> {
return Err(EIO);
}
- self.parse_rpc_message(GspMessage {
+ Ok(GspMessage {
header,
contents: (slice_1, slice_2),
})
}
- /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
- /// to the RPC payload.
+ /// Wait for the next element on the message queue, pass it to [`MessageElement::read`], and
+ /// advances the read pointer past it.
+ ///
+ /// The read pointer advances regardless of whether [`MessageElement::read`] succeeds or not.
///
/// # Errors
///
- /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
- fn parse_rpc_message<'a>(&self, element: GspMessage<'a>) -> Result<RpcMessage<'a>> {
- let GspMessage {
- header,
- contents: (slice_1, slice_2),
- } = element;
+ /// Errors from [`Self::wait_for_element`] and from [`MessageElement::read`] are propagated
+ /// as-is.
+ fn consume_element<M: MessageElement>(&mut self, timeout: Delta) -> Result<M> {
+ let message = self.wait_for_element(timeout)?;
+ let elem_count = u32::try_from(message.header.length().div_ceil(GSP_PAGE_SIZE))?;
+ let result = M::read(self.dev, message);
- let rpc_header = header.rpc_header();
- let payload_length = rpc_header.length();
-
- dev_dbg!(
- &self.dev,
- "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- rpc_header.sequence(),
- rpc_header.function(),
- payload_length,
- );
-
- // Check that the driver read area is large enough for the message.
- if slice_1.len() + slice_2.len() < payload_length {
- return Err(EIO);
- }
-
- // Cut the message slices down to the actual length of the message.
- let (slice_1, slice_2) = if slice_1.len() > payload_length {
- // PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
- (slice_1.split_at(payload_length).0, &slice_2[0..0])
- } else {
- (
- slice_1,
- // PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
- // large as `payload_length`.
- slice_2.split_at(payload_length - slice_1.len()).0,
- )
- };
-
- Ok(RpcMessage {
- header: rpc_header,
- contents: (slice_1, slice_2),
- })
- }
-
- /// Receive a message from the GSP.
- ///
- /// The expected message type is specified using the `M` generic parameter. If the pending
- /// message has a different function code, `ERANGE` is returned and the message is consumed.
- ///
- /// The read pointer is always advanced past the message, regardless of whether it matched.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
- /// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
- /// message queue.
- /// - `EINVAL` if the function code of the message was not recognized.
- /// - `ERANGE` if the message had a recognized but non-matching function code.
- ///
- /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
- where
- // This allows all error types, including `Infallible`, to be used for `M::InitError`.
- Error: From<M::InitError>,
- {
- let message = self.wait_for_msg(timeout)?;
- let function = message.header.function().map_err(|_| EINVAL)?;
-
- // Extract the message. Store the result as we want to advance the read pointer even in
- // case of failure.
- let result = if function == M::FUNCTION {
- let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
- let mut sbuffer = SBufferIter::new_reader([contents_1, message.contents.1]);
-
- M::read(cmd, &mut sbuffer)
- .map_err(|e| e.into())
- .inspect(|_| {
- if !sbuffer.is_empty() {
- dev_warn!(
- &self.dev,
- "GSP message {:?} has unprocessed data\n",
- function
- );
- }
- })
- } else {
- Err(ERANGE)
- };
-
- // Advance the read pointer past this message.
- self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
- message.header.length().div_ceil(GSP_PAGE_SIZE),
- )?);
+ self.gsp_mem.advance_cpu_read_ptr(elem_count);
result
}
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 7/9] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (5 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 6/9] gpu: nova-core: gsp: cmdq: split the transport " Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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 and receiving RPC commands
and messages into the `rpc` sub-module. The methods are still declared
as members of `CmdqInner` and `Cmdq`; only they are now in their own
sub-module.
This is a pure move commit, with no functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 343 +------------------
drivers/gpu/nova-core/gsp/cmdq/rpc.rs | 380 +++++++++++++++++++++
.../nova-core/gsp/cmdq/{ => rpc}/continuation.rs | 0
drivers/gpu/nova-core/gsp/commands.rs | 6 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 4 +-
5 files changed, 388 insertions(+), 345 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 07036972dbec..06386d1ab192 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,8 +36,6 @@
},
};
-use continuation::SplitState;
-
use pin_init::pin_init_scope;
use crate::{
@@ -44,10 +43,8 @@
gsp::{
fw::{
GspMsgElement,
- MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
- RpcMessageHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -77,124 +74,6 @@ trait CommandElement {
fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result;
}
-/// Trait implemented by types representing a command to send to the GSP.
-///
-/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
-/// a given command.
-///
-/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
-/// into the space reserved for it in the command queue buffer.
-///
-/// Some commands may be followed by a variable-length payload. For these, the
-/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
-/// defined as well.
-pub(crate) trait CommandToGsp {
- /// Function identifying this command to the GSP.
- const FUNCTION: MsgFunction;
-
- /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
- type Command: FromBytes + AsBytes;
-
- /// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
- /// have a reply.
- type Reply;
-
- /// Error type returned by [`CommandToGsp::init`].
- type InitError;
-
- /// In-place command initializer responsible for filling the command in the command queue
- /// buffer.
- fn init(&self) -> impl Init<Self::Command, Self::InitError>;
-
- /// Size of the variable-length payload following the command structure generated by
- /// [`CommandToGsp::init`].
- ///
- /// Most commands don't have a variable-length payload, so this is zero by default.
- fn variable_payload_len(&self) -> usize {
- 0
- }
-
- /// Method initializing the variable-length payload.
- ///
- /// The command buffer is circular, which means that we may need to jump back to its beginning
- /// while in the middle of a command. For this reason, the variable-length payload is
- /// initialized using a [`SBufferIter`].
- ///
- /// This method will receive a buffer of the length returned by
- /// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
- /// unwritten space will lead to an error.
- ///
- /// Most commands don't have a variable-length payload, so this does nothing by default.
- fn init_variable_payload(
- &self,
- _dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
- ) -> Result {
- Ok(())
- }
-
- /// Total size of the command (including its variable-length payload) without the
- /// [`GspMsgElement`] header.
- fn size(&self) -> usize {
- size_of::<Self::Command>() + self.variable_payload_len()
- }
-}
-
-/// Wrapper type for sending a RPC command as a command queue element.
-///
-/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket
-/// implementation as it would conflict with other future command types.
-struct RpcCommandElement<M>(M);
-
-impl<M> CommandElement for RpcCommandElement<M>
-where
- M: CommandToGsp,
- Error: From<M::InitError>,
-{
- fn size(&self) -> usize {
- self.0.size()
- }
-
- fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
- let command = &self.0;
- let size_in_bytes = command.size();
- // Extract area for the command itself. The GSP message header and the command header
- // together are guaranteed to fit entirely into a single page, so it's ok to only look
- // at `dst.contents.0` here.
- let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
- let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
- // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
- // initializer fails.
- unsafe {
- pin_init::raw_try_init(
- core::ptr::from_mut(dst.header.rpc_header_mut()),
- rpc_header_init,
- )?;
- }
- // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
- unsafe {
- pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
- }
-
- // Fill the variable-length payload, which may be empty.
- let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
- command.init_variable_payload(&mut sbuffer)?;
-
- if !sbuffer.is_empty() {
- return Err(EIO);
- }
-
- dev_dbg!(
- dev,
- "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
- seq,
- M::FUNCTION,
- size_in_bytes,
- );
-
- Ok(())
- }
-}
-
/// Trait implemented by types that can be received as single command queue elements.
///
/// The command queue validates the element header before calling `read()` to interpret the
@@ -204,117 +83,6 @@ trait MessageElement: Sized {
fn read(dev: &device::Device, element: GspMessage<'_>) -> Result<Self>;
}
-/// 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>;
-}
-
-/// Wrapper type for receiving a RPC message from a command queue element.
-///
-/// [`MessageElement`] cannot be directly implemented for all [`MessageFromGsp`] with a blanket
-/// implementation as it would conflict with other future message types.
-struct RpcMessageElement<M>(M);
-
-impl<M> RpcMessageElement<M>
-where
- M: MessageFromGsp,
-{
- /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
- /// to the RPC payload.
- ///
- /// # Errors
- ///
- /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
- fn parse_rpc_message<'a>(
- dev: &device::Device,
- element: GspMessage<'a>,
- ) -> Result<RpcMessage<'a>> {
- let GspMessage {
- header,
- contents: (slice_1, slice_2),
- } = element;
-
- let rpc_header = header.rpc_header();
- let payload_length = rpc_header.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(RpcMessage {
- header: rpc_header,
- contents: (slice_1, slice_2),
- })
- }
-}
-
-impl<M> MessageElement for RpcMessageElement<M>
-where
- M: MessageFromGsp,
- Error: From<M::InitError>,
-{
- fn read(dev: &device::Device, element: GspMessage<'_>) -> Result<Self> {
- let message = Self::parse_rpc_message(dev, element)?;
- let function = message.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(Self)
- .map_err(|e| e.into())
- .inspect(|_| {
- if !sbuffer.is_empty() {
- dev_warn!(dev, "GSP message {:?} has unprocessed data\n", function);
- }
- })
- }
-}
-
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
@@ -650,17 +418,6 @@ struct GspMessage<'a> {
contents: (&'a [u8], &'a [u8]),
}
-/// A RPC message ready to be processed from the message queue.
-///
-/// This is the type returned by [`CmdqInner::parse_rpc_message`].
-struct RpcMessage<'a> {
- // Reference to the RPC header of the message.
- header: &'a RpcMessageHeader,
- // Slices to the contents of the message. The second slice is zero unless the message loops
- // over the message queue.
- contents: (&'a [u8], &'a [u8]),
-}
-
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
@@ -731,72 +488,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));
}
-
- /// 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.consume_element::<RpcMessageElement<M::Reply>>(Self::RECEIVE_TIMEOUT) {
- Ok(reply) => break Ok(reply.0),
- 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::consume_element`] 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()
- .consume_element::<RpcMessageElement<M>>(timeout)
- .map(|m| m.0)
- }
}
/// Inner mutex protected state of [`Cmdq`].
@@ -857,36 +548,6 @@ fn send_command_element<E: CommandElement>(&mut self, command: E) -> Result {
Ok(())
}
- /// Sends `command` to the GSP.
- ///
- /// The command may be split into multiple messages if it is large.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if space does not become available within the timeout.
- /// - `EIO` if the variable payload requested by the command has not been entirely
- /// written to by its [`CommandToGsp::init_variable_payload`] method.
- ///
- /// Error codes returned by the command initializers are propagated as-is.
- fn send_command<M>(&mut self, command: M) -> Result
- where
- M: CommandToGsp,
- Error: From<M::InitError>,
- {
- match SplitState::new(command)? {
- SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)),
- SplitState::Split(command, mut continuations) => {
- self.send_command_element(RpcCommandElement(command))?;
-
- while let Some(continuation) = continuations.next() {
- self.send_command_element(RpcCommandElement(continuation))?;
- }
-
- Ok(())
- }
- }
- }
-
/// Wait for the next element to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
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..7ec0fed76637
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Support for the RPC message type on the GSP command queue.
+
+mod continuation;
+
+use kernel::{
+ device,
+ prelude::*,
+ time::Delta,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use continuation::SplitState;
+
+use crate::{
+ gsp::{
+ cmdq::{
+ GspCommand,
+ NoReply, //
+ },
+ fw::{
+ MsgFunction,
+ RpcMessageHeader, //
+ },
+ },
+ sbuffer::SBufferIter,
+};
+
+use super::{
+ Cmdq,
+ CmdqInner,
+ CommandElement,
+ GspMessage,
+ MessageElement, //
+};
+
+/// Trait implemented by types representing a command to send to the GSP.
+///
+/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
+/// a given command.
+///
+/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
+/// into the space reserved for it in the command queue buffer.
+///
+/// Some commands may be followed by a variable-length payload. For these, the
+/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
+/// defined as well.
+pub(crate) trait CommandToGsp {
+ /// Function identifying this command to the GSP.
+ const FUNCTION: MsgFunction;
+
+ /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
+ type Command: FromBytes + AsBytes;
+
+ /// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
+ /// have a reply.
+ type Reply;
+
+ /// Error type returned by [`CommandToGsp::init`].
+ type InitError;
+
+ /// In-place command initializer responsible for filling the command in the command queue
+ /// buffer.
+ fn init(&self) -> impl Init<Self::Command, Self::InitError>;
+
+ /// Size of the variable-length payload following the command structure generated by
+ /// [`CommandToGsp::init`].
+ ///
+ /// Most commands don't have a variable-length payload, so this is zero by default.
+ fn variable_payload_len(&self) -> usize {
+ 0
+ }
+
+ /// Method initializing the variable-length payload.
+ ///
+ /// The command buffer is circular, which means that we may need to jump back to its beginning
+ /// while in the middle of a command. For this reason, the variable-length payload is
+ /// initialized using a [`SBufferIter`].
+ ///
+ /// This method will receive a buffer of the length returned by
+ /// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
+ /// unwritten space will lead to an error.
+ ///
+ /// Most commands don't have a variable-length payload, so this does nothing by default.
+ fn init_variable_payload(
+ &self,
+ _dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
+ ) -> Result {
+ Ok(())
+ }
+
+ /// Total size of the command (including its variable-length payload) without the
+ /// [`GspMsgElement`] header.
+ fn size(&self) -> usize {
+ size_of::<Self::Command>() + self.variable_payload_len()
+ }
+}
+
+/// Wrapper type for sending a RPC command as a command queue element.
+///
+/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket
+/// implementation as it would conflict with other future command types.
+struct RpcCommandElement<M>(M);
+
+impl<M> CommandElement for RpcCommandElement<M>
+where
+ M: CommandToGsp,
+ Error: From<M::InitError>,
+{
+ fn size(&self) -> usize {
+ self.0.size()
+ }
+
+ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result {
+ let command = &self.0;
+ let size_in_bytes = command.size();
+ // Extract area for the command itself. The GSP message header and the command header
+ // together are guaranteed to fit entirely into a single page, so it's ok to only look
+ // at `dst.contents.0` here.
+ let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
+ let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION);
+ // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the
+ // initializer fails.
+ unsafe {
+ pin_init::raw_try_init(
+ core::ptr::from_mut(dst.header.rpc_header_mut()),
+ rpc_header_init,
+ )?;
+ }
+ // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails.
+ unsafe {
+ pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?;
+ }
+
+ // Fill the variable-length payload, which may be empty.
+ let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
+ command.init_variable_payload(&mut sbuffer)?;
+
+ if !sbuffer.is_empty() {
+ return Err(EIO);
+ }
+
+ dev_dbg!(
+ dev,
+ "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
+ seq,
+ M::FUNCTION,
+ size_in_bytes,
+ );
+
+ Ok(())
+ }
+}
+
+/// Trait representing messages received from the GSP.
+///
+/// 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>;
+}
+
+/// Wrapper type for receiving a RPC message from a command queue element.
+///
+/// [`MessageElement`] cannot be directly implemented for all [`MessageFromGsp`] with a blanket
+/// implementation as it would conflict with other future message types.
+struct RpcMessageElement<M>(M);
+
+impl<M> RpcMessageElement<M>
+where
+ M: MessageFromGsp,
+{
+ /// Validate the RPC layer of `element` and returns its RPC header and its contents trimmed down
+ /// to the RPC payload.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if the element is shorter than the payload length advertised by the RPC header.
+ fn parse_rpc_message<'a>(
+ dev: &device::Device,
+ element: GspMessage<'a>,
+ ) -> Result<RpcMessage<'a>> {
+ let GspMessage {
+ header,
+ contents: (slice_1, slice_2),
+ } = element;
+
+ let rpc_header = header.rpc_header();
+ let payload_length = rpc_header.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(RpcMessage {
+ header: rpc_header,
+ contents: (slice_1, slice_2),
+ })
+ }
+}
+
+impl<M> MessageElement for RpcMessageElement<M>
+where
+ M: MessageFromGsp,
+ Error: From<M::InitError>,
+{
+ fn read(dev: &device::Device, element: GspMessage<'_>) -> Result<Self> {
+ let message = Self::parse_rpc_message(dev, element)?;
+ let function = message.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(Self)
+ .map_err(|e| e.into())
+ .inspect(|_| {
+ if !sbuffer.is_empty() {
+ dev_warn!(dev, "GSP message {:?} has unprocessed data\n", function);
+ }
+ })
+ }
+}
+
+/// A RPC message ready to be processed from the message queue.
+///
+/// This is the type returned by [`CmdqInner::parse_rpc_message`].
+struct RpcMessage<'a> {
+ // Reference to the RPC header of the message.
+ header: &'a RpcMessageHeader,
+ // Slices to the contents of the message. The second slice is zero unless the message loops
+ // over the message queue.
+ contents: (&'a [u8], &'a [u8]),
+}
+
+impl CmdqInner<'_> {
+ /// Sends `command` to the GSP.
+ ///
+ /// The command may be split into multiple messages if it is large.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializers are propagated as-is.
+ fn send_command<M>(&mut self, command: M) -> Result
+ where
+ M: CommandToGsp,
+ Error: From<M::InitError>,
+ {
+ match SplitState::new(command)? {
+ SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)),
+ SplitState::Split(command, mut continuations) => {
+ self.send_command_element(RpcCommandElement(command))?;
+
+ while let Some(continuation) = continuations.next() {
+ self.send_command_element(RpcCommandElement(continuation))?;
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+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.consume_element::<RpcMessageElement<M::Reply>>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply.0),
+ 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::consume_element`] 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()
+ .consume_element::<RpcMessageElement<M>>(timeout)
+ .map(|m| m.0)
+ }
+}
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..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,
+ MessageFromGsp, //
+ },
Cmdq,
- CommandToGsp,
- MessageFromGsp,
NoReply, //
},
fw::{
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index 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] 10+ messages in thread
* [PATCH v3 8/9] gpu: nova-core: gsp: move the RPC commands into a sub-module
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (6 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 7/9] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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>
Reviewed-by: Eliot Courtney <ecourtney@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] 10+ messages in thread
* [PATCH v3 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
` (7 preceding siblings ...)
2026-09-30 14:55 ` [PATCH v3 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
@ 2026-09-30 14:55 ` Alexandre Courbot
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Courbot @ 2026-09-30 14:55 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>
Reviewed-by: Eliot Courtney <ecourtney@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 | 14 +++++++-------
drivers/gpu/nova-core/gsp/commands/rpc.rs | 2 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 2 +-
6 files changed, 14 insertions(+), 14 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 06386d1ab192..17cbed8277f3 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;
/// Trait implemented by types that can be sent as a single command queue element.
diff --git a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
index 7ec0fed76637..627616140162 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/rpc.rs
@@ -159,7 +159,7 @@ fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Res
/// 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;
@@ -291,7 +291,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_command<M>(&mut self, command: M) -> Result
+ fn send_rpc_command<M>(&mut self, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
@@ -328,7 +328,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,
@@ -336,7 +336,7 @@ 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.consume_element::<RpcMessageElement<M::Reply>>(Self::RECEIVE_TIMEOUT) {
@@ -356,18 +356,18 @@ 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::consume_element`] for details.
- pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ pub(crate) fn receive_rpc_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
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] 10+ messages in thread
end of thread, other threads:[~2026-09-30 14:58 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 14:55 [PATCH v3 0/9] gpu: nova-core: gsp: prepare the command queue for r000 dual-message types Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 1/9] gpu: nova-core: gsp: cmdq: validate checksum earlier on receive Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 2/9] gpu: nova-core: gsp: introduce and use proper RpcMessageHeader type Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 3/9] gpu: nova-core: gsp: cmdq: group the RPC-specific part of send_single_command Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 4/9] gpu: nova-core: gsp: cmdq: split the transport part of the send path Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 5/9] gpu: nova-core: gsp: cmdq: split RPC parsing part of the receive path Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 6/9] gpu: nova-core: gsp: cmdq: split the transport " Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 7/9] gpu: nova-core: gsp: cmdq: move the RPC code into a sub-module Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 8/9] gpu: nova-core: gsp: move the RPC commands " Alexandre Courbot
2026-09-30 14:55 ` [PATCH v3 9/9] gpu: nova-core: gsp: add `rpc` to RPC message send/receive methods Alexandre Courbot
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®