Fix sff panic deadlock, share the evdev probe, assorted review cleanups

This commit is contained in:
flamingcow
2026-08-31 15:51:52 -07:00
parent 10d97ee82a
commit e4731c94cd
13 changed files with 205 additions and 272 deletions
+21 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}