For a scmd which suffers failover, requeue the master bio of each bio
attached to its request.

A handler is added in the scsi_driver structure to lookup a
mpath_disk from a request. This is needed because the scsi_disk structure
will manage the mpath_disk, and the code core has no method to look this
up from the scsi_scmnd.

Failover occurs when the scsi_cmnd has failed and it is discovered that the
original scsi_device has transport down.

Signed-off-by: John Garry <[email protected]>
---
 drivers/scsi/scsi_error.c     | 12 ++++++
 drivers/scsi/scsi_lib.c       |  9 +++-
 drivers/scsi/scsi_multipath.c | 80 +++++++++++++++++++++++++++++++++++
 include/scsi/scsi.h           |  1 +
 include/scsi/scsi_driver.h    |  3 ++
 include/scsi/scsi_multipath.h | 14 ++++++
 6 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index f869108fd9693..0fd1b46764c3f 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -40,6 +40,7 @@
 #include <scsi/scsi_ioctl.h>
 #include <scsi/scsi_dh.h>
 #include <scsi/scsi_devinfo.h>
+#include <scsi/scsi_multipath.h>
 #include <scsi/sg.h>
 
 #include "scsi_priv.h"
@@ -1901,12 +1902,16 @@ bool scsi_noretry_cmd(struct scsi_cmnd *scmd)
 enum scsi_disposition scsi_decide_disposition(struct scsi_cmnd *scmd)
 {
        enum scsi_disposition rtn;
+       struct request *req = scsi_cmd_to_rq(scmd);
 
        /*
         * if the device is offline, then we clearly just pass the result back
         * up to the top level.
         */
        if (!scsi_device_online(scmd->device)) {
+               if (scsi_is_mpath_request(req))
+                       return scsi_mpath_failover_disposition(scmd);
+
                SCSI_LOG_ERROR_RECOVERY(5, scmd_printk(KERN_INFO, scmd,
                        "%s: device offline - report as SUCCESS\n", __func__));
                return SUCCESS;
@@ -2070,6 +2075,13 @@ enum scsi_disposition scsi_decide_disposition(struct 
scsi_cmnd *scmd)
 
 maybe_retry:
 
+       /*
+        * For SCSI Multipath check if there are path errors to
+        * trigger failover to available path
+        */
+       if (scsi_is_mpath_request(req))
+               return scsi_mpath_failover_disposition(scmd);
+
        /* we requeue for retry because the error was retryable, and
         * the request was not marked fast fail.  Note that above,
         * even if the request is marked fast fail, we still requeue
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index ab224cd61f3ae..7ed0defc8161e 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -1550,7 +1550,7 @@ static void scsi_complete(struct request *rq)
                atomic_inc(&cmd->device->ioerr_cnt);
 
        disposition = scsi_decide_disposition(cmd);
-       if (disposition != SUCCESS && scsi_cmd_runtime_exceeced(cmd))
+       if (disposition != SUCCESS && disposition != FAILOVER && 
scsi_cmd_runtime_exceeced(cmd))
                disposition = SUCCESS;
 
        scsi_log_completion(cmd, disposition);
@@ -1565,6 +1565,9 @@ static void scsi_complete(struct request *rq)
        case ADD_TO_MLQUEUE:
                scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY);
                break;
+       case FAILOVER:
+               scsi_mpath_failover_req(rq);
+               break;
        default:
                scsi_eh_scmd_add(cmd);
                break;
@@ -1935,6 +1938,10 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx 
*hctx,
                if (req->rq_flags & RQF_DONTPREP)
                        scsi_mq_uninit_cmd(cmd);
                scsi_run_queue_async(sdev);
+               if (!scsi_device_online(sdev) && scsi_is_mpath_request(req)) {
+                       scsi_mpath_failover_req(req);
+                       return 0;
+               }
                break;
        }
        return ret;
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index c3e0f792e921f..16b1f84fc552c 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -518,6 +518,86 @@ void scsi_mpath_put_head(struct scsi_mpath_head 
*scsi_mpath_head)
 }
 EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
 
+bool scsi_is_mpath_request(struct request *req)
+{
+       return is_mpath_request(req);
+}
+EXPORT_SYMBOL_GPL(scsi_is_mpath_request);
+
+static inline void bio_list_add_clone_master(struct bio_list *bl,
+                               struct bio *clone)
+{
+       struct scsi_mpath_clone_bio *scsi_mpath_clone_bio;
+       struct bio *master_bio;
+
+       if (clone->bi_next)
+               bio_list_add_clone_master(bl, clone->bi_next);
+
+       scsi_mpath_clone_bio = scsi_mpath_to_master_bio(clone);
+       master_bio = scsi_mpath_clone_bio->master_bio;
+
+       if (bl->tail)
+               bl->tail->bi_next = master_bio;
+       else
+               bl->head = master_bio;
+
+       bl->tail = master_bio;
+
+       bio_put(clone);
+}
+
+void scsi_mpath_failover_req(struct request *req)
+{
+       struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+       struct scsi_device *sdev = scmd->device;
+       struct scsi_driver *drv = to_scsi_driver(sdev->sdev_gendev.driver);
+       struct mpath_disk *mpath_disk = drv->to_mpath_disk(req);
+       struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+       struct mpath_head *mpath_head = mpath_disk->mpath_head;
+       unsigned long flags;
+
+       scsi_mpath_dev_clear_path(scsi_mpath_dev);
+
+       spin_lock_irqsave(&mpath_head->requeue_lock, flags);
+       bio_list_add_clone_master(&mpath_head->requeue_list, req->bio);
+       spin_unlock_irqrestore(&mpath_head->requeue_lock, flags);
+       req->bio = NULL;
+       req->biotail = NULL;
+       req->__data_len = 0;
+
+       /* End old request with clone detached */
+       scmd->result = 0;
+       blk_mq_end_request(req, 0);
+
+       kblockd_schedule_work(&mpath_head->requeue_work);
+}
+
+static inline bool scsi_is_mpath_error(struct scsi_cmnd *scmd)
+{
+       struct scsi_device *sdev = scmd->device;
+
+       if (sdev->sdev_state == SDEV_TRANSPORT_OFFLINE)
+               return true;
+       return false;
+}
+
+int scsi_mpath_failover_disposition(struct scsi_cmnd *scmd)
+{
+       struct request *req = scsi_cmd_to_rq(scmd);
+
+       if (is_mpath_request(req)) {
+               if (scsi_is_mpath_error(scmd) ||
+                   blk_queue_dying(req->q))
+                       return FAILOVER;
+               return NEEDS_RETRY;
+       } else {
+               if (blk_queue_dying(req->q))
+                       return SUCCESS;
+       }
+
+       return SUCCESS;
+}
+
 int __init scsi_multipath_init(void)
 {
        return class_register(&scsi_mpath_device_class);
diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h
index 96b3503666703..544153a01b3fd 100644
--- a/include/scsi/scsi.h
+++ b/include/scsi/scsi.h
@@ -103,6 +103,7 @@ enum scsi_disposition {
        TIMEOUT_ERROR           = 0x2007,
        SCSI_RETURN_NOT_HANDLED = 0x2008,
        FAST_IO_FAIL            = 0x2009,
+       FAILOVER                = 0x2010,
 };
 
 /*
diff --git a/include/scsi/scsi_driver.h b/include/scsi/scsi_driver.h
index c0e89996bdb3f..85e792dc4db50 100644
--- a/include/scsi/scsi_driver.h
+++ b/include/scsi/scsi_driver.h
@@ -19,6 +19,9 @@ struct scsi_driver {
        int (*done)(struct scsi_cmnd *);
        int (*eh_action)(struct scsi_cmnd *, int);
        void (*eh_reset)(struct scsi_cmnd *);
+       #ifdef CONFIG_SCSI_MULTIPATH
+       struct mpath_disk *(*to_mpath_disk)(struct request *);
+       #endif
 };
 #define to_scsi_driver(drv) \
        container_of((drv), struct scsi_driver, gendrv)
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 79e6860243e74..07db217edb085 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -43,6 +43,9 @@ struct scsi_mpath_device {
 #define to_scsi_mpath_device(d) \
        container_of(d, struct scsi_mpath_device, mpath_device)
 
+void scsi_mpath_failover_req(struct request *);
+int scsi_mpath_failover_disposition(struct scsi_cmnd *);
+bool scsi_is_mpath_request(struct request *req);
 int scsi_mpath_dev_alloc(struct scsi_device *sdev);
 void scsi_mpath_dev_release(struct scsi_device *sdev);
 int scsi_multipath_init(void);
@@ -60,6 +63,17 @@ struct scsi_mpath_head {
 struct scsi_mpath_device {
 };
 
+static inline void scsi_mpath_failover_req(struct request *)
+{
+}
+static inline int scsi_mpath_failover_disposition(struct scsi_cmnd *)
+{
+       return 0;
+}
+static inline bool scsi_is_mpath_request(struct request *req)
+{
+       return false;
+}
 static inline int scsi_mpath_dev_alloc(struct scsi_device *sdev)
 {
        return 0;
-- 
2.43.5


Reply via email to