Jumbo in the size mix (MTU 9000 + module jumbo pinned), check-first module config trusting GET reads, cable diag async at startup through the reset path, MODULES table dissolved into SETTINGS/LINKS, per-driver NIC counter sets, comment cleanup

This commit is contained in:
flamingcow
2026-08-13 08:04:26 -07:00
parent 979dc7a772
commit bba1fa5e02
12 changed files with 201 additions and 135 deletions
+116 -77
View File
@@ -27,7 +27,10 @@ const (
bcmStBusy uint16 = 0xBBBB
bcmCmdGetPairSwap uint16 = 0x8000
bcmCmdGetEEEMode uint16 = 0x8008
bcmCmdSetEEEMode uint16 = 0x8009
bcmCmdSetJumbo uint16 = 0x801C
bcmCmdGetJumbo uint16 = 0x801D
bcmCmdGetSNR uint16 = 0x8030
bcmRegECDCtrl uint16 = 0x4006
@@ -64,9 +67,8 @@ type bcm struct {
ifname string
path string
// Serializes the multi-op sequences — a handler command, an ECD run — that
// would corrupt each other interleaved. Single register reads ride bare:
// the compound op makes each one atomic on the wire.
// Guards multi-op sequences only; single reads are already atomic on the
// wire through the compound op.
mu sync.Mutex
}
@@ -129,8 +131,8 @@ func parseHexBytes(s string, n int) ([]byte, error) {
return out, nil
}
// One write-STOP-delay-read transaction under a single bus hold, so the
// driver's own SFP traffic can never consume the bridge's pending data.
// A single bus hold; split write/read ops would let the driver's own SFP
// traffic consume the bridge's pending read.
func (b *bcm) compound(waddr, raddr byte, delayUs, n int, wdata []byte) ([]byte, error) {
var sb strings.Builder
fmt.Fprintf(&sb, "x %02x %02x %d %x", waddr, raddr, delayUs, n)
@@ -260,11 +262,33 @@ func (b *bcm) linkUp() (bool, error) {
return v&0x0004 != 0, nil
}
func (b *bcm) eeeMode() (uint16, error) {
d, err := b.command(bcmCmdGetEEEMode)
return d[0], err
}
func (b *bcm) forceEEEOff() error {
_, err := b.command(bcmCmdSetEEEMode, 0x0000, 0x0000, 0x7A12, 0x0480, 0x0000)
return err
}
func (b *bcm) jumboState() (bool, string, error) {
d, err := b.command(bcmCmdGetJumbo)
if err != nil {
return false, "", err
}
size := map[uint16]string{0: "10K", 1: "18K", 2: "9K"}[d[1]]
if size == "" {
size = fmt.Sprintf("size %d", d[1])
}
return d[0] == 1, size, nil
}
func (b *bcm) forceJumbo() error {
_, err := b.command(bcmCmdSetJumbo, 1, 0, 0, 0, 0)
return err
}
func (b *bcm) restartAN() error {
v, err := b.mdioRead(7, 0)
if err != nil {
@@ -414,10 +438,8 @@ func (m *phyModule) poll() error {
m.link = link
m.haveSNR = link
m.snr = snr
// The first poll after a baseline drains what the latches gathered during
// the bringup or diag retrain, which predates the run: it only establishes
// the origin. The retrain counter is 5 bits and rolls over, so only its
// forward motion is kept.
// The first poll after a baseline drains latches from the bringup/diag
// retrain era, so it only sets the origin; the retrain counter is 5 bits.
if m.primed {
delta := blocks + ber + uint64((count-m.retrainCount)&0x1F)
m.blocks += blocks
@@ -433,8 +455,6 @@ func (m *phyModule) poll() error {
return nil
}
// A tester that quietly loses its SNR eye goes on reporting a clean link, so a
// transport that stays dark past every transient explanation stops the run.
func (m *phyModule) run(done *atomic.Bool) {
tick := time.NewTicker(phyInterval)
defer tick.Stop()
@@ -496,8 +516,6 @@ type cableInfo struct {
maps [2]byte
}
// The four pair lengths of one healthy cable disagree by a few metres of twist
// rate, so the cable's length is shown as their mean.
func (c cableInfo) metresString() string {
sum, n := 0, 0
for i, v := range c.ecd.verdicts {
@@ -546,10 +564,6 @@ func pairSwapped(i int, maps [2]byte) bool {
return int(maps[0]>>(2*i))&3 != i || int(maps[1]>>(2*i))&3 != i
}
// The cable as one figure and one judgment: the mean length of its healthy
// pairs, red when the diag found a fault, amber when a pair arrived swapped.
// Per-pair detail stays on the console — pair letters don't correlate back to
// wires by eye.
func cableSummary(cable cableInfo, measuring bool) (string, int) {
if measuring {
return "...", clsNone
@@ -578,7 +592,6 @@ func cableSummary(cable cableInfo, measuring bool) (string, int) {
return s, clsGood
}
// The worse of the two receivers' margins, worst pair across the cable.
func phyDisplayFrom(cable cableInfo, measuring bool, a, b phyModView) phyDisplay {
d := phyDisplay{
haveSNR: a.fresh && b.fresh && a.link && b.link,
@@ -619,9 +632,7 @@ func waitLink(mods []*phyModule, done *atomic.Bool) (time.Duration, bool, error)
}
}
// The whole cable picture in one pass: the ECD's per-pair verdicts and
// lengths, then — after the blip it causes has settled — both ends' pair
// maps, read post-link so the MDI resolution is the fresh one.
// Pair maps are read after the relink, so the MDI resolution is the fresh one.
func measureCable(mods []*phyModule, waitRelink bool, done *atomic.Bool) (cableInfo, bool, error) {
var c cableInfo
var err error
@@ -643,9 +654,6 @@ func measureCable(mods []*phyModule, waitRelink bool, done *atomic.Bool) (cableI
return c, relinked, nil
}
// Owns the cable picture after bringup: a reset re-measures — the cable under
// a reset is usually a different cable — and the counters re-baseline only
// once the diag's own link blip is over, so it is never charged to the run.
type cableDiag struct {
mods []*phyModule
completed chan struct{}
@@ -665,9 +673,6 @@ func (c *cableDiag) snapshot() (cableInfo, bool) {
return c.info, c.running
}
// Runs the re-measure off the display loop, so the panel keeps drawing while
// the diag and the relink take their seconds. Reports whether one started; a
// press while one is in flight is absorbed.
func (c *cableDiag) kick(done *atomic.Bool) bool {
c.mu.Lock()
if c.running {
@@ -724,46 +729,94 @@ func verdictString(r ecdResult) string {
r.metres[0], r.metres[1], r.metres[2], r.metres[3])
}
// Runs before any socket opens: forcing EEE off retrains the link and the ECD
// blips it, and both belong before the baselines rather than under them.
func moduleBringup(names [2]string) ([]*phyModule, cableInfo, []checkResult) {
var out []checkResult
var cable cableInfo
func openModules(names [2]string) ([]*phyModule, [2]string, error) {
mods := make([]*phyModule, 0, 2)
fail := func(item string, err error) ([]*phyModule, cableInfo, []checkResult) {
return nil, cable, append(out, checkResult{item: item, err: err})
}
for _, name := range names {
res := checkResult{item: name + " module"}
var idents [2]string
for i, name := range names {
b, err := openBCM(name)
if err != nil {
return fail(res.item, err)
return nil, idents, err
}
res.state, err = b.identify()
idents[i], err = b.identify()
if err != nil {
return nil, idents, err
}
mods = append(mods, &phyModule{bcm: b})
}
return mods, idents, nil
}
func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
var out []checkResult
fail := func(item string, err error) []checkResult {
return append(out, checkResult{item: item, err: err})
}
retrained := false
for i, m := range mods {
changed := false
res := checkResult{item: names[i] + " eee"}
mode, err := m.bcm.eeeMode()
if err != nil {
return fail(res.item, err)
}
if mode == 0 {
res.state = "off"
} else {
if err := m.bcm.forceEEEOff(); err != nil {
return fail(res.item, err)
}
res.fixed = true
res.state = fmt.Sprintf("was %#04x, forced off", mode)
changed = true
}
out = append(out, res)
mods = append(mods, &phyModule{bcm: b})
}
for i, m := range mods {
res := checkResult{item: names[i] + " eee", fixed: true}
if err := m.bcm.forceEEEOff(); err != nil {
res = checkResult{item: names[i] + " jumbo"}
on, size, err := m.bcm.jumboState()
if err != nil {
return fail(res.item, err)
}
if err := m.bcm.restartAN(); err != nil {
return fail(res.item, err)
if on {
res.state = "on, " + size
} else {
if err := m.bcm.forceJumbo(); err != nil {
return fail(res.item, err)
}
res.fixed = true
res.state = "was off, forced on"
changed = true
}
res.state = "forced off, retraining"
out = append(out, res)
if changed {
if err := m.bcm.restartAN(); err != nil {
return fail(names[i]+" retrain", err)
}
retrained = true
}
}
res := checkResult{item: "link retrain"}
took, up, err := waitLink(mods, nil)
if err != nil {
return fail(res.item, err)
res := checkResult{item: "link"}
var took time.Duration
var up bool
var err error
if retrained {
res.item = "link retrain"
took, up, err = waitLink(mods, nil)
if err != nil {
return fail(res.item, err)
}
} else {
up = true
for _, m := range mods {
v, err := m.bcm.linkUp()
if err != nil {
return fail(res.item, err)
}
up = up && v
}
}
if up {
var adv [2]string
@@ -777,32 +830,18 @@ func moduleBringup(names [2]string) ([]*phyModule, cableInfo, []checkResult) {
res.err = fmt.Errorf("%s still advertises EEE %#04x", names[i], v)
}
}
res.state = fmt.Sprintf("up in %.1fs, eee advert %s/%s", took.Seconds(), adv[0], adv[1])
if retrained {
res.state = fmt.Sprintf("up in %.1fs, eee advert %s/%s", took.Seconds(), adv[0], adv[1])
} else {
res.state = fmt.Sprintf("up, eee advert %s/%s", adv[0], adv[1])
}
} else {
res.state = "no link (cable unplugged?)"
}
out = append(out, res)
if res.err != nil {
return nil, cable, out
}
res = checkResult{item: "cable diag"}
cable, relinked, err := measureCable(mods, up, nil)
if err != nil {
return fail(res.item, err)
}
res.state = verdictString(cable.ecd)
if up && !relinked {
res.err = fmt.Errorf("link did not return after cable diag")
}
out = append(out, res)
for i := range mods {
out = append(out, checkResult{
item: names[i] + " pair map",
state: mapString(cable.maps[i]),
})
}
return mods, cable, out
return append(out, res)
}
func cableLine(c cableInfo) string {
return fmt.Sprintf("%s; map %s / %s",
verdictString(c.ecd), mapString(c.maps[0]), mapString(c.maps[1]))
}