aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/device_handler/scsi_dh_alua.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/device_handler/scsi_dh_alua.c')
-rw-r--r--drivers/scsi/device_handler/scsi_dh_alua.c120
1 files changed, 95 insertions, 25 deletions
diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c
index fda9cdea0e6..7bcf67eec92 100644
--- a/drivers/scsi/device_handler/scsi_dh_alua.c
+++ b/drivers/scsi/device_handler/scsi_dh_alua.c
@@ -46,13 +46,16 @@
#define TPGS_SUPPORT_OFFLINE 0x40
#define TPGS_SUPPORT_TRANSITION 0x80
+#define RTPG_FMT_MASK 0x70
+#define RTPG_FMT_EXT_HDR 0x10
+
#define TPGS_MODE_UNINITIALIZED -1
#define TPGS_MODE_NONE 0x0
#define TPGS_MODE_IMPLICIT 0x1
#define TPGS_MODE_EXPLICIT 0x2
#define ALUA_INQUIRY_SIZE 36
-#define ALUA_FAILOVER_TIMEOUT (60 * HZ)
+#define ALUA_FAILOVER_TIMEOUT 60
#define ALUA_FAILOVER_RETRIES 5
/* flags passed from user level */
@@ -68,6 +71,7 @@ struct alua_dh_data {
unsigned char inq[ALUA_INQUIRY_SIZE];
unsigned char *buff;
int bufflen;
+ unsigned char transition_tmo;
unsigned char sense[SCSI_SENSE_BUFFERSIZE];
int senselen;
struct scsi_device *sdev;
@@ -116,6 +120,7 @@ static struct request *get_alua_req(struct scsi_device *sdev,
"%s: blk_get_request failed\n", __func__);
return NULL;
}
+ blk_rq_set_block_pc(rq);
if (buflen && blk_rq_map_kern(q, rq, buffer, buflen, GFP_NOIO)) {
blk_put_request(rq);
@@ -124,11 +129,10 @@ static struct request *get_alua_req(struct scsi_device *sdev,
return NULL;
}
- rq->cmd_type = REQ_TYPE_BLOCK_PC;
rq->cmd_flags |= REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT |
REQ_FAILFAST_DRIVER;
rq->retries = ALUA_FAILOVER_RETRIES;
- rq->timeout = ALUA_FAILOVER_TIMEOUT;
+ rq->timeout = ALUA_FAILOVER_TIMEOUT * HZ;
return rq;
}
@@ -174,7 +178,8 @@ done:
* submit_rtpg - Issue a REPORT TARGET GROUP STATES command
* @sdev: sdev the command should be sent to
*/
-static unsigned submit_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
+static unsigned submit_rtpg(struct scsi_device *sdev, struct alua_dh_data *h,
+ bool rtpg_ext_hdr_req)
{
struct request *rq;
int err = SCSI_DH_RES_TEMP_UNAVAIL;
@@ -185,7 +190,10 @@ static unsigned submit_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
/* Prepare the command. */
rq->cmd[0] = MAINTENANCE_IN;
- rq->cmd[1] = MI_REPORT_TARGET_PGS;
+ if (rtpg_ext_hdr_req)
+ rq->cmd[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT;
+ else
+ rq->cmd[1] = MI_REPORT_TARGET_PGS;
rq->cmd[6] = (h->bufflen >> 24) & 0xff;
rq->cmd[7] = (h->bufflen >> 16) & 0xff;
rq->cmd[8] = (h->bufflen >> 8) & 0xff;
@@ -224,13 +232,13 @@ static void stpg_endio(struct request *req, int error)
struct scsi_sense_hdr sense_hdr;
unsigned err = SCSI_DH_OK;
- if (error || host_byte(req->errors) != DID_OK ||
- msg_byte(req->errors) != COMMAND_COMPLETE) {
+ if (host_byte(req->errors) != DID_OK ||
+ msg_byte(req->errors) != COMMAND_COMPLETE) {
err = SCSI_DH_IO;
goto done;
}
- if (h->senselen > 0) {
+ if (req->sense_len > 0) {
err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE,
&sense_hdr);
if (!err) {
@@ -247,7 +255,9 @@ static void stpg_endio(struct request *req, int error)
ALUA_DH_NAME, sense_hdr.sense_key,
sense_hdr.asc, sense_hdr.ascq);
err = SCSI_DH_IO;
- }
+ } else if (error)
+ err = SCSI_DH_IO;
+
if (err == SCSI_DH_OK) {
h->state = TPGS_STATE_OPTIMIZED;
sdev_printk(KERN_INFO, h->sdev,
@@ -471,6 +481,11 @@ static int alua_check_sense(struct scsi_device *sdev,
* Power On, Reset, or Bus Device Reset, just retry.
*/
return ADD_TO_MLQUEUE;
+ if (sense_hdr->asc == 0x29 && sense_hdr->ascq == 0x04)
+ /*
+ * Device internal reset
+ */
+ return ADD_TO_MLQUEUE;
if (sense_hdr->asc == 0x2a && sense_hdr->ascq == 0x01)
/*
* Mode Parameters Changed
@@ -507,22 +522,30 @@ static int alua_check_sense(struct scsi_device *sdev,
/*
* alua_rtpg - Evaluate REPORT TARGET GROUP STATES
* @sdev: the device to be evaluated.
+ * @wait_for_transition: if nonzero, wait ALUA_FAILOVER_TIMEOUT seconds for device to exit transitioning state
*
* Evaluate the Target Port Group State.
* Returns SCSI_DH_DEV_OFFLINED if the path is
* found to be unusable.
*/
-static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
+static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h, int wait_for_transition)
{
struct scsi_sense_hdr sense_hdr;
int len, k, off, valid_states = 0;
unsigned char *ucp;
unsigned err;
- unsigned long expiry, interval = 1000;
+ bool rtpg_ext_hdr_req = 1;
+ unsigned long expiry, interval = 0;
+ unsigned int tpg_desc_tbl_off;
+ unsigned char orig_transition_tmo;
+
+ if (!h->transition_tmo)
+ expiry = round_jiffies_up(jiffies + ALUA_FAILOVER_TIMEOUT * HZ);
+ else
+ expiry = round_jiffies_up(jiffies + h->transition_tmo * HZ);
- expiry = round_jiffies_up(jiffies + ALUA_FAILOVER_TIMEOUT);
retry:
- err = submit_rtpg(sdev, h);
+ err = submit_rtpg(sdev, h, rtpg_ext_hdr_req);
if (err == SCSI_DH_IO && h->senselen > 0) {
err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE,
@@ -530,6 +553,21 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
if (!err)
return SCSI_DH_IO;
+ /*
+ * submit_rtpg() has failed on existing arrays
+ * when requesting extended header info, and
+ * the array doesn't support extended headers,
+ * even though it shouldn't according to T10.
+ * The retry without rtpg_ext_hdr_req set
+ * handles this.
+ */
+ if (rtpg_ext_hdr_req == 1 &&
+ sense_hdr.sense_key == ILLEGAL_REQUEST &&
+ sense_hdr.asc == 0x24 && sense_hdr.ascq == 0) {
+ rtpg_ext_hdr_req = 0;
+ goto retry;
+ }
+
err = alua_check_sense(sdev, &sense_hdr);
if (err == ADD_TO_MLQUEUE && time_before(jiffies, expiry))
goto retry;
@@ -556,7 +594,28 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
goto retry;
}
- for (k = 4, ucp = h->buff + 4; k < len; k += off, ucp += off) {
+ orig_transition_tmo = h->transition_tmo;
+ if ((h->buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR && h->buff[5] != 0)
+ h->transition_tmo = h->buff[5];
+ else
+ h->transition_tmo = ALUA_FAILOVER_TIMEOUT;
+
+ if (wait_for_transition && (orig_transition_tmo != h->transition_tmo)) {
+ sdev_printk(KERN_INFO, sdev,
+ "%s: transition timeout set to %d seconds\n",
+ ALUA_DH_NAME, h->transition_tmo);
+ expiry = jiffies + h->transition_tmo * HZ;
+ }
+
+ if ((h->buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR)
+ tpg_desc_tbl_off = 8;
+ else
+ tpg_desc_tbl_off = 4;
+
+ for (k = tpg_desc_tbl_off, ucp = h->buff + tpg_desc_tbl_off;
+ k < len;
+ k += off, ucp += off) {
+
if (h->group_id == (ucp[2] << 8) + ucp[3]) {
h->state = ucp[0] & 0x0f;
h->pref = ucp[0] >> 7;
@@ -579,19 +638,23 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
switch (h->state) {
case TPGS_STATE_TRANSITIONING:
- if (time_before(jiffies, expiry)) {
- /* State transition, retry */
- interval *= 2;
- msleep(interval);
- goto retry;
+ if (wait_for_transition) {
+ if (time_before(jiffies, expiry)) {
+ /* State transition, retry */
+ interval += 2000;
+ msleep(interval);
+ goto retry;
+ }
+ err = SCSI_DH_RETRY;
+ } else {
+ err = SCSI_DH_OK;
}
+
/* Transitioning time exceeded, set port to standby */
- err = SCSI_DH_RETRY;
h->state = TPGS_STATE_STANDBY;
break;
case TPGS_STATE_OFFLINE:
- case TPGS_STATE_UNAVAILABLE:
- /* Path unusable for unavailable/offline */
+ /* Path unusable */
err = SCSI_DH_DEV_OFFLINED;
break;
default:
@@ -621,7 +684,7 @@ static int alua_initialize(struct scsi_device *sdev, struct alua_dh_data *h)
if (err != SCSI_DH_OK)
goto out;
- err = alua_rtpg(sdev, h);
+ err = alua_rtpg(sdev, h, 0);
if (err != SCSI_DH_OK)
goto out;
@@ -660,6 +723,10 @@ static int alua_set_params(struct scsi_device *sdev, const char *params)
return result;
}
+static uint optimize_stpg;
+module_param(optimize_stpg, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(optimize_stpg, "Allow use of a non-optimized path, rather than sending a STPG, when implicit TPGS is supported (0=No,1=Yes). Default is 0.");
+
/*
* alua_activate - activate a path
* @sdev: device on the path to be activated
@@ -677,10 +744,13 @@ static int alua_activate(struct scsi_device *sdev,
int err = SCSI_DH_OK;
int stpg = 0;
- err = alua_rtpg(sdev, h);
+ err = alua_rtpg(sdev, h, 1);
if (err != SCSI_DH_OK)
goto out;
+ if (optimize_stpg)
+ h->flags |= ALUA_OPTIMIZE_STPG;
+
if (h->tpgs & TPGS_MODE_EXPLICIT) {
switch (h->state) {
case TPGS_STATE_NONOPTIMIZED:
@@ -691,9 +761,9 @@ static int alua_activate(struct scsi_device *sdev,
stpg = 0;
break;
case TPGS_STATE_STANDBY:
+ case TPGS_STATE_UNAVAILABLE:
stpg = 1;
break;
- case TPGS_STATE_UNAVAILABLE:
case TPGS_STATE_OFFLINE:
err = SCSI_DH_IO;
break;