DF-4797 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | --- 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 { |