From de11996fcaec2197e57a5c2805663d4ece141b11 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 17 Jun 2026 15:47:23 -0700 Subject: [PATCH 1/2] net: fec: add direct MDIO bus access via SIOCGMIIREG/SIOCSMIIREG Add a fec_enet_ioctl() wrapper that services the SIOCGMIIREG and SIOCSMIIREG ioctls directly against fep->mii_bus, delegating all other commands to the standard phy_do_ioctl_running() handler. The generic PHY ioctl path has two limitations for board bring-up and diagnostics: - It requires an attached phydev. phy_do_ioctl() returns -ENODEV when ndev->phydev is NULL, which is the case when the FEC is wired to a DSA switch (SJA1105 on this platform) over a fixed-link rather than to a conventional PHY. In that configuration the MDIO bus cannot be reached from userspace at all. - For an attached PHY it routes register writes through the PHY state machine, applying side effects (autoneg disable/restart, phy_init_hw) for BMCR/ADVERTISE/CTRL1000 writes. That is undesirable for raw register inspection and poking. Servicing the two MII register commands directly against the bus gives side-effect-free access to any device address on the FEC MDIO bus, which userspace tooling relies on. This reworks a patch distributed with the Audinate i.MX8MM EVK to fit the 6.6 driver, which no longer has a custom fec_enet_ioctl() (it wired phy_do_ioctl_running() directly). The original logged failures with KERN_ALERT and ignored read errors; this version returns the bus error to the caller and propagates read failures instead of reporting a bogus zero. This is a diagnostic capability rather than a correctness fix. If it proves unused (verifiable at runtime by stracing for SIOC[GS]MIIREG) or problematic, it can be reverted without affecting normal operation. --- drivers/net/ethernet/freescale/fec_main.c | 38 ++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index e7eb6f598f58..d59bd971eba0 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -4024,6 +4024,42 @@ static int fec_hwtstamp_set(struct net_device *ndev, return fec_ptp_set(ndev, config, extack); } +/* Provide direct MDIO bus register access via SIOCGMIIREG/SIOCSMIIREG. + * + * The generic phy_do_ioctl_running() path only works when a phydev is + * attached to this net_device and routes register access through the PHY + * state machine (with side effects such as autoneg restarts). On boards + * where the FEC is wired to a DSA switch (e.g. SJA1105) over a fixed-link, + * there is no attached phydev, so the generic path returns -ENODEV and the + * MDIO bus is unreachable from userspace. + * + * Intercept the two MII register commands and service them directly against + * fep->mii_bus so bring-up/diagnostic tooling can reach any device address + * on the bus without side effects. All other commands fall through to the + * standard handler. (Reworked from a patch shipped with the Audinate EVK.) + */ +static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) +{ + struct fec_enet_private *fep = netdev_priv(ndev); + struct mii_ioctl_data *mii = if_mii(rq); + int ret; + + switch (cmd) { + case SIOCGMIIREG: + ret = mdiobus_read_nested(fep->mii_bus, mii->phy_id, + mii->reg_num); + if (ret < 0) + return ret; + mii->val_out = ret; + return 0; + case SIOCSMIIREG: + return mdiobus_write_nested(fep->mii_bus, mii->phy_id, + mii->reg_num, mii->val_in); + default: + return phy_do_ioctl_running(ndev, rq, cmd); + } +} + static const struct net_device_ops fec_netdev_ops = { .ndo_open = fec_enet_open, .ndo_stop = fec_enet_close, @@ -4033,7 +4069,7 @@ static const struct net_device_ops fec_netdev_ops = { .ndo_validate_addr = eth_validate_addr, .ndo_tx_timeout = fec_timeout, .ndo_set_mac_address = fec_set_mac_address, - .ndo_eth_ioctl = phy_do_ioctl_running, + .ndo_eth_ioctl = fec_enet_ioctl, .ndo_set_features = fec_set_features, .ndo_bpf = fec_enet_bpf, .ndo_xdp_xmit = fec_enet_xdp_xmit, From aa98197fcbe46f3ff0460640198938d095f777a0 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Mon, 29 Jun 2026 13:23:25 -0700 Subject: [PATCH 2/2] net: fec: handle Clause-45 phy_id in direct MDIO ioctl path The direct SIOCGMIIREG/SIOCSMIIREG path only used the Clause-22 bus accessors and passed mii->phy_id through unmodified. A Clause-45-encoded phy_id (MII_ADDR_C45) would therefore be treated as a C22 address and hit the wrong device/register, shadowing the C45 support that phy_mii_ioctl() provides via FEC_QUIRK_HAS_MDIO_C45. Mirror phy_mii_ioctl(): when mdio_phy_id_is_c45() is set, split the phy_id into prtad/devad and route through mdiobus_c45_read_nested/ mdiobus_c45_write_nested. The C22 path is unchanged. --- drivers/net/ethernet/freescale/fec_main.c | 30 +++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index d59bd971eba0..cb886aa138d9 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -4035,24 +4035,44 @@ static int fec_hwtstamp_set(struct net_device *ndev, * * Intercept the two MII register commands and service them directly against * fep->mii_bus so bring-up/diagnostic tooling can reach any device address - * on the bus without side effects. All other commands fall through to the - * standard handler. (Reworked from a patch shipped with the Audinate EVK.) + * on the bus without side effects. A Clause-45-encoded phy_id (MII_ADDR_C45) + * is split into prtad/devad and routed through the C45 bus accessors, mirroring + * phy_mii_ioctl() so C45 PHYs on the FEC bus remain reachable. All other + * commands fall through to the standard handler. (Reworked from a patch shipped + * with the Audinate EVK.) */ static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) { struct fec_enet_private *fep = netdev_priv(ndev); struct mii_ioctl_data *mii = if_mii(rq); - int ret; + int prtad, devad, ret; + bool is_c45; switch (cmd) { case SIOCGMIIREG: - ret = mdiobus_read_nested(fep->mii_bus, mii->phy_id, - mii->reg_num); + is_c45 = mdio_phy_id_is_c45(mii->phy_id); + if (is_c45) { + prtad = mdio_phy_id_prtad(mii->phy_id); + devad = mdio_phy_id_devad(mii->phy_id); + ret = mdiobus_c45_read_nested(fep->mii_bus, prtad, devad, + mii->reg_num); + } else { + ret = mdiobus_read_nested(fep->mii_bus, mii->phy_id, + mii->reg_num); + } if (ret < 0) return ret; mii->val_out = ret; return 0; case SIOCSMIIREG: + is_c45 = mdio_phy_id_is_c45(mii->phy_id); + if (is_c45) { + prtad = mdio_phy_id_prtad(mii->phy_id); + devad = mdio_phy_id_devad(mii->phy_id); + return mdiobus_c45_write_nested(fep->mii_bus, prtad, + devad, mii->reg_num, + mii->val_in); + } return mdiobus_write_nested(fep->mii_bus, mii->phy_id, mii->reg_num, mii->val_in); default: