* [PATCH v2 01/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 02/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
` (17 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé
Add the PCI-facing "root" part (D0:F0) of the BCM2838 (Raspberry Pi
4B / BCM2711) PCIe Root Complex: a PCIe root port with the vendor's
device/vendor IDs and capability offsets, not usable on its own
without the host-facing part added in the next patch.
Adapted from patch 13/41 of Sergey Kambalin's "Raspberry Pi 4B
machine" series (patchwork series 829638, posted to qemu-devel in
2024 but never merged) against current QEMU master; the surrounding
PCIe/GENET networking support for raspi4b was left unmerged upstream
(base scaffolding aside), so this and the following patches complete
and debug that series to get real networking working under
raspi4b.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838_pcie.c | 82 +++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 4 +-
include/hw/arm/bcm2838_pcie.h | 53 ++++++++++++++++++++++
3 files changed, 138 insertions(+), 1 deletion(-)
create mode 100644 hw/arm/bcm2838_pcie.c
create mode 100644 include/hw/arm/bcm2838_pcie.h
diff --git a/hw/arm/bcm2838_pcie.c b/hw/arm/bcm2838_pcie.c
new file mode 100644
index 0000000000..96a45c1863
--- /dev/null
+++ b/hw/arm/bcm2838_pcie.c
@@ -0,0 +1,82 @@
+/*
+ * BCM2838 PCIe Root Complex emulation
+ *
+ * Copyright (C) 2022 Ovchinnikov Vitalii <vitalii.ovchinnikov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "hw/pci-host/gpex.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "hw/arm/bcm2838_pcie.h"
+#include "trace.h"
+
+/*
+ * RC root part (D0:F0)
+ */
+
+static void bcm2838_pcie_root_port_reset_hold(Object *obj, ResetType type)
+{
+ PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(obj);
+ PCIDevice *dev = PCI_DEVICE(obj);
+ BCM2838PcieRootState *s = BCM2838_PCIE_ROOT(dev);
+
+ if (rpc->parent_phases.hold) {
+ rpc->parent_phases.hold(obj, type);
+ }
+
+ memset(s->regs, 0xFF, sizeof(s->regs));
+}
+
+static void bcm2838_pcie_root_init(Object *obj)
+{
+ PCIBridge *br = PCI_BRIDGE(obj);
+ br->bus_name = "pcie.1";
+}
+
+static void bcm2838_pcie_root_class_init(ObjectClass *class, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
+ ResettableClass *rc = RESETTABLE_CLASS(class);
+ PCIERootPortClass *rpc = PCIE_ROOT_PORT_CLASS(class);
+
+ dc->desc = "BCM2711 PCIe Bridge";
+ /*
+ * PCI-facing part of the host bridge, not usable without the host-facing
+ * part, which can't be device_add'ed.
+ */
+
+ resettable_class_set_parent_phases(rc, NULL,
+ bcm2838_pcie_root_port_reset_hold,
+ NULL, &rpc->parent_phases);
+
+ dc->user_creatable = false;
+ k->vendor_id = BCM2838_PCIE_VENDOR_ID;
+ k->device_id = BCM2838_PCIE_DEVICE_ID;
+ k->revision = BCM2838_PCIE_REVISION;
+
+ rpc->exp_offset = BCM2838_PCIE_EXP_CAP_OFFSET;
+ rpc->aer_offset = BCM2838_PCIE_AER_CAP_OFFSET;
+}
+
+static const TypeInfo bcm2838_pcie_root_info = {
+ .name = TYPE_BCM2838_PCIE_ROOT,
+ .parent = TYPE_PCIE_ROOT_PORT,
+ .instance_size = sizeof(BCM2838PcieRootState),
+ .instance_init = bcm2838_pcie_root_init,
+ .class_init = bcm2838_pcie_root_class_init,
+};
+
+static void bcm2838_pcie_register(void)
+{
+ type_register_static(&bcm2838_pcie_root_info);
+}
+
+type_init(bcm2838_pcie_register)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 4233a800be..b02aa346bf 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -104,7 +104,9 @@ arm_common_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4_boards.c'))
arm_common_ss.add(when: 'CONFIG_MAX78000FTHR', if_true: files('max78000fthr.c'))
arm_common_ss.add(when: 'CONFIG_NETDUINO2', if_true: files('netduino2.c'))
arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_peripherals.c'))
-arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2838_peripherals.c'))
+arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files(
+ 'bcm2838_peripherals.c',
+ 'bcm2838_pcie.c'))
arm_common_ss.add(when: 'CONFIG_STRONGARM', if_true: files('strongarm.c'))
arm_common_ss.add(when: 'CONFIG_SX1', if_true: files('omap_sx1.c'))
arm_common_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
diff --git a/include/hw/arm/bcm2838_pcie.h b/include/hw/arm/bcm2838_pcie.h
new file mode 100644
index 0000000000..fe7a6cff19
--- /dev/null
+++ b/include/hw/arm/bcm2838_pcie.h
@@ -0,0 +1,53 @@
+/*
+ * BCM2838 PCIe Root Complex emulation
+ *
+ * Copyright (C) 2022 Ovchinnikov Vitalii <vitalii.ovchinnikov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BCM2838_PCIE_H
+#define BCM2838_PCIE_H
+
+#include "exec/hwaddr.h"
+#include "hw/core/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pcie_host.h"
+#include "hw/pci/pcie_port.h"
+#include "qom/object.h"
+
+#define TYPE_BCM2838_PCIE_ROOT "bcm2838-pcie-root"
+OBJECT_DECLARE_TYPE(BCM2838PcieRootState, BCM2838PcieRootClass,
+ BCM2838_PCIE_ROOT)
+
+#define BCM2838_PCIE_VENDOR_ID 0x14E4
+#define BCM2838_PCIE_DEVICE_ID 0x2711
+#define BCM2838_PCIE_REVISION 20
+
+#define BCM2838_PCIE_REGS_SIZE 0x9310
+#define BCM2838_PCIE_NUM_IRQS 4
+
+#define BCM2838_PCIE_EXP_CAP_OFFSET 0xAC
+#define BCM2838_PCIE_AER_CAP_OFFSET 0x100
+
+#define BCM2838_PCIE_EXT_CFG_DATA 0x8000
+#define BCM2838_PCIE_EXT_CFG_INDEX 0x9000
+
+struct BCM2838PcieRootState {
+ /*< private >*/
+ PCIESlot parent_obj;
+
+ /*< public >*/
+ uint8_t regs[BCM2838_PCIE_REGS_SIZE - PCIE_CONFIG_SPACE_SIZE];
+};
+
+struct BCM2838PcieRootClass {
+ /*< private >*/
+ PCIERootPortClass parent_obj;
+
+ /*< public >*/
+ void (*parent_realize)(PCIDevice *dev, Error **errp);
+};
+
+
+#endif /* BCM2838_PCIE_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 02/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe host
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 01/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 03/19] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
` (16 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé
Add the BCM2838 PCIe host bridge model: config space, MMIO window,
and register access for the root port.
Per Peter Maydell's review of a later follow-up series, fold in what
was originally a separate bugfix here, since this device isn't in
upstream git yet and the bug was in code this patch itself introduces.
Four defects fixed, all on the enumeration path (none of this was
exercised until a later patch actually attaches a PCI device):
1. bcm2838_pcie_host_read/write computed
"offset - PCIE_CONFIG_SPACE_SIZE" on an unsigned hwaddr, which
underflows for every offset below 4KB. The root port's own config
space -- including vendor/device ID at offset 0 -- therefore always
fell into the out-of-range branch and read back as all-ones, i.e.
"no device present", so the guest stopped scanning immediately.
2. pcie_host_mmcfg_init() was never called, yet both accessors
dereference pcie_hb->mmio.ops to service EXT_CFG_DATA. Once (1) was
fixed and the guest actually reached that path, it touched an
uninitialised MemoryRegion.
3. The root port overrode PCIDeviceClass::config_read/config_write with
plain pci_default_*_config(). That bypasses rp_write_config() ->
pci_bridge_write_config(), so programming the bridge's memory window
never updated the bridge's address space and BARs behind the root
port stayed unreachable. Drop the overrides and dispatch through
pci_host_config_{read,write}_common() so the device's real handlers
run.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838_pcie.c | 226 ++++++++++++++++++++++++++++++++++
hw/arm/trace-events | 4 +
include/hw/arm/bcm2838_pcie.h | 22 ++++
3 files changed, 252 insertions(+)
diff --git a/hw/arm/bcm2838_pcie.c b/hw/arm/bcm2838_pcie.c
index 96a45c1863..88166d1f44 100644
--- a/hw/arm/bcm2838_pcie.c
+++ b/hw/arm/bcm2838_pcie.c
@@ -11,12 +11,237 @@
#include "qapi/error.h"
#include "hw/core/irq.h"
#include "hw/pci-host/gpex.h"
+#include "hw/pci/pci_host.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
#include "hw/arm/bcm2838_pcie.h"
#include "trace.h"
+static uint64_t bcm2838_pcie_host_read(void *opaque, hwaddr offset,
+ unsigned size) {
+ hwaddr mmcfg_addr;
+ uint64_t value = ~0;
+ BCM2838PcieHostState *s = opaque;
+ PCIExpressHost *pcie_hb = PCIE_HOST_BRIDGE(s);
+ uint8_t *root_regs = s->root_port.regs;
+ uint32_t *cfg_idx = (uint32_t *)(root_regs + BCM2838_PCIE_EXT_CFG_INDEX
+ - PCIE_CONFIG_SPACE_SIZE);
+
+ if (offset < PCIE_CONFIG_SPACE_SIZE) {
+ /*
+ * The first 4KB of the window is the root port's own PCI config
+ * space (vendor/device ID, BARs, capabilities). Serve it from the
+ * real PCIDevice, not from the raw regs[] shadow buffer, which is
+ * only backing store for the controller registers above 4KB.
+ */
+ value = pci_host_config_read_common(PCI_DEVICE(&s->root_port),
+ offset, PCIE_CONFIG_SPACE_SIZE,
+ size);
+ } else if (offset - PCIE_CONFIG_SPACE_SIZE + size
+ <= sizeof(s->root_port.regs)) {
+ switch (offset) {
+ case BCM2838_PCIE_EXT_CFG_DATA
+ ... BCM2838_PCIE_EXT_CFG_DATA + PCIE_CONFIG_SPACE_SIZE - 1:
+ mmcfg_addr = *cfg_idx
+ | PCIE_MMCFG_CONFOFFSET(offset - BCM2838_PCIE_EXT_CFG_DATA);
+ value = pcie_hb->mmio.ops->read(opaque, mmcfg_addr, size);
+ break;
+ default:
+ memcpy(&value, root_regs + offset - PCIE_CONFIG_SPACE_SIZE, size);
+ }
+ } else {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+ }
+
+ trace_bcm2838_pcie_host_read(size, offset, value);
+ return value;
+}
+
+static void bcm2838_pcie_host_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size) {
+ hwaddr mmcfg_addr;
+ BCM2838PcieHostState *s = opaque;
+ PCIExpressHost *pcie_hb = PCIE_HOST_BRIDGE(s);
+ uint8_t *root_regs = s->root_port.regs;
+ uint32_t *cfg_idx = (uint32_t *)(root_regs + BCM2838_PCIE_EXT_CFG_INDEX
+ - PCIE_CONFIG_SPACE_SIZE);
+
+ trace_bcm2838_pcie_host_write(size, offset, value);
+
+ if (offset < PCIE_CONFIG_SPACE_SIZE) {
+ /* Root port's own PCI config space -- see read path above */
+ pci_host_config_write_common(PCI_DEVICE(&s->root_port), offset,
+ PCIE_CONFIG_SPACE_SIZE, value, size);
+ } else if (offset - PCIE_CONFIG_SPACE_SIZE + size
+ <= sizeof(s->root_port.regs)) {
+ switch (offset) {
+ case BCM2838_PCIE_EXT_CFG_DATA
+ ... BCM2838_PCIE_EXT_CFG_DATA + PCIE_CONFIG_SPACE_SIZE - 1:
+ mmcfg_addr = *cfg_idx
+ | PCIE_MMCFG_CONFOFFSET(offset - BCM2838_PCIE_EXT_CFG_DATA);
+ pcie_hb->mmio.ops->write(opaque, mmcfg_addr, value, size);
+ break;
+ default:
+ memcpy(root_regs + offset - PCIE_CONFIG_SPACE_SIZE, &value, size);
+ }
+ } else {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+ }
+}
+
+static const MemoryRegionOps bcm2838_pcie_host_ops = {
+ .read = bcm2838_pcie_host_read,
+ .write = bcm2838_pcie_host_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {.max_access_size = sizeof(uint64_t)},
+};
+
+int bcm2838_pcie_host_set_irq_num(BCM2838PcieHostState *s, int index, int spi)
+{
+ if (index >= BCM2838_PCIE_NUM_IRQS) {
+ return -EINVAL;
+ }
+
+ s->irq_num[index] = spi;
+ return 0;
+}
+
+static void bcm2838_pcie_host_set_irq(void *opaque, int irq_num, int level)
+{
+ BCM2838PcieHostState *s = opaque;
+
+ qemu_set_irq(s->irq[irq_num], level);
+}
+
+static PCIINTxRoute bcm2838_pcie_host_route_intx_pin_to_irq(void *opaque,
+ int pin)
+{
+ PCIINTxRoute route;
+ BCM2838PcieHostState *s = opaque;
+
+ route.irq = s->irq_num[pin];
+ route.mode = route.irq < 0 ? PCI_INTX_DISABLED : PCI_INTX_ENABLED;
+
+ return route;
+}
+
+static int bcm2838_pcie_host_map_irq(PCIDevice *pci_dev, int pin)
+{
+ return pin;
+}
+
+static void bcm2838_pcie_host_realize(DeviceState *dev, Error **errp)
+{
+ PCIHostState *pci = PCI_HOST_BRIDGE(dev);
+ BCM2838PcieHostState *s = BCM2838_PCIE_HOST(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ PCIExpressHost *pex = PCIE_HOST_BRIDGE(dev);
+
+ int i;
+
+ /*
+ * Initialise the ECAM config-space window. The BCM2838 does not expose
+ * it to the guest directly; config accesses are made indirectly through
+ * the EXT_CFG_INDEX/EXT_CFG_DATA register pair, which dispatch into
+ * pex->mmio. Without this the ops pointer is never set up.
+ */
+ pcie_host_mmcfg_init(pex, PCIE_MMCFG_SIZE_MAX);
+
+ memory_region_init_io(&s->cfg_regs, OBJECT(s), &bcm2838_pcie_host_ops, s,
+ "bcm2838_pcie_cfg_regs", BCM2838_PCIE_REGS_SIZE);
+ sysbus_init_mmio(sbd, &s->cfg_regs);
+
+ /*
+ * The MemoryRegions io_mmio and io_ioport that we pass to
+ * pci_register_root_bus() are not the same as the MemoryRegions
+ * io_mmio_window and io_ioport_window that we expose as SysBus MRs.
+ * The difference is in the behavior of accesses to addresses where no PCI
+ * device has been mapped.
+ *
+ * io_mmio and io_ioport are the underlying PCI view of the PCI address
+ * space, and when a PCI device does a bus master access to a bad address
+ * this is reported back to it as a transaction failure.
+ *
+ * io_mmio_window and io_ioport_window implement "unmapped addresses read as
+ * -1 and ignore writes"; this is a traditional x86 PC behavior, which is
+ * not mandated properly by the PCI spec but expected by the majority of
+ * PCI-using guest software, including Linux.
+ *
+ * We implement it in the PCIe host controller, by providing the *_window
+ * MRs, which are containers with io ops that implement the 'background'
+ * behavior and which hold the real PCI MRs as sub-regions.
+ */
+ memory_region_init(&s->io_mmio, OBJECT(s), "bcm2838_pcie_mmio", UINT64_MAX);
+ memory_region_init(&s->io_ioport, OBJECT(s), "bcm2838_pcie_ioport",
+ 64 * 1024);
+
+ memory_region_init_io(&s->io_mmio_window, OBJECT(s),
+ &unassigned_io_ops, OBJECT(s),
+ "bcm2838_pcie_mmio_window", UINT64_MAX);
+ memory_region_init_io(&s->io_ioport_window, OBJECT(s),
+ &unassigned_io_ops, OBJECT(s),
+ "bcm2838_pcie_ioport_window", 64 * 1024);
+
+ memory_region_add_subregion(&s->io_mmio_window, 0, &s->io_mmio);
+ memory_region_add_subregion(&s->io_ioport_window, 0, &s->io_ioport);
+ sysbus_init_mmio(sbd, &s->io_mmio_window);
+ sysbus_init_mmio(sbd, &s->io_ioport_window);
+
+ for (i = 0; i < BCM2838_PCIE_NUM_IRQS; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ s->irq_num[i] = -1;
+ }
+
+ pci->bus = pci_register_root_bus(dev, "pcie.0", bcm2838_pcie_host_set_irq,
+ bcm2838_pcie_host_map_irq, s, &s->io_mmio,
+ &s->io_ioport, 0, BCM2838_PCIE_NUM_IRQS,
+ TYPE_PCIE_BUS);
+ pci_bus_set_route_irq_fn(pci->bus, bcm2838_pcie_host_route_intx_pin_to_irq);
+ qdev_realize(DEVICE(&s->root_port), BUS(pci->bus), &error_fatal);
+}
+
+static const char *bcm2838_pcie_host_root_bus_path(PCIHostState *host_bridge,
+ PCIBus *rootbus)
+{
+ return "0000:00";
+}
+
+static void bcm2838_pcie_host_class_init(ObjectClass *class, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(class);
+
+ hc->root_bus_path = bcm2838_pcie_host_root_bus_path;
+ dc->realize = bcm2838_pcie_host_realize;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->fw_name = "pci";
+}
+
+static void bcm2838_pcie_host_initfn(Object *obj)
+{
+ BCM2838PcieHostState *s = BCM2838_PCIE_HOST(obj);
+ BCM2838PcieRootState *root = &s->root_port;
+
+ object_initialize_child(obj, "root_port", root, TYPE_BCM2838_PCIE_ROOT);
+ qdev_prop_set_int32(DEVICE(root), "addr", PCI_DEVFN(0, 0));
+ qdev_prop_set_bit(DEVICE(root), "multifunction", false);
+}
+
+static const TypeInfo bcm2838_pcie_host_info = {
+ .name = TYPE_BCM2838_PCIE_HOST,
+ .parent = TYPE_PCIE_HOST_BRIDGE,
+ .instance_size = sizeof(BCM2838PcieHostState),
+ .instance_init = bcm2838_pcie_host_initfn,
+ .class_init = bcm2838_pcie_host_class_init,
+};
+
/*
* RC root part (D0:F0)
*/
@@ -77,6 +302,7 @@ static const TypeInfo bcm2838_pcie_root_info = {
static void bcm2838_pcie_register(void)
{
type_register_static(&bcm2838_pcie_root_info);
+ type_register_static(&bcm2838_pcie_host_info);
}
type_init(bcm2838_pcie_register)
diff --git a/hw/arm/trace-events b/hw/arm/trace-events
index 1b16f710fe..579d4b12c2 100644
--- a/hw/arm/trace-events
+++ b/hw/arm/trace-events
@@ -96,3 +96,7 @@ z2_aer915_event(int8_t event, int8_t len) "i2c event =0x%x len=%d bytes"
# bcm2838.c
bcm2838_gic_set_irq(int irq, int level) "gic irq:%d lvl:%d"
+
+# bcm2838_pcie.c
+bcm2838_pcie_host_read(unsigned int size, uint64_t offset, uint64_t value) "%u bytes @ 0x%04"PRIx64": 0x%016"PRIx64
+bcm2838_pcie_host_write(unsigned int size, uint64_t offset, uint64_t value) "%u bytes @ 0x%04"PRIx64": 0x%016"PRIx64
diff --git a/include/hw/arm/bcm2838_pcie.h b/include/hw/arm/bcm2838_pcie.h
index fe7a6cff19..18ab04f24a 100644
--- a/include/hw/arm/bcm2838_pcie.h
+++ b/include/hw/arm/bcm2838_pcie.h
@@ -16,6 +16,9 @@
#include "hw/pci/pcie_port.h"
#include "qom/object.h"
+#define TYPE_BCM2838_PCIE_HOST "bcm2838-pcie-host"
+OBJECT_DECLARE_SIMPLE_TYPE(BCM2838PcieHostState, BCM2838_PCIE_HOST)
+
#define TYPE_BCM2838_PCIE_ROOT "bcm2838-pcie-root"
OBJECT_DECLARE_TYPE(BCM2838PcieRootState, BCM2838PcieRootClass,
BCM2838_PCIE_ROOT)
@@ -50,4 +53,23 @@ struct BCM2838PcieRootClass {
};
+struct BCM2838PcieHostState {
+ /*< private >*/
+ PCIExpressHost parent_obj;
+
+ /*< public >*/
+ BCM2838PcieRootState root_port;
+
+ MemoryRegion cfg_regs;
+ MemoryRegion io_ioport;
+ MemoryRegion io_mmio;
+ MemoryRegion io_ioport_window;
+ MemoryRegion io_mmio_window;
+
+ qemu_irq irq[BCM2838_PCIE_NUM_IRQS];
+ int irq_num[BCM2838_PCIE_NUM_IRQS];
+};
+
+int bcm2838_pcie_host_set_irq_num(BCM2838PcieHostState *s, int index, int spi);
+
#endif /* BCM2838_PCIE_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 03/19] hw/arm/bcm2838: enable BCM2838 PCIe host bridge
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 01/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 02/19] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 04/19] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
` (15 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé
Wire the BCM2838 PCIe host bridge into the SoC's peripheral block:
instantiate it, map its RC registers and MMIO window.
Per Peter Maydell's review of a later follow-up series, fold in the
remaining defect from what was originally a separate bugfix here,
since this device isn't in upstream git yet: the PCI MMIO window was
mapped at the ARM base address with no address translation, even
though PCIE_MMIO_OFFSET was defined for exactly that purpose (and
otherwise unused). The DTB declares CPU 0x600000000 as mapping to PCI
0xc0000000, so a guest would read PCI address 0 where it expected a
device BAR. Map an alias at the correct PCI offset instead.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838.c | 9 +++++++
hw/arm/bcm2838_peripherals.c | 35 ++++++++++++++++++++++++++++
hw/arm/raspi4b.c | 1 -
include/hw/arm/bcm2838_peripherals.h | 3 +++
4 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/hw/arm/bcm2838.c b/hw/arm/bcm2838.c
index 089af412a3..fc56f87934 100644
--- a/hw/arm/bcm2838.c
+++ b/hw/arm/bcm2838.c
@@ -230,6 +230,15 @@ static void bcm2838_realize(DeviceState *dev, Error **errp)
qdev_connect_gpio_out(dma_9_10_irq_orgate, 0,
qdev_get_gpio_in(gicdev, GIC_SPI_INTERRUPT_DMA_9_10));
+ /* Connect PCIe host bridge to the interrupt controller */
+ for (int n = 0; n < BCM2838_PCIE_NUM_IRQS; n++) {
+ int int_n = GIC_SPI_INTERRUPT_PCI_INT_A + n;
+ sysbus_connect_irq(SYS_BUS_DEVICE(&ps->pcie_host), n,
+ qdev_get_gpio_in(gicdev, int_n));
+ bcm2838_pcie_host_set_irq_num(BCM2838_PCIE_HOST(&ps->pcie_host), n,
+ int_n);
+ }
+
/* Pass through inbound GPIO lines to the GIC */
qdev_init_gpio_in(dev, bcm2838_gic_set_irq, GIC_NUM_IRQS);
diff --git a/hw/arm/bcm2838_peripherals.c b/hw/arm/bcm2838_peripherals.c
index 812b5b8480..d923ba968a 100644
--- a/hw/arm/bcm2838_peripherals.c
+++ b/hw/arm/bcm2838_peripherals.c
@@ -15,6 +15,11 @@
#define CLOCK_ISP_OFFSET 0xc11000
#define CLOCK_ISP_SIZE 0x100
+#define PCIE_RC_OFFSET 0x1500000
+#define PCIE_MMIO_OFFSET 0xc0000000
+#define PCIE_MMIO_ARM_OFFSET 0x600000000
+#define PCIE_MMIO_SIZE 0x40000000
+
/* Lower peripheral base address on the VC (GPU) system bus */
#define BCM2838_VC_PERI_LOW_BASE 0x7c000000
@@ -35,6 +40,10 @@ static void bcm2838_peripherals_init(Object *obj)
/* Extended Mass Media Controller 2 */
object_initialize_child(obj, "emmc2", &s->emmc2, TYPE_SYSBUS_SDHCI);
+ /* PCIe Host Bridge */
+ object_initialize_child(obj, "pcie-host", &s->pcie_host,
+ TYPE_BCM2838_PCIE_HOST);
+
/* GPIO */
object_initialize_child(obj, "gpio", &s->gpio, TYPE_BCM2838_GPIO);
@@ -67,6 +76,8 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
MemoryRegion *mphi_mr;
BCM2838PeripheralState *s = BCM2838_PERIPHERALS(dev);
BCMSocPeripheralBaseState *s_base = BCM_SOC_PERIPHERALS_BASE(dev);
+ MemoryRegion *regs_mr;
+ MemoryRegion *mmio_mr;
int n;
bcm_soc_peripherals_common_realize(dev, errp);
@@ -182,6 +193,30 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
create_unimp(s_base, &s->clkisp, "bcm2835-clkisp", CLOCK_ISP_OFFSET,
CLOCK_ISP_SIZE);
+ /* PCIe Root Complex */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie_host), errp)) {
+ return;
+ }
+ /* RC registers region */
+ regs_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pcie_host), 0);
+ memory_region_add_subregion(&s->peri_low_mr, PCIE_RC_OFFSET, regs_mr);
+ /*
+ * MMIO region.
+ *
+ * The BCM2711 PCIe controller translates addresses between the ARM and
+ * PCI address spaces: the DTB declares CPU 0x600000000 as mapping to PCI
+ * 0xc0000000. Map an alias of the PCI window starting at that PCI offset
+ * so accesses land on the right addresses; mapping the window directly
+ * would expose PCI address 0 at the ARM base instead, and every BAR
+ * behind the root port would be read at the wrong address.
+ */
+ mmio_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pcie_host), 1);
+ memory_region_init_alias(&s->pcie_mmio_alias, OBJECT(s),
+ "bcm2838_pcie_mmio_alias", mmio_mr,
+ PCIE_MMIO_OFFSET, PCIE_MMIO_SIZE);
+ memory_region_add_subregion(get_system_memory(), PCIE_MMIO_ARM_OFFSET,
+ &s->pcie_mmio_alias);
+
/* GPIO */
if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio), errp)) {
return;
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c
index 06aeb8db01..038eefb234 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -64,7 +64,6 @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt)
/* Temporarily disable following devices until they are implemented */
const char *nodes_to_remove[] = {
- "brcm,bcm2711-pcie",
"brcm,bcm2711-rng200",
"brcm,bcm2711-thermal",
"brcm,bcm2711-genet-v5",
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 0be97e67c7..7fe92789e8 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -10,6 +10,7 @@
#define BCM2838_PERIPHERALS_H
#include "hw/arm/bcm2835_peripherals.h"
+#include "hw/arm/bcm2838_pcie.h"
#include "hw/sd/sdhci.h"
#include "hw/gpio/bcm2838_gpio.h"
@@ -65,6 +66,8 @@ struct BCM2838PeripheralState {
MemoryRegion mphi_mr_alias;
SDHCIState emmc2;
+ MemoryRegion pcie_mmio_alias;
+ BCM2838PcieHostState pcie_host;
BCM2838GpioState gpio;
OrIRQState mmc_irq_orgate;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 04/19] hw/net/bcm2838_genet: add GENET stub device
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (2 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 03/19] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 05/19] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
` (14 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé, Jason Wang
Add a bare-bones BCM2838 GENET (Gigabit Ethernet) sysbus device:
MMIO region registration, an out-of-range read/write handler that
just logs, and a reset callback. No register-level behavior yet;
that comes in the following patches.
Adapted from patch 19/41 of Sergey Kambalin's Raspberry Pi 4B
series (patchwork series 829638) against current QEMU master. The
reset callback and class_init() use the current Resettable-phases
API (ResettableClass / resettable_class_set_parent_phases()) rather
than the plain DeviceClass::reset hook the original patch used, and
headers moved under hw/core/, both API changes since the series was
originally posted.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 103 +++++++++++++++++++++++++++
hw/net/meson.build | 2 +
hw/net/trace-events | 16 +++++
include/hw/arm/bcm2838_peripherals.h | 2 +
include/hw/net/bcm2838_genet.h | 40 +++++++++++
5 files changed, 163 insertions(+)
create mode 100644 hw/net/bcm2838_genet.c
create mode 100644 include/hw/net/bcm2838_genet.h
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
new file mode 100644
index 0000000000..2ff93a3417
--- /dev/null
+++ b/hw/net/bcm2838_genet.c
@@ -0,0 +1,103 @@
+/*
+ * BCM2838 Gigabit Ethernet emulation
+ *
+ * Copyright (C) 2022 Ovchinnikov Vitalii <vitalii.ovchinnikov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "net/eth.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "net/checksum.h"
+#include "system/dma.h"
+#include "hw/core/resettable.h"
+#include "hw/net/bcm2838_genet.h"
+#include "trace.h"
+
+
+static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint64_t value = ~0;
+
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+
+ trace_bcm2838_genet_read(size, offset, value);
+ return value;
+}
+
+static void bcm2838_genet_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+}
+
+static const MemoryRegionOps bcm2838_genet_ops = {
+ .read = bcm2838_genet_read,
+ .write = bcm2838_genet_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {.max_access_size = 4},
+ .valid = {.min_access_size = 4},
+};
+
+
+static void bcm2838_genet_realize(DeviceState *dev, Error **errp)
+{
+ BCM2838GenetState *s = BCM2838_GENET(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ /* Controller registers */
+ memory_region_init_io(&s->regs_mr, OBJECT(s), &bcm2838_genet_ops, s,
+ "bcm2838_genet_regs", sizeof(s->regs));
+ sysbus_init_mmio(sbd, &s->regs_mr);
+}
+
+static void bcm2838_genet_phy_reset(BCM2838GenetState *s)
+{
+ trace_bcm2838_genet_phy_reset("done");
+}
+
+static void bcm2838_genet_reset(Object *obj, ResetType type)
+{
+ BCM2838GenetState *s = BCM2838_GENET(obj);
+
+ memset(&s->regs, 0x00, sizeof(s->regs));
+
+ trace_bcm2838_genet_reset("done");
+
+ bcm2838_genet_phy_reset(s);
+}
+
+static void bcm2838_genet_class_init(ObjectClass *class, const void *data)
+{
+ static ResettablePhases unused_parent_phases;
+ DeviceClass *dc = DEVICE_CLASS(class);
+ ResettableClass *rc = RESETTABLE_CLASS(class);
+
+ dc->realize = bcm2838_genet_realize;
+ resettable_class_set_parent_phases(rc, NULL, bcm2838_genet_reset, NULL,
+ &unused_parent_phases);
+}
+
+static const TypeInfo bcm2838_genet_info = {
+ .name = TYPE_BCM2838_GENET,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2838GenetState),
+ .class_init = bcm2838_genet_class_init,
+};
+
+static void bcm2838_genet_register(void)
+{
+ type_register_static(&bcm2838_genet_info);
+}
+
+type_init(bcm2838_genet_register)
diff --git a/hw/net/meson.build b/hw/net/meson.build
index 84f142df22..e290e1796b 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -69,4 +69,6 @@ system_ss.add(when: 'CONFIG_ROCKER', if_true: files(
stub_ss.add(files('rocker/rocker-stubs.c'))
system_ss.add(files('rocker/rocker-hmp-cmds.c'))
+system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2838_genet.c'))
+
subdir('can')
diff --git a/hw/net/trace-events b/hw/net/trace-events
index 001a20b0e2..2dde33e892 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -528,3 +528,19 @@ xen_netdev_rx(int dev, int idx, int status, int flags) "vif%u idx %d status %d f
ethlite_pkt_lost(uint32_t rx_ctrl) "rx_ctrl:0x%" PRIx32
ethlite_pkt_size_too_big(uint64_t size) "size:0x%" PRIx64
ethlite_pkt_tx_size_too_big(uint64_t size) "size:0x%" PRIx64
+# bcm2838_genet.c
+bcm2838_genet_read(unsigned int size, uint64_t offset, uint64_t value) "%u bytes @ 0x%04" PRIx64 ": 0x%016" PRIx64
+bcm2838_genet_write(unsigned int size, uint64_t offset, uint64_t value) "%u bytes @ 0x%04" PRIx64 ": 0x%016" PRIx64
+bcm2838_genet_can_receive(const char *state) "receive is %s"
+bcm2838_genet_receive(ssize_t bytes_received) "%zd bytes received"
+bcm2838_genet_phy_update_link(const char *link_state) "link is %s"
+bcm2838_genet_phy_reset(const char *status) "PHY reset %s"
+bcm2838_genet_reset(const char *status) "MAC reset %s"
+bcm2838_genet_mac_address(const char *info) "%s"
+bcm2838_genet_tx_dma(const char *dma_state) "TX DMA %s"
+bcm2838_genet_tx_dma_ring(uint32_t ring_en) "TX DMA enabled rings: 0x%05x"
+bcm2838_genet_tx_dma_ring_buf(uint32_t ring_buf_en) "TX DMA enabled ring buffers: 0x%05x"
+bcm2838_genet_tx_dma_ring_active(unsigned int ring, const char *ring_state) "ring %u is %s"
+bcm2838_genet_tx_request(unsigned int ring_idx, uint32_t prod_idx, uint32_t cons_idx) "ring %u, PROD_INDEX %u, CONS_INDEX %u"
+bcm2838_genet_tx(unsigned int ring_idx, uint64_t desc_idx, uint32_t desc_status, uint64_t data_addr) "ring %u, descriptor %" PRIu64 ": 0x%08x, data @ 0x%08" PRIx64
+bcm2838_genet_rx_dma_ring_active(unsigned int ring, const char *ring_state) "ring %u is %s"
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 7fe92789e8..2a4307bed7 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -52,6 +52,8 @@
#define BCM2838_MPHI_OFFSET 0xb200
#define BCM2838_MPHI_SIZE 0x200
+#define GENET_OFFSET 0x1580000
+
#define TYPE_BCM2838_PERIPHERALS "bcm2838-peripherals"
OBJECT_DECLARE_TYPE(BCM2838PeripheralState, BCM2838PeripheralClass,
BCM2838_PERIPHERALS)
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
new file mode 100644
index 0000000000..91cf1773d5
--- /dev/null
+++ b/include/hw/net/bcm2838_genet.h
@@ -0,0 +1,40 @@
+/*
+ * BCM2838 Gigabit Ethernet emulation
+ *
+ * Copyright (C) 2022 Ovchinnikov Vitalii <vitalii.ovchinnikov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BCM2838_GENET_H
+#define BCM2838_GENET_H
+
+#include "net/net.h"
+#include "hw/core/sysbus.h"
+
+#define TYPE_BCM2838_GENET "bcm2838-genet"
+OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
+
+#define BCM2838_GENET_REV_MAJOR 6
+#define BCM2838_GENET_REV_MINOR 0
+
+typedef struct {
+ uint8_t stub_area[0x10000]; /* temporary stub */
+} BCM2838GenetRegs;
+
+struct BCM2838GenetState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+
+ MemoryRegion regs_mr;
+ AddressSpace dma_as;
+
+ BCM2838GenetRegs regs;
+
+ qemu_irq irq_default;
+ qemu_irq irq_prio;
+};
+
+#endif /* BCM2838_GENET_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 05/19] hw/net/bcm2838_genet: add GENET register structs, part 1/4
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (3 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 04/19] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 06/19] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
` (13 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Add the SYS, GR_BRIDGE, EXT, INTRL0, and INTRL1 register block
layouts and the top-level BCM2838GenetRegs/BCM2838GenetState
structs, plus REG32/FIELD accessors for the revision-control and
interrupt-status registers.
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.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 37 ++++++++++++++++
include/hw/net/bcm2838_genet.h | 77 +++++++++++++++++++++++++++++++++-
2 files changed, 113 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 2ff93a3417..ff6602c1f3 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -15,10 +15,47 @@
#include "hw/core/irq.h"
#include "net/checksum.h"
#include "system/dma.h"
+#include "hw/core/registerfields.h"
#include "hw/core/resettable.h"
#include "hw/net/bcm2838_genet.h"
#include "trace.h"
+REG32(GENET_SYS_REV_CTRL, 0)
+FIELD(GENET_SYS_REV_CTRL, GPHY_REV, 0, 16)
+FIELD(GENET_SYS_REV_CTRL, MINOR_REV, 16, 4)
+FIELD(GENET_SYS_REV_CTRL, RSVD_20_23, 20, 4)
+FIELD(GENET_SYS_REV_CTRL, MAJOR_REV, 24, 4)
+FIELD(GENET_SYS_REV_CTRL, RSVD_28_31, 28, 4)
+
+REG32(GENET_INTRL_0, 0)
+FIELD(GENET_INTRL_0, SCB, 0, 1)
+FIELD(GENET_INTRL_0, EPHY, 1, 1)
+FIELD(GENET_INTRL_0, PHY_DET_R, 2, 1)
+FIELD(GENET_INTRL_0, PHY_DET_F, 3, 1)
+FIELD(GENET_INTRL_0, LINK_UP, 4, 1)
+FIELD(GENET_INTRL_0, LINK_DOWN, 5, 1)
+FIELD(GENET_INTRL_0, UMAC, 6, 1)
+FIELD(GENET_INTRL_0, UMAC_TSV, 7, 1)
+FIELD(GENET_INTRL_0, TBUF_UNDERRUN, 8, 1)
+FIELD(GENET_INTRL_0, RBUF_OVERFLOW, 9, 1)
+FIELD(GENET_INTRL_0, HFB_SM, 10, 1)
+FIELD(GENET_INTRL_0, HFB_MM, 11, 1)
+FIELD(GENET_INTRL_0, MPD_R, 12, 1)
+FIELD(GENET_INTRL_0, RXDMA_MBDONE, 13, 1)
+FIELD(GENET_INTRL_0, RXDMA_PDONE, 14, 1)
+FIELD(GENET_INTRL_0, RXDMA_BDONE, 15, 1)
+FIELD(GENET_INTRL_0, TXDMA_MBDONE, 16, 1)
+FIELD(GENET_INTRL_0, TXDMA_PDONE, 17, 1)
+FIELD(GENET_INTRL_0, TXDMA_BDONE, 18, 1)
+FIELD(GENET_INTRL_0, RSVD_19_22, 19, 4)
+FIELD(GENET_INTRL_0, MDIO_DONE, 23, 1)
+FIELD(GENET_INTRL_0, MDIO_ERROR, 24, 1)
+FIELD(GENET_INTRL_0, RSVD_25_31, 25, 4)
+
+REG32(GENET_INTRL_1, 0)
+FIELD(GENET_INTRL_1, TX_INTRS, 0, 16)
+FIELD(GENET_INTRL_1, RX_INTRS, 16, 16)
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index 91cf1773d5..af5e8abd2e 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -19,7 +19,82 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_REV_MINOR 0
typedef struct {
- uint8_t stub_area[0x10000]; /* temporary stub */
+ uint32_t rev_ctrl;
+ uint32_t port_ctrl;
+ uint32_t rbuf_flush_ctrl;
+ uint32_t tbuf_flush_ctrl;
+ uint32_t reserved_0x10[12];
+} BCM2838GenetRegsSys;
+
+typedef struct {
+ uint32_t reserved_0x0[16];
+} BCM2838GenetRegsGrBridge;
+
+typedef struct {
+ uint32_t pwr_mgmt;
+ uint32_t reserved_0x4[2];
+ uint32_t rgmii_oob_ctrl;
+ uint32_t reserved_0x10[3];
+ uint32_t gphy_ctrl;
+ uint32_t reserved_0x20[24];
+} BCM2838GenetRegsExt;
+
+typedef struct {
+ uint32_t stat;
+ uint32_t set;
+ uint32_t clear;
+ uint32_t mask_status;
+ uint32_t mask_set;
+ uint32_t mask_clear;
+ uint32_t reserved_0x18[10];
+} BCM2838GenetRegsIntrl0;
+
+typedef struct {
+ uint32_t stat;
+ uint32_t set;
+ uint32_t clear;
+ uint32_t mask_status;
+ uint32_t mask_set;
+ uint32_t mask_clear;
+ uint32_t reserved_0x18[10];
+} BCM2838GenetRegsIntrl1;
+
+typedef struct {
+ uint32_t ctrl;
+ uint32_t reserved_0x4[2];
+ uint32_t status;
+ uint32_t reserved_0x10;
+ uint32_t chk_ctrl;
+ uint32_t reserved_0x18[31];
+ uint32_t ovfl_cnt;
+ uint32_t err_cnt;
+ uint32_t energy_ctrl;
+ uint32_t reserved_0xA0[5];
+ uint32_t size_ctrl;
+ uint32_t reserved_0xB8[18];
+} BCM2838GenetRegsRbuf;
+
+typedef struct {
+ uint32_t ctrl;
+ uint32_t reserved_0x4[2];
+ uint32_t bp_mc;
+ uint32_t reserved_0x10;
+ uint32_t energy_ctrl;
+ uint32_t reserved_0x18[58];
+} BCM2838GenetRegsTbuf;
+
+typedef struct {
+ BCM2838GenetRegsSys sys;
+ BCM2838GenetRegsGrBridge gr_bridge;
+ BCM2838GenetRegsExt ext;
+ uint32_t reserved_0x100[64];
+ BCM2838GenetRegsIntrl0 intrl0;
+ BCM2838GenetRegsIntrl1 intrl1;
+ uint32_t reserved_0x280[32];
+ BCM2838GenetRegsRbuf rbuf;
+ uint32_t reserved_0x400[128];
+ BCM2838GenetRegsTbuf tbuf;
+ uint32_t reserved_0x700[64];
} BCM2838GenetRegs;
struct BCM2838GenetState {
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 06/19] hw/net/bcm2838_genet: add GENET register structs, part 2/4
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (4 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 05/19] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 07/19] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
` (12 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Add the UMAC (CMD, MAC address, MDIO command) and DMA (ring config,
control, producer/consumer index, status, RX descriptor
length/status) register layouts and their field accessors.
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.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 91 ++++++++++++++++++++++++++++++++++
include/hw/net/bcm2838_genet.h | 89 +++++++++++++++++++++++++++++++++
2 files changed, 180 insertions(+)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index ff6602c1f3..12a80534fb 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -20,6 +20,7 @@
#include "hw/net/bcm2838_genet.h"
#include "trace.h"
+/* GENET layouts */
REG32(GENET_SYS_REV_CTRL, 0)
FIELD(GENET_SYS_REV_CTRL, GPHY_REV, 0, 16)
FIELD(GENET_SYS_REV_CTRL, MINOR_REV, 16, 4)
@@ -56,6 +57,96 @@ REG32(GENET_INTRL_1, 0)
FIELD(GENET_INTRL_1, TX_INTRS, 0, 16)
FIELD(GENET_INTRL_1, RX_INTRS, 16, 16)
+REG32(GENET_UMAC_CMD, 0)
+FIELD(GENET_UMAC_CMD, TX_EN, 0, 1)
+FIELD(GENET_UMAC_CMD, RX_EN, 1, 1)
+FIELD(GENET_UMAC_CMD, SPEED, 2, 2)
+FIELD(GENET_UMAC_CMD, PROMISC, 4, 1)
+FIELD(GENET_UMAC_CMD, PAD_EN, 5, 1)
+FIELD(GENET_UMAC_CMD, CRC_FWD, 6, 1)
+FIELD(GENET_UMAC_CMD, PAUSE_FWD, 7, 1)
+FIELD(GENET_UMAC_CMD, RX_PAUSE_IGNORE, 8, 1)
+FIELD(GENET_UMAC_CMD, TX_ADDR_INS, 9, 1)
+FIELD(GENET_UMAC_CMD, HD_EN, 10, 1)
+FIELD(GENET_UMAC_CMD, SW_RESET_OLD, 11, 1)
+FIELD(GENET_UMAC_CMD, RSVD_12, 12, 1)
+FIELD(GENET_UMAC_CMD, SW_RESET, 13, 1)
+FIELD(GENET_UMAC_CMD, RSVD_14, 14, 1)
+FIELD(GENET_UMAC_CMD, LCL_LOOP_EN, 15, 1)
+FIELD(GENET_UMAC_CMD, RSVD_16_21, 16, 6)
+FIELD(GENET_UMAC_CMD, AUTO_CONFIG, 22, 1)
+FIELD(GENET_UMAC_CMD, CNTL_FRM_EN, 23, 1)
+FIELD(GENET_UMAC_CMD, NO_LEN_CHK, 24, 1)
+FIELD(GENET_UMAC_CMD, RMT_LOOP_EN, 25, 1)
+FIELD(GENET_UMAC_CMD, RX_ERR_DISC, 26, 1)
+FIELD(GENET_UMAC_CMD, PRBL_EN, 27, 1)
+FIELD(GENET_UMAC_CMD, TX_PAUSE_IGNORE, 28, 1)
+FIELD(GENET_UMAC_CMD, TX_RX_EN, 29, 1)
+FIELD(GENET_UMAC_CMD, RUNT_FILTER_DIS, 30, 1)
+FIELD(GENET_UMAC_CMD, RSVD_31, 31, 1)
+
+REG32(GENET_UMAC_MAC_0, 0)
+FIELD(GENET_UMAC_MAC_0, ADDR_3, 0, 8)
+FIELD(GENET_UMAC_MAC_0, ADDR_2, 8, 8)
+FIELD(GENET_UMAC_MAC_0, ADDR_1, 16, 8)
+FIELD(GENET_UMAC_MAC_0, ADDR_0, 24, 8)
+
+REG32(GENET_UMAC_MAC_1, 0)
+FIELD(GENET_UMAC_MAC_1, ADDR_5, 0, 8)
+FIELD(GENET_UMAC_MAC_1, ADDR_4, 8, 8)
+FIELD(GENET_UMAC_MAC_1, RSVD_16_31, 16, 8)
+
+REG32(GENET_UMAC_MDIO_CMD, 0)
+FIELD(GENET_UMAC_MDIO_CMD, REG_DATA, 0, 16)
+FIELD(GENET_UMAC_MDIO_CMD, REG_ID, 16, 5)
+FIELD(GENET_UMAC_MDIO_CMD, PHY_ID, 21, 5)
+FIELD(GENET_UMAC_MDIO_CMD, WR, 26, 1)
+FIELD(GENET_UMAC_MDIO_CMD, RD, 27, 1)
+FIELD(GENET_UMAC_MDIO_CMD, RD_FAIL, 28, 1)
+FIELD(GENET_UMAC_MDIO_CMD, START_BUSY, 29, 1)
+FIELD(GENET_UMAC_MDIO_CMD, RSVD_30_31, 30, 2)
+
+REG32(GENET_DMA_RING_CFG, 0)
+FIELD(GENET_DMA_RING_CFG, EN, 0, 17)
+FIELD(GENET_DMA_RING_CFG, RSVD_17_31, 17, 14)
+
+REG32(GENET_DMA_CTRL, 0)
+FIELD(GENET_DMA_CTRL, EN, 0, 1)
+FIELD(GENET_DMA_CTRL, RING_BUF_EN, 1, 17)
+FIELD(GENET_DMA_CTRL, RSVD_18_19, 18, 2)
+FIELD(GENET_DMA_CTRL, TSB_SWAP_EN, 20, 1)
+FIELD(GENET_DMA_CTRL, RSVD_21_31, 21, 11)
+
+REG32(GENET_DMA_PROD_INDEX, 0)
+FIELD(GENET_DMA_PROD_INDEX, INDEX, 0, 16)
+FIELD(GENET_DMA_PROD_INDEX, DISCARD_CNT, 16, 16)
+
+REG32(GENET_DMA_CONS_INDEX, 0)
+FIELD(GENET_DMA_CONS_INDEX, INDEX, 0, 16)
+FIELD(GENET_DMA_CONS_INDEX, RSVD_16_31, 16, 16)
+
+REG32(GENET_DMA_STATUS, 0)
+FIELD(GENET_DMA_STATUS, DISABLED, 0, 1)
+FIELD(GENET_DMA_STATUS, DESC_RAM_INIT_BUSY, 1, 1)
+FIELD(GENET_DMA_STATUS, RSVD_2_31, 2, 30)
+
+#define GENET_DMA_ENABLE_MASK ((1U << 18) - 1)
+
+REG32(GENET_RDMA_LENGTH_STATUS, 0)
+FIELD(GENET_RDMA_LENGTH_STATUS, OVERRUN, 0, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, CRC_ERROR, 1, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, RXERR, 2, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, NO, 3, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, LG, 4, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, MULTICAST, 5, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, BROADCAST, 6, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, RSVD_7_11, 7, 5)
+FIELD(GENET_RDMA_LENGTH_STATUS, WRAP, 12, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, SOP, 13, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, EOP, 14, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, OWN, 15, 1)
+FIELD(GENET_RDMA_LENGTH_STATUS, BUFLENGTH, 16, 12)
+FIELD(GENET_RDMA_LENGTH_STATUS, RSVD_28_31, 29, 4)
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index af5e8abd2e..b3893c2dad 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -18,6 +18,10 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_REV_MAJOR 6
#define BCM2838_GENET_REV_MINOR 0
+#define BCM2838_GENET_DMA_DESC_CNT 256
+#define BCM2838_GENET_DMA_RING_CNT 17
+#define BCM2838_GENET_DMA_RING_DEFAULT (BCM2838_GENET_DMA_RING_CNT - 1)
+
typedef struct {
uint32_t rev_ctrl;
uint32_t port_ctrl;
@@ -83,6 +87,88 @@ typedef struct {
uint32_t reserved_0x18[58];
} BCM2838GenetRegsTbuf;
+typedef struct {
+ uint32_t reserved_0x0;
+ uint32_t hd_bkp_ctrl;
+ uint32_t cmd;
+ uint32_t mac0;
+ uint32_t mac1;
+ uint32_t max_frame_len;
+ uint32_t pause_quanta;
+ uint32_t reserved_0x1C[10];
+ uint32_t mode;
+ uint32_t frm_tag0;
+ uint32_t frm_tag1;
+ uint32_t reserved_0x50[3];
+ uint32_t tx_ipg_len;
+ uint32_t reserved_0x60;
+ uint32_t eee_ctrl;
+ uint32_t eee_lpi_timer;
+ uint32_t eee_wake_timer;
+ uint32_t eee_ref_count;
+ uint32_t reserved_0x74;
+ uint32_t rx_ipg_inv;
+ uint32_t reserved_0x7C[165];
+ uint32_t macsec_prog_tx_crc;
+ uint32_t macsec_ctrl;
+ uint32_t reserved_0x318[6];
+ uint32_t pause_ctrl;
+ uint32_t tx_flush;
+ uint32_t rx_fifo_status;
+ uint32_t tx_fifo_status;
+ uint32_t reserved_0x340[48];
+ uint32_t mib[96];
+ uint32_t mib_ctrl;
+ uint32_t reserved_0x584[36];
+ uint32_t mdio_cmd;
+ uint32_t reserved_0x618[2];
+ uint32_t mpd_ctrl;
+ uint32_t mpd_pw_ms;
+ uint32_t mpd_pw_ls;
+ uint32_t reserved_0x62C[3];
+ uint32_t mdf_err_cnt;
+ uint32_t reserved_0x63C[5];
+ uint32_t mdf_ctrl;
+ uint32_t mdf_addr;
+ uint32_t reserved_0x658[106];
+} BCM2838GenetRegsUmac;
+
+typedef struct {
+ uint32_t length_status;
+ uint32_t address_lo;
+ uint32_t address_hi;
+} BCM2838GenetRdmaDesc;
+
+typedef struct {
+ uint32_t write_ptr;
+ uint32_t write_ptr_hi;
+ uint32_t prod_index;
+ uint32_t cons_index;
+ uint32_t ring_buf_size;
+ uint32_t start_addr;
+ uint32_t start_addr_hi;
+ uint32_t end_addr;
+ uint32_t end_addr_hi;
+ uint32_t mbuf_done_tresh;
+ uint32_t xon_xoff_tresh;
+ uint32_t read_ptr;
+ uint32_t read_ptr_hi;
+ uint32_t reserved_0x34[3];
+} BCM2838GenetRdmaRing;
+
+typedef struct {
+ BCM2838GenetRdmaDesc descs[BCM2838_GENET_DMA_DESC_CNT];
+ BCM2838GenetRdmaRing rings[BCM2838_GENET_DMA_RING_CNT];
+ uint32_t ring_cfg;
+ uint32_t ctrl;
+ uint32_t status;
+ uint32_t scb_burst_size;
+ uint32_t reserved_0x1050[7];
+ uint32_t ring_timeout[17];
+ uint32_t index2ring[8];
+ uint32_t reserved_0x10D0[972];
+} BCM2838GenetRegsRdma;
+
typedef struct {
BCM2838GenetRegsSys sys;
BCM2838GenetRegsGrBridge gr_bridge;
@@ -95,6 +181,9 @@ typedef struct {
uint32_t reserved_0x400[128];
BCM2838GenetRegsTbuf tbuf;
uint32_t reserved_0x700[64];
+ BCM2838GenetRegsUmac umac;
+ uint32_t reserved_0x1000[1024];
+ BCM2838GenetRegsRdma rdma;
} BCM2838GenetRegs;
struct BCM2838GenetState {
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 07/19] hw/net/bcm2838_genet: add GENET register structs, part 3/4
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (5 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 06/19] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 08/19] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
` (11 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Add the TDMA (transmit DMA ring/length-status) register layout and
field accessors, and the PHY BMCR/BMSR MII register field
definitions.
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.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 88 ++++++++++++++++++++++++++++++++++
include/hw/net/bcm2838_genet.h | 88 ++++++++++++++++++++++++++++++++++
2 files changed, 176 insertions(+)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 12a80534fb..2bc2a770b1 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -148,6 +148,94 @@ FIELD(GENET_RDMA_LENGTH_STATUS, OWN, 15, 1)
FIELD(GENET_RDMA_LENGTH_STATUS, BUFLENGTH, 16, 12)
FIELD(GENET_RDMA_LENGTH_STATUS, RSVD_28_31, 29, 4)
+REG32(GENET_TDMA_LENGTH_STATUS, 0)
+FIELD(GENET_TDMA_LENGTH_STATUS, RSVD_0_3, 0, 4)
+FIELD(GENET_TDMA_LENGTH_STATUS, DO_CSUM, 4, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, OW_CRC, 5, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, APPEND_CRC, 6, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, RSVD_7_8, 7, 2)
+FIELD(GENET_TDMA_LENGTH_STATUS, UNDERRUN, 9, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, RSVD_10_11, 10, 2)
+FIELD(GENET_TDMA_LENGTH_STATUS, WRAP, 12, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, SOP, 13, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, EOP, 14, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, OWN, 15, 1)
+FIELD(GENET_TDMA_LENGTH_STATUS, BUFLENGTH, 16, 12)
+FIELD(GENET_TDMA_LENGTH_STATUS, RSVD_28_31, 29, 4)
+
+REG16(GENET_PHY_BMCR, 0)
+FIELD(GENET_PHY_BMCR, RSVD_0_5, 0, 6)
+FIELD(GENET_PHY_BMCR, SPEED1000, 6, 1)
+FIELD(GENET_PHY_BMCR, CTST, 7, 1)
+FIELD(GENET_PHY_BMCR, FULLDPLX, 8, 1)
+FIELD(GENET_PHY_BMCR, ANRESTART, 9, 1)
+FIELD(GENET_PHY_BMCR, ISOLATE, 10, 1)
+FIELD(GENET_PHY_BMCR, PDOWN, 11, 1)
+FIELD(GENET_PHY_BMCR, AENABLE, 12, 1)
+FIELD(GENET_PHY_BMCR, SPEED100, 13, 1)
+FIELD(GENET_PHY_BMCR, LOOPBACK, 14, 1)
+FIELD(GENET_PHY_BMCR, RESET, 15, 1)
+
+REG16(GENET_PHY_BMSR, 0)
+FIELD(GENET_PHY_BMSR, ERCAP, 0, 1)
+FIELD(GENET_PHY_BMSR, JCD, 1, 1)
+FIELD(GENET_PHY_BMSR, LSTATUS, 2, 1)
+FIELD(GENET_PHY_BMSR, ANEGCAPABLE, 3, 1)
+FIELD(GENET_PHY_BMSR, RFAULT, 4, 1)
+FIELD(GENET_PHY_BMSR, ANEGCOMPLETE, 5, 1)
+FIELD(GENET_PHY_BMSR, RSVD_6_7, 6, 2)
+FIELD(GENET_PHY_BMSR, ESTATEN, 8, 1)
+FIELD(GENET_PHY_BMSR, _100HALF2, 9, 1)
+FIELD(GENET_PHY_BMSR, _100FULL2, 10, 1)
+FIELD(GENET_PHY_BMSR, _10HALF, 11, 1)
+FIELD(GENET_PHY_BMSR, _10FULL, 12, 1)
+FIELD(GENET_PHY_BMSR, _100HALF, 13, 1)
+FIELD(GENET_PHY_BMSR, _100FULL, 14, 1)
+FIELD(GENET_PHY_BMSR, _10BASE4, 15, 1)
+
+REG16(GENET_PHY_LPA, 0)
+FIELD(GENET_PHY_LPA, SLCT, 0, 5)
+FIELD(GENET_PHY_LPA, _10HALF_1000XFULL, 5, 1)
+FIELD(GENET_PHY_LPA, _10FULL_1000XHALF, 6, 1)
+FIELD(GENET_PHY_LPA, _100HALF_1000XPAUSE, 7, 1)
+FIELD(GENET_PHY_LPA, _100FULL_1000XPAUSE_ASYM, 8, 1)
+FIELD(GENET_PHY_LPA, _100BASE4, 9, 1)
+FIELD(GENET_PHY_LPA, PAUSE_CAP, 10, 1)
+FIELD(GENET_PHY_LPA, PAUSE_ASYM, 11, 1)
+FIELD(GENET_PHY_LPA, RSVD_12, 12, 1)
+FIELD(GENET_PHY_LPA, RFAULT, 13, 1)
+FIELD(GENET_PHY_LPA, LPACK, 14, 1)
+FIELD(GENET_PHY_LPA, NPAGE, 15, 1)
+
+REG16(GENET_PHY_STAT_1000, 0)
+FIELD(GENET_PHY_STAT_1000, RSVD_0_9, 0, 10)
+FIELD(GENET_PHY_STAT_1000, HALF, 10, 1)
+FIELD(GENET_PHY_STAT_1000, FULL, 11, 1)
+FIELD(GENET_PHY_STAT_1000, REMRXOK, 12, 1)
+FIELD(GENET_PHY_STAT_1000, LOCALRXOK, 13, 1)
+FIELD(GENET_PHY_STAT_1000, MSRES, 14, 1)
+FIELD(GENET_PHY_STAT_1000, MSFAIL, 15, 1)
+
+REG16(GENET_PHY_AUX_CTRL_0, 0)
+FIELD(GENET_PHY_AUX_CTRL_0, REG_ID_MASK, 0, 3)
+FIELD(GENET_PHY_AUX_CTRL_0, RSVD_3, 3, 1)
+FIELD(GENET_PHY_AUX_CTRL_0, REG_DATA, 4, 8)
+FIELD(GENET_PHY_AUX_CTRL_0, REG_ID, 12, 3)
+FIELD(GENET_PHY_AUX_CTRL_0, MISC_WREN, 15, 1)
+
+REG16(GENET_PHY_AUX_CTRL_1, 0)
+FIELD(GENET_PHY_AUX_CTRL_1, RSVD_0_3, 0, 4)
+FIELD(GENET_PHY_AUX_CTRL_1, REG_DATA, 4, 12)
+
+REG16(GENET_PHY_SHADOW, 0)
+FIELD(GENET_PHY_SHADOW, REG_DATA, 0, 10)
+FIELD(GENET_PHY_SHADOW, REG_ID, 10, 5)
+FIELD(GENET_PHY_SHADOW, WR, 15, 1)
+
+REG16(GENET_PHY_EXP_SEL, 0)
+FIELD(GENET_PHY_EXP_SEL, REG_ID, 0, 8)
+FIELD(GENET_PHY_EXP_SEL, BLOCK_ID, 8, 8)
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t value = ~0;
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index b3893c2dad..fedfbde066 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -22,6 +22,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_DMA_RING_CNT 17
#define BCM2838_GENET_DMA_RING_DEFAULT (BCM2838_GENET_DMA_RING_CNT - 1)
+#define BCM2838_GENET_HFB_FILTER_CNT 48
+#define BCM2838_GENET_HFB_FILTER_SIZE 128
+
typedef struct {
uint32_t rev_ctrl;
uint32_t port_ctrl;
@@ -169,6 +172,53 @@ typedef struct {
uint32_t reserved_0x10D0[972];
} BCM2838GenetRegsRdma;
+typedef struct {
+ uint32_t length_status;
+ uint32_t address_lo;
+ uint32_t address_hi;
+} BCM2838GenetTdmaDesc;
+
+typedef struct {
+ uint32_t read_ptr;
+ uint32_t read_ptr_hi;
+ uint32_t cons_index;
+ uint32_t prod_index;
+ uint32_t ring_buf_size;
+ uint32_t start_addr;
+ uint32_t start_addr_hi;
+ uint32_t end_addr;
+ uint32_t end_addr_hi;
+ uint32_t mbuf_done_tresh;
+ uint32_t flow_period;
+ uint32_t write_ptr;
+ uint32_t write_ptr_hi;
+ uint32_t reserved_0x34[3];
+} BCM2838GenetTdmaRing;
+
+typedef struct {
+ BCM2838GenetTdmaDesc descs[BCM2838_GENET_DMA_DESC_CNT];
+ BCM2838GenetTdmaRing rings[BCM2838_GENET_DMA_RING_CNT];
+ uint32_t ring_cfg;
+ uint32_t ctrl;
+ uint32_t status;
+ uint32_t scb_burst_size;
+ uint32_t reserved_0x1050[7];
+ uint32_t arb_ctrl;
+ uint32_t priority[3];
+ uint32_t reserved_0x10D0[993];
+} BCM2838GenetRegsTdma;
+
+typedef struct {
+ uint8_t flt[BCM2838_GENET_HFB_FILTER_CNT * BCM2838_GENET_HFB_FILTER_SIZE
+ * sizeof(uint32_t)];
+ uint32_t reserved_0x6000[1792];
+ uint32_t ctrl;
+ uint32_t flt_enable[2];
+ uint32_t reserved_0x7C0C[4];
+ uint32_t flt_len[BCM2838_GENET_HFB_FILTER_CNT / sizeof(uint32_t)];
+ uint32_t reserved_0x7C4C[237];
+} BCM2838GenetRegsHfb;
+
typedef struct {
BCM2838GenetRegsSys sys;
BCM2838GenetRegsGrBridge gr_bridge;
@@ -184,8 +234,45 @@ typedef struct {
BCM2838GenetRegsUmac umac;
uint32_t reserved_0x1000[1024];
BCM2838GenetRegsRdma rdma;
+ BCM2838GenetRegsTdma tdma;
+ uint32_t reserved_0x6000[2048];
+ BCM2838GenetRegsHfb hfb;
} BCM2838GenetRegs;
+typedef struct {
+ uint16_t bmcr;
+ uint16_t bmsr;
+ uint16_t sid1;
+ uint16_t sid2;
+ uint16_t advertise;
+ uint16_t lpa;
+ uint16_t expansion;
+ uint16_t next_page;
+ uint16_t lpa_next_page;
+ uint16_t ctrl1000;
+ uint16_t stat1000;
+ uint16_t reserved_11_12[2];
+ uint16_t mmd_ctrl;
+ uint16_t mmd_data;
+ uint16_t estatus;
+ uint16_t ecr;
+ uint16_t esr;
+ uint16_t dcounter;
+ uint16_t fcscounter;
+ uint16_t nwaytest;
+ uint16_t exp_data;
+ uint16_t srevision;
+ uint16_t exp_ctrl;
+ uint16_t aux_ctl;
+ uint16_t phyaddr;
+ uint16_t isr;
+ uint16_t imr;
+ uint16_t shd;
+ uint16_t reserved_29;
+ uint16_t rdb_addr;
+ uint16_t rdb_data;
+} BCM2838GenetPhyRegs;
+
struct BCM2838GenetState {
/*< private >*/
SysBusDevice parent_obj;
@@ -196,6 +283,7 @@ struct BCM2838GenetState {
AddressSpace dma_as;
BCM2838GenetRegs regs;
+ BCM2838GenetPhyRegs phy_regs;
qemu_irq irq_default;
qemu_irq irq_prio;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 08/19] hw/net/bcm2838_genet: add GENET register structs, part 4/4
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (6 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 07/19] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 09/19] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
` (10 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Add the PHY "shadow" register structs (BCM54213PE PHY-specific
clock control, RGMII mode, LED, and expansion-block registers
accessed via the AUX_CTL/EXP_SHD indirection scheme) used by the
MDIO emulation added in a later patch.
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.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
include/hw/net/bcm2838_genet.h | 37 ++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index fedfbde066..12439b580d 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -25,6 +25,12 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_HFB_FILTER_CNT 48
#define BCM2838_GENET_HFB_FILTER_SIZE 128
+#define BCM2838_GENET_PHY_AUX_CTL_MISC 0x7
+#define BCM2838_GENET_PHY_AUX_CTL_REGS_SIZE 8
+
+#define BCM2838_GENET_PHY_EXP_SHD_BLOCKS_CNT 256
+#define BCM2838_GENET_PHY_EXP_SHD_REGS_CNT 256
+
typedef struct {
uint32_t rev_ctrl;
uint32_t port_ctrl;
@@ -273,6 +279,34 @@ typedef struct {
uint16_t rdb_data;
} BCM2838GenetPhyRegs;
+typedef struct {
+ uint16_t reserved_0_2[3];
+ uint16_t clk_ctl;
+ uint16_t scr2;
+ uint16_t scr3;
+ uint16_t reserved_6_9[4];
+ uint16_t apd;
+ uint16_t rgmii_mode;
+ uint16_t reserved_12;
+ uint16_t leds1;
+ uint16_t reserved_14_18[5];
+ uint16_t _100fx_ctrl;
+ uint16_t ssd;
+ uint16_t reserved_21_30[10];
+ uint16_t mode;
+} BCM2838GenetPhyShdRegs;
+
+typedef struct {
+ uint16_t auxctl;
+ uint16_t reserved_1_6[BCM2838_GENET_PHY_AUX_CTL_REGS_SIZE - 2];
+ uint16_t misc;
+} BCM2838GenetPhyAuxShdRegs;
+
+typedef struct {
+ uint16_t regs[BCM2838_GENET_PHY_EXP_SHD_BLOCKS_CNT]
+ [BCM2838_GENET_PHY_EXP_SHD_REGS_CNT];
+} BCM2838GenetPhyExpShdRegs;
+
struct BCM2838GenetState {
/*< private >*/
SysBusDevice parent_obj;
@@ -284,6 +318,9 @@ struct BCM2838GenetState {
BCM2838GenetRegs regs;
BCM2838GenetPhyRegs phy_regs;
+ BCM2838GenetPhyShdRegs phy_shd_regs;
+ BCM2838GenetPhyAuxShdRegs phy_aux_ctl_shd_regs;
+ BCM2838GenetPhyExpShdRegs phy_exp_shd_regs;
qemu_irq irq_default;
qemu_irq irq_prio;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 09/19] hw/net/bcm2838_genet: add GENET register access macros
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (7 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 08/19] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 10/19] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
` (9 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Add offsetof()-based byte-offset macros for the register block
layouts added in the previous patches (INTRL0/1, UMAC, TDMA/RDMA
and their per-ring registers, HFB filters), used by the MMIO
read/write dispatch implemented in the following patches.
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.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
include/hw/net/bcm2838_genet.h | 78 ++++++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index 12439b580d..b9895933b6 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -22,9 +22,87 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_DMA_RING_CNT 17
#define BCM2838_GENET_DMA_RING_DEFAULT (BCM2838_GENET_DMA_RING_CNT - 1)
+#define BCM2838_GENET_HFB_FILTER_REGS offsetof(BCM2838GenetRegs, hfb)
+#define BCM2838_GENET_HFB_FILTER_REG(reg) (BCM2838_GENET_HFB_FILTER_REGS \
+ + offsetof(BCM2838GenetRegsHfb, reg))
#define BCM2838_GENET_HFB_FILTER_CNT 48
#define BCM2838_GENET_HFB_FILTER_SIZE 128
+#define BCM2838_GENET_INTRL0_REG(reg) (offsetof(BCM2838GenetRegs, intrl0) \
+ + offsetof(BCM2838GenetRegsIntrl0, reg))
+#define BCM2838_GENET_INTRL0_SET BCM2838_GENET_INTRL0_REG(set)
+#define BCM2838_GENET_INTRL0_CLEAR BCM2838_GENET_INTRL0_REG(clear)
+#define BCM2838_GENET_INTRL0_MASK_SET BCM2838_GENET_INTRL0_REG(mask_set)
+#define BCM2838_GENET_INTRL0_MASK_CLEAR BCM2838_GENET_INTRL0_REG(mask_clear)
+
+#define BCM2838_GENET_INTRL1_REG(reg) (offsetof(BCM2838GenetRegs, intrl1) \
+ + offsetof(BCM2838GenetRegsIntrl1, reg))
+#define BCM2838_GENET_INTRL1_SET BCM2838_GENET_INTRL1_REG(set)
+#define BCM2838_GENET_INTRL1_CLEAR BCM2838_GENET_INTRL1_REG(clear)
+#define BCM2838_GENET_INTRL1_MASK_SET BCM2838_GENET_INTRL1_REG(mask_set)
+#define BCM2838_GENET_INTRL1_MASK_CLEAR BCM2838_GENET_INTRL1_REG(mask_clear)
+
+#define BCM2838_GENET_UMAC_REG(reg) (offsetof(BCM2838GenetRegs, umac) \
+ + offsetof(BCM2838GenetRegsUmac, reg))
+#define BCM2838_GENET_UMAC_CMD BCM2838_GENET_UMAC_REG(cmd)
+#define BCM2838_GENET_UMAC_MAC0 BCM2838_GENET_UMAC_REG(mac0)
+#define BCM2838_GENET_UMAC_MAC1 BCM2838_GENET_UMAC_REG(mac1)
+#define BCM2838_GENET_UMAC_MDIO_CMD BCM2838_GENET_UMAC_REG(mdio_cmd)
+
+#define BCM2838_GENET_TDMA_REGS offsetof(BCM2838GenetRegs, tdma)
+#define BCM2838_GENET_TDMA_REG(reg) (BCM2838_GENET_TDMA_REGS \
+ + offsetof(BCM2838GenetRegsTdma, reg))
+#define BCM2838_GENET_TDMA_RINGS BCM2838_GENET_TDMA_REG(rings)
+#define BCM2838_GENET_TDMA_RING_CFG BCM2838_GENET_TDMA_REG(ring_cfg)
+#define BCM2838_GENET_TDMA_CTRL BCM2838_GENET_TDMA_REG(ctrl)
+#define BCM2838_GENET_TDMA_STATUS BCM2838_GENET_TDMA_REG(status)
+
+#define BCM2838_GENET_RDMA_REGS offsetof(BCM2838GenetRegs, rdma)
+#define BCM2838_GENET_RDMA_REG(reg) (BCM2838_GENET_RDMA_REGS \
+ + offsetof(BCM2838GenetRegsRdma, reg))
+#define BCM2838_GENET_RDMA_RINGS BCM2838_GENET_RDMA_REG(rings)
+#define BCM2838_GENET_RDMA_RING_CFG BCM2838_GENET_RDMA_REG(ring_cfg)
+#define BCM2838_GENET_RDMA_CTRL BCM2838_GENET_RDMA_REG(ctrl)
+#define BCM2838_GENET_RDMA_STATUS BCM2838_GENET_RDMA_REG(status)
+
+#define BCM2838_GENET_TRING_REG(reg) offsetof(BCM2838GenetTdmaRing, reg)
+#define BCM2838_GENET_TRING_WRITE_PTR BCM2838_GENET_TRING_REG(write_ptr)
+#define BCM2838_GENET_TRING_WRITE_PTR_HI BCM2838_GENET_TRING_REG(write_ptr_hi)
+#define BCM2838_GENET_TRING_PROD_INDEX BCM2838_GENET_TRING_REG(prod_index)
+#define BCM2838_GENET_TRING_CONS_INDEX BCM2838_GENET_TRING_REG(cons_index)
+#define BCM2838_GENET_TRING_RING_BUF_SIZE BCM2838_GENET_TRING_REG(ring_buf_size)
+#define BCM2838_GENET_TRING_RING_START_ADDR BCM2838_GENET_TRING_REG(start_addr)
+#define BCM2838_GENET_TRING_RING_START_ADDR_HI BCM2838_GENET_TRING_REG(start_addr_hi)
+#define BCM2838_GENET_TRING_RING_END_ADDR BCM2838_GENET_TRING_REG(end_addr)
+#define BCM2838_GENET_TRING_RING_END_ADDR_HI BCM2838_GENET_TRING_REG(end_addr_hi)
+#define BCM2838_GENET_TRING_RING_MBUF_DONE_TRESH BCM2838_GENET_TRING_REG(mbuf_done_tresh)
+#define BCM2838_GENET_TRING_RING_FLOW_PERIOD BCM2838_GENET_TRING_REG(flow_period)
+#define BCM2838_GENET_TRING_RING_READ_PTR BCM2838_GENET_TRING_REG(read_ptr)
+#define BCM2838_GENET_TRING_RING_READ_PTR_HI BCM2838_GENET_TRING_REG(read_ptr_hi)
+
+#define BCM2838_GENET_RRING_REG(reg) offsetof(BCM2838GenetRdmaRing, reg)
+#define BCM2838_GENET_RRING_WRITE_PTR BCM2838_GENET_RRING_REG(write_ptr)
+#define BCM2838_GENET_RRING_WRITE_PTR_HI BCM2838_GENET_RRING_REG(write_ptr_hi)
+#define BCM2838_GENET_RRING_PROD_INDEX BCM2838_GENET_RRING_REG(prod_index)
+#define BCM2838_GENET_RRING_CONS_INDEX BCM2838_GENET_RRING_REG(cons_index)
+#define BCM2838_GENET_RRING_RING_BUF_SIZE BCM2838_GENET_RRING_REG(ring_buf_size)
+#define BCM2838_GENET_RRING_RING_START_ADDR BCM2838_GENET_RRING_REG(start_addr)
+#define BCM2838_GENET_RRING_RING_START_ADDR_HI BCM2838_GENET_RRING_REG(start_addr_hi)
+#define BCM2838_GENET_RRING_RING_END_ADDR BCM2838_GENET_RRING_REG(end_addr)
+#define BCM2838_GENET_RRING_RING_END_ADDR_HI BCM2838_GENET_RRING_REG(end_addr_hi)
+#define BCM2838_GENET_RRING_RING_MBUF_DONE_TRESH BCM2838_GENET_RRING_REG(mbuf_done_tresh)
+#define BCM2838_GENET_RRING_RING_XON_XOFF_TRESH BCM2838_GENET_RRING_REG(xon_xoff_tresh)
+#define BCM2838_GENET_RRING_RING_READ_PTR BCM2838_GENET_RRING_REG(read_ptr)
+#define BCM2838_GENET_RRING_RING_READ_PTR_HI BCM2838_GENET_RRING_REG(read_ptr_hi)
+
+
+#define BCM2838_GENET_PHY_REG(reg) (offsetof(BCM2838GenetPhyRegs, reg) / 2)
+#define BCM2838_GENET_PHY_BMCR BCM2838_GENET_PHY_REG(bmcr)
+#define BCM2838_GENET_PHY_AUX_CTL BCM2838_GENET_PHY_REG(aux_ctl)
+#define BCM2838_GENET_PHY_SHD BCM2838_GENET_PHY_REG(shd)
+#define BCM2838_GENET_EXP_DATA BCM2838_GENET_PHY_REG(exp_data)
+#define BCM2838_GENET_EXP_SEL BCM2838_GENET_PHY_REG(exp_ctrl)
+
#define BCM2838_GENET_PHY_AUX_CTL_MISC 0x7
#define BCM2838_GENET_PHY_AUX_CTL_REGS_SIZE 8
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 10/19] hw/net/bcm2838_genet: implement GENET register ops
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (8 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 09/19] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 11/19] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
` (8 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Replace the stub MMIO read/write handlers with real dispatch over
the register offsets from the previous patches: SYS revision
control, INTRL0/1 status/mask registers, UMAC command/MAC-address/
MDIO-command registers, and TDMA/RDMA ring configuration, wired up
to the NIC's realize()/reset() and qdev NIC properties
(DEFINE_NIC_PROPERTIES) so the device gets a MAC address and peer
netdev from the command line.
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:
adapted to the current Property/class_init/Resettable APIs, with no
functional change to the register dispatch logic itself.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 197 +++++++++++++++++++++++++++++++--
include/hw/net/bcm2838_genet.h | 2 +
2 files changed, 190 insertions(+), 9 deletions(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 2bc2a770b1..f3c5ca26c5 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -236,25 +236,151 @@ REG16(GENET_PHY_EXP_SEL, 0)
FIELD(GENET_PHY_EXP_SEL, REG_ID, 0, 8)
FIELD(GENET_PHY_EXP_SEL, BLOCK_ID, 8, 8)
+static void bcm2838_genet_set_qemu_mac(BCM2838GenetState *s)
+{
+ const MACAddr *addr = &s->nic_conf.macaddr;
+
+ s->regs.umac.mac0 = FIELD_DP32(s->regs.umac.mac0, GENET_UMAC_MAC_0,
+ ADDR_0, addr->a[0]);
+ s->regs.umac.mac0 = FIELD_DP32(s->regs.umac.mac0, GENET_UMAC_MAC_0,
+ ADDR_1, addr->a[1]);
+ s->regs.umac.mac0 = FIELD_DP32(s->regs.umac.mac0, GENET_UMAC_MAC_0,
+ ADDR_2, addr->a[2]);
+ s->regs.umac.mac0 = FIELD_DP32(s->regs.umac.mac0, GENET_UMAC_MAC_0,
+ ADDR_3, addr->a[3]);
+ s->regs.umac.mac1 = FIELD_DP32(s->regs.umac.mac1, GENET_UMAC_MAC_1,
+ ADDR_4, addr->a[4]);
+ s->regs.umac.mac1 = FIELD_DP32(s->regs.umac.mac1, GENET_UMAC_MAC_1,
+ ADDR_5, addr->a[5]);
+}
+
+static void bcm2838_genet_set_irq_default(BCM2838GenetState *s)
+{
+ uint32_t intrl_0_status = s->regs.intrl0.stat;
+ uint32_t intrl_0_mask = s->regs.intrl0.mask_status;
+ int level = (intrl_0_status & ~intrl_0_mask) == 0 ? 0 : 1;
+
+ qemu_set_irq(s->irq_default, level);
+}
+
+static void bcm2838_genet_set_irq_prio(BCM2838GenetState *s)
+{
+ uint32_t intrl_1_status = s->regs.intrl1.stat;
+ uint32_t intrl_1_mask = s->regs.intrl1.mask_status;
+ int level = (intrl_1_status & ~intrl_1_mask) == 0 ? 0 : 1;
+
+ qemu_set_irq(s->irq_prio, level);
+}
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t value = ~0;
+ BCM2838GenetState *s = opaque;
- qemu_log_mask(
- LOG_GUEST_ERROR,
- "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
- __func__, size, offset);
+ if (offset + size < sizeof(s->regs)) {
+ memcpy(&value, (uint8_t *)&s->regs + offset, size);
+ } else {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+ }
trace_bcm2838_genet_read(size, offset, value);
return value;
}
static void bcm2838_genet_write(void *opaque, hwaddr offset, uint64_t value,
- unsigned size) {
- qemu_log_mask(
- LOG_GUEST_ERROR,
- "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
- __func__, size, offset);
+ unsigned size)
+{
+ BCM2838GenetState *s = opaque;
+ MACAddr *mac = &s->nic_conf.macaddr;
+ NetClientState *ncs = qemu_get_queue(s->nic);
+
+ trace_bcm2838_genet_write(size, offset, value);
+
+ if (offset + size < sizeof(s->regs)) {
+ switch (offset) {
+ case BCM2838_GENET_INTRL0_SET:
+ s->regs.intrl0.stat |= value;
+ break;
+ case BCM2838_GENET_INTRL0_CLEAR:
+ s->regs.intrl0.stat &= ~value;
+ break;
+ case BCM2838_GENET_INTRL0_MASK_SET:
+ s->regs.intrl0.mask_status |= value;
+ break;
+ case BCM2838_GENET_INTRL0_MASK_CLEAR:
+ s->regs.intrl0.mask_status &= ~value;
+ break;
+ case BCM2838_GENET_INTRL1_SET:
+ s->regs.intrl1.stat |= value;
+ break;
+ case BCM2838_GENET_INTRL1_CLEAR:
+ s->regs.intrl1.stat &= ~value;
+ break;
+ case BCM2838_GENET_INTRL1_MASK_SET:
+ s->regs.intrl1.mask_status |= value;
+ break;
+ case BCM2838_GENET_INTRL1_MASK_CLEAR:
+ s->regs.intrl1.mask_status &= ~value;
+ break;
+ case BCM2838_GENET_UMAC_CMD:
+ /* Complete SW reset as soon as it has been requested */
+ if (FIELD_EX32(value, GENET_UMAC_CMD, SW_RESET) == 1) {
+ device_cold_reset(DEVICE(s));
+ value = FIELD_DP32(value, GENET_UMAC_CMD, SW_RESET, 0);
+ }
+ break;
+ /*
+ * TODO: before changing MAC address we'd better inform QEMU
+ * network subsystem about freeing previously used one, but
+ * qemu_macaddr_set_free function isn't accessible for us (marked
+ * as static in net/net.c), see also https://lists.nongnu.org/
+ * archive/html/qemu-devel/2022-07/msg02123.html
+ */
+ case BCM2838_GENET_UMAC_MAC0:
+ mac->a[0] = FIELD_EX32(value, GENET_UMAC_MAC_0, ADDR_0);
+ mac->a[1] = FIELD_EX32(value, GENET_UMAC_MAC_0, ADDR_1);
+ mac->a[2] = FIELD_EX32(value, GENET_UMAC_MAC_0, ADDR_2);
+ mac->a[3] = FIELD_EX32(value, GENET_UMAC_MAC_0, ADDR_3);
+ qemu_macaddr_default_if_unset(mac);
+ qemu_format_nic_info_str(ncs, mac->a);
+ trace_bcm2838_genet_mac_address(ncs->info_str);
+ break;
+ case BCM2838_GENET_UMAC_MAC1:
+ mac->a[4] = FIELD_EX32(value, GENET_UMAC_MAC_1, ADDR_4);
+ mac->a[5] = FIELD_EX32(value, GENET_UMAC_MAC_1, ADDR_5);
+ qemu_macaddr_default_if_unset(mac);
+ qemu_format_nic_info_str(ncs, mac->a);
+ trace_bcm2838_genet_mac_address(ncs->info_str);
+ break;
+ case BCM2838_GENET_RDMA_CTRL:
+ s->regs.rdma.status = (~value) & GENET_DMA_ENABLE_MASK;
+ break;
+ case BCM2838_GENET_UMAC_MDIO_CMD:
+ case BCM2838_GENET_TDMA_REGS
+ ... BCM2838_GENET_TDMA_REGS + sizeof(BCM2838GenetRegsTdma) - 1:
+ qemu_log_mask(LOG_UNIMP,
+ "UMAC MDIO and TDMA aren't implemented yet");
+ break;
+ default:
+ break;
+ }
+
+ memcpy((uint8_t *)&s->regs + offset, &value, size);
+ if (offset == BCM2838_GENET_RDMA_CTRL &&
+ FIELD_EX32(value, GENET_DMA_CTRL, EN)) {
+ qemu_flush_queued_packets(ncs);
+ }
+ bcm2838_genet_set_irq_default(s);
+ bcm2838_genet_set_irq_prio(s);
+ } else {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: out-of-range access, %u bytes @ offset 0x%04" PRIx64 "\n",
+ __func__, size, offset);
+ }
}
static const MemoryRegionOps bcm2838_genet_ops = {
@@ -265,9 +391,14 @@ static const MemoryRegionOps bcm2838_genet_ops = {
.valid = {.min_access_size = 4},
};
+static NetClientInfo bcm2838_genet_client_info = {
+ .type = NET_CLIENT_DRIVER_NIC,
+ .size = sizeof(NICState)
+};
static void bcm2838_genet_realize(DeviceState *dev, Error **errp)
{
+ NetClientState *ncs;
BCM2838GenetState *s = BCM2838_GENET(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
@@ -275,10 +406,46 @@ static void bcm2838_genet_realize(DeviceState *dev, Error **errp)
memory_region_init_io(&s->regs_mr, OBJECT(s), &bcm2838_genet_ops, s,
"bcm2838_genet_regs", sizeof(s->regs));
sysbus_init_mmio(sbd, &s->regs_mr);
+
+ /* QEMU-managed NIC (host network back-end connection) */
+ qemu_macaddr_default_if_unset(&s->nic_conf.macaddr);
+ s->nic = qemu_new_nic(&bcm2838_genet_client_info, &s->nic_conf,
+ object_get_typename(OBJECT(dev)), dev->id,
+ &dev->mem_reentrancy_guard, s);
+ bcm2838_genet_set_qemu_mac(s);
+ ncs = qemu_get_queue(s->nic);
+ qemu_format_nic_info_str(ncs, s->nic_conf.macaddr.a);
+ trace_bcm2838_genet_mac_address(ncs->info_str);
+
+ /* Interrupts */
+ sysbus_init_irq(sbd, &s->irq_default);
+ sysbus_init_irq(sbd, &s->irq_prio);
+
+ /* DMA space */
+ address_space_init(&s->dma_as, get_system_memory(), "bcm2838_genet_dma");
}
static void bcm2838_genet_phy_reset(BCM2838GenetState *s)
{
+ memset(&s->phy_regs, 0x00, sizeof(s->phy_regs));
+ memset(&s->phy_shd_regs, 0x00, sizeof(s->phy_shd_regs));
+ memset(&s->phy_aux_ctl_shd_regs, 0x00, sizeof(s->phy_aux_ctl_shd_regs));
+
+ /* All values below were taken from real HW trace and logs */
+ s->phy_regs.bmcr = 0x1140;
+ s->phy_regs.bmsr = 0x7949;
+ s->phy_regs.sid1 = 0x600D;
+ s->phy_regs.sid2 = 0x84A2;
+ s->phy_regs.advertise = 0x01E1;
+ s->phy_regs.ctrl1000 = 0x0200;
+ s->phy_regs.estatus = 0x3000;
+
+ s->phy_shd_regs.clk_ctl = 0x0200;
+ s->phy_shd_regs.scr3 = 0x001F;
+ s->phy_shd_regs.apd = 0x0001;
+
+ s->phy_aux_ctl_shd_regs.misc = 0x1E;
+
trace_bcm2838_genet_phy_reset("done");
}
@@ -288,11 +455,22 @@ static void bcm2838_genet_reset(Object *obj, ResetType type)
memset(&s->regs, 0x00, sizeof(s->regs));
+ s->regs.sys.rev_ctrl = FIELD_DP32(s->regs.sys.rev_ctrl, GENET_SYS_REV_CTRL,
+ MAJOR_REV, BCM2838_GENET_REV_MAJOR);
+ s->regs.sys.rev_ctrl = FIELD_DP32(s->regs.sys.rev_ctrl, GENET_SYS_REV_CTRL,
+ MINOR_REV, BCM2838_GENET_REV_MINOR);
+ s->regs.rdma.status = GENET_DMA_ENABLE_MASK;
+ s->regs.tdma.status = GENET_DMA_ENABLE_MASK;
+
trace_bcm2838_genet_reset("done");
bcm2838_genet_phy_reset(s);
}
+static const Property genet_properties[] = {
+ DEFINE_NIC_PROPERTIES(BCM2838GenetState, nic_conf),
+};
+
static void bcm2838_genet_class_init(ObjectClass *class, const void *data)
{
static ResettablePhases unused_parent_phases;
@@ -302,6 +480,7 @@ static void bcm2838_genet_class_init(ObjectClass *class, const void *data)
dc->realize = bcm2838_genet_realize;
resettable_class_set_parent_phases(rc, NULL, bcm2838_genet_reset, NULL,
&unused_parent_phases);
+ device_class_set_props(dc, genet_properties);
}
static const TypeInfo bcm2838_genet_info = {
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index b9895933b6..e3dee5a78f 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -390,6 +390,8 @@ struct BCM2838GenetState {
SysBusDevice parent_obj;
/*< public >*/
+ NICState *nic;
+ NICConf nic_conf;
MemoryRegion regs_mr;
AddressSpace dma_as;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 11/19] hw/net/bcm2838_genet: implement GENET MDIO
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (9 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 10/19] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 12/19] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
` (7 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
From: Sergey Kambalin <serg.oker@gmail.com>
Implement the MDIO command register handling used to read/write the
BCM54213PE PHY's standard MII registers (BMCR, BMSR, LPA, ...) plus
its vendor "shadow" register blocks reached through the AUX_CTL/
EXP_SHD indirection scheme, and a simple link-state model (PHY
starts down, comes up once BMCR is programmed and autonegotiation
is (re)started).
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;
see the following "Fix GENET PHY autonegotiation-restart deadlock"
patch for a real bug found and fixed in this code during testing.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 132 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 3 +-
2 files changed, 132 insertions(+), 3 deletions(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index f3c5ca26c5..6b3c9cb12b 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -216,6 +216,7 @@ FIELD(GENET_PHY_STAT_1000, LOCALRXOK, 13, 1)
FIELD(GENET_PHY_STAT_1000, MSRES, 14, 1)
FIELD(GENET_PHY_STAT_1000, MSFAIL, 15, 1)
+/* There are two data representations for PHY_AUX_CTRL register */
REG16(GENET_PHY_AUX_CTRL_0, 0)
FIELD(GENET_PHY_AUX_CTRL_0, REG_ID_MASK, 0, 3)
FIELD(GENET_PHY_AUX_CTRL_0, RSVD_3, 3, 1)
@@ -272,6 +273,129 @@ static void bcm2838_genet_set_irq_prio(BCM2838GenetState *s)
qemu_set_irq(s->irq_prio, level);
}
+static void bcm2838_genet_phy_aux_ctl_write(BCM2838GenetState *s,
+ uint16_t value)
+{
+ uint16_t reg_id = FIELD_EX16(value, GENET_PHY_AUX_CTRL_0, REG_ID);
+ uint16_t reg_id_mask = FIELD_EX16(value, GENET_PHY_AUX_CTRL_0, REG_ID_MASK);
+ uint16_t misc_wren = FIELD_EX16(value, GENET_PHY_AUX_CTRL_0, MISC_WREN);
+ uint16_t reg_data = FIELD_EX16(value, GENET_PHY_AUX_CTRL_0, REG_DATA);
+ uint16_t reg_data12 = FIELD_EX16(value, GENET_PHY_AUX_CTRL_1, REG_DATA);
+
+ uint16_t *phy_aux_ctl_shd_reg_id =
+ (uint16_t *)&s->phy_aux_ctl_shd_regs + reg_id;
+ uint16_t *phy_aux_ctl_shd_reg_id_mask =
+ (uint16_t *)&s->phy_aux_ctl_shd_regs + reg_id_mask;
+
+ if (reg_id_mask == BCM2838_GENET_PHY_AUX_CTL_MISC) {
+ if (reg_id == BCM2838_GENET_PHY_AUX_CTL_MISC) {
+ if (misc_wren == 0) {
+ /* write for subsequent read (8-bit from AUX_CTL_MISC) */
+ FIELD_DP16(value, GENET_PHY_AUX_CTRL_0, REG_DATA,
+ *phy_aux_ctl_shd_reg_id);
+ } else {
+ /* write 8 bits to AUX_CTL_MISC */
+ *phy_aux_ctl_shd_reg_id_mask = reg_data;
+ }
+ } else {
+ /* write for subsequent read (12-bit) */
+ FIELD_DP16(value, GENET_PHY_AUX_CTRL_1, REG_DATA,
+ *phy_aux_ctl_shd_reg_id);
+ }
+ } else {
+ /* write 12 bits */
+ *phy_aux_ctl_shd_reg_id_mask = reg_data12;
+ }
+
+ s->phy_regs.aux_ctl = value;
+}
+
+static void bcm2838_genet_phy_shadow_write(BCM2838GenetState *s,
+ uint16_t value)
+{
+ uint16_t reg_id = FIELD_EX16(value, GENET_PHY_SHADOW, REG_ID);
+ uint16_t wr = FIELD_EX16(value, GENET_PHY_SHADOW, WR);
+ uint16_t reg_data = FIELD_EX16(value, GENET_PHY_SHADOW, REG_DATA);
+
+ uint16_t *phy_shd_reg = (uint16_t *)&s->phy_shd_regs + reg_id;
+
+ if (wr == 0) {
+ FIELD_DP16(value, GENET_PHY_SHADOW, REG_DATA, *phy_shd_reg);
+ } else {
+ *phy_shd_reg = reg_data;
+ }
+
+ s->phy_regs.shd = value;
+}
+
+static void bcm2838_genet_phy_exp_shadow_write(BCM2838GenetState *s,
+ uint16_t value)
+{
+ /*
+ * TODO Stub implementation without side effect,
+ * just storing registers values
+ */
+ uint16_t reg_id = FIELD_EX16(s->phy_regs.exp_ctrl,
+ GENET_PHY_EXP_SEL, REG_ID);
+ uint16_t block_id = FIELD_EX16(s->phy_regs.exp_ctrl,
+ GENET_PHY_EXP_SEL, BLOCK_ID);
+
+ s->phy_exp_shd_regs.regs[block_id][reg_id] = value;
+}
+
+static uint16_t bcm2838_genet_phy_exp_shadow_read(BCM2838GenetState *s)
+{
+ uint16_t reg_id = FIELD_EX16(s->phy_regs.exp_ctrl,
+ GENET_PHY_EXP_SEL, REG_ID);
+ uint16_t block_id = FIELD_EX16(s->phy_regs.exp_ctrl,
+ GENET_PHY_EXP_SEL, BLOCK_ID);
+
+ return s->phy_exp_shd_regs.regs[block_id][reg_id];
+}
+
+static uint64_t bcm2838_genet_mdio_cmd(BCM2838GenetState *s, uint64_t cmd)
+{
+ uint32_t phy_reg_id = FIELD_EX32(cmd, GENET_UMAC_MDIO_CMD, REG_ID);
+ uint32_t phy_reg_data = FIELD_EX32(cmd, GENET_UMAC_MDIO_CMD, REG_DATA);
+ uint32_t start_busy = FIELD_EX32(cmd, GENET_UMAC_MDIO_CMD, START_BUSY);
+ uint32_t rd = FIELD_EX32(cmd, GENET_UMAC_MDIO_CMD, RD);
+ uint32_t wr = FIELD_EX32(cmd, GENET_UMAC_MDIO_CMD, WR);
+ uint16_t *phy_reg = (uint16_t *)&s->phy_regs + phy_reg_id;
+
+ uint16_t anrestart = FIELD_EX16(phy_reg_data, GENET_PHY_BMCR, ANRESTART);
+
+ if (start_busy != 0) {
+ cmd = FIELD_DP32(cmd, GENET_UMAC_MDIO_CMD, START_BUSY, 0);
+
+ if (rd != 0) {
+ if (phy_reg_id == BCM2838_GENET_EXP_DATA) {
+ cmd = FIELD_DP32(cmd, GENET_UMAC_MDIO_CMD, REG_DATA,
+ bcm2838_genet_phy_exp_shadow_read(s));
+ } else {
+ cmd = FIELD_DP32(cmd, GENET_UMAC_MDIO_CMD, REG_DATA, *phy_reg);
+ }
+ } else if (wr != 0) {
+ if (phy_reg_id == BCM2838_GENET_PHY_AUX_CTL) {
+ bcm2838_genet_phy_aux_ctl_write(s, phy_reg_data);
+ } else if (phy_reg_id == BCM2838_GENET_PHY_SHD) {
+ bcm2838_genet_phy_shadow_write(s, phy_reg_data);
+ } else if (phy_reg_id == BCM2838_GENET_EXP_DATA) {
+ bcm2838_genet_phy_exp_shadow_write(s, phy_reg_data);
+ } else {
+ if (phy_reg_id == BCM2838_GENET_PHY_BMCR) {
+ /* Initiate auto-negotiation once it has been restarted */
+ if (anrestart == 1) {
+ FIELD_DP16(phy_reg_data, GENET_PHY_BMCR, ANRESTART, 0);
+ }
+ }
+ *phy_reg = phy_reg_data;
+ }
+ }
+ }
+
+ return cmd;
+}
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t value = ~0;
@@ -359,10 +483,13 @@ static void bcm2838_genet_write(void *opaque, hwaddr offset, uint64_t value,
s->regs.rdma.status = (~value) & GENET_DMA_ENABLE_MASK;
break;
case BCM2838_GENET_UMAC_MDIO_CMD:
+ value = bcm2838_genet_mdio_cmd(s, value);
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat,
+ GENET_INTRL_0, MDIO_DONE, 1);
+ break;
case BCM2838_GENET_TDMA_REGS
... BCM2838_GENET_TDMA_REGS + sizeof(BCM2838GenetRegsTdma) - 1:
- qemu_log_mask(LOG_UNIMP,
- "UMAC MDIO and TDMA aren't implemented yet");
+ qemu_log_mask(LOG_UNIMP, "TDMA isn't implemented yet");
break;
default:
break;
@@ -464,6 +591,7 @@ static void bcm2838_genet_reset(Object *obj, ResetType type)
trace_bcm2838_genet_reset("done");
+ bcm2838_genet_set_qemu_mac(s);
bcm2838_genet_phy_reset(s);
}
diff --git a/include/hw/net/bcm2838_genet.h b/include/hw/net/bcm2838_genet.h
index e3dee5a78f..b34c4b3ce7 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -103,7 +103,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(BCM2838GenetState, BCM2838_GENET)
#define BCM2838_GENET_EXP_DATA BCM2838_GENET_PHY_REG(exp_data)
#define BCM2838_GENET_EXP_SEL BCM2838_GENET_PHY_REG(exp_ctrl)
-#define BCM2838_GENET_PHY_AUX_CTL_MISC 0x7
+#define BCM2838_GENET_PHY_AUX_CTL_AUXCTL 0x0
+#define BCM2838_GENET_PHY_AUX_CTL_MISC 0x7
#define BCM2838_GENET_PHY_AUX_CTL_REGS_SIZE 8
#define BCM2838_GENET_PHY_EXP_SHD_BLOCKS_CNT 256
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 12/19] hw/net/bcm2838_genet: implement GENET TX path
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (10 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 11/19] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 13/19] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
` (6 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
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;
see the later "Fix TX ring activation check" patch for a real bug
found and fixed in the ring-activity check this path depends on.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 219 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 17 +++
2 files changed, 235 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 6b3c9cb12b..1b02876094 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;
@@ -396,6 +403,216 @@ static uint64_t bcm2838_genet_mdio_cmd(BCM2838GenetState *s, uint64_t cmd)
return cmd;
}
+static void bcm2838_genet_xmit_packet(NetClientState *s, void *packet,
+ size_t size)
+{
+ uint8_t *buf = packet + sizeof(BCM2838GenetXmitStatus);
+ size_t len = size;
+ uint16_t len_type = 0;
+
+ len -= sizeof(BCM2838GenetXmitStatus);
+ net_checksum_calculate(buf, len, CSUM_ALL);
+
+ memcpy(&len_type, &buf[12], sizeof(len_type));
+ len_type = ntohs(len_type);
+ if (len_type < MAX_PAYLOAD_SIZE) {
+ len_type = len;
+ len_type = htons(len_type);
+ memcpy(&buf[12], &len_type, sizeof(len_type));
+ }
+
+ 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)
+{
+ const unsigned int DESC_SIZE_WORDS
+ = sizeof(BCM2838GenetTdmaDesc) / sizeof(uint32_t);
+ const uint64_t RING_START_ADDR
+ = ((uint64_t)s->regs.tdma.rings[ring_index].start_addr_hi << 32)
+ + s->regs.tdma.rings[ring_index].start_addr;
+ const uint64_t RING_END_ADDR
+ = ((uint64_t)s->regs.tdma.rings[ring_index].end_addr_hi << 32)
+ + s->regs.tdma.rings[ring_index].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
+ = ((uint64_t)s->regs.tdma.rings[ring_index].read_ptr_hi << 32)
+ + s->regs.tdma.rings[ring_index].read_ptr;
+ off_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 = ((uint64_t)s->regs.tdma.descs[desc_index].address_hi << 32)
+ + 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);
+
+ /* TODO: Add address_space_read() return value check */
+ address_space_read(&s->dma_as, data_addr,
+ MEMTXATTRS_UNSPECIFIED,
+ s->tx_packet + packet_off,
+ buflength);
+ 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;
+ }
+
+ s->regs.tdma.rings[ring_index].read_ptr = read_ptr;
+ s->regs.tdma.rings[ring_index].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) != 0;
+ bool ring_en =
+ (FIELD_EX32(ring_cfg_reg, GENET_DMA_CTRL, EN) & ring_mask) != 0;
+ bool ring_buf_en =
+ (FIELD_EX32(ctrl_reg, GENET_DMA_CTRL, RING_BUF_EN) & ring_mask) != 0;
+ bool active = dma_en && ring_en && ring_buf_en;
+
+ trace_bcm2838_genet_tx_dma_ring_active(ring_index,
+ active ? "active" : "halted");
+ return active;
+}
+
+static void bcm2838_genet_tdma(BCM2838GenetState *s, hwaddr offset,
+ uint64_t value)
+{
+ hwaddr ring_offset;
+ uint64_t num_descs_tx;
+ unsigned int ring_index;
+ uint32_t tx_intrs;
+ uint32_t cons_index;
+ uint32_t prod_index = value;
+ uint32_t ring_cfg = value;
+ uint32_t dma_ctrl = value;
+
+ uint32_t cons_index_fld;
+ uint32_t prod_index_fld =
+ FIELD_EX32(prod_index, GENET_DMA_PROD_INDEX, INDEX);
+
+ 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:
+ cons_index = s->regs.tdma.rings[ring_index].cons_index;
+ cons_index_fld = FIELD_EX32(cons_index,
+ GENET_DMA_CONS_INDEX, INDEX);
+ if (cons_index_fld != prod_index_fld) {
+ 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) {
+ s->regs.tdma.rings[ring_index].cons_index =
+ FIELD_DP32(s->regs.tdma.rings[ring_index].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);
+ }
+ }
+ }
+ 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;
@@ -489,7 +706,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 b34c4b3ce7..bad582fe0d 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;
@@ -405,6 +420,8 @@ struct BCM2838GenetState {
qemu_irq irq_default;
qemu_irq irq_prio;
+
+ uint8_t tx_packet[MAX_FRAME_SIZE];
};
#endif /* BCM2838_GENET_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 13/19] hw/net/bcm2838_genet: implement GENET RX path
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (11 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 12/19] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 14/19] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
` (5 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, Philippe Mathieu-Daudé,
Jason Wang
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;
see the later "Fix bogus RX checksum reporting" patch for a real bug
found and fixed in this code during testing.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 274 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 1 +
2 files changed, 274 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 1b02876094..746e416e53 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -520,6 +520,25 @@ static bool bcm2838_genet_tdma_ring_active(BCM2838GenetState *s,
return active;
}
+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) != 0;
+ bool ring_en = (FIELD_EX32(s->regs.rdma.ring_cfg, GENET_DMA_RING_CFG, EN)
+ & ring_mask) != 0;
+ bool ring_buf_en = (FIELD_EX32(s->regs.rdma.ctrl,
+ GENET_DMA_CTRL, RING_BUF_EN)
+ & ring_mask) != 0;
+ 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)
{
@@ -735,9 +754,260 @@ static const MemoryRegionOps bcm2838_genet_ops = {
.valid = {.min_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 =
+ (ring->write_ptr + ((hwaddr)ring->write_ptr_hi << 32)) / DESC_WORD_SIZE;
+ BCM2838GenetRdmaDesc *desc = &rdma->descs[write_index];
+
+ const hwaddr START_INDEX =
+ (ring->start_addr + ((hwaddr)ring->start_addr_hi << 32))
+ / DESC_WORD_SIZE;
+ const hwaddr END_INDEX =
+ (ring->end_addr + ((hwaddr)ring->end_addr_hi << 32)) / DESC_WORD_SIZE;
+
+ if (!bcm2838_genet_rdma_ring_active(s, ring_idx)) {
+ return -1;
+ }
+
+ 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 = ring->ring_buf_size & 0xffff;
+ uint8_t *dma_buf = s->rx_packet;
+ hwaddr dma_buf_addr =
+ desc->address_lo + ((hwaddr)desc->address_hi << 32);
+ MemTxResult mem_tx_result = MEMTX_OK;
+ uint8_t *frame_buf = dma_buf + sizeof(BCM2838GenetXmitStatus) + 2;
+ BCM2838GenetXmitStatus *xmit_status = (BCM2838GenetXmitStatus *)dma_buf;
+ struct iovec iov;
+ bool isip4, isip6;
+ size_t l3hdr_off, l4hdr_off, l5hdr_off;
+ eth_ip6_hdr_info ip6hdr_info;
+ eth_ip4_hdr_info ip4hdr_info;
+ eth_l4_hdr_info l4hdr_info;
+
+ 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);
+ iov.iov_base = frame_buf;
+ iov.iov_len = l;
+ eth_get_protocols(&iov, 1, 0,
+ &isip4, &isip6,
+ &l3hdr_off, &l4hdr_off, &l5hdr_off,
+ &ip6hdr_info, &ip4hdr_info, &l4hdr_info);
+
+ 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));
+
+ xmit_status->rx_csum = 0;
+ if (isip4) {
+ xmit_status->rx_csum = ip4hdr_info.ip4_hdr.ip_sum;
+ }
+ xmit_status->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;
+ }
+ 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 != 0;
+
+ bool lstatus = FIELD_EX32(s->phy_regs.bmsr, GENET_PHY_BMSR, LSTATUS) != 0;
+
+ 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)
@@ -791,6 +1061,8 @@ static void bcm2838_genet_phy_reset(BCM2838GenetState *s)
s->phy_aux_ctl_shd_regs.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 bad582fe0d..960042f6f4 100644
--- a/include/hw/net/bcm2838_genet.h
+++ b/include/hw/net/bcm2838_genet.h
@@ -422,6 +422,7 @@ struct BCM2838GenetState {
qemu_irq irq_prio;
uint8_t tx_packet[MAX_FRAME_SIZE];
+ uint8_t rx_packet[MAX_FRAME_SIZE];
};
#endif /* BCM2838_GENET_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 14/19] hw/arm/bcm2838: enable BCM2838 GENET controller
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (12 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 13/19] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 15/19] hw/net/bcm2838_genet: fix PHY autonegotiation-restart deadlock Marcelo Manzo
` (4 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé
Wire the BCM2838 GENET device (added in the previous patches) into
the SoC: instantiate it in the peripherals block with
qemu_configure_nic_device() so it picks up a -nic/-netdev from the
command line, map its register region at the real BCM2711 physical
offset, and route its two interrupt lines through the GIC. Also stop
raspi4b from masking out the "brcm,bcm2711-genet-v5" device-tree
node, now that the controller it describes is actually implemented.
Adapted from patches 19-29/41 of Sergey Kambalin's Raspberry Pi 4B
series (patchwork series 829638) against current QEMU master.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838.c | 6 ++++++
hw/arm/bcm2838_peripherals.c | 11 +++++++++++
hw/arm/raspi4b.c | 1 -
include/hw/arm/bcm2838_peripherals.h | 2 ++
4 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/hw/arm/bcm2838.c b/hw/arm/bcm2838.c
index fc56f87934..f47b96c3bf 100644
--- a/hw/arm/bcm2838.c
+++ b/hw/arm/bcm2838.c
@@ -239,6 +239,12 @@ static void bcm2838_realize(DeviceState *dev, Error **errp)
int_n);
}
+ /* Connect Gigabit Ethernet controller to the interrupt controller */
+ sysbus_connect_irq(SYS_BUS_DEVICE(&ps->genet), 0,
+ qdev_get_gpio_in(gicdev, GIC_SPI_INTERRUPT_GENET_A));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&ps->genet), 1,
+ qdev_get_gpio_in(gicdev, GIC_SPI_INTERRUPT_GENET_B));
+
/* Pass through inbound GPIO lines to the GIC */
qdev_init_gpio_in(dev, bcm2838_gic_set_irq, GIC_NUM_IRQS);
diff --git a/hw/arm/bcm2838_peripherals.c b/hw/arm/bcm2838_peripherals.c
index d923ba968a..012a4567e7 100644
--- a/hw/arm/bcm2838_peripherals.c
+++ b/hw/arm/bcm2838_peripherals.c
@@ -44,6 +44,10 @@ static void bcm2838_peripherals_init(Object *obj)
object_initialize_child(obj, "pcie-host", &s->pcie_host,
TYPE_BCM2838_PCIE_HOST);
+ /* Gigabit Ethernet */
+ object_initialize_child(obj, "genet", &s->genet, TYPE_BCM2838_GENET);
+ qemu_configure_nic_device(DEVICE(&s->genet), true, NULL);
+
/* GPIO */
object_initialize_child(obj, "gpio", &s->gpio, TYPE_BCM2838_GPIO);
@@ -217,6 +221,13 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion(get_system_memory(), PCIE_MMIO_ARM_OFFSET,
&s->pcie_mmio_alias);
+ /* Gigabit Ethernet */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->genet), errp)) {
+ return;
+ }
+ regs_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->genet), 0);
+ memory_region_add_subregion(&s->peri_low_mr, GENET_OFFSET, regs_mr);
+
/* GPIO */
if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio), errp)) {
return;
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c
index 038eefb234..79f60932f7 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -66,7 +66,6 @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt)
const char *nodes_to_remove[] = {
"brcm,bcm2711-rng200",
"brcm,bcm2711-thermal",
- "brcm,bcm2711-genet-v5",
};
for (int i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) {
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 2a4307bed7..deddab50d2 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -11,6 +11,7 @@
#include "hw/arm/bcm2835_peripherals.h"
#include "hw/arm/bcm2838_pcie.h"
+#include "hw/net/bcm2838_genet.h"
#include "hw/sd/sdhci.h"
#include "hw/gpio/bcm2838_gpio.h"
@@ -70,6 +71,7 @@ struct BCM2838PeripheralState {
SDHCIState emmc2;
MemoryRegion pcie_mmio_alias;
BCM2838PcieHostState pcie_host;
+ BCM2838GenetState genet;
BCM2838GpioState gpio;
OrIRQState mmc_irq_orgate;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 15/19] hw/net/bcm2838_genet: fix PHY autonegotiation-restart deadlock
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (13 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 14/19] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 16/19] hw/net/bcm2838_genet: fix bogus RX checksum reporting Marcelo Manzo
` (3 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé, Jason Wang
The guest PHY driver requests an autonegotiation restart by setting
BMCR.ANRESTART, then polls BMCR waiting for the hardware to clear
that bit as an acknowledgment. bcm2838_genet_mdio_cmd() called
FIELD_DP16() to clear ANRESTART in phy_reg_data but discarded its
return value -- FIELD_DP16() does not modify its argument in place,
it returns a new value -- so the bit was never actually cleared in
the register the guest reads back. The guest driver then spun
forever waiting for an acknowledgment that would never come,
appearing to hang the whole boot.
Assign the FIELD_DP16() result back to phy_reg_data, and also call
bcm2838_genet_phy_update_link() once the restart is handled so link
status actually gets refreshed, matching the surrounding code's own
comment ("Initiate auto-negotiation once it has been restarted").
Found by booting a real guest against this series and tracing the
MDIO command register with targeted debug prints: the guest was
observed polling BMCR indefinitely, always reading back the
ANRESTART bit still set. Confirmed fixed across repeated boots: link
comes up and autonegotiation completes every time.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 746e416e53..dfdc5ac728 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)
@@ -392,10 +394,15 @@ static uint64_t bcm2838_genet_mdio_cmd(BCM2838GenetState *s, uint64_t cmd)
if (phy_reg_id == BCM2838_GENET_PHY_BMCR) {
/* Initiate auto-negotiation once it has been restarted */
if (anrestart == 1) {
- FIELD_DP16(phy_reg_data, GENET_PHY_BMCR, ANRESTART, 0);
+ phy_reg_data = FIELD_DP16(phy_reg_data,
+ GENET_PHY_BMCR,
+ ANRESTART, 0);
}
}
*phy_reg = phy_reg_data;
+ if (phy_reg_id == BCM2838_GENET_PHY_BMCR && anrestart == 1) {
+ bcm2838_genet_phy_update_link(s);
+ }
}
}
}
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 16/19] hw/net/bcm2838_genet: fix bogus RX checksum reporting
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (14 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 15/19] hw/net/bcm2838_genet: fix PHY autonegotiation-restart deadlock Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 17/19] hw/net/bcm2838_genet: fix TX ring activation check Marcelo Manzo
` (2 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé, Jason Wang
bcm2838_genet_rdma() reported the received frame's IPv4 header
checksum (in host byte order) as the descriptor's rx_csum value.
The guest driver's CHECKSUM_COMPLETE path expects a running checksum
over the L4 payload there, not the L3 header checksum, so the
mismatch made the guest reject otherwise-valid inbound packets
("hw csum failure" in dmesg, from bcmgenet_rx_poll()), breaking
DHCP and general connectivity intermittently.
Report no hardware-computed checksum instead (rx_csum = 0), which
makes the guest fall back to verifying checksums in software; this
succeeds since the packet data itself was never corrupted. Drop the
eth_get_protocols() call and its output variables along with it,
since isip4/ip4hdr_info were only ever used to compute the checksum
value being removed here.
Confirmed with the guest booted repeatedly: "hw csum failure" no
longer appears in dmesg, and ping to the gateway succeeds with 0%
loss.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index dfdc5ac728..ccf3c2fef0 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -828,12 +828,6 @@ static ssize_t bcm2838_genet_rdma(BCM2838GenetState *s, uint32_t ring_idx,
MemTxResult mem_tx_result = MEMTX_OK;
uint8_t *frame_buf = dma_buf + sizeof(BCM2838GenetXmitStatus) + 2;
BCM2838GenetXmitStatus *xmit_status = (BCM2838GenetXmitStatus *)dma_buf;
- struct iovec iov;
- bool isip4, isip6;
- size_t l3hdr_off, l4hdr_off, l5hdr_off;
- eth_ip6_hdr_info ip6hdr_info;
- eth_ip4_hdr_info ip4hdr_info;
- eth_l4_hdr_info l4hdr_info;
bool crc_fwd = FIELD_EX32(s->regs.umac.cmd, GENET_UMAC_CMD, CRC_FWD);
size_t buflength;
@@ -844,12 +838,6 @@ static ssize_t bcm2838_genet_rdma(BCM2838GenetState *s, uint32_t ring_idx,
}
memcpy(frame_buf, buf + len, l);
- iov.iov_base = frame_buf;
- iov.iov_len = l;
- eth_get_protocols(&iov, 1, 0,
- &isip4, &isip6,
- &l3hdr_off, &l4hdr_off, &l5hdr_off,
- &ip6hdr_info, &ip4hdr_info, &l4hdr_info);
len += l;
@@ -875,10 +863,19 @@ static ssize_t bcm2838_genet_rdma(BCM2838GenetState *s, uint32_t ring_idx,
MULTICAST,
!!is_packet_multicast(frame_buf, l));
+ /*
+ * Report no hardware-computed checksum. The IP header's own
+ * checksum was previously (incorrectly) stored here, but the
+ * guest's CHECKSUM_COMPLETE path expects a running checksum over
+ * the L4 payload, not the L3 header checksum -- and it was stored
+ * in host byte order besides. That mismatch was causing the guest
+ * to reject otherwise-valid inbound packets ("hw csum failure" in
+ * dmesg, tied to bcmgenet_rx_poll), breaking DHCP/connectivity
+ * intermittently. Leaving this at 0 makes the guest fall back to
+ * verifying checksums itself in software, which succeeds since the
+ * packet data itself is intact.
+ */
xmit_status->rx_csum = 0;
- if (isip4) {
- xmit_status->rx_csum = ip4hdr_info.ip4_hdr.ip_sum;
- }
xmit_status->length_status = desc->length_status;
mem_tx_result = address_space_write(&s->dma_as, dma_buf_addr,
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 17/19] hw/net/bcm2838_genet: fix TX ring activation check
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (15 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 16/19] hw/net/bcm2838_genet: fix bogus RX checksum reporting Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 18/19] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 19/19] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé, Jason Wang
bcm2838_genet_tdma_ring_active() extracted the per-ring enable bit
from ring_cfg_reg using the GENET_DMA_CTRL register's EN field
definition (1 bit wide, at bit 0) instead of GENET_DMA_RING_CFG's
own EN field (17 bits wide, one bit per ring). Since a 1-bit
extraction at bit 0 only ever reflects ring 0's enable bit, every
other ring (1-15, and the default ring 16) was silently treated as
inactive regardless of whether the driver had actually enabled it.
TX traffic hashed onto those queues was dropped with no error,
eventually tripping the guest's qdisc watchdog ("NETDEV WATCHDOG:
transmit queue N timed out") and causing intermittent DHCP failure /
link-local address fallback.
bcm2838_genet_rdma_ring_active(), the RX equivalent, already used
the correct field, confirming this was a copy-paste error isolated
to the TX path.
Confirmed fixed across repeated boots: real DHCP lease every time,
0% ping loss, no more checksum or watchdog messages in dmesg.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index ccf3c2fef0..bb967ad845 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -517,7 +517,7 @@ static bool bcm2838_genet_tdma_ring_active(BCM2838GenetState *s,
uint32_t ring_mask = 1 << ring_index;
bool dma_en = FIELD_EX32(ctrl_reg, GENET_DMA_CTRL, EN) != 0;
bool ring_en =
- (FIELD_EX32(ring_cfg_reg, GENET_DMA_CTRL, EN) & ring_mask) != 0;
+ (FIELD_EX32(ring_cfg_reg, GENET_DMA_RING_CFG, EN) & ring_mask) != 0;
bool ring_buf_en =
(FIELD_EX32(ctrl_reg, GENET_DMA_CTRL, RING_BUF_EN) & ring_mask) != 0;
bool active = dma_en && ring_en && ring_buf_en;
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 18/19] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (16 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 17/19] hw/net/bcm2838_genet: fix TX ring activation check Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
2026-08-11 14:35 ` [PATCH v2 19/19] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé, Pierrick Bouvier
raspi4b now has a working PCIe Root Complex and GENET Ethernet
controller (added in the preceding patches); move them out of the
"Missing devices" list and into "Implemented devices".
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
docs/system/arm/raspi.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/system/arm/raspi.rst b/docs/system/arm/raspi.rst
index 44eec3f1c3..a8fc37e920 100644
--- a/docs/system/arm/raspi.rst
+++ b/docs/system/arm/raspi.rst
@@ -36,10 +36,12 @@ Implemented devices
* VideoCore firmware (property)
* Peripheral SPI controller (SPI)
* Broadcom Serial Controller (I2C)
+ * PCIe Root Complex (raspi4b)
+ * GENET Ethernet Controller (raspi4b)
Missing devices
---------------
* Pulse Width Modulation (PWM)
- * PCIE Root Port (raspi4b)
- * GENET Ethernet Controller (raspi4b)
+ * PCIe MSI/MSI-X interrupt delivery (raspi4b) -- INTx works; a PCIe
+ device that only signals via MSI/MSI-X needs ``msi=off,msix=off``
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 19/19] tests/functional/aarch64: add raspi4b GENET networking test
2026-08-11 14:35 [PATCH v2 00/19] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (17 preceding siblings ...)
2026-08-11 14:35 ` [PATCH v2 18/19] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
@ 2026-08-11 14:35 ` Marcelo Manzo
18 siblings, 0 replies; 20+ messages in thread
From: Marcelo Manzo @ 2026-08-11 14:35 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
Philippe Mathieu-Daudé
Add test_arm_raspi4_genet, exercising the raspi4b GENET controller
end to end: boot with a -nic bcm2838-genet device attached, confirm
the guest driver probes it ("GENET 5.0 EPHY" in the kernel log),
reach a shell, and confirm the link actually comes up
("ip link show eth0" reports LOWER_UP) rather than just that the
device exists.
Verified locally against this series with the exact kernel/initrd
assets this file already pins (raspberrypi-kernel_1.20230106-1 /
groeck linux-build-test rootfs): without the "fix PHY
autonegotiation-restart deadlock" patch earlier in this series, the
link never comes up and this test would hang at the "ip link show
eth0" step, so it also serves as a regression test for that fix.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
tests/functional/aarch64/test_raspi4.py | 30 +++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tests/functional/aarch64/test_raspi4.py b/tests/functional/aarch64/test_raspi4.py
index 7a4302b0c5..0e254a2034 100755
--- a/tests/functional/aarch64/test_raspi4.py
+++ b/tests/functional/aarch64/test_raspi4.py
@@ -92,5 +92,35 @@ def test_arm_raspi4_initrd(self):
#self.vm.wait()
+ def test_arm_raspi4_genet(self):
+ kernel_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+ member='boot/kernel8.img')
+ dtb_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+ member='boot/bcm2711-rpi-4-b.dtb')
+ initrd_path = self.uncompress(self.ASSET_INITRD)
+
+ self.set_machine('raspi4b')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ 'earlycon=pl011,mmio32,0xfe201000 ' +
+ 'console=ttyAMA0,115200 ' +
+ 'panic=-1 noreboot ' +
+ 'dwc_otg.fiq_fsm_enable=0')
+ self.vm.add_args('-kernel', kernel_path,
+ '-dtb', dtb_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-no-reboot',
+ '-nic', 'user,model=bcm2838-genet')
+ self.vm.launch()
+ self.wait_for_console_pattern(
+ 'bcmgenet fd580000.ethernet: GENET 5.0 EPHY')
+ self.wait_for_console_pattern('Boot successful.')
+
+ exec_command_and_wait_for_pattern(self, 'ip link show eth0',
+ 'LOWER_UP')
+ exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
+
+
if __name__ == '__main__':
LinuxKernelTest.main()
--
2.47.1
^ permalink raw reply related [flat|nested] 20+ messages in thread