Strip canaries, double-reads, retries and floors from the module protocol; every bcm method holds the per-module lock for its whole operation; facts stay in docs with internal-client contention marked unresolved

This commit is contained in:
flamingcow
2026-08-13 10:32:24 -07:00
parent 9d1f15045f
commit f2891886f3
3 changed files with 47 additions and 106 deletions
+43 -102
View File
@@ -67,8 +67,8 @@ type bcm struct {
ifname string
path string
// Guards multi-op sequences only; single reads are already atomic on the
// wire through the compound op.
// 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
}
@@ -191,27 +191,9 @@ func (b *bcm) waitStatus(want func(uint16) bool) (uint16, error) {
return 0, fmt.Errorf("%s: command handler stuck, status %#04x", b.ifname, st)
}
// The bridge gives no signal tying a response to its fetch, so a known-answer
// read validates each batch: 0x3590 back from PHY ID 1.2 proves the bridge
// was serving timely through the window; anything else discards the batch.
func (b *bcm) canary() error {
v, err := b.mdioRead(1, 2)
if err != nil {
return err
}
if v != bcmPHYIDHi {
return fmt.Errorf("%s: bridge served %#04x for the PHY ID canary", b.ifname, v)
}
return nil
}
// GETs must be invoked bare: pre-writing any DATA register — not just the
// documented DATA1 flag — leaves the handler executing as a no-op. SETs must
// pass their full parameter set (the handler executes stale DATA) and get
// settle time in place of unprovable completion. Results are read twice and
// must agree — a stale PASS otherwise serves the handler's in-flight scratch
// — with DATA1 excluded from the comparison (firmware writes temperature
// there autonomously).
// GETs must be invoked bare (pre-writing any DATA register leaves the handler
// executing as a no-op); SETs must pass their full parameter set (the handler
// executes stale DATA) and get settle time in place of unprovable completion.
func (b *bcm) command(code uint16, params ...uint16) ([5]uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
@@ -242,69 +224,44 @@ func (b *bcm) command(code uint16, params ...uint16) ([5]uint16, error) {
if st == bcmStError {
return data, fmt.Errorf("%s: command %#04x returned ERROR", b.ifname, code)
}
// A SET's DATA registers are its parameters, scribbled over by firmware
// afterwards; there is nothing to read back.
if len(params) > 0 {
return data, b.canary()
return data, nil
}
for i := range data {
if data[i], err = b.mdioRead(bcmMMDVendor, bcmRegData1+uint16(i)); err != nil {
return data, err
}
}
for i := 1; i < len(data); i++ {
again, err := b.mdioRead(bcmMMDVendor, bcmRegData1+uint16(i))
if err != nil {
return data, err
}
if again != data[i] {
return data, fmt.Errorf("%s: command %#04x results unstable", b.ifname, code)
}
}
// The firmware is its own mailbox client (an internal GET_CURRENT_TEMP
// every few seconds); CMD still holding our code proves a GET's results
// are ours. Bit 15 is consumed on acceptance.
cmdv, err := b.mdioRead(bcmMMDVendor, bcmRegCmd)
if err != nil {
return data, err
}
if cmdv != code&^0x8000 {
return data, fmt.Errorf("%s: command %#04x preempted, CMD reads %#04x", b.ifname, code, cmdv)
}
if err := b.canary(); err != nil {
return data, err
}
return data, nil
}
// Retried against the known constant, long enough to outlast a µC left busy
// by a dead process's in-flight diag or a link mid-training.
func (b *bcm) identify() (string, error) {
var hi, lo uint16
var err error
for i := 0; i < 30; i++ {
if hi, err = b.mdioRead(1, 2); err != nil {
return "", err
}
if lo, err = b.mdioRead(1, 3); err != nil {
return "", err
}
if hi == bcmPHYIDHi && lo == bcmPHYIDLo {
sn, err := b.eeprom(68, 16)
if err != nil {
return "", err
}
return "BCM84891L sn " + strings.TrimSpace(string(sn)), nil
}
time.Sleep(500 * time.Millisecond)
b.mu.Lock()
defer b.mu.Unlock()
hi, err := b.mdioRead(1, 2)
if err != nil {
return "", err
}
return "", fmt.Errorf("%s: PHY ID %#04x:%#04x, want %#04x:%#04x",
b.ifname, hi, lo, bcmPHYIDHi, bcmPHYIDLo)
lo, err := b.mdioRead(1, 3)
if err != nil {
return "", err
}
if hi != bcmPHYIDHi || lo != bcmPHYIDLo {
return "", fmt.Errorf("%s: PHY ID %#04x:%#04x, want %#04x:%#04x",
b.ifname, hi, lo, bcmPHYIDHi, bcmPHYIDLo)
}
sn, err := b.eeprom(68, 16)
if err != nil {
return "", err
}
return "BCM84891L sn " + strings.TrimSpace(string(sn)), nil
}
// 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()
if _, err := b.mdioRead(1, 1); err != nil {
return false, err
}
@@ -326,6 +283,8 @@ func (b *bcm) forceJumbo() error {
}
func (b *bcm) restartAN() error {
b.mu.Lock()
defer b.mu.Unlock()
v, err := b.mdioRead(7, 0)
if err != nil {
return err
@@ -334,6 +293,8 @@ func (b *bcm) restartAN() error {
}
func (b *bcm) eeeAdvert() (uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.mdioRead(7, 60)
}
@@ -358,6 +319,8 @@ func (b *bcm) snr() ([4]float64, error) {
}
func (b *bcm) pcsLatch() (blocks, ber uint64, err error) {
b.mu.Lock()
defer b.mu.Unlock()
v, err := b.mdioRead(3, 33)
if err != nil {
return 0, 0, err
@@ -366,6 +329,8 @@ func (b *bcm) pcsLatch() (blocks, ber uint64, err error) {
}
func (b *bcm) fastRetrainCount() (uint16, error) {
b.mu.Lock()
defer b.mu.Unlock()
v, err := b.mdioRead(1, 147)
if err != nil {
return 0, err
@@ -404,30 +369,19 @@ func (b *bcm) cableDiag() (ecdResult, error) {
}
time.Sleep(ecdPoll)
}
// The run's own blip leaves the µC busy training, so a failed canary here
// means try the latched results again shortly, not give up.
var canaryErr error
for try := 0; try < 20; try++ {
if try > 0 {
time.Sleep(500 * time.Millisecond)
}
v, err := b.mdioRead(1, bcmRegECDResult)
v, err := b.mdioRead(1, bcmRegECDResult)
if err != nil {
return res, err
}
for i := range res.verdicts {
res.verdicts[i] = int(v>>(4*i)) & 0xF
m, err := b.mdioRead(1, bcmRegECDLen+uint16(i))
if err != nil {
return res, err
}
for i := range res.verdicts {
res.verdicts[i] = int(v>>(4*i)) & 0xF
m, err := b.mdioRead(1, bcmRegECDLen+uint16(i))
if err != nil {
return res, err
}
res.metres[i] = int(m)
}
if canaryErr = b.canary(); canaryErr == nil {
return res, nil
}
res.metres[i] = int(m)
}
return res, canaryErr
return res, nil
}
const (
@@ -477,9 +431,6 @@ func (m *phyModule) poll() error {
}
quiet := !m.upSince.IsZero() && time.Since(m.upSince) >= bcmQuiet
if err := m.bcm.canary(); err != nil {
return err
}
link, err := m.bcm.linkUp()
if err != nil {
return err
@@ -490,13 +441,6 @@ func (m *phyModule) poll() error {
if snr, err = m.bcm.snr(); err != nil {
return err
}
// The proven garbage signatures — die temperature, handler status —
// all sit far below any SNR a trained link can have.
for _, s := range snr {
if s < 15 {
return fmt.Errorf("%s: implausible SNR %.1f discarded", m.bcm.ifname, s)
}
}
}
blocks, ber, err := m.bcm.pcsLatch()
if err != nil {
@@ -506,9 +450,6 @@ func (m *phyModule) poll() error {
if err != nil {
return err
}
if err := m.bcm.canary(); err != nil {
return err
}
m.mu.Lock()
m.sampled = true