--- a/sys/bus/firewire/firewirereg.h +++ b/sys/bus/firewire/firewirereg.h @@ struct firewire_dev_comm { struct firewire_comm *fc; device_t dev; void (*post_explore) (void *); void (*post_busreset) (void *); + int fd_cb_ref; /* fw_busreset dispatch drain counter; + * child detach must clear post_busreset and + * wait for this to fall back to 0 */ }; struct fw_xferq { --- a/sys/bus/firewire/firewire.c +++ b/sys/bus/firewire/firewire.c @@ fw_busreset(struct firewire_comm *fc) if (device_get_children(fc->bdev, &devlistp, &devcnt) == 0) { for( i = 0 ; i < devcnt ; i++) if (device_get_state(devlistp[i]) >= DS_ATTACHED) { fdc = device_get_softc(devlistp[i]); - if (fdc->post_busreset != NULL) - fdc->post_busreset(fdc); + /* + * Take the dispatch reference BEFORE the + * NULL check so a concurrent detach that + * clears post_busreset and drains fd_cb_ref + * cannot free the softc underneath us. + */ + atomic_add_int(&fdc->fd_cb_ref, 1); + if (fdc->post_busreset != NULL) + fdc->post_busreset(fdc); + atomic_subtract_int(&fdc->fd_cb_ref, 1); + wakeup(&fdc->fd_cb_ref); } kfree(devlistp, M_TEMP); } --- a/sys/dev/misc/dcons/dcons_crom.c +++ b/sys/dev/misc/dcons/dcons_crom.c @@ dcons_crom_detach(device_t dev) sc = (struct dcons_crom_softc *) device_get_softc(dev); - sc->fd.post_busreset = NULL; + /* detach the bus-reset callback and drain any in-flight + * fw_busreset dispatch before returning: newbus frees the softc + * right after this method returns. */ + sc->fd.post_busreset = NULL; + while (sc->fd.fd_cb_ref) + tsleep(&sc->fd.fd_cb_ref, 0, "dcromdr", 1); /* XXX */ if (dcons_conf->dma_tag == sc->dma_tag)