[PATCH v5 1/2] scsi: sd: mark disk dead on surprise host removal to avoid I/O hang

From: 胡连勤

Date: Fri Oct 09 2026 - 07:31:44 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.

Add a surprise_removal flag to struct Scsi_Host that LLDDs can set
before calling scsi_remove_host() to indicate that the device is
already gone and cannot handle I/O. In sd_remove(), check this flag
and call blk_mark_disk_dead() to set GD_DEAD, so that del_gendisk()
sees GD_DEAD already set and skips the sync. For orderly removal
where the flag is not set, sync proceeds normally.

The actual setting of this flag by the USB storage driver is done
in a follow-up patch.

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>
---

Changes in v5:
- Fix v4 data loss risk: scsi_host_in_cancel() cannot distinguish
surprise removal from orderly removal, causing sync_filesystem()
to be skipped for all host removals.
- Replace with an explicit surprise_removal flag in struct Scsi_Host
that LLDDs set only when the device is physically gone.
- Split into a 2-patch series; patch 2/2 sets the flag in USB storage.
- Link to v4: https://lore.kernel.org/all/TYUPR06MB6217570B9AFA4B5980FCCED0D2932@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/

Changes in v4:
- Repost: v3 did not receive any review feedback.
- The block layer counterpart (7e9a46004b47 'block: set QUEUE_FLAG_DYING
unconditionally in blk_mark_disk_dead()') has been merged, but it alone
is not sufficient as the queue is not frozen during autosuspend. This
SCSI patch is still needed to mark the disk dead before del_gendisk().
- Link to v3: https://lore.kernel.org/all/TYUPR06MB62177178F9305DF5C1A7D046D2A92@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/

Changes in v3:
- Repost after merge window closure as requested by maintainer.
- Use scsi_get_host_state() to match scsi_host_in_recovery() pattern.
- Fix Link to v1 URL.
- Link to v2: https://lore.kernel.org/all/PUZPR06MB622453C37D2BABC5E71B195DD2DC2@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/

Changes in v2:
- Split into a two-patch series for independent review by block and SCSI maintainers.
- This is the SCSI part extracted from v1.
- Link to v1: https://lore.kernel.org/all/TYUPR06MB62172E412054140753D5E782D2C82@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/

drivers/scsi/sd.c | 4 ++++
include/scsi/scsi_host.h | 3 +++
2 files changed, 7 insertions(+)

diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index a1b21ea14e54..98f861fb7e80 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4272,6 +4272,10 @@ static void sd_remove(struct scsi_device *sdp)

scsi_autopm_get_device(sdkp->device);

+ /* Device is gone; avoid sync_filesystem() hang in del_gendisk(). */
+ if (sdkp->device->host->surprise_removal)
+ 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 16243ec376cd..6788e284877b 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -698,6 +698,9 @@ struct Scsi_Host {
/* Host responded with short (<36 bytes) INQUIRY result */
unsigned short_inquiry:1;

+ /* Device is physically gone; skip sync in sd_remove() */
+ unsigned surprise_removal:1;
+
/* The transport requires the LUN bits NOT to be stored in CDB[1] */
unsigned no_scsi2_lun_in_cdb:1;

--
2.48.1