# DF-5422 — ucom/uftdi TX close race: write(2)+close(2) wedges the closer in `ttywait` forever, only the first 64-byte packet is transmitted

## What (human-readable)

Writing more than one USB transfer's worth of data (UFTDI_OBUFSIZE = 64,
uftdi.c:82) to `/dev/cuaU0` and closing the descriptor immediately makes
`close(2)` **sleep forever** (wchan `ttywai`, tty.c:1396-1398) — and the
data beyond the **first 64-byte bulk-OUT packet is never transmitted**
(verified host-side through the QEMU usb-serial chardev socket).

Trigger is a *race*: the closer's `ttywait` competes with the asynchronous
USB TX drain. Reproduced 10+ times on the live rig; hangs observed for every
size from 64/65 up to 1 MiB; with a settle delay (≥1 s of ioctl polling or a
plain sleep) before close, the same writes drain and close cleanly.

## Who can trigger

* Any local user able to open `/dev/cuaU*` (mode 0660 uucp:dialer,
  usb_serial.c:483-488 — the standard serial-user group) — root trivially.
* Amplified by an **evil USB device**: a physical FTDI-alike that NAKs the
  bulk-OUT endpoint (or stalls it) makes the race deterministic — the TX
  queue can never drain, so *every* write+close wedges, with no timing
  window needed.
* After one wedged close the port is typically left in a polluted state
  (subsequent sessions block on carrier / behave erratically) until replug.

## Root cause (path:line)

* `ucom_close()` — sys/bus/u4b/serial/usb_serial.c:938-978 — queues the
  `cfg_close` message and waits only for that *message* to run
  (usb_proc_mwait inside `ucom_queue_command`, usb_serial.c:671-672,
  invoked at 955-957). It does **not** wait for the tty output queue to
  drain. The async WR-xfer completion chain (uftdi_write_callback →
  ucom_get_data, 64 bytes per transfer) is still mid-drain when the close
  proceeds.
* `sc->sc_flag &= ~UCOM_FLAG_HL_READY` at usb_serial.c:959 turns
  `ucom_start()` into a no-op (early return at usb_serial.c:1734-1739), so
  the `t_oproc` that `ttywait()` calls each iteration (tty.c:1392) can no
  longer submit transfers.
* `(*linesw[tp->t_line].l_close)(tp, 0)` (usb_serial.c:966) →
  `ttylclose()` (tty.c:1562) → `ttywflush()` → `ttywait()` (tty.c:1384-1406)
  sleeps on `TSA_OCOMPLETE` with **no timeout** (`tp->t_timeout == 0`).
  Nothing in the ucom TX-completion path ever calls `ttwwakeup()`, so the
  sleeper is never woken even if the queue would eventually drain; QEMU
  tracing shows the bulk-OUT chain goes fully idle at close time (zero USB
  packets while hung), so the outq is frozen permanently.
* `ucom_get_data()` (usb_serial.c:1844-1942) — the only consumer of
  `t_outq` — never signals `TSA_OCOMPLETE`/`TSA_OLOWAT` waiters when it
  empties the queue (missing `ttwwakeup`, contrast sio.c's TX-complete
  handler).

## Reproduce (QEMU rig: qemu-xhci or piix3-usb-uhci + `-device usb-serial`)

```
# guest:
cc -O2 -o /root/uftdi_live /root/uftdi_live.c      # or ./build.sh
timeout 10 /root/uftdi_live w 1024                  # or ./run.sh
# expect: no output, exit=124 (timeout had to SIGTERM it)
# while hung:  ps -ax -o pid,stat,wchan,command | grep uftdi_live
#   -> STAT I2/S*, WCHAN ttywai
# host side (chardev socket byte counter): only +64 bytes ever arrived.
```

Control runs in run.2.log / VERDICT.md: sizes 64..1024 all hang;
`uftdi_probe2 1024 0 1 0 1` (2 s settle + TIOCOUTQ polling) drains to 0
and closes in 0 ms; RX direction works (10/10 bytes delivered with the
2-byte FTDI status header stripped by uftdi_read_callback, uftdi.c:427-491).

## Fix

`fix.diff` (usb_serial.c): (1) drain `t_outq` in `ucom_close()` while
`t_oproc` is still live, bounded by `tp->t_timeout` so an evil/dead device
cannot wedge close(2) indefinitely; (2) `ttwwakeup(tp)` from
`ucom_get_data()` when the TX queue empties, giving `ttywait` drain-waiters
their wakeup. `git apply --check` passes against `sys/`.
