From: Marcelo Manzo <marcelomanzo@gmail.com>
To: qemu-devel@nongnu.org, qemu-arm@nongnu.org
Cc: peter.maydell@linaro.org, philmd@oss.qualcomm.com,
Sergey Kambalin <serg.oker@gmail.com>,
Sergey Kambalin <sergey.kambalin@auriga.com>,
Marcelo Manzo <marcelomanzo@gmail.com>,
Jason Wang <jasowangio@gmail.com>
Subject: [PATCH v4 12/17] hw/net/bcm2838_genet: implement GENET TX path
Date: Sun, 18 Oct 2026 09:35:58 -0400 [thread overview]
Message-ID: <20261018133603.64323-13-marcelomanzo@gmail.com> (raw)
In-Reply-To: <20261018133603.64323-1-marcelomanzo@gmail.com>
From: Sergey Kambalin <serg.oker@gmail.com>
Implement the transmit DMA ring handling: walk TX descriptors
between the consumer and producer index for a ring, pull frame data
out of guest memory, and hand completed frames to the netdev peer
via qemu_send_packet(), advancing the consumer index and raising the
TXDMA_MBDONE interrupt as descriptors complete.
Part of Sergey Kambalin's original Raspberry Pi 4B PCIe/GENET
networking series (patchwork series 829638, posted to qemu-devel in
2024, never merged). Carried forward and completed by Marcelo Manzo.
While carrying this forward, fix bcm2838_genet_tdma_ring_active():
it extracted the per-ring enable bit using the DMA_CTRL register's
1-bit EN field definition instead of DMA_RING_CFG's 17-bit EN field
(one bit per ring), so only ring 0 ever appeared active and TX
traffic hashed onto any other queue was silently dropped, eventually
tripping the guest's qdisc watchdog ("NETDEV WATCHDOG: transmit
queue N timed out") and breaking DHCP.
While carrying this forward, also:
- Check that a frame is at least a status block plus an Ethernet
header before looking at it, and that the descriptors of a frame
don't add up to more than the TX buffer, so a guest can't make us
read or write outside it.
- Check that reading the frame data from guest memory succeeded.
- Use lduw_be_p()/stw_be_p() for the length/type field.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 244 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 17 +++
2 files changed, 260 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 331a598855..7274a66d8f 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -237,6 +237,13 @@ REG16(GENET_PHY_EXP_SEL, 0)
FIELD(GENET_PHY_EXP_SEL, REG_ID, 0, 8)
FIELD(GENET_PHY_EXP_SEL, BLOCK_ID, 8, 8)
+REG32(GENET_TX_CSUM_INFO, 0)
+FIELD(GENET_TX_CSUM_INFO, OFFSET, 0, 15)
+FIELD(GENET_TX_CSUM_INFO, PROTO_UDP, 15, 1)
+FIELD(GENET_TX_CSUM_INFO, START, 16, 15)
+FIELD(GENET_TX_CSUM_INFO, LV, 30, 1)
+
+
static void bcm2838_genet_set_qemu_mac(BCM2838GenetState *s)
{
const MACAddr *addr = &s->nic_conf.macaddr;
@@ -402,6 +409,241 @@ static uint64_t bcm2838_genet_mdio_cmd(BCM2838GenetState *s, uint64_t cmd)
return cmd;
}
+/* Assemble a 64-bit value from its high and low 32-bit register halves */
+static uint64_t bcm2838_genet_make_u64(uint32_t hi, uint32_t lo)
+{
+ return ((uint64_t)hi << 32) | lo;
+}
+
+static void bcm2838_genet_xmit_packet(NetClientState *s, void *packet,
+ size_t size)
+{
+ uint8_t *buf = packet + sizeof(BCM2838GenetXmitStatus);
+ size_t len;
+ uint16_t len_type;
+
+ /* We need the status block and a whole Ethernet header (len/type at 12) */
+ if (size < sizeof(BCM2838GenetXmitStatus) + ETH_HLEN) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: TX frame too short (%zu bytes)\n", __func__, size);
+ return;
+ }
+
+ len = size - sizeof(BCM2838GenetXmitStatus);
+ net_checksum_calculate(buf, len, CSUM_ALL);
+
+ len_type = lduw_be_p(buf + 12);
+ if (len_type < MAX_PAYLOAD_SIZE) {
+ stw_be_p(buf + 12, len);
+ }
+
+ qemu_send_packet(s, buf, len);
+}
+
+static uint64_t bcm2838_genet_tx(BCM2838GenetState *s, unsigned int ring_index,
+ uint32_t prod_index,
+ uint32_t cons_index)
+{
+ BCM2838GenetTdmaRing *ring = &s->regs.tdma.rings[ring_index];
+ const unsigned int desc_size_words
+ = sizeof(BCM2838GenetTdmaDesc) / sizeof(uint32_t);
+ const uint64_t ring_start_addr
+ = bcm2838_genet_make_u64(ring->start_addr_hi, ring->start_addr);
+ const uint64_t ring_end_addr
+ = bcm2838_genet_make_u64(ring->end_addr_hi, ring->end_addr);
+
+ hwaddr data_addr;
+ uint64_t desc_index;
+ uint32_t desc_status = 0;
+ uint32_t buflength = 0;
+ uint64_t num_descs = 0;
+ uint64_t read_ptr
+ = bcm2838_genet_make_u64(ring->read_ptr_hi, ring->read_ptr);
+ size_t packet_off = 0;
+
+ uint32_t prod_index_fld = FIELD_EX32(prod_index,
+ GENET_DMA_PROD_INDEX, INDEX);
+ uint32_t cons_index_fld = FIELD_EX32(cons_index,
+ GENET_DMA_CONS_INDEX, INDEX);
+
+ while (cons_index_fld != prod_index_fld) {
+ desc_index = read_ptr / desc_size_words;
+ if (desc_index >= BCM2838_GENET_DMA_DESC_CNT) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: invalid TX descriptor index %" PRIu64 " (exceeds %u)\n",
+ __func__, desc_index, BCM2838_GENET_DMA_DESC_CNT - 1);
+ break;
+ }
+ desc_status = s->regs.tdma.descs[desc_index].length_status;
+ data_addr = bcm2838_genet_make_u64(
+ s->regs.tdma.descs[desc_index].address_hi,
+ s->regs.tdma.descs[desc_index].address_lo);
+ trace_bcm2838_genet_tx(ring_index, desc_index, desc_status,
+ data_addr);
+
+ if (FIELD_EX32(desc_status, GENET_RDMA_LENGTH_STATUS, SOP) != 0) {
+ packet_off = 0;
+ }
+
+ buflength = FIELD_EX32(desc_status,
+ GENET_RDMA_LENGTH_STATUS, BUFLENGTH);
+
+ if (packet_off + buflength > sizeof(s->tx_packet)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: TX frame too big (%zu + %u bytes, max %zu)\n",
+ __func__, packet_off, buflength,
+ sizeof(s->tx_packet));
+ break;
+ }
+
+ if (address_space_read(&s->dma_as, data_addr,
+ MEMTXATTRS_UNSPECIFIED,
+ s->tx_packet + packet_off,
+ buflength) != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: failed to read TX buffer at 0x%" HWADDR_PRIx
+ "\n", __func__, data_addr);
+ break;
+ }
+ packet_off += buflength;
+
+ if (FIELD_EX32(desc_status, GENET_RDMA_LENGTH_STATUS, EOP) != 0) {
+ bcm2838_genet_xmit_packet(qemu_get_queue(s->nic), s->tx_packet,
+ packet_off);
+ packet_off = 0;
+ }
+
+ num_descs++;
+ cons_index_fld++;
+ s->regs.tdma.descs[desc_index].length_status =
+ FIELD_DP32(s->regs.tdma.descs[desc_index].length_status,
+ GENET_RDMA_LENGTH_STATUS, OWN, 1);
+ read_ptr = read_ptr == ring_end_addr + 1 - desc_size_words
+ ? ring_start_addr : read_ptr + desc_size_words;
+ }
+
+ ring->read_ptr = read_ptr;
+ ring->read_ptr_hi = read_ptr >> 32;
+
+ return num_descs;
+}
+
+static bool bcm2838_genet_tdma_ring_active(BCM2838GenetState *s,
+ unsigned int ring_index)
+{
+ uint32_t ctrl_reg = s->regs.tdma.ctrl;
+ uint32_t ring_cfg_reg = s->regs.tdma.ring_cfg;
+ uint32_t ring_mask = 1 << ring_index;
+ bool dma_en = FIELD_EX32(ctrl_reg, GENET_DMA_CTRL, EN);
+ bool ring_en =
+ FIELD_EX32(ring_cfg_reg, GENET_DMA_RING_CFG, EN) & ring_mask;
+ bool ring_buf_en =
+ FIELD_EX32(ctrl_reg, GENET_DMA_CTRL, RING_BUF_EN) & ring_mask;
+ bool active = dma_en && ring_en && ring_buf_en;
+
+ trace_bcm2838_genet_tx_dma_ring_active(ring_index,
+ active ? "active" : "halted");
+ return active;
+}
+
+/* The guest wrote a ring's producer index: send what it has queued */
+static void bcm2838_genet_tdma_prod_index_write(BCM2838GenetState *s,
+ unsigned int ring_index,
+ uint32_t prod_index)
+{
+ BCM2838GenetTdmaRing *ring = &s->regs.tdma.rings[ring_index];
+ uint32_t cons_index = ring->cons_index;
+ uint32_t cons_index_fld = FIELD_EX32(cons_index,
+ GENET_DMA_CONS_INDEX, INDEX);
+ uint32_t prod_index_fld = FIELD_EX32(prod_index,
+ GENET_DMA_PROD_INDEX, INDEX);
+ uint64_t num_descs_tx;
+ uint32_t tx_intrs;
+
+ if (cons_index_fld == prod_index_fld) {
+ return;
+ }
+
+ trace_bcm2838_genet_tx_request(ring_index, prod_index_fld,
+ cons_index_fld);
+ num_descs_tx = bcm2838_genet_tx(s, ring_index, prod_index, cons_index);
+ if (num_descs_tx == 0) {
+ return;
+ }
+
+ ring->cons_index = FIELD_DP32(ring->cons_index, GENET_DMA_CONS_INDEX,
+ INDEX, cons_index + num_descs_tx);
+
+ if (ring_index == BCM2838_GENET_DMA_RING_DEFAULT) {
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat, GENET_INTRL_0,
+ TXDMA_MBDONE, 1);
+ } else {
+ tx_intrs = FIELD_EX32(s->regs.intrl1.stat, GENET_INTRL_1, TX_INTRS);
+ s->regs.intrl1.stat = FIELD_DP32(s->regs.intrl1.stat, GENET_INTRL_1,
+ TX_INTRS, tx_intrs | 1 << ring_index);
+ }
+}
+
+static void bcm2838_genet_tdma(BCM2838GenetState *s, hwaddr offset,
+ uint64_t value)
+{
+ hwaddr ring_offset;
+ unsigned int ring_index;
+ uint32_t prod_index = value;
+ uint32_t ring_cfg = value;
+ uint32_t dma_ctrl = value;
+
+ uint32_t exst_tdma_en =
+ FIELD_EX32(s->regs.tdma.ctrl, GENET_DMA_CTRL, EN);
+ uint32_t exst_ring_en =
+ FIELD_EX32(s->regs.tdma.ring_cfg, GENET_DMA_RING_CFG, EN);
+ uint32_t incm_tdma_en =
+ FIELD_EX32(dma_ctrl, GENET_DMA_CTRL, EN);
+ uint32_t incm_ring_en =
+ FIELD_EX32(ring_cfg, GENET_DMA_RING_CFG, EN);
+ uint32_t incm_ring_buf_en =
+ FIELD_EX32(dma_ctrl, GENET_DMA_CTRL, RING_BUF_EN);
+
+ switch (offset) {
+ case BCM2838_GENET_TDMA_RINGS
+ ... BCM2838_GENET_TDMA_RINGS + sizeof(s->regs.tdma.rings) - 1:
+ ring_index = (offset - BCM2838_GENET_TDMA_RINGS)
+ / sizeof(BCM2838GenetTdmaRing);
+ if (bcm2838_genet_tdma_ring_active(s, ring_index)) {
+ ring_offset = offset - BCM2838_GENET_TDMA_RINGS
+ - ring_index * sizeof(BCM2838GenetTdmaRing);
+ switch (ring_offset) {
+ case BCM2838_GENET_TRING_PROD_INDEX:
+ bcm2838_genet_tdma_prod_index_write(s, ring_index,
+ prod_index);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case BCM2838_GENET_TDMA_RING_CFG:
+ if (exst_ring_en != incm_ring_en) {
+ trace_bcm2838_genet_tx_dma_ring(incm_ring_en);
+ }
+ break;
+ case BCM2838_GENET_TDMA_CTRL:
+ s->regs.tdma.status = (~dma_ctrl) & GENET_DMA_ENABLE_MASK;
+ if (exst_tdma_en != incm_tdma_en) {
+ trace_bcm2838_genet_tx_dma(incm_tdma_en == 1
+ ? "enabled"
+ : "disabled");
+ }
+ if (exst_ring_en != incm_ring_buf_en) {
+ trace_bcm2838_genet_tx_dma_ring_buf(incm_ring_buf_en);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t value = ~0;
@@ -493,7 +735,7 @@ static void bcm2838_genet_write(void *opaque, hwaddr offset, uint64_t value,
break;
case BCM2838_GENET_TDMA_REGS
... BCM2838_GENET_TDMA_REGS + sizeof(BCM2838GenetRegsTdma) - 1:
- qemu_log_mask(LOG_UNIMP, "TDMA isn't implemented yet");
+ bcm2838_genet_tdma(s, offset, value);
break;
default:
break;
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index 9566b2a29a..28529381d1 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -110,6 +110,21 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_PHY_EXP_SHD_BLOCKS_CNT 256
#define BCM2838_GENET_PHY_EXP_SHD_REGS_CNT 256
+#define MAX_FRAME_SIZE 0xFFF
+#define MAX_PACKET_SIZE 1518
+#define MAX_PAYLOAD_SIZE 1500
+#define TX_MIN_PKT_SIZE 60
+
+
+typedef struct BCM2838GenetXmitStatus {
+ uint32_t length_status; /* length and peripheral status */
+ uint32_t ext_status; /* Extended status */
+ uint32_t rx_csum; /* partial rx checksum */
+ uint32_t unused1[9]; /* unused */
+ uint32_t tx_csum_info; /* Tx checksum info. */
+ uint32_t unused2[3]; /* unused */
+} BCM2838GenetXmitStatus;
+
typedef struct {
uint32_t rev_ctrl;
uint32_t port_ctrl;
@@ -402,6 +417,8 @@ struct BCM2838GenetState {
qemu_irq irq_default;
qemu_irq irq_prio;
+
+ uint8_t tx_packet[MAX_FRAME_SIZE];
};
/*
--
2.55.0
next prev parent reply other threads:[~2026-10-18 13:37 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 13:35 [PATCH v4 00/17] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 01/17] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 02/17] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 03/17] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 04/17] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 05/17] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 06/17] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 07/17] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 08/17] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 09/17] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 10/17] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
2026-10-18 13:35 ` [PATCH v4 11/17] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
2026-10-18 13:35 ` Marcelo Manzo [this message]
2026-10-18 13:35 ` [PATCH v4 13/17] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
2026-10-18 13:36 ` [PATCH v4 14/17] hw/net/bcm2838_genet: add migration state Marcelo Manzo
2026-10-18 13:36 ` [PATCH v4 15/17] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
2026-10-18 13:36 ` [PATCH v4 16/17] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
2026-10-18 13:36 ` [PATCH v4 17/17] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261018133603.64323-13-marcelomanzo@gmail.com \
--to=marcelomanzo@gmail.com \
--cc=jasowangio@gmail.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@oss.qualcomm.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=serg.oker@gmail.com \
--cc=sergey.kambalin@auriga.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.