improvements to phy package

This commit is contained in:
Patricio Whittingslow
2026-01-25 22:30:26 -03:00
parent 470960f924
commit 2e5cbd6f76
2 changed files with 100 additions and 38 deletions
+62 -3
View File
@@ -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.
+38 -35
View File
@@ -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.