[SCSI] scsi_dh: Implement match callback function
[linux-2.6.git] / drivers / scsi / device_handler / scsi_dh_alua.c
index 6e46448..80c5cf3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Generic SCSI-3 ALUA SCSI Device Handler
  *
- * Copyright (C) 2007, 2008 Hannes Reinecke, SUSE Linux Products GmbH.
+ * Copyright (C) 2007-2010 Hannes Reinecke, SUSE Linux Products GmbH.
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  *
  */
+#include <linux/slab.h>
+#include <linux/delay.h>
 #include <scsi/scsi.h>
 #include <scsi/scsi_eh.h>
 #include <scsi/scsi_dh.h>
 
 #define ALUA_DH_NAME "alua"
-#define ALUA_DH_VER "1.2"
+#define ALUA_DH_VER "1.3"
 
 #define TPGS_STATE_OPTIMIZED           0x0
 #define TPGS_STATE_NONOPTIMIZED                0x1
 #define TPGS_STATE_STANDBY             0x2
 #define TPGS_STATE_UNAVAILABLE         0x3
+#define TPGS_STATE_LBA_DEPENDENT       0x4
 #define TPGS_STATE_OFFLINE             0xe
 #define TPGS_STATE_TRANSITIONING       0xf
 
@@ -38,6 +41,7 @@
 #define TPGS_SUPPORT_NONOPTIMIZED      0x02
 #define TPGS_SUPPORT_STANDBY           0x04
 #define TPGS_SUPPORT_UNAVAILABLE       0x08
+#define TPGS_SUPPORT_LBA_DEPENDENT     0x10
 #define TPGS_SUPPORT_OFFLINE           0x40
 #define TPGS_SUPPORT_TRANSITION                0x80
 
@@ -60,11 +64,17 @@ struct alua_dh_data {
        int                     bufflen;
        unsigned char           sense[SCSI_SENSE_BUFFERSIZE];
        int                     senselen;
+       struct scsi_device      *sdev;
+       activate_complete       callback_fn;
+       void                    *callback_data;
 };
 
 #define ALUA_POLICY_SWITCH_CURRENT     0
 #define ALUA_POLICY_SWITCH_ALL         1
 
+static char print_alua_state(int);
+static int alua_check_sense(struct scsi_device *, struct scsi_sense_hdr *);
+
 static inline struct alua_dh_data *get_alua_data(struct scsi_device *sdev)
 {
        struct scsi_dh_data *scsi_dh_data = sdev->scsi_dh_data;
@@ -97,19 +107,20 @@ static struct request *get_alua_req(struct scsi_device *sdev,
 
        if (!rq) {
                sdev_printk(KERN_INFO, sdev,
-                           "%s: blk_get_request failed\n", __FUNCTION__);
+                           "%s: blk_get_request failed\n", __func__);
                return NULL;
        }
 
        if (buflen && blk_rq_map_kern(q, rq, buffer, buflen, GFP_NOIO)) {
                blk_put_request(rq);
                sdev_printk(KERN_INFO, sdev,
-                           "%s: blk_rq_map_kern failed\n", __FUNCTION__);
+                           "%s: blk_rq_map_kern failed\n", __func__);
                return NULL;
        }
 
        rq->cmd_type = REQ_TYPE_BLOCK_PC;
-       rq->cmd_flags |= REQ_FAILFAST | REQ_NOMERGE;
+       rq->cmd_flags |= REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT |
+                        REQ_FAILFAST_DRIVER;
        rq->retries = ALUA_FAILOVER_RETRIES;
        rq->timeout = ALUA_FAILOVER_TIMEOUT;
 
@@ -117,43 +128,6 @@ static struct request *get_alua_req(struct scsi_device *sdev,
 }
 
 /*
- * submit_std_inquiry - Issue a standard INQUIRY command
- * @sdev: sdev the command should be send to
- */
-static int submit_std_inquiry(struct scsi_device *sdev, struct alua_dh_data *h)
-{
-       struct request *rq;
-       int err = SCSI_DH_RES_TEMP_UNAVAIL;
-
-       rq = get_alua_req(sdev, h->inq, ALUA_INQUIRY_SIZE, READ);
-       if (!rq)
-               goto done;
-
-       /* Prepare the command. */
-       rq->cmd[0] = INQUIRY;
-       rq->cmd[1] = 0;
-       rq->cmd[2] = 0;
-       rq->cmd[4] = ALUA_INQUIRY_SIZE;
-       rq->cmd_len = COMMAND_SIZE(INQUIRY);
-
-       rq->sense = h->sense;
-       memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE);
-       rq->sense_len = h->senselen = 0;
-
-       err = blk_execute_rq(rq->q, NULL, rq, 1);
-       if (err == -EIO) {
-               sdev_printk(KERN_INFO, sdev,
-                           "%s: std inquiry failed with %x\n",
-                           ALUA_DH_NAME, rq->errors);
-               h->senselen = rq->sense_len;
-               err = SCSI_DH_IO;
-       }
-       blk_put_request(rq);
-done:
-       return err;
-}
-
-/*
  * submit_vpd_inquiry - Issue an INQUIRY VPD page 0x83 command
  * @sdev: sdev the command should be sent to
  */
@@ -230,28 +204,83 @@ done:
 }
 
 /*
+ * alua_stpg - Evaluate SET TARGET GROUP STATES
+ * @sdev: the device to be evaluated
+ * @state: the new target group state
+ *
+ * Send a SET TARGET GROUP STATES command to the device.
+ * We only have to test here if we should resubmit the command;
+ * any other error is assumed as a failure.
+ */
+static void stpg_endio(struct request *req, int error)
+{
+       struct alua_dh_data *h = req->end_io_data;
+       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) {
+               err = SCSI_DH_IO;
+               goto done;
+       }
+
+       if (h->senselen > 0) {
+               err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE,
+                                          &sense_hdr);
+               if (!err) {
+                       err = SCSI_DH_IO;
+                       goto done;
+               }
+               err = alua_check_sense(h->sdev, &sense_hdr);
+               if (err == ADD_TO_MLQUEUE) {
+                       err = SCSI_DH_RETRY;
+                       goto done;
+               }
+               sdev_printk(KERN_INFO, h->sdev,
+                           "%s: stpg sense code: %02x/%02x/%02x\n",
+                           ALUA_DH_NAME, sense_hdr.sense_key,
+                           sense_hdr.asc, sense_hdr.ascq);
+               err = SCSI_DH_IO;
+       }
+       if (err == SCSI_DH_OK) {
+               h->state = TPGS_STATE_OPTIMIZED;
+               sdev_printk(KERN_INFO, h->sdev,
+                           "%s: port group %02x switched to state %c\n",
+                           ALUA_DH_NAME, h->group_id,
+                           print_alua_state(h->state));
+       }
+done:
+       req->end_io_data = NULL;
+       __blk_put_request(req->q, req);
+       if (h->callback_fn) {
+               h->callback_fn(h->callback_data, err);
+               h->callback_fn = h->callback_data = NULL;
+       }
+       return;
+}
+
+/*
  * submit_stpg - Issue a SET TARGET GROUP STATES command
- * @sdev: sdev the command should be sent to
  *
  * Currently we're only setting the current target port group state
  * to 'active/optimized' and let the array firmware figure out
  * the states of the remaining groups.
  */
-static unsigned submit_stpg(struct scsi_device *sdev, struct alua_dh_data *h)
+static unsigned submit_stpg(struct alua_dh_data *h)
 {
        struct request *rq;
-       int err = SCSI_DH_RES_TEMP_UNAVAIL;
        int stpg_len = 8;
+       struct scsi_device *sdev = h->sdev;
 
        /* Prepare the data buffer */
        memset(h->buff, 0, stpg_len);
        h->buff[4] = TPGS_STATE_OPTIMIZED & 0x0f;
-       h->buff[6] = (h->group_id >> 8) & 0x0f;
-       h->buff[7] = h->group_id & 0x0f;
+       h->buff[6] = (h->group_id >> 8) & 0xff;
+       h->buff[7] = h->group_id & 0xff;
 
        rq = get_alua_req(sdev, h->buff, stpg_len, WRITE);
        if (!rq)
-               goto done;
+               return SCSI_DH_RES_TEMP_UNAVAIL;
 
        /* Prepare the command. */
        rq->cmd[0] = MAINTENANCE_OUT;
@@ -265,38 +294,24 @@ static unsigned submit_stpg(struct scsi_device *sdev, struct alua_dh_data *h)
        rq->sense = h->sense;
        memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE);
        rq->sense_len = h->senselen = 0;
+       rq->end_io_data = h;
 
-       err = blk_execute_rq(rq->q, NULL, rq, 1);
-       if (err == -EIO) {
-               sdev_printk(KERN_INFO, sdev,
-                           "%s: stpg failed with %x\n",
-                           ALUA_DH_NAME, rq->errors);
-               h->senselen = rq->sense_len;
-               err = SCSI_DH_IO;
-       }
-       blk_put_request(rq);
-done:
-       return err;
+       blk_execute_rq_nowait(rq->q, NULL, rq, 1, stpg_endio);
+       return SCSI_DH_OK;
 }
 
 /*
- * alua_std_inquiry - Evaluate standard INQUIRY command
+ * alua_check_tpgs - Evaluate TPGS setting
  * @sdev: device to be checked
  *
- * Just extract the TPGS setting to find out if ALUA
+ * Examine the TPGS setting of the sdev to find out if ALUA
  * is supported.
  */
-static int alua_std_inquiry(struct scsi_device *sdev, struct alua_dh_data *h)
+static int alua_check_tpgs(struct scsi_device *sdev, struct alua_dh_data *h)
 {
-       int err;
-
-       err = submit_std_inquiry(sdev, h);
-
-       if (err != SCSI_DH_OK)
-               return err;
+       int err = SCSI_DH_OK;
 
-       /* Check TPGS setting */
-       h->tpgs = (h->inq[5] >> 4) & 0x3;
+       h->tpgs = scsi_device_tpgs(sdev);
        switch (h->tpgs) {
        case TPGS_MODE_EXPLICIT|TPGS_MODE_IMPLICIT:
                sdev_printk(KERN_INFO, sdev,
@@ -407,6 +422,8 @@ static char print_alua_state(int state)
                return 'S';
        case TPGS_STATE_UNAVAILABLE:
                return 'U';
+       case TPGS_STATE_LBA_DEPENDENT:
+               return 'L';
        case TPGS_STATE_OFFLINE:
                return 'O';
        case TPGS_STATE_TRANSITIONING:
@@ -425,7 +442,7 @@ static int alua_check_sense(struct scsi_device *sdev,
                        /*
                         * LUN Not Accessible - ALUA state transition
                         */
-                       return NEEDS_RETRY;
+                       return ADD_TO_MLQUEUE;
                if (sense_hdr->asc == 0x04 && sense_hdr->ascq == 0x0b)
                        /*
                         * LUN Not Accessible -- Target port in standby state
@@ -447,19 +464,28 @@ static int alua_check_sense(struct scsi_device *sdev,
                        /*
                         * Power On, Reset, or Bus Device Reset, just retry.
                         */
-                       return NEEDS_RETRY;
+                       return ADD_TO_MLQUEUE;
                if (sense_hdr->asc == 0x2a && sense_hdr->ascq == 0x06) {
                        /*
                         * ALUA state changed
                         */
-                       return NEEDS_RETRY;
+                       return ADD_TO_MLQUEUE;
                }
                if (sense_hdr->asc == 0x2a && sense_hdr->ascq == 0x07) {
                        /*
                         * Implicit ALUA state transition failed
                         */
-                       return NEEDS_RETRY;
+                       return ADD_TO_MLQUEUE;
                }
+               if (sense_hdr->asc == 0x3f && sense_hdr->ascq == 0x0e) {
+                       /*
+                        * REPORTED_LUNS_DATA_HAS_CHANGED is reported
+                        * when switching controllers on targets like
+                        * Intel Multi-Flex. We can just retry.
+                        */
+                       return ADD_TO_MLQUEUE;
+               }
+
                break;
        }
 
@@ -467,56 +493,12 @@ static int alua_check_sense(struct scsi_device *sdev,
 }
 
 /*
- * alua_stpg - Evaluate SET TARGET GROUP STATES
- * @sdev: the device to be evaluated
- * @state: the new target group state
- *
- * Send a SET TARGET GROUP STATES command to the device.
- * We only have to test here if we should resubmit the command;
- * any other error is assumed as a failure.
- */
-static int alua_stpg(struct scsi_device *sdev, int state,
-                    struct alua_dh_data *h)
-{
-       struct scsi_sense_hdr sense_hdr;
-       unsigned err;
-       int retry = ALUA_FAILOVER_RETRIES;
-
- retry:
-       err = submit_stpg(sdev, h);
-       if (err == SCSI_DH_IO && h->senselen > 0) {
-               err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE,
-                                          &sense_hdr);
-               if (!err)
-                       return SCSI_DH_IO;
-               err = alua_check_sense(sdev, &sense_hdr);
-               if (retry > 0 && err == NEEDS_RETRY) {
-                       retry--;
-                       goto retry;
-               }
-               sdev_printk(KERN_INFO, sdev,
-                           "%s: stpg sense code: %02x/%02x/%02x\n",
-                           ALUA_DH_NAME, sense_hdr.sense_key,
-                           sense_hdr.asc, sense_hdr.ascq);
-               err = SCSI_DH_IO;
-       }
-       if (err == SCSI_DH_OK) {
-               h->state = state;
-               sdev_printk(KERN_INFO, sdev,
-                           "%s: port group %02x switched to state %c\n",
-                           ALUA_DH_NAME, h->group_id,
-                           print_alua_state(h->state) );
-       }
-       return err;
-}
-
-/*
  * alua_rtpg - Evaluate REPORT TARGET GROUP STATES
  * @sdev: the device to be evaluated.
  *
  * Evaluate the Target Port Group State.
  * Returns SCSI_DH_DEV_OFFLINED if the path is
- * found to be unuseable.
+ * found to be unusable.
  */
 static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
 {
@@ -524,7 +506,9 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
        int len, k, off, valid_states = 0;
        char *ucp;
        unsigned err;
+       unsigned long expiry, interval = 10;
 
+       expiry = round_jiffies_up(jiffies + ALUA_FAILOVER_TIMEOUT);
  retry:
        err = submit_rtpg(sdev, h);
 
@@ -535,7 +519,7 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
                        return SCSI_DH_IO;
 
                err = alua_check_sense(sdev, &sense_hdr);
-               if (err == NEEDS_RETRY)
+               if (err == ADD_TO_MLQUEUE && time_before(jiffies, expiry))
                        goto retry;
                sdev_printk(KERN_INFO, sdev,
                            "%s: rtpg sense code %02x/%02x/%02x\n",
@@ -553,7 +537,7 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
                /* Resubmit with the correct length */
                if (realloc_buffer(h, len)) {
                        sdev_printk(KERN_WARNING, sdev,
-                                   "%s: kmalloc buffer failed\n",__FUNCTION__);
+                                   "%s: kmalloc buffer failed\n",__func__);
                        /* Temporary failure, bypass */
                        return SCSI_DH_DEV_TEMP_BUSY;
                }
@@ -569,38 +553,37 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_dh_data *h)
        }
 
        sdev_printk(KERN_INFO, sdev,
-                   "%s: port group %02x state %c supports %c%c%c%c%c%c\n",
+                   "%s: port group %02x state %c supports %c%c%c%c%c%c%c\n",
                    ALUA_DH_NAME, h->group_id, print_alua_state(h->state),
                    valid_states&TPGS_SUPPORT_TRANSITION?'T':'t',
                    valid_states&TPGS_SUPPORT_OFFLINE?'O':'o',
+                   valid_states&TPGS_SUPPORT_LBA_DEPENDENT?'L':'l',
                    valid_states&TPGS_SUPPORT_UNAVAILABLE?'U':'u',
                    valid_states&TPGS_SUPPORT_STANDBY?'S':'s',
                    valid_states&TPGS_SUPPORT_NONOPTIMIZED?'N':'n',
                    valid_states&TPGS_SUPPORT_OPTIMIZED?'A':'a');
 
-       if (h->tpgs & TPGS_MODE_EXPLICIT) {
-               switch (h->state) {
-               case TPGS_STATE_TRANSITIONING:
+       switch (h->state) {
+       case TPGS_STATE_TRANSITIONING:
+               if (time_before(jiffies, expiry)) {
                        /* State transition, retry */
+                       interval *= 10;
+                       msleep(interval);
                        goto retry;
-                       break;
-               case TPGS_STATE_OFFLINE:
-                       /* Path is offline, fail */
-                       err = SCSI_DH_DEV_OFFLINED;
-                       break;
-               default:
-                       break;
                }
-       } else {
-               /* Only Implicit ALUA support */
-               if (h->state == TPGS_STATE_OPTIMIZED ||
-                   h->state == TPGS_STATE_NONOPTIMIZED ||
-                   h->state == TPGS_STATE_STANDBY)
-                       /* Useable path if active */
-                       err = SCSI_DH_OK;
-               else
-                       /* Path unuseable for unavailable/offline */
-                       err = SCSI_DH_DEV_OFFLINED;
+               /* 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 */
+               err = SCSI_DH_DEV_OFFLINED;
+               break;
+       default:
+               /* Useable path if active */
+               err = SCSI_DH_OK;
+               break;
        }
        return err;
 }
@@ -616,7 +599,7 @@ static int alua_initialize(struct scsi_device *sdev, struct alua_dh_data *h)
 {
        int err;
 
-       err = alua_std_inquiry(sdev, h);
+       err = alua_check_tpgs(sdev, h);
        if (err != SCSI_DH_OK)
                goto out;
 
@@ -642,7 +625,8 @@ out:
  * based on a certain policy. But until we actually encounter them it
  * should be okay.
  */
-static int alua_activate(struct scsi_device *sdev)
+static int alua_activate(struct scsi_device *sdev,
+                       activate_complete fn, void *data)
 {
        struct alua_dh_data *h = get_alua_data(sdev);
        int err = SCSI_DH_OK;
@@ -653,11 +637,21 @@ static int alua_activate(struct scsi_device *sdev)
                        goto out;
        }
 
-       if (h->tpgs == TPGS_MODE_EXPLICIT && h->state != TPGS_STATE_OPTIMIZED)
-               err = alua_stpg(sdev, TPGS_STATE_OPTIMIZED, h);
+       if (h->tpgs & TPGS_MODE_EXPLICIT &&
+           h->state != TPGS_STATE_OPTIMIZED &&
+           h->state != TPGS_STATE_LBA_DEPENDENT) {
+               h->callback_fn = fn;
+               h->callback_data = data;
+               err = submit_stpg(h);
+               if (err == SCSI_DH_OK)
+                       return 0;
+               h->callback_fn = h->callback_data = NULL;
+       }
 
 out:
-       return err;
+       if (fn)
+               fn(data, err);
+       return 0;
 }
 
 /*
@@ -671,8 +665,11 @@ static int alua_prep_fn(struct scsi_device *sdev, struct request *req)
        struct alua_dh_data *h = get_alua_data(sdev);
        int ret = BLKPREP_OK;
 
-       if (h->state != TPGS_STATE_OPTIMIZED &&
-           h->state != TPGS_STATE_NONOPTIMIZED) {
+       if (h->state == TPGS_STATE_TRANSITIONING)
+               ret = BLKPREP_DEFER;
+       else if (h->state != TPGS_STATE_OPTIMIZED &&
+                h->state != TPGS_STATE_NONOPTIMIZED &&
+                h->state != TPGS_STATE_LBA_DEPENDENT) {
                ret = BLKPREP_KILL;
                req->cmd_flags |= REQ_QUIET;
        }
@@ -680,18 +677,10 @@ static int alua_prep_fn(struct scsi_device *sdev, struct request *req)
 
 }
 
-const struct scsi_dh_devlist alua_dev_list[] = {
-       {"HP", "MSA VOLUME" },
-       {"HP", "HSV101" },
-       {"HP", "HSV111" },
-       {"HP", "HSV200" },
-       {"HP", "HSV210" },
-       {"HP", "HSV300" },
-       {"IBM", "2107900" },
-       {"IBM", "2145" },
-       {"Pillar", "Axiom" },
-       {NULL, NULL}
-};
+static bool alua_match(struct scsi_device *sdev)
+{
+       return (scsi_device_tpgs(sdev) != 0);
+}
 
 static int alua_bus_attach(struct scsi_device *sdev);
 static void alua_bus_detach(struct scsi_device *sdev);
@@ -699,12 +688,12 @@ static void alua_bus_detach(struct scsi_device *sdev);
 static struct scsi_device_handler alua_dh = {
        .name = ALUA_DH_NAME,
        .module = THIS_MODULE,
-       .devlist = alua_dev_list,
        .attach = alua_bus_attach,
        .detach = alua_bus_detach,
        .prep_fn = alua_prep_fn,
        .check_sense = alua_check_sense,
        .activate = alua_activate,
+       .match = alua_match,
 };
 
 /*
@@ -718,7 +707,7 @@ static int alua_bus_attach(struct scsi_device *sdev)
        unsigned long flags;
        int err = SCSI_DH_OK;
 
-       scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *)
+       scsi_dh_data = kzalloc(sizeof(*scsi_dh_data)
                               + sizeof(*h) , GFP_KERNEL);
        if (!scsi_dh_data) {
                sdev_printk(KERN_ERR, sdev, "%s: Attach failed\n",
@@ -734,9 +723,10 @@ static int alua_bus_attach(struct scsi_device *sdev)
        h->rel_port = -1;
        h->buff = h->inq;
        h->bufflen = ALUA_INQUIRY_SIZE;
+       h->sdev = sdev;
 
        err = alua_initialize(sdev, h);
-       if (err != SCSI_DH_OK)
+       if ((err != SCSI_DH_OK) && (err != SCSI_DH_DEV_OFFLINED))
                goto failed;
 
        if (!try_module_get(THIS_MODULE))