After ERS reports pci_channel_io_perm_failure, virtio-pci must ask the
virtio driver to tear down the block device — not only mark virtqueues
broken.  Call the virtio driver shutdown hook from virtio-pci on
perm_failure; virtio-blk implements shutdown with blk_mark_disk_dead().
Fail new requests early in virtio_queue_rq when the disk is dead or
virtqueues were removed during frozen reset_prepare.

Signed-off-by: Xixin Liu <[email protected]>
---
 drivers/block/virtio_blk.c         | 39 +++++++++++++++++++++++++++++++++++++++
 drivers/virtio/virtio_pci_common.c | 10 +++++++++-
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
index 32bf3ba07a9d..4740ae91d5be 100644
--- a/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -435,6 +435,12 @@ static blk_status_t virtio_queue_rq(struct blk_mq_hw_ctx 
*hctx,
        blk_status_t status;
        int err;
 
+       /* Fail fast if ERS frozen tore down VQs or the disk was marked dead. */
+       if (unlikely(!disk_live(vblk->disk) || !vblk->vqs || !vblk->vdev)) {
+               blk_mq_start_request(req);
+               return BLK_STS_IOERR;
+       }
+
        status = virtblk_prep_rq(hctx, vblk, req, vbr);
        if (unlikely(status))
                return status;
@@ -1561,6 +1567,29 @@ static int virtblk_probe(struct virtio_device *vdev)
        return err;
 }
 
+/* Stop I/O and mark the gendisk dead (ERS perm_failure or system shutdown). */
+static void virtblk_shutdown(struct virtio_device *vdev)
+{
+       struct virtio_blk *vblk = vdev->priv;
+       struct request_queue *q;
+       unsigned int memflags;
+
+       if (!vblk || !vblk->disk)
+               return;
+
+       flush_work(&vblk->config_work);
+       virtio_break_device(vdev);
+
+       q = vblk->disk->queue;
+       memflags = blk_mq_freeze_queue(q);
+       blk_mq_quiesce_queue_nowait(q);
+
+       blk_mark_disk_dead(vblk->disk);
+
+       blk_mq_unquiesce_queue(q);
+       blk_mq_unfreeze_queue(q, memflags);
+}
+
 static void virtblk_remove(struct virtio_device *vdev)
 {
        struct virtio_blk *vblk = vdev->priv;
@@ -1684,6 +1713,7 @@ static struct virtio_driver virtio_blk = {
        .probe                          = virtblk_probe,
        .remove                         = virtblk_remove,
        .config_changed                 = virtblk_config_changed,
+       .shutdown                       = virtblk_shutdown,
 #ifdef CONFIG_PM_SLEEP
        .freeze                         = virtblk_freeze,
        .restore                        = virtblk_restore,
diff --git a/drivers/virtio/virtio_pci_common.c 
b/drivers/virtio/virtio_pci_common.c
index e2dda946e70e..924ceead436b 100644
--- a/drivers/virtio/virtio_pci_common.c
+++ b/drivers/virtio/virtio_pci_common.c
@@ -845,7 +845,15 @@ static pci_ers_result_t virtio_pci_error_detected(struct 
pci_dev *pci_dev,
        case pci_channel_io_perm_failure:
                dev_warn(&pci_dev->dev,
                         "permanent failure, disconnecting device\n");
-               virtio_break_device(&vp_dev->vdev);
+               {
+                       struct virtio_driver *drv =
+                               drv_to_virtio(vp_dev->vdev.dev.driver);
+
+                       if (drv && drv->shutdown)
+                               drv->shutdown(&vp_dev->vdev);
+                       else
+                               virtio_break_device(&vp_dev->vdev);
+               }
                return PCI_ERS_RESULT_DISCONNECT;
        default:
                break;


Reply via email to