Commit 9059c634f0 for qemu.org
commit 9059c634f0c1e3a28f8b2e2661142c8962f4e2a6
Author: Lorand Marton <lorandmarton.dev@gmail.com>
Date: Mon Nov 9 13:52:08 2026 +0000
hw/net/bcm2838_genet: wrap the TX consumer index at 16 bits
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 <lorandmarton.dev@gmail.com>
Reviewed-by: Marcelo Manzo <marcelomanzo@gmail.com>
Tested-by: Marcelo Manzo <marcelomanzo@gmail.com>
Message-id: 0bd9bd85fd70c12f7e9ad77ad92a0ac1a778a29e.1793613972.git.lorandmarton.dev@gmail.com
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
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 ff4bf9528a..7994696086 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -287,6 +287,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 : []) + \