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 13/17] hw/net/bcm2838_genet: implement GENET RX path
Date: Sun, 18 Oct 2026 09:35:59 -0400 [thread overview]
Message-ID: <20261018133603.64323-14-marcelomanzo@gmail.com> (raw)
In-Reply-To: <20261018133603.64323-1-marcelomanzo@gmail.com>
From: Sergey Kambalin <serg.oker@gmail.com>
Implement the receive DMA ring handling: qemu_can_receive()/
receive() callbacks for the NIC's netdev peer, copying incoming
frame data into descriptor buffers on the active RX ring,
classifying broadcast/multicast, and raising the RXDMA_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, make these changes to the original code:
- Report no hardware-computed RX checksum (rx_csum = 0). The original
stored the IPv4 header checksum in host byte order, but the guest's
CHECKSUM_COMPLETE path expects a running checksum over the L4
payload, which made the guest reject otherwise-valid inbound
packets ("hw csum failure" in bcmgenet_rx_poll()) and broke
DHCP/connectivity intermittently. With 0 the guest verifies
checksums in software instead.
- Fill in the per-packet status block with stl_le_p() rather than by
casting the packet buffer to a C struct, which is undefined
behaviour for a misaligned buffer and wrong on big-endian hosts.
- Refresh the link state once a BMCR autonegotiation restart has been
handled by the MDIO code (moved here from the MDIO patch, where the
function it calls does not exist yet).
- Validate the guest-programmed descriptor indexes against the size
of the descriptor array, and limit the DMA write to the size of our
packet buffer, so that a guest-controlled register can't make us
access memory outside them.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 286 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 1 +
2 files changed, 286 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 7274a66d8f..e20358c8e7 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -20,6 +20,8 @@
#include "hw/net/bcm2838_genet.h"
#include "trace.h"
+static void bcm2838_genet_phy_update_link(BCM2838GenetState *s);
+
/* GENET layouts */
REG32(GENET_SYS_REV_CTRL, 0)
FIELD(GENET_SYS_REV_CTRL, GPHY_REV, 0, 16)
@@ -402,6 +404,9 @@ static uint64_t bcm2838_genet_mdio_cmd(BCM2838GenetState *s, uint64_t cmd)
}
}
*phy_reg = phy_reg_data;
+ if (phy_reg_id == BCM2838_GENET_PHY_BMCR && anrestart == 1) {
+ bcm2838_genet_phy_update_link(s);
+ }
}
}
}
@@ -585,6 +590,24 @@ static void bcm2838_genet_tdma_prod_index_write(BCM2838GenetState *s,
}
}
+static bool bcm2838_genet_rdma_ring_active(BCM2838GenetState *s,
+ unsigned int ring_index)
+{
+ uint32_t ring_mask = 1 << ring_index;
+
+ bool dma_en = FIELD_EX32(s->regs.rdma.ctrl, GENET_DMA_CTRL, EN);
+ bool ring_en = FIELD_EX32(s->regs.rdma.ring_cfg, GENET_DMA_RING_CFG, EN)
+ & ring_mask;
+ bool ring_buf_en = FIELD_EX32(s->regs.rdma.ctrl,
+ GENET_DMA_CTRL, RING_BUF_EN) & ring_mask;
+ bool active = dma_en && ring_en && ring_buf_en;
+
+ trace_bcm2838_genet_rx_dma_ring_active(ring_index,
+ active ? "active" : "halted");
+
+ return active;
+}
+
static void bcm2838_genet_tdma(BCM2838GenetState *s, hwaddr offset,
uint64_t value)
{
@@ -764,9 +787,268 @@ static const MemoryRegionOps bcm2838_genet_ops = {
.valid = {.min_access_size = 4, .max_access_size = 4},
};
+static int32_t bcm2838_genet_filter(BCM2838GenetState *s, const void *buf,
+ size_t size)
+{
+ qemu_log_mask(LOG_UNIMP,
+ "Packet filtration with HFB isn't implemented yet");
+ return -1;
+}
+
+static int32_t bcm2838_genet_filter2ring(BCM2838GenetState *s,
+ uint32_t filter_idx)
+{
+ qemu_log_mask(LOG_UNIMP,
+ "Packet filtration with HFB isn't implemented yet");
+ return -1;
+}
+
+static bool is_packet_broadcast(const uint8_t *buf, size_t size)
+{
+ static const uint8_t bcst_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+ if (size < sizeof(bcst_addr)) {
+ return false;
+ }
+
+ return !memcmp(buf, bcst_addr, sizeof(bcst_addr));
+}
+
+static bool is_packet_multicast(const uint8_t *buf, size_t size)
+{
+ return !!(buf[0] & 0x01);
+}
+
+static ssize_t bcm2838_genet_rdma(BCM2838GenetState *s, uint32_t ring_idx,
+ const void *buf, size_t size)
+{
+ const size_t desc_word_size =
+ sizeof(BCM2838GenetRdmaDesc) / sizeof(uint32_t);
+
+ ssize_t len = 0;
+ BCM2838GenetRegsRdma *rdma = &s->regs.rdma;
+ BCM2838GenetRdmaRing *ring = &rdma->rings[ring_idx];
+ hwaddr write_index =
+ bcm2838_genet_make_u64(ring->write_ptr_hi, ring->write_ptr)
+ / desc_word_size;
+ BCM2838GenetRdmaDesc *desc;
+
+ const hwaddr start_index =
+ bcm2838_genet_make_u64(ring->start_addr_hi, ring->start_addr)
+ / desc_word_size;
+ const hwaddr end_index =
+ bcm2838_genet_make_u64(ring->end_addr_hi, ring->end_addr)
+ / desc_word_size;
+
+ if (!bcm2838_genet_rdma_ring_active(s, ring_idx)) {
+ return -1;
+ }
+
+ if (write_index >= BCM2838_GENET_DMA_DESC_CNT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid RX descriptor index %" PRIu64
+ " (exceeds %u)\n", __func__, (uint64_t)write_index,
+ BCM2838_GENET_DMA_DESC_CNT - 1);
+ return -1;
+ }
+ desc = &rdma->descs[write_index];
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS, SOP, 1);
+
+ while (len < size) {
+ size_t l = size - len;
+ size_t buf_size = MIN(ring->ring_buf_size & 0xffff,
+ sizeof(s->rx_packet));
+ uint8_t *dma_buf = s->rx_packet;
+ hwaddr dma_buf_addr =
+ bcm2838_genet_make_u64(desc->address_hi, desc->address_lo);
+ MemTxResult mem_tx_result = MEMTX_OK;
+ uint8_t *frame_buf = dma_buf + sizeof(BCM2838GenetXmitStatus) + 2;
+
+ bool crc_fwd = FIELD_EX32(s->regs.umac.cmd, GENET_UMAC_CMD, CRC_FWD);
+ size_t buflength;
+ uint32_t prod_index;
+
+ if (l > ring->ring_buf_size) {
+ l = ring->ring_buf_size;
+ }
+
+ memcpy(frame_buf, buf + len, l);
+
+ len += l;
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ EOP, !!(len >= size));
+
+ buflength = l + sizeof(BCM2838GenetXmitStatus) + 2;
+ if (crc_fwd) {
+ buflength += 4;
+ }
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ BUFLENGTH, buflength);
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ BROADCAST,
+ !!is_packet_broadcast(frame_buf, l));
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ MULTICAST,
+ !!is_packet_multicast(frame_buf, l));
+
+ /*
+ * We don't implement the hardware's partial RX checksum: report 0 and
+ * rely on the guest verifying checksums in software.
+ */
+ stl_le_p(dma_buf + offsetof(BCM2838GenetXmitStatus, rx_csum), 0);
+ stl_le_p(dma_buf + offsetof(BCM2838GenetXmitStatus, length_status),
+ desc->length_status);
+
+ mem_tx_result = address_space_write(&s->dma_as, dma_buf_addr,
+ MEMTXATTRS_UNSPECIFIED,
+ dma_buf, buf_size);
+ if (mem_tx_result != MEMTX_OK) {
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ RXERR, 1);
+ }
+
+ if (FIELD_EX32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS, RXERR) != 0) {
+ break;
+ }
+
+ prod_index = FIELD_EX32(ring->prod_index, GENET_DMA_PROD_INDEX, INDEX);
+ ring->prod_index = FIELD_DP32(ring->prod_index,
+ GENET_DMA_PROD_INDEX,
+ INDEX, ++prod_index);
+ if (++write_index > end_index) {
+ write_index = start_index;
+ }
+ if (write_index >= BCM2838_GENET_DMA_DESC_CNT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid RX descriptor index %" PRIu64
+ " (exceeds %u)\n", __func__, (uint64_t)write_index,
+ BCM2838_GENET_DMA_DESC_CNT - 1);
+ break;
+ }
+ desc = &rdma->descs[write_index];
+ ring->write_ptr = write_index * desc_word_size;
+ ring->write_ptr_hi = ((hwaddr)write_index * desc_word_size) >> 32;
+ }
+
+ if (ring_idx == BCM2838_GENET_DMA_RING_DEFAULT) {
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat,
+ GENET_INTRL_0, RXDMA_MBDONE, 1);
+ } else {
+ uint32_t rx_intrs =
+ FIELD_EX32(s->regs.intrl1.stat, GENET_INTRL_1, RX_INTRS);
+ rx_intrs |= 1 << ring_idx;
+
+ s->regs.intrl1.stat = FIELD_DP32(s->regs.intrl1.stat,
+ GENET_INTRL_1, RX_INTRS, rx_intrs);
+ }
+
+ return len;
+}
+
+static ssize_t bcm2838_genet_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ BCM2838GenetState *s = (BCM2838GenetState *)qemu_get_nic_opaque(nc);
+ ssize_t bytes_received = -1;
+ int32_t filter_index = -1;
+ int32_t ring_index = -1;
+
+ if (FIELD_EX32(s->regs.rdma.ctrl, GENET_DMA_CTRL, EN) != 0) {
+ filter_index = bcm2838_genet_filter(s, buf, size);
+
+ if (filter_index >= 0) {
+ ring_index = bcm2838_genet_filter2ring(s, filter_index);
+ } else {
+ ring_index = BCM2838_GENET_DMA_RING_CNT - 1;
+ if (!bcm2838_genet_rdma_ring_active(s, ring_index)) {
+ for (ring_index = 0;
+ ring_index < BCM2838_GENET_DMA_RING_CNT - 1;
+ ring_index++) {
+ if (bcm2838_genet_rdma_ring_active(s, ring_index)) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (size <= MAX_PACKET_SIZE) {
+ bytes_received = bcm2838_genet_rdma(s, ring_index, buf, size);
+ }
+ }
+
+ bcm2838_genet_set_irq_default(s);
+ bcm2838_genet_set_irq_prio(s);
+
+ return bytes_received;
+}
+
+static void bcm2838_genet_phy_update_link(BCM2838GenetState *s)
+{
+ bool qemu_link_down = qemu_get_queue(s->nic)->link_down;
+
+ bool lstatus = FIELD_EX32(s->phy_regs.bmsr, GENET_PHY_BMSR, LSTATUS);
+
+ if (qemu_link_down && lstatus) {
+ trace_bcm2838_genet_phy_update_link("down");
+
+ s->phy_regs.bmsr = FIELD_DP32(s->phy_regs.bmsr,
+ GENET_PHY_BMSR, ANEGCOMPLETE, 0);
+ s->phy_regs.bmsr = FIELD_DP32(s->phy_regs.bmsr,
+ GENET_PHY_BMSR, LSTATUS, 0);
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat,
+ GENET_INTRL_0, LINK_DOWN, 1);
+ } else if (!qemu_link_down && !lstatus) {
+ trace_bcm2838_genet_phy_update_link("up");
+
+ /*
+ * Complete auto-negotiation (fixed link partner's abilities for now:
+ * 1Gbps with flow control)
+ */
+ s->phy_regs.stat1000 = FIELD_DP32(s->phy_regs.stat1000,
+ GENET_PHY_STAT_1000, HALF, 1);
+ s->phy_regs.stat1000 = FIELD_DP32(s->phy_regs.stat1000,
+ GENET_PHY_STAT_1000, FULL, 1);
+
+ s->phy_regs.lpa = FIELD_DP32(s->phy_regs.lpa,
+ GENET_PHY_LPA, PAUSE_CAP, 1);
+ s->phy_regs.lpa = FIELD_DP32(s->phy_regs.lpa,
+ GENET_PHY_LPA, PAUSE_ASYM, 1);
+ s->phy_regs.lpa = FIELD_DP32(s->phy_regs.lpa, GENET_PHY_LPA, LPACK, 1);
+
+ s->phy_regs.bmsr = FIELD_DP32(s->phy_regs.bmsr,
+ GENET_PHY_BMSR, ANEGCOMPLETE, 1);
+ s->phy_regs.bmsr = FIELD_DP32(s->phy_regs.bmsr,
+ GENET_PHY_BMSR, LSTATUS, 1);
+
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat,
+ GENET_INTRL_0, LINK_UP, 1);
+ }
+
+ bcm2838_genet_set_irq_default(s);
+}
+static void bcm2838_genet_set_link(NetClientState *nc)
+{
+ BCM2838GenetState *s = qemu_get_nic_opaque(nc);
+
+ bcm2838_genet_phy_update_link(s);
+}
+
static NetClientInfo bcm2838_genet_client_info = {
.type = NET_CLIENT_DRIVER_NIC,
- .size = sizeof(NICState)
+ .size = sizeof(NICState),
+ .receive = bcm2838_genet_receive,
+ .link_status_changed = bcm2838_genet_set_link,
};
static void bcm2838_genet_realize(DeviceState *dev, Error **errp)
@@ -820,6 +1102,8 @@ static void bcm2838_genet_phy_reset(BCM2838GenetState *s)
s->phy_aux_ctl_shd_regs.regs[BCM2838_GENET_PHY_AUX_CTL_MISC] = 0x1E;
trace_bcm2838_genet_phy_reset("done");
+
+ bcm2838_genet_phy_update_link(s);
}
static void bcm2838_genet_reset(Object *obj, ResetType type)
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index 28529381d1..63af38d412 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -419,6 +419,7 @@ struct BCM2838GenetState {
qemu_irq irq_prio;
uint8_t tx_packet[MAX_FRAME_SIZE];
+ uint8_t rx_packet[MAX_FRAME_SIZE];
};
/*
--
2.55.0
next prev parent reply other threads:[~2026-10-18 13:39 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 ` [PATCH v4 12/17] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
2026-10-18 13:35 ` Marcelo Manzo [this message]
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-14-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.