diff --git a/sys/dev/disk/ahci/ahci.h b/sys/dev/disk/ahci/ahci.h index 1111111..2222222 100644 --- a/sys/dev/disk/ahci/ahci.h +++ b/sys/dev/disk/ahci/ahci.h @@ -545,6 +545,10 @@ struct ahci_softc { u_int sc_ncmds; + u_int sc_intr_active; /* in-flight ahci_intr()s */ + u_int sc_intr_wakeup; /* teardown waiting for drain */ + struct ahci_port *sc_ports[AHCI_MAX_PORTS]; }; diff --git a/sys/dev/disk/ahci/ahci.c b/sys/dev/disk/ahci/ahci.c index 3333333..4444444 100644 --- a/sys/dev/disk/ahci/ahci.c +++ b/sys/dev/disk/ahci/ahci.c @@ -2479,9 +2479,26 @@ ahci_intr(void *arg) /* * Check if the master enable is up, and whether any interrupts are * pending. */ if ((sc->sc_flags & AHCI_F_INT_GOOD) == 0) return; + /* + * DF-4744: bus_teardown_intr() does NOT wait for a handler already + * running on another CPU. Count ourselves as in-flight and re-check + * the flag so ahci_pci_detach() can drain us before it frees the + * ports and releases the register mapping. + */ + atomic_add_int(&sc->sc_intr_active, 1); + cpu_sfence(); + if ((sc->sc_flags & AHCI_F_INT_GOOD) == 0) { + if (atomic_fetchadd_int(&sc->sc_intr_active, -1) == 1 && + sc->sc_intr_wakeup) + wakeup(&sc->sc_intr_active); + return; + } is = ahci_read(sc, AHCI_REG_IS); if (is == 0 || is == 0xffffffff) { + if (atomic_fetchadd_int(&sc->sc_intr_active, -1) == 1 && + sc->sc_intr_wakeup) + wakeup(&sc->sc_intr_active); return; } @@ -2510,6 +2527,12 @@ ahci_intr(void *arg) ahci_pwrite(ap, AHCI_PREG_IE, 0); ahci_os_signal_port_thread(ap, AP_SIGF_PORTINT); } } + if (atomic_fetchadd_int(&sc->sc_intr_active, -1) == 1 && + sc->sc_intr_wakeup) + wakeup(&sc->sc_intr_active); } diff --git a/sys/dev/disk/ahci/ahci_attach.c b/sys/dev/disk/ahci/ahci_attach.c index 5555555..6666666 100644 --- a/sys/dev/disk/ahci/ahci_attach.c +++ b/sys/dev/disk/ahci/ahci_attach.c @@ -596,8 +596,21 @@ ahci_pci_detach(device_t dev) if (sc->sc_irq_handle) { bus_teardown_intr(dev, sc->sc_irq, sc->sc_irq_handle); sc->sc_irq_handle = NULL; } + /* + * DF-4744: bus_teardown_intr() unlinks and masks the vector but does + * not wait for an ahci_intr() already executing on another CPU. + * Drain any in-flight handler before we free the port structures and + * release the register mapping it may still be walking. The fence + * orders our flag clear above against the active-count read below. + */ + cpu_mfence(); + if (sc->sc_intr_active) { + sc->sc_intr_wakeup = 1; + while (sc->sc_intr_active) + tsleep(&sc->sc_intr_active, 0, "ahcdet", 1); + } /* * Free port structures and DMA memory */