Bugs-evident cleanup: cable-measure failure is fatal with its reason (failed/fail pretend-path and dead relinked plumbing deleted), bcm.command refuses partial SETs (0 or 5 params, stale-DATA trap made structural), BOOT gains a patched-ixgbe check naming stock-driver boots, panel fault rows matched by label and console rows strict on column count, phy.go split into sff/bcm/rollball and main.go's direction machinery into direction.go (pure moves), input events decoded via encoding/binary, gofmt; verified on hardware (20.00G, zero errors, ECD 41m)
This commit is contained in:
+186
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
rbI2CWrite = 0xA2
|
||||
rbI2CRead = 0xA3
|
||||
|
||||
rbOffPassword byte = 0x7B
|
||||
rbOffPage byte = 0x7F
|
||||
rbOffCmd byte = 0x80
|
||||
rbOffDevad byte = 0x81
|
||||
rbOffValHi byte = 0x84
|
||||
rbOffPartNum byte = 0xFA
|
||||
|
||||
rbPageMailbox byte = 3
|
||||
|
||||
rbCmdWrite byte = 0x01
|
||||
rbCmdRead byte = 0x02
|
||||
rbCmdDone byte = 0x04
|
||||
|
||||
rbReadDelayUs = 500
|
||||
rbCmdPoll = 20 * time.Millisecond
|
||||
// Matches the BCM allowance for a handler frozen by 10GBASE-T training.
|
||||
rbCmdTimeout = 3 * time.Second
|
||||
|
||||
rbPHYIDHi uint16 = 0x002B
|
||||
rbPHYIDLo uint16 = 0x0BF4
|
||||
|
||||
// IEEE margins land near 7-9 dB on a healthy short cable; far outside is
|
||||
// another register's data.
|
||||
rbGhostLow = -10.0
|
||||
rbGhostHigh = 25.0
|
||||
)
|
||||
|
||||
// Only the registers proven safe on this PHY (docs/modules/wiitek/): single
|
||||
// reads in the vendor windows brick the µC permanently, so everything else
|
||||
// refuses before touching hardware.
|
||||
var rbReadSafe = map[uint16]map[uint16]bool{
|
||||
1: {1: true, 2: true, 3: true, 133: true, 134: true, 135: true, 136: true, 147: true},
|
||||
3: {32: true, 33: true},
|
||||
7: {0: true, 33: true, 60: true},
|
||||
}
|
||||
|
||||
var rbWriteSafe = map[uint16]map[uint16]bool{
|
||||
7: {0: true},
|
||||
}
|
||||
|
||||
type rollball struct {
|
||||
*sff
|
||||
}
|
||||
|
||||
func (r *rollball) i2cWrite(off byte, data ...byte) error {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "w %02x %02x", rbI2CWrite, off)
|
||||
for _, v := range data {
|
||||
fmt.Fprintf(&sb, " %02x", v)
|
||||
}
|
||||
_, err := r.op(sb.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *rollball) i2cRead(off byte, n int) ([]byte, error) {
|
||||
return r.compound(rbI2CWrite, rbI2CRead, rbReadDelayUs, n, []byte{off})
|
||||
}
|
||||
|
||||
func (r *rollball) unlock() error {
|
||||
if err := r.i2cWrite(rbOffPage, rbPageMailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.i2cWrite(rbOffPassword, 0xFF, 0xFF, 0xFF, 0xFF)
|
||||
}
|
||||
|
||||
// The µC can be mid-service of an earlier session's command when this one is
|
||||
// issued: it completes the old one, leaving DONE and a stale value in the
|
||||
// block, and a status sample taken before the new command commits reads them
|
||||
// as this command's (seen live: PHY ID high word answered by an orphaned 7.60
|
||||
// read). So status is never sampled before a full poll gap, the value rides
|
||||
// in the same block read as the status, and a completion only counts when the
|
||||
// block echoes this command's devad/reg.
|
||||
func (r *rollball) mbox(cmd byte, devad, reg, val uint16) (out [2]byte, err error) {
|
||||
if err = r.unlock(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = r.i2cWrite(rbOffDevad, byte(devad), byte(reg>>8), byte(reg)); err != nil {
|
||||
return
|
||||
}
|
||||
if cmd == rbCmdWrite {
|
||||
if err = r.i2cWrite(rbOffValHi, byte(val>>8), byte(val)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = r.i2cWrite(rbOffCmd, cmd); err != nil {
|
||||
return
|
||||
}
|
||||
deadline := time.Now().Add(rbCmdTimeout)
|
||||
for {
|
||||
time.Sleep(rbCmdPoll)
|
||||
var d []byte
|
||||
if d, err = r.i2cRead(rbOffCmd, 6); err != nil {
|
||||
return
|
||||
}
|
||||
if d[0] == rbCmdDone && d[1] == byte(devad) &&
|
||||
d[2] == byte(reg>>8) && d[3] == byte(reg) {
|
||||
out[0], out[1] = d[4], d[5]
|
||||
return
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
err = fmt.Errorf("%s: mailbox %d.%#04x stuck at %#02x", r.ifname, devad, reg, d[0])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rbGuard(safe map[uint16]map[uint16]bool, ifname, what string, devad, reg uint16) {
|
||||
if !safe[devad][reg] {
|
||||
panic(fmt.Sprintf("%s: refusing MDIO %s %d.%#04x: outside the proven-safe set",
|
||||
ifname, what, devad, reg))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rollball) mdioRead(devad, reg uint16) (uint16, error) {
|
||||
rbGuard(rbReadSafe, r.ifname, "read", devad, reg)
|
||||
d, err := r.mbox(rbCmdRead, devad, reg, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(d[0])<<8 | uint16(d[1]), nil
|
||||
}
|
||||
|
||||
func (r *rollball) mdioWrite(devad, reg, val uint16) error {
|
||||
rbGuard(rbWriteSafe, r.ifname, "write", devad, reg)
|
||||
_, err := r.mbox(rbCmdWrite, devad, reg, val)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *rollball) identify() (ident string, err error) {
|
||||
r.exec(func() {
|
||||
var hi, lo uint16
|
||||
if hi, err = r.mdioRead(1, 2); err != nil {
|
||||
return
|
||||
}
|
||||
if lo, err = r.mdioRead(1, 3); err != nil {
|
||||
return
|
||||
}
|
||||
if hi != rbPHYIDHi || lo != rbPHYIDLo {
|
||||
err = fmt.Errorf("%s: PHY ID %#04x:%#04x, want %#04x:%#04x",
|
||||
r.ifname, hi, lo, rbPHYIDHi, rbPHYIDLo)
|
||||
return
|
||||
}
|
||||
if err = r.unlock(); err != nil {
|
||||
return
|
||||
}
|
||||
var part []byte
|
||||
if part, err = r.i2cRead(rbOffPartNum, 1); err != nil {
|
||||
return
|
||||
}
|
||||
var sn []byte
|
||||
if sn, err = r.eeprom(68, 16); err != nil {
|
||||
return
|
||||
}
|
||||
ident = fmt.Sprintf("CUX3610 sn %s (A2.250=%d)", strings.TrimSpace(string(sn)), part[0])
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rollball) snrMargins() (out [4]float64, err error) {
|
||||
r.exec(func() {
|
||||
for i := range out {
|
||||
var v uint16
|
||||
if v, err = r.mdioRead(1, uint16(133+i)); err != nil {
|
||||
return
|
||||
}
|
||||
m := (float64(v) - 0x8000) / 10
|
||||
if m < rbGhostLow || m > rbGhostHigh {
|
||||
panic(fmt.Sprintf("%s: ghost SNR margin %.1f dB (1.%d=%#04x)", r.ifname, m, 133+i, v))
|
||||
}
|
||||
out[i] = m
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user