From e50f240d3304369978be0a6ca88f7943e7fc6321 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:28:53 +0000 Subject: [PATCH] Explicit ALUA support Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6576 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 73 +++-- scst/include/scst.h | 11 +- scst/include/scst_const.h | 7 +- scst/include/scst_event.h | 19 ++ scst/src/dev_handlers/scst_vdisk.c | 113 ++++++- scst/src/scst_event.c | 2 +- scst/src/scst_lib.c | 34 +- scst/src/scst_pres.c | 2 +- scst/src/scst_pres.h | 2 + scst/src/scst_priv.h | 2 +- scst/src/scst_sysfs.c | 2 +- scst/src/scst_tg.c | 490 ++++++++++++++++++++++++++++- 12 files changed, 695 insertions(+), 62 deletions(-) diff --git a/scst/README b/scst/README index b14a4a510..61592401e 100644 --- a/scst/README +++ b/scst/README @@ -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 diff --git a/scst/include/scst.h b/scst/include/scst.h index 69ccd12ba..7f1b7ebb2 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -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 diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 39a829b58..1552b8e87 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -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 diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h index f77300126..c13f2b47f 100644 --- a/scst/include/scst_event.h +++ b/scst/include/scst_event.h @@ -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 */ diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 6a5181061..be6f489c2 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -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) { diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c index 15aad6a6d..52cec1121 100644 --- a/scst/src/scst_event.c +++ b/scst/src/scst_event.c @@ -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); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 4da51033a..a707c5129 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -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); } diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index acc04481d..037c0d462 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -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; diff --git a/scst/src/scst_pres.h b/scst/src/scst_pres.h index 2decf5b4c..5ee98a3ef 100644 --- a/scst/src/scst_pres.h +++ b/scst/src/scst_pres.h @@ -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, diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 308c9d9f8..17462a38a 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -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); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 6f406c900..cf88bea63 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -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': diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 3dc891a7a..68dc6a668 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -16,6 +16,8 @@ */ #include +#include +#include #include #ifdef INSIDE_KERNEL_TREE #include @@ -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);