diff --git a/sys/dev/usb/net/if_ure.c b/sys/dev/usb/net/if_ure.c index c3f7b622d687..8cccb3873eb0 100644 --- a/sys/dev/usb/net/if_ure.c +++ b/sys/dev/usb/net/if_ure.c @@ -124,6 +124,7 @@ static usb_callback_t ure_bulk_write_callback; static miibus_readreg_t ure_miibus_readreg; static miibus_writereg_t ure_miibus_writereg; static miibus_statchg_t ure_miibus_statchg; +static miibus_linkchg_t ure_miibus_linkchg; static uether_fn_t ure_attach_post; static uether_fn_t ure_init; @@ -180,6 +181,7 @@ static device_method_t ure_methods[] = { DEVMETHOD(miibus_readreg, ure_miibus_readreg), DEVMETHOD(miibus_writereg, ure_miibus_writereg), DEVMETHOD(miibus_statchg, ure_miibus_statchg), + DEVMETHOD(miibus_linkchg, ure_miibus_linkchg), DEVMETHOD_END }; @@ -439,6 +441,8 @@ ure_miibus_statchg(device_t dev) struct mii_data *mii; if_t ifp; int locked; + uint16_t bmsr; + bool new_link, old_link; sc = device_get_softc(dev); mii = GET_MII(sc); @@ -451,6 +455,7 @@ ure_miibus_statchg(device_t dev) (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) goto done; + old_link = (sc->sc_flags & URE_FLAG_LINK) ? true : false; sc->sc_flags &= ~URE_FLAG_LINK; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { @@ -471,14 +476,72 @@ ure_miibus_statchg(device_t dev) } } - /* Lost link, do nothing. */ - if ((sc->sc_flags & URE_FLAG_LINK) == 0) - goto done; + new_link = (sc->sc_flags & URE_FLAG_LINK) ? true : false; + if (old_link && !new_link) { + /* + * MII layer reports link down. Verify by reading + * the PHY BMSR register directly. BMSR link status + * is latched-low, so read twice: first clears any + * stale latch, second gives current state. + */ + (void)ure_ocp_reg_read(sc, + URE_OCP_BASE_MII + MII_BMSR * 2); + bmsr = ure_ocp_reg_read(sc, + URE_OCP_BASE_MII + MII_BMSR * 2); + + if (bmsr & BMSR_LINK) { + /* + * PHY still has link. This is a spurious + * link-down from the MII polling race (see + * PR 252165). Restore IFM_ACTIVE so the + * subsequent MIIBUS_LINKCHG check in + * mii_phy_update sees no change. + */ + device_printf(dev, + "spurious link down (PHY link up), overriding\n"); + sc->sc_flags |= URE_FLAG_LINK; + mii->mii_media_status |= IFM_ACTIVE; + } + } done: if (!locked) URE_UNLOCK(sc); } +static void +ure_miibus_linkchg(device_t dev) +{ + struct ure_softc *sc; + struct mii_data *mii; + int locked; + uint16_t bmsr; + + sc = device_get_softc(dev); + mii = GET_MII(sc); + locked = mtx_owned(&sc->sc_mtx); + if (locked == 0) + URE_LOCK(sc); + + /* + * This is called by the default miibus linkchg handler + * before it calls if_link_state_change(). If the PHY + * still has link but the MII layer lost IFM_ACTIVE due + * to the polling race (see PR 252165), restore it so the + * notification goes out as LINK_STATE_UP rather than DOWN. + */ + if (mii != NULL && (mii->mii_media_status & IFM_ACTIVE) == 0) { + (void)ure_ocp_reg_read(sc, + URE_OCP_BASE_MII + MII_BMSR * 2); + bmsr = ure_ocp_reg_read(sc, + URE_OCP_BASE_MII + MII_BMSR * 2); + if (bmsr & BMSR_LINK) + mii->mii_media_status |= IFM_ACTIVE; + } + + if (locked == 0) + URE_UNLOCK(sc); +} + /* * Probe for a RTL8152/RTL8153/RTL8156 chip. */ @@ -515,6 +578,7 @@ ure_attach(device_t dev) sc->sc_flags = USB_GET_DRIVER_INFO(uaa); device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + sx_init(&sc->sc_mii_lock, "uremii"); iface_index = URE_IFACE_IDX; @@ -589,6 +653,7 @@ ure_detach(device_t dev) usbd_transfer_unsetup(sc->sc_tx_xfer, URE_MAX_TX); usbd_transfer_unsetup(sc->sc_rx_xfer, URE_MAX_RX); uether_ifdetach(ue); + sx_destroy(&sc->sc_mii_lock); mtx_destroy(&sc->sc_mtx); return (0); @@ -1153,6 +1218,13 @@ ure_tick(struct usb_ether *ue) URE_LOCK_ASSERT(sc, MA_OWNED); + URE_UNLOCK(sc); + if (!URE_MII_TRYLOCK(sc)) { + URE_LOCK(sc); + return; + } + URE_LOCK(sc); + (void)ifp; for (int i = 0; i < URE_MAX_RX; i++) DEVPRINTFN(13, sc->sc_ue.ue_dev, @@ -1175,6 +1247,9 @@ ure_tick(struct usb_ether *ue) ure_start(ue); } } + URE_UNLOCK(sc); + URE_MII_UNLOCK(sc); + URE_LOCK(sc); } static u_int @@ -1340,9 +1415,19 @@ ure_ifmedia_upd(if_t ifp) mii = GET_MII(sc); URE_LOCK_ASSERT(sc, MA_OWNED); + + URE_UNLOCK(sc); + URE_MII_LOCK(sc); + URE_LOCK(sc); + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); + + URE_UNLOCK(sc); + URE_MII_UNLOCK(sc); + URE_LOCK(sc); + return (error); } @@ -1384,11 +1469,13 @@ ure_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) mii = GET_MII(sc); + URE_MII_LOCK(sc); URE_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; URE_UNLOCK(sc); + URE_MII_UNLOCK(sc); } static void diff --git a/sys/dev/usb/net/if_urereg.h b/sys/dev/usb/net/if_urereg.h index e4e171f4910e..52fd3e0ff8f2 100644 --- a/sys/dev/usb/net/if_urereg.h +++ b/sys/dev/usb/net/if_urereg.h @@ -581,6 +581,7 @@ struct ure_softc { struct usb_ether sc_ue; struct ifmedia sc_ifmedia; struct mtx sc_mtx; + struct sx sc_mii_lock; struct usb_xfer *sc_rx_xfer[URE_MAX_RX]; struct usb_xfer *sc_tx_xfer[URE_MAX_TX]; @@ -616,5 +617,8 @@ struct ure_softc { #define URE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define URE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define URE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) +#define URE_MII_LOCK(_sc) sx_xlock(&(_sc)->sc_mii_lock) +#define URE_MII_UNLOCK(_sc) sx_xunlock(&(_sc)->sc_mii_lock) +#define URE_MII_TRYLOCK(_sc) sx_try_xlock(&(_sc)->sc_mii_lock) #endif /* _IF_UREREG_H_ */