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

