* [PATCH v3 01/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:54 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
` (14 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo
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 bcc068de04..f3c1b0e00f 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 01/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex
2026-10-15 18:11 ` [PATCH v3 01/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
@ 2026-10-17 12:54 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:54 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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
> +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.
> + */
This comment is supposed to be next to the line that
sets dc->user_creatable = false, because it is the rationale
for why we do that, but it's got separated from it.
> +
> + 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;
> +}
> +struct BCM2838PcieRootState {
> + /*< private >*/
> + PCIESlot parent_obj;
> +
> + /*< public >*/
> + uint8_t regs[BCM2838_PCIE_REGS_SIZE - PCIE_CONFIG_SPACE_SIZE];
> +};
If this is register state doesn't it need to be in a vmstate
struct, or does it get migrated some other way by the PCI core code ?
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 01/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 13:07 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
` (13 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo
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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host
2026-10-15 18:11 ` [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
@ 2026-10-17 13:07 ` Peter Maydell
2026-10-17 14:36 ` Marcelo Manzo
0 siblings, 1 reply; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 13:07 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé, Eric Auger
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> +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);
I think these read and write functions have endianness issues.
In a read/write function the values are host-order, but we are
updating the regs[] array by simple memcpy(), which means that
on a big-endian host the guest will see the wrong thing if it
writes as 32-bits and then reads back as 8-bits.
You probably want to define that the regs[] array is bytes
to be treated as little-endian order, and then use
ldn_le_p()/stn_le_p() to write an arbitrary sized value into
it, or lduw_le_p()/stw_le_p() for fixed-sized accesses, etc.
(pci.h wraps the latter into pci_{set,get}_{word,long,...}
accessors for this kind of "access config space" purpose, but
has no equivalent wrapper of the ldn/stn fns for "N byte access".)
> +
> + 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 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;
> +}
It's not clear to me if we need this -- most PCI controllers don't
seem to call pci_bus_set_route_irq_fn(). I have a feeling this is
only relevant for PCI passthrough. Eric, do you know ?
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread* Re: Re: [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host
2026-10-17 13:07 ` Peter Maydell
@ 2026-10-17 14:36 ` Marcelo Manzo
0 siblings, 0 replies; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-17 14:36 UTC (permalink / raw)
To: peter.maydell; +Cc: qemu-devel, qemu-arm, philmd, eric.auger
[-- Attachment #1: Type: text/plain, Size: 6741 bytes --]
On 17 Oct 2026, Peter Maydell wrote:
>
> I think these read and write functions have endianness issues.
> [...]
> You probably want to define that the regs[] array is bytes
> to be treated as little-endian order, and then use
> ldn_le_p()/stn_le_p() to write an arbitrary sized value into
> it
Agreed, thanks -- I'll do that in v4 (regs[] as little-endian bytes,
accessed with ldn_le_p()/stn_le_p(), and ldl_le_p() for the EXT_CFG_INDEX
value instead of the uint32_t * overlay).
> It's not clear to me if we need this -- most PCI controllers don't
> seem to call pci_bus_set_route_irq_fn(). I have a feeling this is
> only relevant for PCI passthrough. Eric, do you know ?
I had a look, and I think you're right. The only users of
pci_device_route_intx_to_irq() are VFIO, the x86 PIIX/Xen code and the
multi-process proxy. VFIO only calls it when KVM is active with irqfds
and resamplefds enabled; there is a comment in vfio_intx_enable() saying
it is conditional "to avoid generating error messages on platforms where
we won't actually use the result anyway".
raspi4b runs under TCG only, so none of those can apply. I copied this
from the original series (and gpex, which has it for VFIO under KVM)
without needing it.
I also tried it: with the hook, the irq_num[] storage and
bcm2838_pcie_host_set_irq_num() removed, a qemu-xhci with a usb-kbd on
the root port still enumerates and takes its INTx interrupts exactly as
before. So I'll drop it in v4.
Eric, shout if you know of a reason to keep it.
thanks
-- Marcelo
On Sat, Oct 17, 2026 09:07 AM, Peter Maydell <peter.maydell@linaro.org>
wrote:
> On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com>
> wrote:
> >
> > 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>
>
> > +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);
>
> I think these read and write functions have endianness issues.
> In a read/write function the values are host-order, but we are
> updating the regs[] array by simple memcpy(), which means that
> on a big-endian host the guest will see the wrong thing if it
> writes as 32-bits and then reads back as 8-bits.
>
> You probably want to define that the regs[] array is bytes
> to be treated as little-endian order, and then use
> ldn_le_p()/stn_le_p() to write an arbitrary sized value into
> it, or lduw_le_p()/stw_le_p() for fixed-sized accesses, etc.
> (pci.h wraps the latter into pci_{set,get}_{word,long,...}
> accessors for this kind of "access config space" purpose, but
> has no equivalent wrapper of the ldn/stn fns for "N byte access".)
>
> > +
> > + 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 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;
> > +}
>
> It's not clear to me if we need this -- most PCI controllers don't
> seem to call pci_bus_set_route_irq_fn(). I have a feeling this is
> only relevant for PCI passthrough. Eric, do you know ?
>
> thanks
> -- PMM
>
[-- Attachment #2: Type: text/html, Size: 8337 bytes --]
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 01/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe Root Complex Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 02/16] hw/arm/bcm2838_pcie: add BCM2838 PCIe host Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:51 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
` (12 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo
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 566d64572d..931f2cca67 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -63,7 +63,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge
2026-10-15 18:11 ` [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
@ 2026-10-17 12:51 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:51 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> ---
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (2 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:42 ` Peter Maydell
2026-10-17 14:33 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 05/16] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
` (11 subsequent siblings)
15 siblings, 2 replies; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
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 63b0e96245..ef85b85adf 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -70,4 +70,6 @@ if have_hmp
system_ss.add(files('rocker/rocker-hmp-cmds.c'))
endif
+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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device
2026-10-15 18:11 ` [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
@ 2026-10-17 12:42 ` Peter Maydell
2026-10-17 14:33 ` Peter Maydell
1 sibling, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:42 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device
2026-10-15 18:11 ` [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
2026-10-17 12:42 ` Peter Maydell
@ 2026-10-17 14:33 ` Peter Maydell
1 sibling, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 14:33 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> --
> +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},
We should set both min and max to 4 on impl and valid here;
I think it's clearer and less prone to accidental misreading.
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 05/16] hw/net/bcm2838_genet: add GENET register structs, part 1/4
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (3 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:41 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
` (10 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 05/16] hw/net/bcm2838_genet: add GENET register structs, part 1/4
2026-10-15 18:11 ` [PATCH v3 05/16] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
@ 2026-10-17 12:41 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:41 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> ---
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (4 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 05/16] hw/net/bcm2838_genet: add GENET register structs, part 1/4 Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:41 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 07/16] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
` (9 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4
2026-10-15 18:11 ` [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
@ 2026-10-17 12:41 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:41 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> ---
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 07/16] hw/net/bcm2838_genet: add GENET register structs, part 3/4
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (5 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:41 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
` (8 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 07/16] hw/net/bcm2838_genet: add GENET register structs, part 3/4
2026-10-15 18:11 ` [PATCH v3 07/16] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
@ 2026-10-17 12:41 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:41 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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(+)
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (6 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 07/16] hw/net/bcm2838_genet: add GENET register structs, part 3/4 Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:41 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 09/16] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
` (7 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4
2026-10-15 18:11 ` [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
@ 2026-10-17 12:41 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:41 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 09/16] hw/net/bcm2838_genet: add GENET register access macros
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (7 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:41 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
` (6 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 09/16] hw/net/bcm2838_genet: add GENET register access macros
2026-10-15 18:11 ` [PATCH v3 09/16] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
@ 2026-10-17 12:41 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:41 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (8 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 09/16] hw/net/bcm2838_genet: add GENET register access macros Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 15:01 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
` (5 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops
2026-10-15 18:11 ` [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
@ 2026-10-17 15:01 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 15:01 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> 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);
This has endianness problems, because not everything in s->regs
is a host uint32_t. It works for the uint32_t fields because we
force that only 4-byte accesses are valid and 'value' here is
in host order, but in e.g. BCM2838GenetRegsHfb the flt[] array
is uint8_t. (Plus it looks on the face of it like it will handle
an arbitrary offset/size but it won't work on big-endian hosts
for anything except 4/4.) Similarly for the write memcpy.
This kind of thing is why devices tend to either
(a) have a uint32_t regs[] array, and use "s->regs[offset >> 2] = value"
as their default "set the register", or
(b) have structs where the fields are named after the registers, and
have switch(offset) statements which handle each register case
separately with "s->myfield = value", and don't try to have a default
case that handles most registers.
> + } 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);
No other network device uses this function like this. This is the
function for "in your realize method, if the user didn't specify
a MAC address via config, let the net code pick one for them".
Maybe (as per the linked email thread) the APIs here could be
improved, but a new device model isn't the place to start doing
something that no other device is doing. The standard thing seems
to be that we call qemu_format_nic_info_str() with the new MAC and
call that good enough.
If we want to fix bugs in the MAC address APIs we should do that
as a separate patchset that proposes its change and implements it
in the existing device models.
> + 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;
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (9 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 12:39 ` Peter Maydell
2026-10-17 15:10 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
` (4 subsequent siblings)
15 siblings, 2 replies; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.
While carrying this forward, fix the autonegotiation-restart handling
in bcm2838_genet_mdio_cmd(): the guest PHY driver sets BMCR.ANRESTART
and polls BMCR until it reads back as cleared, but the result of
FIELD_DP16() (which returns a new value rather than modifying its
argument) was discarded, so the bit never cleared and the guest
spun forever at boot. Also refresh the link state once the restart
has been handled.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 139 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 3 +-
2 files changed, 139 insertions(+), 3 deletions(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index f3c5ca26c5..fe2a3cf890 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)
@@ -216,6 +218,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 +275,134 @@ 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) {
+ 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);
+ }
+ }
+ }
+ }
+
+ return cmd;
+}
+
static uint64_t bcm2838_genet_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t value = ~0;
@@ -359,10 +490,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 +598,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO
2026-10-15 18:11 ` [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
@ 2026-10-17 12:39 ` Peter Maydell
2026-10-17 15:10 ` Peter Maydell
1 sibling, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 12:39 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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.
>
> While carrying this forward, fix the autonegotiation-restart handling
> in bcm2838_genet_mdio_cmd(): the guest PHY driver sets BMCR.ANRESTART
> and polls BMCR until it reads back as cleared, but the result of
> FIELD_DP16() (which returns a new value rather than modifying its
> argument) was discarded, so the bit never cleared and the guest
> spun forever at boot. Also refresh the link state once the restart
> has been handled.
>
> Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
> Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
This doesn't compile at this point in the patchseries, because
this function:
> +static void bcm2838_genet_phy_update_link(BCM2838GenetState *s);
is prototyped and called, but never defined:
../../hw/net/bcm2838_genet.c:23:13: error:
‘bcm2838_genet_phy_update_link’ used but never defined [-Werror]
23 | static void bcm2838_genet_phy_update_link(BCM2838GenetState *s);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
The patchset has to be able to compile at all points, to
avoid breaking future 'git bisect' operations. Moving the
callsite and the prototype from here to the "RX path" patch
that adds the function is probably the simplest fix.
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread* Re: [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO
2026-10-15 18:11 ` [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
2026-10-17 12:39 ` Peter Maydell
@ 2026-10-17 15:10 ` Peter Maydell
1 sibling, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 15:10 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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.
>
> While carrying this forward, fix the autonegotiation-restart handling
> in bcm2838_genet_mdio_cmd(): the guest PHY driver sets BMCR.ANRESTART
> and polls BMCR until it reads back as cleared, but the result of
> FIELD_DP16() (which returns a new value rather than modifying its
> argument) was discarded, so the bit never cleared and the guest
> spun forever at boot. Also refresh the link state once the restart
> has been handled.
>
> Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
> Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
> ---
> hw/net/bcm2838_genet.c | 139 ++++++++++++++++++++++++++++++++-
> include/hw/net/bcm2838_genet.h | 3 +-
> 2 files changed, 139 insertions(+), 3 deletions(-)
>
> diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
> index f3c5ca26c5..fe2a3cf890 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)
> @@ -216,6 +218,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 +275,134 @@ 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;
Here again we are taking a pointer to a struct and assuming we
can also treat it as effectively an array of uint16_t. In this
case BCM2838GenetPhyAuxShdRegs is all uint16_t, but it would be
clearer IMHO to define it as an array of uint16_t rather than
as a struct, as we do with BCM2838GenetPhyExpShdRegs.
> +
> + 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;
8 bits? This pointer is a uint16_t *.
> + }
> + } 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;
I think it would be nice to have some (preferably ARRAY_SIZE-based,
but maybe that's tricky for a 2D array) asserts here that the indexes
aren't out of range. In this case they can't be, because the fields
are both 8 bits and the arrays are 256 elements, but an assert()
guards against future danger and saves the reader having to go look up
the details.
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (10 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 15:38 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
` (3 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.
While carrying this forward, fix bcm2838_genet_tdma_ring_active():
it extracted the per-ring enable bit using the DMA_CTRL register's
1-bit EN field definition instead of DMA_RING_CFG's 17-bit EN field
(one bit per ring), so only ring 0 ever appeared active and TX
traffic hashed onto any other queue was silently dropped, eventually
tripping the guest's qdisc watchdog ("NETDEV WATCHDOG: transmit
queue N timed out") and breaking DHCP.
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 fe2a3cf890..7eba0edbb6 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -239,6 +239,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;
@@ -403,6 +410,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_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;
+
+ 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;
@@ -496,7 +713,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path
2026-10-15 18:11 ` [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
@ 2026-10-17 15:38 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 15:38 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> From: Sergey Kambalin <serg.oker@gmail.com>
>
> Implement the transmit DMA ring handling: walk TX descriptors
> between the consumer and producer index for a ring, pull frame data
> out of guest memory, and hand completed frames to the netdev peer
> via qemu_send_packet(), advancing the consumer index and raising the
> TXDMA_MBDONE interrupt as descriptors complete.
>
> Part of Sergey Kambalin's original Raspberry Pi 4B PCIe/GENET
> networking series (patchwork series 829638, posted to qemu-devel in
> 2024, never merged). Carried forward and completed by Marcelo Manzo.
>
> While carrying this forward, fix bcm2838_genet_tdma_ring_active():
> it extracted the per-ring enable bit using the DMA_CTRL register's
> 1-bit EN field definition instead of DMA_RING_CFG's 17-bit EN field
> (one bit per ring), so only ring 0 ever appeared active and TX
> traffic hashed onto any other queue was silently dropped, eventually
> tripping the guest's qdisc watchdog ("NETDEV WATCHDOG: transmit
> queue N timed out") and breaking DHCP.
>
> 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 fe2a3cf890..7eba0edbb6 100644
> --- a/hw/net/bcm2838_genet.c
> +++ b/hw/net/bcm2838_genet.c
> @@ -239,6 +239,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;
> @@ -403,6 +410,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);
Do we know that the size is always big enough for this
(and for the accesses to buf + 12 and buf + 13 below) ?
> + 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);
This can be more clearly written as
len_type = lduw_be_p(buf + 12);
rather than manual memcpy-and-swap
> + if (len_type < MAX_PAYLOAD_SIZE) {
> + len_type = len;
> + len_type = htons(len_type);
> + memcpy(&buf[12], &len_type, sizeof(len_type));
and this is
stw_be_p(buf + 12, len);
> + }
> +
> + qemu_send_packet(s, buf, len);
> +}
> +
> +static uint64_t bcm2838_genet_tx(BCM2838GenetState *s, unsigned int ring_index,
> + uint32_t prod_index,
> + uint32_t cons_index)
> +{
> + 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;
Use lower case for variables please, upper case is for preprocessor macros.
This "construct the 64 bit value from the two 32 bit halves" is awkward
enough and happens often enough in this function that maybe it would
read more clearly if abstracted out somehow?
> +
> + 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 */
That would be a good idea :-)
> + address_space_read(&s->dma_as, data_addr,
> + MEMTXATTRS_UNSPECIFIED,
> + s->tx_packet + packet_off,
> + buflength);
There doesn't seem to be a check here that this isn't going to read
more data than we can fit without running off the end of tx_packet[].
> + 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;
You don't need the != 0 when you're assigning to a bool;
dma_en = FIELD_EX32(....);
is sufficient.
> + bool ring_en =
> + (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;
> +
> + 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;
Misaligned indent.
> + 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);
> + }
> + }
> + }
This is quite awkward to read because it indents in a lot and a
lot of the lines end up heavily wrapped as a consequence. Abstracting
some of the inner bit out into a function might help.
> + 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;
> + }
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (11 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 15:49 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
` (2 subsequent siblings)
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Sergey Kambalin,
Sergey Kambalin, Marcelo Manzo, 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.
While carrying this forward, make these changes to the original code:
- Report no hardware-computed RX checksum (rx_csum = 0). The original
stored the IPv4 header checksum in host byte order, but the guest's
CHECKSUM_COMPLETE path expects a running checksum over the L4
payload, which made the guest reject otherwise-valid inbound
packets ("hw csum failure" in bcmgenet_rx_poll()) and broke
DHCP/connectivity intermittently. With 0 the guest verifies
checksums in software instead.
- Fill in the per-packet status block with stl_le_p() rather than by
casting the packet buffer to a C struct, which is undefined
behaviour for a misaligned buffer and wrong on big-endian hosts.
- Validate the guest-programmed descriptor indexes against the size
of the descriptor array, and limit the DMA write to the size of our
packet buffer, so that a guest-controlled register can't make us
access memory outside them.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/net/bcm2838_genet.c | 288 ++++++++++++++++++++++++++++++++-
include/hw/net/bcm2838_genet.h | 1 +
2 files changed, 288 insertions(+), 1 deletion(-)
diff --git a/hw/net/bcm2838_genet.c b/hw/net/bcm2838_genet.c
index 7eba0edbb6..a0e8dd4751 100644
--- a/hw/net/bcm2838_genet.c
+++ b/hw/net/bcm2838_genet.c
@@ -527,6 +527,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)
{
@@ -742,9 +761,274 @@ 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;
+
+ 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;
+ }
+
+ if (write_index >= BCM2838_GENET_DMA_DESC_CNT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid RX descriptor index %" PRIu64
+ " (exceeds %u)\n", __func__, (uint64_t)write_index,
+ BCM2838_GENET_DMA_DESC_CNT - 1);
+ return -1;
+ }
+ desc = &rdma->descs[write_index];
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS, SOP, 1);
+
+ while (len < size) {
+ size_t l = size - len;
+ size_t buf_size = MIN(ring->ring_buf_size & 0xffff,
+ sizeof(s->rx_packet));
+ uint8_t *dma_buf = s->rx_packet;
+ hwaddr dma_buf_addr =
+ desc->address_lo + ((hwaddr)desc->address_hi << 32);
+ MemTxResult mem_tx_result = MEMTX_OK;
+ uint8_t *frame_buf = dma_buf + sizeof(BCM2838GenetXmitStatus) + 2;
+
+ bool crc_fwd = FIELD_EX32(s->regs.umac.cmd, GENET_UMAC_CMD, CRC_FWD);
+ size_t buflength;
+ uint32_t prod_index;
+
+ if (l > ring->ring_buf_size) {
+ l = ring->ring_buf_size;
+ }
+
+ memcpy(frame_buf, buf + len, l);
+
+ len += l;
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ EOP, !!(len >= size));
+
+ buflength = l + sizeof(BCM2838GenetXmitStatus) + 2;
+ if (crc_fwd) {
+ buflength += 4;
+ }
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ BUFLENGTH, buflength);
+
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ BROADCAST,
+ !!is_packet_broadcast(frame_buf, l));
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ MULTICAST,
+ !!is_packet_multicast(frame_buf, l));
+
+ /*
+ * 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.
+ */
+ stl_le_p(dma_buf + offsetof(BCM2838GenetXmitStatus, rx_csum), 0);
+ stl_le_p(dma_buf + offsetof(BCM2838GenetXmitStatus, length_status),
+ desc->length_status);
+
+ mem_tx_result = address_space_write(&s->dma_as, dma_buf_addr,
+ MEMTXATTRS_UNSPECIFIED,
+ dma_buf, buf_size);
+ if (mem_tx_result != MEMTX_OK) {
+ desc->length_status = FIELD_DP32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS,
+ RXERR, 1);
+ }
+
+ if (FIELD_EX32(desc->length_status,
+ GENET_RDMA_LENGTH_STATUS, RXERR) != 0) {
+ break;
+ }
+
+ prod_index = FIELD_EX32(ring->prod_index, GENET_DMA_PROD_INDEX, INDEX);
+ ring->prod_index = FIELD_DP32(ring->prod_index,
+ GENET_DMA_PROD_INDEX,
+ INDEX, ++prod_index);
+ if (++write_index > END_INDEX) {
+ write_index = START_INDEX;
+ }
+ if (write_index >= BCM2838_GENET_DMA_DESC_CNT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid RX descriptor index %" PRIu64
+ " (exceeds %u)\n", __func__, (uint64_t)write_index,
+ BCM2838_GENET_DMA_DESC_CNT - 1);
+ break;
+ }
+ desc = &rdma->descs[write_index];
+ ring->write_ptr = write_index * DESC_WORD_SIZE;
+ ring->write_ptr_hi = ((hwaddr)write_index * DESC_WORD_SIZE) >> 32;
+ }
+
+ if (ring_idx == BCM2838_GENET_DMA_RING_DEFAULT) {
+ s->regs.intrl0.stat = FIELD_DP32(s->regs.intrl0.stat,
+ GENET_INTRL_0, RXDMA_MBDONE, 1);
+ } else {
+ uint32_t rx_intrs =
+ FIELD_EX32(s->regs.intrl1.stat, GENET_INTRL_1, RX_INTRS);
+ rx_intrs |= 1 << ring_idx;
+
+ s->regs.intrl1.stat = FIELD_DP32(s->regs.intrl1.stat,
+ GENET_INTRL_1, RX_INTRS, rx_intrs);
+ }
+
+ return len;
+}
+
+static ssize_t bcm2838_genet_receive(NetClientState *nc, const uint8_t *buf,
+ size_t size)
+{
+ BCM2838GenetState *s = (BCM2838GenetState *)qemu_get_nic_opaque(nc);
+ ssize_t bytes_received = -1;
+ int32_t filter_index = -1;
+ int32_t ring_index = -1;
+
+ if (FIELD_EX32(s->regs.rdma.ctrl, GENET_DMA_CTRL, EN) != 0) {
+ filter_index = bcm2838_genet_filter(s, buf, size);
+
+ if (filter_index >= 0) {
+ ring_index = bcm2838_genet_filter2ring(s, filter_index);
+ } else {
+ ring_index = BCM2838_GENET_DMA_RING_CNT - 1;
+ if (!bcm2838_genet_rdma_ring_active(s, ring_index)) {
+ for (ring_index = 0;
+ ring_index < BCM2838_GENET_DMA_RING_CNT - 1;
+ ring_index++) {
+ if (bcm2838_genet_rdma_ring_active(s, ring_index)) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (size <= MAX_PACKET_SIZE) {
+ bytes_received = bcm2838_genet_rdma(s, ring_index, buf, size);
+ }
+ }
+
+ bcm2838_genet_set_irq_default(s);
+ bcm2838_genet_set_irq_prio(s);
+
+ return bytes_received;
+}
+
+static void bcm2838_genet_phy_update_link(BCM2838GenetState *s)
+{
+ bool qemu_link_down = qemu_get_queue(s->nic)->link_down != 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)
@@ -798,6 +1082,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path
2026-10-15 18:11 ` [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
@ 2026-10-17 15:49 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 15:49 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Jason Wang
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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.
>
Some of my comments on the TX path will apply here too.
> +
> + /*
> + * 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.
> + */
We don't need comments to document bugs we've fixed (especially bugs
that were only present in previous versions of the patchset, not
upstream). It is useful to note that we don't implement hardware
checksums and rely on the guest to do it in software (i.e. documenting
where we deviate from the actual hardware behaviour).
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (12 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 13:14 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 16/16] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo
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 931f2cca67..6076b43922 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -65,7 +65,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller
2026-10-15 18:11 ` [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
@ 2026-10-17 13:14 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 13:14 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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>
> ---
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
(you can keep that R-by even with the rebase fixup that
the nodes_to_remove processing loop goes away when this
lands after the RNG200 series.)
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (13 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 13:10 ` Peter Maydell
2026-10-15 18:11 ` [PATCH v3 16/16] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo,
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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-15 18:11 ` [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
@ 2026-10-17 13:10 ` Peter Maydell
2026-10-17 13:31 ` Marcelo Manzo
0 siblings, 1 reply; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 13:10 UTC (permalink / raw)
To: Marcelo Manzo
Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé,
Pierrick Bouvier
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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``
Do you know what's missing for the MSI support to work?
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: Re: [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-17 13:10 ` Peter Maydell
@ 2026-10-17 13:31 ` Marcelo Manzo
2026-10-17 14:24 ` Peter Maydell
0 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-17 13:31 UTC (permalink / raw)
To: peter.maydell; +Cc: qemu-devel, qemu-arm, philmd, pierrick.bouvier
[-- Attachment #1: Type: text/plain, Size: 2734 bytes --]
On 17 Oct 2026, Peter Maydell wrote:
>
> Do you know what's missing for the MSI support to work?
I think it's the controller's built-in MSI receiver, which we don't
model.
From the Linux driver (drivers/pci/controller/pcie-brcmstb.c): the guest
programs an MSI target address into PCIE_MISC_MSI_BAR_CONFIG_LO/HI
(0x4044/0x4048, bit 0 = enable) and the message data into
PCIE_MISC_MSI_DATA_CONFIG (0x404c). A device's MSI write then goes to
that target address (0xfffffffc, or 0xffffffffc if the inbound window
extends above 4GB) with data 0x6540 | vector. The controller is supposed
to catch that write, set bit <vector> in its MSI_INTR2 block at 0x4500
(STATUS 0x4500, CLR 0x4508, MASK_SET 0x4510, MASK_CLR 0x4514), and
assert the second interrupt of the pcie DT node ("msi") towards the GIC.
Right now none of that exists: the MSI_INTR2 registers are plain backing
bytes in regs[] with no status/clear/mask behaviour, nothing in the PCI
bus address space claims the target address so the device's write goes
nowhere, and the "msi" interrupt isn't wired to the GIC.
I think it needs a small doorbell MemoryRegion in the PCI address space
at the target address, the INTR2 register semantics, and one more SPI --
on the order of 100-150 lines. I left it out of this series since INTx
covers what I've needed, but I'm happy to do it as a follow-up if you'd
like.
thanks
-- Marcelo
On Sat, Oct 17, 2026 09:10 AM, Peter Maydell <peter.maydell@linaro.org>
wrote:
> On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com>
> wrote:
> >
> > 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``
>
> Do you know what's missing for the MSI support to work?
>
> thanks
> -- PMM
>
[-- Attachment #2: Type: text/html, Size: 3520 bytes --]
^ permalink raw reply [flat|nested] 40+ messages in thread* Re: Re: [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-17 13:31 ` Marcelo Manzo
@ 2026-10-17 14:24 ` Peter Maydell
2026-10-17 15:16 ` Marcelo Manzo
0 siblings, 1 reply; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 14:24 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, philmd, pierrick.bouvier
On Sat, 17 Oct 2026 at 14:31, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> On 17 Oct 2026, Peter Maydell wrote:
> >
> > Do you know what's missing for the MSI support to work?
>
> I think it's the controller's built-in MSI receiver, which we don't
> model.
>
> From the Linux driver (drivers/pci/controller/pcie-brcmstb.c): the guest
> programs an MSI target address into PCIE_MISC_MSI_BAR_CONFIG_LO/HI
> (0x4044/0x4048, bit 0 = enable) and the message data into
> PCIE_MISC_MSI_DATA_CONFIG (0x404c). A device's MSI write then goes to
> that target address (0xfffffffc, or 0xffffffffc if the inbound window
> extends above 4GB) with data 0x6540 | vector. The controller is supposed
> to catch that write, set bit <vector> in its MSI_INTR2 block at 0x4500
> (STATUS 0x4500, CLR 0x4508, MASK_SET 0x4510, MASK_CLR 0x4514), and
> assert the second interrupt of the pcie DT node ("msi") towards the GIC.
>
> Right now none of that exists: the MSI_INTR2 registers are plain backing
> bytes in regs[] with no status/clear/mask behaviour, nothing in the PCI
> bus address space claims the target address so the device's write goes
> nowhere, and the "msi" interrupt isn't wired to the GIC.
>
> I think it needs a small doorbell MemoryRegion in the PCI address space
> at the target address, the INTR2 register semantics, and one more SPI --
> on the order of 100-150 lines. I left it out of this series since INTx
> covers what I've needed, but I'm happy to do it as a follow-up if you'd
> like.
So if I understand right this is because the SoC doesn't implement
MSI interrupts by having a GICv3 ITS or a GICv2m, but it does it
in the PCIe controller ? Let's get the base support in before we
think about implementing that. (The aspeed pcie controller also
handles MSI interrupts, so we have some existing code models to
look at if necessary.)
Does the system end up with the "msi_nonbroken" global set to
"false" or "true" ? I think since we don't have a gicv3 or gicv2m
we ought to have it still at its default "false" value, but if it's
false I'm not sure why we need to explicitly turn off MSIs
with msi=off,msix=off. (This isn't worth spending too much time
investigating, though.)
Anyway for this patch
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread* Re: Re: Re: [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-17 14:24 ` Peter Maydell
@ 2026-10-17 15:16 ` Marcelo Manzo
2026-10-17 15:40 ` Peter Maydell
0 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-17 15:16 UTC (permalink / raw)
To: peter.maydell; +Cc: qemu-devel, qemu-arm, philmd, pierrick.bouvier
[-- Attachment #1: Type: text/plain, Size: 2865 bytes --]
On 17 Oct 2026, Peter Maydell wrote:
>
> Does the system end up with the "msi_nonbroken" global set to
> "false" or "true" ?
It ends up true. aspeed_pcie_rc_class_init() sets msi_nonbroken, and
class_init runs for every class at startup, so any qemu-system-aarch64
has it set whichever machine is selected. That's why qemu-xhci ends up
with MSI-X enabled and needs msix=off.
thanks for the R-b
-- Marcelo
On Sat, Oct 17, 2026 10:24 AM, Peter Maydell <peter.maydell@linaro.org>
wrote:
> On Sat, 17 Oct 2026 at 14:31, Marcelo Manzo <marcelomanzo@gmail.com>
> wrote:
> >
> > On 17 Oct 2026, Peter Maydell wrote:
> > >
> > > Do you know what's missing for the MSI support to work?
> >
> > I think it's the controller's built-in MSI receiver, which we don't
> > model.
> >
> > From the Linux driver (drivers/pci/controller/pcie-brcmstb.c): the guest
> > programs an MSI target address into PCIE_MISC_MSI_BAR_CONFIG_LO/HI
> > (0x4044/0x4048, bit 0 = enable) and the message data into
> > PCIE_MISC_MSI_DATA_CONFIG (0x404c). A device's MSI write then goes to
> > that target address (0xfffffffc, or 0xffffffffc if the inbound window
> > extends above 4GB) with data 0x6540 | vector. The controller is supposed
> > to catch that write, set bit <vector> in its MSI_INTR2 block at 0x4500
> > (STATUS 0x4500, CLR 0x4508, MASK_SET 0x4510, MASK_CLR 0x4514), and
> > assert the second interrupt of the pcie DT node ("msi") towards the GIC.
> >
> > Right now none of that exists: the MSI_INTR2 registers are plain backing
> > bytes in regs[] with no status/clear/mask behaviour, nothing in the PCI
> > bus address space claims the target address so the device's write goes
> > nowhere, and the "msi" interrupt isn't wired to the GIC.
> >
> > I think it needs a small doorbell MemoryRegion in the PCI address space
> > at the target address, the INTR2 register semantics, and one more SPI --
> > on the order of 100-150 lines. I left it out of this series since INTx
> > covers what I've needed, but I'm happy to do it as a follow-up if you'd
> > like.
>
> So if I understand right this is because the SoC doesn't implement
> MSI interrupts by having a GICv3 ITS or a GICv2m, but it does it
> in the PCIe controller ? Let's get the base support in before we
> think about implementing that. (The aspeed pcie controller also
> handles MSI interrupts, so we have some existing code models to
> look at if necessary.)
>
> Does the system end up with the "msi_nonbroken" global set to
> "false" or "true" ? I think since we don't have a gicv3 or gicv2m
> we ought to have it still at its default "false" value, but if it's
> false I'm not sure why we need to explicitly turn off MSIs
> with msi=off,msix=off. (This isn't worth spending too much time
> investigating, though.)
>
> Anyway for this patch
> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
>
> thanks
> -- PMM
>
[-- Attachment #2: Type: text/html, Size: 3715 bytes --]
^ permalink raw reply [flat|nested] 40+ messages in thread* Re: Re: Re: [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented
2026-10-17 15:16 ` Marcelo Manzo
@ 2026-10-17 15:40 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 15:40 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, philmd, pierrick.bouvier
On Sat, 17 Oct 2026 at 16:16, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> On 17 Oct 2026, Peter Maydell wrote:
> >
> > Does the system end up with the "msi_nonbroken" global set to
> > "false" or "true" ?
>
> It ends up true. aspeed_pcie_rc_class_init() sets msi_nonbroken, and
> class_init runs for every class at startup, so any qemu-system-aarch64
> has it set whichever machine is selected. That's why qemu-xhci ends up
> with MSI-X enabled and needs msix=off.
Whoops, that's a bug. It should be doing it on realize. I'll
follow that up with the aspeed maintainer.
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v3 16/16] tests/functional/aarch64: add raspi4b GENET networking test
2026-10-15 18:11 [PATCH v3 00/16] hw/arm/raspi4b: working PCIe and GENET (real networking) Marcelo Manzo
` (14 preceding siblings ...)
2026-10-15 18:11 ` [PATCH v3 15/16] docs/system/arm/raspi: move PCIe/GENET from missing to implemented Marcelo Manzo
@ 2026-10-15 18:11 ` Marcelo Manzo
2026-10-17 13:12 ` Peter Maydell
15 siblings, 1 reply; 40+ messages in thread
From: Marcelo Manzo @ 2026-10-15 18:11 UTC (permalink / raw)
To: qemu-devel, qemu-arm
Cc: Peter Maydell, Philippe Mathieu-Daudé, Marcelo Manzo
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). The link only comes up because the
MDIO patch handles the PHY autonegotiation-restart request (BMCR
ANRESTART) correctly; without that 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 it.
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 bacecbbe9c..f703fc69ae 100755
--- a/tests/functional/aarch64/test_raspi4.py
+++ b/tests/functional/aarch64/test_raspi4.py
@@ -109,5 +109,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.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v3 16/16] tests/functional/aarch64: add raspi4b GENET networking test
2026-10-15 18:11 ` [PATCH v3 16/16] tests/functional/aarch64: add raspi4b GENET networking test Marcelo Manzo
@ 2026-10-17 13:12 ` Peter Maydell
0 siblings, 0 replies; 40+ messages in thread
From: Peter Maydell @ 2026-10-17 13:12 UTC (permalink / raw)
To: Marcelo Manzo; +Cc: qemu-devel, qemu-arm, Philippe Mathieu-Daudé
On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <marcelomanzo@gmail.com> wrote:
>
> 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). The link only comes up because the
> MDIO patch handles the PHY autonegotiation-restart request (BMCR
> ANRESTART) correctly; without that 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 it.
>
> 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 bacecbbe9c..f703fc69ae 100755
> --- a/tests/functional/aarch64/test_raspi4.py
> +++ b/tests/functional/aarch64/test_raspi4.py
> @@ -109,5 +109,35 @@ def test_arm_raspi4_initrd(self):
> #self.vm.wait()
>
>
> + def test_arm_raspi4_genet(self):
I think you need
self.require_netdev('user')
here, to skip the test on builds which were configured without
the usermode networking backend.
Otherwise
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
thanks
-- PMM
^ permalink raw reply [flat|nested] 40+ messages in thread