⬢ DragonFlyBSD Kernel Audit
DF-5044 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/disk/sili/sili_dragonfly.c
+++ b/sys/dev/disk/sili/sili_dragonfly.c
@@ -205,22 +205,43 @@
 /*
  * Stop the OS-specific port helper thread and kill the per-port lock.
  */
-void
+int
 sili_os_stop_port(struct sili_port *ap)
 {
 	if (ap->ap_thread) {
+		int t;
+
 		sili_os_signal_port_thread(ap, AP_SIGF_STOP);
 		sili_os_sleep(10);
 		if (ap->ap_thread) {
 			kprintf("%s: Waiting for thread to terminate\n",
 				PORTNAME(ap));
-			while (ap->ap_thread)
-				sili_os_sleep(100);
+			/*
+			 * Bounded wait: a helper thread wedged in the driver
+			 * core (or behind a stuck lock) must not turn
+			 * kldunload/shutdown into a permanent hang.  On
+			 * timeout the caller leaks the port instead of
+			 * freeing memory the thread still references.
+			 */
+			t = 60;				/* seconds */
+			while (ap->ap_thread && t > 0) {
+				sili_os_sleep(1000);
+				--t;
+			}
+			if (ap->ap_thread) {
+				kprintf("%s: thread did not terminate; "
+					"leaking port to avoid unload hang\n",
+					PORTNAME(ap));
+				return (ETIMEDOUT);
+			}
 			kprintf("%s: thread terminated\n",
 				PORTNAME(ap));
 		}
 	}
 	lockuninit(&ap->ap_lock);
+	lockuninit(&ap->ap_sim_lock);
+	lockuninit(&ap->ap_sig_lock);
+	return (0);
 }
 
 /*
--- a/sys/dev/disk/sili/sili.h
+++ b/sys/dev/disk/sili/sili.h
@@ -869,7 +869,7 @@
 int	sili_port_init(struct sili_port *ap);
 int	sili_port_alloc(struct sili_softc *, u_int);
 void	sili_port_state_machine(struct sili_port *ap, int initial);
-void	sili_port_free(struct sili_softc *, u_int);
+int	sili_port_free(struct sili_softc *, u_int);
 int	sili_port_reset(struct sili_port *, struct ata_port *at, int);
 void	sili_exclusive_access(struct sili_port *ap);
 
@@ -921,7 +921,7 @@
 void	sili_os_hardsleep(int us);
 int	sili_os_softsleep(void);
 void	sili_os_start_port(struct sili_port *ap);
-void	sili_os_stop_port(struct sili_port *ap);
+int	sili_os_stop_port(struct sili_port *ap);
 void	sili_os_signal_port_thread(struct sili_port *ap, int mask);
 void	sili_os_lock_port(struct sili_port *ap);
 int	sili_os_lock_port_nb(struct sili_port *ap);
--- a/sys/dev/disk/sili/sili.c
+++ b/sys/dev/disk/sili/sili.c
@@ -661,7 +661,7 @@
 /*
  * De-initialize and detach a port.
  */
-void
+int
 sili_port_free(struct sili_softc *sc, u_int port)
 {
 	struct sili_port		*ap = sc->sc_ports[port];
@@ -671,7 +671,15 @@
 	 * Ensure port is disabled and its interrupts are all flushed.
 	 */
 	if (ap->ap_sc) {
-		sili_os_stop_port(ap);
+		if (sili_os_stop_port(ap) != 0) {
+			/*
+			 * Helper thread wedged: keep the port (and the
+			 * controller resources, via the caller) allocated so
+			 * the thread never touches freed memory; leak, do
+			 * not hang.
+			 */
+			return (ETIMEDOUT);
+		}
 		sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_MASK);
 		sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
 		sili_write(ap->ap_sc, SILI_REG_GCTL,
@@ -714,8 +722,10 @@
 
 	/* bus_space(9) says we dont free the subregions handle */
 
+	lockuninit(&ap->ap_ccb_lock);
 	kfree(ap, M_DEVBUF);
 	sc->sc_ports[port] = NULL;
+	return (0);
 }
 
 /*
--- a/sys/dev/disk/sili/sili_attach.c
+++ b/sys/dev/disk/sili/sili_attach.c
@@ -329,6 +329,7 @@
 {
 	struct sili_softc *sc = device_get_softc(dev);
 	struct sili_port *ap;
+	int	error = 0;
 	int	i;
 
 	/*
@@ -352,9 +353,21 @@
 		ap = sc->sc_ports[i];
 		if (ap) {
 			sili_cam_detach(ap);
-			sili_port_free(sc, i);
+			if (sili_port_free(sc, i) != 0)
+				error = EBUSY;
 		}
 	}
+	if (error) {
+		/*
+		 * A port helper thread is wedged.  Keep every controller
+		 * resource (IRQ/BARs/tags) allocated so the thread never
+		 * touches freed memory, and fail the detach so kldunload
+		 * reports an error instead of hanging the kernel.
+		 */
+		device_printf(dev, "port thread stuck; leaking controller "
+			      "resources to avoid unload hang\n");
+		return (error);
+	}
 
 	/*
 	 * Clean up the bus space