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:
@@ -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]))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user