From bba1fa5e02bdc4ac10b7180233ddc4b01c403ba4 Mon Sep 17 00:00:00 2001 From: flamingcow Date: Thu, 13 Aug 2026 08:04:26 -0700 Subject: [PATCH] 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 --- counters.go | 10 +- docs/modules/fs/README.md | 9 +- docs/state.md | 2 +- init.go | 1 - main.go | 63 ++++++++----- phy.go | 193 +++++++++++++++++++++++--------------- phy_test.go | 7 -- render.go | 4 - system.go | 34 +++++++ ts.go | 6 +- ui.go | 5 - ui_test.go | 2 - 12 files changed, 201 insertions(+), 135 deletions(-) diff --git a/counters.go b/counters.go index d2d56df..2ccfb26 100644 --- a/counters.go +++ b/counters.go @@ -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", diff --git a/docs/modules/fs/README.md b/docs/modules/fs/README.md index 45819d9..a4c8591 100644 --- a/docs/modules/fs/README.md +++ b/docs/modules/fs/README.md @@ -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 diff --git a/docs/state.md b/docs/state.md index b7eaa39..1f1f157 100644 --- a/docs/state.md +++ b/docs/state.md @@ -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 diff --git a/init.go b/init.go index b75fb5d..bc4e68e 100644 --- a/init.go +++ b/init.go @@ -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. diff --git a/main.go b/main.go index 3cdaaac..5205922 100644 --- a/main.go +++ b/main.go @@ -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() diff --git a/phy.go b/phy.go index 811e70b..5ea04d1 100644 --- a/phy.go +++ b/phy.go @@ -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])) } diff --git a/phy_test.go b/phy_test.go index b25ff7e..9ad4794 100644 --- a/phy_test.go +++ b/phy_test.go @@ -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}, diff --git a/render.go b/render.go index 241f10b..052da1b 100644 --- a/render.go +++ b/render.go @@ -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) diff --git a/system.go b/system.go index 390d4c3..b0ac2cb 100644 --- a/system.go +++ b/system.go @@ -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 diff --git a/ts.go b/ts.go index 22ab78a..e8c9375 100644 --- a/ts.go +++ b/ts.go @@ -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 { diff --git a/ui.go b/ui.go index a421f27..9e61e52 100644 --- a/ui.go +++ b/ui.go @@ -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 { diff --git a/ui_test.go b/ui_test.go index 2fdc2df..e0bfa26 100644 --- a/ui_test.go +++ b/ui_test.go @@ -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 {