[PATCH 09/24] scsi-multipath: failover handling

John Garry posted 24 patches 1 month, 1 week ago
[PATCH 09/24] scsi-multipath: failover handling
Posted by John Garry 1 month, 1 week ago
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 <john.g.garry@oracle.com>
---
 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
Re: [PATCH 09/24] scsi-multipath: failover handling
Posted by Benjamin Marzinski 1 month ago
On Wed, Feb 25, 2026 at 03:36:12PM +0000, John Garry wrote:
> 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 <john.g.garry@oracle.com>
> ---
>  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_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 {

nitpick: this else block is unnecessary.

-Ben

> +		if (blk_queue_dying(req->q))
> +			return SUCCESS;
> +	}
> +
> +	return SUCCESS;
> +}
> +
Re: [PATCH 09/24] scsi-multipath: failover handling
Posted by John Garry 1 month ago
On 04/03/2026 05:46, Benjamin Marzinski wrote:
>> +
>> +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 {
> nitpick: this else block is unnecessary.

Sure, but I will prob be changing this code anyway.

Cheers
Re: [PATCH 09/24] scsi-multipath: failover handling
Posted by Benjamin Marzinski 1 month ago
On Wed, Feb 25, 2026 at 03:36:12PM +0000, John Garry wrote:
> 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 <john.g.garry@oracle.com>
> ---
>  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)
>  {

I'm no scsi expert, but the checks in scsi_decide_disposition() don't
look quite right to me. You only failover in cases were it the code
might want to retry (and when the device is offline), but there are
other cases, when you don't want to retry the same path, where it would
make sense to try another path, DID_TRANSPORT_FAILFAST for example. 

As a more general issue, since you are already cloning the bios,
couldn't you just trigger the failovers in scsi_mpath_clone_end_io().
blk_path_error() can tell you which bios should be retried. That seems
like a much simpler approach than trying to intercept the request before
it completes the clones, and I don't see what you're losing by letting
the clones complete, and dealing requeueing there (again, I'm no scsi
expert).

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

This is a pretty minimal function, but a request could have a lot of
merged bios, so you probably want to hande them iteratively, instead of
recursing into the function, and eating up stack space unnecessarily.
Also, this reverses the bio's order when adding them to the requeue
list.

-Ben

> +
> +	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);
> +}
Re: [PATCH 09/24] scsi-multipath: failover handling
Posted by John Garry 1 month ago
On 02/03/2026 03:57, Benjamin Marzinski wrote:
> n Wed, Feb 25, 2026 at 03:36:12PM +0000, John Garry wrote:
>> 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<john.g.garry@oracle.com>
>> ---
>>   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)
>>   {
> I'm no scsi expert, but the checks in scsi_decide_disposition() don't
> look quite right to me. You only failover in cases were it the code
> might want to retry (and when the device is offline), but there are
> other cases, when you don't want to retry the same path, where it would
> make sense to try another path, DID_TRANSPORT_FAILFAST for example.

Yeah, I need to check handling of FAILFAST-types more closely.

> 
> As a more general issue, since you are already cloning the bios,
> couldn't you just trigger the failovers in scsi_mpath_clone_end_io().
> blk_path_error() can tell you which bios should be retried. That seems
> like a much simpler approach than trying to intercept the request before
> it completes the clones, and I don't see what you're losing by letting
> the clones complete, and dealing requeueing there (again, I'm no scsi
> expert).

Sure, I'll consider it further. But I am thinking that it's nicer to 
keep the error handling centralized and not have FAILOVER specifics in 
the cloning code.

> 
>>   	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;
>> 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);
> This is a pretty minimal function, but a request could have a lot of
> merged bios, so you probably want to hande them iteratively, instead of
> recursing into the function, and eating up stack space unnecessarily.
> Also, this reverses the bio's order when adding them to the requeue
> list.

Yeah, I think that this function can be improved. Self-calling functions 
are generally nasty.

Thanks!