--- 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