Skip to contentSkip to conversation

gpu: nova-core: gsp: fix command queue ring buffer bugs

PATCH Superseded Eliot Courtney 4 participants 15 messages Jan 23 – Jan 28

Subsystems (4)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

v2 cover gpu: nova-core: gsp: fix command queue ring buffer bugs

This series fixes a few bugs in the GSP command queue ring buffer implementation in nova-core and also clarifies some of the comments.

The ring buffer uses read and write pointers (rx/tx) to track which areas are available for the CPU vs the GSP to read/write into.

In the ring buffers there were some indexing issues which could end up causing panics, so I fixed those and added more rigorous proofs of correctness in the panic comments.

---
Changes in v2:
- Modified PANIC comments to match existing style.
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Eliot Courtney (4):
gpu: nova-core: gsp: fix incorrect advancing of write pointer
gpu: nova-core: gsp: clarify comments about invariants and pointer roles
gpu: nova-core: gsp: fix improper handling of empty slot in cmdq
gpu: nova-core: gsp: fix improper indexing in driver_read_area

 drivers/gpu/nova-core/gsp/cmdq.rs | 72 ++++++++++++++++++++++++---------------
 1 file changed, 44 insertions(+), 28 deletions(-)

---
base-commit: 6ea52b6d8f33ae627f4dcf43b12b6e713a8b9331
change-id: 20260121-nova-core-cmdq1-6aaa369824c4

Best regards,

Signature · 1 line
Eliot Courtney <[email protected]>
#1 lore raw

PATCH v2 1/4 gpu: nova-core: gsp: fix incorrect advancing of write pointer

We should modulo not bitwise-and here. The current code could, for example, set wptr to MSGQ_NUM_PAGES which is not valid.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
index 46819a82a51a..f139aad7af3f 100644
@@ -384,7 +384,7 @@ fn cpu_write_ptr(&self) -> u32 {
 
     // Informs the GSP that it can process `elem_count` new pages from the command queue.
     fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
-        let wptr = self.cpu_write_ptr().wrapping_add(elem_count) & MSGQ_NUM_PAGES;
+        let wptr = self.cpu_write_ptr().wrapping_add(elem_count) % MSGQ_NUM_PAGES;
         let gsp_mem = self.0.start_ptr_mut();
 
         // SAFETY:
Signature · 1 line
2.52.0
#2 lore raw

PATCH v2 2/4 gpu: nova-core: gsp: clarify comments about invariants and pointer roles

In reply to Eliot Courtney

Disambiguate a few things in comments in cmdq.rs.

---
 drivers/gpu/nova-core/gsp/cmdq.rs | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +10 -8
index f139aad7af3f..09c28eeb6f12 100644
@@ -161,12 +161,14 @@ struct GspMem {
     /// Self-mapping page table entries.
     ptes: PteArray<{ GSP_PAGE_SIZE / size_of::<u64>() }>,
     /// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
-    /// write and read pointers that the CPU updates.
+    /// write and read pointers that the CPU updates. This means that the read pointer here is an
+    /// index into the GSP queue.
     ///
     /// This member is read-only for the GSP.
     cpuq: Msgq,
     /// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
-    /// write and read pointers that the GSP updates.
+    /// write and read pointers that the GSP updates. This means that the read pointer here is an
+    /// index into the CPU queue.
     ///
     /// This member is read-only for the driver.
     gspq: Msgq,
@@ -222,7 +224,7 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &mut unsafe { self.0.as_slice_mut(0, 1) }.unwrap()[0];
-        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
         if rx <= tx {
@@ -257,7 +259,7 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &unsafe { self.0.as_slice(0, 1) }.unwrap()[0];
-        // PANIC: per the invariant of `cpu_read_ptr`, `xx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);
 
         match tx.cmp(&rx) {
@@ -315,7 +317,7 @@ fn allocate_command(&mut self, size: usize) -> Result<GspCommand<'_>> {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn gsp_write_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -329,7 +331,7 @@ fn gsp_write_ptr(&self) -> u32 {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn gsp_read_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -343,7 +345,7 @@ fn gsp_read_ptr(&self) -> u32 {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn cpu_read_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -372,7 +374,7 @@ fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn cpu_write_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
Signature · 1 line
2.52.0
reviewer
#3 lore raw

Re: [PATCH v2 2/4] gpu: nova-core: gsp: clarify comments about invariants and pointer roles

Quoted 29 lines · Eliot Courtney

Disambiguate a few things in comments in cmdq.rs.

---
 drivers/gpu/nova-core/gsp/cmdq.rs | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +4 -2
index f139aad7af3f..09c28eeb6f12 100644
@@ -161,12 +161,14 @@ struct GspMem {
     /// Self-mapping page table entries.
     ptes: PteArray<{ GSP_PAGE_SIZE / size_of::<u64>() }>,
     /// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
-    /// write and read pointers that the CPU updates.
+    /// write and read pointers that the CPU updates. This means that the read pointer here is an
+    /// index into the GSP queue.
     ///
     /// This member is read-only for the GSP.
     cpuq: Msgq,
     /// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
-    /// write and read pointers that the GSP updates.
+    /// write and read pointers that the GSP updates. This means that the read pointer here is an
+    /// index into the CPU queue.
     ///
     /// This member is read-only for the driver.
     gspq: Msgq,

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 227

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
@@ -222,7 +224,7 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &mut unsafe { self.0.as_slice_mut(0, 1) }.unwrap()[0];
-        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.

Can this just be tx < MSGQ_NUM_PAGES?

Quoted 13 lines
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
         if rx <= tx {
@@ -257,7 +259,7 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &unsafe { self.0.as_slice(0, 1) }.unwrap()[0];
-        // PANIC: per the invariant of `cpu_read_ptr`, `xx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);
 
         match tx.cmp(&rx) {

drivers/gpu/nova-core/gsp/cmdq.rs fn allocate_command(&mut self, size: usize) -> Result<GspCommand<'_>> { · line 320

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
@@ -315,7 +317,7 @@ fn allocate_command(&mut self, size: usize) -> Result<GspCommand<'_>> {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.

I wonder if this can be is within 0..MSGQ_NUM_PAGES. What do others think?

Best,
Gary

Quoted 30 lines
drivers/gpu/nova-core/gsp/cmdq.rs +3 -3
     fn gsp_write_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -329,7 +331,7 @@ fn gsp_write_ptr(&self) -> u32 {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn gsp_read_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -343,7 +345,7 @@ fn gsp_read_ptr(&self) -> u32 {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn cpu_read_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
@@ -372,7 +374,7 @@ fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
     //
     // # Invariants
     //
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
     fn cpu_write_ptr(&self) -> u32 {
         let gsp_mem = self.0.start_ptr();
 
#4 lore raw

Re: [PATCH v2 2/4] gpu: nova-core: gsp: clarify comments about invariants and pointer roles

Quoted 7 lines · Gary Guo

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 227

Quoted 5 lines
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &mut unsafe { self.0.as_slice_mut(0, 1) }.unwrap()[0];
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
-        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.

Can this just be tx < MSGQ_NUM_PAGES?

In previous discussion[1] it's been noted that it's important to explain why the preconditions are satisfied, not just what they are, so that's the reason I kept this in. Happy to hear arguments either way though.

[1]: https://lore.kernel.org/all/CAH5fLgg0O=t2T2MQH2hvm4eZnCOa_NffzRw=XZPi9+7+=XsmRg@mail.gmail.com/

Quoted 6 lines

Review

Quoted 4 lines
     // # Invariants
     //
(quoted diff) +1 -1
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.

I wonder if this can be is within 0..MSGQ_NUM_PAGES. What do others think?

I think this is very reasonable, since this is part of the rust range syntax so it should be understandable. I also considered the mathematical syntax [0, MSGQ_NUM_PAGES), but not sure if this would be conventional - it does seem that this notation is used in a bunch of places though. Will apply your suggestion in the next version unless there is a definitive convention for this.

#5 lore raw

Re: [PATCH v2 2/4] gpu: nova-core: gsp: clarify comments about invariants and pointer roles

Quoted 7 lines · Eliot Courtney
Quoted 6 lines · Gary Guo
Quoted 5 lines
         // - We will only access the driver-owned part of the shared memory.
         // - Per the safety statement of the function, no concurrent access will be performed.
         let gsp_mem = &mut unsafe { self.0.as_slice_mut(0, 1) }.unwrap()[0];
drivers/gpu/nova-core/gsp/cmdq.rs +1 -1
-        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `<= MSGQ_NUM_PAGES`.
+        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.
Quoted 8 lines
Quoted 1 line

Can this just be tx < MSGQ_NUM_PAGES?

In previous discussion[1] it's been noted that it's important to explain why the preconditions are satisfied, not just what they are, so that's the reason I kept this in. Happy to hear arguments either way though.

[1]: https://lore.kernel.org/all/CAH5fLgg0O=t2T2MQH2hvm4eZnCOa_NffzRw=XZPi9+7+=XsmRg@mail.gmail.com/

I agree it's a good idea to mention the source of the invariant, lest we may lose track of why it is indeed true.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
     // # Invariants
     //
(quoted diff) +1 -1
-    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
+    // - The returned value is between `0` and `MSGQ_NUM_PAGES - 1`, inclusive.
Quoted 8 lines
Quoted 1 line

I wonder if this can be is within 0..MSGQ_NUM_PAGES. What do others think?

I think this is very reasonable, since this is part of the rust range syntax so it should be understandable. I also considered the mathematical syntax [0, MSGQ_NUM_PAGES), but not sure if this would be conventional - it does seem that this notation is used in a bunch of places though. Will apply your suggestion in the next version unless there is a definitive convention for this.

Since this is Rust code, the Rust syntax to express ranges (within `` quotes) makes sense IMHO.

maintainer
#6 lore raw

Re: [PATCH v2 2/4] gpu: nova-core: gsp: clarify comments about invariants and pointer roles

Quoted 11 lines · Alexandre Courbot
Quoted 9 lines · Eliot Courtney
Quoted 1 line · Gary Guo

I wonder if this can be is within 0..MSGQ_NUM_PAGES. What do others think?

I think this is very reasonable, since this is part of the rust range syntax so it should be understandable. I also considered the mathematical syntax [0, MSGQ_NUM_PAGES), but not sure if this would be conventional - it does seem that this notation is used in a bunch of places though. Will apply your suggestion in the next version unless there is a definitive convention for this.

Quoted 2 lines

Since this is Rust code, the Rust syntax to express ranges (within `` quotes) makes sense IMHO.

While I really like the mathematical syntax, I think using the Rust syntax is superior, as it requires zero mental cycles to translate it to what is likely to be found in the code as well.

(There also have been some considerations of using tools to validate safety comments or invariants to some extend eventually.)

#7 lore raw

PATCH v2 3/4 gpu: nova-core: gsp: fix improper handling of empty slot in cmdq

In reply to Eliot Courtney

The current code hands out buffers that go all the way up to and including rx - 1, but we need to maintain an empty slot to prevent the ring buffer from wrapping around into having 'tx == rx', which means empty.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +19 -14
index 09c28eeb6f12..aa8758fc7723 100644
@@ -227,21 +227,26 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
-        if rx <= tx {
-            // The area from `tx` up to the end of the ring, and from the beginning of the ring up
-            // to `rx`, minus one unit, belongs to the driver.
-            if rx == 0 {
-                let last = after_tx.len() - 1;
-                (&mut after_tx[..last], &mut before_tx[0..0])
-            } else {
-                (after_tx, &mut before_tx[..rx])
-            }
+        // The area starting at `tx` and ending at `rx - 2` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for writing.
+        if rx == 0 {
+            // Since `rx` is zero, leave an empty slot at end of the buffer.
+            let last = after_tx.len() - 1;
+            (&mut after_tx[..last], &mut before_tx[0..0])
+        } else if rx > tx {
+            // The area is contiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - tx - 1` is non-negative because `rx > tx` in this branch.
+            // - The index does not exceed `after_tx.len()` (which is `MSGQ_NUM_PAGES - tx`)
+            //   because `rx < MSGQ_NUM_PAGES` by the `gsp_read_ptr` invariant.
+            (&mut after_tx[..(rx - tx - 1)], &mut before_tx[0..0])
         } else {
-            // The area from `tx` to `rx`, minus one unit, belongs to the driver.
-            //
-            // PANIC: per the invariants of `cpu_write_ptr` and `gsp_read_ptr`, `rx` and `tx` are
-            // `<= MSGQ_NUM_PAGES`, and the test above ensured that `rx > tx`.
-            (after_tx.split_at_mut(rx - tx).0, &mut before_tx[0..0])
+            // The area is discontiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - 1` is non-negative because `rx != 0` in this branch.
+            // - The index does not exceed `before_tx.len()` (which equals `tx`) because
+            //   `rx <= tx` in this branch.
+            (after_tx, &mut before_tx[..(rx - 1)])
         }
     }
 
Signature · 1 line
2.52.0
reviewer
#8 lore raw

Re: [PATCH v2 3/4] gpu: nova-core: gsp: fix improper handling of empty slot in cmdq

Quoted 26 lines · Eliot Courtney

The current code hands out buffers that go all the way up to and including rx - 1, but we need to maintain an empty slot to prevent the ring buffer from wrapping around into having 'tx == rx', which means empty.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +0 -5
index 09c28eeb6f12..aa8758fc7723 100644
@@ -227,21 +227,26 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
-        if rx <= tx {
-            // The area from `tx` up to the end of the ring, and from the beginning of the ring up
-            // to `rx`, minus one unit, belongs to the driver.
-            if rx == 0 {
-                let last = after_tx.len() - 1;

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 235

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +6 -4
-                (&mut after_tx[..last], &mut before_tx[0..0])
-            } else {
-                (after_tx, &mut before_tx[..rx])
-            }
+        // The area starting at `tx` and ending at `rx - 2` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for writing.
+        if rx == 0 {
+            // Since `rx` is zero, leave an empty slot at end of the buffer.
+            let last = after_tx.len() - 1;
+            (&mut after_tx[..last], &mut before_tx[0..0])

Does the address actually matter? Otherwise I would find &mut [] easier to understand than an empty indexing.

Quoted 9 lines
drivers/gpu/nova-core/gsp/cmdq.rs +7 -1
+        } else if rx > tx {
+            // The area is contiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - tx - 1` is non-negative because `rx > tx` in this branch.
+            // - The index does not exceed `after_tx.len()` (which is `MSGQ_NUM_PAGES - tx`)
+            //   because `rx < MSGQ_NUM_PAGES` by the `gsp_read_ptr` invariant.
+            (&mut after_tx[..(rx - tx - 1)], &mut before_tx[0..0])
         } else {
-            // The area from `tx` to `rx`, minus one unit, belongs to the driver.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 249

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +6 -4
-            //
-            // PANIC: per the invariants of `cpu_write_ptr` and `gsp_read_ptr`, `rx` and `tx` are
-            // `<= MSGQ_NUM_PAGES`, and the test above ensured that `rx > tx`.
-            (after_tx.split_at_mut(rx - tx).0, &mut before_tx[0..0])
+            // The area is discontiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - 1` is non-negative because `rx != 0` in this branch.
+            // - The index does not exceed `before_tx.len()` (which equals `tx`) because
+            //   `rx <= tx` in this branch.
+            (after_tx, &mut before_tx[..(rx - 1)])

If this is written with get_disjoint_mut, the indices would be so much easier to understand... To bad that that API is only available from 1.86 onwards.

Best,
Gary

Quoted 3 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -0
         }
     }
 
#9 lore raw

Re: [PATCH v2 3/4] gpu: nova-core: gsp: fix improper handling of empty slot in cmdq

Quoted 32 lines · Gary Guo
Quoted 31 lines · Eliot Courtney

The current code hands out buffers that go all the way up to and including rx - 1, but we need to maintain an empty slot to prevent the ring buffer from wrapping around into having 'tx == rx', which means empty.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +1 -9
index 09c28eeb6f12..aa8758fc7723 100644
@@ -227,21 +227,26 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
-        if rx <= tx {
-            // The area from `tx` up to the end of the ring, and from the beginning of the ring up
-            // to `rx`, minus one unit, belongs to the driver.
-            if rx == 0 {
-                let last = after_tx.len() - 1;
-                (&mut after_tx[..last], &mut before_tx[0..0])
-            } else {
-                (after_tx, &mut before_tx[..rx])
-            }
+        // The area starting at `tx` and ending at `rx - 2` modulo MSGQ_NUM_PAGES, inclusive,
Quoted 8 lines

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 235

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +5 -0
+        // belongs to the driver for writing.
+        if rx == 0 {
+            // Since `rx` is zero, leave an empty slot at end of the buffer.
+            let last = after_tx.len() - 1;
+            (&mut after_tx[..last], &mut before_tx[0..0])

Does the address actually matter? Otherwise I would find &mut [] easier to understand than an empty indexing.

The address doesn't matter, and indeed I am not sure why we did that (possibly a lifetime issue with some previous version of the code?).

Your suggestion seems to work fine, so Eliot feel free to include it in your series (as a separate patch please to make sure we can isolate the effects of both changes).

Quoted 21 lines
Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +7 -1
+        } else if rx > tx {
+            // The area is contiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - tx - 1` is non-negative because `rx > tx` in this branch.
+            // - The index does not exceed `after_tx.len()` (which is `MSGQ_NUM_PAGES - tx`)
+            //   because `rx < MSGQ_NUM_PAGES` by the `gsp_read_ptr` invariant.
+            (&mut after_tx[..(rx - tx - 1)], &mut before_tx[0..0])
         } else {
-            // The area from `tx` to `rx`, minus one unit, belongs to the driver.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 249

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +6 -4
-            //
-            // PANIC: per the invariants of `cpu_write_ptr` and `gsp_read_ptr`, `rx` and `tx` are
-            // `<= MSGQ_NUM_PAGES`, and the test above ensured that `rx > tx`.
-            (after_tx.split_at_mut(rx - tx).0, &mut before_tx[0..0])
+            // The area is discontiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - 1` is non-negative because `rx != 0` in this branch.
+            // - The index does not exceed `before_tx.len()` (which equals `tx`) because
+            //   `rx <= tx` in this branch.
+            (after_tx, &mut before_tx[..(rx - 1)])
Quoted 2 lines

If this is written with get_disjoint_mut, the indices would be so much easier to understand... To bad that that API is only available from 1.86 onwards.

I for one am longing for split_at_checked... :)

#10 lore raw

Re: [PATCH v2 3/4] gpu: nova-core: gsp: fix improper handling of empty slot in cmdq

In reply to Eliot Courtney
Quoted 19 lines · Eliot Courtney

The current code hands out buffers that go all the way up to and including rx - 1, but we need to maintain an empty slot to prevent the ring buffer from wrapping around into having 'tx == rx', which means empty.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +0 -0
index 09c28eeb6f12..aa8758fc7723 100644
@@ -227,21 +227,26 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `< MSGQ_NUM_PAGES`.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 237

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -8
         let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);
 
-        if rx <= tx {
-            // The area from `tx` up to the end of the ring, and from the beginning of the ring up
-            // to `rx`, minus one unit, belongs to the driver.
-            if rx == 0 {
-                let last = after_tx.len() - 1;
-                (&mut after_tx[..last], &mut before_tx[0..0])
-            } else {
-                (after_tx, &mut before_tx[..rx])

Indeed, the comment clearly states "minus one unit" but the second branch (and the one below) ignores that. >_<

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 231

Quoted 3 lines
drivers/gpu/nova-core/gsp/cmdq.rs +2 -1
-            }
+        // The area starting at `tx` and ending at `rx - 2` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for writing.

I have trouble parsing this comment - it says rx - 2 but nowhere does that expression appear in the code. It's particularly confusing because it apparently applies to all 3 blocks, but the position suggests it is only the first. I'd add a blank line after it to signal that it is relevant to the rest of the method.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 236

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +5 -0
+        if rx == 0 {
+            // Since `rx` is zero, leave an empty slot at end of the buffer.
+            let last = after_tx.len() - 1;
+            (&mut after_tx[..last], &mut before_tx[0..0])
+        } else if rx > tx {

In the original code, the rx < tx case appears first. Can we preserve this order so the corresponding diffs also appear next to one another?

Quoted 8 lines
drivers/gpu/nova-core/gsp/cmdq.rs +6 -1
+            // The area is contiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - tx - 1` is non-negative because `rx > tx` in this branch.
+            // - The index does not exceed `after_tx.len()` (which is `MSGQ_NUM_PAGES - tx`)
+            //   because `rx < MSGQ_NUM_PAGES` by the `gsp_read_ptr` invariant.
+            (&mut after_tx[..(rx - tx - 1)], &mut before_tx[0..0])
         } else {
-            // The area from `tx` to `rx`, minus one unit, belongs to the driver.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 249

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +6 -4
-            //
-            // PANIC: per the invariants of `cpu_write_ptr` and `gsp_read_ptr`, `rx` and `tx` are
-            // `<= MSGQ_NUM_PAGES`, and the test above ensured that `rx > tx`.
-            (after_tx.split_at_mut(rx - tx).0, &mut before_tx[0..0])
+            // The area is discontiguous and we leave an empty slot before `rx`.
+            // PANIC:
+            // - The index `rx - 1` is non-negative because `rx != 0` in this branch.
+            // - The index does not exceed `before_tx.len()` (which equals `tx`) because
+            //   `rx <= tx` in this branch.
+            (after_tx, &mut before_tx[..(rx - 1)])

In any case, nice catch! This would have bitten us sooner or later.

#11 lore raw

PATCH v2 4/4 gpu: nova-core: gsp: fix improper indexing in driver_read_area

In reply to Eliot Courtney

The current code indexes into after_rx using tx which is an index for the whole buffer, not the split buffer after_rx.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +14 -5
index aa8758fc7723..c26396fda29c 100644
@@ -1,7 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use core::{
-    cmp,
     mem,
     sync::atomic::{
         fence,
@@ -267,10 +266,20 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);
 
-        match tx.cmp(&rx) {
-            cmp::Ordering::Equal => (&after_rx[0..0], &after_rx[0..0]),
-            cmp::Ordering::Greater => (&after_rx[..tx], &before_rx[0..0]),
-            cmp::Ordering::Less => (after_rx, &before_rx[..tx]),
+        // The area starting at `rx` and ending at `tx - 1` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for reading.
+        if rx <= tx {
+            // The area is contiguous.
+            // PANIC:
+            // - The index `tx - rx` is non-negative because `rx <= tx` in this branch.
+            // - The index does not exceed `after_rx.len()` (which is `MSGQ_NUM_PAGES - rx`)
+            //   because `tx < MSGQ_NUM_PAGES` by the `gsp_write_ptr` invariant.
+            (&after_rx[..(tx - rx)], &after_rx[0..0])
+        } else {
+            // The area is discontiguous.
+            // PANIC: `tx` does not exceed `before_rx.len()` (which equals `rx`) because
+            //   `tx < rx` in this branch.
+            (after_rx, &before_rx[..tx])
         }
     }
 
Signature · 1 line
2.52.0
reviewer
#12 lore raw

Re: [PATCH v2 4/4] gpu: nova-core: gsp: fix improper indexing in driver_read_area

Quoted 24 lines · Eliot Courtney

The current code indexes into after_rx using tx which is an index for the whole buffer, not the split buffer after_rx.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +0 -1
index aa8758fc7723..c26396fda29c 100644
@@ -1,7 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use core::{
-    cmp,
     mem,
     sync::atomic::{
         fence,

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 267

Quoted 2 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -0
@@ -267,10 +266,20 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);

This code doesn't need splitting as it doesn't have the uniqueness issue that mutable references have. While you're at it, probably it's chance to simplify the code.

Quoted 4 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -3
-        match tx.cmp(&rx) {
-            cmp::Ordering::Equal => (&after_rx[0..0], &after_rx[0..0]),
-            cmp::Ordering::Greater => (&after_rx[..tx], &before_rx[0..0]),

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 277

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +9 -1
-            cmp::Ordering::Less => (after_rx, &before_rx[..tx]),
+        // The area starting at `rx` and ending at `tx - 1` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for reading.
+        if rx <= tx {
+            // The area is contiguous.
+            // PANIC:
+            // - The index `tx - rx` is non-negative because `rx <= tx` in this branch.
+            // - The index does not exceed `after_rx.len()` (which is `MSGQ_NUM_PAGES - rx`)
+            //   because `tx < MSGQ_NUM_PAGES` by the `gsp_write_ptr` invariant.
+            (&after_rx[..(tx - rx)], &after_rx[0..0])

This can be just (&data[rx..tx], &[]) without the split.

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 282

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +5 -0
+        } else {
+            // The area is discontiguous.
+            // PANIC: `tx` does not exceed `before_rx.len()` (which equals `rx`) because
+            //   `tx < rx` in this branch.
+            (after_rx, &before_rx[..tx])

This can be just (&data[rx..], &data[..tx]) without the split.

Best,
Gary

Quoted 3 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -0
         }
     }
 
#13 lore raw

Re: [PATCH v2 4/4] gpu: nova-core: gsp: fix improper indexing in driver_read_area

Quoted 49 lines · Gary Guo
Quoted 24 lines · Eliot Courtney

The current code indexes into after_rx using tx which is an index for the whole buffer, not the split buffer after_rx.

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +0 -1
index aa8758fc7723..c26396fda29c 100644
@@ -1,7 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use core::{
-    cmp,
     mem,
     sync::atomic::{
         fence,

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 267

Quoted 2 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -0
@@ -267,10 +266,20 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);

This code doesn't need splitting as it doesn't have the uniqueness issue that mutable references have. While you're at it, probably it's chance to simplify the code.

Quoted 4 lines
drivers/gpu/nova-core/gsp/cmdq.rs +0 -3
-        match tx.cmp(&rx) {
-            cmp::Ordering::Equal => (&after_rx[0..0], &after_rx[0..0]),
-            cmp::Ordering::Greater => (&after_rx[..tx], &before_rx[0..0]),

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 277

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +9 -1
-            cmp::Ordering::Less => (after_rx, &before_rx[..tx]),
+        // The area starting at `rx` and ending at `tx - 1` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for reading.
+        if rx <= tx {
+            // The area is contiguous.
+            // PANIC:
+            // - The index `tx - rx` is non-negative because `rx <= tx` in this branch.
+            // - The index does not exceed `after_rx.len()` (which is `MSGQ_NUM_PAGES - rx`)
+            //   because `tx < MSGQ_NUM_PAGES` by the `gsp_write_ptr` invariant.
+            (&after_rx[..(tx - rx)], &after_rx[0..0])

This can be just (&data[rx..tx], &[]) without the split.

Quoted 7 lines

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 282

Quoted 5 lines
drivers/gpu/nova-core/gsp/cmdq.rs +5 -0
+        } else {
+            // The area is discontiguous.
+            // PANIC: `tx` does not exceed `before_rx.len()` (which equals `rx`) because
+            //   `tx < rx` in this branch.
+            (after_rx, &before_rx[..tx])

This can be just (&data[rx..], &data[..tx]) without the split.

Indeed, this is arguably easier to understand.

#14 lore raw

Re: [PATCH v2 4/4] gpu: nova-core: gsp: fix improper indexing in driver_read_area

In reply to Eliot Courtney
Quoted 2 lines · Eliot Courtney

The current code indexes into after_rx using tx which is an index for the whole buffer, not the split buffer after_rx.

Another dangerous one! Well found.

Quoted 28 lines

Also add more rigorous no-panic proofs.

Fixes 75f6b1de8133 ("gpu: nova-core: gsp: Add GSP command queue bindings and handling")
---
 drivers/gpu/nova-core/gsp/cmdq.rs | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gsp/cmdq.rs +0 -4
index aa8758fc7723..c26396fda29c 100644
@@ -1,7 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use core::{
-    cmp,
     mem,
     sync::atomic::{
         fence,
@@ -267,10 +266,20 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
         // PANIC: per the invariant of `cpu_read_ptr`, `rx` is `< MSGQ_NUM_PAGES`.
         let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);
 
-        match tx.cmp(&rx) {
-            cmp::Ordering::Equal => (&after_rx[0..0], &after_rx[0..0]),
-            cmp::Ordering::Greater => (&after_rx[..tx], &before_rx[0..0]),

drivers/gpu/nova-core/gsp/cmdq.rs fn new(dev: &device::Device<device::Bound>) -> Result<Self> { · line 277

Quoted 10 lines
drivers/gpu/nova-core/gsp/cmdq.rs +9 -1
-            cmp::Ordering::Less => (after_rx, &before_rx[..tx]),
+        // The area starting at `rx` and ending at `tx - 1` modulo MSGQ_NUM_PAGES, inclusive,
+        // belongs to the driver for reading.
+        if rx <= tx {
+            // The area is contiguous.
+            // PANIC:
+            // - The index `tx - rx` is non-negative because `rx <= tx` in this branch.
+            // - The index does not exceed `after_rx.len()` (which is `MSGQ_NUM_PAGES - rx`)
+            //   because `tx < MSGQ_NUM_PAGES` by the `gsp_write_ptr` invariant.
+            (&after_rx[..(tx - rx)], &after_rx[0..0])

I guess we can also use &[] here (maybe convert the whole file in the same patch).