Add a dedicated ALUA driver which can be used for native SCSI multipath and also DH-based ALUA support.
Only core functions to submit a RTPG, STPG, tur, and also helper functions are added. The code from scsi_dh_alua.c to maintain the port groups is not added, because it is quite intertwined with the DH code. However the port group management code would be quite useful. Hannes Reinecke originally authored this code. Signed-off-by: John Garry <[email protected]> --- drivers/scsi/Kconfig | 9 + drivers/scsi/Makefile | 1 + drivers/scsi/device_handler/Kconfig | 1 + drivers/scsi/device_handler/scsi_dh_alua.c | 198 +------------------- drivers/scsi/scsi_alua.c | 204 +++++++++++++++++++++ include/scsi/scsi_alua.h | 50 +++++ 6 files changed, 269 insertions(+), 194 deletions(-) create mode 100644 drivers/scsi/scsi_alua.c create mode 100644 include/scsi/scsi_alua.h diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index cfab7ad1e3c2c..1a538c84ddf0a 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -86,6 +86,15 @@ config SCSI_MULTIPATH If unsure say N. +config SCSI_ALUA + tristate "SPC-3 ALUA support" + depends on SCSI + help + SCSI support for generic SPC-3 Asymmetric Logical Unit + Access (ALUA). + + If unsure, say Y. + comment "SCSI support type (disk, tape, CD-ROM)" depends on SCSI diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 64b7a82828b81..053fb51d98092 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -153,6 +153,7 @@ obj-$(CONFIG_SCSI_ENCLOSURE) += ses.o obj-$(CONFIG_SCSI_HISI_SAS) += hisi_sas/ +obj-$(CONFIG_SCSI_ALUA) += scsi_alua.o # This goes last, so that "real" scsi devices probe earlier obj-$(CONFIG_SCSI_DEBUG) += scsi_debug.o scsi_mod-y += scsi.o hosts.o scsi_ioctl.o \ diff --git a/drivers/scsi/device_handler/Kconfig b/drivers/scsi/device_handler/Kconfig index 368eb94c24562..ff06aea8c272c 100644 --- a/drivers/scsi/device_handler/Kconfig +++ b/drivers/scsi/device_handler/Kconfig @@ -35,6 +35,7 @@ config SCSI_DH_EMC config SCSI_DH_ALUA tristate "SPC-3 ALUA Device Handler" depends on SCSI_DH && SCSI + select SCSI_ALUA help SCSI Device handler for generic SPC-3 Asymmetric Logical Unit Access (ALUA). diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c index e9edd45ae28a3..42fa6bf8bb480 100644 --- a/drivers/scsi/device_handler/scsi_dh_alua.c +++ b/drivers/scsi/device_handler/scsi_dh_alua.c @@ -14,31 +14,11 @@ #include <scsi/scsi_dbg.h> #include <scsi/scsi_eh.h> #include <scsi/scsi_dh.h> +#include <scsi/scsi_alua.h> #define ALUA_DH_NAME "alua" #define ALUA_DH_VER "2.0" -#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 TPGS_MODE_UNINITIALIZED -1 -#define TPGS_MODE_NONE 0x0 -#define TPGS_MODE_IMPLICIT 0x1 -#define TPGS_MODE_EXPLICIT 0x2 - -#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 @@ -119,70 +99,6 @@ static void release_port_group(struct kref *kref) kfree_rcu(pg, rcu); } -/* - * 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, - bool ext_hdr_unsupp) -{ - 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 (ext_hdr_unsupp) - cdb[1] = MI_REPORT_TARGET_PGS; - else - cdb[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT; - put_unaligned_be32(bufflen, &cdb[6]); - - return scsi_execute_cmd(sdev, cdb, opf, buff, bufflen, - ALUA_FAILOVER_TIMEOUT * HZ, - ALUA_FAILOVER_RETRIES, &exec_args); -} - -/* - * submit_stpg - Issue a SET TARGET PORT GROUP command - * - * 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 int submit_stpg(struct scsi_device *sdev, int group_id, - struct scsi_sense_hdr *sshdr) -{ - u8 cdb[MAX_COMMAND_SIZE]; - unsigned char stpg_data[8]; - int stpg_len = 8; - blk_opf_t opf = REQ_OP_DRV_OUT | REQ_FAILFAST_DEV | - REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; - const struct scsi_exec_args exec_args = { - .sshdr = sshdr, - }; - - /* Prepare the data buffer */ - memset(stpg_data, 0, stpg_len); - stpg_data[4] = SCSI_ACCESS_STATE_OPTIMAL; - put_unaligned_be16(group_id, &stpg_data[6]); - - /* Prepare the command. */ - memset(cdb, 0x0, MAX_COMMAND_SIZE); - cdb[0] = MAINTENANCE_OUT; - cdb[1] = MO_SET_TARGET_PGS; - put_unaligned_be32(stpg_len, &cdb[6]); - - return scsi_execute_cmd(sdev, cdb, opf, stpg_data, - stpg_len, ALUA_FAILOVER_TIMEOUT * HZ, - ALUA_FAILOVER_RETRIES, &exec_args); -} - static struct alua_port_group *alua_find_get_pg(char *id_str, size_t id_size, int group_id) { @@ -265,58 +181,6 @@ static struct alua_port_group *alua_alloc_pg(struct scsi_device *sdev, return pg; } -/* - * alua_check_tpgs - Evaluate TPGS setting - * @sdev: device to be checked - * - * Examine the TPGS setting of the sdev to find out if ALUA - * is supported. - */ -static int alua_check_tpgs(struct scsi_device *sdev) -{ - int tpgs = TPGS_MODE_NONE; - - /* - * ALUA support for non-disk devices is fraught with - * difficulties, so disable it for now. - */ - if (sdev->type != TYPE_DISK) { - sdev_printk(KERN_INFO, sdev, - "%s: disable for non-disk devices\n", - ALUA_DH_NAME); - return tpgs; - } - - tpgs = scsi_device_tpgs(sdev); - switch (tpgs) { - case TPGS_MODE_EXPLICIT|TPGS_MODE_IMPLICIT: - sdev_printk(KERN_INFO, sdev, - "%s: supports implicit and explicit TPGS\n", - ALUA_DH_NAME); - break; - case TPGS_MODE_EXPLICIT: - sdev_printk(KERN_INFO, sdev, "%s: supports explicit TPGS\n", - ALUA_DH_NAME); - break; - case TPGS_MODE_IMPLICIT: - sdev_printk(KERN_INFO, sdev, "%s: supports implicit TPGS\n", - ALUA_DH_NAME); - break; - case TPGS_MODE_NONE: - sdev_printk(KERN_INFO, sdev, "%s: not supported\n", - ALUA_DH_NAME); - break; - default: - sdev_printk(KERN_INFO, sdev, - "%s: unsupported TPGS setting %d\n", - ALUA_DH_NAME, tpgs); - tpgs = TPGS_MODE_NONE; - break; - } - - return tpgs; -} - /* * alua_check_vpd - Evaluate INQUIRY vpd page 0x83 * @sdev: device to be checked @@ -393,27 +257,6 @@ static int alua_check_vpd(struct scsi_device *sdev, struct alua_dh_data *h, return SCSI_DH_OK; } -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'; - } -} static void alua_handle_state_transition(struct scsi_device *sdev) { @@ -500,30 +343,6 @@ static enum scsi_disposition alua_check_sense(struct scsi_device *sdev, return SCSI_RETURN_NOT_HANDLED; } -/* - * alua_tur - Send a TEST UNIT READY - * @sdev: device to which the TEST UNIT READY command should be send - * - * Send a TEST UNIT READY to @sdev to figure out the device state - * Returns SCSI_DH_RETRY if the sense code is NOT READY/ALUA TRANSITIONING, - * SCSI_DH_OK if no error occurred, and SCSI_DH_IO otherwise. - */ -static int alua_tur(struct scsi_device *sdev) -{ - struct scsi_sense_hdr sense_hdr; - int retval; - - retval = scsi_test_unit_ready(sdev, ALUA_FAILOVER_TIMEOUT * HZ, - ALUA_FAILOVER_RETRIES, &sense_hdr); - if ((sense_hdr.sense_key == NOT_READY || - sense_hdr.sense_key == UNIT_ATTENTION) && - sense_hdr.asc == 0x04 && sense_hdr.ascq == 0x0a) - return SCSI_DH_RETRY; - else if (retval) - return SCSI_DH_IO; - else - return SCSI_DH_OK; -} /* * alua_rtpg - Evaluate REPORT TARGET GROUP STATES @@ -722,17 +541,8 @@ static int alua_rtpg(struct scsi_device *sdev, struct alua_port_group *pg) if (group_id_old != pg->group_id || state_old != pg->state || pref_old != pg->pref || valid_states_old != pg->valid_states) - sdev_printk(KERN_INFO, sdev, - "%s: port group %02x state %c %s supports %c%c%c%c%c%c%c\n", - ALUA_DH_NAME, pg->group_id, print_alua_state(pg->state), - pg->pref ? "preferred" : "non-preferred", - pg->valid_states&TPGS_SUPPORT_TRANSITION?'T':'t', - pg->valid_states&TPGS_SUPPORT_OFFLINE?'O':'o', - pg->valid_states&TPGS_SUPPORT_LBA_DEPENDENT?'L':'l', - pg->valid_states&TPGS_SUPPORT_UNAVAILABLE?'U':'u', - pg->valid_states&TPGS_SUPPORT_STANDBY?'S':'s', - pg->valid_states&TPGS_SUPPORT_NONOPTIMIZED?'N':'n', - pg->valid_states&TPGS_SUPPORT_OPTIMIZED?'A':'a'); + alua_print_info(sdev, pg->group_id, pg->state, pg->pref, + pg->valid_states); switch (pg->state) { case SCSI_ACCESS_STATE_TRANSITIONING: @@ -911,7 +721,7 @@ static void alua_rtpg_work(struct work_struct *work) pg->flags &= ~ALUA_PG_RUN_RTPG; spin_unlock_irqrestore(&pg->lock, flags); if (state == SCSI_ACCESS_STATE_TRANSITIONING) { - if (alua_tur(sdev) == SCSI_DH_RETRY) { + if (alua_tur(sdev) == -EAGAIN) { spin_lock_irqsave(&pg->lock, flags); pg->flags &= ~ALUA_PG_RUNNING; pg->flags |= ALUA_PG_RUN_RTPG; diff --git a/drivers/scsi/scsi_alua.c b/drivers/scsi/scsi_alua.c new file mode 100644 index 0000000000000..2e4102192dcb9 --- /dev/null +++ b/drivers/scsi/scsi_alua.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic SCSI-3 ALUA SCSI driver + * + * Copyright (C) 2007-2010 Hannes Reinecke, SUSE Linux Products GmbH. + * All rights reserved. + */ + +#include <scsi/scsi.h> +#include <scsi/scsi_proto.h> +#include <scsi/scsi_dbg.h> +#include <scsi/scsi_eh.h> +#include <scsi/scsi_alua.h> + +#define DRV_NAME "alua" + +#define ALUA_FAILOVER_RETRIES 5 + +/* + * alua_check_tpgs - Evaluate TPGS setting + * @sdev: device to be checked + * + * Examine the TPGS setting of the sdev to find out if ALUA + * is supported. + */ +int alua_check_tpgs(struct scsi_device *sdev) +{ + int tpgs = TPGS_MODE_NONE; + + /* + * ALUA support for non-disk devices is fraught with + * difficulties, so disable it for now. + */ + if (sdev->type != TYPE_DISK) { + sdev_printk(KERN_INFO, sdev, + "%s: disable for non-disk devices\n", + DRV_NAME); + return tpgs; + } + + tpgs = scsi_device_tpgs(sdev); + switch (tpgs) { + case TPGS_MODE_EXPLICIT|TPGS_MODE_IMPLICIT: + sdev_printk(KERN_INFO, sdev, + "%s: supports implicit and explicit TPGS\n", + DRV_NAME); + break; + case TPGS_MODE_EXPLICIT: + sdev_printk(KERN_INFO, sdev, "%s: supports explicit TPGS\n", + DRV_NAME); + break; + case TPGS_MODE_IMPLICIT: + sdev_printk(KERN_INFO, sdev, "%s: supports implicit TPGS\n", + DRV_NAME); + break; + case TPGS_MODE_NONE: + sdev_printk(KERN_INFO, sdev, "%s: not supported\n", + DRV_NAME); + break; + default: + sdev_printk(KERN_INFO, sdev, + "%s: unsupported TPGS setting %d\n", + DRV_NAME, tpgs); + tpgs = TPGS_MODE_NONE; + break; + } + + return tpgs; +} +EXPORT_SYMBOL_GPL(alua_check_tpgs); + +/* + * alua_tur - Send a TEST UNIT READY + * @sdev: device to which the TEST UNIT READY command should be send + * + * Send a TEST UNIT READY to @sdev to figure out the device state + * Returns SCSI_DH_RETRY if the sense code is NOT READY/ALUA TRANSITIONING, + * 0 if no error occurred, and SCSI_DH_IO otherwise. + */ +int alua_tur(struct scsi_device *sdev) +{ + struct scsi_sense_hdr sense_hdr; + int retval; + + retval = scsi_test_unit_ready(sdev, ALUA_FAILOVER_TIMEOUT * HZ, + ALUA_FAILOVER_RETRIES, &sense_hdr); + if ((sense_hdr.sense_key == NOT_READY || + sense_hdr.sense_key == UNIT_ATTENTION) && + sense_hdr.asc == 0x04 && sense_hdr.ascq == 0x0a) + return -EAGAIN; + else if (retval) + return -EIO; + else + return 0; +} +EXPORT_SYMBOL_GPL(alua_tur); + +/* + * submit_rtpg - Issue a REPORT TARGET GROUP STATES command + * @sdev: sdev the command should be sent to + */ +int submit_rtpg(struct scsi_device *sdev, unsigned char *buff, + int bufflen, struct scsi_sense_hdr *sshdr, + bool alua_rtpg_ext_hdr_unsupp) +{ + 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 (alua_rtpg_ext_hdr_unsupp) + cdb[1] = MI_REPORT_TARGET_PGS; + else + cdb[1] = MI_REPORT_TARGET_PGS | MI_EXT_HDR_PARAM_FMT; + put_unaligned_be32(bufflen, &cdb[6]); + + return scsi_execute_cmd(sdev, cdb, opf, buff, bufflen, + ALUA_FAILOVER_TIMEOUT * HZ, + ALUA_FAILOVER_RETRIES, &exec_args); +} +EXPORT_SYMBOL_GPL(submit_rtpg); + +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'; + } +} + +void alua_print_info(struct scsi_device *sdev, int group_id, int state, + int pref, int valid_states) +{ + sdev_printk(KERN_INFO, sdev, + "alua: port group %02x state %c %s supports %c%c%c%c%c%c%c\n", + group_id, print_alua_state(state), + pref ? "preferred" : "non-preferred", + 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'); +} +EXPORT_SYMBOL_GPL(alua_print_info); + +/* + * submit_stpg - Issue a SET TARGET PORT GROUP command + * + * 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. + */ +int submit_stpg(struct scsi_device *sdev, int group_id, + struct scsi_sense_hdr *sshdr) +{ + u8 cdb[MAX_COMMAND_SIZE]; + unsigned char stpg_data[8]; + int stpg_len = 8; + blk_opf_t opf = REQ_OP_DRV_OUT | REQ_FAILFAST_DEV | + REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; + const struct scsi_exec_args exec_args = { + .sshdr = sshdr, + }; + + /* Prepare the data buffer */ + memset(stpg_data, 0, stpg_len); + stpg_data[4] = SCSI_ACCESS_STATE_OPTIMAL; + put_unaligned_be16(group_id, &stpg_data[6]); + + /* Prepare the command. */ + memset(cdb, 0x0, MAX_COMMAND_SIZE); + cdb[0] = MAINTENANCE_OUT; + cdb[1] = MO_SET_TARGET_PGS; + put_unaligned_be32(stpg_len, &cdb[6]); + + return scsi_execute_cmd(sdev, cdb, opf, stpg_data, + stpg_len, ALUA_FAILOVER_TIMEOUT * HZ, + ALUA_FAILOVER_RETRIES, &exec_args); +} +EXPORT_SYMBOL_GPL(submit_stpg); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("scsi_alua"); diff --git a/include/scsi/scsi_alua.h b/include/scsi/scsi_alua.h new file mode 100644 index 0000000000000..51b5ed1836c99 --- /dev/null +++ b/include/scsi/scsi_alua.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic SCSI-3 ALUA SCSI Device Handler + * + * Copyright (C) 2007-2010 Hannes Reinecke, SUSE Linux Products GmbH. + * All rights reserved. + */ +#ifndef _SCSI_ALUA_H +#define _SCSI_ALUA_H + +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_proto.h> +#include <scsi/scsi_dbg.h> +#include <scsi/scsi_eh.h> + +#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 TPGS_MODE_UNINITIALIZED -1 +#define TPGS_MODE_NONE 0x0 +#define TPGS_MODE_IMPLICIT 0x1 +#define TPGS_MODE_EXPLICIT 0x2 + +#define ALUA_RTPG_SIZE 128 +#define ALUA_FAILOVER_TIMEOUT 60 + +int alua_check_tpgs(struct scsi_device *sdev); +int submit_rtpg(struct scsi_device *sdev, unsigned char *buff, + int bufflen, struct scsi_sense_hdr *sshdr, + bool alua_rtpg_ext_hdr_unsupp); +int alua_tur(struct scsi_device *sdev); +void alua_print_info(struct scsi_device *sdev, int group_id, int state, + int pref, int valid_states); +int submit_stpg(struct scsi_device *sdev, int group_id, + struct scsi_sense_hdr *sshdr); +#endif // _SCSI_ALUA_H -- 2.43.5

