Explicit ALUA support

Prepared with help from Prasidh Srikanth <Prasidh.Srikanth@sandisk.com>



git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6576 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
Vladislav Bolkhovitin
2015-11-06 03:28:53 +00:00
parent 2fdbaf0fae
commit e50f240d33
12 changed files with 695 additions and 62 deletions
+44 -29
View File
@@ -1452,16 +1452,18 @@ persistent reservations from this device are released, upon reconnect
the initiators will see it.
Implicit ALUA Support
---------------------
ALUA Support
------------
SCST supports implicit asymmetric logical unit access (ALUA). Implicit ALUA is
a feature defined by the ANSI T10 SCSI committee that allows a target to tell
the initiator which path to use in a multipath setup. The redundant paths
between initiator and target can be used either for redundancy or for load
sharing purposes. The target can either be a single target system running SCST
with multiple communication interfaces or two target systems each running SCST
and configured in a high availability setup.
SCST supports both implicit and explicit asymmetric logical unit access
(ALUA). ALUA is a feature defined by the ANSI T10 SCSI committee. It
allows a target to tell the initiator which path to use in a multipath
setup plus, in the explicit case, control state of each path via SET
TARGET PORT GROUPS SCSI command. The redundant paths between initiator
and target can be used either for redundancy or for load sharing
purposes. The target can either be a single target system running SCST
with multiple communication interfaces or two target systems each
running SCST and configured in a high availability setup.
In the SPC-4 standard the following concepts are defined related to ALUA:
* Relative target port ID. A number between 1 and 65535 that uniquely
@@ -1482,17 +1484,17 @@ ANSI T10 standard called SPC-4.
ALUA support in SCST
....................
SCST allows to define implicit ALUA settings for each unique combination of
SCST device and SCST target. An initiator however queries ALUA settings by
sending an appropriate SCSI command to a specific LUN of an SCST target. Each
such LUN maps uniquely to an SCST device. For hardware SCST target drivers,
e.g. ib_srpt, there is a one-to-one correspondence between SCST target and
SCSI target port. With other SCST targets, e.g. iSCSI-SCST, by default the
only relationship between SCST targets and SCSI target ports is that all SCST
targets defined on a system are visible via all SCSI target ports. See also
the iSCSI-SCST documentation about the allowed_portal attribute for
information about how to associate iSCSI targets with a single physical
interface.
SCST allows to define ALUA settings for each unique combination of SCST
device and SCST target. An initiator however queries ALUA settings by
sending an appropriate SCSI command to a specific LUN of an SCST target.
Each such LUN maps uniquely to an SCST device. For hardware SCST target
drivers, e.g. ib_srpt, there is a one-to-one correspondence between SCST
target and SCSI target port. With other SCST targets, e.g. iSCSI-SCST,
by default the only relationship between SCST targets and SCSI target
ports is that all SCST targets defined on a system are visible via all
SCSI target ports. See also the iSCSI-SCST documentation about the
allowed_portal attribute for information about how to associate iSCSI
targets with a single physical interface.
Notes:
- In a H.A. setup it is the responsibility of the user to synchronize ALUA
@@ -1515,7 +1517,7 @@ Notes:
Configuring ALUA in SCST
........................
SCST allows to configure the following settings related to implicit ALUA
SCST allows to configure the following settings related to ALUA
for each unique combination of SCST target and virtual SCST device
(vdisk_fileio, vdisk_blockio, vcdrom, ...):
* The target port group asymmetric access state. SCST supports all ALUA port
@@ -1613,15 +1615,26 @@ DEVICE_GROUP dgroup2 {
}
}
Explicit ALUA
.............
To enable explicit ALUA you need in addition to the above settings set
expl_alua device attribute to 1 (by default it is 0). Also you need to
run stpgd and supply to it path to a script or program, which will
perform actual path state switching on SET TARGET PORT GROUPS command,
for instance, by calling drbdadm. For more information see stpgd README
as well as sample script scst_on_stpg.
Checking the Target Configuration
.................................
One way to verify the implicit ALUA configuration from a Linux initiator is
via the commands provided in the sg3_utils package. The first step is to
verify whether for a certain LUN implicit ALUA has been configured on the
target. This is possible by checking whether the TPGS=1 text appears in the
sg_inq output, where /dev/sdb is a device node created by the ib_srp initiator:
One way to verify the ALUA configuration from a Linux initiator is via
the commands provided in the sg3_utils package. The first step is to
verify whether for a certain LUN ALUA has been configured on the target.
This is possible by checking whether the TPGS=1 text appears in the
sg_inq output, where /dev/sdb is a device node created by the ib_srp
initiator:
# sg_inq /dev/sdb
standard INQUIRY:
@@ -1681,9 +1694,11 @@ Device Identification VPD page:
Initiator Support
.................
On Linux systems implicit ALUA support is provided by the scsi_dh_alua kernel
driver in combination with the user space multipathd daemon. You will have to
modify at least the following in /etc/multipath.conf to enable implicit ALUA:
On Linux systems ALUA support is provided by the scsi_dh_alua kernel
driver in combination with the user space multipathd daemon. You will
have to modify at least the following in /etc/multipath.conf to enable
ALUA:
* hardware_handler "1 alua"
* prio alua
* path_grouping_policy group_by_prio
+9 -2
View File
@@ -2658,6 +2658,9 @@ struct scst_device {
/* Used to serialize invocations of __scst_ext_blocking_done() */
unsigned int ext_unblock_scheduled:1;
/* Set if this device was blocked during STPG command processing */
unsigned int stpg_ext_blocked:1;
/* Set if this device does not support DIF IP checking */
unsigned int dev_dif_ip_not_supported:1;
@@ -3212,6 +3215,8 @@ struct scst_acn {
* @kobj: For making this object visible in sysfs.
* @dev_kobj: Sysfs devices directory.
* @tg_kobj: Sysfs target groups directory.
* @stpg_transport_id Initiator transport ID for STPG originating I_T nexus, if any
* @stpg_rel_tgt_id Relative target ID for STPG originating I_T nexus, if any
*
* Each device is member of zero or one device groups. With each device group
* there are zero or more target groups associated.
@@ -3224,6 +3229,8 @@ struct scst_dev_group {
struct kobject kobj;
struct kobject *dev_kobj;
struct kobject *tg_kobj;
uint8_t *stpg_transport_id;
uint16_t stpg_rel_tgt_id;
};
/**
@@ -3362,7 +3369,6 @@ extern const struct scst_opcode_descriptor scst_op_descr_report_supp_opcodes;
&scst_op_descr_report_supp_opcodes, \
&scst_op_descr_report_supp_tm_fns,
#ifndef smp_mb__after_set_bit
/* There is no smp_mb__after_set_bit() in the kernel */
#define smp_mb__after_set_bit() smp_mb()
@@ -3691,9 +3697,10 @@ static inline void scst_sess_set_tgt_priv(struct scst_session *sess,
}
uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt);
bool scst_impl_alua_configured(struct scst_device *dev);
bool scst_alua_configured(struct scst_device *dev);
int scst_tg_get_group_info(void **buf, uint32_t *response_length,
struct scst_device *dev, uint8_t data_format);
int scst_tg_set_group_info(struct scst_cmd *cmd);
/*
* Get/set functions for dev's static DIF APP TAG
+4 -3
View File
@@ -290,9 +290,9 @@ static inline int scst_sense_response_code(const uint8_t *sense)
/* NOT_READY is 2 */
#define scst_sense_format_in_progress NOT_READY, 0x04, 0x04
#define scst_sense_tp_transitioning NOT_READY, 0x04, 0x0A
#define scst_sense_tp_standby NOT_READY, 0x04, 0x0B
#define scst_sense_tp_unav NOT_READY, 0x04, 0x0C
#define scst_sense_alua_transitioning NOT_READY, 0x04, 0x0A
#define scst_sense_alua_standby NOT_READY, 0x04, 0x0B
#define scst_sense_alua_unav NOT_READY, 0x04, 0x0C
#define scst_sense_no_medium NOT_READY, 0x3a, 0
/* MEDIUM_ERROR is 3 */
@@ -301,6 +301,7 @@ static inline int scst_sense_response_code(const uint8_t *sense)
/* HARDWARE_ERROR is 4 */
#define scst_sense_hardw_error HARDWARE_ERROR, 0x44, 0 /* non-retriable */
#define scst_sense_set_target_pgs_failed HARDWARE_ERROR, 0x67, 0xA
/* ILLEGAL_REQUEST is 5 */
#define scst_sense_invalid_opcode ILLEGAL_REQUEST, 0x20, 0
+19
View File
@@ -135,4 +135,23 @@ struct scst_event_tm_fn_received_payload {
};
};
#define SCST_EVENT_STPG_USER_INVOKE 5
struct scst_event_stpg_descr {
uint16_t group_id;
/*
* Better to keep below fields as small as possible to fit
* in single page as many descriptors as possible.
*/
uint8_t prev_state[32];
uint8_t new_state[32];
uint8_t dg_name[64];
uint8_t tg_name[64];
};
struct scst_event_stpg_payload {
aligned_u64 stpg_cmd_tag;
uint8_t device_name[64];
uint16_t stpg_descriptors_cnt;
struct scst_event_stpg_descr stpg_descriptors[0];
};
#endif /* __SCST_EVENT_H */
+112 -1
View File
@@ -112,6 +112,7 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = {
#define DEF_REMOVABLE 0
#define DEF_ROTATIONAL 1
#define DEF_THIN_PROVISIONED 0
#define DEF_EXPL_ALUA 0
#define VDISK_NULLIO_SIZE (5LL*1024*1024*1024*1024/2)
@@ -178,6 +179,7 @@ struct scst_vdisk_dev {
unsigned int tst:3;
unsigned int format_active:1;
unsigned int discard_zeroes_data:1;
unsigned int expl_alua:1;
struct file *fd;
struct file *dif_fd;
@@ -322,6 +324,7 @@ static enum compl_status_e vdisk_exec_read_capacity(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_get_lba_status(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_report_tpgs(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_request_sense(struct vdisk_cmd_params *p);
static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p);
@@ -382,6 +385,10 @@ static ssize_t vdisk_sysfs_tst_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static ssize_t vdisk_sysfs_rotational_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count);
static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
@@ -474,6 +481,9 @@ static struct kobj_attribute vdisk_tst_attr =
__ATTR(tst, S_IRUGO, vdisk_sysfs_tst_show, NULL);
static struct kobj_attribute vdisk_rotational_attr =
__ATTR(rotational, S_IRUGO, vdisk_sysfs_rotational_show, NULL);
static struct kobj_attribute vdisk_expl_alua_attr =
__ATTR(expl_alua, S_IWUSR|S_IRUGO, vdisk_sysfs_expl_alua_show,
vdisk_sysfs_expl_alua_store);
static struct kobj_attribute vdisk_nv_cache_attr =
__ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL);
static struct kobj_attribute vdisk_o_direct_attr =
@@ -543,6 +553,7 @@ static const struct attribute *vdisk_fileio_attrs[] = {
&vdisk_tp_attr.attr,
&vdisk_tst_attr.attr,
&vdisk_rotational_attr.attr,
&vdisk_expl_alua_attr.attr,
&vdisk_nv_cache_attr.attr,
&vdisk_o_direct_attr.attr,
&vdisk_removable_attr.attr,
@@ -570,6 +581,7 @@ static const struct attribute *vdisk_blockio_attrs[] = {
&vdisk_blocksize_attr.attr,
&vdisk_rd_only_attr.attr,
&vdisk_wt_attr.attr,
&vdisk_expl_alua_attr.attr,
&vdisk_nv_cache_attr.attr,
&vdisk_tst_attr.attr,
&vdisk_removable_attr.attr,
@@ -1874,6 +1886,17 @@ static enum compl_status_e vdisk_exec_maintenance_in(struct vdisk_cmd_params *p)
return CMD_SUCCEEDED;
}
static enum compl_status_e vdisk_exec_maintenance_out(struct vdisk_cmd_params *p)
{
switch (p->cmd->cdb[1] & 0x1f) {
case MO_SET_TARGET_PGS:
return vdisk_exec_set_tpgs(p);
}
scst_set_invalid_field_in_cdb(p->cmd, 1,
0 | SCST_INVAL_FIELD_BIT_OFFS_VALID);
return CMD_SUCCEEDED;
}
static enum compl_status_e vdisk_exec_send_diagnostic(struct vdisk_cmd_params *p)
{
return CMD_SUCCEEDED;
@@ -2590,6 +2613,7 @@ static const struct scst_opcode_descriptor scst_op_descr_read_toc = {
[WRITE_SAME_16] = vdisk_exec_write_same, \
[COMPARE_AND_WRITE] = vdisk_exec_caw, \
[MAINTENANCE_IN] = vdisk_exec_maintenance_in, \
[MAINTENANCE_OUT] = vdisk_exec_maintenance_out, \
[SEND_DIAGNOSTIC] = vdisk_exec_send_diagnostic, \
[FORMAT_UNIT] = vdisk_exec_format_unit,
@@ -2714,6 +2738,7 @@ static const struct scst_opcode_descriptor *vdisk_opcode_descriptors[] = {
SHARED_OPCODE_DESCRIPTORS
VDISK_OPCODE_DESCRIPTORS
SCST_OPCODE_DESCRIPTORS
&scst_op_descr_stpg, /* must be last, see vdisk_get_supported_opcodes()! */
};
static const struct scst_opcode_descriptor *vdisk_opcode_descriptors_type2[] = {
@@ -2738,6 +2763,9 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd,
const struct scst_opcode_descriptor ***out_supp_opcodes,
int *out_supp_opcodes_cnt)
{
struct scst_device *dev = cmd->dev;
struct scst_vdisk_dev *virt_dev = dev->dh_priv;
if (cmd->dev->dev_dif_type != 2) {
*out_supp_opcodes = vdisk_opcode_descriptors;
*out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors);
@@ -2745,6 +2773,10 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd,
*out_supp_opcodes = vdisk_opcode_descriptors_type2;
*out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors_type2);
}
if (!virt_dev->expl_alua) {
(*out_supp_opcodes_cnt)--;
sBUG_ON((*out_supp_opcodes)[*out_supp_opcodes_cnt]->od_serv_action != MO_SET_TARGET_PGS);
}
return 0;
}
@@ -4091,8 +4123,11 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd,
buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */
if (cmd->dev->dev_dif_mode != SCST_DIF_MODE_NONE)
buf[5] |= 1; /* PROTECT */
if (scst_impl_alua_configured(cmd->dev))
if (scst_alua_configured(cmd->dev)) {
buf[5] |= SCST_INQ_TPGS_MODE_IMPLICIT;
if (virt_dev->expl_alua)
buf[5] |= SCST_INQ_TPGS_MODE_EXPLICIT;
}
buf[6] = 0x10; /* MultiP 1 */
buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */
@@ -5237,6 +5272,34 @@ out:
return CMD_SUCCEEDED;
}
/* SPC-4 SET TARGET PORT GROUPS command */
static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p)
{
struct scst_cmd *cmd = p->cmd;
struct scst_device *dev = cmd->dev;
struct scst_vdisk_dev *virt_dev = dev->dh_priv;
int res = CMD_SUCCEEDED, rc;
TRACE_ENTRY();
if (!virt_dev->expl_alua) {
PRINT_ERROR("SET TARGET PORT GROUPS: not explicit ALUA mode "
"(dev %s)", dev->virt_name);
/* Invalid opcode, i.e. SA field */
scst_set_invalid_field_in_cdb(cmd, 1,
0 | SCST_INVAL_FIELD_BIT_OFFS_VALID);
goto out;
}
rc = scst_tg_set_group_info(cmd);
if (rc == 0)
res = RUNNING_ASYNC;
out:
TRACE_EXIT_RES(res);
return res;
}
static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p)
{
struct scst_cmd *cmd = p->cmd;
@@ -7462,6 +7525,7 @@ static int vdev_create(struct scst_dev_type *devt,
virt_dev->thin_provisioned = DEF_THIN_PROVISIONED;
virt_dev->tst = DEF_TST;
virt_dev->caw_len_lim = DEF_CAW_LEN_LIM;
virt_dev->expl_alua = DEF_EXPL_ALUA;
virt_dev->blk_shift = DEF_DISK_BLOCK_SHIFT;
@@ -8574,6 +8638,53 @@ static ssize_t vdisk_sysfs_tp_show(struct kobject *kobj,
return pos;
}
static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
struct scst_device *dev;
struct scst_vdisk_dev *virt_dev;
int pos;
TRACE_ENTRY();
dev = container_of(kobj, struct scst_device, dev_kobj);
virt_dev = dev->dh_priv;
pos = sprintf(buf, "%d\n%s", virt_dev->expl_alua,
virt_dev->expl_alua != DEF_EXPL_ALUA ?
SCST_SYSFS_KEY_MARK "\n" : "");
TRACE_EXIT_RES(pos);
return pos;
}
static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct scst_device *dev;
struct scst_vdisk_dev *virt_dev;
char ch[16];
bool expl_alua;
int res;
TRACE_ENTRY();
dev = container_of(kobj, struct scst_device, dev_kobj);
virt_dev = dev->dh_priv;
sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf);
expl_alua = !!simple_strtoul(ch, NULL, 0);
spin_lock(&virt_dev->flags_lock);
virt_dev->expl_alua = expl_alua;
spin_unlock(&virt_dev->flags_lock);
res = count;
TRACE_EXIT_RES(res);
return res;
}
static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
+1 -1
View File
@@ -160,7 +160,7 @@ static void __scst_event_queue(struct scst_event_entry *event_entry)
{
const struct scst_event *event = &event_entry->event;
struct scst_event_priv *priv;
struct scst_event_entry *allowed_entry = NULL;
struct scst_event_entry *allowed_entry;
bool queued = false;
int rc = 0;
static atomic_t base_event_id = ATOMIC_INIT(0);
+28 -6
View File
@@ -248,7 +248,7 @@ const struct scst_opcode_descriptor scst_op_descr_stpg = {
.od_cdb_size = 12,
.od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC,
.od_recommended_timeout = SCST_GENERIC_DISK_SMALL_TIMEOUT/HZ,
.od_cdb_usage_bits = { MAINTENANCE_IN, MO_SET_TARGET_PGS, 0, 0, 0, 0,
.od_cdb_usage_bits = { MAINTENANCE_OUT, MO_SET_TARGET_PGS, 0, 0, 0, 0,
0xFF, 0xFF, 0xFF, 0xFF, 0, SCST_OD_DEFAULT_CONTROL_BYTE },
};
EXPORT_SYMBOL(scst_op_descr_stpg);
@@ -453,6 +453,8 @@ static int get_cdb_info_apt(struct scst_cmd *cmd,
const struct scst_sdbops *sdbops);
static int get_cdb_info_min(struct scst_cmd *cmd,
const struct scst_sdbops *sdbops);
static int get_cdb_info_mo(struct scst_cmd *cmd,
const struct scst_sdbops *sdbops);
static int get_cdb_info_var_len(struct scst_cmd *cmd,
const struct scst_sdbops *sdbops);
@@ -1410,12 +1412,12 @@ static const struct scst_sdbops scst_scsi_op_table[] = {
.info_op_flags = FLAG_NONE,
.info_len_off = 8, .info_len_len = 2,
.get_cdb_info = get_cdb_info_len_2},
{.ops = 0xA4, .devkey = " O ",
{.ops = 0xA4, .devkey = "OOO O OOOO MO O",
.info_op_name = "MAINTENANCE(OUT)",
.info_data_direction = SCST_DATA_WRITE,
.info_op_flags = FLAG_NONE,
.info_len_off = 6, .info_len_len = 4,
.get_cdb_info = get_cdb_info_len_4},
.get_cdb_info = get_cdb_info_mo},
{.ops = 0xA5, .devkey = " M ",
.info_op_name = "MOVE MEDIUM",
.info_data_direction = SCST_DATA_NONE,
@@ -10518,6 +10520,20 @@ static int get_cdb_info_min(struct scst_cmd *cmd,
return get_cdb_info_len_4(cmd, sdbops);
}
/* Parse MAINTENANCE (OUT) */
static int get_cdb_info_mo(struct scst_cmd *cmd,
const struct scst_sdbops *sdbops)
{
switch (cmd->cdb[1] & 0x1f) {
case MO_SET_TARGET_PGS:
cmd->op_name = "SET TARGET PORT GROUPS";
cmd->op_flags |= SCST_STRICTLY_SERIALIZED;
break;
}
return get_cdb_info_len_4(cmd, sdbops);
}
/**
* scst_get_cdb_info() - fill various info about the command's CDB
*
@@ -13304,14 +13320,20 @@ out_free_success:
goto out_success;
}
void scst_ext_unblock_dev(struct scst_device *dev)
void scst_ext_unblock_dev(struct scst_device *dev, bool stpg)
{
TRACE_ENTRY();
spin_lock_bh(&dev->dev_lock);
if (dev->ext_blocks_cnt == 0) {
TRACE_DBG("Nothing to unblock (dev %p)", dev);
TRACE_DBG("Nothing to unblock (dev %s)", dev->virt_name);
goto out_unlock;
}
if ((dev->ext_blocks_cnt == 1) && dev->stpg_ext_blocked && !stpg) {
TRACE_DBG("Can not unblock internal STPG ext block (dev %s)",
dev->virt_name);
goto out_unlock;
}
@@ -13340,7 +13362,7 @@ void scst_ext_unblock_dev(struct scst_device *dev)
spin_unlock_bh(&dev->dev_lock);
} else {
TRACE_DBG("Ext unblock: pending done, unblocking...");
scst_ext_unblock_dev(dev);
scst_ext_unblock_dev(dev, stpg);
}
spin_lock_bh(&dev->dev_lock);
}
+1 -1
View File
@@ -110,7 +110,7 @@ static inline void tid_secure(uint8_t *tid)
}
/* Returns false if tid's are not equal, true otherwise */
static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b)
bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b)
{
int len;
+2
View File
@@ -131,6 +131,8 @@ void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer,
int buffer_size);
int scst_tid_size(const uint8_t *tid);
bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b);
struct scst_dev_registrant *scst_pr_find_reg(struct scst_device *dev,
const uint8_t *transport_id, const uint16_t rel_tgt_id);
struct scst_dev_registrant *scst_pr_add_registrant(struct scst_device *dev,
+1 -1
View File
@@ -717,7 +717,7 @@ void scst_unblock_dev(struct scst_device *dev);
int scst_ext_block_dev(struct scst_device *dev, bool sync,
ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len);
void scst_ext_unblock_dev(struct scst_device *dev);
void scst_ext_unblock_dev(struct scst_device *dev, bool stpg);
void __scst_ext_blocking_done(struct scst_device *dev);
void scst_ext_blocking_done(struct scst_device *dev);
+1 -1
View File
@@ -3522,7 +3522,7 @@ static ssize_t scst_dev_block_store(struct kobject *kobj,
TRACE_DBG("Sysfs unblocking (dev %s)", dev->virt_name);
scst_ext_unblock_dev(dev);
scst_ext_unblock_dev(dev, false);
res = 0;
goto out;
case '1':
+473 -17
View File
@@ -16,6 +16,8 @@
*/
#include <linux/moduleparam.h>
#include <linux/delay.h>
#include <linux/kmod.h>
#include <asm/unaligned.h>
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
@@ -23,6 +25,8 @@
#include "scst.h"
#endif
#include "scst_priv.h"
#include "scst_event.h"
#include "scst_pres.h"
struct alua_state_and_name {
enum scst_tg_state s;
@@ -127,8 +131,8 @@ static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg,
}
/* Look up a target group by name in the given device group. */
static struct scst_target_group *
__lookup_tg_by_name(struct scst_dev_group *dg, const char *name)
static struct scst_target_group *__lookup_tg_by_name(struct scst_dev_group *dg,
const char *name)
{
struct scst_target_group *tg;
@@ -141,9 +145,24 @@ __lookup_tg_by_name(struct scst_dev_group *dg, const char *name)
return NULL;
}
/* Look up a target group by group ID. */
static struct scst_target_group *__lookup_tg_by_group_id(struct scst_dev_group *dg,
uint16_t group_id)
{
struct scst_target_group *tg;
lockdep_assert_held(&scst_mutex);
list_for_each_entry(tg, &dg->tg_list, entry)
if (tg->group_id == group_id)
return tg;
return NULL;
}
/* Look up a target group by target port. */
static struct scst_target_group *
__lookup_tg_by_tgt(struct scst_dev_group *dg, const struct scst_tgt *tgt)
static struct scst_target_group *__lookup_tg_by_tgt(struct scst_dev_group *dg,
const struct scst_tgt *tgt)
{
struct scst_target_group *tg;
struct scst_tg_tgt *tg_tgt;
@@ -319,12 +338,11 @@ static bool scst_tg_accept_standby(struct scst_cmd *cmd)
return true;
}
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_standby));
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_standby));
return false;
}
/*
* Whether or not to accept a command in the ALUA unavailable state.
*/
@@ -333,7 +351,7 @@ static bool scst_tg_accept_unav(struct scst_cmd *cmd)
bool process_cmd = scst_tg_accept(cmd);
if (!process_cmd)
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_unav));
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav));
return process_cmd;
}
@@ -347,7 +365,7 @@ static bool scst_tg_accept_transitioning(struct scst_cmd *cmd)
if (!process_cmd)
scst_set_cmd_error(cmd,
SCST_LOAD_SENSE(scst_sense_tp_transitioning));
SCST_LOAD_SENSE(scst_sense_alua_transitioning));
return process_cmd;
}
@@ -424,7 +442,9 @@ static void scst_tg_change_tgt_dev_state(struct scst_tgt_dev *tgt_dev,
{
lockdep_assert_held(&scst_dg_mutex);
TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed", tgt_dev);
TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed (gen_ua %d)",
tgt_dev, gen_ua);
scst_update_tgt_dev_alua_filter(tgt_dev, state);
if (gen_ua)
scst_gen_aen_or_ua(tgt_dev,
@@ -724,14 +744,86 @@ out:
return res;
}
static void scst_event_stpg_notify_fn(struct scst_event *event,
void *priv, int status)
{
struct scst_dev_group *dg;
struct scst_cmd *cmd = (struct scst_cmd *)priv;
struct scst_event_stpg_payload *p =
(struct scst_event_stpg_payload *)event->payload;
struct scst_event_stpg_descr *d;
struct scst_dg_dev *dgd;
int i;
TRACE_ENTRY();
PRINT_INFO("Notification for event %u (id %d) received "
"with status %d (priv %p)", event->event_code,
event->event_id, status, priv);
mutex_lock(&scst_mutex);
mutex_lock(&scst_dg_mutex);
dg = __lookup_dg_by_dev(cmd->dev);
if (!dg) {
PRINT_ERROR("STPG: unable to find DG for device %s",
cmd->dev->virt_name);
goto out_fail;
}
list_for_each_entry(dgd, &dg->dev_list, entry) {
if (dgd->dev->stpg_ext_blocked) {
TRACE_DBG("STPG: ext unblocking dev %s",
dgd->dev->virt_name);
scst_ext_unblock_dev(dgd->dev, true);
dgd->dev->stpg_ext_blocked = 0;
}
}
kfree(dg->stpg_transport_id);
dg->stpg_transport_id = NULL;
if (status != 0) {
PRINT_ERROR("on_stpg script for device group %s failed with status %d",
dg->name, status);
goto out_fail;
}
for (i = 0, d = &p->stpg_descriptors[0]; i < p->stpg_descriptors_cnt; i++, d++) {
struct scst_target_group *tg = __lookup_tg_by_group_id(dg, d->group_id);
if (!tg) {
PRINT_ERROR("STPG: unable to find TG %d", d->group_id);
goto out_fail;
} else if (tg->state == scst_alua_name_to_state(d->prev_state)) {
PRINT_ERROR("on_stpg script did not change ALUA state"
" for device group %s / target group %s",
dg->name, tg->name);
goto out_fail;
}
}
out_unlock:
mutex_unlock(&scst_dg_mutex);
mutex_unlock(&scst_mutex);
cmd->completed = 1;
cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD);
TRACE_EXIT();
return;
out_fail:
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed));
goto out_unlock;
}
/*
* Update the ALUA filter of those LUNs (tgt_dev) whose target port is a member
* of target group @tg and that export a device that is a member of the device
* group @tg->dg.
*/
static void __scst_tg_set_state(struct scst_target_group *tg,
enum scst_tg_state state,
struct scst_tgt *no_ua_tgt)
enum scst_tg_state state)
{
struct scst_dg_dev *dg_dev;
struct scst_device *dev;
@@ -754,8 +846,12 @@ static void __scst_tg_set_state(struct scst_target_group *tg,
tgt = tgt_dev->sess->tgt;
list_for_each_entry(tg_tgt, &tg->tgt_list, entry) {
if (tg_tgt->tgt == tgt) {
bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING);
if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) &&
tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id))
gen_ua = false;
scst_tg_change_tgt_dev_state(tgt_dev,
state, tgt != no_ua_tgt);
state, gen_ua);
break;
}
}
@@ -780,7 +876,7 @@ int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state)
if (res)
goto out;
__scst_tg_set_state(tg, state, NULL);
__scst_tg_set_state(tg, state);
mutex_unlock(&scst_dg_mutex);
out:
@@ -945,9 +1041,16 @@ out_free:
goto out;
}
/* scst_dg_mutex supposed to be locked */
static void __scst_dg_dev_remove(struct scst_dev_group *dg,
struct scst_dg_dev *dgdev)
{
if (dgdev->dev->stpg_ext_blocked) {
TRACE_DBG("DG %s remove: unblocking STPG ext blocked "
"dev %s", dg->name, dgdev->dev->virt_name);
scst_ext_unblock_dev(dgdev->dev, true);
dgdev->dev->stpg_ext_blocked = 0;
}
list_del(&dgdev->entry);
scst_dg_dev_sysfs_del(dg, dgdev);
scst_reset_dev_alua_filter(dgdev->dev);
@@ -1080,7 +1183,7 @@ static void __scst_dg_remove(struct scst_dev_group *dg)
list_del(&dg->entry);
scst_dg_sysfs_del(dg);
list_for_each_entry(tg, &dg->tg_list, entry)
__scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED, NULL);
__scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED);
while (!list_empty(&dg->dev_list)) {
dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev,
entry);
@@ -1201,10 +1304,10 @@ out_unlock:
EXPORT_SYMBOL_GPL(scst_lookup_tg_id);
/**
* scst_impl_alua_configured() - Whether implicit ALUA has been configured.
* scst_alua_configured() - Whether implicit ALUA has been configured.
* @dev: Pointer to the SCST device to verify.
*/
bool scst_impl_alua_configured(struct scst_device *dev)
bool scst_alua_configured(struct scst_device *dev)
{
struct scst_dev_group *dg;
@@ -1214,7 +1317,7 @@ bool scst_impl_alua_configured(struct scst_device *dev)
return dg != NULL;
}
EXPORT_SYMBOL_GPL(scst_impl_alua_configured);
EXPORT_SYMBOL_GPL(scst_alua_configured);
/**
* scst_tg_get_group_info() - Build REPORT TARGET GROUPS response.
@@ -1329,3 +1432,356 @@ out:
return res;
}
EXPORT_SYMBOL_GPL(scst_tg_get_group_info);
struct scst_stpg_wait {
atomic_t stpg_wait_left;
int status;
struct scst_dev_group *dg;
struct scst_event_entry *event_entry;
};
/* No locks */
static void scst_stpg_check_blocking_done(struct scst_stpg_wait *wait)
{
TRACE_ENTRY();
TRACE_DBG("wait %p, left %d", wait, atomic_read(&wait->stpg_wait_left));
if (atomic_dec_and_test(&wait->stpg_wait_left)) {
if (wait->status == 0)
scst_event_queue(SCST_EVENT_STPG_USER_INVOKE,
SCST_EVENT_SCST_CORE_ISSUER, wait->event_entry);
else {
wait->event_entry->event_notify_fn(&wait->event_entry->event,
wait->event_entry->notify_fn_priv, wait->status);
}
kfree(wait);
}
TRACE_EXIT();
return;
}
/* No locks */
static void scst_stpg_ext_blocking_done(struct scst_device *dev,
uint8_t *data, int len)
{
sBUG_ON(len != sizeof(data));
scst_stpg_check_blocking_done(*((struct scst_stpg_wait **)data));
}
/**
* scst_tg_set_group_info - SET TARGET PORT GROUPS implementation.
*
* Returns >=0 upon success or negative error code otherwise, for instance,
* if either an invalid group ID has been specified or the group ID
* of a target group with one, or more non-local target ports has been
* specified. In the error case the cmd has its sense set.
*
* In case of returned 0 the command completed asynchronously, i.e. upon
* return might be already dead!!
*/
int scst_tg_set_group_info(struct scst_cmd *cmd)
{
struct scst_device *dev = cmd->dev;
uint8_t *buf;
int len;
int i, j, res = 1, tpg_desc_count, valid_desc_count;
struct scst_dev_group *dg;
struct osi {
uint16_t group_id;
struct scst_target_group *tg;
enum scst_tg_state prev_state;
enum scst_tg_state new_state;
} *osi = NULL;
int event_entry_len, payload_len;
struct scst_event_entry *event_entry;
struct scst_event *event;
struct scst_event_stpg_payload *payload;
struct scst_event_stpg_descr *descr;
TRACE_ENTRY();
len = scst_get_buf_full(cmd, &buf);
if (len < 0) {
PRINT_ERROR("scst_get_buf_full() failed: %d", len);
res = len;
if (len == -ENOMEM)
scst_set_busy(cmd);
else
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
/*
* From SPC-4: "A parameter list length of zero specifies that no data
* shall be transferred, and that no change shall be made in the
* target port asymmetric access state of any target port groups or
* target ports".
*/
if (len == 0)
goto out_put;
tpg_desc_count = (len - 4) / 4;
/* Check for some reasonable limit */
if (tpg_desc_count > 64) {
PRINT_ERROR("Too many STPG descriptors (%d) for dev %s",
tpg_desc_count, dev->virt_name);
res = -EINVAL;
scst_set_invalid_field_in_cdb(cmd, 6, 0);
goto out_put;
}
TRACE_DBG("tpg_desc_count %d", tpg_desc_count);
osi = kcalloc(tpg_desc_count, sizeof(*osi), GFP_KERNEL);
if (!osi) {
res = -ENOMEM;
scst_set_busy(cmd);
goto out_put;
}
res = mutex_lock_interruptible(&scst_mutex);
if (res) {
PRINT_INFO("mutex_lock_interruptible() returned %d, finishing "
"cmd %p", res, cmd);
scst_set_busy(cmd);
goto out_put;
}
res = mutex_lock_interruptible(&scst_dg_mutex);
if (res) {
PRINT_INFO("mutex_lock_interruptible() returned %d, finishing "
"cmd %p", res, cmd);
scst_set_busy(cmd);
goto out_unlock_sm_fail;
}
dg = __lookup_dg_by_dev(dev);
if (!dg) {
res = -EINVAL;
goto out_unlock_fail;
}
TRACE_DBG("dg %s (%p) found, dev %s", dg->name, dg, dev->virt_name);
for (i = 4, j = 0; i + 4 <= len; i += 4, j++) {
WARN_ON_ONCE(j >= tpg_desc_count);
osi[j].new_state = buf[i] & 0x1f;
switch (osi[j].new_state) {
case SCST_TG_STATE_OPTIMIZED:
case SCST_TG_STATE_NONOPTIMIZED:
case SCST_TG_STATE_STANDBY:
case SCST_TG_STATE_UNAVAILABLE:
case SCST_TG_STATE_OFFLINE:
break;
default:
TRACE_MGMT_DBG("Incorrect new state %d", osi[j].new_state);
res = -EINVAL;
goto out_unlock_fail;
}
osi[j].group_id = get_unaligned_be16(&buf[i + 2]);
if (!osi[j].group_id) {
TRACE_MGMT_DBG("Invalid group_id %d", osi[j].group_id);
res = -EINVAL;
goto out_unlock_fail;
}
osi[j].tg = __lookup_tg_by_group_id(dg, osi[j].group_id);
if (!osi[j].tg) {
TRACE_MGMT_DBG("No TG for group_id %d", osi[j].group_id);
res = -ESRCH;
goto out_unlock_fail;
}
if (osi[j].tg->state == SCST_TG_STATE_TRANSITIONING) {
TRACE_MGMT_DBG("TG %p is transitioning", osi[j].tg);
res = -EBUSY;
scst_set_cmd_error(cmd,
SCST_LOAD_SENSE(scst_sense_alua_transitioning));
/* second sense will not override the set one */
goto out_unlock_fail;
}
osi[j].prev_state = osi[j].tg->state;
TRACE_DBG("j %d, group_id %u, tg %s (%p), state %d", j, osi[j].group_id,
osi[j].tg->name, osi[j].tg, osi[j].tg->state);
}
mutex_unlock(&scst_dg_mutex);
mutex_unlock(&scst_mutex);
scst_put_buf_full(cmd, buf);
payload_len = sizeof(*payload) + sizeof(*descr) * tpg_desc_count;
event_entry_len = sizeof(*event_entry) + payload_len;
event_entry = kzalloc(event_entry_len, GFP_KERNEL);
if (event_entry == NULL) {
PRINT_ERROR("Unable to allocate event (size %d)", event_entry_len);
res = -ENOMEM;
scst_set_busy(cmd);
goto out_free;
}
TRACE_MEM("event_entry %p (len %d) allocated", event_entry,
event_entry_len);
event = &event_entry->event;
event->payload_len = payload_len;
payload = (struct scst_event_stpg_payload *)event->payload;
payload->stpg_cmd_tag = cmd->tag;
res = 1;
if (strlen(dev->virt_name) >= sizeof(payload->device_name)) {
PRINT_ERROR("Device name %s too long", dev->virt_name);
goto out_too_long;
}
strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name));
valid_desc_count = 0;
for (j = 0, descr = &payload->stpg_descriptors[0]; j < tpg_desc_count; j++) {
if (osi[j].prev_state == osi[j].new_state)
continue;
if (strlen(scst_alua_state_name(osi[j].prev_state)) >= sizeof(descr->prev_state)) {
PRINT_ERROR("prev state too long (%d)", osi[j].prev_state);
goto out_too_long;
}
strlcpy(descr->prev_state, scst_alua_state_name(osi[j].prev_state),
sizeof(descr->prev_state));
if (strlen(scst_alua_state_name(osi[j].new_state)) >= sizeof(descr->new_state)) {
PRINT_ERROR("new state too long (%d)", osi[j].new_state);
goto out_too_long;
}
strlcpy(descr->new_state, scst_alua_state_name(osi[j].new_state),
sizeof(descr->new_state));
if (strlen(dg->name) >= sizeof(descr->dg_name)) {
PRINT_ERROR("dg_name too long (%s)", dg->name);
goto out_too_long;
}
strlcpy(descr->dg_name, dg->name, sizeof(descr->dg_name));
if (strlen(osi[j].tg->name) >= sizeof(descr->tg_name)) {
PRINT_ERROR("tg_name too long (%s)", osi[j].tg->name);
goto out_too_long;
}
strlcpy(descr->tg_name, osi[j].tg->name,
sizeof(descr->tg_name));
descr->group_id = osi[j].group_id;
TRACE_DBG("group_id %u, prev_state %s, new_state %s, dg_name %s, "
"tg_name %s", descr->group_id, descr->prev_state,
descr->new_state, descr->dg_name, descr->tg_name);
valid_desc_count++;
descr++;
}
payload->stpg_descriptors_cnt = valid_desc_count;
if (valid_desc_count > 0) {
struct scst_dg_dev *dgd;
struct scst_stpg_wait *wait;
int rc;
dg->stpg_rel_tgt_id = cmd->tgt->rel_tgt_id;
dg->stpg_transport_id = kmemdup(cmd->sess->transport_id,
scst_tid_size(cmd->sess->transport_id), GFP_KERNEL);
if (dg->stpg_transport_id == NULL) {
PRINT_ERROR("Unable to duplicate stpg_transport_id");
goto out_free_event;
}
wait = kzalloc(sizeof(*wait), GFP_KERNEL);
if (wait == NULL) {
PRINT_ERROR("Unable to allocate STPG wait struct "
"(size %zd)", sizeof(*wait));
scst_set_busy(cmd);
res = -ENOMEM;
goto out_free_tr_id;
}
atomic_set(&wait->stpg_wait_left, 1);
wait->event_entry = event_entry;
event_entry->event_notify_fn = scst_event_stpg_notify_fn;
event_entry->notify_fn_priv = cmd;
mutex_lock(&scst_dg_mutex);
list_for_each_entry(dgd, &dg->dev_list, entry) {
if (dgd->dev == dev)
continue;
TRACE_DBG("STPG: ext blocking dev %s", dgd->dev->virt_name);
atomic_inc(&wait->stpg_wait_left);
spin_lock_bh(&dev->dev_lock);
WARN_ON(dgd->dev->stpg_ext_blocked);
dgd->dev->stpg_ext_blocked = 1;
spin_unlock_bh(&dev->dev_lock);
rc = scst_ext_block_dev(dgd->dev, false,
scst_stpg_ext_blocking_done, (uint8_t *)&wait,
sizeof(wait));
if (rc != 0) {
TRACE_DBG("scst_ext_block_dev() returned %d, "
"stepping back (cmd %p)", rc, cmd);
wait->status = rc;
wait->dg = dg;
atomic_dec(&wait->stpg_wait_left);
spin_lock_bh(&dev->dev_lock);
WARN_ON(dgd->dev->stpg_ext_blocked);
dgd->dev->stpg_ext_blocked = 0;
spin_unlock_bh(&dev->dev_lock);
break;
}
}
mutex_unlock(&scst_dg_mutex);
scst_stpg_check_blocking_done(wait);
/* !! cmd can be already dead here !! */
} else {
TRACE_DBG("Nothing to do");
goto out_free_event;
}
res = 0;
out_free:
kfree(osi);
out:
TRACE_EXIT_RES(res);
return res;
out_unlock_fail:
mutex_unlock(&scst_dg_mutex);
out_unlock_sm_fail:
mutex_unlock(&scst_mutex);
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed));
out_put:
scst_put_buf_full(cmd, buf);
goto out_free;
out_too_long:
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed));
res = -EOVERFLOW;
out_free_tr_id:
kfree(dg->stpg_transport_id);
out_free_event:
kfree(event_entry);
goto out_free;
}
EXPORT_SYMBOL_GPL(scst_tg_set_group_info);