Add basic support just to get the per-port group state.
This support does not account of state transitioning, sdev port group
reconfiguration, etc, required for full support.
libmultipath callbacks scsi_mpath_is_optimized() and
scsi_mpath_is_disabled() are updated to take account of the ALUA-provided
path information.
As before, for no ALUA support (and scsi_multipath_always on) we assume
that the paths are all optimized.
Much of this code in scsi_mpath_alua_init() is copied from scsi_dh_alua.c,
originally authored by Hannes Reinecke.
Signed-off-by: John Garry <[email protected]>
---
drivers/scsi/scsi_multipath.c | 163 ++++++++++++++++++++++++++++++++--
include/scsi/scsi_multipath.h | 3 +
2 files changed, 160 insertions(+), 6 deletions(-)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 1489c7e979167..0a314080bf0a5 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -4,6 +4,7 @@
*
*/
+#include <scsi/scsi_alua.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_proto.h>
@@ -346,18 +347,29 @@ static bool scsi_mpath_is_disabled(struct mpath_device
*mpath_device)
to_scsi_mpath_device(mpath_device);
struct scsi_device *sdev = scsi_mpath_dev->sdev;
enum scsi_device_state sdev_state = sdev->sdev_state;
+ int alua_state = scsi_mpath_dev->alua_state;
if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_CANCEL)
return false;
- return true;
+ if (alua_state == SCSI_ACCESS_STATE_OPTIMAL ||
+ alua_state == SCSI_ACCESS_STATE_ACTIVE)
+ return true;
+
+ return false;
}
static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+
if (scsi_mpath_is_disabled(mpath_device))
return false;
- return true;
+ if (scsi_mpath_dev->alua_state == SCSI_ACCESS_STATE_OPTIMAL)
+ return true;
+ return false;
+
}
/* Until we have ALUA support, we're always optimised */
@@ -366,7 +378,7 @@ static enum mpath_access_state scsi_mpath_get_access_state(
{
if (scsi_mpath_is_disabled(mpath_device))
return MPATH_STATE_INVALID;
- return MPATH_STATE_OPTIMIZED;
+ return scsi_mpath_is_optimized(mpath_device);
}
static bool scsi_mpath_available_path(struct mpath_device *mpath_device, bool *available)
@@ -579,16 +591,147 @@ static void scsi_multipath_sdev_uninit(struct
scsi_device *sdev)
sdev->scsi_mpath_dev = NULL;
}
+static int scsi_mpath_alua_init(struct scsi_device *sdev)
+{
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_sense_hdr sense_hdr;
+ int len, k, off, bufflen = ALUA_RTPG_SIZE;
+ unsigned char *desc, *buff;
+ unsigned int tpg_desc_tbl_off;
+ int group_id, rel_port = -1;
+ bool ext_hdr_unsupp = false;
+ int ret;
+
+ group_id = scsi_vpd_tpg_id(sdev, &rel_port);
+ if (group_id < 0) {
+ /*
+ * Internal error; TPGS supported but required
+ * VPD identification descriptors not present.
+ * Disable ALUA support.
+ */
+ sdev_printk(KERN_INFO, sdev,
+ "%s: No target port descriptors found\n",
+ __func__);
+ return -EIO;
+ }
+
+ buff = kzalloc(bufflen, GFP_KERNEL);
+ if (!buff)
+ return -ENOMEM;
+ retry:
+ ret = submit_rtpg(sdev, buff, bufflen, &sense_hdr,
+ ext_hdr_unsupp);
+
+ if (ret) {
+ if (ret < 0 || !scsi_sense_valid(&sense_hdr)) {
+ sdev_printk(KERN_INFO, sdev,
+ "%s: rtpg failed, result %d\n",
+ __func__, ret);
+ kfree(buff);
+ if (ret < 0)
+ return -EBUSY;
+ if (host_byte(ret) == DID_NO_CONNECT)
+ return -ENODEV;
+ return -EIO;
+ }
+
+ /*
+ * 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.
+ * Note: some arrays return a sense key of ILLEGAL_REQUEST
+ * with ASC 00h if they don't support the extended header.
+ */
+ if (ext_hdr_unsupp &&
+ sense_hdr.sense_key == ILLEGAL_REQUEST) {
+ ext_hdr_unsupp = true;
+ goto retry;
+ }
+ /*
+ * If the array returns with 'ALUA state transition'
+ * sense code here it cannot return RTPG data during
+ * transition. So set the state to 'transitioning' directly.
+ */
+ if (sense_hdr.sense_key == NOT_READY &&
+ sense_hdr.asc == 0x04 && sense_hdr.ascq == 0x0a)
+ goto out;
+
+ /*
+ * Retry on any other UNIT ATTENTION occurred.
+ */
+ if (sense_hdr.sense_key == UNIT_ATTENTION) {
+ scsi_print_sense_hdr(sdev, __func__, &sense_hdr);
+ kfree(buff);
+ return -EAGAIN;
+ }
+ sdev_printk(KERN_ERR, sdev, "%s: rtpg failed\n",
+ __func__);
+ scsi_print_sense_hdr(sdev, __func__, &sense_hdr);
+ kfree(buff);
+ return -EIO;
+ }
+
+ len = get_unaligned_be32(&buff[0]) + 4;
+
+ if (len > bufflen) {
+ /* Resubmit with the correct length */
+ kfree(buff);
+ bufflen = len;
+ buff = kmalloc(bufflen, GFP_KERNEL);
+ if (!buff) {
+ /* Temporary failure, bypass */
+ return -EBUSY;
+ }
+ goto retry;
+ }
+
+ if ((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, desc = buff + tpg_desc_tbl_off;
+ k < len;
+ k += off, desc += off) {
+ u16 group_id_found = get_unaligned_be16(&desc[2]);
+
+ if (group_id_found == group_id) {
+ int valid_states, state, pref;
+
+ state = desc[0] & 0x0f;
+ pref = desc[0] >> 7;
+ valid_states = desc[1];
+
+ alua_print_info(sdev, group_id, state, pref,
valid_states);
+
+ scsi_mpath_dev->alua_state = state;
+ scsi_mpath_dev->alua_pref = pref;
+ scsi_mpath_dev->alua_valid_states = valid_states;
+ goto out;
+ }
+
+ off = 8 + (desc[7] * 4);
+ }
+
+out:
+ kfree(buff);
+ return 0;
+}
+
int scsi_mpath_dev_alloc(struct scsi_device *sdev)
{
struct scsi_mpath_head *scsi_mpath_head;
- int ret;
+ int ret, tpgs;
if (!scsi_multipath)
return 0;
- if (!scsi_device_tpgs(sdev) && !scsi_multipath_always) {
- sdev_printk(KERN_NOTICE, sdev, "tpgs are required for multipath
support\n");
+ tpgs = alua_check_tpgs(sdev);
+ if (!(tpgs & TPGS_MODE_IMPLICIT) && !scsi_multipath_always) {
+ sdev_printk(KERN_DEBUG, sdev, "IMPLICIT TPGS are required for
multipath support\n");
return 0;
}
@@ -622,6 +765,14 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev)
sdev->scsi_mpath_dev->scsi_mpath_head = scsi_mpath_head;
found:
+ if (tpgs & TPGS_MODE_IMPLICIT) {
+ ret = scsi_mpath_alua_init(sdev);
+ if (ret)
+ goto out_put_head;
+ } else {
+ sdev->scsi_mpath_dev->alua_state = SCSI_ACCESS_STATE_OPTIMAL;
+ }
+
sdev->scsi_mpath_dev->index = ida_alloc(&scsi_mpath_head->ida,
GFP_KERNEL);
if (sdev->scsi_mpath_dev->index < 0) {
ret = sdev->scsi_mpath_dev->index;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 2011447f482d6..7c7ee2fb7def7 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -38,6 +38,9 @@ struct scsi_mpath_device {
int index;
atomic_t nr_active;
struct scsi_mpath_head *scsi_mpath_head;
+ int alua_state;
+ int alua_pref;
+ int alua_valid_states;
char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
};