Add scsi_alua_rtpg(), which does the same as alua_rtpg() from
scsi_dh_alua.c
Members of the per-sdev alua_data structure are updated from same in
alua_dh_data.
Signed-off-by: John Garry <[email protected]>
---
drivers/scsi/scsi_alua.c | 311 +++++++++++++++++++++++++++++++++++++++
include/scsi/scsi_alua.h | 8 +
2 files changed, 319 insertions(+)
diff --git a/drivers/scsi/scsi_alua.c b/drivers/scsi/scsi_alua.c
index a5a67c6deff17..50c1d17b52dc7 100644
--- a/drivers/scsi/scsi_alua.c
+++ b/drivers/scsi/scsi_alua.c
@@ -6,6 +6,8 @@
* All rights reserved.
*/
+#include <linux/unaligned.h>
+
#include <scsi/scsi.h>
#include <scsi/scsi_proto.h>
#include <scsi/scsi_dbg.h>
@@ -16,6 +18,314 @@
static struct workqueue_struct *kalua_wq;
+#define TPGS_SUPPORT_NONE 0x00
+#define TPGS_SUPPORT_OPTIMIZED 0x01
+#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
+#define TPGS_SUPPORT_ALL 0xdf
+
+#define RTPG_FMT_MASK 0x70
+#define RTPG_FMT_EXT_HDR 0x10
+
+#define ALUA_RTPG_SIZE 128
+#define ALUA_FAILOVER_TIMEOUT 60
+#define ALUA_FAILOVER_RETRIES 5
+#define ALUA_RTPG_DELAY_MSECS 5
+#define ALUA_RTPG_RETRY_DELAY 2
+
+/*
+ * submit_rtpg - Issue a REPORT TARGET GROUP STATES command
+ * @sdev: sdev the command should be sent to
+ */
+static int submit_rtpg(struct scsi_device *sdev, unsigned char *buff,
+ int bufflen, struct scsi_sense_hdr *sshdr)
+{
+ u8 cdb[MAX_COMMAND_SIZE];
+ blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV |
+ REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER;
+ const struct scsi_exec_args exec_args = {
+ .sshdr = sshdr,
+ };
+
+ /* Prepare the command. */
+ memset(cdb, 0x0, MAX_COMMAND_SIZE);
+ cdb[0] = MAINTENANCE_IN;
+ if (!sdev->alua->rtpg_ext_hdr_unsupp)
+ cdb[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT;
+ else
+ cdb[1] = MI_REPORT_TARGET_PGS;
+ put_unaligned_be32(bufflen, &cdb[6]);
+
+ return scsi_execute_cmd(sdev, cdb, opf, buff, bufflen,
+ ALUA_FAILOVER_TIMEOUT * HZ,
+ ALUA_FAILOVER_RETRIES, &exec_args);
+}
+
+static char print_alua_state(unsigned char state)
+{
+ switch (state) {
+ case SCSI_ACCESS_STATE_OPTIMAL:
+ return 'A';
+ case SCSI_ACCESS_STATE_ACTIVE:
+ return 'N';
+ case SCSI_ACCESS_STATE_STANDBY:
+ return 'S';
+ case SCSI_ACCESS_STATE_UNAVAILABLE:
+ return 'U';
+ case SCSI_ACCESS_STATE_LBA:
+ return 'L';
+ case SCSI_ACCESS_STATE_OFFLINE:
+ return 'O';
+ case SCSI_ACCESS_STATE_TRANSITIONING:
+ return 'T';
+ default:
+ return 'X';
+ }
+}
+
+/*
+ * scsi_alua_rtpg - Evaluate REPORT TARGET GROUP STATES
+ * @sdev: the device to be evaluated.
+ *
+ * Evaluate the Target Port Group State.
+ * Returns -ENODEV if the path is
+ * found to be unusable.
+ */
+__maybe_unused
+static int scsi_alua_rtpg(struct scsi_device *sdev)
+{
+ struct alua_data *alua = sdev->alua;
+ struct scsi_sense_hdr sense_hdr;
+ int len, k, off, bufflen = ALUA_RTPG_SIZE;
+ int group_id_old, state_old, pref_old, valid_states_old;
+ unsigned char *desc, *buff;
+ unsigned err;
+ int retval;
+ unsigned int tpg_desc_tbl_off;
+ unsigned char orig_transition_tmo;
+ unsigned long flags;
+ bool transitioning_sense = false;
+ int rel_port, 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",
+ DRV_NAME);
+ return -EOPNOTSUPP; //SCSI_DH_DEV_UNSUPP;
+ }
+
+ group_id_old = alua->group_id;
+ state_old = alua->state;
+ pref_old = alua->pref;
+ valid_states_old = alua->valid_states;
+
+ if (!alua->expiry) {
+ unsigned long transition_tmo = ALUA_FAILOVER_TIMEOUT * HZ;
+
+ if (alua->transition_tmo)
+ transition_tmo = alua->transition_tmo * HZ;
+
+ alua->expiry = round_jiffies_up(jiffies + transition_tmo);
+ }
+
+ buff = kzalloc(bufflen, GFP_KERNEL);
+ if (!buff)
+ return -ENOMEM;
+
+ retry:
+ err = 0;
+ retval = submit_rtpg(sdev, buff, bufflen, &sense_hdr);
+
+ if (retval) {
+ /*
+ * Some (broken) implementations have a habit of returning
+ * an error during things like firmware update etc.
+ * But if the target only supports active/optimized there's
+ * not much we can do; it's not that we can switch paths
+ * or anything.
+ * So ignore any errors to avoid spurious failures during
+ * path failover.
+ */
+ if ((alua->valid_states & ~TPGS_SUPPORT_OPTIMIZED) == 0) {
+ sdev_printk(KERN_INFO, sdev,
+ "%s: ignoring rtpg result %d\n",
+ DRV_NAME, retval);
+ kfree(buff);
+ return 0;//SCSI_DH_OK
+ }
+ if (retval < 0 || !scsi_sense_valid(&sense_hdr)) {
+ sdev_printk(KERN_INFO, sdev,
+ "%s: rtpg failed, result %d\n",
+ DRV_NAME, retval);
+ kfree(buff);
+ if (retval < 0)
+ return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
+ if (host_byte(retval) == DID_NO_CONNECT)
+ return -ENOENT;//SCSI_DH_RES_TEMP_UNAVAIL;
+ return -EIO;//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.
+ * Note: some arrays return a sense key of ILLEGAL_REQUEST
+ * with ASC 00h if they don't support the extended header.
+ */
+ if (!alua->rtpg_ext_hdr_unsupp &&
+ sense_hdr.sense_key == ILLEGAL_REQUEST) {
+ alua->rtpg_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) {
+ transitioning_sense = true;
+ goto skip_rtpg;
+ }
+ /*
+ * Retry on any other UNIT ATTENTION occurred.
+ */
+ if (sense_hdr.sense_key == UNIT_ATTENTION)
+ err = -EAGAIN;//SCSI_DH_RETRY
+ if (err == -EAGAIN &&
+ alua->expiry != 0 && time_before(jiffies, alua->expiry)) {
+ sdev_printk(KERN_ERR, sdev, "%s: rtpg retry\n",
+ DRV_NAME);
+ scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
+ kfree(buff);
+ return err;
+ }
+ sdev_printk(KERN_ERR, sdev, "%s: rtpg failed\n",
+ DRV_NAME);
+ scsi_print_sense_hdr(sdev, DRV_NAME, &sense_hdr);
+ kfree(buff);
+ alua->expiry = 0;
+ return -EIO;//SCSI_DH_IO
+ }
+
+ 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) {
+ sdev_printk(KERN_WARNING, sdev,
+ "%s: kmalloc buffer failed\n",__func__);
+ /* Temporary failure, bypass */
+ alua->expiry = 0;
+ return -EBUSY;//SCSI_DH_DEV_TEMP_BUSY;
+ }
+ goto retry;
+ }
+
+ orig_transition_tmo = alua->transition_tmo;
+ if ((buff[4] & RTPG_FMT_MASK) == RTPG_FMT_EXT_HDR && buff[5] != 0)
+ alua->transition_tmo = buff[5];
+ else
+ alua->transition_tmo = ALUA_FAILOVER_TIMEOUT;
+
+ if (orig_transition_tmo != alua->transition_tmo) {
+ sdev_printk(KERN_INFO, sdev,
+ "%s: transition timeout set to %d seconds\n",
+ DRV_NAME, alua->transition_tmo);
+ alua->expiry = jiffies + alua->transition_tmo * HZ;
+ }
+
+ 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_desc = get_unaligned_be16(&desc[2]);
+
+ spin_lock_irqsave(&alua->lock, flags);
+ if (group_id_desc == group_id) {
+ alua->group_id = group_id;
+ WRITE_ONCE(alua->state, desc[0] & 0x0f);
+ alua->pref = desc[0] >> 7;
+ WRITE_ONCE(sdev->access_state, desc[0]);
+ alua->valid_states = desc[1];
+ }
+ spin_unlock_irqrestore(&alua->lock, flags);
+ off = 8 + (desc[7] * 4);
+ }
+
+ skip_rtpg:
+ spin_lock_irqsave(&alua->lock, flags);
+ if (transitioning_sense)
+ alua->state = SCSI_ACCESS_STATE_TRANSITIONING;
+
+ if (group_id_old != alua->group_id || state_old != alua->state ||
+ pref_old != alua->pref || valid_states_old !=
alua->valid_states)
+ sdev_printk(KERN_INFO, sdev,
+ "%s: port group %02x state %c %s supports
%c%c%c%c%c%c%c\n",
+ DRV_NAME, alua->group_id, print_alua_state(alua->state),
+ alua->pref ? "preferred" : "non-preferred",
+ alua->valid_states&TPGS_SUPPORT_TRANSITION?'T':'t',
+ alua->valid_states&TPGS_SUPPORT_OFFLINE?'O':'o',
+ alua->valid_states&TPGS_SUPPORT_LBA_DEPENDENT?'L':'l',
+ alua->valid_states&TPGS_SUPPORT_UNAVAILABLE?'U':'u',
+ alua->valid_states&TPGS_SUPPORT_STANDBY?'S':'s',
+ alua->valid_states&TPGS_SUPPORT_NONOPTIMIZED?'N':'n',
+ alua->valid_states&TPGS_SUPPORT_OPTIMIZED?'A':'a');
+
+ switch (alua->state) {
+ case SCSI_ACCESS_STATE_TRANSITIONING:
+ if (time_before(jiffies, alua->expiry)) {
+ /* State transition, retry */
+ alua->interval = ALUA_RTPG_RETRY_DELAY;
+ err = -EAGAIN;//SCSI_DH_RETRY
+ } else {
+ unsigned char access_state;
+
+ /* Transitioning time exceeded, set port to standby */
+ err = -EIO;//SCSI_DH_IO;
+ alua->state = SCSI_ACCESS_STATE_STANDBY;
+ alua->expiry = 0;
+ access_state = alua->state & SCSI_ACCESS_STATE_MASK;
+ if (alua->pref)
+ access_state |= SCSI_ACCESS_STATE_PREFERRED;
+ WRITE_ONCE(sdev->access_state, access_state);
+ }
+ break;
+ case SCSI_ACCESS_STATE_OFFLINE:
+ /* Path unusable */
+ err = -ENODEV;//SCSI_DH_DEV_OFFLINED;
+ alua->expiry = 0;
+ break;
+ default:
+ /* Useable path if active */
+ err = 0;//SCSI_DH_OK
+ alua->expiry = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&alua->lock, flags);
+ kfree(buff);
+ return err;
+}
+
int scsi_alua_sdev_init(struct scsi_device *sdev)
{
int rel_port, ret, tpgs;
@@ -47,6 +357,7 @@ int scsi_alua_sdev_init(struct scsi_device *sdev)
sdev->alua->sdev = sdev;
sdev->alua->tpgs = tpgs;
+ spin_lock_init(&sdev->alua->lock);
return 0;
out_free_data:
diff --git a/include/scsi/scsi_alua.h b/include/scsi/scsi_alua.h
index 07cdcb4f5b518..068277261ed9d 100644
--- a/include/scsi/scsi_alua.h
+++ b/include/scsi/scsi_alua.h
@@ -16,7 +16,15 @@
struct alua_data {
int group_id;
int tpgs;
+ int state;
+ int pref;
+ int valid_states;
+ bool rtpg_ext_hdr_unsupp;
+ unsigned char transition_tmo;
+ unsigned long expiry;
+ unsigned long interval;
struct scsi_device *sdev;
+ spinlock_t lock;
};
int scsi_alua_sdev_init(struct scsi_device *sdev);