BCM module diagnostics replace timestamp probes: bringup forces EEE off and runs ECD (re-run on every reset, re-baselining past its blip), 1 Hz SNR margin + corrected counters on panel and console; X520 bench divergences bypassed and tracked in open-questions

This commit is contained in:
flamingcow
2026-08-13 07:10:39 -07:00
parent a00f2216b7
commit 979dc7a772
18 changed files with 1240 additions and 416 deletions
+94 -36
View File
@@ -17,9 +17,22 @@ var (
uiDim = rgb{0x9a, 0xa2, 0xac}
uiCyan = rgb{0x5c, 0xc8, 0xe0}
uiGreen = rgb{0x6c, 0xdc, 0x86}
uiAmber = rgb{0xe8, 0xc4, 0x52}
uiRed = rgb{0xf0, 0x6b, 0x6b}
)
func classColor(c int) rgb {
switch c {
case clsGood:
return uiGreen
case clsWarn:
return uiAmber
case clsBad:
return uiRed
}
return uiDim
}
// Distances between ink, since layout measures a line from the top of a digit
// to the baseline rather than across a cell with accent and descender slack in
// it. Values carried over from spacing cells will look too small here.
@@ -48,7 +61,7 @@ const (
chipBorder = 2
gridCols = 2
chipPadY = spaceGroup
chipPadY = step * 3
chipGap = spaceTight
statRowGap = spaceRow
)
@@ -92,9 +105,9 @@ func newDisplay() (*display, error) {
bold bool
size float64
}{
{&d.big, true, 40},
{&d.grid, false, 34},
{&d.gridB, true, 34},
{&d.big, true, 36},
{&d.grid, false, 30},
{&d.gridB, true, 30},
} {
face, err := loadFace(spec.bold, spec.size)
if err != nil {
@@ -108,22 +121,29 @@ func newDisplay() (*display, error) {
return nil, fmt.Errorf("grid faces disagree on cell width: %d vs %d",
d.grid.cellW, d.gridB.cellW)
}
now := []int{d.statsH(d.big, 2), d.chipsH()}
since := []int{d.statsH(d.gridB, 4), d.countsH(), btnH}
if err := d.layout(fb.w, fb.h); err != nil {
fb.close()
return nil, err
}
return d, nil
}
func (d *display) layout(w, h int) error {
now := []int{d.statsH(d.big, 3), d.chipsH()}
since := []int{d.statsH(d.gridB, 4), d.statsH(d.gridB, 1), d.countsH(), btnH}
// One gap for both panels, and vertically the frame is the border alone.
// Insetting by uiPad as well would add it to the gaps at a panel's ends but
// not to the ones between blocks, which is not equal spacing however evenly
// the remainder is divided.
gaps := len(now) + len(since) + 2
spare := fb.h - 2*uiMargin - blockGap - 4*uiBorder - sum(now) - sum(since)
spare := h - 2*uiMargin - blockGap - 4*uiBorder - sum(now) - sum(since)
if spare < 0 {
fb.close()
return nil, fmt.Errorf("panel content is %dpx taller than the screen", -spare)
return fmt.Errorf("panel content is %dpx taller than the screen", -spare)
}
gap := spare / gaps
inner := fb.w - 2*uiMargin
inner := w - 2*uiMargin
nowH := 2*uiBorder + sum(now) + (len(now)+1)*gap
sinceH := 2*uiBorder + sum(since) + (len(since)+1)*gap
d.nowPanel = rect{uiMargin, uiMargin, inner, nowH}
@@ -137,8 +157,8 @@ func newDisplay() (*display, error) {
w: btnW,
h: btnH,
}
d.versionSpot = rect{fb.w - versionSpotSide, 0, versionSpotSide, versionSpotSide}
return d, nil
d.versionSpot = rect{w - versionSpotSide, 0, versionSpotSide, versionSpotSide}
return nil
}
func sum(hs []int) int {
@@ -270,10 +290,10 @@ func (d *display) statsH(vf *textFace, n int) int {
}
// A last row that does not fill the grid is centred.
func gridCell(i, n, x, w int) (cx, cw int) {
cw = (w - (gridCols-1)*chipGap) / gridCols
inRow := min(n-(i/gridCols)*gridCols, gridCols)
cx = x + (w-(inRow*cw+(inRow-1)*chipGap))/2 + (i%gridCols)*(cw+chipGap)
func gridCell(i, n, cols, x, w int) (cx, cw int) {
cw = (w - (cols-1)*chipGap) / cols
inRow := min(n-(i/cols)*cols, cols)
cx = x + (w-(inRow*cw+(inRow-1)*chipGap))/2 + (i%cols)*(cw+chipGap)
return cx, cw
}
@@ -284,7 +304,7 @@ func (d *display) stats(vf *textFace, x, w, y int, cells []statCell) int {
if c.value == "" {
continue
}
cx, cw := gridCell(i, len(cells), x, w)
cx, cw := gridCell(i, len(cells), gridCols, x, w)
cy := y + (i/gridCols)*(d.statPairH(vf)+statRowGap)
ly := d.centerIn(vf, cx, cw, cy, c.value, c.col)
d.centerIn(d.grid, cx, cw, ly, c.label, uiDim)
@@ -306,9 +326,10 @@ func (d *display) chipH() int { return d.grid.lineH + 2*chipPadY }
func (d *display) countChipH() int { return d.chipH() + d.gridB.lineH + pairGap }
// The error chips plus the noise cable's, which shares their row grid.
// The error chips plus corrected and the noise cable's, which share their
// row grid.
func (d *display) chipsH() int {
return gridRows(len(errRows)+1)*(d.chipH()+chipGap) - chipGap
return gridRows(len(errRows)+2)*(d.chipH()+chipGap) - chipGap
}
func (d *display) countsH() int {
@@ -317,9 +338,9 @@ func (d *display) countsH() int {
// Outlined by drawing the border colour and sinking a smaller well of
// background into it, so both curves get the same antialiasing.
func (d *display) chipAt(i, n, x, w, y, h int, c rgb) (int, int, int) {
cx, cw := gridCell(i, n, x, w)
cy := y + (i/gridCols)*(h+chipGap)
func (d *display) chipAt(i, n, cols, x, w, y, h int, c rgb) (int, int, int) {
cx, cw := gridCell(i, n, cols, x, w)
cy := y + (i/cols)*(h+chipGap)
d.fb.roundRect(cx, cy, cw, h, chipRadius, c)
d.fb.roundRect(cx+chipBorder, cy+chipBorder,
@@ -330,15 +351,22 @@ func (d *display) chipAt(i, n, x, w, y, h int, c rgb) (int, int, int) {
// Whether rather than how many: over a window this short a count changes faster
// than it can be read. The noise chip rides along at the end, presence rather
// than health: red is the cable missing, not the cable failing.
func (d *display) errChips(x, w, y int, e errs, noiseMissing uint64) int {
n := len(errRows) + 1
func (d *display) errChips(x, w, y int, e errs, recentCorrected, noiseMissing uint64) int {
n := len(errRows) + 2
for i, r := range errRows {
c := errColor(r.get(e))
cx, cw, cy := d.chipAt(i, n, x, w, y, d.chipH(), c)
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)
}
c := errColor(noiseMissing)
cx, cw, cy := d.chipAt(len(errRows), n, x, w, y, d.chipH(), c)
// Amber rather than red: the phy absorbed these before they cost a frame.
c := uiGreen
if recentCorrected > 0 {
c = uiAmber
}
cx, cw, cy := d.chipAt(len(errRows), n, gridCols, x, w, y, d.chipH(), c)
d.centerIn(d.grid, cx, cw, cy+chipPadY, "corrected", c)
c = errColor(noiseMissing)
cx, cw, cy = d.chipAt(len(errRows)+1, n, gridCols, x, w, y, d.chipH(), c)
d.centerIn(d.grid, cx, cw, cy+chipPadY, "noise", c)
return y + d.chipsH()
}
@@ -347,16 +375,26 @@ func (d *display) errCounts(x, w, y int, e errs) int {
for i, r := range errRows {
n := r.get(e)
c := errColor(n)
cx, cw, cy := d.chipAt(i, len(errRows), x, w, y, d.countChipH(), c)
cx, cw, cy := d.chipAt(i, len(errRows), gridCols, x, w, y, d.countChipH(), c)
ty := d.centerIn(d.gridB, cx, cw, cy+chipPadY, scaleCount(n), c)
d.centerIn(d.grid, cx, cw, ty, r.label, c)
}
return y + d.countsH()
}
func (d *display) panel(p rect, e errs) (int, int) {
// 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 {
col = classColor(phy.metresClass)
}
return statCell{phy.metres, "m", col}
}
func (d *display) panel(p rect, bad bool) (int, int) {
fill, edge := uiOKFill, uiOKEdge
if e.total() > 0 {
if bad {
fill, edge = uiErrFil, uiErrEdg
}
d.fb.rect(p.x, p.y, p.w, p.h, edge)
@@ -374,25 +412,45 @@ func errColor(n uint64) rgb {
return uiRed
}
func (d *display) render(v view, elapsed time.Duration, cable string, noiseMissing uint64) error {
func snrStat(phy phyDisplay) statCell {
if !phy.haveSNR {
return statCell{"-", "db margin", uiDim}
}
return statCell{fmt.Sprintf("%+.1f", phy.worstMargin), "db margin",
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 {
col = uiAmber
}
return statCell{scaleCount(v), "corrected", col}
}
func (d *display) render(v view, elapsed time.Duration, phy phyDisplay, noiseMissing uint64) error {
fb := d.fb
fb.fill(uiBg)
x, w := d.panel(d.nowPanel, v.window)
x, w := d.panel(d.nowPanel, v.window.total() > 0)
d.stats(d.big, x, w, d.nowYs[0], []statCell{
{scaleSI(v.rxGbps * 1e9), "bits/s", uiFg},
{scaleSI(v.rxPPS), "packets/s", uiFg},
snrStat(phy),
})
d.errChips(x, w, d.nowYs[1], v.window, noiseMissing)
d.errChips(x, w, d.nowYs[1], v.window, phy.recent, noiseMissing)
x, w = d.panel(d.sincePanel, v.since)
x, w = d.panel(d.sincePanel, v.since.total() > 0 || phy.metresClass == clsBad)
d.stats(d.gridB, x, w, d.sinceYs[0], []statCell{
{scaleTime(elapsed), "elapsed", uiFg},
{scaleCount(v.rxFrames), "packets", uiFg},
{scaleCount(v.rxBytes), "bytes", uiFg},
{cable, "m", uiFg},
correctedStat(phy.corrected),
})
d.errCounts(x, w, d.sinceYs[1], v.since)
d.stats(d.gridB, x, w, d.sinceYs[1], []statCell{metresStat(phy)})
d.errCounts(x, w, d.sinceYs[2], v.since)
d.drawResetButton()
return fb.flush()