308 lines
7.7 KiB
Go
308 lines
7.7 KiB
Go
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
|
||
|
|
}
|