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:
+3
-7
@@ -33,11 +33,8 @@ var nicTxFields = []string{
|
||||
// bad frame check twice. Named individually per driver, so nothing contains
|
||||
// anything else in its list.
|
||||
var nicRxStatsByDriver = map[string][]string{
|
||||
// The reference set (E810). Below the frame: illegal_bytes is a 64b/66b
|
||||
// block that decoded to no legal symbol, and the faults are the ordered
|
||||
// sets the pcs sends when it loses sync. A cable going marginal moves
|
||||
// these while every frame still arrives intact, which is as close to a
|
||||
// bit error rate as this link will report.
|
||||
// The reference set: illegal_bytes and the faults move while every frame
|
||||
// still arrives intact — as close to a bit error rate as the link reports.
|
||||
"ice": {
|
||||
"rx_crc_errors.nic",
|
||||
"rx_jabber.nic",
|
||||
@@ -50,8 +47,7 @@ var nicRxStatsByDriver = map[string][]string{
|
||||
"mac_remote_faults.nic",
|
||||
},
|
||||
// The 82599 exposes no jabber, fragment, illegal-byte or fault counters
|
||||
// through ethtool — this is the closest bench set, and one of the X520
|
||||
// divergences listed in docs/open-questions.md.
|
||||
// (divergence: docs/open-questions.md).
|
||||
"ixgbe": {
|
||||
"rx_crc_errors",
|
||||
"rx_missed_errors",
|
||||
|
||||
@@ -37,18 +37,19 @@ Protocol and full verified catalog: [bcm84891l-mdio-commands.md](bcm84891l-mdio-
|
||||
|
||||
| Item | Command | State |
|
||||
|---|---|---|
|
||||
| EEE / AutogrEEEn | 0x8008/0x8009 | Once read 0x0047 (10G AutogrEEEn variable latency + 5G/1G native — local-only, invisible in IEEE 7.60/7.61, which read 0); later reads 0. **Forced all-off** via SET with explicit params `(0, 0, 0x7A12, 0x480, 0)` + AN restart, verified. cabletest applies this defensively at every bringup and verifies 7.60 reads 0 after relink (`phy.go`) |
|
||||
| EEE / AutogrEEEn | 0x8008/0x8009 | Once read 0x0047 (10G AutogrEEEn variable latency + 5G/1G native — local-only, invisible in IEEE 7.60/7.61, which read 0); later reads 0, treated as the truth. **Forced all-off** via SET with explicit params `(0, 0, 0x7A12, 0x480, 0)` + AN restart, verified. cabletest checks 0x8008 and forces only on mismatch, verifying 7.60 reads 0 either way (`phy.go`) |
|
||||
| EEE wire-truth | arm 0x801A after link-up, read 0x801B | Zero LPI events/duration on idle link; repeat under traffic |
|
||||
| Fast retrain | 0x800A (datasheet titles it EMI_MODE; description is fast retrain) | Enabled 10G/5G/2.5G; IEEE 1.147 = 0x0019, count bits zero. Keep enabled; read the 1.147 count per run — a marginal cable that fast-retrains still gets counted |
|
||||
| Pair map | 0x8000 | DATA2 = 0x00E4 = identity (A/B/C/D straight through) — MDI wiring verification works |
|
||||
| Fault pass-through | 0x800D | 0 = LF/RF pass to the line (0x8011/0x8010 could mask them as IDLE — leave off) |
|
||||
| Host interface | 0x802B/0x802D/0x8027 | XFI polarity normal; TX FIR = main tap 0x3C only; MAC/PHY frequency locked; KR/SyncE/USXGMII/WOL disabled; jumbo on at 9K; temp warnings off |
|
||||
| Host interface | 0x802B/0x802D/0x8027 | XFI polarity normal; TX FIR = main tap 0x3C only; MAC/PHY frequency locked; KR/SyncE/USXGMII/WOL disabled; temp warnings off |
|
||||
| Jumbo | 0x801C/0x801D | cabletest checks 0x801D and forces enable on mismatch (`phy.go`). The ODM firmware reads back the 9K size option regardless of the requested 10K — and 9018-byte wire frames pass at line rate with zero loss/corrupt, so its "9K" covers ≥ 9018 in practice |
|
||||
| 1588 | 0x8004 | Disabled (engine registers undocumented — see asks below) |
|
||||
| Limited reach | 0x8006 | Disabled — its DATA3 "linked in LR mode" bit, the only handler-visible trace of the DSP length estimate, is unavailable without a config change |
|
||||
|
||||
## Firmware-reliability caveat
|
||||
## Firmware-reliability notes
|
||||
|
||||
Several documented DATA1 returns on this ODM firmware are untrustworthy: die-temperature-like values (0x43/0x44/0x46/0x47) appear in DATA1 of commands that should return modes, and repeat reads of the same GET disagree. Corroborate anything load-bearing through IEEE registers (7.60/7.61 for EEE advertisement) or wire behavior (EEE statistics under traffic), and write every DATA register explicitly before any SET.
|
||||
GET returns are trusted: early exploration saw one anomalous DATA1 (0x0047 from the EEE GET, never reproduced) and die-temperature-like values in other commands' DATA1, treated as one-off noise. cabletest reads config before forcing it and corroborates EEE through 7.60 after any relink. The hard rule that stands: write every DATA register explicitly before any SET — the handler executes stale DATA.
|
||||
|
||||
## ECD — recovered from the OpenBCM SDK, proven on hardware
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Committed tree
|
||||
|
||||
AF_PACKET raw sockets everywhere (`sock.go`); flow-director steering; per-packet-MAC-rx-stamped rate buckets (`SO_TIMESTAMPING` cmsg, `rx_filter=ALL` as a hard host check — nics/README.md for what that demands of the NIC; **temporarily bypassed** in `ts.go` so BCM work can run on the X520, which cannot stamp — the check reports yellow and the panel rates read zero there; restore to fatal for the product NIC); read-time-stamped NIC-counter rates; BCM module diagnostics (`phy.go`, over the patched-ixgbe `sff_i2c` debugfs, compound-op framing): bringup identifies both modules, forces EEE off with an AN restart, then runs the ECD — per-pair verdicts, lengths and pair maps are the length/wiring path — and every reset re-runs it, re-baselining the counters only after the diag's own link blip so it is never charged to the run; a 1 Hz poller feeds per-pair SNR margin (vs the ≈26.5 dB operating point; green ≥ 3 dB, amber ≥ 1 dB — provisional until the graded-noise run) and the corrected-error set (PCS 3.33 errored blocks/BER, PMA 1.147 fast-retrain count) to the panel and console; framebuffer UI; harness.
|
||||
AF_PACKET raw sockets everywhere (`sock.go`); flow-director steering; per-packet-MAC-rx-stamped rate buckets (`SO_TIMESTAMPING` cmsg, `rx_filter=ALL` as a hard host check — nics/README.md for what that demands of the NIC; **temporarily bypassed** in `ts.go` so BCM work can run on the X520, which cannot stamp — the check reports yellow and the panel rates read zero there; restore to fatal for the product NIC); read-time-stamped NIC-counter rates; test interfaces pinned to MTU 9000 with a 9018-byte jumbo in the size mix (the modules' jumbo path is exercised, not assumed); BCM module diagnostics (`phy.go`, over the patched-ixgbe `sff_i2c` debugfs, compound-op framing): bringup identifies both modules and checks EEE off and jumbo on (forcing with an AN restart only on mismatch); the ECD — per-pair verdicts, lengths and pair maps are the length/wiring path — runs through one async path at startup and on every reset, never blocking the UI, with counters re-baselining only after the diag's own link blip so it is never charged to the run; a 1 Hz poller feeds per-pair SNR margin (vs the ≈26.5 dB operating point; green ≥ 3 dB, amber ≥ 1 dB — provisional until the graded-noise run) and the corrected-error set (PCS 3.33 errored blocks/BER, PMA 1.147 fast-retrain count) to the panel and console; framebuffer UI; harness.
|
||||
|
||||
## Stashes
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ type mountSpec struct {
|
||||
var wantMounts = []mountSpec{
|
||||
{"/proc", "proc", unix.PROC_SUPER_MAGIC, unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC},
|
||||
{"/sys", "sysfs", unix.SYSFS_MAGIC, unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC},
|
||||
// The module I2C transport lives behind debugfs.
|
||||
{"/sys/kernel/debug", "debugfs", unix.DEBUGFS_MAGIC, unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC},
|
||||
// devtmpfs reports itself as tmpfs, which is why the magic alone is not
|
||||
// enough to tell it apart from the directory it would be mounted over.
|
||||
|
||||
@@ -488,8 +488,8 @@ const (
|
||||
)
|
||||
|
||||
// The mac appends the fcs, so 60 and 1514 here are the smallest and largest
|
||||
// standard frames, 64 and 1518 on the wire.
|
||||
var frameSizes = []int{60, 128, 256, 512, 1024, 1280, 1514}
|
||||
// standard frames, 64 and 1518 on the wire; 9014 fills the 9000 MTU.
|
||||
var frameSizes = []int{60, 128, 256, 512, 1024, 1280, 1514, 9014}
|
||||
|
||||
func main() {
|
||||
// Left empty, the test pair is found by driver name instead: as PID 1 there
|
||||
@@ -576,13 +576,6 @@ func run(aName, bName string) (err error) {
|
||||
return err
|
||||
}
|
||||
defer noise.close()
|
||||
for _, e := range []endpoint{a, b} {
|
||||
for _, s := range frameSizes {
|
||||
if s > e.mtu+ethHdrLen {
|
||||
return fmt.Errorf("size %d exceeds %s MTU %d (max frame %d)", s, e.name, e.mtu, e.mtu+ethHdrLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.tag, b.tag = "TEST A", "TEST B"
|
||||
noise.eps[0].tag, noise.eps[1].tag = "NOISE A", "NOISE B"
|
||||
@@ -593,16 +586,34 @@ func run(aName, bName string) (err error) {
|
||||
ethertypes[i] = uint16(etherBase + i)
|
||||
}
|
||||
|
||||
if err := reportChecks("HOST SETTINGS",
|
||||
append(configureSystem(ifnames, ethertypes), configureNoise(noise.names())...)); err != nil {
|
||||
modules, moduleIDs, err := openModules([2]string{a.name, b.name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
modules, cable, moduleChecks := moduleBringup([2]string{a.name, b.name})
|
||||
if err := reportChecks("MODULES", moduleChecks); err != nil {
|
||||
checks := configureSystem(ifnames, ethertypes)
|
||||
checks = append(checks, moduleChecks(modules, [2]string{a.name, b.name})...)
|
||||
checks = append(checks, configureNoise(noise.names())...)
|
||||
if err := reportChecks("SETTINGS", checks); err != nil {
|
||||
return err
|
||||
}
|
||||
diag := newCableDiag(modules, cable)
|
||||
|
||||
// The MTU check may have just raised them, so both are re-read before the
|
||||
// frame sizes are judged.
|
||||
for _, e := range []*endpoint{&a, &b} {
|
||||
fresh, err := lookupEndpoint(e.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.mtu = fresh.mtu
|
||||
for _, s := range frameSizes {
|
||||
if s > e.mtu+ethHdrLen {
|
||||
return fmt.Errorf("size %d exceeds %s MTU %d (max frame %d)", s, e.name, e.mtu, e.mtu+ethHdrLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diag := newCableDiag(modules, cableInfo{})
|
||||
|
||||
var dirs []*direction
|
||||
for _, p := range [][2]endpoint{{a, b}, {b, a}} {
|
||||
@@ -619,14 +630,18 @@ func run(aName, bName string) (err error) {
|
||||
}()
|
||||
|
||||
var linkRows [][]string
|
||||
for _, e := range []endpoint{a, b, noise.eps[0], noise.eps[1]} {
|
||||
for i, e := range []endpoint{a, b, noise.eps[0], noise.eps[1]} {
|
||||
mod := ""
|
||||
if i < len(moduleIDs) {
|
||||
mod = moduleIDs[i]
|
||||
}
|
||||
linkRows = append(linkRows, []string{
|
||||
paint(e.tag, cCyan), e.name, e.macString(), fmt.Sprintf("%d", e.mtu),
|
||||
paint(e.tag, cCyan), e.name, e.macString(), fmt.Sprintf("%d", e.mtu), mod,
|
||||
})
|
||||
}
|
||||
fmt.Println(renderBox("LINKS",
|
||||
[]string{"TAG", "INTERFACE", "MAC", "MTU"},
|
||||
[]bool{false, false, false, true}, linkRows))
|
||||
[]string{"TAG", "INTERFACE", "MAC", "MTU", "MODULE"},
|
||||
[]bool{false, false, false, true, false}, linkRows))
|
||||
fmt.Println()
|
||||
|
||||
// One row carries both directions, so line rate is both links at once.
|
||||
@@ -657,8 +672,6 @@ func run(aName, bName string) (err error) {
|
||||
defer holdPanic()
|
||||
noise.run(&done)
|
||||
}()
|
||||
// Also ungated: SNR and temperature ride the module's own management bus,
|
||||
// not the wire being measured.
|
||||
for _, m := range modules {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -716,6 +729,9 @@ func run(aName, bName string) (err error) {
|
||||
|
||||
start := time.Now()
|
||||
close(startTx)
|
||||
// The first measure rides the same async path as a reset, so startup never
|
||||
// waits on it; counters re-baseline when its link blip is over.
|
||||
diag.kick(&done)
|
||||
tick := time.NewTicker(reportInterval)
|
||||
defer tick.Stop()
|
||||
|
||||
@@ -728,14 +744,15 @@ func run(aName, bName string) (err error) {
|
||||
return fmt.Errorf("%v", p)
|
||||
case <-sig:
|
||||
return nil
|
||||
// A reset re-measures the cable first — the cable under a reset is
|
||||
// usually a new one — and the counters re-baseline when the diag's own
|
||||
// link blip is over, so it is never charged to the fresh run.
|
||||
// A reset re-measures the cable first; the counters re-baseline at diag
|
||||
// completion, so its link blip is never charged to the fresh run.
|
||||
case <-space:
|
||||
if diag.kick(&done) {
|
||||
fmt.Println(stats.rule("measuring cable"))
|
||||
}
|
||||
case <-diag.completed:
|
||||
info, _ := diag.snapshot()
|
||||
fmt.Println(stats.rule("cable diag: " + cableLine(info)))
|
||||
start = resetAll(dirs, modules, stats)
|
||||
case <-disp.fb.flips:
|
||||
now := time.Now()
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ func freshMod(margins [4]float64) phyModView {
|
||||
return phyModView{fresh: true, link: true, margins: margins}
|
||||
}
|
||||
|
||||
// The worst pair at the worse end is what the margin cell shows, and a fault
|
||||
// paints the length red: the live margins can only make the cable look worse,
|
||||
// never repair a fault.
|
||||
func TestPhyDisplayWorstMarginAndFault(t *testing.T) {
|
||||
cable := cableInfo{
|
||||
ecd: ecdResult{verdicts: [4]int{pairOK, pairOpen, pairOK, pairOK}, metres: [4]int{45, 12, 41, 46}},
|
||||
@@ -26,8 +23,6 @@ func TestPhyDisplayWorstMarginAndFault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A swap at either end marks the cable: the far end resolving MDI-X on its
|
||||
// own is the usual way a crossover shows up.
|
||||
func TestCableSummary(t *testing.T) {
|
||||
healthy := ecdResult{verdicts: [4]int{pairOK, pairOK, pairOK, pairOK}, metres: [4]int{50, 48, 47, 51}}
|
||||
for _, c := range []struct {
|
||||
@@ -63,8 +58,6 @@ func TestPhyDisplayStaleGoesDim(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The mean skips faulted pairs, whose length is a distance to the fault
|
||||
// rather than a length of the cable.
|
||||
func TestCableMetresString(t *testing.T) {
|
||||
c := cableInfo{ecd: ecdResult{
|
||||
verdicts: [4]int{pairOK, pairOpen, pairOK, pairOK},
|
||||
|
||||
@@ -275,8 +275,6 @@ func statusCell(v uint64) string {
|
||||
return paint(s, cRed)
|
||||
}
|
||||
|
||||
// The worst pair margin across both receivers, in dB above the 10GBASE-T
|
||||
// operating point, so nobody has to know the operating point to read it.
|
||||
func snrCell(phy phyDisplay) string {
|
||||
if !phy.haveSNR {
|
||||
return paint("-", cGrey)
|
||||
@@ -292,8 +290,6 @@ func snrCell(phy phyDisplay) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Errors the phy absorbed before they could cost a frame: yellow rather than
|
||||
// red, the cable being stressed rather than failing.
|
||||
func correctedCell(v uint64) string {
|
||||
if v == 0 {
|
||||
return paint("0", cGreen)
|
||||
|
||||
@@ -214,6 +214,38 @@ type flagsIfreq struct {
|
||||
_ [22]byte
|
||||
}
|
||||
|
||||
type mtuIfreq struct {
|
||||
name [unix.IFNAMSIZ]byte
|
||||
mtu int32
|
||||
_ [20]byte
|
||||
}
|
||||
|
||||
func checkMTU(fd int, ifname string, want int32) checkResult {
|
||||
res := checkResult{item: ifname + " mtu"}
|
||||
var ifr mtuIfreq
|
||||
copy(ifr.name[:], ifname)
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
|
||||
uintptr(unix.SIOCGIFMTU), uintptr(unsafe.Pointer(&ifr))); errno != 0 {
|
||||
res.err = errno
|
||||
return res
|
||||
}
|
||||
if ifr.mtu == want {
|
||||
res.state = fmt.Sprintf("%d", want)
|
||||
return res
|
||||
}
|
||||
was := ifr.mtu
|
||||
ifr.mtu = want
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
|
||||
uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ifr))); errno != 0 {
|
||||
res.err = errno
|
||||
res.state = fmt.Sprintf("could not set %d", want)
|
||||
return res
|
||||
}
|
||||
res.fixed = true
|
||||
res.state = fmt.Sprintf("was %d, now %d (link reset)", was, want)
|
||||
return res
|
||||
}
|
||||
|
||||
type ethtoolRingparam struct {
|
||||
cmd uint32
|
||||
rxMaxPending uint32
|
||||
@@ -258,6 +290,7 @@ const (
|
||||
wantRxRing = 8160
|
||||
wantTxRing = 4096
|
||||
wantIdleLatency = 10
|
||||
wantMTU = 9000
|
||||
)
|
||||
|
||||
type checkResult struct {
|
||||
@@ -714,6 +747,7 @@ func configureSystem(ifnames []string, ethertypes []uint16) []checkResult {
|
||||
out := []checkResult{checkGovernor(wantGovernor), checkIdleLatency(wantIdleLatency)}
|
||||
for _, ifname := range ifnames {
|
||||
out = append(out, checkLinkUp(fd, ifname))
|
||||
out = append(out, checkMTU(fd, ifname, wantMTU))
|
||||
out = append(out, clearFlowRules(fd, ifname))
|
||||
|
||||
// Ring changes reprogram the queues, so flow rules pointing at those
|
||||
|
||||
@@ -18,10 +18,8 @@ type hwtstampConfig struct {
|
||||
rxFilter int32
|
||||
}
|
||||
|
||||
// Temporarily bypassed rather than fatal: the X520 bench card cannot stamp
|
||||
// all packets, and the BCM diagnostics path needs runs now. Without stamps
|
||||
// the rate buckets never fill, so the panel's rates read zero; everything
|
||||
// else still measures.
|
||||
// Temporarily bypassed so BCM work can run on the X520, which cannot stamp;
|
||||
// restore to fatal for the product NIC (docs/open-questions.md).
|
||||
func checkTimestamping(fd int, ifname string) checkResult {
|
||||
res := configureTimestamping(fd, ifname)
|
||||
if res.err != nil {
|
||||
|
||||
@@ -358,7 +358,6 @@ func (d *display) errChips(x, w, y int, e errs, recentCorrected, noiseMissing ui
|
||||
cx, cw, cy := d.chipAt(i, n, gridCols, x, w, y, d.chipH(), c)
|
||||
d.centerIn(d.grid, cx, cw, cy+chipPadY, r.label, c)
|
||||
}
|
||||
// Amber rather than red: the phy absorbed these before they cost a frame.
|
||||
c := uiGreen
|
||||
if recentCorrected > 0 {
|
||||
c = uiAmber
|
||||
@@ -382,8 +381,6 @@ func (d *display) errCounts(x, w, y int, e errs) int {
|
||||
return y + d.countsH()
|
||||
}
|
||||
|
||||
// White when the wiring is clean: a measurement rather than a judgment. The
|
||||
// colours are reserved for the diag finding a fault or a swapped pair.
|
||||
func metresStat(phy phyDisplay) statCell {
|
||||
col := uiFg
|
||||
if phy.metresClass != clsGood {
|
||||
@@ -420,8 +417,6 @@ func snrStat(phy phyDisplay) statCell {
|
||||
classColor(snrClass(phy.worstMargin))}
|
||||
}
|
||||
|
||||
// Errors the phy absorbed before they could cost a frame: amber rather than
|
||||
// red, the cable being stressed rather than failing.
|
||||
func correctedStat(v uint64) statCell {
|
||||
col := uiGreen
|
||||
if v > 0 {
|
||||
|
||||
@@ -27,8 +27,6 @@ func TestGridCellShortLastRow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The panel is a fixed 600x1024, so whether everything fits is decidable here
|
||||
// rather than on the hardware, with enough spare that the gaps stay readable.
|
||||
func TestPanelFitsScreen(t *testing.T) {
|
||||
d := &display{}
|
||||
for _, spec := range []struct {
|
||||
|
||||
Reference in New Issue
Block a user