From e1447ae241bfe033f5d3048f16b51842e17306d6 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Wed, 11 May 2011 02:22:48 +0000 Subject: [PATCH] Add implicit ALUA support. On a setup with more than one target port (sometimes called storage head) this patch makes it possible to tell the initiator which port to use. Since that information can be specified per LUN, with this patch it is not only possible to tell the initiator which path to choose but also to balance the load over target ports. Target port attributes can not only be defined for target ports that exist on the system on which SCST is running but also for target ports present on other nodes of the same HA setup. As far as I know this feature is unique among open source storage target implementations. Note: with this patch SCSI target port attributes can be defined for each LUN defined on each SCST target individually. Such an SCST target is either a physical entity (e.g. a HCA for the ib_srpt driver) or a logical entity (e.g. an iSCSI target for the iscsi_scst driver). In the last case it is up to the user to make sure that there is a one-to-one relationship between SCST target and SCSI target port. This patch is based on the specifications in the ANSI T10 spc4r30 document. Signed-off-by: Bart Van Assche git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@3446 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scripts/generate-kernel-patch | 3 +- scst/README | 240 +++++++ scst/include/scst.h | 68 ++ scst/include/scst_const.h | 48 ++ scst/kernel/in-tree/Makefile.scst-2.6.23 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.24 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.25 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.26 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.27 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.28 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.29 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.30 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.31 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.32 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.33 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.34 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.35 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.36 | 1 + scst/kernel/in-tree/Makefile.scst-2.6.37 | 1 + scst/src/Makefile | 1 + scst/src/dev_handlers/scst_vdisk.c | 84 +++ scst/src/scst_main.c | 11 + scst/src/scst_priv.h | 64 ++ scst/src/scst_sysfs.c | 715 +++++++++++++++++++ scst/src/scst_tg.c | 835 +++++++++++++++++++++++ 25 files changed, 2083 insertions(+), 1 deletion(-) create mode 100644 scst/src/scst_tg.c diff --git a/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index 8640b0c08..d7a23c4a0 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -288,7 +288,8 @@ scst_proc="scst/src/scst_proc.c" scst_10_sgv="scst/include/scst_sgv.h scst/src/scst_mem.h scst/src/scst_mem.c doc/sgv_cache.sgml" scst_user="scst/include/scst_user.h scst/src/dev_handlers/scst_user.c" scst_13_vdisk="scst/src/dev_handlers/scst_vdisk.c" -separate_patches="scst_03_public_headers scst_04_main scst_05_targ scst_06_lib scst_07_pres scst_08_sysfs scst_09_debug scst_10_sgv scst_user scst_13_vdisk" +scst_14_tg="scst/src/scst_tg.c" +separate_patches="scst_03_public_headers scst_04_main scst_05_targ scst_06_lib scst_07_pres scst_08_sysfs scst_09_debug scst_10_sgv scst_user scst_13_vdisk scst_14_tg" if [ "${generating_upstream_patch}" = "false" ]; then separate_patches+=" scst_proc" diff --git a/scst/README b/scst/README index d63d38bb8..74482b88a 100644 --- a/scst/README +++ b/scst/README @@ -1099,6 +1099,246 @@ persistent reservations from this device are released, upon reconnect the initiators will see it. +Implicit 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. + +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 + identifies a target port. These numbers must be unique over the target as + a whole, even if that target consists of multiple systems each running SCST. +* Target port group asymmetric access state. One of active/optimized, + active/non-optimized, standby, unavailable, logical block dependent or + offline. The access state of a port defines which (if any) SCSI commands + will be processed by the target port. +* Target port preference indicator. This indicator is additional information + next to the asymmetric access state that is provided by the target to an + initiator and that may impact the decision taken by the initiator about + which path that will be choosen. + +More detailed information about ALUA can be found in section 5.11.2 of the +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, 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. Hence for +target drivers like iSCSI-SCST configuring ALUA in SCST only makes sense in a +setup involving more than one system, e.g. a H.A. setup. + +Notes: +- In a H.A. setup it is the responsibility of the user to synchronize ALUA + information between the individual systems running SCST. There are no + provisions in SCST to exchange ALUA information automatically between + individual systems. +- In order to support H.A. setups it is possible to let one SCST system + report information about target ports present in other SCST systems. +- With SCST, and certainly in a H.A. setup, it is possible to configure ALUA + such that an initiator receives information that is not standard compliant, + e.g. setting all target ports in the offline state. It is the responsibility + of the user to make sure that the information queried by an initiator is + consistent independent of the LUN and the target port used by the initiator + to query this information. + +Configuring ALUA in SCST +........................ + +SCST allows to configure the following settings related to implicit 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 + states except logical block dependent. +* The preference indicator for a target port group. +* The relative target port ID associated with the SCST target. + +It is possible to configure the following ALUA-related information via the +sysfs interface of SCST: +* Device groups, where each device group has a name and contains zero or more + SCST devices. If a device group contains only a single SCST device, the name + of the group may be identical to the device name. See also + /sys/kernel/scst_tgt/device_groups/mgmt. +* Which devices are inside a device group. See also + /sys/kernel/scst_tgt/device_groups//devices/mgmt. +* Target groups, where each target group has a name and contains zero or more + SCST target names. See also + /sys/kernel/scst_tgt/device_groups//target_groups/mgmt. +* Target port group identifier. This is a number in the range 0..65535 and is + called the TARGET PORT GROUP in SPC-4. See also + /sys/kernel/scst_tgt/device_groups//target_groups//group_id. +* Target port group preference indicator. This is a boolean value called the + PREF bit in SPC-4. See also /sys/kernel/scst_tgt/device_groups//target_groups//preferred. +* Target port group state name. One of active, nonoptimized, standby, + unavailable, offline or transitioning. See also + /sys/kernel/scst_tgt/device_groups//target_groups//state. +* Target group contents - zero or more target names. The target names either + exist on the local system or on a remote system in a H.A. setup. For target + names that refer to SCST targets on another system only the relative target + port identifier matters, not the assigned name. See also + /sys/kernel/scst_tgt/device_groups//target_groups//mgmt. +* Relative target identifier. See also + /sys/kernel/scst_tgt/device_groups//target_groups///rel_tgt_id. + +The steps involved in configuring ALUA are: +* Identify the SCST devices that will always share the same ALUA settings and + state. Assign a name to each such group of SCST devices. If a device group + only contains a single device, the group name may be identical to the device + name. +* Configure that device group in SCST via sysfs. +* Identify the SCSI target ports that will always share the same ALUA settings + and state. Assign a name, a group ID and preference indicator to each such + SCSI target port group. +* Configure the target port group information in SCST via sysfs. +* Identify all SCST targets that can be accessed via a target port group. +* Assign all these SCST target names to the target group via sysfs. +* Assign a relative target port identifier to each target. + +As an example, in a H.A. setup with two systems each having one InfiniBand +HCA controlled by the ib_srpt driver and where each system exports two LUNs +could be configured as follows: + +own_tgt_id=1 +other_tgt_id=2 +cd /sys/kernel/scst_tgt/device_groups +echo del dgroup1 >mgmt +echo del dgroup2 >mgmt +echo add dgroup1 >mgmt +echo add disk01 >dgroup1/devices/mgmt +echo add tgroup1 >dgroup1/target_groups/mgmt +echo ${own_tgt_id} >dgroup1/target_groups/tgroup1/group_id +echo add ib_srpt_0 >dgroup1/target_groups/tgroup1/mgmt +echo ${own_tgt_id} >dgroup1/target_groups/tgroup1/ib_srpt_0/rel_tgt_id +if [ ${own_tgt_id} = 1 ]; then + echo 1 >dgroup1/target_groups/tgroup1/preferred +fi +echo add tgroup2 >dgroup1/target_groups/mgmt +echo ${other_tgt_id} >dgroup1/target_groups/tgroup2/group_id +echo add ib_srpt_0-other >dgroup1/target_groups/tgroup2/mgmt +echo ${other_tgt_id} >dgroup1/target_groups/tgroup2/ib_srpt_0-other/rel_tgt_id +if [ ${other_tgt_id} = 1 ]; then + echo 1 >dgroup1/target_groups/tgroup1/preferred +fi +echo add dgroup2 >mgmt +echo add disk02 >dgroup2/devices/mgmt +echo add tgroup1 >dgroup2/target_groups/mgmt +echo ${own_tgt_id} >dgroup2/target_groups/tgroup1/group_id +echo add ib_srpt_0 >dgroup2/target_groups/tgroup1/mgmt +echo ${own_tgt_id} >dgroup2/target_groups/tgroup1/ib_srpt_0/rel_tgt_id +if [ ${own_tgt_id} = 2 ]; then + echo 1 >dgroup2/target_groups/tgroup1/preferred +fi +echo add tgroup2 >dgroup2/target_groups/mgmt +echo ${other_tgt_id} >dgroup2/target_groups/tgroup2/group_id +echo add ib_srpt_0-other >dgroup2/target_groups/tgroup2/mgmt +echo ${other_tgt_id} >dgroup2/target_groups/tgroup2/ib_srpt_0-other/rel_tgt_id +if [ ${other_tgt_id} = 2 ]; then + echo 1 >dgroup2/target_groups/tgroup1/preferred +fi + +The second system in the same H.A. setup can be configured with the same +commands but with the values of ${own_rel_tgt_id} and ${other_rel_tgt_id} +swapped. + +The result of the above commands is: + +$ find -type f | grep -v '/mgmt$' | cut -c3- | sort | \ + while read f; do echo $f = $(head -n 1 $f); done +dgroup1/target_groups/tgroup1/group_id = 1 +dgroup1/target_groups/tgroup1/ib_srpt_0/rel_tgt_id = 1 +dgroup1/target_groups/tgroup1/preferred = 1 +dgroup1/target_groups/tgroup1/state = active +dgroup1/target_groups/tgroup2/group_id = 2 +dgroup1/target_groups/tgroup2/ib_srpt_0-other/rel_tgt_id = 2 +dgroup1/target_groups/tgroup2/preferred = 0 +dgroup1/target_groups/tgroup2/state = active +dgroup2/target_groups/tgroup1/group_id = 1 +dgroup2/target_groups/tgroup1/ib_srpt_0/rel_tgt_id = 1 +dgroup2/target_groups/tgroup1/preferred = 1 +dgroup2/target_groups/tgroup1/state = active +dgroup2/target_groups/tgroup2/group_id = 2 +dgroup2/target_groups/tgroup2/ib_srpt_0-other/rel_tgt_id = 2 +dgroup2/target_groups/tgroup2/preferred = 0 +dgroup2/target_groups/tgroup2/state = active + +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: + +# sg_inq /dev/sdb +standard INQUIRY: + PQual=0 Device_type=0 RMB=0 version=0x05 [SPC-3] + [AERC=0] [TrmTsk=0] NormACA=0 HiSUP=1 Resp_data_format=2 + SCCS=0 ACC=0 TPGS=1 3PC=0 Protect=0 BQue=0 + EncServ=0 MultiP=0 [MChngr=0] [ACKREQQ=0] Addr16=1 + [RelAdr=0] WBus16=0 Sync=0 Linked=0 [TranDis=0] CmdQue=1 + [SPI: Clocking=0x0 QAS=0 IUS=0] + length=66 (0x42) Peripheral device type: disk + Vendor identification: SCST_FIO + Product identification: disk01 + Product revision level: 300 + Unit serial number: 27cddc71 + +The next step is to verify the target group configuration. That is possible +by verifying whether the output of the sg_rtpg command matches the values +configured on the target: + +# sg_rtpg /dev/sdb +Report target port groups: + target port group id : 0x1 , Pref=1 + target port group asymmetric access state : 0x00 + T_SUP : 0, O_SUP : 0, LBD_SUP : 0, U_SUP : 1, S_SUP : 1, AN_SUP : 1, AO_SUP : 1 + status code : 0x02 + vendor unique status : 0x00 + target port count : 01 + Relative target port ids: + 0x01 + target port group id : 0x2 , Pref=0 + target port group asymmetric access state : 0x00 + T_SUP : 0, O_SUP : 0, LBD_SUP : 0, U_SUP : 1, S_SUP : 1, AN_SUP : 1, AO_SUP : 1 + status code : 0x02 + vendor unique status : 0x00 + target port count : 01 + Relative target port ids: + 0x02 + +Initiator Support +................. + +On Linux systems implicit ALUA support is provided by the scsi_dh_alua driver +of the device mapper. Information about how to configure the device mapper and +the scsi_dh_alua driver can be found in the manual of your Linux distribution. + +Windows initiator systems support ALUA from Windows Server 2008 on. For more +information, see also: +* Microsoft, Multipathing Support in Windows Server 2008, MSDN +(http://blogs.msdn.com/b/san/archive/2008/07/27/multipathing-support-in-windows-server-2008.aspx). +* Microsoft, ALUA MPIO Logo Test, MSDN +(http://msdn.microsoft.com/en-us/library/gg607458%28v=vs.85%29.aspx). + + Caching ------- diff --git a/scst/include/scst.h b/scst/include/scst.h index 434252192..5bc76a108 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -5,6 +5,7 @@ * Copyright (C) 2004 - 2005 Leonid Stoljar * Copyright (C) 2007 - 2010 ID7 Ltd. * Copyright (C) 2010 - 2011 SCST Ltd. + * Copyright (C) 2010 - 2011 Bart Van Assche . * * Main SCSI target mid-level include file. * @@ -2578,6 +2579,65 @@ struct scst_acn { struct kobj_attribute *acn_attr; }; +/** + * struct scst_dev_group - A group of SCST devices (struct scst_device). + * + * Each device is member of zero or one device groups. With each device group + * there are zero or more target groups associated. + */ +struct scst_dev_group { + char *name; + struct list_head entry; + struct list_head dev_list; + struct list_head tg_list; + struct kobject kobj; + struct kobject *dev_kobj; + struct kobject *tg_kobj; +}; + +/** + * struct scst_dg_dev - A node in scst_dev_group.dev_list. + */ +struct scst_dg_dev { + struct list_head entry; + struct scst_device *dev; +}; + +/** + * struct scst_target_group - A group of SCSI targets (struct scst_tgt). + * + * Such a group is either a primary target port group or a secondary + * port group. See also SPC-4 for more information. + */ +struct scst_target_group { + struct scst_dev_group *dg; + char *name; + uint16_t group_id; + enum scst_tg_state state; + bool preferred; + struct list_head entry; + struct list_head tgt_list; + struct kobject kobj; +}; + +/** + * struct scst_tg_tgt - A node in scst_target_group.tgt_list. + * + * Such a node can either represent a local storage target (struct scst_tgt) + * or a storage target on another system running SCST. In the former case tgt + * != NULL and rel_tgt_id is ignored. In the latter case tgt == NULL and + * rel_tgt_id is relevant. + */ +struct scst_tg_tgt { + struct list_head entry; + struct scst_target_group *tg; + struct kobject kobj; + struct scst_tgt *tgt; + char *name; + uint16_t rel_tgt_id; +}; + + /* * Used to store per-session UNIT ATTENTIONs */ @@ -2858,6 +2918,11 @@ static inline void scst_sess_set_tgt_priv(struct scst_session *sess, sess->tgt_priv = val; } +uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt); +bool scst_impl_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); + /** * Returns TRUE if cmd is being executed in atomic context. * @@ -4109,6 +4174,9 @@ struct scst_sysfs_work_item { struct scst_tgt *tgt_r; unsigned long rel_tgt_id; }; + struct { + struct kobject *kobj; + }; }; int work_res; char *res_buf; diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index f80fac57e..c0d32480b 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -266,6 +266,7 @@ enum scst_cdb_flags { #define scst_sense_reservation_preempted UNIT_ATTENTION, 0x2A, 0x03 #define scst_sense_reservation_released UNIT_ATTENTION, 0x2A, 0x04 #define scst_sense_registrations_preempted UNIT_ATTENTION, 0x2A, 0x05 +#define scst_sense_asym_access_state_changed UNIT_ATTENTION, 0x2A, 0x06 #define scst_sense_reported_luns_data_changed UNIT_ATTENTION, 0x3F, 0xE #define scst_sense_inquery_data_changed UNIT_ATTENTION, 0x3F, 0x3 @@ -368,6 +369,14 @@ enum scst_cdb_flags { #define SCST_INQ_NORMACA_BIT 0x20 +/************************************************************* + ** TPGS field in byte 5 of the INQUIRY response (SPC-4). + *************************************************************/ +enum { + SCST_INQ_TPGS_MODE_IMPLICIT = 0x10, + SCST_INQ_TPGS_MODE_EXPLICIT = 0x20, +}; + /************************************************************* ** Byte 2 in RESERVE_10 CDB *************************************************************/ @@ -402,6 +411,45 @@ enum scst_cdb_flags { #define SCSI_TRANSPORTID_PROTOCOLID_ISCSI 5 #define SCSI_TRANSPORTID_PROTOCOLID_SAS 6 +/** + * enum scst_tg_state - SCSI target port group asymmetric access state. + * + * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4. + */ +enum scst_tg_state { + SCST_TG_STATE_OPTIMIZED = 0x0, + SCST_TG_STATE_NONOPTIMIZED = 0x1, + SCST_TG_STATE_STANDBY = 0x2, + SCST_TG_STATE_UNAVAILABLE = 0x3, + SCST_TG_STATE_LBA_DEPENDENT = 0x4, + SCST_TG_STATE_OFFLINE = 0xe, + SCST_TG_STATE_TRANSITIONING = 0xf, +}; + +/** + * Target port group preferred bit. + * + * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4. + */ +enum { + SCST_TG_PREFERRED = 0x80, +}; + +/** + * enum scst_tg_sup - Supported SCSI target port group states. + * + * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4. + */ +enum scst_tg_sup { + SCST_TG_SUP_OPTIMIZED = 0x01, + SCST_TG_SUP_NONOPTIMIZED = 0x02, + SCST_TG_SUP_STANDBY = 0x04, + SCST_TG_SUP_UNAVAILABLE = 0x08, + SCST_TG_SUP_LBA_DEPENDENT = 0x10, + SCST_TG_SUP_OFFLINE = 0x40, + SCST_TG_SUP_TRANSITION = 0x80, +}; + /************************************************************* ** Misc SCSI constants *************************************************************/ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.23 b/scst/kernel/in-tree/Makefile.scst-2.6.23 index c93ee5d59..f801073af 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.23 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.23 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.24 b/scst/kernel/in-tree/Makefile.scst-2.6.24 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.24 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.24 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.25 b/scst/kernel/in-tree/Makefile.scst-2.6.25 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.25 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.25 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.26 b/scst/kernel/in-tree/Makefile.scst-2.6.26 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.26 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.26 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.27 b/scst/kernel/in-tree/Makefile.scst-2.6.27 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.27 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.27 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.28 b/scst/kernel/in-tree/Makefile.scst-2.6.28 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.28 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.28 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.29 b/scst/kernel/in-tree/Makefile.scst-2.6.29 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.29 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.29 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.30 b/scst/kernel/in-tree/Makefile.scst-2.6.30 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.30 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.30 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.31 b/scst/kernel/in-tree/Makefile.scst-2.6.31 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.31 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.31 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.32 b/scst/kernel/in-tree/Makefile.scst-2.6.32 index 02f9d3a76..e040252e4 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.32 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.32 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_proc.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ srpt/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.33 b/scst/kernel/in-tree/Makefile.scst-2.6.33 index e95e98fe4..d2dd04dc2 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.33 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.33 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_sysfs.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.34 b/scst/kernel/in-tree/Makefile.scst-2.6.34 index e95e98fe4..d2dd04dc2 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.34 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.34 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_sysfs.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.35 b/scst/kernel/in-tree/Makefile.scst-2.6.35 index df4312ba2..53af5f388 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.35 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.35 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_sysfs.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.36 b/scst/kernel/in-tree/Makefile.scst-2.6.36 index df4312ba2..53af5f388 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.36 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.36 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_sysfs.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ diff --git a/scst/kernel/in-tree/Makefile.scst-2.6.37 b/scst/kernel/in-tree/Makefile.scst-2.6.37 index df4312ba2..53af5f388 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.37 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.37 @@ -6,6 +6,7 @@ scst-y += scst_targ.o scst-y += scst_lib.o scst-y += scst_sysfs.o scst-y += scst_mem.o +scst-y += scst_tg.o scst-y += scst_debug.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ diff --git a/scst/src/Makefile b/scst/src/Makefile index 558371240..0247e358f 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -45,6 +45,7 @@ scst-y += scst_sysfs.o scst-y += scst_mem.o scst-y += scst_debug.o scst-y += scst_pres.o +scst-y += scst_tg.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ obj-$(BUILD_DEV) += $(DEV_HANDLERS_DIR)/ diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 45f2e896b..7530f7716 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -211,6 +211,7 @@ static void vdisk_exec_verify(struct scst_cmd *cmd, struct scst_vdisk_thr *thr, loff_t loff); static void vdisk_exec_read_capacity(struct scst_cmd *cmd); static void vdisk_exec_read_capacity16(struct scst_cmd *cmd); +static void vdisk_exec_report_tpgs(struct scst_cmd *cmd); static void vdisk_exec_inquiry(struct scst_cmd *cmd); static void vdisk_exec_request_sense(struct scst_cmd *cmd); static void vdisk_exec_mode_sense(struct scst_cmd *cmd); @@ -1137,6 +1138,15 @@ static int vdisk_do_job(struct scst_cmd *cmd) case UNMAP: vdisk_exec_unmap(cmd, thr); break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + vdisk_exec_report_tpgs(cmd); + break; + default: + goto out_invalid_opcode; + } + break; case REPORT_LUNS: default: goto out_invalid_opcode; @@ -1376,6 +1386,7 @@ static void vdisk_exec_inquiry(struct scst_cmd *cmd) uint8_t *address; uint8_t *buf; struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + uint16_t tg_id; TRACE_ENTRY(); @@ -1476,6 +1487,22 @@ static void vdisk_exec_inquiry(struct scst_cmd *cmd) num += 4; + tg_id = scst_lookup_tg_id(cmd->dev, cmd->tgt); + if (tg_id) { + /* + * Target port group designator + */ + buf[num + 0] = 0x01; /* binary */ + /* Target port group id */ + buf[num + 1] = 0x10 | 0x05; + + put_unaligned(cpu_to_be16(tg_id), + (__be16 *)&buf[num + 4 + 2]); + + buf[num + 3] = 4; + num += 4 + buf[num + 3]; + } + /* * IEEE id */ @@ -1574,6 +1601,8 @@ static void vdisk_exec_inquiry(struct scst_cmd *cmd) if (cmd->tgtt->fake_aca) buf[3] |= 0x20; buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */ + if (scst_impl_alua_configured(cmd->dev)) + buf[5] = SCST_INQ_TPGS_MODE_IMPLICIT; buf[6] = 0x10; /* MultiP 1 */ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ @@ -2336,6 +2365,61 @@ out: return; } +/* SPC-4 REPORT TARGET PORT GROUPS command */ +static void vdisk_exec_report_tpgs(struct scst_cmd *cmd) +{ + struct scst_device *dev; + uint8_t *address; + void *buf; + int32_t buf_len; + uint32_t allocation_length, data_length, length; + uint8_t data_format; + int res; + + TRACE_ENTRY(); + + buf_len = scst_get_buf_full(cmd, &address); + if (buf_len < 0) { + PRINT_ERROR("scst_get_buf_full() failed: %d", buf_len); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + if (cmd->cdb_len < 12) + PRINT_WARNING("received invalid REPORT TARGET PORT GROUPS " + "command - length %d is too small (should be at " + "least 12 bytes)", cmd->cdb_len); + + dev = cmd->dev; + data_format = cmd->cdb_len > 1 ? cmd->cdb[1] >> 5 : 0; + allocation_length = cmd->cdb_len >= 10 ? + be32_to_cpu(get_unaligned((__be32 *)(cmd->cdb + 6))) : 1024; + + res = scst_tg_get_group_info(&buf, &data_length, dev, data_format); + if (res == -ENOMEM) { + scst_set_busy(cmd); + goto out_put; + } else if (res < 0) { + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + length = min_t(uint32_t, min(allocation_length, data_length), buf_len); + memcpy(address, buf, length); + kfree(buf); + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out_put: + scst_put_buf_full(cmd, address); + +out: + TRACE_EXIT(); + return; +} + static void vdisk_exec_read_toc(struct scst_cmd *cmd) { int32_t length, off = 0; diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 98706b692..b85e00328 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -645,6 +645,8 @@ again: del_timer_sync(&tgt->retry_timer); + scst_tg_tgt_remove_by_tgt(tgt); + #ifdef CONFIG_SCST_PROC scst_cleanup_proc_target_entries(tgt); #else @@ -1006,6 +1008,8 @@ static void scst_unregister_device(struct scsi_device *scsidp) list_del(&dev->dev_list_entry); + scst_dg_dev_remove_by_dev(dev); + scst_assign_dev_handler(dev, &scst_null_devtype); list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list, @@ -1254,6 +1258,8 @@ void scst_unregister_virtual_device(int id) scst_pr_clear_dev(dev); + scst_dg_dev_remove_by_dev(dev); + scst_assign_dev_handler(dev, &scst_null_devtype); list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list, @@ -2277,6 +2283,8 @@ static int __init init_scst(void) if (res != 0) goto out_destroy_aen_mempool; + scst_tg_init(); + if (scst_max_cmd_mem == 0) { struct sysinfo si; si_meminfo(&si); @@ -2366,6 +2374,7 @@ out_free_acg: out_destroy_sgv_pool: scst_sgv_pools_deinit(); + scst_tg_cleanup(); out_sysfs_cleanup: scst_sysfs_cleanup(); @@ -2441,6 +2450,8 @@ static void __exit exit_scst(void) scst_sgv_pools_deinit(); + scst_tg_cleanup(); + scst_sysfs_cleanup(); #define DEINIT_CACHEP(p) do { \ diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index b83b18bc0..83633d04e 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -409,6 +409,70 @@ void scst_done_cmd_mgmt(struct scst_cmd *cmd); static inline void scst_devt_cleanup(struct scst_dev_type *devt) { } +void scst_tg_init(void); +void scst_tg_cleanup(void); +int scst_dg_add(struct kobject *parent, const char *name); +int scst_dg_remove(const char *name); +struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj); +int scst_dg_dev_add(struct scst_dev_group *dg, const char *name); +int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name); +int scst_dg_dev_remove_by_dev(struct scst_device *dev); +int scst_tg_add(struct scst_dev_group *dg, const char *name); +int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name); +int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state); +int scst_tg_tgt_add(struct scst_target_group *tg, const char *name); +int scst_tg_tgt_remove_by_name(struct scst_target_group *tg, const char *name); +void scst_tg_tgt_remove_by_tgt(struct scst_tgt *tgt); +#ifndef CONFIG_SCST_PROC +int scst_dg_sysfs_add(struct kobject *parent, struct scst_dev_group *dg); +void scst_dg_sysfs_del(struct scst_dev_group *dg); +int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, struct scst_dg_dev *dgdev); +void scst_dg_dev_sysfs_del(struct scst_dev_group *dg, + struct scst_dg_dev *dgdev); +int scst_tg_sysfs_add(struct scst_dev_group *dg, + struct scst_target_group *tg); +void scst_tg_sysfs_del(struct scst_target_group *tg); +int scst_tg_tgt_sysfs_add(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt); +void scst_tg_tgt_sysfs_del(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt); +#else +static inline int scst_dg_sysfs_add(struct kobject *parent, + struct scst_dev_group *dg) +{ + return 0; +} +static inline void scst_dg_sysfs_del(struct scst_dev_group *dg) +{ +} +static inline int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, + struct scst_dg_dev *dgdev) +{ + return 0; +} +static inline void scst_dg_dev_sysfs_del(struct scst_dev_group *dg, + struct scst_dg_dev *dgdev) +{ +} +static inline int scst_tg_sysfs_add(struct scst_dev_group *dg, + struct scst_target_group *tg) +{ + return 0; +} +static inline void scst_tg_sysfs_del(struct scst_target_group *tg) +{ +} +static inline int scst_tg_tgt_sysfs_add(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt) +{ + return 0; +} +static inline void scst_tg_tgt_sysfs_del(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt) +{ +} +#endif + #ifdef CONFIG_SCST_PROC int scst_proc_init_module(void); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 26a648f38..aad52fe26 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -39,6 +39,7 @@ static DECLARE_COMPLETION(scst_sysfs_root_release_completion); static struct kobject *scst_targets_kobj; static struct kobject *scst_devices_kobj; static struct kobject *scst_handlers_kobj; +static struct kobject *scst_device_groups_kobj; static const char *const scst_dev_handler_types[] = { "Direct-access device (e.g., magnetic disk)", @@ -4879,6 +4880,698 @@ void scst_devt_sysfs_del(struct scst_dev_type *devt) return; } +/** + ** SCST sysfs device_groups//devices/ implementation. + **/ + +int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) +{ + int res; + + TRACE_ENTRY(); + res = sysfs_create_link(dg->dev_kobj, &dgdev->dev->dev_kobj, + dgdev->dev->virt_name); + TRACE_EXIT_RES(res); + return res; +} + +void scst_dg_dev_sysfs_del(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) +{ + TRACE_ENTRY(); + sysfs_remove_link(dg->dev_kobj, dgdev->dev->virt_name); + TRACE_EXIT(); +} + +/** + ** SCST sysfs device_groups//devices directory implementation. + **/ + +static ssize_t scst_dg_devs_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + static const char help[] = + "Usage: echo \"add device\" >mgmt\n" + " echo \"del device\" >mgmt\n"; + + return scnprintf(buf, PAGE_SIZE, help); +} + +static int scst_dg_devs_mgmt_store_work_fn(struct scst_sysfs_work_item *w) +{ + struct scst_dev_group *dg; + char *cmd, *p, *pp, *dev_name; + int res; + + TRACE_ENTRY(); + + cmd = w->buf; + dg = scst_lookup_dg_by_kobj(w->kobj); + WARN_ON(!dg); + + p = strchr(cmd, '\n'); + if (p) + *p = '\0'; + + res = -EINVAL; + pp = cmd; + p = scst_get_next_lexem(&pp); + if (strcasecmp(p, "add") == 0) { + dev_name = scst_get_next_lexem(&pp); + if (!*dev_name) + goto out; + res = scst_dg_dev_add(dg, dev_name); + } else if (strcasecmp(p, "del") == 0) { + dev_name = scst_get_next_lexem(&pp); + if (!*dev_name) + goto out; + res = scst_dg_dev_remove_by_name(dg, dev_name); + } +out: + kobject_put(w->kobj); + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_dg_devs_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *cmd; + struct scst_sysfs_work_item *work; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!cmd) + goto out; + + res = scst_alloc_sysfs_work(scst_dg_devs_mgmt_store_work_fn, false, + &work); + if (res) + goto out; + + work->buf = cmd; + work->kobj = kobj; + kobject_get(kobj); + res = scst_sysfs_queue_wait_work(work); + +out: + if (res == 0) + res = count; + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_dg_devs_mgmt = + __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_devs_mgmt_show, + scst_dg_devs_mgmt_store); + +static const struct attribute *scst_dg_devs_attrs[] = { + &scst_dg_devs_mgmt.attr, + NULL, +}; + +/** + ** SCST sysfs device_groups//target_groups// implementation. + **/ + +static ssize_t scst_tg_tgt_rel_tgt_id_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_tg_tgt *tg_tgt; + + tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); + return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n", + tg_tgt->rel_tgt_id); +} + +static ssize_t scst_tg_tgt_rel_tgt_id_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct scst_tg_tgt *tg_tgt; + unsigned long rel_tgt_id; + char ch[8]; + int res; + + TRACE_ENTRY(); + tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); + snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); + res = strict_strtoul(ch, 0, &rel_tgt_id); + if (res) + goto out; + res = -EINVAL; + if (rel_tgt_id == 0 || rel_tgt_id > 0xffff) + goto out; + tg_tgt->rel_tgt_id = rel_tgt_id; + res = count; +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_tg_tgt_rel_tgt_id = + __ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_tg_tgt_rel_tgt_id_show, + scst_tg_tgt_rel_tgt_id_store); + +static const struct attribute *scst_tg_tgt_attrs[] = { + &scst_tg_tgt_rel_tgt_id.attr, + NULL, +}; + +int scst_tg_tgt_sysfs_add(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt) +{ + int res; + + TRACE_ENTRY(); + BUG_ON(!tg); + BUG_ON(!tg_tgt); + BUG_ON(!tg_tgt->name); + if (tg_tgt->tgt) + res = sysfs_create_link(&tg->kobj, &tg_tgt->tgt->tgt_kobj, + tg_tgt->name); + else { + res = kobject_add(&tg_tgt->kobj, &tg->kobj, "%s", tg_tgt->name); + if (res) + goto err; + res = sysfs_create_files(&tg_tgt->kobj, scst_tg_tgt_attrs); + if (res) + goto err; + } +out: + TRACE_EXIT_RES(res); + return res; +err: + scst_tg_tgt_sysfs_del(tg, tg_tgt); + goto out; +} + +void scst_tg_tgt_sysfs_del(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt) +{ + TRACE_ENTRY(); + if (tg_tgt->tgt) + sysfs_remove_link(&tg->kobj, tg_tgt->name); + else { + sysfs_remove_files(&tg_tgt->kobj, scst_tg_tgt_attrs); + kobject_del(&tg_tgt->kobj); + } + TRACE_EXIT(); +} + +/** + ** SCST sysfs device_groups//target_groups/ directory implementation. + **/ + +static ssize_t scst_tg_group_id_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_target_group *tg; + + tg = container_of(kobj, struct scst_target_group, kobj); + return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n", + tg->group_id); +} + +static ssize_t scst_tg_group_id_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct scst_target_group *tg; + unsigned long group_id; + char ch[8]; + int res; + + TRACE_ENTRY(); + tg = container_of(kobj, struct scst_target_group, kobj); + snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); + res = strict_strtoul(ch, 0, &group_id); + if (res) + goto out; + res = -EINVAL; + if (group_id == 0 || group_id > 0xffff) + goto out; + tg->group_id = group_id; + res = count; +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_tg_group_id = + __ATTR(group_id, S_IRUGO | S_IWUSR, scst_tg_group_id_show, + scst_tg_group_id_store); + +static ssize_t scst_tg_preferred_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_target_group *tg; + + tg = container_of(kobj, struct scst_target_group, kobj); + return scnprintf(buf, PAGE_SIZE, "%u\n%s", + tg->preferred, SCST_SYSFS_KEY_MARK "\n"); +} + +static ssize_t scst_tg_preferred_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct scst_target_group *tg; + unsigned long preferred; + char ch[8]; + int res; + + TRACE_ENTRY(); + tg = container_of(kobj, struct scst_target_group, kobj); + snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); + res = strict_strtoul(ch, 0, &preferred); + if (res) + goto out; + res = -EINVAL; + if (preferred != 0 && preferred != 1) + goto out; + tg->preferred = preferred; + res = count; +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_tg_preferred = + __ATTR(preferred, S_IRUGO | S_IWUSR, scst_tg_preferred_show, + scst_tg_preferred_store); + +static struct { enum scst_tg_state s; const char *n; } scst_tg_state_names[] = { + { SCST_TG_STATE_OPTIMIZED, "active" }, + { SCST_TG_STATE_NONOPTIMIZED, "nonoptimized" }, + { SCST_TG_STATE_STANDBY, "standby" }, + { SCST_TG_STATE_UNAVAILABLE, "unavailable" }, + { SCST_TG_STATE_OFFLINE, "offline" }, + { SCST_TG_STATE_TRANSITIONING, "transitioning" }, +}; + +static ssize_t scst_tg_state_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_target_group *tg; + int i; + + tg = container_of(kobj, struct scst_target_group, kobj); + for (i = ARRAY_SIZE(scst_tg_state_names) - 1; i >= 0; i--) + if (scst_tg_state_names[i].s == tg->state) + break; + + return scnprintf(buf, PAGE_SIZE, "%s\n" SCST_SYSFS_KEY_MARK "\n", + i >= 0 ? scst_tg_state_names[i].n : "???"); +} + +static int scst_tg_state_store_work_fn(struct scst_sysfs_work_item *w) +{ + struct scst_target_group *tg; + char *cmd, *p; + int i, res; + + TRACE_ENTRY(); + + cmd = w->buf; + tg = container_of(w->kobj, struct scst_target_group, kobj); + + p = strchr(cmd, '\n'); + if (p) + *p = '\0'; + + for (i = ARRAY_SIZE(scst_tg_state_names) - 1; i >= 0; i--) + if (strcmp(scst_tg_state_names[i].n, cmd) == 0) + break; + + res = -EINVAL; + if (i < 0) + goto out; + res = scst_tg_set_state(tg, scst_tg_state_names[i].s); +out: + kobject_put(w->kobj); + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_tg_state_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *cmd; + struct scst_sysfs_work_item *work; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!cmd) + goto out; + + res = scst_alloc_sysfs_work(scst_tg_state_store_work_fn, false, + &work); + if (res) + goto out; + + work->buf = cmd; + work->kobj = kobj; + kobject_get(kobj); + res = scst_sysfs_queue_wait_work(work); + +out: + if (res == 0) + res = count; + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_tg_state = + __ATTR(state, S_IRUGO | S_IWUSR, scst_tg_state_show, + scst_tg_state_store); + +static ssize_t scst_tg_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + static const char help[] = + "Usage: echo \"add target\" >mgmt\n" + " echo \"del target\" >mgmt\n"; + + return scnprintf(buf, PAGE_SIZE, help); +} + +static int scst_tg_mgmt_store_work_fn(struct scst_sysfs_work_item *w) +{ + struct scst_target_group *tg; + char *cmd, *p, *pp, *target_name; + int res; + + TRACE_ENTRY(); + + cmd = w->buf; + tg = container_of(w->kobj, struct scst_target_group, kobj); + + p = strchr(cmd, '\n'); + if (p) + *p = '\0'; + + res = -EINVAL; + pp = cmd; + p = scst_get_next_lexem(&pp); + if (strcasecmp(p, "add") == 0) { + target_name = scst_get_next_lexem(&pp); + if (!*target_name) + goto out; + res = scst_tg_tgt_add(tg, target_name); + } else if (strcasecmp(p, "del") == 0) { + target_name = scst_get_next_lexem(&pp); + if (!*target_name) + goto out; + res = scst_tg_tgt_remove_by_name(tg, target_name); + } +out: + kobject_put(w->kobj); + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_tg_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *cmd; + struct scst_sysfs_work_item *work; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!cmd) + goto out; + + res = scst_alloc_sysfs_work(scst_tg_mgmt_store_work_fn, false, + &work); + if (res) + goto out; + + work->buf = cmd; + work->kobj = kobj; + kobject_get(kobj); + res = scst_sysfs_queue_wait_work(work); + +out: + if (res == 0) + res = count; + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_tg_mgmt = + __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tg_mgmt_show, + scst_tg_mgmt_store); + +static const struct attribute *scst_tg_attrs[] = { + &scst_tg_mgmt.attr, + &scst_tg_group_id.attr, + &scst_tg_preferred.attr, + &scst_tg_state.attr, + NULL, +}; + +int scst_tg_sysfs_add(struct scst_dev_group *dg, struct scst_target_group *tg) +{ + int res; + + TRACE_ENTRY(); + res = kobject_add(&tg->kobj, dg->tg_kobj, "%s", tg->name); + if (res) + goto err; + res = sysfs_create_files(&tg->kobj, scst_tg_attrs); + if (res) + goto err; +out: + TRACE_EXIT_RES(res); + return res; +err: + scst_tg_sysfs_del(tg); + goto out; +} + +void scst_tg_sysfs_del(struct scst_target_group *tg) +{ + TRACE_ENTRY(); + sysfs_remove_files(&tg->kobj, scst_tg_attrs); + kobject_del(&tg->kobj); + TRACE_EXIT(); +} + +/** + ** SCST sysfs device_groups//target_groups directory implementation. + **/ + +static ssize_t scst_dg_tgs_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + static const char help[] = + "Usage: echo \"add group_name\" >mgmt\n" + " echo \"del group_name\" >mgmt\n"; + + return scnprintf(buf, PAGE_SIZE, help); +} + +static int scst_dg_tgs_mgmt_store_work_fn(struct scst_sysfs_work_item *w) +{ + struct scst_dev_group *dg; + char *cmd, *p, *pp, *dev_name; + int res; + + TRACE_ENTRY(); + + cmd = w->buf; + dg = scst_lookup_dg_by_kobj(w->kobj); + WARN_ON(!dg); + + p = strchr(cmd, '\n'); + if (p) + *p = '\0'; + + res = -EINVAL; + pp = cmd; + p = scst_get_next_lexem(&pp); + if (strcasecmp(p, "add") == 0) { + dev_name = scst_get_next_lexem(&pp); + if (!*dev_name) + goto out; + res = scst_tg_add(dg, dev_name); + } else if (strcasecmp(p, "del") == 0) { + dev_name = scst_get_next_lexem(&pp); + if (!*dev_name) + goto out; + res = scst_tg_remove_by_name(dg, dev_name); + } +out: + kobject_put(w->kobj); + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_dg_tgs_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *cmd; + struct scst_sysfs_work_item *work; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!cmd) + goto out; + + res = scst_alloc_sysfs_work(scst_dg_tgs_mgmt_store_work_fn, false, + &work); + if (res) + goto out; + + work->buf = cmd; + work->kobj = kobj; + kobject_get(kobj); + res = scst_sysfs_queue_wait_work(work); + +out: + if (res == 0) + res = count; + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_dg_tgs_mgmt = + __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_tgs_mgmt_show, + scst_dg_tgs_mgmt_store); + +static const struct attribute *scst_dg_tgs_attrs[] = { + &scst_dg_tgs_mgmt.attr, + NULL, +}; + +/** + ** SCST sysfs device_groups directory implementation. + **/ + +int scst_dg_sysfs_add(struct kobject *parent, struct scst_dev_group *dg) +{ + int res; + + dg->dev_kobj = NULL; + dg->tg_kobj = NULL; + res = kobject_add(&dg->kobj, parent, "%s", dg->name); + if (res) + goto err; + res = -EEXIST; + dg->dev_kobj = kobject_create_and_add("devices", &dg->kobj); + if (!dg->dev_kobj) + goto err; + res = sysfs_create_files(dg->dev_kobj, scst_dg_devs_attrs); + if (res) + goto err; + dg->tg_kobj = kobject_create_and_add("target_groups", &dg->kobj); + if (!dg->tg_kobj) + goto err; + res = sysfs_create_files(dg->tg_kobj, scst_dg_tgs_attrs); + if (res) + goto err; +out: + return res; +err: + scst_dg_sysfs_del(dg); + goto out; +} + +void scst_dg_sysfs_del(struct scst_dev_group *dg) +{ + if (dg->tg_kobj) { + sysfs_remove_files(dg->tg_kobj, scst_dg_tgs_attrs); + kobject_del(dg->tg_kobj); + kobject_put(dg->tg_kobj); + dg->tg_kobj = NULL; + } + if (dg->dev_kobj) { + sysfs_remove_files(dg->dev_kobj, scst_dg_devs_attrs); + kobject_del(dg->dev_kobj); + kobject_put(dg->dev_kobj); + dg->dev_kobj = NULL; + } + kobject_del(&dg->kobj); +} + +static ssize_t scst_device_groups_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + static const char help[] = + "Usage: echo \"add group_name\" >mgmt\n" + " echo \"del group_name\" >mgmt\n"; + + return scnprintf(buf, PAGE_SIZE, help); +} + +static ssize_t scst_device_groups_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int res; + char *p, *pp, *input, *group_name; + + TRACE_ENTRY(); + + input = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + pp = input; + p = strchr(input, '\n'); + if (p) + *p = '\0'; + + res = -EINVAL; + p = scst_get_next_lexem(&pp); + if (strcasecmp(p, "add") == 0) { + group_name = scst_get_next_lexem(&pp); + if (!*group_name) + goto out; + res = scst_dg_add(scst_device_groups_kobj, group_name); + } else if (strcasecmp(p, "del") == 0) { + group_name = scst_get_next_lexem(&pp); + if (!*group_name) + goto out; + res = scst_dg_remove(group_name); + } +out: + kfree(input); + if (res == 0) + res = count; + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_device_groups_mgmt = + __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_device_groups_mgmt_show, + scst_device_groups_mgmt_store); + +static const struct attribute *scst_device_groups_attrs[] = { + &scst_device_groups_mgmt.attr, + NULL, +}; + /** ** SCST sysfs root directory implementation **/ @@ -5456,10 +6149,27 @@ int __init scst_sysfs_init(void) if (scst_handlers_kobj == NULL) goto handlers_kobj_error; + scst_device_groups_kobj = kobject_create_and_add("device_groups", + &scst_sysfs_root_kobj); + if (scst_device_groups_kobj == NULL) + goto device_groups_kobj_error; + + if (sysfs_create_files(scst_device_groups_kobj, + scst_device_groups_attrs)) + goto device_groups_attrs_error; + out: TRACE_EXIT_RES(res); return res; +device_groups_attrs_error: + kobject_del(scst_device_groups_kobj); + kobject_put(scst_device_groups_kobj); + +device_groups_kobj_error: + kobject_del(scst_handlers_kobj); + kobject_put(scst_handlers_kobj); + handlers_kobj_error: scst_del_put_sgv_kobj(); @@ -5502,6 +6212,11 @@ void scst_sysfs_cleanup(void) kobject_del(scst_handlers_kobj); kobject_put(scst_handlers_kobj); + sysfs_remove_files(scst_device_groups_kobj, scst_device_groups_attrs); + + kobject_del(scst_device_groups_kobj); + kobject_put(scst_device_groups_kobj); + kobject_del(&scst_sysfs_root_kobj); kobject_put(&scst_sysfs_root_kobj); diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c new file mode 100644 index 000000000..a3915d759 --- /dev/null +++ b/scst/src/scst_tg.c @@ -0,0 +1,835 @@ +/* + * scst_tg.c + * + * SCSI target group related code. + * + * Copyright (C) 2011 Bart Van Assche . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#ifdef INSIDE_KERNEL_TREE +#include +#else +#include "scst.h" +#endif +#include "scst_priv.h" + +static struct list_head scst_dev_group_list; + +/* Look up a device by name. */ +static struct scst_device *__lookup_dev(const char *name) +{ + struct scst_device *dev; + + list_for_each_entry(dev, &scst_dev_list, dev_list_entry) + if (strcmp(dev->virt_name, name) == 0) + return dev; + + return NULL; +} + +/* Look up a target by name. */ +static struct scst_tgt *__lookup_tgt(const char *name) +{ + struct scst_tgt_template *t; + struct scst_tgt *tgt; + + list_for_each_entry(t, &scst_template_list, scst_template_list_entry) + list_for_each_entry(tgt, &t->tgt_list, tgt_list_entry) + if (strcmp(tgt->tgt_name, name) == 0) + return tgt; + + return NULL; +} + +/* Look up a target by name in the given device group. */ +static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg, + const char *tgt_name) +{ + struct scst_target_group *tg; + struct scst_tg_tgt *tg_tgt; + + BUG_ON(!dg); + BUG_ON(!tgt_name); + list_for_each_entry(tg, &dg->tg_list, entry) + list_for_each_entry(tg_tgt, &tg->tgt_list, entry) + if (strcmp(tg_tgt->name, tgt_name) == 0) + return tg_tgt; + + return NULL; +} + +/* 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) +{ + struct scst_target_group *tg; + + list_for_each_entry(tg, &dg->tg_list, entry) + if (strcmp(tg->name, name) == 0) + return tg; + + return NULL; +} + +/* Look up a device node by device pointer in the given device group. */ +static struct scst_dg_dev *__lookup_dg_dev_by_dev(struct scst_dev_group *dg, + struct scst_device *dev) +{ + struct scst_dg_dev *dgd; + + list_for_each_entry(dgd, &dg->dev_list, entry) + if (dgd->dev == dev) + return dgd; + + return NULL; +} + +/* Look up a device node by name in the given device group. */ +static struct scst_dg_dev *__lookup_dg_dev_by_name(struct scst_dev_group *dg, + const char *name) +{ + struct scst_dg_dev *dgd; + + list_for_each_entry(dgd, &dg->dev_list, entry) + if (strcmp(dgd->dev->virt_name, name) == 0) + return dgd; + + return NULL; +} + +/* Look up a device node by name in any device group. */ +static struct scst_dg_dev *__global_lookup_dg_dev_by_name(const char *name) +{ + struct scst_dev_group *dg; + struct scst_dg_dev *dgd; + + list_for_each_entry(dg, &scst_dev_group_list, entry) { + dgd = __lookup_dg_dev_by_name(dg, name); + if (dgd) + return dgd; + } + return NULL; +} + +/* Look up a device group by name. */ +static struct scst_dev_group *__lookup_dg_by_name(const char *name) +{ + struct scst_dev_group *dg; + + list_for_each_entry(dg, &scst_dev_group_list, entry) + if (strcmp(dg->name, name) == 0) + return dg; + + return NULL; +} + +/* Look up a device group by device pointer. */ +static struct scst_dev_group *__lookup_dg_by_dev(struct scst_device *dev) +{ + struct scst_dev_group *dg; + + list_for_each_entry(dg, &scst_dev_group_list, entry) + if (__lookup_dg_dev_by_dev(dg, dev)) + return dg; + + return NULL; +} + +/* + * Target group contents management. + */ + +static void scst_release_tg_tgt(struct kobject *kobj) +{ + struct scst_tg_tgt *tg_tgt; + + tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); + kfree(tg_tgt->name); + kfree(tg_tgt); +} + +static struct kobj_type scst_tg_tgt_ktype = { +#ifndef CONFIG_SCST_PROC + .sysfs_ops = &scst_sysfs_ops, +#endif + .release = scst_release_tg_tgt, +}; + +/** + * scst_tg_tgt_add() - Add a target to a target group. + */ +int scst_tg_tgt_add(struct scst_target_group *tg, const char *name) +{ + struct scst_tg_tgt *tg_tgt; + struct scst_tgt *tgt; + int res; + + TRACE_ENTRY(); + BUG_ON(!tg); + BUG_ON(!name); + res = -ENOMEM; + tg_tgt = kzalloc(sizeof *tg_tgt, GFP_KERNEL); + if (!tg_tgt) + goto out; + tg_tgt->tg = tg; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) + kobject_init(&tg_tgt->kobj, &scst_tg_tgt_ktype); +#else + kobject_init(&tg_tgt->kobj); + tg_tgt->kobj.ktype = &scst_tg_tgt_ktype; +#endif + tg_tgt->name = kstrdup(name, GFP_KERNEL); + if (!tg_tgt->name) + goto out_put; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out_put; + res = -EEXIST; + tgt = __lookup_tgt(name); + if (__lookup_dg_tgt(tg->dg, name)) + goto out_unlock; + tg_tgt->tgt = tgt; + res = scst_tg_tgt_sysfs_add(tg, tg_tgt); + if (res) + goto out_unlock; + list_add_tail(&tg_tgt->entry, &tg->tgt_list); + res = 0; + mutex_unlock(&scst_mutex); +out: + TRACE_EXIT_RES(res); + return res; +out_unlock: + mutex_unlock(&scst_mutex); +out_put: + kobject_put(&tg_tgt->kobj); + goto out; +} + +static void __scst_tg_tgt_remove(struct scst_target_group *tg, + struct scst_tg_tgt *tg_tgt) +{ + TRACE_ENTRY(); + list_del(&tg_tgt->entry); + scst_tg_tgt_sysfs_del(tg, tg_tgt); + kobject_put(&tg_tgt->kobj); + TRACE_EXIT(); +} + +/** + * scst_tg_tgt_remove_by_name() - Remove a target from a target group. + */ +int scst_tg_tgt_remove_by_name(struct scst_target_group *tg, const char *name) +{ + struct scst_tg_tgt *tg_tgt; + int res; + + TRACE_ENTRY(); + BUG_ON(!tg); + BUG_ON(!name); + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + res = -EINVAL; + tg_tgt = __lookup_dg_tgt(tg->dg, name); + if (!tg_tgt) + goto out_unlock; + __scst_tg_tgt_remove(tg, tg_tgt); + res = 0; +out_unlock: + mutex_unlock(&scst_mutex); +out: + TRACE_EXIT_RES(res); + return res; +} + +/* Caller must hold scst_mutex. Called from the target removal code. */ +void scst_tg_tgt_remove_by_tgt(struct scst_tgt *tgt) +{ + struct scst_dev_group *dg; + struct scst_target_group *tg; + struct scst_tg_tgt *t, *t2; + + BUG_ON(!tgt); + list_for_each_entry(dg, &scst_dev_group_list, entry) + list_for_each_entry(tg, &dg->tg_list, entry) + list_for_each_entry_safe(t, t2, &tg->tgt_list, entry) + if (t->tgt == tgt) + __scst_tg_tgt_remove(tg, t); +} + +/* + * Target group management. + */ + +static void scst_release_tg(struct kobject *kobj) +{ + struct scst_target_group *tg; + + tg = container_of(kobj, struct scst_target_group, kobj); + kfree(tg->name); + kfree(tg); +} + +static struct kobj_type scst_tg_ktype = { +#ifndef CONFIG_SCST_PROC + .sysfs_ops = &scst_sysfs_ops, +#endif + .release = scst_release_tg, +}; + +/** + * scst_tg_add() - Add a target group. + */ +int scst_tg_add(struct scst_dev_group *dg, const char *name) +{ + struct scst_target_group *tg; + int res; + + TRACE_ENTRY(); + res = -ENOMEM; + tg = kzalloc(sizeof *tg, GFP_KERNEL); + if (!tg) + goto out; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) + kobject_init(&tg->kobj, &scst_tg_ktype); +#else + kobject_init(&tg->kobj); + tg->kobj.ktype = &scst_tg_ktype; +#endif + tg->name = kstrdup(name, GFP_KERNEL); + if (!tg->name) + goto out_put; + tg->dg = dg; + tg->state = SCST_TG_STATE_OPTIMIZED; + INIT_LIST_HEAD(&tg->tgt_list); + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out_put; + res = -EEXIST; + if (__lookup_tg_by_name(dg, name)) + goto out_unlock; + res = scst_tg_sysfs_add(dg, tg); + if (res) + goto out_unlock; + list_add_tail(&tg->entry, &dg->tg_list); + mutex_unlock(&scst_mutex); + +out: + TRACE_EXIT_RES(res); + return res; + +out_unlock: + mutex_unlock(&scst_mutex); +out_put: + kobject_put(&tg->kobj); + goto out; +} + +static void __scst_tg_remove(struct scst_dev_group *dg, + struct scst_target_group *tg) +{ + struct scst_tg_tgt *tg_tgt; + + TRACE_ENTRY(); + BUG_ON(!dg); + BUG_ON(!tg); + while (!list_empty(&tg->tgt_list)) { + tg_tgt = list_first_entry(&tg->tgt_list, struct scst_tg_tgt, + entry); + __scst_tg_tgt_remove(tg, tg_tgt); + } + list_del(&tg->entry); + scst_tg_sysfs_del(tg); + kobject_put(&tg->kobj); + TRACE_EXIT(); +} + +/** + * scst_tg_remove_by_name() - Remove a target group. + */ +int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name) +{ + struct scst_target_group *tg; + int res; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + res = -EINVAL; + tg = __lookup_tg_by_name(dg, name); + if (!tg) + goto out_unlock; + __scst_tg_remove(dg, tg); + res = 0; +out_unlock: + mutex_unlock(&scst_mutex); +out: + return res; +} + +int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state) +{ + struct scst_dg_dev *dg_dev; + struct scst_device *dev; + struct scst_tgt_dev *tgt_dev; + int res; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + + tg->state = state; + + list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) { + dev = dg_dev->dev; + list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, + dev_tgt_dev_list_entry) { + TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed", + tgt_dev); + scst_gen_aen_or_ua(tgt_dev, + SCST_LOAD_SENSE(scst_sense_asym_access_state_changed)); + } + } + mutex_unlock(&scst_mutex); +out: + return res; +} + +/* + * Device group contents manipulation. + */ + +/** + * scst_dg_dev_add() - Add a device to a device group. + * + * It is verified whether 'name' refers to an existing device and whether that + * device has not yet been added to any other device group. + */ +int scst_dg_dev_add(struct scst_dev_group *dg, const char *name) +{ + struct scst_dg_dev *dgdev; + struct scst_device *dev; + int res; + + res = -ENOMEM; + dgdev = kzalloc(sizeof *dgdev, GFP_KERNEL); + if (!dgdev) + goto out; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out_free; + res = -EEXIST; + if (__global_lookup_dg_dev_by_name(name)) + goto out_unlock; + res = -EINVAL; + dev = __lookup_dev(name); + if (!dev) + goto out_unlock; + dgdev->dev = dev; + res = scst_dg_dev_sysfs_add(dg, dgdev); + if (res) + goto out_unlock; + list_add_tail(&dgdev->entry, &dg->dev_list); + mutex_unlock(&scst_mutex); + +out: + return res; + +out_unlock: + mutex_unlock(&scst_mutex); +out_free: + kfree(dgdev); + goto out; +} + +static void __scst_dg_dev_remove(struct scst_dev_group *dg, + struct scst_dg_dev *dgdev) +{ + list_del(&dgdev->entry); + scst_dg_dev_sysfs_del(dg, dgdev); + kfree(dgdev); +} + +/** + * scst_dg_dev_remove_by_name() - Remove a device from a device group. + */ +int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name) +{ + struct scst_dg_dev *dgdev; + int res; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + res = -EINVAL; + dgdev = __lookup_dg_dev_by_name(dg, name); + if (!dgdev) + goto out_unlock; + __scst_dg_dev_remove(dg, dgdev); + res = 0; +out_unlock: + mutex_unlock(&scst_mutex); +out: + return res; +} + +/* Caller must hold scst_mutex. Called from the device removal code. */ +int scst_dg_dev_remove_by_dev(struct scst_device *dev) +{ + struct scst_dev_group *dg; + struct scst_dg_dev *dgdev; + int res; + + res = -EINVAL; + dg = __lookup_dg_by_dev(dev); + if (!dg) + goto out; + dgdev = __lookup_dg_dev_by_dev(dg, dev); + BUG_ON(!dgdev); + __scst_dg_dev_remove(dg, dgdev); + res = 0; +out: + return res; +} + +/* + * Device group management. + */ + +static void scst_release_dg(struct kobject *kobj) +{ + struct scst_dev_group *dg; + + dg = container_of(kobj, struct scst_dev_group, kobj); + kfree(dg->name); + kfree(dg); +} + +static struct kobj_type scst_dg_ktype = { +#ifndef CONFIG_SCST_PROC + .sysfs_ops = &scst_sysfs_ops, +#endif + .release = scst_release_dg, +}; + +/** + * scst_dg_add() - Add a new device group object and make it visible in sysfs. + */ +int scst_dg_add(struct kobject *parent, const char *name) +{ + struct scst_dev_group *dg; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + dg = kzalloc(sizeof(*dg), GFP_KERNEL); + if (!dg) + goto out; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) + kobject_init(&dg->kobj, &scst_dg_ktype); +#else + kobject_init(&dg->kobj); + dg->kobj.ktype = &scst_dg_ktype; +#endif + dg->name = kstrdup(name, GFP_KERNEL); + if (!dg->name) + goto out_put; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out_put; + res = -EEXIST; + if (__lookup_dg_by_name(name)) + goto out_unlock; + res = -ENOMEM; + INIT_LIST_HEAD(&dg->dev_list); + INIT_LIST_HEAD(&dg->tg_list); + res = scst_dg_sysfs_add(parent, dg); + if (res) + goto out_unlock; + list_add_tail(&dg->entry, &scst_dev_group_list); + mutex_unlock(&scst_mutex); +out: + TRACE_EXIT_RES(res); + return res; + +out_unlock: + mutex_unlock(&scst_mutex); +out_put: + kobject_put(&dg->kobj); + goto out; +} + +static void __scst_dg_remove(struct scst_dev_group *dg) +{ + struct scst_dg_dev *dgdev; + struct scst_target_group *tg; + + list_del(&dg->entry); + scst_dg_sysfs_del(dg); + while (!list_empty(&dg->dev_list)) { + dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev, + entry); + __scst_dg_dev_remove(dg, dgdev); + } + while (!list_empty(&dg->tg_list)) { + tg = list_first_entry(&dg->tg_list, struct scst_target_group, + entry); + __scst_tg_remove(dg, tg); + } + kobject_put(&dg->kobj); +} + +int scst_dg_remove(const char *name) +{ + struct scst_dev_group *dg; + int res; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + res = -EINVAL; + dg = __lookup_dg_by_name(name); + if (!dg) + goto out_unlock; + __scst_dg_remove(dg); + res = 0; +out_unlock: + mutex_unlock(&scst_mutex); +out: + return res; +} + +/* + * Given a pointer to a device_groups//devices or + * device_groups//target_groups kobject, return the pointer to the + * corresponding device group. + * + * Note: The caller must hold a reference on the kobject to avoid that the + * object disappears before the caller stops using the device group pointer. + */ +struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj) +{ + int res; + struct scst_dev_group *dg; + + dg = NULL; + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out; + list_for_each_entry(dg, &scst_dev_group_list, entry) + if (dg->dev_kobj == kobj || dg->tg_kobj == kobj) + goto out_unlock; + dg = NULL; +out_unlock: + mutex_unlock(&scst_mutex); +out: + return dg; +} + + +/* + * Target group module management. + */ + +void scst_tg_init(void) +{ + INIT_LIST_HEAD(&scst_dev_group_list); +} + +void scst_tg_cleanup(void) +{ + struct scst_dev_group *tg; + + mutex_lock(&scst_mutex); + while (!list_empty(&scst_dev_group_list)) { + tg = list_first_entry(&scst_dev_group_list, + struct scst_dev_group, entry); + __scst_dg_remove(tg); + } + mutex_unlock(&scst_mutex); +} + +/* + * Functions for target group related SCSI command support. + */ + +/** + * scst_lookup_tg_id() - Look up a target port group identifier. + * @dev: SCST device. + * @tgt: SCST target. + * + * Returns a non-zero number if the lookup was successful and zero if not. + */ +uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt) +{ + struct scst_dev_group *dg; + struct scst_target_group *tg; + struct scst_tg_tgt *tg_tgt; + uint16_t tg_id = 0; + + TRACE_ENTRY(); + mutex_lock(&scst_mutex); + dg = __lookup_dg_by_dev(dev); + if (!dg) + goto out_unlock; + tg_tgt = __lookup_dg_tgt(dg, tgt->tgt_name); + if (!tg_tgt) + goto out_unlock; + tg = tg_tgt->tg; + BUG_ON(!tg); + tg_id = tg->group_id; +out_unlock: + mutex_unlock(&scst_mutex); + + TRACE_EXIT_RES(tg_id); + return tg_id; +} +EXPORT_SYMBOL_GPL(scst_lookup_tg_id); + +/** + * scst_impl_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) +{ + struct scst_dev_group *dg; + bool res; + + mutex_lock(&scst_mutex); + dg = __lookup_dg_by_dev(dev); + res = dg != NULL; + mutex_unlock(&scst_mutex); + + return res; +} +EXPORT_SYMBOL_GPL(scst_impl_alua_configured); + +/** + * scst_tg_get_group_info() - Build REPORT TARGET GROUPS response. + * @buf: Pointer to a pointer to which the result buffer pointer will be set. + * @length: Response length, including the "RETURN DATA LENGTH" field. + * @dev: Pointer to the SCST device for which to obtain group information. + * @data_format: Three-bit response data format specification. + */ +int scst_tg_get_group_info(void **buf, uint32_t *length, + struct scst_device *dev, uint8_t data_format) +{ + struct scst_dev_group *dg; + struct scst_target_group *tg; + struct scst_tg_tgt *tgtgt; + struct scst_tgt *tgt; + uint8_t *p; + uint32_t ret_data_len; + uint16_t rel_tgt_id; + int res; + + TRACE_ENTRY(); + + BUG_ON(!buf); + BUG_ON(!length); + + ret_data_len = 0; + + res = -EINVAL; + switch (data_format) { + case 0: + break; + case 1: + /* Extended header */ + ret_data_len += 4; + break; + default: + goto out; + } + + *length = 4; + + mutex_lock(&scst_mutex); + + dg = __lookup_dg_by_dev(dev); + if (dg) { + list_for_each_entry(tg, &dg->tg_list, entry) { + /* Target port group descriptor header. */ + ret_data_len += 8; + list_for_each_entry(tgtgt, &tg->tgt_list, entry) { + /* Target port descriptor. */ + ret_data_len += 4; + } + } + } + + *length += ret_data_len; + + res = -ENOMEM; + *buf = kzalloc(*length, GFP_KERNEL); + if (!*buf) + goto out_unlock; + + p = *buf; + /* Return data length. */ + put_unaligned(cpu_to_be32(ret_data_len), (__be32 *)p); + p += 4; + if (data_format == 1) { + /* Extended header */ + *p++ = 0x10; /* format = 1 */ + *p++ = 0x00; /* implicit transition time = 0 */ + p += 2; /* reserved */ + } + + if (!dg) + goto done; + + list_for_each_entry(tg, &dg->tg_list, entry) { + /* Target port group descriptor header. */ + *p++ = (tg->preferred ? SCST_TG_PREFERRED : 0) | tg->state; + *p++ = SCST_TG_SUP_OPTIMIZED + | SCST_TG_SUP_NONOPTIMIZED + | SCST_TG_SUP_STANDBY + | SCST_TG_SUP_UNAVAILABLE; + put_unaligned(cpu_to_be16(tg->group_id), (__be16 *)p); + p += 2; + p++; /* reserved */ + *p++ = 2; /* status code: implicit transition */ + p++; /* vendor specific */ + list_for_each_entry(tgtgt, &tg->tgt_list, entry) + (*p)++; /* target port count */ + p++; + list_for_each_entry(tgtgt, &tg->tgt_list, entry) { + tgt = tgtgt->tgt; + rel_tgt_id = tgt ? tgt->rel_tgt_id : tgtgt->rel_tgt_id; + /* Target port descriptor. */ + p += 2; /* reserved */ + /* Relative target port identifier. */ + put_unaligned(cpu_to_be16(rel_tgt_id), + (__be16 *)p); + p += 2; + } + } + +done: + WARN_ON(p - (uint8_t *)*buf != *length); + + res = 0; + +out_unlock: + mutex_unlock(&scst_mutex); +out: + TRACE_EXIT_RES(res); + return res; +} +EXPORT_SYMBOL_GPL(scst_tg_get_group_info);