diff --git a/phy/phy.go b/phy/phy.go index f67177a..c9b584b 100644 --- a/phy/phy.go +++ b/phy/phy.go @@ -56,6 +56,10 @@ func FindClause22PHYs(mdio MDIOBus, dst []uint8) (n int, err error) { return n, err } +var ( + errInvalidPhyAddr = errors.New("invalid phy addr") +) + type Device struct { mdio MDIOBus phyaddr uint8 @@ -64,13 +68,17 @@ type Device struct { } // ConfigureAs22 resets all state of device to be used as a Clause22 device. Does not do a software reset. -func (phy *Device) ConfigureAs22(mdio MDIOBus, phyAddr uint8) { - if phyAddr > 31 || mdio == nil { - panic("invalid argument to phy.Device.Reset") +func (phy *Device) ConfigureAs22(mdio MDIOBus, phyAddr uint8) error { + if phyAddr > 31 { + return errInvalidPhyAddr + + } else if mdio == nil { + return errors.New("nil mdio bus") } phy.mdio = mdio phy.phyaddr = phyAddr phy.isClause45 = 0 + return nil } // IsClause45 returns true if the device uses Clause 45 MDIO addressing (extended register access). @@ -214,6 +222,57 @@ func (phy *Device) IsLinkUp() (bool, error) { return status&BMSRLinkStatus != 0, nil } +// WaitForLinkWithDeadline waits for link to establish until the deadline. +// If auto-negotiation is enabled (BMCR.ANEnable=1), waits for AN to complete first. +// Returns true if link is up, false if deadline exceeded. +// +// Per IEEE 802.3: +// - BMSR.LinkStatus is latched-low, so first read clears any previous fault +// - BMSR.ANComplete must be set before link parameters are valid (when AN enabled) +// - link_fail_inhibit_timer (50-75ms) delays link indication after AN completes +func (phy *Device) WaitForLinkWithDeadline(deadline time.Time) (bool, error) { + const pollInterval = 50 * time.Millisecond + // Check current PHY configuration. + // Early exit: link impossible if PHY isolated or powered down. + ctl, err := phy.BasicControl() + if err != nil { + return false, err + } else if ctl&BMCRIsolate != 0 { + return false, errors.New("PHY isolated from MII") + } else if ctl&BMCRPowerDown != 0 { + return false, errors.New("PHY powered down") + } + + // First read clears latched-low bits (LinkStatus, ANComplete). + // This ensures we get fresh status on subsequent reads. + _, _ = phy.BasicStatus() + anEnabled := ctl&BMCRANEnable != 0 + for time.Now().Before(deadline) { + status, err := phy.BasicStatus() + if err != nil { + return false, err + } + // If AN enabled, must wait for it to complete first. + // No point checking link status until AN is done. + if anEnabled && !status.AutoNegotiationComplete() { + time.Sleep(pollInterval) + continue + } + // AN complete (or disabled). Check link status. + if status.LinkUp() { + return true, nil + } + time.Sleep(pollInterval) + } + + // Final check after deadline. + status, err := phy.BasicStatus() + if err != nil { + return false, err + } + return status.LinkUp(), nil +} + // NegotiatedLink returns the auto-negotiated link mode using standard MII registers. // Returns LinkMode based on ANAR (our advertisement) AND ANLPAR (link partner ability). // Priority order per IEEE 802.3 Annex 28B.3. diff --git a/phy/reg.go b/phy/reg.go index 4fd8576..fc04a6d 100644 --- a/phy/reg.go +++ b/phy/reg.go @@ -11,7 +11,7 @@ const ( regAutoNegotiationAdvertisement = 0x04 regAutoNegotiationLinkPartnerAbility = 0x05 - regAutoNegotiationExpansion = 0x05 + regAutoNegotiationExpansion = 0x06 regModeControlStatus = 0x11 regSpecialModes = 0x12 regSymbolErorCounter = 0x1a @@ -115,22 +115,6 @@ const ( ANARPauseMask ANAR = ANARPause | ANARPauseAsym ) -func (l LinkMode) ANAR() (a ANAR) { - switch l { - case Link10HDX: - a = ANAR10Half - case Link10FDX: - a = ANAR10Full - case Link100HDX: - a = ANAR100Half - case Link100FDX: - a = ANAR100Full - case Link100T4: - a = ANAR100BaseT4 - } - return a -} - // WithPause returns ANAR with pause bits set according to parameters. // // Flow control allows a receiver to signal the sender to pause transmission. @@ -203,21 +187,22 @@ func (a ANAR) Without100M() ANAR { // LinkMode returns the highest priority LinkMode from the ANAR speed bits. // Priority order per IEEE 802.3 Annex 28B.3. // Returns LinkDown if no speed bits are set. -func (a ANAR) LinkMode() LinkMode { +func (a ANAR) LinkMode() (l LinkMode) { switch { case a&ANAR100Full != 0: - return Link100FDX + l = Link100FDX case a&ANAR100BaseT4 != 0: - return Link100T4 + l = Link100T4 case a&ANAR100Half != 0: - return Link100HDX + l = Link100HDX case a&ANAR10Full != 0: - return Link10FDX + l = Link10FDX case a&ANAR10Half != 0: - return Link10HDX + l = Link10HDX default: - return LinkDown + l = LinkDown } + return l } // LinkMode represents the negotiated/force-set Ethernet link speed and duplex mode. @@ -249,30 +234,48 @@ const ( Link100GFDX // 100G-F ) +func (l LinkMode) ANAR() (a ANAR) { + a = NewANAR() + switch l { + case Link10HDX: + a |= ANAR10Half + case Link10FDX: + a |= ANAR10Full + case Link100HDX: + a |= ANAR100Half + case Link100FDX: + a |= ANAR100Full + case Link100T4: + a |= ANAR100BaseT4 + } + return a +} + // SpeedMbps returns the link speed in megabits per second. -func (lm LinkMode) SpeedMbps() int { +func (lm LinkMode) SpeedMbps() (Mbps int) { switch lm { case Link10HDX, Link10FDX: - return 10 + Mbps = 10 case Link100HDX, Link100FDX, Link100T4: - return 100 + Mbps = 100 case Link1000HDX, Link1000FDX: - return 1000 + Mbps = 1000 case Link2500FDX: - return 2500 + Mbps = 2500 case Link5GFDX: - return 5000 + Mbps = 5000 case Link10GFDX: - return 10_000 + Mbps = 10_000 case Link25GFDX: - return 25_000 + Mbps = 25_000 case Link40GFDX: - return 40_000 + Mbps = 40_000 case Link100GFDX: - return 100_000 + Mbps = 100_000 default: - return 0 + Mbps = 0 } + return Mbps } // IsFullDuplex returns true if the link mode is full duplex.