package main import ( "fmt" "strings" "time" ) const ( bcmI2CWrite = 0xAC bcmI2CRead = 0xAD bcmMMDVendor uint16 = 0x1E bcmRegCmd uint16 = 0x4005 bcmRegStatus uint16 = 0x4037 bcmRegData1 uint16 = 0x4038 bcmStInProgress uint16 = 0x0002 bcmStPass uint16 = 0x0004 bcmStError uint16 = 0x0008 bcmStBusy uint16 = 0xBBBB bcmCmdGetPairSwap uint16 = 0x8000 bcmCmdSetEEEMode uint16 = 0x8009 bcmCmdSetJumbo uint16 = 0x801C bcmRegECDCtrl uint16 = 0x4006 bcmRegECDResult uint16 = 0xA896 bcmRegECDLen uint16 = 0xA897 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 ecdPoll = 200 * time.Millisecond ecdDeadline = 50 * time.Second ) type bcm struct { *sff windowEnd time.Time } func newBCM(t *sff) *bcm { b := &bcm{sff: t} t.exec(func() { t.admit = b.window }) return b } // The firmware's internal temp poll serves stale bridge reads for ~50 ms // around it; work stays inside 3.4 s of an observed poll. A resident 0x0031 at // expiry means the phase is unknown, so re-lock: arm, then take the true edge. // Each taken edge immediately re-arms — the one CMD write per window lands at // the start of the quiet period, maximally far from the next poll (writes near // the poll are the µC-wedge risk), and every later expiry reads the phase // without writing. func (b *bcm) window() { if time.Now().Add(bcmWindowFit).Before(b.windowEnd) { return } armed := false 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 { if armed { b.windowEnd = time.Now().Add(bcmWindow) b.rearm() return } b.rearm() armed = true deadline = time.Now().Add(bcmFlipWait) continue } armed = true if time.Now().After(deadline) { b.windowEnd = time.Now().Add(bcmWindow) return } time.Sleep(bcmFlipPoll) } } func (b *bcm) rearm() { if _, err := b.waitStatus(func(st uint16) bool { return st != bcmStInProgress && st != bcmStBusy }); err != nil { panic(fmt.Sprintf("%s: rearm: %v", b.ifname, err)) } if err := b.mdioWrite(bcmMMDVendor, bcmRegCmd, bcmCmdGetPairSwap); err != nil { panic(fmt.Sprintf("%s: rearm: %v", b.ifname, err)) } if _, err := b.waitStatus(func(st uint16) bool { return st == bcmStPass || st == bcmStError }); err != nil { panic(fmt.Sprintf("%s: rearm: %v", b.ifname, err)) } } func (b *bcm) mdioReadDelay(devad, reg uint16, delayUs int) (uint16, error) { d, err := b.compound(bcmI2CWrite, bcmI2CRead, delayUs, 2, []byte{0x20 | byte(devad), byte(reg >> 8), byte(reg)}) if err != nil { return 0, err } return uint16(d[0])<<8 | uint16(d[1]), nil } // 0x0000 is also the bridge's not-ready signature, so a zero is read again at // a longer delay before being believed. func (b *bcm) mdioRead(devad, reg uint16) (uint16, error) { v, err := b.mdioReadDelay(devad, reg, bcmReadDelayUs) if err != nil || v != 0 { return v, err } return b.mdioReadDelay(devad, reg, bcmRetryDelayUs) } func (b *bcm) mdioWrite(devad, reg, val uint16) error { _, err := b.op(fmt.Sprintf("w %02x %02x %02x %02x %02x %02x", bcmI2CWrite, byte(devad), byte(reg>>8), byte(reg), byte(val>>8), byte(val))) return err } // The datasheet's completion handshake: poll STATUS on its 100 ms cadence // until the wanted state, bounded by a deadline. func (b *bcm) waitStatus(want func(uint16) bool) (uint16, error) { deadline := time.Now().Add(bcmStatusTimeout) for { st, err := b.mdioRead(bcmMMDVendor, bcmRegStatus) if err != nil { return 0, err } if want(st) { return st, nil } if time.Now().After(deadline) { return 0, fmt.Errorf("%s: command handler stuck, status %#04x", b.ifname, st) } time.Sleep(bcmStatusPoll) } } // GETs must be invoked bare (pre-writing any DATA register leaves the handler // executing as a no-op); SETs must write every DATA register (the handler // executes stale DATA), so a partial parameter set is refused outright. func (b *bcm) command(code uint16, params ...uint16) (data [5]uint16, err error) { if len(params) != 0 && len(params) != len(data) { panic(fmt.Sprintf("%s: command %#04x with %d params: a SET must write all %d DATA registers", b.ifname, code, len(params), len(data))) } b.exec(func() { if _, err = b.waitStatus(func(st uint16) bool { return st != bcmStInProgress && st != bcmStBusy }); err != nil { return } for i, p := range params { if err = b.mdioWrite(bcmMMDVendor, bcmRegData1+uint16(i), p); err != nil { return } } if err = b.mdioWrite(bcmMMDVendor, bcmRegCmd, code); err != nil { return } var st uint16 if st, err = b.waitStatus(func(st uint16) bool { return st == bcmStPass || st == bcmStError }); err != nil { return } if st == bcmStError { err = fmt.Errorf("%s: command %#04x returned ERROR", b.ifname, code) return } if len(params) > 0 { return } for i := range data { if data[i], err = b.mdioRead(bcmMMDVendor, bcmRegData1+uint16(i)); err != nil { return } } }) return } func (b *bcm) identify() (ident string, err error) { b.exec(func() { var hi, lo uint16 if hi, err = b.mdioRead(1, 2); err != nil { return } if lo, err = b.mdioRead(1, 3); err != nil { return } if hi != bcmPHYIDHi || lo != bcmPHYIDLo { err = fmt.Errorf("%s: PHY ID %#04x:%#04x, want %#04x:%#04x", b.ifname, hi, lo, bcmPHYIDHi, bcmPHYIDLo) return } var sn []byte if sn, err = b.eeprom(68, 16); err != nil { return } ident = "BCM84891L sn " + strings.TrimSpace(string(sn)) }) return } func (b *bcm) forceEEEOff() error { _, err := b.command(bcmCmdSetEEEMode, 0x0000, 0x0000, 0x7A12, 0x0480, 0x0000) return err } func (b *bcm) forceJumbo() error { _, err := b.command(bcmCmdSetJumbo, 1, 0, 0, 0, 0) return err } // Left to AN, master/slave is a per-training lottery and each training's DSP // convergence moves per-pair SNR by up to ~3.6 dB; pinned roles at least keep // every session measured under identical conditions. func (b *bcm) forceRole(master bool) (err error) { b.exec(func() { var v uint16 if v, err = b.mdioRead(7, 32); err != nil { return } v |= 0x8000 if master { v |= 0x4000 } else { v &^= 0x4000 } err = b.mdioWrite(7, 32, v) }) return } func (b *bcm) pairMap() (byte, error) { d, err := b.command(bcmCmdGetPairSwap) if err != nil { return 0, err } return byte(d[1]), nil } func (b *bcm) cableDiag() (res ecdResult, err error) { b.exec(func() { var ctrl uint16 if ctrl, err = b.mdioRead(bcmMMDVendor, bcmRegECDCtrl); err != nil { return } if err = b.mdioWrite(bcmMMDVendor, bcmRegECDCtrl, ctrl&^0xF400|0x8400); err != nil { return } deadline := time.Now().Add(ecdDeadline) for { if ctrl, err = b.mdioRead(bcmMMDVendor, bcmRegECDCtrl); err != nil { return } if ctrl&0x0800 == 0 { break } if time.Now().After(deadline) { err = fmt.Errorf("%s: cable diag still busy after %s", b.ifname, ecdDeadline) return } time.Sleep(ecdPoll) } b.window() var v uint16 if v, err = b.mdioRead(1, bcmRegECDResult); err != nil { return } for i := range res.verdicts { res.verdicts[i] = int(v>>(4*i)) & 0xF if res.verdicts[i] > pairXtalk { panic(fmt.Sprintf("%s: ghost ECD verdict %#04x", b.ifname, v)) } var m uint16 if m, err = b.mdioRead(1, bcmRegECDLen+uint16(i)); err != nil { return } res.metres[i] = int(m) } }) return }