[PATCH] scsi: sd: Mark disk dead on host removal to avoid I/O hang

From: 胡连勤

Date: Thu Jul 30 2026 - 23:45:25 EST


When a USB mass storage device with a mounted filesystem (e.g. exFAT)
enters runtime suspend (autosuspend), physical removal triggers
scsi_remove_host() which invokes sd_remove() -> del_gendisk() ->
blk_report_disk_dead(). This triggers sync_filesystem() to write
back dirty data, submits bio and blocks in __bio_queue_enter()
waiting for I/O completion that can never happen since the device
is already gone, leading to a hung task.

del_gendisk() unconditionally calls blk_report_disk_dead(disk, false)
when GD_DEAD is not yet set, which always attempts to sync the
filesystem. For surprise removal where the device cannot handle I/O,
this sync is futile and hangs forever. Additionally, SCSI disks
created via blk_mq_alloc_disk_for_queue() do not have GD_OWNS_QUEUE
set, so __blk_mark_disk_dead() (called by del_gendisk()) never sets
QUEUE_FLAG_DYING, and blk_queue_enter() remains blocked waiting for
I/O that will never complete.

Set QUEUE_FLAG_DYING unconditionally in blk_mark_disk_dead(), so
that any in-flight I/O from other threads gets -ENODEV immediately
from blk_queue_enter() regardless of GD_OWNS_QUEUE ownership.

Add scsi_host_in_cancel() helper and call blk_mark_disk_dead() in
sd_remove() when the SCSI host is in SHOST_CANCEL or
SHOST_CANCEL_RECOVERY state (set by scsi_remove_host() before
removing devices), so that del_gendisk() sees GD_DEAD already set
and skips the sync. For orderly removal (e.g. echo 1 >
/sys/.../delete), the host remains SHOST_RUNNING and sync proceeds
normally. This matches the NVMe behavior where
nvme_mark_namespaces_dead() is called only on surprise removal
(NVME_CTRL_DEAD).

Call trace on USB storage surprise removal:
INFO: task "kworker/1:0":31 blocked for more than 147 seconds.
Call trace:
__switch_to+0x198/0x380
__schedule+0x548/0xfbc
schedule+0x4c/0x118
__bio_queue_enter+0xb8/0x174
blk_mq_submit_bio+0x640/0x7e4
__submit_bio+0x1a4/0x314
__submit_bio_noacct_mq+0x38/0x8c
submit_bio_noacct+0x678/0x750
submit_bio+0xa8/0x1c8
submit_bh_wbc+0x148/0x1b4
__sync_dirty_buffer+0x120/0x1f8
exfat_sync_fs+0xa4/0xe0
sync_filesystem+0xa8/0xdc
fs_bdev_mark_dead+0x30/0x88
bdev_mark_dead+0x54/0xc4
blk_report_disk_dead+0x8c/0xd4
del_gendisk+0xa0/0x2f8
sd_remove+0x30/0x60
device_release_driver_internal+0x1c4/0x2bc
device_release_driver+0x18/0x28
bus_remove_device+0x158/0x170
device_del+0x1c8/0x320
__scsi_remove_device+0x9c/0x184
scsi_forget_host+0x50/0x70
scsi_remove_host+0x88/0x18c
usb_stor_disconnect+0x68/0xf4
usb_unbind_interface+0x13c/0x340
device_release_driver_internal+0x1c4/0x2bc
device_release_driver+0x18/0x28
bus_remove_device+0x158/0x170
device_del+0x1c8/0x320
usb_disable_device+0x84/0x190
usb_disconnect+0xe8/0x338
hub_event+0xbd8/0x19ac
process_scheduled_works+0x200/0x9d8
worker_thread+0x154/0x3b0

Fixes: 6f8191fdf41d ("block: simplify disk shutdown")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Lianqin Hu <hulianqin@xxxxxxxx>
---
block/genhd.c | 1 +
drivers/scsi/sd.c | 10 ++++++++++
include/scsi/scsi_host.h | 6 ++++++
3 files changed, 17 insertions(+)

diff --git a/block/genhd.c b/block/genhd.c
index df2c3c69b467..7b089e2d52c4 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -681,6 +681,7 @@ static bool __blk_mark_disk_dead(struct gendisk *disk)
*/
void blk_mark_disk_dead(struct gendisk *disk)
{
+ blk_queue_flag_set(QUEUE_FLAG_DYING, disk->queue);
__blk_mark_disk_dead(disk);
blk_report_disk_dead(disk, true);
}
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 599e75f33334..fb5086465a23 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4257,6 +4257,16 @@ static void sd_remove(struct scsi_device *sdp)

scsi_autopm_get_device(sdkp->device);

+ /*
+ * If the host is being torn down (e.g. USB surprise removal),
+ * the device cannot handle I/O anymore.
+ * Mark the disk dead before del_gendisk() so that new I/O is
+ * rejected immediately and sync_filesystem() in del_gendisk()
+ * is skipped, avoiding a hang waiting for I/O that can never
+ * complete.
+ */
+ if (scsi_host_in_cancel(sdkp->device->host))
+ blk_mark_disk_dead(sdkp->disk);
device_del(&sdkp->disk_dev);
del_gendisk(sdkp->disk);
if (!sdkp->suspended)
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index f6b286fa59f2..03523ad3284d 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -796,6 +796,12 @@ static inline int scsi_host_in_recovery(struct Scsi_Host *shost)
shost->tmf_in_progress;
}

+static inline bool scsi_host_in_cancel(struct Scsi_Host *shost)
+{
+ return shost->shost_state == SHOST_CANCEL ||
+ shost->shost_state == SHOST_CANCEL_RECOVERY;
+}
+
extern int scsi_queue_work(struct Scsi_Host *, struct work_struct *);
extern void scsi_flush_work(struct Scsi_Host *);

--
2.39.0