From e4731c94cd3490d966e2c7e66190a9aef045a56d Mon Sep 17 00:00:00 2001 From: flamingcow Date: Mon, 31 Aug 2026 15:51:52 -0700 Subject: [PATCH] Fix sff panic deadlock, share the evdev probe, assorted review cleanups --- direction.go | 4 +- harness/main.go | 24 +++++++-- harness/stream.go | 10 +++- harness/touch.go | 114 ++++++---------------------------------- history.go | 2 +- init.go | 6 ++- input.go | 106 ++++++------------------------------- internal/evdev/evdev.go | 91 ++++++++++++++++++++++++++++++++ main.go | 5 +- phy.go | 18 +++---- sff.go | 26 ++++++--- system.go | 69 ++++++------------------ ui.go | 2 +- 13 files changed, 205 insertions(+), 272 deletions(-) create mode 100644 internal/evdev/evdev.go diff --git a/direction.go b/direction.go index 603cbd5..d3d3ab9 100644 --- a/direction.go +++ b/direction.go @@ -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 diff --git a/harness/main.go b/harness/main.go index b7d84d2..b61f60c 100644 --- a/harness/main.go +++ b/harness/main.go @@ -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() diff --git a/harness/stream.go b/harness/stream.go index a82cb70..edb8019 100644 --- a/harness/stream.go +++ b/harness/stream.go @@ -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 = "" diff --git a/harness/touch.go b/harness/touch.go index 572b100..b8a6400 100644 --- a/harness/touch.go +++ b/harness/touch.go @@ -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<