public inbox for rust-for-linux@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq
@ 2026-02-25 13:41 Eliot Courtney
  2026-02-25 13:41 ` [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Eliot Courtney @ 2026-02-25 13:41 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 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>
---
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      | 240 ++++++++++++++++++++++-----------
 drivers/gpu/nova-core/gsp/commands.rs  |  23 ++--
 drivers/gpu/nova-core/gsp/sequencer.rs |   2 +-
 5 files changed, 182 insertions(+), 101 deletions(-)
---
base-commit: 4f938c7d3b25d87b356af4106c2682caf8c835a2
change-id: 20260225-cmdq-locking-d32928a2c2cf
prerequisite-message-id: <20260129-nova-core-cmdq1-v3-0-2ede85493a27@nvidia.com>
prerequisite-patch-id: bd2da0d580038dbab7ef06c0508d157873796ff4
prerequisite-patch-id: f834078971a3a46babe131251353184ffd7ecc38
prerequisite-patch-id: db5049ec7fe4348eed9e54dd5a2c07c697a54c81
prerequisite-patch-id: bafdd38cb9aba355a796232e07d5bbcd6fc59ba5
prerequisite-patch-id: 72a8d4626458707ae4ed632c629ffb6728cee032
prerequisite-message-id: <20260219-cmdq-continuation-v2-0-2e8b7615536f@nvidia.com>
prerequisite-patch-id: 20003099ca19d81b6255c5e95148f6584c108a29
prerequisite-patch-id: d0f59ef489346e978a222755f9fb42dfe7af19e5
prerequisite-patch-id: 8844970d0e387488c70979a73732579ba174b46c
prerequisite-patch-id: e138a94ed48fa8d50d5ed1eb36524f98923ed478
prerequisite-patch-id: 4599a5e90d6665fa3acb7d4045de5d378ac28b4d
prerequisite-patch-id: 7975f5bcaf28a99202d86d755fc256cd25be9cc4
prerequisite-patch-id: ee694b609c76628508d12fbe9b8b5a8a9354bfc4
prerequisite-patch-id: 1bb2f05c6a409a39421586e8148f82fa62ce6875
prerequisite-patch-id: a932945cb83fb631ebff3ac76ffff4319cacaabe

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


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

* [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods
  2026-02-25 13:41 [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
@ 2026-02-25 13:41 ` Eliot Courtney
  2026-02-25 19:42   ` Zhi Wang
  2026-02-25 13:41 ` [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Eliot Courtney @ 2026-02-25 13:41 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

Fix some inaccuracies / old doc comments.

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 436d1ff20705..cfae5b35adec 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] 10+ messages in thread

* [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
  2026-02-25 13:41 [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
  2026-02-25 13:41 ` [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-02-25 13:41 ` Eliot Courtney
  2026-02-25 19:42   ` Zhi Wang
  2026-02-25 13:41 ` [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
  2026-02-25 13:41 ` [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
  3 siblings, 1 reply; 10+ messages in thread
From: Eliot Courtney @ 2026-02-25 13:41 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 be427fe26a58..1cb21da855b9 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 cfae5b35adec..e1ca1bb9e07d 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 1683ebb4c685..b42e32dcc55c 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> {
@@ -354,6 +351,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> {
@@ -410,6 +408,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] 10+ messages in thread

* [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type
  2026-02-25 13:41 [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
  2026-02-25 13:41 ` [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
  2026-02-25 13:41 ` [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
@ 2026-02-25 13:41 ` Eliot Courtney
  2026-02-25 19:43   ` Zhi Wang
  2026-02-25 13:41 ` [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
  3 siblings, 1 reply; 10+ messages in thread
From: Eliot Courtney @ 2026-02-25 13:41 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

Make `Cmdq` a pinned type. This is needed to use Mutex, which is needed
to add locking to `Cmdq`.

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 e1ca1bb9e07d..44c3e960c965 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] 10+ messages in thread

* [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq
  2026-02-25 13:41 [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
                   ` (2 preceding siblings ...)
  2026-02-25 13:41 ` [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-02-25 13:41 ` Eliot Courtney
  2026-02-25 19:48   ` Zhi Wang
  3 siblings, 1 reply; 10+ messages in thread
From: Eliot Courtney @ 2026-02-25 13:41 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

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.

Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
 drivers/gpu/nova-core/gsp/boot.rs      |   8 +-
 drivers/gpu/nova-core/gsp/cmdq.rs      | 260 ++++++++++++++++++---------------
 drivers/gpu/nova-core/gsp/commands.rs  |   4 +-
 drivers/gpu/nova-core/gsp/sequencer.rs |   2 +-
 4 files changed, 152 insertions(+), 122 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 1cb21da855b9..cb583f57666a 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,
@@ -232,13 +232,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.as_ref(), "GPU name: {}\n", name),
             Err(e) => dev_warn!(pdev.as_ref(), "GPU name unavailable: {:?}\n", e),
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 44c3e960c965..faf1e9d5072b 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
@@ -802,9 +709,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 b42e32dcc55c..7ceff2e6bd63 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 2fa2a0792cec..bc94b8567c6a 100644
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ b/drivers/gpu/nova-core/gsp/sequencer.rs
@@ -366,7 +366,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] 10+ messages in thread

* Re: [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
  2026-02-25 13:41 ` [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
@ 2026-02-25 19:42   ` Zhi Wang
  2026-02-26  0:42     ` Eliot Courtney
  0 siblings, 1 reply; 10+ messages in thread
From: Zhi Wang @ 2026-02-25 19:42 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 Wed, 25 Feb 2026 22:41:49 +0900
Eliot Courtney <ecourtney@nvidia.com> wrote:

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

snip

> +        loop {
> +            match self.receive_msg::<M::Reply>(Delta::from_secs(10))

It turns out here the timeout is changed to 10s from 5s which was used
in other places. Any problem you encountered during the debugging?

Z.

> {
> +                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
> 1683ebb4c685..b42e32dcc55c 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> {
> @@ -354,6 +351,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> {
> @@ -410,6 +408,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> {
> 


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

* Re: [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods
  2026-02-25 13:41 ` [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
@ 2026-02-25 19:42   ` Zhi Wang
  0 siblings, 0 replies; 10+ messages in thread
From: Zhi Wang @ 2026-02-25 19:42 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 Wed, 25 Feb 2026 22:41:48 +0900
Eliot Courtney <ecourtney@nvidia.com> wrote:

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

> Fix some inaccuracies / old doc comments.
> 
> 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 436d1ff20705..cfae5b35adec
> 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`.
> 


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

* Re: [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type
  2026-02-25 13:41 ` [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
@ 2026-02-25 19:43   ` Zhi Wang
  0 siblings, 0 replies; 10+ messages in thread
From: Zhi Wang @ 2026-02-25 19:43 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 Wed, 25 Feb 2026 22:41:50 +0900
Eliot Courtney <ecourtney@nvidia.com> wrote:

Looks good to me.

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

> Make `Cmdq` a pinned type. This is needed to use Mutex, which is
> needed to add locking to `Cmdq`.
> 
> 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 e1ca1bb9e07d..44c3e960c965
> 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,
>          })
>      }
>  
> 


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

* Re: [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq
  2026-02-25 13:41 ` [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
@ 2026-02-25 19:48   ` Zhi Wang
  0 siblings, 0 replies; 10+ messages in thread
From: Zhi Wang @ 2026-02-25 19: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 Wed, 25 Feb 2026 22:41:51 +0900
Eliot Courtney <ecourtney@nvidia.com> wrote:

Looks good to me.

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

> 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.
> 
> Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
> ---
>  drivers/gpu/nova-core/gsp/boot.rs      |   8 +-
>  drivers/gpu/nova-core/gsp/cmdq.rs      | 260
> ++++++++++++++++++---------------
> drivers/gpu/nova-core/gsp/commands.rs  |   4 +-
> drivers/gpu/nova-core/gsp/sequencer.rs |   2 +- 4 files changed, 152
> insertions(+), 122 deletions(-)
> 
> diff --git a/drivers/gpu/nova-core/gsp/boot.rs
> b/drivers/gpu/nova-core/gsp/boot.rs index 1cb21da855b9..cb583f57666a
> 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,
> @@ -232,13 +232,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.as_ref(), "GPU name: {}\n",
> name), Err(e) => dev_warn!(pdev.as_ref(), "GPU name unavailable:
> {:?}\n", e), diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs
> b/drivers/gpu/nova-core/gsp/cmdq.rs index 44c3e960c965..faf1e9d5072b
> 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 @@ -802,9 +709,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
> b42e32dcc55c..7ceff2e6bd63 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
> 2fa2a0792cec..bc94b8567c6a 100644 ---
> a/drivers/gpu/nova-core/gsp/sequencer.rs +++
> b/drivers/gpu/nova-core/gsp/sequencer.rs @@ -366,7 +366,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,
> 


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

* Re: [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq`
  2026-02-25 19:42   ` Zhi Wang
@ 2026-02-26  0:42     ` Eliot Courtney
  0 siblings, 0 replies; 10+ messages in thread
From: Eliot Courtney @ 2026-02-26  0:42 UTC (permalink / raw)
  To: Zhi Wang, Eliot Courtney
  Cc: Danilo Krummrich, Alice Ryhl, Alexandre Courbot, Simona Vetter,
	Benno Lossin, Gary Guo, nouveau, dri-devel, linux-kernel,
	rust-for-linux

On Thu Feb 26, 2026 at 4:42 AM JST, Zhi Wang wrote:
> On Wed, 25 Feb 2026 22:41:49 +0900
> Eliot Courtney <ecourtney@nvidia.com> wrote:
>
>> Add sync and async command queue API and the type infrastructure to
>> know what reply is expected from each `CommandToGsp`.
>> 
>
> snip
>
>> +        loop {
>> +            match self.receive_msg::<M::Reply>(Delta::from_secs(10))
>
> It turns out here the timeout is changed to 10s from 5s which was used
> in other places. Any problem you encountered during the debugging?

No issues, but it /looked/ to me like the particular values didn't
really matter so it would be fine and simpler to have a uniform 10
second timeout for now. Please LMK if you know this is wrong and the
5 second value is important and specifically chosen.

I think it's also fine to include the timeout as a parameter and would
also fit the convention with receive_msg.

Thanks for the review~

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

end of thread, other threads:[~2026-02-26  0:42 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-25 13:41 [PATCH 0/4] gpu: nova-core: gsp: add locking to Cmdq Eliot Courtney
2026-02-25 13:41 ` [PATCH 1/4] gpu: nova-core: gsp: fix stale doc comments on command queue methods Eliot Courtney
2026-02-25 19:42   ` Zhi Wang
2026-02-25 13:41 ` [PATCH 2/4] gpu: nova-core: gsp: add sync and async command queue API to `Cmdq` Eliot Courtney
2026-02-25 19:42   ` Zhi Wang
2026-02-26  0:42     ` Eliot Courtney
2026-02-25 13:41 ` [PATCH 3/4] gpu: nova-core: gsp: make `Cmdq` a pinned type Eliot Courtney
2026-02-25 19:43   ` Zhi Wang
2026-02-25 13:41 ` [PATCH 4/4] gpu: nova-core: gsp: add mutex locking to Cmdq Eliot Courtney
2026-02-25 19:48   ` Zhi Wang

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