--- a/sys/dev/disk/nata/ata-disk.h 2026-09-23 22:16:40.311576960 +0000 +++ b/sys/dev/disk/nata/ata-disk.h 2026-09-23 22:16:40.335576649 +0000 @@ -44,7 +44,9 @@ #define AD_F_32B_ENABLED 0x0004 #define AD_F_TAG_ENABLED 0x0008 #define AD_F_RAID_SUBDISK 0x0010 +#define AD_F_DETACHING 0x0020 + int refs; /* in-flight ad_strategy refs */ struct devstat stats; /* devstat entry */ struct disk disk; /* disklabel/slice stuff */ cdev_t cdev; /* device placeholder */ --- a/sys/dev/disk/nata/ata-disk.c 2026-09-23 22:16:40.311576960 +0000 +++ b/sys/dev/disk/nata/ata-disk.c 2026-09-23 22:45:57.340784783 +0000 @@ -169,6 +169,7 @@ { struct ad_softc *adp = device_get_ivars(dev); struct ata_device *atadev = device_get_softc(dev); + struct ata_channel *ch = device_get_softc(device_get_parent(dev)); device_t *children; int nchildren, i; @@ -176,6 +177,32 @@ if (!adp) return ENXIO; + /* + * Refuse to detach while any disk/slice device is open: the cdevs + * outlive disk_destroy() and keep walking the embedded struct disk + * (diskclose/dsclose) until their last close. (DF-4797) + */ + if (adp->disk.d_opencount != 0) + return EBUSY; + + /* + * Stop the data path: no new bio may pass ad_strategy(), and the + * softc must survive until the last in-flight request's ad_done(). + * (DF-4797) + */ + lockmgr(&ch->queue_mtx, LK_EXCLUSIVE); + adp->flags |= AD_F_DETACHING; + device_set_ivars(dev, NULL); + if (adp->disk.d_opencount != 0) { /* lost an open race */ + adp->flags &= ~AD_F_DETACHING; + device_set_ivars(dev, adp); + lockmgr(&ch->queue_mtx, LK_RELEASE); + return EBUSY; + } + lockmgr(&ch->queue_mtx, LK_RELEASE); + while (adp->refs) + tsleep(adp, 0, "addetach", hz); + /* destroy the power timeout */ callout_drain(&atadev->spindown_timer); @@ -187,17 +214,19 @@ kfree(children, M_TEMP); } + /* + * Fail anything still queued or running on the channel so their + * biodone()s happen while the disk layer structures are intact. + */ + ata_fail_requests(dev); + /* detroy disk from the system so we dont get any further requests */ disk_invalidate(&adp->disk); disk_destroy(&adp->disk); - /* fail requests on the queue and any thats "in flight" for this device */ - ata_fail_requests(dev); - /* dont leave anything behind */ /* disk_destroy() already took care of the dev_ops */ devstat_remove_entry(&adp->stats); - device_set_ivars(dev, NULL); kfree(adp, M_AD); return 0; } @@ -286,6 +315,22 @@ struct ata_device *atadev = device_get_softc(dev); struct ata_request *request; struct ad_softc *adp = device_get_ivars(dev); + struct ata_channel *ch = device_get_softc(device_get_parent(dev)); + + /* + * Serialize against ad_detach(): either we obtain a reference and + * the softc stays alive until our ad_done() drops it, or the driver + * is detaching and the bio is failed immediately. (DF-4797) + */ + lockmgr(&ch->queue_mtx, LK_EXCLUSIVE); + adp = device_get_ivars(dev); + if (adp == NULL || (adp->flags & AD_F_DETACHING)) { + lockmgr(&ch->queue_mtx, LK_RELEASE); + biofinish(bp, NULL, ENXIO); + return(0); + } + atomic_add_int(&adp->refs, 1); + lockmgr(&ch->queue_mtx, LK_RELEASE); if (atadev->spindown) callout_reset(&atadev->spindown_timer, hz * atadev->spindown, @@ -294,10 +339,13 @@ if (!(request = ata_alloc_request())) { device_printf(dev, "FAILURE - out of memory in strategy\n"); biofinish(bp, NULL, ENOMEM); + if (atomic_fetchadd_int(&adp->refs, -1) == 1) + wakeup(adp); return(0); } /* setup request */ + request->driver = adp; /* ad_done(): stable softc pointer */ request->dev = dev; request->bio = bp; request->callback = ad_done; @@ -354,6 +402,8 @@ device_printf(dev, "FAILURE - unknown BUF operation\n"); ata_free_request(request); biofinish(bp, NULL, EIO); + if (atomic_fetchadd_int(&adp->refs, -1) == 1) + wakeup(adp); return(0); } request->flags |= ATA_R_ORDERED; @@ -365,7 +415,8 @@ static void ad_done(struct ata_request *request) { - struct ad_softc *adp = device_get_ivars(request->dev); + /* ivars are NULL after ad_detach(); use the strategy-time pointer */ + struct ad_softc *adp = request->driver; struct bio *bp = request->bio; struct buf *bbp = bp->bio_buf; @@ -376,6 +427,8 @@ devstat_end_transaction_buf(&adp->stats, bbp); biodone(bp); ata_free_request(request); + if (atomic_fetchadd_int(&adp->refs, -1) == 1) + wakeup(adp); } static int --- a/sys/kern/subr_disk.c 2026-09-23 22:16:40.315576907 +0000 +++ b/sys/kern/subr_disk.c 2026-09-23 22:16:40.339576597 +0000 @@ -1243,6 +1243,13 @@ * returns NULL it will have handled the bio for us (e.g. EOF * or error due to being beyond the device size). */ + if (dp->d_slice == NULL) { + /* the disk is being invalidated/detached underneath us */ + bio->bio_buf->b_error = ENXIO; + bio->bio_buf->b_flags |= B_ERROR; + biodone(bio); + return(0); + } if ((nbio = dscheck(dev, bio, dp->d_slice)) != NULL) { dev_dstrategy(dp->d_rawdev, nbio); } else {