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 3d10dec7d..5cfc4bbc9 100644 --- a/scst/README +++ b/scst/README @@ -1101,6 +1101,265 @@ 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, 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 + 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. You will have to modify at least the following in +/etc/multipath.conf: +* path_checker scsi_dh_alua +* prio_callout "/sbin/mpath_prio_alua /dev/%n" + +If your distribution does not provide a /sbin/mpath_prio_alua script, you can +use the following implementation: +$ cat /sbin/mpath_prio_alua +#!/bin/bash +# Given a SCSI device node, query the target port group asymmetric access +# state and report it in numeric form. +tpg_id="$(sg_vpd --page=di "$1" | sed -n 's/.*Target port group: //p')" +aas="$(sg_rtpg "$1" \ +| grep -A1 "target port group id : $tpg_id" \ +| tail -n 1 \ +| sed 's/.*target port group asymmetric access state : //')" +echo $((aas)) + +More 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 f66f006a3..1a871199e 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. * @@ -2589,6 +2590,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 */ @@ -2869,6 +2929,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. * @@ -4113,6 +4178,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 162af19fa..d7eeb5d6c 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/kernel/in-tree/Makefile.scst-2.6.38 b/scst/kernel/in-tree/Makefile.scst-2.6.38 index df4312ba2..53af5f388 100644 --- a/scst/kernel/in-tree/Makefile.scst-2.6.38 +++ b/scst/kernel/in-tree/Makefile.scst-2.6.38 @@ -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 50498dad7..6024e2f13 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 f9543f381..4c33412e7 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)", @@ -4884,6 +4885,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 **/ @@ -5461,10 +6154,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(); @@ -5507,6 +6217,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);