* [PATCH v4 1/5] gpu: nova-core: gsp: fix stale doc comments on command queue methods
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
@ 2026-03-10 8:09 ` Eliot Courtney
2026-03-11 3:32 ` Claude review: " Claude Code Review Bot
2026-03-10 8:09 ` [PATCH v4 2/5] gpu: nova-core: gsp: add `RECEIVE_TIMEOUT` constant for command queue Eliot Courtney
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-10 8:09 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Fix some inaccuracies / old doc comments.
Reviewed-by: Zhi Wang <zhiw@nvidia.com>
Tested-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index e0b096546d23..cbdc76a33a54 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -531,6 +531,7 @@ fn notify_gsp(bar: &Bar0) {
///
/// # 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.
@@ -711,22 +712,20 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
/// Receive a message from the GSP.
///
- /// `init` is a closure tasked with processing the message. It receives a reference to the
- /// message in the message queue, and a [`SBufferIter`] pointing to its variable-length
- /// payload, if any.
+ /// 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 expected message is specified using the `M` generic parameter. If the pending message
- /// is different, `EAGAIN` is returned and the unexpected message is dropped.
- ///
- /// This design is by no means final, but it is simple and will let us go through GSP
- /// initialization.
+ /// 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 of the message was unrecognized.
+ /// - `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.
pub(crate) 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`.
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: fix stale doc comments on command queue methods
2026-03-10 8:09 ` [PATCH v4 1/5] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
Clean documentation-only patch. Updates doc comments on `send_command` and `receive_msg` to match current behavior.
The change from `EAGAIN` to `ERANGE` in the doc comments for non-matching messages aligns with what appears to be an earlier refactoring:
```rust
+ /// - `ERANGE` if the message had a recognized but non-matching function code.
```
And the addition of the `EMSGSIZE` error documentation for `send_command`:
```rust
+ /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
```
This is a good cleanup. Note that the existing tree's `send_command` doc mentions `EAGAIN` for insufficient space (line 487 of the current tree), while this patch's context shows `ETIMEDOUT` -- this implies the prerequisite series already changed the space-waiting behavior to use a polling timeout, which is consistent.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 2/5] gpu: nova-core: gsp: add `RECEIVE_TIMEOUT` constant for command queue
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
2026-03-10 8:09 ` [PATCH v4 1/5] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-03-10 8:09 ` Eliot Courtney
2026-03-11 3:32 ` Claude review: " Claude Code Review Bot
2026-03-10 8:09 ` [PATCH v4 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp` Eliot Courtney
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-10 8:09 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Remove magic numbers and add a default timeout for callers to use.
Tested-by: Zhi Wang <zhiw@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 3 +++
drivers/gpu/nova-core/gsp/commands.rs | 5 ++---
drivers/gpu/nova-core/gsp/sequencer.rs | 2 +-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index cbdc76a33a54..08ac3067f1b5 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -496,6 +496,9 @@ impl Cmdq {
/// Timeout for waiting for space on the command queue.
const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1);
+ /// Default timeout for receiving a message from the GSP.
+ pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
+
/// Creates a new command queue for `dev`.
pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> {
let gsp_mem = DmaGspMem::new(dev)?;
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 8f270eca33be..88df117ba575 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -11,7 +11,6 @@
device,
pci,
prelude::*,
- time::Delta,
transmute::{
AsBytes,
FromBytes, //
@@ -165,7 +164,7 @@ fn read(
/// Waits for GSP initialization to complete.
pub(crate) fn wait_gsp_init_done(cmdq: &mut Cmdq) -> Result {
loop {
- match cmdq.receive_msg::<GspInitDone>(Delta::from_secs(10)) {
+ match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
Ok(_) => break Ok(()),
Err(ERANGE) => continue,
Err(e) => break Err(e),
@@ -235,7 +234,7 @@ pub(crate) fn get_gsp_info(cmdq: &mut Cmdq, bar: &Bar0) -> Result<GetGspStaticIn
cmdq.send_command(bar, GetGspStaticInfo)?;
loop {
- match cmdq.receive_msg::<GetGspStaticInfoReply>(Delta::from_secs(5)) {
+ match cmdq.receive_msg::<GetGspStaticInfoReply>(Cmdq::RECEIVE_TIMEOUT) {
Ok(info) => return Ok(info),
Err(ERANGE) => continue,
Err(e) => return Err(e),
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index 0cfbedc47fcf..ce2b3bb05d22 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -358,7 +358,7 @@ pub(crate) struct GspSequencerParams<'a> {
impl<'a> GspSequencer<'a> {
pub(crate) fn run(cmdq: &mut Cmdq, params: GspSequencerParams<'a>) -> Result {
let seq_info = loop {
- match cmdq.receive_msg::<GspSequence>(Delta::from_secs(10)) {
+ match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
Ok(seq_info) => break seq_info,
Err(ERANGE) => continue,
Err(e) => return Err(e),
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: add `RECEIVE_TIMEOUT` constant for command queue
2026-03-10 8:09 ` [PATCH v4 2/5] gpu: nova-core: gsp: add `RECEIVE_TIMEOUT` constant for command queue Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
Straightforward extraction of magic timeout values into a named constant:
```rust
+ pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
```
The previous code used `Delta::from_secs(10)` in two places and `Delta::from_secs(5)` in one place. This patch unifies all three to 5 seconds (per the v4 changelog). The `wait_gsp_init_done` and `GspSequencer::run` callers were previously using 10s timeouts; reducing them to 5s is a behavioral change. Given the cover letter mentions GSP is expected to be fast and v4 explicitly changed this from 10s to 5s, this seems intentional and reasonable.
The visibility `pub(super)` is appropriate since it's used across sibling modules within `gsp/`.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp`
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
2026-03-10 8:09 ` [PATCH v4 1/5] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
2026-03-10 8:09 ` [PATCH v4 2/5] gpu: nova-core: gsp: add `RECEIVE_TIMEOUT` constant for command queue Eliot Courtney
@ 2026-03-10 8:09 ` Eliot Courtney
2026-03-11 3:32 ` Claude review: " Claude Code Review Bot
2026-03-10 8:09 ` [PATCH v4 4/5] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-10 8:09 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Add type infrastructure to know what reply is expected from each
`CommandToGsp`. Uses a marker type `NoReply` which does not implement
`MessageFromGsp` to mark commands which don't expect a response.
Update `send_command` to wait for a reply and add `send_command_no_wait`
which sends a command that has no reply, without blocking.
This prepares for adding locking to the queue.
Tested-by: Zhi Wang <zhiw@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 5 ++-
drivers/gpu/nova-core/gsp/cmdq.rs | 59 ++++++++++++++++++++++++--
drivers/gpu/nova-core/gsp/cmdq/continuation.rs | 8 +++-
drivers/gpu/nova-core/gsp/commands.rs | 16 +++----
4 files changed, 72 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index c56029f444cb..991eb5957e3d 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -160,8 +160,9 @@ pub(crate) fn boot(
dma_write!(wpr_meta[0] = GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
self.cmdq
- .send_command(bar, commands::SetSystemInfo::new(pdev))?;
- self.cmdq.send_command(bar, commands::SetRegistry::new())?;
+ .send_command_no_wait(bar, commands::SetSystemInfo::new(pdev))?;
+ self.cmdq
+ .send_command_no_wait(bar, commands::SetRegistry::new())?;
gsp_falcon.reset(bar)?;
let libos_handle = self.libos.dma_handle();
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 08ac3067f1b5..b631daae5642 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -51,10 +51,14 @@
sbuffer::SBufferIter, //
};
+/// 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`].
+pub(crate) struct NoReply;
+
/// Trait implemented by types representing a command to send to the GSP.
///
-/// The main purpose of this trait is to provide [`Cmdq::send_command`] with the information it
-/// needs to send a given command.
+/// 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.
@@ -69,6 +73,10 @@ pub(crate) trait CommandToGsp {
/// 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;
@@ -610,7 +618,7 @@ fn send_single_command<M>(&mut self, bar: &Bar0, command: M) -> Result
/// 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<M>(&mut self, bar: &Bar0, command: M) -> Result
+ fn send_command_internal<M>(&mut self, bar: &Bar0, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
@@ -630,6 +638,51 @@ pub(crate) fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result
}
}
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// # 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>(&mut self, bar: &Bar0, command: M) -> Result<M::Reply>
+ where
+ M: CommandToGsp,
+ M::Reply: MessageFromGsp,
+ Error: From<M::InitError>,
+ Error: From<<M::Reply as MessageFromGsp>::InitError>,
+ {
+ self.send_command_internal(bar, command)?;
+
+ loop {
+ match self.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
+
+ /// Sends `command` to the GSP without waiting for a reply.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializers are propagated as-is.
+ pub(crate) fn send_command_no_wait<M>(&mut self, bar: &Bar0, command: M) -> Result
+ where
+ M: CommandToGsp<Reply = NoReply>,
+ Error: From<M::InitError>,
+ {
+ self.send_command_internal(bar, command)
+ }
+
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
diff --git a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
index 2aa17caac2e0..05e904f18097 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
@@ -6,7 +6,10 @@
use kernel::prelude::*;
-use super::CommandToGsp;
+use super::{
+ CommandToGsp,
+ NoReply, //
+};
use crate::{
gsp::fw::{
@@ -63,6 +66,7 @@ fn new(data: &'a [u8]) -> Self {
impl<'a> CommandToGsp for ContinuationRecord<'a> {
const FUNCTION: MsgFunction = MsgFunction::ContinuationRecord;
type Command = ();
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -144,6 +148,7 @@ fn new(command: C, payload: KVVec<u8>) -> Self {
impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> {
const FUNCTION: MsgFunction = C::FUNCTION;
type Command = C::Command;
+ type Reply = C::Reply;
type InitError = C::InitError;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -206,6 +211,7 @@ fn new(len: usize) -> Result<Self> {
impl CommandToGsp for TestPayload {
const FUNCTION: MsgFunction = MsgFunction::Nop;
type Command = TestHeader;
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 88df117ba575..77054c92fcc2 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -23,7 +23,8 @@
cmdq::{
Cmdq,
CommandToGsp,
- MessageFromGsp, //
+ MessageFromGsp,
+ NoReply, //
},
fw::{
commands::*,
@@ -48,6 +49,7 @@ pub(crate) fn new(pdev: &'a pci::Device<device::Bound>) -> Self {
impl<'a> CommandToGsp for SetSystemInfo<'a> {
const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
type Command = GspSetSystemInfo;
+ type Reply = NoReply;
type InitError = Error;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -99,6 +101,7 @@ pub(crate) fn new() -> Self {
impl CommandToGsp for SetRegistry {
const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
type Command = PackedRegistryTable;
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -178,6 +181,7 @@ pub(crate) fn wait_gsp_init_done(cmdq: &mut Cmdq) -> Result {
impl CommandToGsp for GetGspStaticInfo {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
type Command = GspStaticConfigInfo;
+ type Reply = GetGspStaticInfoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -231,13 +235,5 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
/// Send the [`GetGspInfo`] command and awaits for its reply.
pub(crate) fn get_gsp_info(cmdq: &mut Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
- cmdq.send_command(bar, GetGspStaticInfo)?;
-
- loop {
- match cmdq.receive_msg::<GetGspStaticInfoReply>(Cmdq::RECEIVE_TIMEOUT) {
- Ok(info) => return Ok(info),
- Err(ERANGE) => continue,
- Err(e) => return Err(e),
- }
- }
+ cmdq.send_command(bar, GetGspStaticInfo)
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp`
2026-03-10 8:09 ` [PATCH v4 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp` Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This is the key design patch. It introduces:
1. A `NoReply` marker type for commands with no expected response
2. An associated `type Reply` on `CommandToGsp`
3. Splits the public API into `send_command` (waits for reply) and `send_command_no_wait` (fire-and-forget)
The type-level enforcement is well done:
```rust
+ pub(crate) fn send_command_no_wait<M>(&mut self, bar: &Bar0, command: M) -> Result
+ where
+ M: CommandToGsp<Reply = NoReply>,
```
This constrains `send_command_no_wait` to only accept commands whose `Reply` is `NoReply`, which prevents accidentally forgetting to wait for a reply.
The `send_command` method absorbs the receive loop pattern that was previously duplicated in `get_gsp_info`:
```rust
+ loop {
+ match self.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
```
This simplifies `get_gsp_info` to a single line, which is nice. The `SplitCommand` wrapper correctly propagates `type Reply = C::Reply`, and `ContinuationRecord` correctly uses `NoReply`.
One observation: the `wait_gsp_init_done` function still has its own manual receive loop in `commands.rs` since `GspInitDone` isn't triggered by a specific command -- it's an unsolicited notification. This is correct; it cannot use `send_command`.
The `// ` trailing comment on the import formatting (e.g., `NoReply, //`) is the `rustfmt` skip trick to force vertical formatting -- standard practice in the kernel Rust code.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 4/5] gpu: nova-core: gsp: make `Cmdq` a pinned type
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
` (2 preceding siblings ...)
2026-03-10 8:09 ` [PATCH v4 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp` Eliot Courtney
@ 2026-03-10 8:09 ` Eliot Courtney
2026-03-11 3:32 ` Claude review: " Claude Code Review Bot
2026-03-10 8:09 ` [PATCH v4 5/5] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
2026-03-11 3:32 ` Claude review: gpu: nova-core: gsp: add " Claude Code Review Bot
5 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-10 8:09 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Make `Cmdq` a pinned type. This is needed to use Mutex, which is needed
to add locking to `Cmdq`.
Reviewed-by: Zhi Wang <zhiw@nvidia.com>
Tested-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 5 +++--
drivers/gpu/nova-core/gsp/cmdq.rs | 9 ++++-----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 174feaca0a6b..a6f3918c20b1 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -112,6 +112,7 @@ pub(crate) struct Gsp {
/// RM log buffer.
logrm: LogBuffer,
/// Command queue.
+ #[pin]
pub(crate) cmdq: Cmdq,
/// RM arguments.
rmargs: CoherentAllocation<GspArgumentsPadded>,
@@ -132,7 +133,7 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
loginit: LogBuffer::new(dev)?,
logintr: LogBuffer::new(dev)?,
logrm: LogBuffer::new(dev)?,
- cmdq: Cmdq::new(dev)?,
+ cmdq <- Cmdq::new(dev),
rmargs: CoherentAllocation::<GspArgumentsPadded>::alloc_coherent(
dev,
1,
@@ -149,7 +150,7 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
libos[1] = LibosMemoryRegionInitArgument::new("LOGINTR", &logintr.0)
)?;
dma_write!(libos[2] = LibosMemoryRegionInitArgument::new("LOGRM", &logrm.0))?;
- dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(cmdq))?;
+ dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(&cmdq))?;
dma_write!(libos[3] = LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
},
}))
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index b631daae5642..90179256a929 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -475,6 +475,7 @@ struct GspMessage<'a> {
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
/// area.
+#[pin_data]
pub(crate) struct Cmdq {
/// Device this command queue belongs to.
dev: ARef<device::Device>,
@@ -508,13 +509,11 @@ impl Cmdq {
pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
/// Creates a new command queue for `dev`.
- pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> {
- let gsp_mem = DmaGspMem::new(dev)?;
-
- Ok(Cmdq {
+ pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
+ try_pin_init!(Self {
+ gsp_mem: DmaGspMem::new(dev)?,
dev: dev.into(),
seq: 0,
- gsp_mem,
})
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: make `Cmdq` a pinned type
2026-03-10 8:09 ` [PATCH v4 4/5] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
Mechanical conversion of `Cmdq` from a regular type to a pinned type using `#[pin_data]` and `try_pin_init!`:
```rust
+ pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
+ try_pin_init!(Self {
+ gsp_mem: DmaGspMem::new(dev)?,
dev: dev.into(),
seq: 0,
- gsp_mem,
})
```
The `Gsp` struct update correctly uses the `<-` pin-init operator:
```rust
- cmdq: Cmdq::new(dev)?,
+ cmdq <- Cmdq::new(dev),
```
And the `GspArgumentsCached::new` call changes from passing `cmdq` (which was previously `Cmdq` by value-ish) to `&cmdq`:
```rust
- dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(cmdq))?;
+ dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(&cmdq))?;
```
This is clean and correct preparation for the mutex in the next patch.
No issues.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 5/5] gpu: nova-core: gsp: add mutex locking to Cmdq
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
` (3 preceding siblings ...)
2026-03-10 8:09 ` [PATCH v4 4/5] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-03-10 8:09 ` Eliot Courtney
2026-03-11 3:32 ` Claude review: " Claude Code Review Bot
2026-03-11 3:32 ` Claude review: gpu: nova-core: gsp: add " Claude Code Review Bot
5 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-10 8:09 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Wrap `Cmdq`'s mutable state in a new struct `CmdqInner` and wrap that in
a Mutex. This lets `Cmdq` methods take &self instead of &mut self, which
lets required commands be sent e.g. while unloading the driver.
The mutex is held over both send and receive in `send_command` to make
sure that it doesn't get the reply of some other command that could have
been sent just beforehand.
Reviewed-by: Zhi Wang <zhiw@nvidia.com>
Tested-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 8 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 165 ++++++++++++++++++++-------------
drivers/gpu/nova-core/gsp/commands.rs | 4 +-
drivers/gpu/nova-core/gsp/sequencer.rs | 2 +-
4 files changed, 105 insertions(+), 74 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 991eb5957e3d..bc53e667cd9e 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -128,7 +128,7 @@ fn run_fwsec_frts(
///
/// Upon return, the GSP is up and running, and its runtime object given as return value.
pub(crate) fn boot(
- mut self: Pin<&mut Self>,
+ self: Pin<&mut Self>,
pdev: &pci::Device<device::Bound>,
bar: &Bar0,
chipset: Chipset,
@@ -214,13 +214,13 @@ pub(crate) fn boot(
dev: pdev.as_ref().into(),
bar,
};
- GspSequencer::run(&mut self.cmdq, seq_params)?;
+ GspSequencer::run(&self.cmdq, seq_params)?;
// Wait until GSP is fully initialized.
- commands::wait_gsp_init_done(&mut self.cmdq)?;
+ commands::wait_gsp_init_done(&self.cmdq)?;
// Obtain and display basic GPU information.
- let info = commands::get_gsp_info(&mut self.cmdq, bar)?;
+ let info = commands::get_gsp_info(&self.cmdq, bar)?;
match info.gpu_name() {
Ok(name) => dev_info!(pdev, "GPU name: {}\n", name),
Err(e) => dev_warn!(pdev, "GPU name unavailable: {:?}\n", e),
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 90179256a929..47406d494523 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -18,8 +18,12 @@
},
dma_write,
io::poll::read_poll_timeout,
+ new_mutex,
prelude::*,
- sync::aref::ARef,
+ sync::{
+ aref::ARef,
+ Mutex, //
+ },
time::Delta,
transmute::{
AsBytes,
@@ -477,12 +481,9 @@ struct GspMessage<'a> {
/// area.
#[pin_data]
pub(crate) struct Cmdq {
- /// Device this command queue belongs to.
- dev: ARef<device::Device>,
- /// Current command sequence number.
- seq: u32,
- /// Memory area shared with the GSP for communicating commands and messages.
- gsp_mem: DmaGspMem,
+ /// Inner mutex-protected state.
+ #[pin]
+ inner: Mutex<CmdqInner>,
}
impl Cmdq {
@@ -502,18 +503,17 @@ impl Cmdq {
/// Number of page table entries for the GSP shared region.
pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
- /// Timeout for waiting for space on the command queue.
- const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1);
-
/// Default timeout for receiving a message from the GSP.
pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
/// Creates a new command queue for `dev`.
pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
try_pin_init!(Self {
- gsp_mem: DmaGspMem::new(dev)?,
- dev: dev.into(),
- seq: 0,
+ inner <- new_mutex!(CmdqInner {
+ dev: dev.into(),
+ gsp_mem: DmaGspMem::new(dev)?,
+ seq: 0,
+ }),
})
}
@@ -537,6 +537,87 @@ fn notify_gsp(bar: &Bar0) {
.write(bar);
}
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// The mutex is held for the entire send+receive cycle to ensure that no other command can
+ /// be interleaved. Messages with non-matching function codes are silently consumed until the
+ /// expected reply arrives.
+ ///
+ /// # 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, bar: &Bar0, 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(bar, command)?;
+
+ loop {
+ match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
+
+ /// Sends `command` to the GSP without waiting for a reply.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializers are propagated as-is.
+ pub(crate) fn send_command_no_wait<M>(&self, bar: &Bar0, command: M) -> Result
+ where
+ M: CommandToGsp<Reply = NoReply>,
+ Error: From<M::InitError>,
+ {
+ self.inner.lock().send_command(bar, command)
+ }
+
+ /// Receive a message from the GSP.
+ ///
+ /// See [`CmdqInner::receive_msg`] for details.
+ pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ where
+ // This allows all error types, including `Infallible`, to be used for `M::InitError`.
+ Error: From<M::InitError>,
+ {
+ self.inner.lock().receive_msg(timeout)
+ }
+
+ /// Returns the DMA handle of the command queue's shared memory region.
+ pub(crate) fn dma_handle(&self) -> DmaAddress {
+ self.inner.lock().gsp_mem.0.dma_handle()
+ }
+}
+
+/// Inner mutex protected state of [`Cmdq`].
+struct CmdqInner {
+ /// Device this command queue belongs to.
+ dev: ARef<device::Device>,
+ /// Current command sequence number.
+ seq: u32,
+ /// Memory area shared with the GSP for communicating commands and messages.
+ gsp_mem: DmaGspMem,
+}
+
+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.
///
/// # Errors
@@ -617,7 +698,7 @@ fn send_single_command<M>(&mut self, bar: &Bar0, command: M) -> Result
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
- fn send_command_internal<M>(&mut self, bar: &Bar0, command: M) -> Result
+ fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
@@ -637,51 +718,6 @@ fn send_command_internal<M>(&mut self, bar: &Bar0, command: M) -> Result
}
}
- /// Sends `command` to the GSP and waits for the reply.
- ///
- /// # 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>(&mut self, bar: &Bar0, command: M) -> Result<M::Reply>
- where
- M: CommandToGsp,
- M::Reply: MessageFromGsp,
- Error: From<M::InitError>,
- Error: From<<M::Reply as MessageFromGsp>::InitError>,
- {
- self.send_command_internal(bar, command)?;
-
- loop {
- match self.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
- Ok(reply) => break Ok(reply),
- Err(ERANGE) => continue,
- Err(e) => break Err(e),
- }
- }
- }
-
- /// Sends `command` to the GSP without waiting for a reply.
- ///
- /// # Errors
- ///
- /// - `ETIMEDOUT` if space does not become available within the timeout.
- /// - `EIO` if the variable payload requested by the command has not been entirely
- /// written to by its [`CommandToGsp::init_variable_payload`] method.
- ///
- /// Error codes returned by the command initializers are propagated as-is.
- pub(crate) fn send_command_no_wait<M>(&mut self, bar: &Bar0, command: M) -> Result
- where
- M: CommandToGsp<Reply = NoReply>,
- Error: From<M::InitError>,
- {
- self.send_command_internal(bar, command)
- }
-
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
@@ -717,7 +753,7 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
let (header, slice_1) = GspMsgElement::from_bytes_prefix(slice_1).ok_or(EIO)?;
dev_dbg!(
- self.dev,
+ &self.dev,
"GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
header.sequence(),
header.function(),
@@ -752,7 +788,7 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
])) != 0
{
dev_err!(
- self.dev,
+ &self.dev,
"GSP RPC: receive: Call {} - bad checksum\n",
header.sequence()
);
@@ -781,7 +817,7 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
/// - `ERANGE` if the message had a recognized but non-matching function code.
///
/// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
- pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
+ 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>,
@@ -817,9 +853,4 @@ pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Resul
result
}
-
- /// Returns the DMA handle of the command queue's shared memory region.
- pub(crate) fn dma_handle(&self) -> DmaAddress {
- self.gsp_mem.0.dma_handle()
- }
}
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 77054c92fcc2..c89c7b57a751 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -165,7 +165,7 @@ fn read(
}
/// Waits for GSP initialization to complete.
-pub(crate) fn wait_gsp_init_done(cmdq: &mut Cmdq) -> Result {
+pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
loop {
match cmdq.receive_msg::<GspInitDone>(Cmdq::RECEIVE_TIMEOUT) {
Ok(_) => break Ok(()),
@@ -234,6 +234,6 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
}
/// Send the [`GetGspInfo`] command and awaits for its reply.
-pub(crate) fn get_gsp_info(cmdq: &mut Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
+pub(crate) fn get_gsp_info(cmdq: &Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
cmdq.send_command(bar, GetGspStaticInfo)
}
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index ce2b3bb05d22..474e4c8021db 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -356,7 +356,7 @@ pub(crate) struct GspSequencerParams<'a> {
}
impl<'a> GspSequencer<'a> {
- pub(crate) fn run(cmdq: &mut Cmdq, params: GspSequencerParams<'a>) -> Result {
+ pub(crate) fn run(cmdq: &Cmdq, params: GspSequencerParams<'a>) -> Result {
let seq_info = loop {
match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
Ok(seq_info) => break seq_info,
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: add mutex locking to Cmdq
2026-03-10 8:09 ` [PATCH v4 5/5] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This is the main event. Key design decisions:
1. **All mutable state moved to `CmdqInner`** wrapped in `Mutex<CmdqInner>`:
```rust
pub(crate) struct Cmdq {
+ #[pin]
+ inner: Mutex<CmdqInner>,
}
```
2. **`Cmdq` methods now take `&self`** instead of `&mut self`, enabling shared access from multiple threads.
3. **The mutex is held across send+receive in `send_command`**:
```rust
+ let mut inner = self.inner.lock();
+ inner.send_command(bar, command)?;
+ loop {
+ match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
```
This is correct for ensuring reply ordering -- no other command can slip in between send and receive.
4. **`dma_handle()` now acquires the mutex**:
```rust
+ pub(crate) fn dma_handle(&self) -> DmaAddress {
+ self.inner.lock().gsp_mem.0.dma_handle()
+ }
```
This is called during init from `MessageQueueInitArguments::new()`. Since `dma_handle()` returns an immutable property (the DMA address doesn't change after allocation), acquiring the mutex here is unnecessary overhead but harmless. If this becomes a hot path in the future, it could be worth storing the DMA handle separately outside the mutex, but for now this is fine.
5. **`boot()` signature change** drops `mut` from `Pin<&mut Self>`:
```rust
- mut self: Pin<&mut Self>,
+ self: Pin<&mut Self>,
```
This is correct since `cmdq` methods no longer need `&mut self`.
6. **Potential concern -- holding mutex during `receive_msg` timeout**: The `receive_msg` call inside `send_command` can block for up to `RECEIVE_TIMEOUT` (5 seconds) while holding the mutex lock. During this time, no other thread can send commands or receive messages. The cover letter acknowledges this: "For now this should be ok, and we expect GSP to be fast anyway." This is acceptable for the current use case but worth noting for future scaling.
7. **The `receive_msg` public wrapper also acquires the lock**:
```rust
+ pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ ...
+ self.inner.lock().receive_msg(timeout)
```
This is used by `wait_gsp_init_done` and `GspSequencer::run`, which call `receive_msg` in loops. Each iteration will acquire and release the lock, which is fine -- it allows other threads to interleave between iterations.
The `ALLOCATE_TIMEOUT` constant correctly moved from `Cmdq` to `CmdqInner` since it's only used by `CmdqInner::send_single_command`.
The `dev_dbg!`/`dev_err!` changes from `self.dev` to `&self.dev` are because `dev` is now `ARef<device::Device>` inside `CmdqInner` and the macro needs a reference.
No blocking issues. The series is clean and well-decomposed.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* Claude review: gpu: nova-core: gsp: add locking to Cmdq
2026-03-10 8:09 [PATCH v4 0/5] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
` (4 preceding siblings ...)
2026-03-10 8:09 ` [PATCH v4 5/5] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
@ 2026-03-11 3:32 ` Claude Code Review Bot
5 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-11 3:32 UTC (permalink / raw)
To: dri-devel-reviews
Overall Series Review
Subject: gpu: nova-core: gsp: add locking to Cmdq
Author: Eliot Courtney <ecourtney@nvidia.com>
Patches: 6
Reviewed: 2026-03-11T13:32:09.489388
---
This is a well-structured 5-patch series that incrementally adds mutex locking to the `Cmdq` (command queue) in the nova-core GSP driver. The series follows a clean decomposition: (1) fix doc comments, (2) extract a timeout constant, (3) add reply/no-reply type infrastructure, (4) make `Cmdq` pinned, (5) add the mutex. The progression is logical, each patch builds on the prior ones, and the final result is clean.
The series is already at v4 and has Reviewed-by/Tested-by tags from Zhi Wang and Gary Guo on several patches. The approach of using an associated `Reply` type on `CommandToGsp` to distinguish reply-expecting vs fire-and-forget commands is a good Rust-idiomatic design choice. The locking strategy of holding the mutex over both send and receive in `send_command` is correct for ensuring reply ordering.
Overall this looks ready to merge with only minor observations below.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp`
2026-03-04 2:46 [PATCH v3 0/5] " Eliot Courtney
@ 2026-03-04 2:46 ` Eliot Courtney
2026-03-05 3:52 ` Claude review: " Claude Code Review Bot
0 siblings, 1 reply; 13+ messages in thread
From: Eliot Courtney @ 2026-03-04 2:46 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
Simona Vetter, Benno Lossin, Gary Guo
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
Zhi Wang
Add type infrastructure to know what reply is expected from each
`CommandToGsp`. Uses a marker type `NoReply` which does not implement
`MessageFromGsp` to mark commands which don't expect a response.
Update `send_command` to wait for a reply and add `send_command_no_wait`
which sends a command that has no reply, without blocking.
This prepares for adding locking to the queue.
Tested-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 5 ++-
drivers/gpu/nova-core/gsp/cmdq.rs | 55 +++++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/cmdq/continuation.rs | 5 ++-
drivers/gpu/nova-core/gsp/commands.rs | 16 +++-----
4 files changed, 67 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index c56029f444cb..991eb5957e3d 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -160,8 +160,9 @@ pub(crate) fn boot(
dma_write!(wpr_meta[0] = GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
self.cmdq
- .send_command(bar, commands::SetSystemInfo::new(pdev))?;
- self.cmdq.send_command(bar, commands::SetRegistry::new())?;
+ .send_command_no_wait(bar, commands::SetSystemInfo::new(pdev))?;
+ self.cmdq
+ .send_command_no_wait(bar, commands::SetRegistry::new())?;
gsp_falcon.reset(bar)?;
let libos_handle = self.libos.dma_handle();
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 0192c85ddd75..7750f5792b21 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -51,6 +51,10 @@
sbuffer::SBufferIter, //
};
+/// Marker type representing the absence of a reply for a command. This does not implement
+/// `MessageFromGsp`.
+pub(crate) struct NoReply;
+
/// Trait implemented by types representing a command to send to the GSP.
///
/// The main purpose of this trait is to provide [`Cmdq::send_command`] with the information it
@@ -69,6 +73,10 @@ pub(crate) trait CommandToGsp {
/// 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;
@@ -610,7 +618,7 @@ fn send_single_command<M>(&mut self, bar: &Bar0, command: M) -> Result
/// 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<M>(&mut self, bar: &Bar0, command: M) -> Result
+ fn send_command_internal<M>(&mut self, bar: &Bar0, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
@@ -630,6 +638,51 @@ pub(crate) fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result
}
}
+ /// Sends `command` to the GSP and waits for the reply.
+ ///
+ /// # 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>(&mut self, bar: &Bar0, command: M) -> Result<M::Reply>
+ where
+ M: CommandToGsp,
+ M::Reply: MessageFromGsp,
+ Error: From<M::InitError>,
+ Error: From<<M::Reply as MessageFromGsp>::InitError>,
+ {
+ self.send_command_internal(bar, command)?;
+
+ loop {
+ match self.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
+ Ok(reply) => break Ok(reply),
+ Err(ERANGE) => continue,
+ Err(e) => break Err(e),
+ }
+ }
+ }
+
+ /// Sends `command` to the GSP without waiting for a reply.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the variable payload requested by the command has not been entirely
+ /// written to by its [`CommandToGsp::init_variable_payload`] method.
+ ///
+ /// Error codes returned by the command initializers are propagated as-is.
+ pub(crate) fn send_command_no_wait<M>(&mut self, bar: &Bar0, command: M) -> Result
+ where
+ M: CommandToGsp<Reply = NoReply>,
+ Error: From<M::InitError>,
+ {
+ self.send_command_internal(bar, command)
+ }
+
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
diff --git a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
index 637942917237..8a6bb8fa7e60 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
@@ -6,7 +6,7 @@
use kernel::prelude::*;
-use super::CommandToGsp;
+use super::{CommandToGsp, NoReply};
use crate::{
gsp::fw::{
@@ -63,6 +63,7 @@ fn new(data: &'a [u8]) -> Self {
impl<'a> CommandToGsp for ContinuationRecord<'a> {
const FUNCTION: MsgFunction = MsgFunction::ContinuationRecord;
type Command = ();
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -144,6 +145,7 @@ fn new(command: C, payload: KVVec<u8>) -> Self {
impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> {
const FUNCTION: MsgFunction = C::FUNCTION;
type Command = C::Command;
+ type Reply = C::Reply;
type InitError = C::InitError;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -206,6 +208,7 @@ fn new(len: usize) -> Result<Self> {
impl CommandToGsp for TestPayload {
const FUNCTION: MsgFunction = MsgFunction::Nop;
type Command = TestHeader;
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 88df117ba575..77054c92fcc2 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -23,7 +23,8 @@
cmdq::{
Cmdq,
CommandToGsp,
- MessageFromGsp, //
+ MessageFromGsp,
+ NoReply, //
},
fw::{
commands::*,
@@ -48,6 +49,7 @@ pub(crate) fn new(pdev: &'a pci::Device<device::Bound>) -> Self {
impl<'a> CommandToGsp for SetSystemInfo<'a> {
const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
type Command = GspSetSystemInfo;
+ type Reply = NoReply;
type InitError = Error;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -99,6 +101,7 @@ pub(crate) fn new() -> Self {
impl CommandToGsp for SetRegistry {
const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
type Command = PackedRegistryTable;
+ type Reply = NoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -178,6 +181,7 @@ pub(crate) fn wait_gsp_init_done(cmdq: &mut Cmdq) -> Result {
impl CommandToGsp for GetGspStaticInfo {
const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
type Command = GspStaticConfigInfo;
+ type Reply = GetGspStaticInfoReply;
type InitError = Infallible;
fn init(&self) -> impl Init<Self::Command, Self::InitError> {
@@ -231,13 +235,5 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
/// Send the [`GetGspInfo`] command and awaits for its reply.
pub(crate) fn get_gsp_info(cmdq: &mut Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
- cmdq.send_command(bar, GetGspStaticInfo)?;
-
- loop {
- match cmdq.receive_msg::<GetGspStaticInfoReply>(Cmdq::RECEIVE_TIMEOUT) {
- Ok(info) => return Ok(info),
- Err(ERANGE) => continue,
- Err(e) => return Err(e),
- }
- }
+ cmdq.send_command(bar, GetGspStaticInfo)
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Claude review: gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp`
2026-03-04 2:46 ` [PATCH v3 3/5] gpu: nova-core: gsp: add reply/no-reply info to `CommandToGsp` Eliot Courtney
@ 2026-03-05 3:52 ` Claude Code Review Bot
0 siblings, 0 replies; 13+ messages in thread
From: Claude Code Review Bot @ 2026-03-05 3:52 UTC (permalink / raw)
To: dri-devel-reviews
Patch Review
This is the core design patch. Adds `type Reply` to the `CommandToGsp` trait and a `NoReply` marker type:
```rust
pub(crate) struct NoReply;
```
The approach is sound. Commands that don't expect a reply use `type Reply = NoReply`, and the `send_command_no_wait` function enforces this at the type level with:
```rust
M: CommandToGsp<Reply = NoReply>,
```
The `send_command` method now returns `Result<M::Reply>` and includes the receive loop:
```rust
pub(crate) fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result<M::Reply>
where
M: CommandToGsp,
M::Reply: MessageFromGsp,
...
{
self.send_command_internal(bar, command)?;
loop {
match self.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
}
}
}
```
**Concern**: The `Err(ERANGE) => continue` loop has no bound. If the GSP keeps sending non-matching messages, this loops forever. Each iteration does have a `RECEIVE_TIMEOUT` on the individual receive, so it won't literally spin forever — eventually `receive_msg` will return `ETIMEDOUT` if no messages arrive. But if a stream of wrong-function-code messages keeps arriving, the loop will consume them all indefinitely without ever timing out. In practice this is unlikely, but a maximum iteration count or an overall deadline would be safer. This is a pre-existing pattern from the old code, so not a regression — but worth noting as a future improvement since this loop is now in a more prominent, reusable position.
The `SplitCommand` correctly propagates `type Reply = C::Reply` (line 951), which is correct since the split command should have the same reply type as the original.
The update to `get_gsp_info` is a nice simplification, collapsing the function to a single line.
The `ContinuationRecord` and test `TestPayload` correctly use `type Reply = NoReply` since continuation records don't receive individual replies.
---
Generated by Claude Code Patch Reviewer
^ permalink raw reply [flat|nested] 13+ messages in thread