# DF-4275 — epic_stop() callout_stop AB-BA deadlock (if_tx.c)

Finding: sys/dev/netif/tx/if_tx.c — SMC EtherPower II (EPIC/100) driver.
DF-3715 family instance (same mechanism as the em(4) finding, this file
was missed in pass 1).

    epic_stats_update()                epic_ifioctl()/epic_ifwatchdog()/epic_intr()/epic_detach()
    (if_tx.c:796-811)                  ------------------------------------------
    lwkt_serialize_enter(              lwkt_serialize_enter(ifp->if_serializer);
        ifp->if_serializer);   <--+    epic_stop(sc);                 (if_tx.c:1341)
        mii_tick(mii);             |   callout_stop(&sc->tx_stat_timer);
        callout_reset(...);        |       |
    lwkt_serialize_exit();       blocked  |  waits 'costp' for the callback
                                       <--+  to finish (kern_timeout.c:888-921)

In this kernel `callout_stop()` is fully synchronous
(sys/kern/kern_timeout.c:1091-1094 -> _callout_cancel_or_stop(..., sync=1)
-> `ssleep(c, &c->spin, 0, "costp", 0)` at kern_timeout.c:920). If the
1-second mii tick has been dequeued by softclock (`CALLOUT_INPROG` set,
kern_timeout.c:567) and is parked in `lwkt_serialize_enter()` on the
interface serializer — which is its normal state whenever the serializer
is contended, e.g. under RX interrupt load, inside
`epic_stop_activity()`'s millisecond DELAY loops, or during a concurrent
`ifconfig` — then any thread that enters `epic_stop()` while holding
`ifp->if_serializer` blocks forever waiting for that callback.

Entry points holding ifp->if_serializer across epic_stop()->callout_stop():
- epic_ifioctl SIOCSIFMTU  (if_tx.c:370-373: epic_stop+epic_init)
- epic_ifioctl SIOCSIFFLAGS down (if_tx.c:389-391)
- epic_ifwatchdog          (if_tx.c:781-782: epic_stop+epic_init; watchdog
  itself runs under ifnet_tryserialize_all from if_slowtimo_dispatch,
  net/if.c — wedging it also wedges netisr0)
- epic_intr PCI-fatal      (if_tx.c:702-703)
- epic_detach              (if_tx.c:300-304)

Because if_tx.c uses plain `callout_init()` (if_tx.c:200, non-MPSAFE), the
stuck softclock thread also holds the MP lock, so the wedge degrades the
whole system, not just one interface.

## Reproduction

The NIC is hardware-gated (no SMC83C170 in the QEMU guest), but the
deadlock is a callout-core/serializer-discipline mechanism: a protocol
replica module reproduces the exact lock discipline (tick callback that
first enters the serializer; stopper holding the serializer calling
synchronous callout_stop) and demonstrates the permanent 'costp' sleep.

    ./build.sh     # df4275rep.ko
    ./run.sh       # kldload; expect stopper parked forever in "costp"

Expected output (console/dmesg):

    df4275: tick() dequeued; entering serializer (blocks: stopper holds it)
    df4275: stopper HOLDS serializer; tick() INPROG+blocked; calling callout_stop() ...
    df4275: stopper_td=... wchan=... wmesg="costp"
    df4275: RESULT: DEADLOCK REPRODUCED -- stopper permanently asleep in "costp"

## Artifacts

- df4275_rep.c   — replica module source
- build.log      — module build output
- run.log        — console capture of decisive run
- verdict.json   — machine verdict
