⬢ DragonFlyBSD Kernel Audit
DF-4744 / fix.diff
← back to finding ↓ download raw
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
 	 */