From: Marcelo Manzo <marcelomanzo@gmail.com>
To: qemu-devel@nongnu.org, qemu-arm@nongnu.org
Cc: "Peter Maydell" <peter.maydell@linaro.org>,
"Philippe Mathieu-Daudé" <philmd@oss.qualcomm.com>,
"Sergey Kambalin" <serg.oker@gmail.com>,
"Sergey Kambalin" <sergey.kambalin@auriga.com>,
"Marcelo Manzo" <marcelomanzo@gmail.com>,
"Jason Wang" <jasowangio@gmail.com>
Subject: [PATCH v3 11/16] hw/net/bcm2838_genet: implement GENET MDIO
Date: Thu, 15 Oct 2026 14:11:16 -0400 [thread overview]
Message-ID: <20261015181122.60608-12-marcelomanzo@gmail.com> (raw)
In-Reply-To: <20261015181122.60608-1-marcelomanzo@gmail.com>
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
next prev parent reply other threads:[~2026-10-15 18:12 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` [PATCH v3 03/16] hw/arm/bcm2838: enable BCM2838 PCIe host bridge Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 04/16] hw/net/bcm2838_genet: add GENET stub device Marcelo Manzo
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 ` [PATCH v3 06/16] hw/net/bcm2838_genet: add GENET register structs, part 2/4 Marcelo Manzo
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 ` [PATCH v3 08/16] hw/net/bcm2838_genet: add GENET register structs, part 4/4 Marcelo Manzo
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 ` [PATCH v3 10/16] hw/net/bcm2838_genet: implement GENET register ops Marcelo Manzo
2026-10-15 18:11 ` Marcelo Manzo [this message]
2026-10-15 18:11 ` [PATCH v3 12/16] hw/net/bcm2838_genet: implement GENET TX path Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 13/16] hw/net/bcm2838_genet: implement GENET RX path Marcelo Manzo
2026-10-15 18:11 ` [PATCH v3 14/16] hw/arm/bcm2838: enable BCM2838 GENET controller Marcelo Manzo
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261015181122.60608-12-marcelomanzo@gmail.com \
--to=marcelomanzo@gmail.com \
--cc=jasowangio@gmail.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@oss.qualcomm.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=serg.oker@gmail.com \
--cc=sergey.kambalin@auriga.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.