public inbox for drm-ai-reviews@public-inbox.freedesktop.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq
@ 2026-02-26 14:50 Eliot Courtney
  2026-02-26 14:50 ` [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
                   ` (5 more replies)
  0 siblings, 6 replies; 14+ messages in thread
From: Eliot Courtney @ 2026-02-26 14:50 UTC (permalink / raw)
  To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo
  Cc: nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney,
	Zhi Wang

Add locking to Cmdq. This is required e.g. for unloading the driver,
which needs to send the UnloadingGuestDriver via the command queue
on unbind which may be on a different thread.

We have commands that need a reply ("sync") and commands that don't
("async"). For sync commands we want to make sure that they don't get
the reply of a different command back. The approach this patch series
takes is by making sync commands block until they get a response. For
now this should be ok, and we expect GSP to be fast anyway.

To do this, we need to know which commands expect a reply and which
don't. John's existing series[1] adds IS_ASYNC which solves part of the
problem, but we need to know a bit more. So instead, add an
associated type called Reply which tells us what the reply is.

An alternative would be to define traits inheriting CommandToGsp, e.g.
SyncCommand and AsyncCommand, instead of using the associated type. I
implemented the associated type version because it feels more
compositional rather than inherity so seemed a bit better to me. But
both of these approaches work and are fine, IMO.

In summary, this patch series has three steps:
1. Add the type infrastructure to know what replies are expected for a
   command and update each caller to explicitly send a sync or async
   command.
2. Make Cmdq pinned so we can use Mutex
3. Add a Mutex to protect Cmdq by moving the relevant state to an
   inner struct.

[1]: https://lore.kernel.org/all/20260211000451.192109-1-jhubbard@nvidia.com/

Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
Changes in v2:
- Rebase on drm-rust-next
- Link to v1: https://lore.kernel.org/r/20260225-cmdq-locking-v1-0-bbf6b4156706@nvidia.com

---
Eliot Courtney (4):
      gpu: nova-core: gsp: fix stale doc comments on command queue methods
      gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
      gpu: nova-core: gsp: make `Cmdq` a pinned type
      gpu: nova-core: gsp: add mutex locking to Cmdq

 drivers/gpu/nova-core/gsp.rs           |   5 +-
 drivers/gpu/nova-core/gsp/boot.rs      |  13 +-
 drivers/gpu/nova-core/gsp/cmdq.rs      | 246 +++++++++++++++++++++------------
 drivers/gpu/nova-core/gsp/commands.rs  |  23 ++-
 drivers/gpu/nova-core/gsp/sequencer.rs |   2 +-
 5 files changed, 183 insertions(+), 106 deletions(-)
---
base-commit: 4a49fe23e357b48845e31fe9c28a802c05458198
change-id: 20260225-cmdq-locking-d32928a2c2cf
prerequisite-message-id: <20260226-cmdq-continuation-v3-0-572ab9916766@nvidia.com>
prerequisite-patch-id: fd45bc5b8eda5e2b54a052dddb1a1c363107f0a7
prerequisite-patch-id: d0f59ef489346e978a222755f9fb42dfe7af19e5
prerequisite-patch-id: 8844970d0e387488c70979a73732579ba174b46c
prerequisite-patch-id: e138a94ed48fa8d50d5ed1eb36524f98923ed478
prerequisite-patch-id: 4599a5e90d6665fa3acb7d4045de5d378ac28b4d
prerequisite-patch-id: 30ed64c398e541d6efbcb2e46ed9a9e6cf953f4f
prerequisite-patch-id: 9a965e9f29c8680c0b554e656ff8e9a1bfc67280
prerequisite-patch-id: d8cccc3dfb070f304805fc7e0f24121809b4b300
prerequisite-patch-id: c0a73dfd1fb630ab02486f0180b90f8fe850b4dc

Best regards,
-- 
Eliot Courtney <ecourtney@nvidia.com>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
@ 2026-02-26 14:50 ` Eliot Courtney
  2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
  2026-02-26 14:50 ` [PATCH v2 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Eliot Courtney @ 2026-02-26 14:50 UTC (permalink / raw)
  To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo
  Cc: 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>
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 d68b93ddf7cc..a3e039117120 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -528,6 +528,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.
@@ -710,22 +711,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] 14+ messages in thread

* [PATCH v2 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
  2026-02-26 14:50 ` [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-02-26 14:50 ` Eliot Courtney
  2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
  2026-02-26 14:50 ` [PATCH v2 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Eliot Courtney @ 2026-02-26 14:50 UTC (permalink / raw)
  To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo
  Cc: nouveau, dri-devel, linux-kernel, rust-for-linux, Eliot Courtney

Add sync and async command queue API and the type infrastructure to know
what reply is expected from each `CommandToGsp`.

Use a marker type `NoReply` which does not implement `MessageFromGsp` to
mark async commands which don't expect a response.

This prepares for adding locking to the queue.

Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
 drivers/gpu/nova-core/gsp/boot.rs     |  5 ++--
 drivers/gpu/nova-core/gsp/cmdq.rs     | 54 ++++++++++++++++++++++++++++++++++-
 drivers/gpu/nova-core/gsp/commands.rs | 19 ++++++------
 3 files changed, 65 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index c56029f444cb..55899eba75db 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_async_command(bar, commands::SetSystemInfo::new(pdev))?;
+        self.cmdq
+            .send_async_command(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 a3e039117120..daf3e1d153d4 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -48,6 +48,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
@@ -66,6 +70,9 @@ 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 async commands.
+    type Reply;
+
     /// Error type returned by [`CommandToGsp::init`].
     type InitError;
 
@@ -604,7 +611,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<M>(&mut self, bar: &Bar0, command: M) -> Result
     where
         M: CommandToGsp,
         Error: From<M::InitError>,
@@ -626,6 +633,51 @@ pub(crate) fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result
         Ok(())
     }
 
+    /// 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_sync_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(bar, command)?;
+
+        loop {
+            match self.receive_msg::<M::Reply>(Delta::from_secs(10)) {
+                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_async_command<M>(&mut self, bar: &Bar0, command: M) -> Result
+    where
+        M: CommandToGsp<Reply = NoReply>,
+        Error: From<M::InitError>,
+    {
+        self.send_command(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/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 74f875755e53..47ca31611927 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -26,7 +26,8 @@
             command_size,
             Cmdq,
             CommandToGsp,
-            MessageFromGsp, //
+            MessageFromGsp,
+            NoReply, //
         },
         fw::{
             commands::*,
@@ -53,6 +54,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> {
@@ -104,6 +106,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> {
@@ -183,6 +186,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> {
@@ -236,15 +240,7 @@ 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>(Delta::from_secs(5)) {
-            Ok(info) => return Ok(info),
-            Err(ERANGE) => continue,
-            Err(e) => return Err(e),
-        }
-    }
+    cmdq.send_sync_command(bar, GetGspStaticInfo)
 }
 
 /// The `ContinuationRecord` command.
@@ -262,6 +258,7 @@ pub(crate) 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> {
@@ -356,6 +353,7 @@ pub(crate) enum SplitCommand<'a, C: CommandToGsp> {
 impl<'a, C: CommandToGsp> CommandToGsp for SplitCommand<'a, 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> {
@@ -412,6 +410,7 @@ fn new(len: usize) -> Result<Self> {
     impl CommandToGsp for TestPayload {
         const FUNCTION: MsgFunction = MsgFunction::Nop;
         type Command = ();
+        type Reply = NoReply;
         type InitError = Infallible;
 
         fn init(&self) -> impl Init<Self::Command, Self::InitError> {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
  2026-02-26 14:50 ` [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
  2026-02-26 14:50 ` [PATCH v2 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
@ 2026-02-26 14:50 ` Eliot Courtney
  2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
  2026-02-26 14:50 ` [PATCH v2 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Eliot Courtney @ 2026-02-26 14:50 UTC (permalink / raw)
  To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo
  Cc: 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>
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 daf3e1d153d4..6bb1decd2af5 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -474,6 +474,7 @@ pub(crate) fn command_size<M>(command: &M) -> usize
 ///
 /// 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>,
@@ -501,13 +502,11 @@ impl Cmdq {
     pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
 
     /// 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] 14+ messages in thread

* [PATCH v2 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
                   ` (2 preceding siblings ...)
  2026-02-26 14:50 ` [PATCH v2 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-02-26 14:50 ` Eliot Courtney
  2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
  2026-02-26 18:48 ` [PATCH v2 0/4] gpu: nova-core: gsp: add " Zhi Wang
  2026-02-27  2:04 ` Claude review: " Claude Code Review Bot
  5 siblings, 1 reply; 14+ messages in thread
From: Eliot Courtney @ 2026-02-26 14:50 UTC (permalink / raw)
  To: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo
  Cc: 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_sync_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>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
 drivers/gpu/nova-core/gsp/boot.rs      |   8 +-
 drivers/gpu/nova-core/gsp/cmdq.rs      | 266 ++++++++++++++++++---------------
 drivers/gpu/nova-core/gsp/commands.rs  |   4 +-
 drivers/gpu/nova-core/gsp/sequencer.rs |   2 +-
 4 files changed, 153 insertions(+), 127 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 55899eba75db..d12ad1bd2cd8 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 6bb1decd2af5..5010587c96f9 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -16,8 +16,12 @@
     },
     dma_write,
     io::poll::read_poll_timeout,
+    new_mutex,
     prelude::*,
-    sync::aref::ARef,
+    sync::{
+        aref::ARef,
+        Mutex, //
+    },
     time::Delta,
     transmute::{
         AsBytes,
@@ -54,8 +58,8 @@
 
 /// 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.
@@ -470,66 +474,15 @@ pub(crate) fn command_size<M>(command: &M) -> usize
     size_of::<M::Command>() + command.variable_payload_len()
 }
 
-/// GSP command queue.
-///
-/// 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>,
+/// Inner mutex protected state of [`Cmdq`].
+struct CmdqInner {
     /// Current command sequence number.
     seq: u32,
     /// Memory area shared with the GSP for communicating commands and messages.
     gsp_mem: DmaGspMem,
 }
 
-impl Cmdq {
-    /// Offset of the data after the PTEs.
-    const POST_PTE_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq);
-
-    /// Offset of command queue ring buffer.
-    pub(crate) const CMDQ_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq)
-        + core::mem::offset_of!(Msgq, msgq)
-        - Self::POST_PTE_OFFSET;
-
-    /// Offset of message queue ring buffer.
-    pub(crate) const STATQ_OFFSET: usize = core::mem::offset_of!(GspMem, gspq)
-        + core::mem::offset_of!(Msgq, msgq)
-        - Self::POST_PTE_OFFSET;
-
-    /// Number of page table entries for the GSP shared region.
-    pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
-
-    /// 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,
-        })
-    }
-
-    /// Computes the checksum for the message pointed to by `it`.
-    ///
-    /// A message is made of several parts, so `it` is an iterator over byte slices representing
-    /// these parts.
-    fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
-        let sum64 = it
-            .enumerate()
-            .map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
-            .fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));
-
-        ((sum64 >> 32) as u32) ^ (sum64 as u32)
-    }
-
-    /// Notifies the GSP that we have updated the command queue pointers.
-    fn notify_gsp(bar: &Bar0) {
-        regs::NV_PGSP_QUEUE_HEAD::default()
-            .set_address(0)
-            .write(bar);
-    }
-
+impl CmdqInner {
     /// Sends `command` to the GSP, without splitting it.
     ///
     /// # Errors
@@ -540,7 +493,7 @@ fn notify_gsp(bar: &Bar0) {
     ///   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, bar: &Bar0, command: M) -> Result
+    fn send_single_command<M>(&mut self, dev: &device::Device, bar: &Bar0, command: M) -> Result
     where
         M: CommandToGsp,
         // This allows all error types, including `Infallible`, to be used for `M::InitError`.
@@ -583,7 +536,7 @@ fn send_single_command<M>(&mut self, bar: &Bar0, command: M) -> Result
             ])));
 
         dev_dbg!(
-            &self.dev,
+            dev,
             "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
             self.seq,
             M::FUNCTION,
@@ -610,73 +563,27 @@ 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<M>(&mut self, bar: &Bar0, command: M) -> Result
+    fn send_command<M>(&mut self, dev: &device::Device, bar: &Bar0, command: M) -> Result
     where
         M: CommandToGsp,
         Error: From<M::InitError>,
     {
         let mut state = SplitState::new(&command)?;
-
-        self.send_single_command(bar, state.command(command))?;
+        self.send_single_command(dev, bar, state.command(command))?;
 
         while let Some(continuation) = state.next_continuation_record() {
             dev_dbg!(
-                &self.dev,
+                dev,
                 "GSP RPC: send continuation: size=0x{:x}\n",
                 command_size(&continuation),
             );
             // Turbofish needed because the compiler cannot infer M here.
-            self.send_single_command::<ContinuationRecord<'_>>(bar, continuation)?;
+            self.send_single_command::<ContinuationRecord<'_>>(dev, bar, continuation)?;
         }
 
         Ok(())
     }
 
-    /// 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_sync_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(bar, command)?;
-
-        loop {
-            match self.receive_msg::<M::Reply>(Delta::from_secs(10)) {
-                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_async_command<M>(&mut self, bar: &Bar0, command: M) -> Result
-    where
-        M: CommandToGsp<Reply = NoReply>,
-        Error: From<M::InitError>,
-    {
-        self.send_command(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
@@ -695,7 +602,7 @@ pub(crate) fn send_async_command<M>(&mut self, bar: &Bar0, 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, dev: &device::Device, 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()),
@@ -712,7 +619,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,
+            dev,
             "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
             header.sequence(),
             header.function(),
@@ -747,7 +654,7 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
         ])) != 0
         {
             dev_err!(
-                self.dev,
+                dev,
                 "GSP RPC: receive: Call {} - bad checksum\n",
                 header.sequence()
             );
@@ -776,12 +683,12 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
     /// - `ERANGE` if the message had a recognized but non-matching function code.
     ///
     /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
-    pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
+    fn receive_msg<M: MessageFromGsp>(&mut self, dev: &device::Device, timeout: Delta) -> Result<M>
     where
         // This allows all error types, including `Infallible`, to be used for `M::InitError`.
         Error: From<M::InitError>,
     {
-        let message = self.wait_for_msg(timeout)?;
+        let message = self.wait_for_msg(dev, 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
@@ -794,11 +701,7 @@ pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Resul
                 .map_err(|e| e.into())
                 .inspect(|_| {
                     if !sbuffer.is_empty() {
-                        dev_warn!(
-                            &self.dev,
-                            "GSP message {:?} has unprocessed data\n",
-                            function
-                        );
+                        dev_warn!(dev, "GSP message {:?} has unprocessed data\n", function);
                     }
                 })
         } else {
@@ -812,9 +715,132 @@ pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Resul
 
         result
     }
+}
+
+/// GSP command queue.
+///
+/// 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>,
+    /// Inner mutex-protected state.
+    #[pin]
+    inner: Mutex<CmdqInner>,
+}
+
+impl Cmdq {
+    /// Offset of the data after the PTEs.
+    const POST_PTE_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq);
+
+    /// Offset of command queue ring buffer.
+    pub(crate) const CMDQ_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq)
+        + core::mem::offset_of!(Msgq, msgq)
+        - Self::POST_PTE_OFFSET;
+
+    /// Offset of message queue ring buffer.
+    pub(crate) const STATQ_OFFSET: usize = core::mem::offset_of!(GspMem, gspq)
+        + core::mem::offset_of!(Msgq, msgq)
+        - Self::POST_PTE_OFFSET;
+
+    /// Number of page table entries for the GSP shared region.
+    pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
+
+    /// Creates a new command queue for `dev`.
+    pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
+        try_pin_init!(Self {
+            inner <- new_mutex!(CmdqInner {
+                gsp_mem: DmaGspMem::new(dev)?,
+                seq: 0,
+                }),
+            dev: dev.into(),
+        })
+    }
+
+    /// Computes the checksum for the message pointed to by `it`.
+    ///
+    /// A message is made of several parts, so `it` is an iterator over byte slices representing
+    /// these parts.
+    fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
+        let sum64 = it
+            .enumerate()
+            .map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
+            .fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));
+
+        ((sum64 >> 32) as u32) ^ (sum64 as u32)
+    }
+
+    /// Notifies the GSP that we have updated the command queue pointers.
+    fn notify_gsp(bar: &Bar0) {
+        regs::NV_PGSP_QUEUE_HEAD::default()
+            .set_address(0)
+            .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_sync_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(&self.dev, bar, command)?;
+
+        loop {
+            match inner.receive_msg::<M::Reply>(&self.dev, Delta::from_secs(10)) {
+                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_async_command<M>(&self, bar: &Bar0, command: M) -> Result
+    where
+        M: CommandToGsp<Reply = NoReply>,
+        Error: From<M::InitError>,
+    {
+        self.inner.lock().send_command(&self.dev, 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(&self.dev, timeout)
+    }
 
     /// 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()
+        self.inner.lock().gsp_mem.0.dma_handle()
     }
 }
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 47ca31611927..4740cda0b51c 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -170,7 +170,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>(Delta::from_secs(10)) {
             Ok(_) => break Ok(()),
@@ -239,7 +239,7 @@ 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_sync_command(bar, GetGspStaticInfo)
 }
 
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
index 0cfbedc47fcf..f99f4fe652ba 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>(Delta::from_secs(10)) {
                 Ok(seq_info) => break seq_info,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
                   ` (3 preceding siblings ...)
  2026-02-26 14:50 ` [PATCH v2 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
@ 2026-02-26 18:48 ` Zhi Wang
  2026-02-27  2:04 ` Claude review: " Claude Code Review Bot
  5 siblings, 0 replies; 14+ messages in thread
From: Zhi Wang @ 2026-02-26 18:48 UTC (permalink / raw)
  To: Eliot Courtney
  Cc: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, David Airlie,
	Simona Vetter, Benno Lossin, Gary Guo, nouveau, dri-devel,
	linux-kernel, rust-for-linux

On Thu, 26 Feb 2026 23:50:22 +0900
Eliot Courtney <ecourtney@nvidia.com> wrote:

Tested-by: Zhi Wang <zhiw@nvidia.com>

> Add locking to Cmdq. This is required e.g. for unloading the driver,
> which needs to send the UnloadingGuestDriver via the command queue
> on unbind which may be on a different thread.
> 
> We have commands that need a reply ("sync") and commands that don't
> ("async"). For sync commands we want to make sure that they don't get
> the reply of a different command back. The approach this patch series
> takes is by making sync commands block until they get a response. For
> now this should be ok, and we expect GSP to be fast anyway.
> 
> To do this, we need to know which commands expect a reply and which
> don't. John's existing series[1] adds IS_ASYNC which solves part of
> the problem, but we need to know a bit more. So instead, add an
> associated type called Reply which tells us what the reply is.
> 
> An alternative would be to define traits inheriting CommandToGsp, e.g.
> SyncCommand and AsyncCommand, instead of using the associated type. I
> implemented the associated type version because it feels more
> compositional rather than inherity so seemed a bit better to me. But
> both of these approaches work and are fine, IMO.
> 
> In summary, this patch series has three steps:
> 1. Add the type infrastructure to know what replies are expected for a
>    command and update each caller to explicitly send a sync or async
>    command.
> 2. Make Cmdq pinned so we can use Mutex
> 3. Add a Mutex to protect Cmdq by moving the relevant state to an
>    inner struct.
> 
> [1]:
> https://lore.kernel.org/all/20260211000451.192109-1-jhubbard@nvidia.com/
> 
> Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
> ---
> Changes in v2:
> - Rebase on drm-rust-next
> - Link to v1:
> https://lore.kernel.org/r/20260225-cmdq-locking-v1-0-bbf6b4156706@nvidia.com
> 
> ---
> Eliot Courtney (4):
>       gpu: nova-core: gsp: fix stale doc comments on command queue
> methods gpu: nova-core: gsp: add sync and async command queue API to
> `Cmdq` gpu: nova-core: gsp: make `Cmdq` a pinned type
>       gpu: nova-core: gsp: add mutex locking to Cmdq
> 
>  drivers/gpu/nova-core/gsp.rs           |   5 +-
>  drivers/gpu/nova-core/gsp/boot.rs      |  13 +-
>  drivers/gpu/nova-core/gsp/cmdq.rs      | 246
> +++++++++++++++++++++------------
> drivers/gpu/nova-core/gsp/commands.rs  |  23 ++-
> drivers/gpu/nova-core/gsp/sequencer.rs |   2 +- 5 files changed, 183
> insertions(+), 106 deletions(-) ---
> base-commit: 4a49fe23e357b48845e31fe9c28a802c05458198
> change-id: 20260225-cmdq-locking-d32928a2c2cf
> prerequisite-message-id:
> <20260226-cmdq-continuation-v3-0-572ab9916766@nvidia.com>
> prerequisite-patch-id: fd45bc5b8eda5e2b54a052dddb1a1c363107f0a7
> prerequisite-patch-id: d0f59ef489346e978a222755f9fb42dfe7af19e5
> prerequisite-patch-id: 8844970d0e387488c70979a73732579ba174b46c
> prerequisite-patch-id: e138a94ed48fa8d50d5ed1eb36524f98923ed478
> prerequisite-patch-id: 4599a5e90d6665fa3acb7d4045de5d378ac28b4d
> prerequisite-patch-id: 30ed64c398e541d6efbcb2e46ed9a9e6cf953f4f
> prerequisite-patch-id: 9a965e9f29c8680c0b554e656ff8e9a1bfc67280
> prerequisite-patch-id: d8cccc3dfb070f304805fc7e0f24121809b4b300
> prerequisite-patch-id: c0a73dfd1fb630ab02486f0180b90f8fe850b4dc
> 
> Best regards,


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add locking to Cmdq
  2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
                   ` (4 preceding siblings ...)
  2026-02-26 18:48 ` [PATCH v2 0/4] gpu: nova-core: gsp: add " Zhi Wang
@ 2026-02-27  2:04 ` Claude Code Review Bot
  5 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  2:04 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-02-27T12:04:08.349113

---

This is a well-structured 4-patch series by Eliot Courtney that adds mutex locking to the `Cmdq` (command queue) in the nova-core GSP driver. The series is motivated by the need to send commands (e.g., `UnloadingGuestDriver`) from different threads during driver unloading.

The approach is sound: introduce a type-level distinction between sync and async commands via an associated `Reply` type, convert `Cmdq` to a pinned type (required for kernel `Mutex`), and then wrap mutable state in a `Mutex<CmdqInner>`. The decomposition into four incremental patches is clean and logical — each builds on the previous in a natural progression.

**Key design decisions:**
- Using an associated `Reply` type rather than separate `SyncCommand`/`AsyncCommand` traits is a good compositional choice
- Holding the mutex across send+receive in `send_sync_command` is the right approach to prevent reply mismatches between concurrent callers
- The `NoReply` marker type that intentionally does *not* implement `MessageFromGsp` gives compile-time enforcement

**Concerns:**
- The `send_sync_command` receive loop with a 10-second per-iteration timeout and no overall deadline could block indefinitely if the GSP keeps sending non-matching messages (ERANGE)
- `dma_handle()` takes the mutex just to read a DMA address that never changes after init — this is unnecessary overhead (though harmless)
- The series depends on prerequisite patches that should be stated clearly for merge order

Overall this is clean, well-documented code that follows Rust/kernel conventions properly. The series is in good shape.

---

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: fix stale doc comments on command queue methods
  2026-02-26 14:50 ` [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-02-27  2:04   ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  2:04 UTC (permalink / raw)
  To: dri-devel-reviews

Patch Review

**Status: Looks good**

A straightforward documentation cleanup. The changes are:

1. Adding missing `EMSGSIZE` error doc:
```
+    /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
```
This was presumably missing from the original docs — good to add.

2. Updating `receive_msg` docs to accurately describe current behavior:
```
-    /// `init` is a closure tasked with processing the message...
+    /// 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 old docs referenced a closure-based API that no longer exists. The replacement accurately describes the current generic-parameter approach.

3. Updating error code documentation:
```
-    /// - `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.
```
The `EAGAIN` → `ERANGE` distinction and the addition of `ERANGE` docs are correct.

4. Adding note about error propagation:
```
+    /// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
```

5. Removing the "this design is by no means final" comment — appropriate for a maturing codebase.

No issues. Already has Reviewed-by from Zhi Wang.

---

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
  2026-02-26 14:50 ` [PATCH v2 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
@ 2026-02-27  2:04   ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  2:04 UTC (permalink / raw)
  To: dri-devel-reviews

Patch Review

**Status: Looks good, minor observations**

This patch adds the type infrastructure for distinguishing sync vs async commands.

The `NoReply` marker type is simple and effective:
```rust
+pub(crate) struct NoReply;
```
Not implementing `MessageFromGsp` means `send_sync_command` cannot accidentally be called with an async command — this is enforced at compile time via the `M::Reply: MessageFromGsp` bound.

The `Reply` associated type on `CommandToGsp`:
```rust
+    /// Type of the reply expected from the GSP, or [`NoReply`] for async commands.
+    type Reply;
```
This is cleaner than a boolean `IS_ASYNC` constant since it carries the reply type information that `send_sync_command` needs.

The `send_sync_command` implementation:
```rust
+    pub(crate) fn send_sync_command<M>(&mut self, bar: &Bar0, command: M) -> Result<M::Reply>
+    where
+        M: CommandToGsp,
+        M::Reply: MessageFromGsp,
+        ...
+    {
+        self.send_command(bar, command)?;
+        loop {
+            match self.receive_msg::<M::Reply>(Delta::from_secs(10)) {
+                Ok(reply) => break Ok(reply),
+                Err(ERANGE) => continue,
+                Err(e) => break Err(e),
+            }
+        }
+    }
```

**Observation:** The `ERANGE` loop retries with a fresh 10-second timeout each iteration. If the GSP keeps sending non-matching messages (e.g. asynchronous notifications), this could loop for a very long time. Currently this is likely fine since the cover letter says "we expect GSP to be fast," but a comment noting this assumption or an overall retry limit would be defensive. This is a pre-existing pattern from `get_gsp_info()` which this patch consolidates — so it's not a regression.

The `send_async_command` properly constrains `Reply = NoReply`:
```rust
+    pub(crate) fn send_async_command<M>(&mut self, bar: &Bar0, command: M) -> Result
+    where
+        M: CommandToGsp<Reply = NoReply>,
```

Making `send_command` private (`fn` instead of `pub(crate) fn`) is correct — callers should now go through the sync/async variants.

The migration of `get_gsp_info` from a manual send+receive loop to a single `send_sync_command` call is a nice cleanup:
```rust
-    cmdq.send_command(bar, GetGspStaticInfo)?;
-    loop {
-        match cmdq.receive_msg::<GetGspStaticInfoReply>(Delta::from_secs(5)) {
-            ...
-        }
-    }
+    cmdq.send_sync_command(bar, GetGspStaticInfo)
```

**Note:** The old code used a 5-second timeout; the new `send_sync_command` uses 10 seconds. This is a behavior change (doubled timeout) though likely intentional.

The `SplitCommand` correctly propagates the `Reply` type:
```rust
+    type Reply = C::Reply;
```

All command types are properly annotated. This patch is solid.

---

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: make `Cmdq` a pinned type
  2026-02-26 14:50 ` [PATCH v2 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-02-27  2:04   ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  2:04 UTC (permalink / raw)
  To: dri-devel-reviews

Patch Review

**Status: Looks good**

A clean mechanical change to make `Cmdq` pin-initialized, required for the `Mutex` in patch 4.

Adding `#[pin_data]` and `#[pin]`:
```rust
+#[pin_data]
 pub(crate) struct Cmdq {
     ...
```

Changing the constructor from `Result<Cmdq>` to `impl PinInit<Self, Error>`:
```rust
-    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,
         })
```

Updating the caller in `gsp.rs` to use pin-init syntax:
```rust
-                cmdq: Cmdq::new(dev)?,
+                cmdq <- Cmdq::new(dev),
```

And the reference change since `cmdq` is now behind a pin:
```rust
-                    dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(cmdq))?;
+                    dma_write!(rmargs[0].inner = fw::GspArgumentsCached::new(&cmdq))?;
```

No issues. Already has Reviewed-by from Zhi Wang.

---

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add mutex locking to Cmdq
  2026-02-26 14:50 ` [PATCH v2 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
@ 2026-02-27  2:04   ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  2:04 UTC (permalink / raw)
  To: dri-devel-reviews

Patch Review

**Status: Looks good, a few observations**

This is the main patch, splitting `Cmdq` into an outer struct (holding `dev` and the `Mutex`) and `CmdqInner` (holding mutable state: `seq` and `gsp_mem`).

The split is well-motivated:
```rust
+struct CmdqInner {
+    seq: u32,
+    gsp_mem: DmaGspMem,
+}
+
+pub(crate) struct Cmdq {
+    dev: ARef<device::Device>,
+    #[pin]
+    inner: Mutex<CmdqInner>,
+}
```

Moving `dev` outside the mutex is correct — it's `ARef` (immutable, reference-counted) and doesn't need mutex protection.

The `send_sync_command` correctly holds the lock across send+receive:
```rust
+    pub(crate) fn send_sync_command<M>(&self, bar: &Bar0, command: M) -> Result<M::Reply>
+    ...
+    {
+        let mut inner = self.inner.lock();
+        inner.send_command(&self.dev, bar, command)?;
+        loop {
+            match inner.receive_msg::<M::Reply>(&self.dev, Delta::from_secs(10)) {
```
This ensures no interleaving of commands and their replies. Methods now take `&self` instead of `&mut self`, which is the whole point.

Moving `calculate_checksum` and `notify_gsp` to `Cmdq` (rather than `CmdqInner`) makes sense since they don't use any mutable state — they're effectively static methods.

**Observations:**

1. **`dma_handle()` takes the lock unnecessarily:**
```rust
+    pub(crate) fn dma_handle(&self) -> DmaAddress {
+        self.inner.lock().gsp_mem.0.dma_handle()
+    }
```
The DMA handle is set once during allocation and never changes. This could instead store the DMA handle in the outer `Cmdq` struct at construction time, avoiding the lock. However, this is a minor performance point and correctness is not affected.

2. **`receive_msg` is exposed as `pub(crate)` on `Cmdq`:**
```rust
+    pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+    ...
+    {
+        self.inner.lock().receive_msg(&self.dev, timeout)
+    }
```
This is used by `wait_gsp_init_done` and `GspSequencer::run` which receive messages without a preceding send. The lock is acquired and released per call, which is correct for these use cases. However, if someone were to call this in a loop (as `wait_gsp_init_done` does), each iteration re-acquires the lock — this is fine for correctness and allows other operations to interleave.

3. **Thread passing `dev` through method parameters:** The `CmdqInner` methods now take `dev: &device::Device` as a parameter instead of accessing `self.dev`, since `dev` lives outside the inner struct. This is a clean pattern that avoids the need for the inner struct to hold a reference back to the outer struct.

4. **Formatting nit** in the constructor:
```rust
+        try_pin_init!(Self {
+            inner <- new_mutex!(CmdqInner {
+                gsp_mem: DmaGspMem::new(dev)?,
+                seq: 0,
+                }),
+            dev: dev.into(),
+        })
```
The closing `})` for `new_mutex!` has unusual indentation — the `)` is at 16 spaces indent while the opening `new_mutex!` is at 24. This is a very minor style nit.

5. The `boot()` signature change from `mut self: Pin<&mut Self>` to `self: Pin<&mut Self>` reflects that `Cmdq` methods no longer require `&mut self`:
```rust
-        mut self: Pin<&mut Self>,
+        self: Pin<&mut Self>,
```

Overall the locking design is correct and well-documented. Already has Reviewed-by from Zhi Wang.

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add locking to Cmdq
  2026-02-25 13:41 [PATCH 0/4] " Eliot Courtney
@ 2026-02-27  3:18 ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-02-27  3:18 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: 10
Reviewed: 2026-02-27T13:18:03.810448

---

This is a well-structured 4-patch series that adds mutex locking to the GSP command queue (`Cmdq`) in the nova-core Rust GPU driver. The series has clear prerequisites (John's cmdq split/continuation series and possibly others) which explains why it doesn't apply to drm-next.

The approach is sound: distinguish sync from async commands at the type level using an associated `Reply` type, make `Cmdq` a pinned type (required for kernel `Mutex`), then protect the mutable state with a `Mutex` so the API takes `&self` instead of `&mut self`. This enables concurrent access from different threads (e.g., during driver unbind).

**Key design decisions that look correct:**
- Holding the mutex across both send and receive in `send_sync_command` prevents reply mismatches between concurrent sync commands.
- Using `NoReply` as a marker type with the `Reply = NoReply` constraint on `send_async_command` provides compile-time enforcement.
- Moving `calculate_checksum` and `notify_gsp` (which don't need `&mut self`) to the outer `Cmdq` struct, while keeping actual state-mutating methods on `CmdqInner`, is a clean split.

**Concerns:**

1. **`dma_handle()` takes the lock**: After patch 4, `dma_handle()` acquires the mutex just to read the DMA address, which is immutable after construction. This is wasteful and could be a problem if the DMA address is needed while the lock is held elsewhere. The DMA handle could be stored as a separate field on `Cmdq` (outside the mutex), or `DmaGspMem` could be split so the handle is accessible without locking.

2. **`send_sync_command` busy-loops on `ERANGE` while holding the mutex**: If unexpected messages arrive (non-matching function codes), the loop silently consumes them while blocking all other command queue access. This means async commands cannot be sent while a sync command is waiting for its reply. The cover letter acknowledges this trade-off and says GSP is expected to be fast, which is acceptable for now, but should be documented as a known limitation.

3. **Timeout accumulation**: The `receive_msg` call inside the `send_sync_command` loop uses a 10-second timeout per attempt. Since `ERANGE` causes a `continue`, the effective timeout can be `N * 10` seconds if many non-matching messages arrive. This differs from the original `get_gsp_info` which used 5-second timeouts — the series changes this to 10 seconds.

Overall the series is clean and well-structured. The concerns above are mostly future considerations rather than blockers.

---

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add locking to Cmdq
  2026-03-04  2:46 [PATCH v3 0/5] " Eliot Courtney
@ 2026-03-05  3:52 ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ messages in thread
From: Claude Code Review Bot @ 2026-03-05  3:52 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: 22
Reviewed: 2026-03-05T13:52:59.195127

---

This is a well-structured 5-patch series adding mutex locking to the nova-core GSP command queue (`Cmdq`). The motivation is clear: commands need to be sent from different threads (e.g. driver unbind), so the queue needs synchronization. The approach of distinguishing reply/no-reply commands via an associated type is clean and leverages Rust's type system well. The series is logically ordered: doc fixes → constant extraction → type infrastructure → pin requirement → mutex.

The series is generally solid. The design choice to hold the mutex across both send+receive for commands expecting a reply is correct and necessary to prevent reply mismatches. The code is well documented.

I have a few concerns, mainly around the `send_command` reply loop potentially blocking the mutex indefinitely, and the `dma_handle` accessor locking the mutex just to read an immutable field.

---
Generated by Claude Code Patch Reviewer

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Claude review: gpu: nova-core: gsp: add locking to Cmdq
  2026-03-10  8:09 [PATCH v4 0/5] " Eliot Courtney
@ 2026-03-11  3:32 ` Claude Code Review Bot
  0 siblings, 0 replies; 14+ 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] 14+ messages in thread

end of thread, other threads:[~2026-03-11  3:32 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-26 14:50 [PATCH v2 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
2026-02-26 14:50 ` [PATCH v2 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
2026-02-26 14:50 ` [PATCH v2 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
2026-02-26 14:50 ` [PATCH v2 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
2026-02-26 14:50 ` [PATCH v2 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
2026-02-27  2:04   ` Claude review: " Claude Code Review Bot
2026-02-26 18:48 ` [PATCH v2 0/4] gpu: nova-core: gsp: add " Zhi Wang
2026-02-27  2:04 ` Claude review: " Claude Code Review Bot
  -- strict thread matches above, loose matches on Subject: below --
2026-03-10  8:09 [PATCH v4 0/5] " Eliot Courtney
2026-03-11  3:32 ` Claude review: " Claude Code Review Bot
2026-03-04  2:46 [PATCH v3 0/5] " Eliot Courtney
2026-03-05  3:52 ` Claude review: " Claude Code Review Bot
2026-02-25 13:41 [PATCH 0/4] " Eliot Courtney
2026-02-27  3:18 ` Claude review: " Claude Code Review Bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox