On Thu, 15 Oct 2026 at 19:11, Marcelo Manzo <[email protected]> wrote:
>
> From: Sergey Kambalin <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Marcelo Manzo <[email protected]>
> ---
>  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

Reply via email to