Fix sff panic deadlock, share the evdev probe, assorted review cleanups
This commit is contained in:
+2
-2
@@ -26,10 +26,10 @@ type direction struct {
|
||||
// aggregate.
|
||||
masked *atomic.Bool
|
||||
|
||||
// Guards everything the sampler touches. Collection consumes self-clearing
|
||||
// sources, so a second concurrent reader would see a gap.
|
||||
iface int
|
||||
|
||||
// Guards everything the sampler touches. Collection consumes self-clearing
|
||||
// sources, so a second concurrent reader would see a gap.
|
||||
mu sync.Mutex
|
||||
prev sample
|
||||
nic *nicCounters
|
||||
|
||||
+21
-3
@@ -46,6 +46,7 @@ func main() {
|
||||
}
|
||||
|
||||
restoreTerm := rawKeys()
|
||||
go relayStdin()
|
||||
|
||||
var err error
|
||||
if *fuzzN > 0 {
|
||||
@@ -84,6 +85,26 @@ func rawKeys() func() {
|
||||
return func() { unix.IoctlSetTermios(fd, unix.TCSETS, &saved) }
|
||||
}
|
||||
|
||||
// The child's own process group makes the tty off limits to it (reading from
|
||||
// the background raises SIGTTIN), so the harness reads and forwards to the
|
||||
// run that currently owns the console. One reader for the life of the
|
||||
// process: a copier per run would pile up under fuzz, each stealing
|
||||
// keystrokes for a run already gone.
|
||||
func relayStdin() {
|
||||
buf := make([]byte, 64)
|
||||
for {
|
||||
n, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
consoleMu.Lock()
|
||||
if console != nil {
|
||||
console.Write(buf[:n])
|
||||
}
|
||||
consoleMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func fuzz(n int, runFor time.Duration, args []string) error {
|
||||
failed := 0
|
||||
for i := 1; i <= n; i++ {
|
||||
@@ -162,13 +183,10 @@ func run(runFor time.Duration, at string, out io.Writer, args []string) error {
|
||||
// killed when go run dies, and an orphan holding the wire poisons every
|
||||
// measurement after it.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
// The child's own process group makes the tty off limits to it (reading
|
||||
// from the background raises SIGTTIN), so the harness reads and forwards.
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(stdin, os.Stdin)
|
||||
consoleMu.Lock()
|
||||
console = stdin
|
||||
consoleMu.Unlock()
|
||||
|
||||
+8
-2
@@ -12,8 +12,10 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"g.fc.run/theater/cabletest/internal/drm"
|
||||
"g.fc.run/theater/cabletest/internal/evdev"
|
||||
)
|
||||
|
||||
const streamAddr = ":8080"
|
||||
@@ -115,7 +117,7 @@ func serveStream() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if touchscreen, err = openTouchscreen(); err != nil {
|
||||
if touchscreen, err = evdev.FindTouchscreen(os.O_RDWR); err != nil {
|
||||
return err
|
||||
}
|
||||
ln, err := net.Listen("tcp", streamAddr)
|
||||
@@ -158,7 +160,10 @@ func keys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Between runs the crtc scans out the console framebuffer, which is not a
|
||||
// dumb buffer and fails to map on every frame; each error reads once, not at
|
||||
// frame rate.
|
||||
// frame rate. The pause paces the retry, since a failure ahead of the vblank
|
||||
// wait would otherwise spin hot.
|
||||
const captureErrPause = 100 * time.Millisecond
|
||||
|
||||
func captureLoop(g *grabber) {
|
||||
lastErr := ""
|
||||
for {
|
||||
@@ -168,6 +173,7 @@ func captureLoop(g *grabber) {
|
||||
lastErr = err.Error()
|
||||
fmt.Fprintf(os.Stderr, "capture: %v\n", err)
|
||||
}
|
||||
time.Sleep(captureErrPause)
|
||||
continue
|
||||
}
|
||||
lastErr = ""
|
||||
|
||||
+16
-98
@@ -2,115 +2,33 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"g.fc.run/theater/cabletest/internal/evdev"
|
||||
)
|
||||
|
||||
const (
|
||||
evSyn = 0x00
|
||||
evKey = 0x01
|
||||
evAbs = 0x03
|
||||
btnTouch = 0x14a
|
||||
absX = 0x00
|
||||
absY = 0x01
|
||||
|
||||
inputPropDirect = 0x01
|
||||
sizeofInputEvent = 24
|
||||
)
|
||||
|
||||
func evIoc(nr, size uintptr) uintptr {
|
||||
const iocRead = 2
|
||||
return iocRead<<30 | size<<16 | 'E'<<8 | nr
|
||||
}
|
||||
|
||||
type touchDev struct {
|
||||
f *os.File
|
||||
minX, maxX, minY, maxY int32
|
||||
}
|
||||
|
||||
var touchscreen *touchDev
|
||||
|
||||
// Picks the direct-input device that reports absolute X and Y — the same
|
||||
// probe cabletest runs, so both ends name the same hardware. Events written
|
||||
// to the node are fed into the input core as if the hardware produced them,
|
||||
// so cabletest sees a finger without carrying any injection code.
|
||||
func openTouchscreen() (*touchDev, error) {
|
||||
paths, err := filepath.Glob("/dev/input/event*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range paths {
|
||||
f, err := os.OpenFile(p, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var props, absBits uint64
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x09, 8), uintptr(unsafe.Pointer(&props))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x20+evAbs, 8), uintptr(unsafe.Pointer(&absBits))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if props&(1<<inputPropDirect) == 0 ||
|
||||
absBits&(1<<absX) == 0 || absBits&(1<<absY) == 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
t := &touchDev{f: f}
|
||||
if t.minX, t.maxX, err = axisRange(f, absX); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s x range: %w", p, err)
|
||||
}
|
||||
if t.minY, t.maxY, err = axisRange(f, absY); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s y range: %w", p, err)
|
||||
}
|
||||
if t.maxX <= t.minX || t.maxY <= t.minY {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s: degenerate axes x %d..%d y %d..%d",
|
||||
p, t.minX, t.maxX, t.minY, t.maxY)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no touchscreen found among /dev/input/event*")
|
||||
}
|
||||
|
||||
func axisRange(f *os.File, axis uintptr) (min, max int32, err error) {
|
||||
var info [6]int32
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x40+axis, 24), uintptr(unsafe.Pointer(&info[0]))); errno != 0 {
|
||||
return 0, 0, errno
|
||||
}
|
||||
return info[1], info[2], nil
|
||||
}
|
||||
var touchscreen *evdev.Touchscreen
|
||||
|
||||
type inputEvent struct {
|
||||
typ, code uint16
|
||||
val int32
|
||||
}
|
||||
|
||||
// Timestamps are left zero: evdev stamps injected events at delivery.
|
||||
func (t *touchDev) send(evs []inputEvent) error {
|
||||
buf := make([]byte, len(evs)*sizeofInputEvent)
|
||||
// Events written to the node are fed into the input core as if the hardware
|
||||
// produced them, so cabletest sees a finger without carrying any injection
|
||||
// code. Timestamps are left zero: evdev stamps injected events at delivery.
|
||||
func send(t *evdev.Touchscreen, evs []inputEvent) error {
|
||||
buf := make([]byte, len(evs)*evdev.EventSize)
|
||||
for i, e := range evs {
|
||||
o := i * sizeofInputEvent
|
||||
o := i * evdev.EventSize
|
||||
binary.NativeEndian.PutUint16(buf[o+16:], e.typ)
|
||||
binary.NativeEndian.PutUint16(buf[o+18:], e.code)
|
||||
binary.NativeEndian.PutUint32(buf[o+20:], uint32(e.val))
|
||||
}
|
||||
_, err := t.f.Write(buf)
|
||||
_, err := t.F.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -124,7 +42,7 @@ func touch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
f := strings.Fields(string(body))
|
||||
if len(f) == 1 && f[0] == "up" {
|
||||
if err := touchscreen.send([]inputEvent{{evKey, btnTouch, 0}, {evSyn, 0, 0}}); err != nil {
|
||||
if err := send(touchscreen, []inputEvent{{evdev.Key, evdev.BtnTouch, 0}, {evdev.Syn, 0, 0}}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
@@ -150,14 +68,14 @@ func touch(w http.ResponseWriter, r *http.Request) {
|
||||
cy = min(max(cy, 0), k.pw-1)
|
||||
xl, yl := cy, k.ph-1-cx
|
||||
t := touchscreen
|
||||
rawX := t.minX + int32(int64(xl)*int64(t.maxX-t.minX)/int64(k.pw-1))
|
||||
rawY := t.minY + int32(int64(yl)*int64(t.maxY-t.minY)/int64(k.ph-1))
|
||||
evs := []inputEvent{{evAbs, absX, rawX}, {evAbs, absY, rawY}}
|
||||
rawX := t.MinX + int32(int64(xl)*int64(t.MaxX-t.MinX)/int64(k.pw-1))
|
||||
rawY := t.MinY + int32(int64(yl)*int64(t.MaxY-t.MinY)/int64(k.ph-1))
|
||||
evs := []inputEvent{{evdev.Abs, evdev.AbsX, rawX}, {evdev.Abs, evdev.AbsY, rawY}}
|
||||
if f[0] == "down" {
|
||||
evs = append(evs, inputEvent{evKey, btnTouch, 1})
|
||||
evs = append(evs, inputEvent{evdev.Key, evdev.BtnTouch, 1})
|
||||
}
|
||||
evs = append(evs, inputEvent{evSyn, 0, 0})
|
||||
if err := t.send(evs); err != nil {
|
||||
evs = append(evs, inputEvent{evdev.Syn, 0, 0})
|
||||
if err := send(touchscreen, evs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ func (h *history) addFaults(now time.Time, e errs) {
|
||||
|
||||
func (h *history) addCorrected(now time.Time, d uint64) {
|
||||
h.mu.Lock()
|
||||
h.slot(int64(now.Sub(h.start)/histSlotSpan)).corrected += d
|
||||
h.slot(int64(now.Sub(h.start) / histSlotSpan)).corrected += d
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"g.fc.run/theater/cabletest/internal/evdev"
|
||||
)
|
||||
|
||||
// Running as PID 1 there is nothing underneath us: sysfs and devtmpfs are what
|
||||
@@ -129,9 +131,9 @@ func waitForTouchscreen() checkResult {
|
||||
res := checkResult{item: "touchscreen"}
|
||||
start := time.Now()
|
||||
for {
|
||||
f, err := findTouchscreen()
|
||||
ts, err := evdev.FindTouchscreen(os.O_RDONLY)
|
||||
if err == nil {
|
||||
f.Close()
|
||||
ts.F.Close()
|
||||
waited := time.Since(start)
|
||||
if waited < touchPoll {
|
||||
res.state = "ready"
|
||||
|
||||
@@ -4,31 +4,13 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"g.fc.run/theater/cabletest/internal/evdev"
|
||||
)
|
||||
|
||||
const (
|
||||
evSyn = 0x00
|
||||
evKey = 0x01
|
||||
evAbs = 0x03
|
||||
synReport = 0x00
|
||||
btnTouch = 0x14a
|
||||
absX = 0x00
|
||||
absY = 0x01
|
||||
|
||||
inputPropDirect = 0x01
|
||||
sizeofInputEvent = 24
|
||||
)
|
||||
|
||||
func evIoc(nr, size uintptr) uintptr {
|
||||
const iocRead = 2
|
||||
return iocRead<<30 | size<<16 | 'E'<<8 | nr
|
||||
}
|
||||
|
||||
// Current finger position and whether it is down. Held as state rather than
|
||||
// delivered as events, so a dropped release can never leave a hold armed.
|
||||
type touchState struct {
|
||||
@@ -49,109 +31,53 @@ func (t *touchState) get() (x, y int, down bool) {
|
||||
return t.x, t.y, t.down
|
||||
}
|
||||
|
||||
// Picks the direct-input device that reports absolute X and Y, which is the
|
||||
// touchscreen; a mouse is relative and a keyboard has no absolute axes.
|
||||
func findTouchscreen() (*os.File, error) {
|
||||
paths, err := filepath.Glob("/dev/input/event*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range paths {
|
||||
f, err := os.OpenFile(p, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var props, absBits uint64
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x09, 8), uintptr(unsafe.Pointer(&props))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x20+evAbs, 8), uintptr(unsafe.Pointer(&absBits))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if props&(1<<inputPropDirect) != 0 &&
|
||||
absBits&(1<<absX) != 0 && absBits&(1<<absY) != 0 {
|
||||
return f, nil
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
return nil, fmt.Errorf("no touchscreen found among /dev/input/event*")
|
||||
}
|
||||
|
||||
func axisRange(f *os.File, axis uintptr) (min, max int32, err error) {
|
||||
var info [6]int32
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
evIoc(0x40+axis, 24), uintptr(unsafe.Pointer(&info[0]))); errno != 0 {
|
||||
return 0, 0, errno
|
||||
}
|
||||
return info[1], info[2], nil
|
||||
}
|
||||
|
||||
// Tracks the finger position, scaled to the given screen size.
|
||||
func watchTouch(w, h int) (*touchState, error) {
|
||||
f, err := findTouchscreen()
|
||||
ts, err := evdev.FindTouchscreen(os.O_RDONLY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minX, maxX, err := axisRange(f, absX)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("x range: %w", err)
|
||||
}
|
||||
minY, maxY, err := axisRange(f, absY)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("y range: %w", err)
|
||||
}
|
||||
if maxX <= minX || maxY <= minY {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("touchscreen axes are degenerate: x %d..%d y %d..%d",
|
||||
minX, maxX, minY, maxY)
|
||||
}
|
||||
|
||||
state := &touchState{}
|
||||
go func() {
|
||||
defer holdPanic()
|
||||
defer f.Close()
|
||||
buf := make([]byte, sizeofInputEvent*32)
|
||||
defer ts.F.Close()
|
||||
buf := make([]byte, evdev.EventSize*32)
|
||||
var rawX, rawY int32
|
||||
var down bool
|
||||
for {
|
||||
n, err := f.Read(buf)
|
||||
n, err := ts.F.Read(buf)
|
||||
// Nothing else feeds this state, so returning here would freeze the
|
||||
// last touch in place and leave the reset button dead while the panel
|
||||
// goes on looking alive.
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("reading touchscreen events: %v", err))
|
||||
}
|
||||
for o := 0; o+sizeofInputEvent <= n; o += sizeofInputEvent {
|
||||
for o := 0; o+evdev.EventSize <= n; o += evdev.EventSize {
|
||||
typ := binary.NativeEndian.Uint16(buf[o+16:])
|
||||
code := binary.NativeEndian.Uint16(buf[o+18:])
|
||||
val := int32(binary.NativeEndian.Uint32(buf[o+20:]))
|
||||
switch typ {
|
||||
case evAbs:
|
||||
case evdev.Abs:
|
||||
switch code {
|
||||
case absX:
|
||||
case evdev.AbsX:
|
||||
rawX = val
|
||||
case absY:
|
||||
case evdev.AbsY:
|
||||
rawY = val
|
||||
}
|
||||
case evKey:
|
||||
if code == btnTouch {
|
||||
case evdev.Key:
|
||||
if code == evdev.BtnTouch {
|
||||
down = val != 0
|
||||
}
|
||||
case evSyn:
|
||||
if code != synReport {
|
||||
case evdev.Syn:
|
||||
if code != evdev.SynReport {
|
||||
break
|
||||
}
|
||||
// Scaled onto [0, w-1], so full deflection is the last pixel
|
||||
// rather than one past it.
|
||||
state.set(
|
||||
int(int64(rawX-minX)*int64(w-1)/int64(maxX-minX)),
|
||||
int(int64(rawY-minY)*int64(h-1)/int64(maxY-minY)),
|
||||
int(int64(rawX-ts.MinX)*int64(w-1)/int64(ts.MaxX-ts.MinX)),
|
||||
int(int64(rawY-ts.MinY)*int64(h-1)/int64(ts.MaxY-ts.MinY)),
|
||||
down)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Package evdev is the kernel input-event ABI.
|
||||
package evdev
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
Syn = 0x00
|
||||
Key = 0x01
|
||||
Abs = 0x03
|
||||
SynReport = 0x00
|
||||
BtnTouch = 0x14a
|
||||
AbsX = 0x00
|
||||
AbsY = 0x01
|
||||
|
||||
propDirect = 0x01
|
||||
|
||||
EventSize = 24
|
||||
)
|
||||
|
||||
func ioc(nr, size uintptr) uintptr {
|
||||
const iocRead = 2
|
||||
return iocRead<<30 | size<<16 | 'E'<<8 | nr
|
||||
}
|
||||
|
||||
type Touchscreen struct {
|
||||
F *os.File
|
||||
MinX, MaxX, MinY, MaxY int32
|
||||
}
|
||||
|
||||
// Picks the direct-input device that reports absolute X and Y, which is the
|
||||
// touchscreen; a mouse is relative and a keyboard has no absolute axes.
|
||||
func FindTouchscreen(flag int) (*Touchscreen, error) {
|
||||
paths, err := filepath.Glob("/dev/input/event*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range paths {
|
||||
f, err := os.OpenFile(p, flag, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var props, absBits uint64
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
ioc(0x09, 8), uintptr(unsafe.Pointer(&props))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
ioc(0x20+Abs, 8), uintptr(unsafe.Pointer(&absBits))); errno != 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
if props&(1<<propDirect) == 0 ||
|
||||
absBits&(1<<AbsX) == 0 || absBits&(1<<AbsY) == 0 {
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
t := &Touchscreen{F: f}
|
||||
if t.MinX, t.MaxX, err = axisRange(f, AbsX); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s x range: %w", p, err)
|
||||
}
|
||||
if t.MinY, t.MaxY, err = axisRange(f, AbsY); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s y range: %w", p, err)
|
||||
}
|
||||
if t.MaxX <= t.MinX || t.MaxY <= t.MinY {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("%s: degenerate axes x %d..%d y %d..%d",
|
||||
p, t.MinX, t.MaxX, t.MinY, t.MaxY)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no touchscreen found among /dev/input/event*")
|
||||
}
|
||||
|
||||
func axisRange(f *os.File, axis uintptr) (min, max int32, err error) {
|
||||
var info [6]int32
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
|
||||
ioc(0x40+axis, 24), uintptr(unsafe.Pointer(&info[0]))); errno != 0 {
|
||||
return 0, 0, errno
|
||||
}
|
||||
return info[1], info[2], nil
|
||||
}
|
||||
@@ -23,8 +23,7 @@ type endpoint struct {
|
||||
}
|
||||
|
||||
func (e endpoint) macString() string {
|
||||
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
e.mac[0], e.mac[1], e.mac[2], e.mac[3], e.mac[4], e.mac[5])
|
||||
return net.HardwareAddr(e.mac[:]).String()
|
||||
}
|
||||
|
||||
func lookupEndpoint(name string) (endpoint, error) {
|
||||
@@ -223,7 +222,7 @@ func run(aName, bName string) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checks = append(checks, moduleChecks(modules, [2]string{a.name, b.name})...)
|
||||
checks = append(checks, moduleChecks(modules)...)
|
||||
if err := reportChecks("SETTINGS", checks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ func waitCarrier(names [2]string, done *atomic.Bool) {
|
||||
// carrier settles — the host checks just reset the links — before any command.
|
||||
// Never waits for a link: there may be no cable, and forcing config needs
|
||||
// none — the AN restart applies it whenever training next happens.
|
||||
func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
|
||||
func moduleChecks(mods []*phyModule) []checkResult {
|
||||
var out []checkResult
|
||||
fail := func(item string, err error) []checkResult {
|
||||
return append(out, checkResult{item: item, err: err})
|
||||
@@ -628,26 +628,26 @@ func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
|
||||
}
|
||||
}
|
||||
master := !mixed
|
||||
for i, m := range mods {
|
||||
for _, m := range mods {
|
||||
b, ok := m.dev.(*bcm)
|
||||
if !ok {
|
||||
out = append(out, checkResult{item: names[i] + " role", state: "auto"})
|
||||
out = append(out, checkResult{item: m.dev.name() + " role", state: "auto"})
|
||||
continue
|
||||
}
|
||||
|
||||
res := checkResult{item: names[i] + " eee", state: "forced off"}
|
||||
res := checkResult{item: m.dev.name() + " eee", state: "forced off"}
|
||||
if err := b.forceEEEOff(); err != nil {
|
||||
return fail(res.item, err)
|
||||
}
|
||||
out = append(out, res)
|
||||
|
||||
res = checkResult{item: names[i] + " jumbo", state: "forced on"}
|
||||
res = checkResult{item: m.dev.name() + " jumbo", state: "forced on"}
|
||||
if err := b.forceJumbo(); err != nil {
|
||||
return fail(res.item, err)
|
||||
}
|
||||
out = append(out, res)
|
||||
|
||||
res = checkResult{item: names[i] + " role", state: "forced master"}
|
||||
res = checkResult{item: m.dev.name() + " role", state: "forced master"}
|
||||
if !master {
|
||||
res.state = "forced slave"
|
||||
}
|
||||
@@ -672,7 +672,7 @@ func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
|
||||
adv[i] = v
|
||||
advs[i] = fmt.Sprintf("%#04x", v)
|
||||
if _, ok := m.dev.(*bcm); ok && v != 0 {
|
||||
res.err = fmt.Errorf("%s still advertises EEE %#04x", names[i], v)
|
||||
res.err = fmt.Errorf("%s still advertises EEE %#04x", m.dev.name(), v)
|
||||
}
|
||||
}
|
||||
if res.err == nil && adv[0]&adv[1] != 0 {
|
||||
@@ -681,9 +681,9 @@ func moduleChecks(mods []*phyModule, names [2]string) []checkResult {
|
||||
res.state = advs[0] + "/" + advs[1]
|
||||
out = append(out, res)
|
||||
|
||||
for i, m := range mods {
|
||||
for _, m := range mods {
|
||||
if err := devRestartAN(m.dev); err != nil {
|
||||
return fail(names[i]+" retrain", err)
|
||||
return fail(m.dev.name()+" retrain", err)
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -15,10 +15,15 @@ type sff struct {
|
||||
|
||||
// Every transport touch happens on the loop goroutine: requests execute
|
||||
// one at a time, each admitted by the module type's own gate first.
|
||||
reqs chan func()
|
||||
reqs chan sffReq
|
||||
admit func()
|
||||
}
|
||||
|
||||
type sffReq struct {
|
||||
fn func()
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func openSFF(ifname string) (*sff, error) {
|
||||
devLink, err := os.Readlink("/sys/class/net/" + ifname + "/device")
|
||||
if err != nil {
|
||||
@@ -34,7 +39,7 @@ func openSFF(ifname string) (*sff, error) {
|
||||
s := &sff{
|
||||
ifname: ifname,
|
||||
path: "/sys/kernel/debug/ixgbe/" + filepath.Base(devLink) + "/sff_i2c",
|
||||
reqs: make(chan func()),
|
||||
reqs: make(chan sffReq),
|
||||
admit: func() {},
|
||||
}
|
||||
if _, err := os.Stat(s.path); err != nil {
|
||||
@@ -45,16 +50,23 @@ func openSFF(ifname string) (*sff, error) {
|
||||
}
|
||||
|
||||
func (s *sff) loop() {
|
||||
defer holdPanic()
|
||||
for fn := range s.reqs {
|
||||
s.admit()
|
||||
fn()
|
||||
for r := range s.reqs {
|
||||
s.serve(r)
|
||||
}
|
||||
}
|
||||
|
||||
// done closes even when the request panics, and the recover keeps the loop
|
||||
// serving, so no exec caller is ever stranded waiting on a dead loop.
|
||||
func (s *sff) serve(r sffReq) {
|
||||
defer close(r.done)
|
||||
defer holdPanic()
|
||||
s.admit()
|
||||
r.fn()
|
||||
}
|
||||
|
||||
func (s *sff) exec(fn func()) {
|
||||
done := make(chan struct{})
|
||||
s.reqs <- func() { fn(); close(done) }
|
||||
s.reqs <- sffReq{fn: fn, done: done}
|
||||
<-done
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ func allRuleLocations(fd int, ifname string) ([]uint32, uint32, error) {
|
||||
// rule_locs follows rule_cnt directly, before the struct's tail padding,
|
||||
// and that is where the kernel copies it to.
|
||||
locOff := unsafe.Offsetof(ethtoolRxnfc{}.ruleCnt) + 4
|
||||
buf := make([]byte, int(unsafe.Sizeof(ethtoolRxnfc{}))+4*int(cnt.ruleCnt))
|
||||
buf := statsBuf((int(unsafe.Sizeof(ethtoolRxnfc{})) + 4*int(cnt.ruleCnt) + 7) / 8)
|
||||
all := (*ethtoolRxnfc)(unsafe.Pointer(&buf[0]))
|
||||
all.cmd = ethtoolGRXCLSRLALL
|
||||
all.ruleCnt = cnt.ruleCnt
|
||||
@@ -113,7 +113,7 @@ func allRuleLocations(fd int, ifname string) ([]uint32, uint32, error) {
|
||||
ifname, all.ruleCnt, cnt.ruleCnt)
|
||||
}
|
||||
|
||||
raw := buf[locOff:]
|
||||
raw := unsafe.Slice((*byte)(unsafe.Pointer(&buf[0])), len(buf)*8)[locOff:]
|
||||
locs := make([]uint32, 0, all.ruleCnt)
|
||||
for i := 0; i < int(all.ruleCnt); i++ {
|
||||
locs = append(locs, binary.LittleEndian.Uint32(raw[i*4:]))
|
||||
@@ -311,7 +311,10 @@ type ethtoolCoalesce struct {
|
||||
}
|
||||
|
||||
const (
|
||||
wantGovernor = "performance"
|
||||
wantGovernor = "performance"
|
||||
// Under the performance governor a max-frequency bound is a fixed clock, not a
|
||||
// ceiling the cores rove beneath, so the line-rate margin it was qualified with
|
||||
// is the margin every run gets.
|
||||
wantMaxFreqKHz = "2000000"
|
||||
wantCoalesceUsecs = 25
|
||||
wantRxRing = 8160
|
||||
@@ -522,11 +525,11 @@ func (r *statReader) sum() uint64 {
|
||||
return total
|
||||
}
|
||||
|
||||
func checkGovernor(want string) checkResult {
|
||||
res := checkResult{item: "cpu governor"}
|
||||
paths, err := filepath.Glob("/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor")
|
||||
func checkCPUAttr(item, attr, want, unit string) checkResult {
|
||||
res := checkResult{item: item}
|
||||
paths, err := filepath.Glob("/sys/devices/system/cpu/cpu*/cpufreq/" + attr)
|
||||
if err != nil || len(paths) == 0 {
|
||||
res.err = fmt.Errorf("no cpufreq governors found")
|
||||
res.err = fmt.Errorf("no cpufreq %s found", attr)
|
||||
return res
|
||||
}
|
||||
var wrong []string
|
||||
@@ -544,7 +547,7 @@ func checkGovernor(want string) checkResult {
|
||||
}
|
||||
}
|
||||
if len(wrong) == 0 {
|
||||
res.state = fmt.Sprintf("%s on all %d cpus", want, len(paths))
|
||||
res.state = fmt.Sprintf("%s%s on all %d cpus", want, unit, len(paths))
|
||||
return res
|
||||
}
|
||||
var found []string
|
||||
@@ -559,51 +562,7 @@ func checkGovernor(want string) checkResult {
|
||||
}
|
||||
}
|
||||
res.fixed = true
|
||||
res.state = fmt.Sprintf("was %s, now %s on all %d cpus", strings.Join(found, " "), want, len(paths))
|
||||
return res
|
||||
}
|
||||
|
||||
// Under the performance governor a max-frequency bound is a fixed clock, not a
|
||||
// ceiling the cores rove beneath, so the line-rate margin it was qualified with
|
||||
// is the margin every run gets.
|
||||
func checkMaxFreq(wantKHz string) checkResult {
|
||||
res := checkResult{item: "cpu max frequency"}
|
||||
paths, err := filepath.Glob("/sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq")
|
||||
if err != nil || len(paths) == 0 {
|
||||
res.err = fmt.Errorf("no cpufreq max frequencies found")
|
||||
return res
|
||||
}
|
||||
var wrong []string
|
||||
counts := map[string]int{}
|
||||
for _, p := range paths {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
res.err = err
|
||||
return res
|
||||
}
|
||||
got := strings.TrimSpace(string(b))
|
||||
counts[got]++
|
||||
if got != wantKHz {
|
||||
wrong = append(wrong, p)
|
||||
}
|
||||
}
|
||||
if len(wrong) == 0 {
|
||||
res.state = fmt.Sprintf("%s kHz on all %d cpus", wantKHz, len(paths))
|
||||
return res
|
||||
}
|
||||
var found []string
|
||||
for k, v := range counts {
|
||||
found = append(found, fmt.Sprintf("%s:%d", k, v))
|
||||
}
|
||||
for _, p := range wrong {
|
||||
if err := os.WriteFile(p, []byte(wantKHz), 0o644); err != nil {
|
||||
res.err = err
|
||||
res.state = fmt.Sprintf("could not set %s", p)
|
||||
return res
|
||||
}
|
||||
}
|
||||
res.fixed = true
|
||||
res.state = fmt.Sprintf("was %s, now %s kHz on all %d cpus", strings.Join(found, " "), wantKHz, len(paths))
|
||||
res.state = fmt.Sprintf("was %s, now %s%s on all %d cpus", strings.Join(found, " "), want, unit, len(paths))
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -774,7 +733,9 @@ func withIoctlSocket(fn func(fd int) []checkResult) []checkResult {
|
||||
// under test, so with no cable there is never going to be one.
|
||||
func configureSystem(ifnames []string, ethertypes []uint16) []checkResult {
|
||||
return withIoctlSocket(func(fd int) []checkResult {
|
||||
out := []checkResult{checkGovernor(wantGovernor), checkMaxFreq(wantMaxFreqKHz),
|
||||
out := []checkResult{
|
||||
checkCPUAttr("cpu governor", "scaling_governor", wantGovernor, ""),
|
||||
checkCPUAttr("cpu max frequency", "scaling_max_freq", wantMaxFreqKHz, " kHz"),
|
||||
checkIdleLatency(wantIdleLatency)}
|
||||
for i, ifname := range ifnames {
|
||||
out = append(out, checkLinkUp(fd, ifname))
|
||||
|
||||
@@ -449,7 +449,7 @@ func (d *display) drawHeader(phy phyDisplay, hv histView, measuring bool,
|
||||
func (d *display) drawColHeaders() {
|
||||
y := d.headerH + 8
|
||||
d.textRightAt(d.small, d.resetX+d.resetW, y, "RESET", uiMuted)
|
||||
hdr := "LAST 90 S"
|
||||
hdr := fmt.Sprintf("LAST %d S", int(histSpan/time.Second))
|
||||
hw := len([]rune(hdr)) * d.small.cellW
|
||||
d.small.draw(d.fb, d.cellsX+(d.cellsW-hw)/2, y-d.small.capTop, hdr, uiMuted)
|
||||
d.textRightAt(d.small, d.nowX+d.nowW, y, "NOW", uiMuted)
|
||||
|
||||
Reference in New Issue
Block a user