--- a/sys/dev/disk/nvme/nvme_attach.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_attach.c 2026-09-21 04:58:58.746843828 +0000 @@ -112,6 +112,7 @@ #endif sc->dev = dev; + sc->flags &= ~NVME_SC_UNLOADING; /* * Map the register window @@ -473,6 +474,22 @@ int i; /* + * Block new NVMEIOCGETLOG ioctls and drain any in-flight ones + * before the admin queue is torn down. Every getlog ioctl holds + * ioctl_lk across its entire get_request -> submit -> wait -> + * put_request sequence, so once we hold ioctl_lk exclusively no + * user thread is inside the admin request machinery. + * + * Without this, nvme_free_subqueue(0) kfree()s reqary and bzero()s + * the queue (nqe = 0) while an ioctl can still be inside + * nvme_get_request(), whose ring-full check computes + * (tail + unsubmitted + 1) % nqe -> integer divide-by-zero panic + * (observed: nvme_get_request+0x34 divl 0x40(%r13),%eax). + */ + sc->flags |= NVME_SC_UNLOADING; + lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE); + + /* * Stop the admin thread */ nvme_stop_admin_thread(sc); @@ -492,6 +509,7 @@ */ nvme_free_subqueue(sc, 0); nvme_free_comqueue(sc, 0); + lockmgr(&sc->ioctl_lk, LK_RELEASE); /* * Release related resources. --- a/sys/dev/disk/nvme/nvme_ioctl.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_ioctl.c 2026-09-21 04:58:40.347081192 +0000 @@ -46,6 +46,16 @@ return EINVAL; lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE); + + /* + * Serialize against device teardown: detach sets NVME_SC_UNLOADING + * and then holds this lock across the admin queue teardown, so we + * never touch a freed/bzero'd queue. + */ + if (sc->flags & NVME_SC_UNLOADING) { + lockmgr(&sc->ioctl_lk, LK_RELEASE); + return ENXIO; + } req = nvme_get_admin_request(sc, NVME_OP_GET_LOG_PAGE); req->cmd.head.nsid = -1; req->cmd.getlog.lid = ioc->lid; --- a/sys/dev/disk/nvme/nvme_admin.c +++ b/sys/dev/disk/nvme/nvme_admin.c 2026-09-21 04:58:40.355081088 +0000 @@ -119,7 +119,13 @@ if ((nsc = sc->nscary[i]) != NULL) { nvme_disk_detach(nsc); - + /* + * The cdev may still be referenced by open descriptors + * after disk_destroy(); make any late dispatch (which + * reads nsc->sc through si_drv1) fail cleanly instead + * of touching freed memory. + */ + nsc->sc = NULL; kfree(nsc, M_NVME); sc->nscary[i] = NULL; } --- a/sys/dev/disk/nvme/nvme_disk.c 2026-09-21 04:58:29.815217063 +0000 +++ b/sys/dev/disk/nvme/nvme_disk.c 2026-09-21 05:53:26.808935656 +0000 @@ -116,6 +116,13 @@ nvme_disk_detach(nvme_softns_t *nsc) { if (nsc->cdev) { + /* + * devfs keeps dispatching through open + * descriptors/nodes until the cdev is fully + * released; fail them cleanly instead of + * dereferencing a freed softns. + */ + nsc->cdev->si_drv1 = NULL; disk_destroy(&nsc->disk); devstat_remove_entry(&nsc->stats); } @@ -127,7 +134,11 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; + + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; if (sc->flags & NVME_SC_UNLOADING) return ENXIO; @@ -143,7 +154,11 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; + + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; atomic_add_long(&sc->opencnt, -1); @@ -155,11 +170,17 @@ { cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; - nvme_softc_t *sc = nsc->sc; + nvme_softc_t *sc; int error; + if (nsc == NULL || nsc->sc == NULL) + return ENXIO; + sc = nsc->sc; + switch(ap->a_cmd) { case NVMEIOCGETLOG: + if (nsc->sc == NULL) + return ENXIO; error = nvme_getlog_ioctl(sc, (void *)ap->a_data); break; default: @@ -175,6 +196,9 @@ cdev_t dev = ap->a_head.a_dev; nvme_softns_t *nsc = dev->si_drv1; + if (nsc == NULL || nsc->sc == NULL) + return 0; /* fail silently, bio is gone with the disk */ + nvme_strategy_core(nsc, ap->a_bio, nvme_sync_delay); return 0;