The producer and consumer indexes of a GENET DMA ring are 16-bit
counters (Linux bcmgenet masks them with DMA_P_INDEX_MASK and
DMA_C_INDEX_MASK). bcm2838_genet_tx() counted the consumer index up in
a uint32_t and compared it with the 16-bit producer index, so the first
transmit that crossed the wrap (consumer 0xfffe, producer 0x0001) never
ended the loop: the device sent the ring's descriptors forever with the
BQL held, and the guest and the monitor hung. A guest sending a
sustained stream hung after about 65k descriptors on one ring.

Wrap the index, and test a transmit across the wrap.

Signed-off-by: Lorand Marton <[email protected]>
---
 hw/net/bcm2838_genet.c           |   4 +-
 tests/qtest/bcm2838-genet-test.c | 101 +++++++++++++++++++++++++++++++
 tests/qtest/meson.build          |   2 +
 3 files changed, 106 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/bcm2838-genet-test.c

diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 53bba99b38..557a8a9636 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -521,7 +521,9 @@ static uint64_t bcm2838_genet_tx(BCM2838GenetState *s, 
unsigned int ring_index,
         }
 
         num_descs++;
-        cons_index_fld++;
+        /* The indexes count modulo 2^16 */
+        cons_index_fld = (cons_index_fld + 1)
+                         & R_GENET_DMA_CONS_INDEX_INDEX_MASK;
         s->regs.tdma.descs[desc_index].length_status =
             FIELD_DP32(s->regs.tdma.descs[desc_index].length_status,
                        GENET_RDMA_LENGTH_STATUS, OWN, 1);
diff --git a/tests/qtest/bcm2838-genet-test.c b/tests/qtest/bcm2838-genet-test.c
new file mode 100644
index 0000000000..393abd7da2
--- /dev/null
+++ b/tests/qtest/bcm2838-genet-test.c
@@ -0,0 +1,101 @@
+/*
+ * QTest testcase for the BCM2838 GENET DMA rings
+ *
+ * Frames go out over a socket netdev. The producer and consumer indexes
+ * of a ring count modulo 2^16.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/bswap.h"
+#include "libqtest.h"
+
+#define GENET_BASE          0xfd580000
+
+#define RING_START_ADDR     0x14
+#define RING_END_ADDR       0x1c
+
+#define TDMA_DESC(i)        (0x4000 + (i) * 12)
+#define TDMA_RING(r)        (0x4c00 + (r) * 0x40)
+#define TRING_READ_PTR      0x00
+#define TRING_CONS_INDEX    0x08
+#define TRING_PROD_INDEX    0x0c
+#define TDMA_RING_CFG       0x5040
+#define TDMA_CTRL           0x5044
+#define TSB_LEN             64      /* transmit status block */
+
+#define DESCS_PER_RING      4
+#define BUF_SIZE            2048
+#define BUF_BASE            0x100000
+#define FRAME_LEN           60
+
+static void genet_writel(QTestState *qts, uint32_t off, uint32_t val)
+{
+    qtest_writel(qts, GENET_BASE + off, val);
+}
+
+static uint32_t genet_readl(QTestState *qts, uint32_t off)
+{
+    return qtest_readl(qts, GENET_BASE + off);
+}
+
+/* Transmit across the wrap of the 16-bit consumer index */
+static void test_tx_index_wrap(void)
+{
+    const unsigned r = 0, n = 3;
+    const uint16_t first = 0xfffe;
+    uint8_t frame[FRAME_LEN] = { 0 };
+    uint8_t pkt[4 + FRAME_LEN];
+    int sv[2];
+    QTestState *qts;
+
+    g_assert_cmpint(socketpair(PF_UNIX, SOCK_STREAM, 0, sv), ==, 0);
+    qts = qtest_initf("-M raspi4b -nic socket,fd=%d,model=bcm2838-genet",
+                      sv[1]);
+    close(sv[1]);
+
+    memset(frame, 0xff, 6);
+    stw_be_p(frame + 12, 0x88b5);
+    for (unsigned i = 0; i < n; i++) {
+        frame[14] = i;
+        qtest_memset(qts, BUF_BASE + i * BUF_SIZE, 0, TSB_LEN);
+        qtest_memwrite(qts, BUF_BASE + i * BUF_SIZE + TSB_LEN, frame,
+                       sizeof(frame));
+        /* SOP, EOP, buffer length */
+        genet_writel(qts, TDMA_DESC(i), BIT(13) | BIT(14) |
+                     (TSB_LEN + FRAME_LEN) << 16);
+        genet_writel(qts, TDMA_DESC(i) + 4, BUF_BASE + i * BUF_SIZE);
+        genet_writel(qts, TDMA_DESC(i) + 8, 0);
+    }
+    genet_writel(qts, TDMA_RING(r) + RING_START_ADDR, 0);
+    genet_writel(qts, TDMA_RING(r) + RING_END_ADDR, DESCS_PER_RING * 3 - 1);
+    genet_writel(qts, TDMA_RING(r) + TRING_READ_PTR, 0);
+    genet_writel(qts, TDMA_RING(r) + TRING_CONS_INDEX, first);
+    genet_writel(qts, TDMA_RING(r) + TRING_PROD_INDEX, first);
+    genet_writel(qts, TDMA_RING_CFG, BIT(r));
+    genet_writel(qts, TDMA_CTRL, 1 | BIT(1 + r));
+
+    genet_writel(qts, TDMA_RING(r) + TRING_PROD_INDEX,
+                 (uint16_t)(first + n));
+    g_assert_cmphex(genet_readl(qts, TDMA_RING(r) + TRING_CONS_INDEX) &
+                    0xffff, ==, (uint16_t)(first + n));
+
+    for (unsigned i = 0; i < n; i++) {
+        g_assert_cmpint(recv(sv[0], pkt, sizeof(pkt), MSG_WAITALL), ==,
+                        sizeof(pkt));
+        g_assert_cmpuint(ldl_be_p(pkt), ==, FRAME_LEN);
+        g_assert_cmpuint(pkt[4 + 14], ==, i);
+    }
+
+    qtest_quit(qts);
+    close(sv[0]);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/bcm2838-genet/tx-index-wrap", test_tx_index_wrap);
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 46d4fc1d2b..36af0b267a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -284,6 +284,8 @@ qtests_aarch64 = \
   (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 
'fuzz-xlnx-dp-test'] : []) + \
   (config_all_devices.has_key('CONFIG_XLNX_VERSAL') ? ['xlnx-canfd-test', 
'xlnx-versal-trng-test'] : []) + \
   (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 
'bcm2835-i2c-test', 'bcm2835-ic-test'] : []) +  \
+  (config_all_devices.has_key('CONFIG_RASPI') and host_os != 'windows' ?
+    ['bcm2838-genet-test'] : []) + \
   (config_all_accel.has_key('CONFIG_TCG') and                                  
          \
    config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 
'tpm-tis-i2c-swtpm-test'] : []) + \
   (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \
-- 
2.56.0


Reply via email to