Windowed module protocol: every op admitted inside a 3.4s window after the firmware's observed temp poll (cadence 3.5-4.2s measured, stuck-at-last-fetch stale mechanism proven and avoided), acquire/window with panic on dead heartbeat; noise column shows green on/off cycle phase, cable-missing state unchanged

This commit is contained in:
flamingcow
2026-08-13 14:04:16 -07:00
parent d81594ffbf
commit a9f10d3055
8 changed files with 104 additions and 48 deletions
+48 -17
View File
@@ -38,9 +38,16 @@ const (
bcmPHYIDHi = 0x3590
bcmPHYIDLo = 0x5081
bcmCmdResidentTemp uint16 = 0x0031
bcmReadDelayUs = 3000
bcmRetryDelayUs = 10000
bcmWindow = 3400 * time.Millisecond
bcmWindowFit = 100 * time.Millisecond
bcmFlipPoll = 10 * time.Millisecond
bcmFlipWait = 5 * time.Second
bcmStatusPoll = 100 * time.Millisecond
// Covers the handler's documented 2 s freeze during 10GBASE-T training.
bcmStatusTimeout = 3 * time.Second
@@ -68,7 +75,35 @@ type bcm struct {
// Every method holds it for its whole logical operation: exactly one
// host-side conversation with the module at a time, by construction.
mu sync.Mutex
mu sync.Mutex
windowEnd time.Time
windowed atomic.Bool
}
// The firmware's internal temp poll (every 3.54.2 s, never under 3.49) serves
// stale bridge reads for ~50 ms around it; work stays inside 3.4 s post-poll.
func (b *bcm) window() {
if !b.windowed.Load() || time.Now().Add(bcmWindowFit).Before(b.windowEnd) {
return
}
deadline := time.Now().Add(bcmFlipWait)
for {
v, err := b.mdioRead(bcmMMDVendor, bcmRegCmd)
if err != nil {
panic(fmt.Sprintf("%s: heartbeat poll: %v", b.ifname, err))
}
if v == bcmCmdResidentTemp || time.Now().After(deadline) {
b.windowEnd = time.Now().Add(bcmWindow)
return
}
time.Sleep(bcmFlipPoll)
}
}
func (b *bcm) acquire() func() {
b.mu.Lock()
b.window()
return b.mu.Unlock
}
func openBCM(ifname string) (*bcm, error) {
@@ -197,8 +232,7 @@ func (b *bcm) waitStatus(want func(uint16) bool) (uint16, error) {
// executing as a no-op); SETs must pass their full parameter set (the handler
// executes stale DATA).
func (b *bcm) command(code uint16, params ...uint16) ([5]uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
var data [5]uint16
if _, err := b.waitStatus(func(st uint16) bool {
@@ -235,8 +269,7 @@ func (b *bcm) command(code uint16, params ...uint16) ([5]uint16, error) {
}
func (b *bcm) identify() (string, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
hi, err := b.mdioRead(1, 2)
if err != nil {
return "", err
@@ -259,8 +292,7 @@ func (b *bcm) identify() (string, error) {
// PMA 1.1 latches low, so the first read reports any drop since it was last
// read and the second reports the wire as it is now.
func (b *bcm) linkUp() (bool, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
if _, err := b.mdioRead(1, 1); err != nil {
return false, err
}
@@ -282,8 +314,7 @@ func (b *bcm) forceJumbo() error {
}
func (b *bcm) restartAN() error {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
v, err := b.mdioRead(7, 0)
if err != nil {
return err
@@ -292,8 +323,7 @@ func (b *bcm) restartAN() error {
}
func (b *bcm) eeeAdvert() (uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
return b.mdioRead(7, 60)
}
@@ -318,8 +348,7 @@ func (b *bcm) snr() ([4]float64, error) {
}
func (b *bcm) pcsLatch() (blocks, ber uint64, err error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
v, err := b.mdioRead(3, 33)
if err != nil {
return 0, 0, err
@@ -328,8 +357,7 @@ func (b *bcm) pcsLatch() (blocks, ber uint64, err error) {
}
func (b *bcm) fastRetrainCount() (uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
v, err := b.mdioRead(1, 147)
if err != nil {
return 0, err
@@ -343,8 +371,7 @@ type ecdResult struct {
}
func (b *bcm) cableDiag() (ecdResult, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.acquire()()
var res ecdResult
ctrl, err := b.mdioRead(bcmMMDVendor, bcmRegECDCtrl)
@@ -368,6 +395,7 @@ func (b *bcm) cableDiag() (ecdResult, error) {
}
time.Sleep(ecdPoll)
}
b.window()
v, err := b.mdioRead(1, bcmRegECDResult)
if err != nil {
return res, err
@@ -812,6 +840,9 @@ func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
}
}
res.state = adv[0] + "/" + adv[1]
for _, m := range mods {
m.bcm.windowed.Store(true)
}
return append(out, res)
}