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

胡连勤 <[email protected]>
Newsgroups org.kernel.vger.linux-scsi,org.kernel.vger.linux-kernel
Message-ID <PUZPR06MB622453C37D2BABC5E71B195DD2DC2@PUZPR06MB6224.apcprd06.prod.outlook.com>
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 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: [email protected]
Signed-off-by: Lianqin Hu <[email protected]>
---

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/TYUPR06MB6217566CFD33F57D3AE46816D2CF2@TYUPR06MB6217.apcprd06.prod.outlook.com/

 drivers/scsi/sd.c        | 10 ++++++++++
 include/scsi/scsi_host.h |  6 ++++++
 2 files changed, 16 insertions(+)

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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.