From a8ecf61074de952d18e9c0b93985b76b34552a0e Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 29 Oct 2015 15:57:32 +0000 Subject: [PATCH 01/82] isert: Rename a function Signed-off-by: Ariel Nahum git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6566 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser.h | 2 +- iscsi-scst/kernel/isert-scst/iser_rdma.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser.h b/iscsi-scst/kernel/isert-scst/iser.h index 3f8113908..90fa056a2 100644 --- a/iscsi-scst/kernel/isert-scst/iser.h +++ b/iscsi-scst/kernel/isert-scst/iser.h @@ -284,7 +284,7 @@ void isert_conn_free(struct isert_connection *isert_conn); void isert_conn_disconnect(struct isert_connection *isert_conn); void isert_post_drain(struct isert_connection *isert_conn); -static inline struct isert_connection *isert_conn_alloc(void) +static inline struct isert_connection *isert_conn_zalloc(void) { return kmem_cache_zalloc(isert_conn_cache, GFP_KERNEL); } diff --git a/iscsi-scst/kernel/isert-scst/iser_rdma.c b/iscsi-scst/kernel/isert-scst/iser_rdma.c index b645c4cf9..1b3e849a9 100644 --- a/iscsi-scst/kernel/isert-scst/iser_rdma.c +++ b/iscsi-scst/kernel/isert-scst/iser_rdma.c @@ -1142,7 +1142,7 @@ static struct isert_connection *isert_conn_create(struct rdma_cm_id *cm_id, TRACE_ENTRY(); - isert_conn = isert_conn_alloc(); + isert_conn = isert_conn_zalloc(); if (unlikely(!isert_conn)) { pr_err("Unable to allocate iser conn, cm_id:%p\n", cm_id); err = -ENOMEM; From 711e3c54a58e745236818834a494fe9f33a51615 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 2 Nov 2015 22:44:48 +0000 Subject: [PATCH 02/82] ib_srpt: Remove one_target_per_port parameter completely git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6567 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/README | 14 +++----------- srpt/src/ib_srpt.c | 10 ---------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/srpt/README b/srpt/README index 1380cf354..1f0c91594 100644 --- a/srpt/README +++ b/srpt/README @@ -211,17 +211,7 @@ fe80:0000:0000:0000:0002:c903:0005:f350 Session names ------------- -The name assigned by the ib_srpt target driver to a session depends on the -mode in which it is operating. If one_target_per_port=y then the source port -GID is used as the session name. If one_target_per_port=n then the 128-bit SRP -initiator port identifier is used as the session name. This identifier is sent -by the SRP initiator to the SRP target via the SRP_LOGIN_REQ information unit. -The Linux SRP initiator (ib_srp) generates the initiator port identifier as -follows: -- The first eight bytes are the identifier extension ('initiator_ext' parameter - specified in the login string echoed into the sysfs file 'add_target'). -- The last eight bytes are the GUID of the initiator HCA port used to - communicate with the target. +The ib_srpt target driver uses the source port GID as session name. An example: @@ -242,7 +232,9 @@ fe80:0000:0000:0000:0002:c903:0003:cca8 $ (cd /sys/kernel/scst_tgt/targets/ib_srpt/[0-9a-f]* && ls -d sessions/*) sessions/fe80:0000:0000:0000:0002:c903:0003:cca7 +sessions/fe80:0000:0000:0000:0002:c903:0003:cca8 sessions/fe80:0000:0000:0000:0002:c903:0005:f34b +sessions/fe80:0000:0000:0000:0002:c903:0005:f34c LUN masking diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 44f7edfae..6f34cbe13 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -169,16 +169,6 @@ module_param(use_node_guid_in_target_name, bool, 0444); MODULE_PARM_DESC(use_node_guid_in_target_name, "Use HCA node GUID as SCST target name."); -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) \ - || defined(RHEL_MAJOR) && RHEL_MAJOR -0 <= 5 -static int one_target_per_port = true; -#else -static bool one_target_per_port = true; -#endif -module_param(one_target_per_port, bool, 0444); -MODULE_PARM_DESC(one_target_per_port, - "One SCST target per HCA port instead of one per HCA."); - static struct rdma_cm_id *rdma_cm_id; static int srpt_get_u64_x(char *buffer, struct kernel_param *kp) From 75499d9cdb14c77188bea3fc653843cd02f2b87c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 3 Nov 2015 05:18:45 +0000 Subject: [PATCH 03/82] nightly build: Add kernel version 4.3 git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6568 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- nightly/conf/nightly.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nightly/conf/nightly.conf b/nightly/conf/nightly.conf index bdb6dc7a6..bdee4697d 100644 --- a/nightly/conf/nightly.conf +++ b/nightly/conf/nightly.conf @@ -3,8 +3,9 @@ ABT_DETAILS="x86_64" ABT_JOBS=5 ABT_KERNELS=" \ -4.2.5 \ -4.1.12-nc \ +4.3 \ +4.2.5-nc \ +4.1.12-nc \ 4.0.9-nc \ 3.19.7-nc \ 3.18.19-nc \ From fc27595d07ef3b370d9858de1f7abf20c1fc35a6 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 3 Nov 2015 05:24:37 +0000 Subject: [PATCH 04/82] nightly build: Add support files for kernel v4.3 git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6569 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x00-target/Makefile_in-tree-4.3 | 5 +++++ .../kernel/in-tree/Kconfig.drivers.Linux-4.3.patch | 13 +++++++++++++ scst/kernel/in-tree/Makefile.dev_handlers-4.3 | 14 ++++++++++++++ .../in-tree/Makefile.drivers.Linux-4.3.patch | 12 ++++++++++++ scst/kernel/in-tree/Makefile.scst-4.3 | 13 +++++++++++++ scst_local/in-tree/Makefile-4.3 | 2 ++ 6 files changed, 59 insertions(+) create mode 100644 qla2x00t/qla2x00-target/Makefile_in-tree-4.3 create mode 100644 scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch create mode 100644 scst/kernel/in-tree/Makefile.dev_handlers-4.3 create mode 100644 scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch create mode 100644 scst/kernel/in-tree/Makefile.scst-4.3 create mode 100644 scst_local/in-tree/Makefile-4.3 diff --git a/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 b/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 new file mode 100644 index 000000000..9657aee84 --- /dev/null +++ b/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 @@ -0,0 +1,5 @@ +ccflags-y += -Idrivers/scsi/qla2xxx + +qla2x00tgt-y := qla2x00t.o + +obj-$(CONFIG_SCST_QLA_TGT_ADDON) += qla2x00tgt.o diff --git a/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch b/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch new file mode 100644 index 000000000..39d30a55e --- /dev/null +++ b/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/Kconfig b/drivers/Kconfig +index c70d6e4..0a4ed1b 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -26,6 +26,8 @@ source "drivers/ide/Kconfig" + + source "drivers/scsi/Kconfig" + ++source "drivers/scst/Kconfig" ++ + source "drivers/ata/Kconfig" + + source "drivers/md/Kconfig" diff --git a/scst/kernel/in-tree/Makefile.dev_handlers-4.3 b/scst/kernel/in-tree/Makefile.dev_handlers-4.3 new file mode 100644 index 000000000..f933b36f7 --- /dev/null +++ b/scst/kernel/in-tree/Makefile.dev_handlers-4.3 @@ -0,0 +1,14 @@ +ccflags-y += -Wno-unused-parameter + +obj-m := scst_cdrom.o scst_changer.o scst_disk.o scst_modisk.o scst_tape.o \ + scst_vdisk.o scst_raid.o scst_processor.o scst_user.o + +obj-$(CONFIG_SCST_DISK) += scst_disk.o +obj-$(CONFIG_SCST_TAPE) += scst_tape.o +obj-$(CONFIG_SCST_CDROM) += scst_cdrom.o +obj-$(CONFIG_SCST_MODISK) += scst_modisk.o +obj-$(CONFIG_SCST_CHANGER) += scst_changer.o +obj-$(CONFIG_SCST_RAID) += scst_raid.o +obj-$(CONFIG_SCST_PROCESSOR) += scst_processor.o +obj-$(CONFIG_SCST_VDISK) += scst_vdisk.o +obj-$(CONFIG_SCST_USER) += scst_user.o diff --git a/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch b/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch new file mode 100644 index 000000000..6bbacd12f --- /dev/null +++ b/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/Makefile b/drivers/Makefile +index 527a6da..db2c24f 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -137,6 +137,7 @@ obj-$(CONFIG_SSB) += ssb/ + obj-$(CONFIG_BCMA) += bcma/ + obj-$(CONFIG_VHOST_RING) += vhost/ + obj-$(CONFIG_VLYNQ) += vlynq/ ++obj-$(CONFIG_SCST) += scst/ + obj-$(CONFIG_STAGING) += staging/ + obj-y += platform/ + #common clk code diff --git a/scst/kernel/in-tree/Makefile.scst-4.3 b/scst/kernel/in-tree/Makefile.scst-4.3 new file mode 100644 index 000000000..53af5f388 --- /dev/null +++ b/scst/kernel/in-tree/Makefile.scst-4.3 @@ -0,0 +1,13 @@ +ccflags-y += -Wno-unused-parameter + +scst-y += scst_main.o +scst-y += scst_pres.o +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/ \ + srpt/ scst_local/ diff --git a/scst_local/in-tree/Makefile-4.3 b/scst_local/in-tree/Makefile-4.3 new file mode 100644 index 000000000..8cbbbff63 --- /dev/null +++ b/scst_local/in-tree/Makefile-4.3 @@ -0,0 +1,2 @@ +obj-$(CONFIG_SCST_LOCAL) += scst_local.o + From 77baa86e0e04c871e48cc384ab11a6b56566cd4b Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 3 Nov 2015 05:25:32 +0000 Subject: [PATCH 05/82] qla2x00t: Source code comment spelling fix git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6570 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x_tgt_def.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qla2x00t/qla2x_tgt_def.h b/qla2x00t/qla2x_tgt_def.h index 28bf9ee47..0957ecb31 100644 --- a/qla2x00t/qla2x_tgt_def.h +++ b/qla2x00t/qla2x_tgt_def.h @@ -633,7 +633,7 @@ typedef struct { * ISP queue - ABTS received/response entries structure definition for 24xx. */ #define ABTS_RECV_24XX 0x54 /* ABTS received (for 24xx) */ -#define ABTS_RESP_24XX 0x55 /* ABTS responce (for 24xx) */ +#define ABTS_RESP_24XX 0x55 /* ABTS response (for 24xx) */ typedef struct { uint8_t entry_type; /* Entry type. */ From 87c1d45bf807830c128b65f71e8f48b40ce82757 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 3 Nov 2015 16:52:00 +0000 Subject: [PATCH 06/82] scst_targ: Report unsupported SELECT REPORT values with 0x prefix git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6571 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_targ.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index dc66d98d7..d0216b238 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -1887,7 +1887,7 @@ static int scst_report_luns_local(struct scst_cmd *cmd) cmd->driver_status = 0; if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) { - TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %x in " + TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %#x in " "REPORT LUNS command", cmd->cdb[2]); scst_set_invalid_field_in_cdb(cmd, 2, 0); goto out_compl; From 5058156b261599beb141dab42b6deb4bc17ae87c Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Thu, 5 Nov 2015 04:03:20 +0000 Subject: [PATCH 07/82] Cleanups git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6572 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 1 - scst/src/scst_lib.c | 2 +- scst/src/scst_priv.h | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index ade936cab..1287a3769 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -8402,7 +8402,6 @@ static ssize_t vdev_size_store(struct kobject *kobj, char *new_size; int res = -ENOMEM; - new_size = kasprintf(GFP_KERNEL, "%d %.*s", size_shift, (int)count, buf); if (!new_size) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index c566f9b5d..9f7118c73 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -11984,7 +11984,7 @@ void scst_unblock_dev(struct scst_device *dev) { TRACE_ENTRY(); - TRACE_BLOCK("Device UNBLOCK(new %d), dev %s", + TRACE_BLOCK("Device UNBLOCK (new %d), dev %s", dev->block_count-1, dev->virt_name); if (--dev->block_count == 0) { diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 198a5f4af..33056bd41 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -681,8 +681,8 @@ void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev, * we don't care about all commands that already on the device. */ -extern void scst_block_dev(struct scst_device *dev); -extern void scst_unblock_dev(struct scst_device *dev); +void scst_block_dev(struct scst_device *dev); +void scst_unblock_dev(struct scst_device *dev); bool __scst_check_blocked_dev(struct scst_cmd *cmd); From 9d61dc4b03362ce04e48925164fb9d3ecf0f2929 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:23:46 +0000 Subject: [PATCH 08/82] Cluster SCSI state sync support From Bart Van Assche Prepared with help from Pralay Dakua git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6573 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- doc/Makefile | 2 + ...LM as a Distributed In-Memory Database.pdf | Bin 0 -> 148782 bytes scst/README.dlm | 166 ++ scst/include/scst.h | 77 + scst/include/scst_const.h | 4 + scst/src/Makefile | 7 + scst/src/dev_handlers/scst_vdisk.c | 144 +- scst/src/scst_dlm.c | 1458 +++++++++++++++++ scst/src/scst_dlm.h | 154 ++ scst/src/scst_lib.c | 13 +- scst/src/scst_no_dlm.c | 118 ++ scst/src/scst_pres.c | 50 +- scst/src/scst_pres.h | 25 + scst/src/scst_priv.h | 45 +- scst/src/scst_sysfs.c | 8 +- scst/src/scst_targ.c | 29 +- 16 files changed, 2261 insertions(+), 39 deletions(-) create mode 100644 doc/Using the DLM as a Distributed In-Memory Database.pdf create mode 100644 scst/README.dlm create mode 100644 scst/src/scst_dlm.c create mode 100644 scst/src/scst_dlm.h create mode 100644 scst/src/scst_no_dlm.c diff --git a/doc/Makefile b/doc/Makefile index d2b95bd76..c1b59b8cc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -36,7 +36,9 @@ rtf: $(RTFS) $(COMMAND)rtf $(<) clean: + mv "Using the DLM as a Distributed In-Memory Database.pdf" "Using the DLM as a Distributed In-Memory Database.pdf_" rm -f *.txt *.html *.tex *.dvi *.ps *.pdf *.info *.lyx *.rtf + mv "Using the DLM as a Distributed In-Memory Database.pdf_" "Using the DLM as a Distributed In-Memory Database.pdf" extraclean: clean rm -f *.orig *.rej diff --git a/doc/Using the DLM as a Distributed In-Memory Database.pdf b/doc/Using the DLM as a Distributed In-Memory Database.pdf new file mode 100644 index 0000000000000000000000000000000000000000..19be45865a01b8e36c3df7b127dc4f824417b98a GIT binary patch literal 148782 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 fsCFb0GJa@79^QP@Fm literal 0 HcmV?d00001 diff --git a/scst/README.dlm b/scst/README.dlm new file mode 100644 index 000000000..435e6d8bb --- /dev/null +++ b/scst/README.dlm @@ -0,0 +1,166 @@ +Synchronization of the Persistent Reservation Information via the DLM +===================================================================== + +Introduction +------------ + +In an H.A. setup where multiple servers share data it is required that +the persistent reservation state is kept consistent across the cluster. +One possible approach is to use the DLM to keep the PR state synchronized +across nodes. Since the DLM can associate data with each DLM lock object, +DLM lock objects can be used to store PR data. The data that is associated +with a DLM lock object is called the Lock Value Block or LVB. The code in +scst_dlm.c uses the DLM to keep PR data synchronized across all nodes in +a cluster. + +Software Components +------------------- + +The following software components are needed by the code in scst_dlm.c: +* The DLM kernel driver (dlm.ko). This driver is only built if CONFIG_DLM + has been set. +* The DLM control daemon (dlm_controld.pcmk). This daemon passes cluster + node IDs and IP addresses to the DLM kernel driver via the configfs + interface of the DLM kernel driver. +* Corosync to manage cluster membership of the cluster nodes and to assign + a node ID to each cluster node. +* A facility to start the DLM control daemon, e.g. Pacemaker. + +On most Linux distributions the software packages that contain this software +have the names kernel, dlm, corosync and pacemaker. + +DLM Configuration +----------------- + +The DLM kernel module supports the TCP and SCTP communication protocols. An +advantage of SCTP for H.A. purposes is that it supports multihoming. One of +these protocols can be selected via the -r option of dlm_controld. +That option can be set via the "args" argument of the Pacemaker dlm_controld +resource. For more information, see also: +* The dlm_controld(8) man page. +* In the "Pacemaker 1.1, Clusters from Scratch" guide, the section "Configure + the Cluster for the DLM". +* The dlm_controld resource agent: /usr/lib/ocf/resource.d/pacemaker/controld + +Here is an example of how to set up a cluster with two nodes and how to +configure and start the DLM control daemon: + 1. If a network switch is present between the two nodes, enable IPv4 multicast + on that switch. + 2. Copy /etc/corosync/corosync.conf.example into /etc/corosync/corosync.conf + and edit that file. + 3. If a file /etc/default/corosync exists, enable Corosync in that file. + 4. Start Corosync: + systemctl start corosync || /etc/init.d/corosync start + 5. Check that all configured Corosync rings have two members: + corosync-cfgtool -s && { corosync-cmapctl | grep members; } + 6. Start pcsd: + systemctl start pcsd || /etc/init.d/pcsd start + 7. Set up cluster authentication: + pcs cluster auth centos7-vm centos7b-vm + 8. Start Pacemaker: + systemctl start pacemaker || /etc/init.d/pacemaker start + 9. If the cluster has only two nodes, disable the Pacemaker quorum policy and + disable STONITH: + crm_attribute -t crm_config -n no-quorum-policy -v ignore + crm_attribute -t crm_config -n stonith-enabled -v false +10. Check the cluster status: + pcs status +11. Create a Pacemaker resource for dlm_controld: + pcs resource delete dlm + pcs resource create dlm ocf:pacemaker:controld \ + args="-q0 -f0" allow_stonith_disabled=true \ + op monitor timeout=60 \ + --clone interleave=true +12. Check the Pacemaker status: + pcs status + +Startup and Shutdown +-------------------- + +The startup sequence is as follows: +* Load and configure SCST with cluster_mode = 0 and with all target ports + disabled. +* Enable cluster mode for all SCST devices that can be accessed through more + than one cluster node: + for x in /sys/kernel/scst_tgt/handlers/*/*/; do + echo 1 >$x/cluster_mode & + done + wait +* Start Corosync and Pacemaker. +* Wait until Pacemaker has reached the idle state: + pacemaker_dc_status() { + local dc + + dc="$(crmadmin -D 2>/dev/null | sed 's/Designated Controller is: //')" + [ -n "$dc" ] && + crmadmin -S "$dc" 2>/dev/null | + sed 's/^Status of crmd@[^[:blank:]]*:[[:blank:]]\([^[:blank:]]*\).*/\1/' + } + for ((i=0;i<300;i++)); do + [ "$(pacemaker_dc_status)" = "S_IDLE" ] && break + sleep 1 + done +* Enable SCST target ports. +* If no DLM resource has been configured in Pacemaker, start dlm_controld.pcmk + explicitly. + +The proper shutdown order is as follows: +* Tell SCST to stop accepting SCSI commands and wait until all initiators have + logged out: + for x in $(find /sys/kernel/scst_tgt/targets/ -name enabled); do + echo 0 > $x & + done + wait + while ls -Ad /sys/kernel/scst_tgt/targets/*/*/sessions/* >/dev/null 2>&1; do + sleep 1 + done +* Tell SCST to release the DLM lockspaces: + while grep -q '^1$' /sys/kernel/scst_tgt/devices/*/cluster_mode 2>/dev/null + do + for x in /sys/kernel/scst_tgt/devices/*/cluster_mode; do + { [ -e "$x" ] && echo 0 > "$x"; } & + done + wait + sleep 1 + done +* Stop Pacemaker and Corosync +* Unload the SCST kernel modules +* Unload the DLM kernel driver + +Lockspace names +--------------- + +The names of the DLM lockspaces used by SCST follow the following pattern: +scst- where t10_dev_id is the T10 device ID of the SCST device +associated with this lockspace. + +Notes +----- + +Since the lockspace name depends on the t10_dev_id it is not allowed to +change the t10_dev_id if cluster mode has been enabled. + +Testing +------- + +Two examples of test suites for the cluster PR support code are: +* The SCSI conformance tests in the libiscsi project. +* The Windows Cluster Validation Tests + (https://technet.microsoft.com/en-us/library/Cc726064.aspx). + +To do +----- + +Ensure that PREEMPT AND ABORT affects all cluster nodes instead of only the +cluster node that received this command. + +See also +-------- + +* Bart Van Assche, Using the DLM as a distributed in-memory database, Linux + Plumbers North America, Seattle, August 20, 2015 + (https://linuxplumbersconf.org/2015/ocw//system/presentations/2691/original/Using%20the%20DLM%20as%20a%20Distributed%20In-Memory%20Database.pdf). +* Andrew Beekhof, Pacemaker Configuration Explained, 2015 + (http://clusterlabs.org/doc/en-US/Pacemaker/1.1/html/Pacemaker_Explained/). +* Andrew Beekhof, Clusters from Scratch, 2015 + (http://clusterlabs.org/doc/en-US/Pacemaker/1.1-pcs/html/Clusters_from_Scratch/index.html). diff --git a/scst/include/scst.h b/scst/include/scst.h index 8d1940bd2..feb76eff7 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -38,6 +38,7 @@ #include #include #include +#include #ifdef CONFIG_SCST_MEASURE_LATENCY #include #endif @@ -1991,6 +1992,18 @@ struct scst_cmd_threads { int scst_set_thr_cpu_mask(struct scst_cmd_threads *cmd_threads, cpumask_t *cpu_mask); +struct scst_pr_dlm_data; + +/* + * DLM lock status block with completion for notifying completion of + * synchronous DLM lock operations. + */ +struct scst_lksb { + struct dlm_lksb lksb; + struct completion compl; + struct scst_pr_dlm_data *pr_dlm; +}; + /* * Used to execute cmd's in order of arrival, honoring SCSI task attributes */ @@ -2545,6 +2558,53 @@ struct scst_dev_registrant { /* 2 auxiliary fields used to rollback changes for errors, etc. */ struct list_head aux_list_entry; __be64 rollback_key; + + /* For registrant information managed via the DLM. */ + int dlm_idx; + struct scst_lksb lksb; + char lvb[PR_DLM_LVB_LEN]; +}; + +/** + * struct scst_cl_ops - Encapsulation of behavior that depends on cluster mode + * @pr_init: Initialize resources needed by one of the functions below. + * @pr_cleanup: Free resources allocated by one of the functions below. + * @pr_is_set: Whether or not one of the registrants holds a reservation. + * @pr_init_reg: Cluster-specific registrant initialization. + * @pr_rm_reg: Cluster-specific registrant cleanup. + * @pr_write_lock: Lock the PR data structures for write access. + * @pr_write_unlock: Unlock the PR data structures for write access. + * @reserved: Whether an initiator holds an SPC-2 reservation. + * @res_lock: Protect the SPC-2 reservation state against concurrent + * modifications. + * @res_unlock: Counterpart of @res_lock. + * @is_rsv_holder: Whether session @sess holds an SPC-2 reservation on @dev. + * @is_not_rsv_holder: Whether another session than @sess holds an SPC-2 + * reservation on @dev. + * @reserve: Apply an SPC-2 reservation for session @sess on @dev if + * @sess != NULL or clear that reservation if @ses == NULL. + */ +struct scst_cl_ops { + int (*pr_init)(struct scst_device *dev, const char *cl_dev_id); + void (*pr_cleanup)(struct scst_device *dev); + bool (*pr_is_set)(struct scst_device *dev); + void (*pr_init_reg)(struct scst_device *dev, + struct scst_dev_registrant *reg); + void (*pr_rm_reg)(struct scst_device *dev, + struct scst_dev_registrant *reg); + void (*pr_write_lock)(struct scst_device *dev, + struct scst_lksb *pr_lksb); + void (*pr_write_unlock)(struct scst_device *dev, + struct scst_lksb *pr_lksb); + + bool (*reserved)(struct scst_device *dev); + void (*res_lock)(struct scst_device *dev, struct scst_lksb *pr_lksb); + void (*res_unlock)(struct scst_device *dev, struct scst_lksb *pr_lksb); + bool (*is_rsv_holder)(struct scst_device *dev, + struct scst_session *sess); + bool (*is_not_rsv_holder)(struct scst_device *dev, + struct scst_session *sess); + void (*reserve)(struct scst_device *dev, struct scst_session *sess); }; /* @@ -2725,6 +2785,9 @@ struct scst_device { /* Set if reserved via the SPC-2 SCSI RESERVE command. */ struct scst_session *reserved_by; + /* Operations that depend on whether or not cluster mode is enabled */ + const struct scst_cl_ops *cl_ops; + /********************************************************************** * Persistent reservation fields. Protected as follows: * - Reading PR data must be protected via scst_pr_read_lock() / @@ -2745,12 +2808,21 @@ struct scst_device { /* Whether or not pr_file_name has been modified via sysfs. */ unsigned int pr_file_name_is_set:1; + /* + * Whether or not the PR state must be synchronized with other cluster + * nodes. + */ + unsigned int cluster_mode:1; + /* Persistent reservation type */ uint8_t pr_type; /* Persistent reservation scope */ uint8_t pr_scope; + /* Data structures for managing PR data via the DLM */ + struct scst_pr_dlm_data *pr_dlm; + /* Mutex to protect PR operations */ struct mutex dev_pr_mutex; @@ -5405,4 +5477,9 @@ void scst_path_put(struct nameidata *nd); #endif int scst_remove_file(const char *name); +int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode, + const char *cl_dev_id); +int scst_pr_init_dev(struct scst_device *dev); +void scst_pr_clear_dev(struct scst_device *dev); + #endif /* __SCST_H */ diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index cdb4a4a6a..39a829b58 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -735,4 +735,8 @@ enum { E_TGT_PRIV_NOT_YET_SET = EBUSY }; +/* Size of the lock value block in the DLM PR lockspace */ +#define PR_DLM_LVB_LEN 256 + + #endif /* __SCST_CONST_H */ diff --git a/scst/src/Makefile b/scst/src/Makefile index 1110bbf93..351bb956e 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -48,6 +48,13 @@ scst-y += scst_sysfs.o scst-y += scst_mem.o scst-y += scst_debug.o scst-y += scst_pres.o +scst-y += scst_no_dlm.o +ifdef CONFIG_DLM +scst-y += scst_dlm.o +endif +ifdef CONFIG_DLM_MODULE +scst-y += scst_dlm.o +endif scst-y += scst_tg.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 1287a3769..6a5181061 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -396,6 +396,10 @@ static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdev_sysfs_filename_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); +static ssize_t vdev_sysfs_cluster_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdev_sysfs_cluster_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); static ssize_t vdisk_sysfs_sync_store(struct kobject *kobj, @@ -483,6 +487,9 @@ static struct kobj_attribute vdisk_removable_attr = __ATTR(removable, S_IRUGO, vdisk_sysfs_removable_show, NULL); static struct kobj_attribute vdisk_filename_attr = __ATTR(filename, S_IRUGO, vdev_sysfs_filename_show, NULL); +static struct kobj_attribute vdisk_cluster_mode_attr = + __ATTR(cluster_mode, S_IWUSR|S_IRUGO, vdev_sysfs_cluster_mode_show, + vdev_sysfs_cluster_mode_store); static struct kobj_attribute vdisk_resync_size_attr = __ATTR(resync_size, S_IWUSR, NULL, vdisk_sysfs_resync_size_store); static struct kobj_attribute vdisk_sync_attr = @@ -540,6 +547,7 @@ static const struct attribute *vdisk_fileio_attrs[] = { &vdisk_o_direct_attr.attr, &vdisk_removable_attr.attr, &vdisk_filename_attr.attr, + &vdisk_cluster_mode_attr.attr, &vdisk_resync_size_attr.attr, &vdisk_sync_attr.attr, &vdev_t10_vend_id_attr.attr, @@ -567,6 +575,7 @@ static const struct attribute *vdisk_blockio_attrs[] = { &vdisk_removable_attr.attr, &vdisk_rotational_attr.attr, &vdisk_filename_attr.attr, + &vdisk_cluster_mode_attr.attr, &vdisk_resync_size_attr.attr, &vdisk_sync_attr.attr, &vdev_t10_vend_id_attr.attr, @@ -680,6 +689,7 @@ static struct scst_dev_type vdisk_file_devtype = { "filename, " "nv_cache, " "o_direct, " + "cluster_mode, " "read_only, " "removable, " "rotational, " @@ -734,6 +744,7 @@ static struct scst_dev_type vdisk_blk_devtype = { "dif_filename, " "filename, " "nv_cache, " + "cluster_mode, " "read_only, " "removable, " "rotational, " @@ -1648,6 +1659,9 @@ next: if (vdev_saved_mode_pages_enabled) vdev_load_mode_pages(virt_dev); + res = scst_pr_set_cluster_mode(dev, dev->cluster_mode, + virt_dev->t10_dev_id); + out: TRACE_EXIT(); return res; @@ -1664,6 +1678,8 @@ static void vdisk_detach(struct scst_device *dev) TRACE_DBG("virt_id %d", dev->virt_id); + scst_pr_set_cluster_mode(dev, false, virt_dev->t10_dev_id); + PRINT_INFO("Detached virtual device %s (\"%s\")", virt_dev->name, vdev_get_filename(virt_dev)); @@ -4437,14 +4453,16 @@ static int vdisk_ctrl_m_pg(unsigned char *p, int pcontrol, */ p[2] |= 7 << 5; /* TST */ #endif - p[2] |= 1 << 2; /* D_SENSE */ - p[2] |= 1 << 3; /* DPICZ */ - p[2] |= 1 << 4; /* TMF_ONLY */ - p[3] |= 0xF << 4; /* QUEUE ALGORITHM MODIFIER */ - p[3] |= 3 << 1; /* QErr */ - p[4] |= 1 << 3; /* SWP */ - p[5] |= 1 << 6; /* TAS */ - p[5] |= 0 << 7; /* ATO */ + if (!virt_dev->dev->cluster_mode) { + p[2] |= 1 << 2; /* D_SENSE */ + p[2] |= 1 << 3; /* DPICZ */ + p[2] |= 1 << 4; /* TMF_ONLY */ + p[3] |= 0xF << 4; /* QUEUE ALGORITHM MODIFIER */ + p[3] |= 3 << 1; /* QErr */ + p[4] |= 1 << 3; /* SWP */ + p[5] |= 1 << 6; /* TAS */ + p[5] |= 0 << 7; /* ATO */ + } break; case 2: /* default */ p[2] |= virt_dev->tst << 5; @@ -4920,6 +4938,13 @@ static enum compl_status_e vdisk_exec_mode_select(struct vdisk_cmd_params *p) TRACE_ENTRY(); virt_dev = cmd->dev->dh_priv; + if (cmd->dev->cluster_mode) { + PRINT_ERROR("MODE SELECT: not supported in cluster mode\n"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + mselect_6 = (MODE_SELECT == cmd->cdb[0]); type = cmd->dev->type; @@ -8810,6 +8835,104 @@ out: return res; } +static ssize_t vdev_sysfs_cluster_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct scst_device *dev = container_of(kobj, struct scst_device, + dev_kobj); + + return sprintf(buf, "%d\n%s", dev->cluster_mode, + dev->cluster_mode ? + SCST_SYSFS_KEY_MARK "\n" : ""); +} + +static int vdev_sysfs_process_cluster_mode_store( + struct scst_sysfs_work_item *work) +{ + struct scst_device *dev = work->dev; + struct scst_vdisk_dev *virt_dev; + long clm; + int res; + + res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); + if (res) + goto out; + + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto resume; + + /* + * This is safe since we hold a reference on dev_kobj and since + * scst_assign_dev_handler() waits until all dev_kobj references + * have been dropped before invoking .detach(). + */ + virt_dev = dev->dh_priv; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) + res = kstrtol(work->buf, 0, &clm); +#else + res = strict_strtol(work->buf, 0, &clm); +#endif + if (res) + goto unlock; + res = -EINVAL; + if (clm < 0 || clm > 1) + goto unlock; + if (clm != dev->cluster_mode) { + res = scst_pr_set_cluster_mode(dev, clm, virt_dev->t10_dev_id); + if (res) + goto unlock; + dev->cluster_mode = clm; + } else { + res = 0; + } + +unlock: + mutex_unlock(&scst_mutex); + +resume: + scst_resume_activity(); + +out: + kobject_put(&dev->dev_kobj); + + return res; +} + +static ssize_t vdev_sysfs_cluster_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct scst_device *dev = container_of(kobj, struct scst_device, + dev_kobj); + struct scst_sysfs_work_item *work; + char *arg; + int res; + + TRACE_ENTRY(); + + res = -ENOMEM; + arg = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!arg) + goto out; + + res = scst_alloc_sysfs_work(vdev_sysfs_process_cluster_mode_store, + false, &work); + if (res) + goto out; + work->dev = dev; + swap(work->buf, arg); + kobject_get(&dev->dev_kobj); + res = scst_sysfs_queue_wait_work(work); + if (res) + goto out; + res = count; + +out: + kfree(arg); + TRACE_EXIT_RES(res); + return res; +} + static int vdisk_sysfs_process_resync_size_store( struct scst_sysfs_work_item *work) { @@ -9165,6 +9288,10 @@ static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); virt_dev = dev->dh_priv; + res = -EPERM; + if (dev->cluster_mode) + goto out; + write_lock(&vdisk_serial_rwlock); if ((count > sizeof(virt_dev->t10_dev_id)) || @@ -9198,6 +9325,7 @@ static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj, out_unlock: write_unlock(&vdisk_serial_rwlock); +out: TRACE_EXIT_RES(res); return res; } diff --git a/scst/src/scst_dlm.c b/scst/src/scst_dlm.c new file mode 100644 index 000000000..369c98c24 --- /dev/null +++ b/scst/src/scst_dlm.c @@ -0,0 +1,1458 @@ +/* + * Copyright (c) 2013 - 2014 Fusion-io, Inc. All rights reserved. + * Copyright (C) 2014 - 2015 SanDisk Corporation. + * + * Synchronization of persistent registration data with DLM lock value blocks. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * 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 +#include +#include +#include +#ifdef INSIDE_KERNEL_TREE +#include +#include +#else +#include "scst.h" +#include "scst_const.h" +#endif +#include "scst_priv.h" +#include "scst_pres.h" +#include "scst_dlm.h" + +static void scst_pr_dlm_cleanup(struct scst_device *dev); +static void scst_dlm_pre_bast(void *bastarg, int mode); +static void scst_dlm_post_bast(void *bastarg, int mode); +static void scst_dlm_post_ast(void *astarg); + +static inline void compile_time_size_checks(void) +{ + BUILD_BUG_ON(sizeof(struct pr_lvb) > PR_DLM_LVB_LEN); + BUILD_BUG_ON(sizeof(struct pr_lvb) != 20); + BUILD_BUG_ON(sizeof(struct pr_reg_lvb) > PR_DLM_LVB_LEN); + BUILD_BUG_ON(sizeof(struct pr_reg_lvb) != 240); +} + +static void scst_dlm_ast(void *astarg) +{ + struct scst_lksb *scst_lksb = astarg; + + complete(&scst_lksb->compl); +} + +/** + * scst_dlm_cancel - Synchronously cancel a pending dlm_lock() operation + */ +static int scst_dlm_cancel(dlm_lockspace_t *ls, struct scst_lksb *lksb, + int flags, const char *name) +{ + int res; + + res = dlm_unlock(ls, lksb->lksb.sb_lkid, + DLM_LKF_CANCEL | (flags & DLM_LKF_VALBLK), + &lksb->lksb, lksb); + if (res < 0) + goto out; + res = wait_for_completion_timeout(&lksb->compl, 10 * HZ); + +out: + return res; +} + +/** + * scst_dlm_lock_wait - Wait until a DLM lock has been granted + * @ls: DLM lock space. + * @mode: DLM lock mode. + * @lksb: DLM lock status block. + * @flags: DLM flags. + * @name: DLM lock name. Only required for non-conversion requests. + * @bast: AST to be invoked in case this lock blocks another one. + */ +static int scst_dlm_lock_wait(dlm_lockspace_t *ls, int mode, + struct scst_lksb *lksb, int flags, + const char *name, void (*bast)(void *, int)) +{ + int res; + + init_completion(&lksb->compl); + res = dlm_lock(ls, mode, &lksb->lksb, flags, + (void *)name, name ? strlen(name) : 0, 0, + scst_dlm_ast, lksb, bast); + if (res < 0) + goto out; + res = wait_for_completion_timeout(&lksb->compl, 60 * HZ); + if (res > 0) + res = lksb->lksb.sb_status; + else if (res == 0) + res = -ETIMEDOUT; + if (res < 0) { + int res2 = scst_dlm_cancel(ls, lksb, flags, name); + + WARN(res2 < 0, "canceling lock %s / %08x failed: %d\n", + name ? : "?", lksb->lksb.sb_lkid, res2); + } + +out: + return res; +} + +/** + * scst_dlm_unlock_wait - Discard a DLM lock + */ +static int scst_dlm_unlock_wait(dlm_lockspace_t *ls, struct scst_lksb *lksb) +{ + int res; + + sBUG_ON(!ls); + + init_completion(&lksb->compl); + res = dlm_unlock(ls, lksb->lksb.sb_lkid, 0, &lksb->lksb, lksb); + if (res < 0) + goto out; + res = wait_for_completion_timeout(&lksb->compl, 60 * HZ); + if (res > 0) { + res = lksb->lksb.sb_status; + if (res == -DLM_EUNLOCK || res == -DLM_ECANCEL) + res = 0; + } else if (res == 0) { + res = -ETIMEDOUT; + } + +out: + return res; +} + +/* Number of persistent reservation registrants. */ +static uint32_t scst_pr_num_regs(struct scst_device *dev) +{ + struct scst_dev_registrant *reg; + uint32_t num_regs = 0; + + lockdep_assert_pr_read_lock_held(dev); + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) + num_regs++; + + return num_regs; +} + +/* DLM-specific registrant initialization. */ +static void scst_dlm_pr_init_reg(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ + reg->lksb.lksb.sb_lvbptr = (void *)reg->lvb; + reg->lksb.lksb.sb_lkid = 0; + reg->dlm_idx = -1; +} + +static void scst_dlm_pr_rm_reg_ls(dlm_lockspace_t *ls, + struct scst_dev_registrant *reg) +{ + int res; + + if (!reg->lksb.lksb.sb_lkid) + return; + res = scst_dlm_unlock_wait(ls, ®->lksb); + WARN(res < 0, "scst_dlm_unlock_wait(%08x) failed (%d)", + reg->lksb.lksb.sb_lkid, res); + reg->lksb.lksb.sb_lkid = 0; + reg->dlm_idx = -1; +} + +/* DLM-specific registrant cleanup. */ +static void scst_dlm_pr_rm_reg(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ + lockdep_assert_pr_write_lock_held(dev); + scst_dlm_pr_rm_reg_ls(dev->pr_dlm->ls, reg); +} + +/* Copy SPC-2 reservation state from the DLM LVB into @dev. */ +static bool scst_copy_res_from_dlm(struct scst_device *dev, struct pr_lvb *lvb) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + struct scst_session *dropped_res = NULL; + bool modified_lvb = false; + + spin_lock_bh(&dev->dev_lock); + pr_dlm->reserved_by_nodeid = be32_to_cpu(lvb->reserved_by_nodeid); + if (dev->reserved_by && + pr_dlm->reserved_by_nodeid != pr_dlm->local_nodeid) { + PRINT_WARNING("%s: dropping SPC-2 reservation for %s (due to" + " split-brain ?) because node %d holds a" + " reservation", dev->virt_name, + dev->reserved_by->initiator_name, + pr_dlm->reserved_by_nodeid); + swap(dev->reserved_by, dropped_res); + } + if (!dev->reserved_by && + pr_dlm->reserved_by_nodeid == pr_dlm->local_nodeid) { + PRINT_WARNING("%s: dropping SPC-2 reservation (due to restart" + " or split-brain ?) and triggering LVB update" + " because of inconstency (holder %d / not rsrvd)", + dev->virt_name, pr_dlm->reserved_by_nodeid); + pr_dlm->reserved_by_nodeid = 0; + lvb->reserved_by_nodeid = 0; + modified_lvb = true; + } + if (dev->reserved_by) + EXTRACHECKS_BUG_ON(pr_dlm->reserved_by_nodeid != + pr_dlm->local_nodeid); + else + EXTRACHECKS_BUG_ON(pr_dlm->reserved_by_nodeid == + pr_dlm->local_nodeid); + if (dropped_res) + scst_sess_get(dropped_res); + spin_unlock_bh(&dev->dev_lock); + + if (dropped_res) { + /* + * To do: something like + * scst_do_nexus_loss_sess(dropped_res, true); + */ + scst_sess_put(dropped_res); + } + + return modified_lvb; +} + +/* + * Update local PR and registrant information from the content of the DLM LVB's. + * Caller must hold PR_DATA_LOCK in PW mode. + * + * Returns -EINVAL if and only if an invalid lock value block has been + * encountered. + */ +static int scst_copy_from_dlm(struct scst_device *dev, dlm_lockspace_t *ls, + bool *modified_lvb) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + struct pr_lvb *lvb = (void *)pr_dlm->lvb; + struct scst_lksb *reg_lksb = NULL; + struct scst_dev_registrant *reg, *tmp_reg; + int i, res = -ENOMEM; + uint32_t nr_registrants; + void *reg_lvb_content = NULL; + + lockdep_assert_held(&pr_dlm->ls_mutex); + + nr_registrants = be32_to_cpu(lvb->nr_registrants); + if (nr_registrants) { + reg_lksb = vzalloc((sizeof(*reg_lksb) + PR_DLM_LVB_LEN) * + nr_registrants); + if (!reg_lksb) { + PRINT_ERROR("%s: failed to allocate %d * %zd bytes of" + " memory", __func__, nr_registrants, + sizeof(*reg_lksb) + PR_DLM_LVB_LEN); + goto out; + } + reg_lvb_content = (void *)reg_lksb + + nr_registrants * sizeof(*reg_lksb); + } + + for (i = 0; i < nr_registrants; i++) { + char reg_name[32]; + struct pr_reg_lvb *reg_lvb; + + snprintf(reg_name, sizeof(reg_name), PR_REG_LOCK, i); + reg_lvb = reg_lvb_content + i * PR_DLM_LVB_LEN; + reg_lksb[i].lksb.sb_lvbptr = (void *)reg_lvb; + res = scst_dlm_lock_wait(ls, DLM_LOCK_PW, ®_lksb[i], + DLM_LKF_VALBLK, reg_name, NULL); + if (res < 0) { + res = -EFAULT; + PRINT_ERROR("locking %s.%s failed", dev->virt_name, + reg_name); + goto cancel; + } else if (reg_lksb[i].lksb.sb_flags & DLM_SBF_VALNOTVALID) { + res = -EINVAL; + PRINT_WARNING("%s.%s has an invalid lock value block", + dev->virt_name, reg_name); + goto cancel; + } else if (reg_lvb->version != 1) { + res = -EPROTONOSUPPORT; + PRINT_ERROR("%s.%s.version = %d instead of 1", + dev->virt_name, reg_name, + reg_lvb->version); + goto cancel; + } + } + + *modified_lvb = scst_copy_res_from_dlm(dev, lvb); + + scst_pr_write_lock(dev); + + dev->pr_aptpl = lvb->pr_aptpl; + dev->pr_generation = be32_to_cpu(lvb->pr_generation); + dev->pr_is_set = lvb->pr_is_set; + dev->pr_type = lvb->pr_type; + dev->pr_scope = lvb->pr_scope; + dev->pr_holder = NULL; + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) + scst_dlm_pr_rm_reg_ls(ls, reg); + + for (i = 0; i < nr_registrants; i++) { + struct pr_reg_lvb *reg_lvb; + uint16_t rel_tgt_id; + + reg_lvb = (struct pr_reg_lvb *)reg_lksb[i].lksb.sb_lvbptr; + rel_tgt_id = be16_to_cpu(reg_lvb->rel_tgt_id); +#if 0 + PRINT_INFO("Transport ID in %s." PR_REG_LOCK " (len %d):", + dev->virt_name, i, scst_tid_size(reg_lvb->tid)); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1, + reg_lvb->tid, scst_tid_size(reg_lvb->tid), 1); +#endif + reg = scst_pr_find_reg(dev, reg_lvb->tid, rel_tgt_id); + if (reg && reg->key != reg_lvb->key) { + scst_pr_remove_registrant(dev, reg); + reg = NULL; + } + if (!reg) + reg = scst_pr_add_registrant(dev, reg_lvb->tid, + rel_tgt_id, reg_lvb->key, + false); + if (reg) { + scst_dlm_pr_rm_reg_ls(ls, reg); + reg->lksb.lksb.sb_lkid = reg_lksb[i].lksb.sb_lkid; + reg->dlm_idx = i; + memcpy(reg->lvb, reg_lvb_content, sizeof(reg->lvb)); + if (reg_lvb->is_holder) { + if (dev->pr_is_set) + scst_pr_clear_holder(dev); + scst_pr_set_holder(dev, reg, lvb->pr_scope, + lvb->pr_type); + } + } else { + PRINT_ERROR("pr_add_registrant %s." PR_REG_LOCK + " failed\n", dev->virt_name, i); + scst_dlm_unlock_wait(ls, ®_lksb[i]); + continue; + } + scst_dlm_lock_wait(ls, DLM_LOCK_CR, ®->lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, NULL, + NULL); + } + + /* Remove all registrants not found in any DLM LVB */ + list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list, + dev_registrants_list_entry) + if (reg->lksb.lksb.sb_lkid == 0) + scst_pr_remove_registrant(dev, reg); + + scst_pr_write_unlock(dev); + + res = 0; + +out: + vfree(reg_lksb); + return res; + +cancel: + for (i = 0; i < nr_registrants; i++) + if (reg_lksb[i].lksb.sb_lkid) + scst_dlm_unlock_wait(ls, ®_lksb[i]); + + goto out; +} + +static struct scst_dev_registrant* +scst_get_reg_by_dlm_idx(struct scst_device *dev, int i) +{ + struct scst_dev_registrant *reg; + + lockdep_assert_pr_read_lock_held(dev); + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) + if (reg->dlm_idx == i) + return reg; + + return NULL; +} + +static int scst_get_available_dlm_idx(struct scst_device *dev) +{ + int i = 0; + + lockdep_assert_pr_read_lock_held(dev); + + while (scst_get_reg_by_dlm_idx(dev, i)) + i++; + + return i; +} + +/* Copy SPC-2 reservation state for @dev into the DLM LVB @lvb. */ +static void scst_copy_res_to_dlm(struct scst_device *dev, struct pr_lvb *lvb) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + + spin_lock_bh(&dev->dev_lock); + lvb->reserved_by_nodeid = cpu_to_be32(pr_dlm->reserved_by_nodeid); + spin_unlock_bh(&dev->dev_lock); +} + +/* + * Update PR and registrant information in the DLM LVB's. Caller must hold + * PR_DATA_LOCK in PW mode. + */ +static void scst_copy_to_dlm(struct scst_device *dev, dlm_lockspace_t *ls) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + struct pr_lvb *lvb = (void *)pr_dlm->lvb; + struct pr_reg_lvb *reg_lvb; + struct scst_dev_registrant *reg; + int i, tid_size; + char reg_name[32]; + uint32_t nr_registrants; + + lockdep_assert_held(&pr_dlm->ls_mutex); + + scst_copy_res_to_dlm(dev, lvb); + + scst_pr_write_lock(dev); + + nr_registrants = scst_pr_num_regs(dev); + lvb->version = 1; + lvb->pr_is_set = dev->pr_is_set; + lvb->pr_type = dev->pr_type; + lvb->pr_scope = dev->pr_scope; + lvb->pr_aptpl = dev->pr_aptpl; + lvb->nr_registrants = cpu_to_be32(nr_registrants); + lvb->pr_generation = cpu_to_be32(dev->pr_generation); + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (reg->dlm_idx >= nr_registrants) + scst_dlm_pr_rm_reg_ls(ls, reg); + if (reg->dlm_idx < 0) { + i = scst_get_available_dlm_idx(dev); + snprintf(reg_name, sizeof(reg_name), PR_REG_LOCK, i); + if (scst_dlm_lock_wait(ls, DLM_LOCK_NL, + ®->lksb, 0, reg_name, NULL) + >= 0) + reg->dlm_idx = i; + } + } + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (WARN_ON(!reg->lksb.lksb.sb_lkid)) + continue; + snprintf(reg_name, sizeof(reg_name), PR_REG_LOCK, reg->dlm_idx); + if (scst_dlm_lock_wait(ls, DLM_LOCK_PW, ®->lksb, + DLM_LKF_VALBLK | DLM_LKF_CONVERT, + reg_name, NULL) >= 0) { + reg_lvb = (void *)reg->lksb.lksb.sb_lvbptr; + memset(reg->lvb, 0, sizeof(reg->lvb)); + reg_lvb->key = reg->key; + reg_lvb->rel_tgt_id = cpu_to_be16(reg->rel_tgt_id); + reg_lvb->version = 1; + reg_lvb->is_holder = dev->pr_holder == reg; + tid_size = scst_tid_size(reg->transport_id); +#if 0 + PRINT_INFO("Copying transport ID into %s." PR_REG_LOCK + " (len %d)", dev->virt_name, reg->dlm_idx, + tid_size); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, + 1, reg->transport_id, tid_size, 1); +#endif + if (WARN(tid_size > sizeof(reg_lvb->tid), + "tid_size %d > %zd\n", tid_size, + sizeof(reg_lvb->tid))) + tid_size = sizeof(reg_lvb->tid); + memcpy(reg_lvb->tid, reg->transport_id, tid_size); + scst_dlm_lock_wait(ls, DLM_LOCK_CR, ®->lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, + reg_name, NULL); + } else { + PRINT_ERROR("Failed to lock %s.%s", dev->virt_name, + reg_name); + } + } + + scst_pr_write_unlock(dev); +} + +/* + * Read the contents of a file, copy it into a buffer and terminate the buffer + * with '\0'. + */ +static int scst_read_file(const char *path, char *buf, int buf_len) +{ + struct file *f; + loff_t pos; + int ret; + + f = filp_open(path, 0, 0400); + if (IS_ERR(f)) { + ret = PTR_ERR(f); + goto out; + } + pos = 0; + ret = vfs_read(f, (char __force __user *)buf, buf_len, &pos); + if (ret >= 0) + buf[min(ret, buf_len - 1)] = '\0'; + filp_close(f, NULL); +out: + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) +struct scst_dlm_readdir_context { + struct dir_context ctx; + char *entries; +}; +#endif + +/* Append the name of each directory entry to the buffer @arg points to. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) +static int scst_dlm_filldir(void *arg, const char *name_arg, int name_len, + loff_t curr_pos, u64 inode, unsigned dtype) +#else +static int scst_dlm_filldir(struct dir_context *arg, const char *name_arg, + int name_len, loff_t curr_pos, u64 inode, + unsigned dtype) +#endif +{ + char *p, *q, name[64]; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0) + char **entries = arg; +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) + struct scst_dlm_readdir_context *ctx = + container_of((struct dir_context *)arg, typeof(*ctx), ctx); +#else + struct scst_dlm_readdir_context *ctx = + container_of(arg, typeof(*ctx), ctx); +#endif + char **entries = &ctx->entries; +#endif + int i; + + snprintf(name, sizeof(name), "%.*s", name_len, name_arg); + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || !*entries) + goto out; + for (p = *entries; *p; p += strlen(p) + 1) + ; + i = p - *entries; + q = *entries; + *entries = krealloc(q, i + strlen(name) + 2, GFP_KERNEL); + if (!*entries) { + kfree(q); + goto out; + } + strcpy(*entries + i, name); + i += strlen(name); + (*entries)[i + 1] = '\0'; + +out: + return *entries ? 0 : -ENOMEM; +} + +/** + * scst_dlm_update_nodeids - Update the Corosync node ID array pr_dlm->nodeid[] + */ +static int scst_dlm_update_nodeids(struct scst_pr_dlm_data *pr_dlm) +{ + static const char comms_dir[] = "/sys/kernel/config/dlm/cluster/comms"; + struct file *comms; + char *p, *entries = kzalloc(1, GFP_KERNEL); + uint32_t nodeid, *new; + int i, ret, num_nodes; + char path[256], buf[64]; + + lockdep_assert_held(&pr_dlm->ls_mutex); + + num_nodes = 0; + + comms = filp_open(comms_dir, 0, 0400); + if (IS_ERR(comms)) { + ret = PTR_ERR(comms); + goto out; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0) + ret = vfs_readdir(comms, scst_dlm_filldir, &entries); +#else + { + struct scst_dlm_readdir_context ctx = { + .ctx = { + .actor = scst_dlm_filldir, + }, + .entries = entries, + }; + ret = iterate_dir(comms, &ctx.ctx); + entries = ctx.entries; + } +#endif + filp_close(comms, NULL); + ret = -ENOMEM; + if (!entries) + goto out; + for (p = entries; *p; p += strlen(p) + 1) + num_nodes++; + new = krealloc(pr_dlm->nodeid, sizeof(*pr_dlm->nodeid) * num_nodes, + GFP_KERNEL); + if (!new) + goto out; + pr_dlm->nodeid = new; + pr_dlm->participants = num_nodes; + for (i = 0, p = entries; *p; i++, p += strlen(p) + 1) { + nodeid = simple_strtoul(p, NULL, 0); + snprintf(path, sizeof(path), "%s/%s/local", comms_dir, p); + if (scst_read_file(path, buf, sizeof(buf)) >= 0 && + strcmp(buf, "1\n") == 0) + pr_dlm->local_nodeid = nodeid; + pr_dlm->nodeid[i] = nodeid; + } + ret = 0; + +out: + kfree(entries); + return ret; +} + +/* + * Toggle all non-local DLM locks with name format @fmt from NL to PR and back + * to NL. + */ +static void scst_pr_toggle_lock(struct scst_pr_dlm_data *pr_dlm, + dlm_lockspace_t *ls, const char *fmt) +{ + struct scst_lksb lksb; + int i, res; + char lock_name[32]; + + memset(&lksb, 0, sizeof(lksb)); + for (i = 0; i < pr_dlm->participants; i++) { + if (pr_dlm->nodeid[i] == pr_dlm->local_nodeid) + continue; + snprintf(lock_name, sizeof(lock_name), fmt, pr_dlm->nodeid[i]); + lksb.lksb.sb_lkid = 0; + res = scst_dlm_lock_wait(ls, DLM_LOCK_PR, &lksb, 0, + lock_name, NULL); + if (res < 0) + PRINT_WARNING("Locking %s.%s failed (%d)", + pr_dlm->dev->virt_name, lock_name, res); + if (!lksb.lksb.sb_lkid) + continue; + scst_dlm_lock_wait(ls, DLM_LOCK_NL, &lksb, + DLM_LKF_CONVERT, lock_name, NULL); + scst_dlm_unlock_wait(ls, &lksb); + } +} + +/* Remove a lock from the local DLM lockspace instance. */ +static void scst_dlm_remove_lock(dlm_lockspace_t *ls, struct scst_lksb *lksb, + const char *name) +{ + if (!lksb->lksb.sb_lkid) + return; + scst_dlm_lock_wait(ls, DLM_LOCK_NL, lksb, DLM_LKF_CONVERT, name, + NULL); + scst_dlm_unlock_wait(ls, lksb); + lksb->lksb.sb_lkid = 0; +} + +static void scst_dlm_remove_locks(struct scst_pr_dlm_data *pr_dlm, + dlm_lockspace_t *ls) +{ + struct scst_device *dev = pr_dlm->dev; + struct scst_dev_registrant *reg; + + lockdep_assert_held(&pr_dlm->ls_mutex); + + scst_pr_write_lock(dev); + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) + scst_dlm_pr_rm_reg_ls(ls, reg); + scst_pr_write_unlock(dev); + + scst_dlm_remove_lock(ls, &pr_dlm->pre_join_lksb, NULL); + scst_dlm_remove_lock(ls, &pr_dlm->post_join_lksb, NULL); + scst_dlm_remove_lock(ls, &pr_dlm->pre_upd_lksb, NULL); + scst_dlm_remove_lock(ls, &pr_dlm->post_upd_lksb, NULL); + scst_dlm_remove_lock(ls, &pr_dlm->data_lksb, PR_DATA_LOCK); +} + +/* + * If two or more nodes are present in the cluster, tell each other node to + * update the local state information from the DLM lock value blocks. The + * caller must hold PR_LOCK in EX mode. + */ +static void scst_trigger_reread_lvb(struct scst_pr_dlm_data *const pr_dlm, + dlm_lockspace_t *ls) +{ + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_PRE_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); +} + +/* + * If two or more nodes are present in the cluster, tell each other node to + * refresh the DLM lock value blocks. The caller must hold PR_LOCK in EX mode. + */ +static void scst_trigger_lvb_update(struct scst_pr_dlm_data *const pr_dlm, + dlm_lockspace_t *ls) +{ + PRINT_INFO("%s: about to trigger an LVB update", + pr_dlm->dev->virt_name); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_JOIN_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_PRE_JOIN_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_JOIN_LOCK); + PRINT_INFO("%s: finished triggering an LVB update", + pr_dlm->dev->virt_name); +} + +static void dump_lockspace(const char *cl_dev_id) +{ + char *argv0 = kstrdup("/bin/bash", GFP_KERNEL); + char *argv1 = kstrdup("-c", GFP_KERNEL); + char *argv2 = kasprintf(GFP_KERNEL, + "{ echo lockspace-dump-start;" + " grep -aH '' /sys/kernel/debug/dlm/%s%s*;" + " echo lockspace-dump-end; } 2>&1 |" + " while read line; do logger \"$line\"; done", + SCST_DLM_LOCKSPACE_PFX, cl_dev_id); + char *argv[] = { argv0, argv1, argv2, NULL }; + char *envp[] = { + kstrdup("PATH=/usr/bin:/bin:/usr/sbin:/sbin", GFP_KERNEL), + NULL + }; + + + if (!argv[0] || !argv[1] || !argv[2] || !envp[0]) { + PRINT_ERROR("%s: out of memory", __func__); + goto out; + } + + PRINT_INFO("Invoking %s", argv2); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + call_usermodehelper(argv0, argv, envp, UMH_WAIT_PROC); +#else + call_usermodehelper_fns(argv0, argv, envp, UMH_WAIT_PROC, NULL, NULL, + NULL); +#endif + +out: + kfree(envp[0]); + kfree(argv[2]); + kfree(argv[1]); + kfree(argv[0]); +} + +static void release_lockspace(dlm_lockspace_t *ls, const char *cl_dev_id) +{ + int res; + + res = dlm_release_lockspace(ls, 1); + if (res) { + PRINT_ERROR("releasing lockspace for %s failed: %d", + cl_dev_id, res); + dump_lockspace(cl_dev_id); + } + if (res == -EBUSY) { + /* + * Releasing a lockspace fails if one or more local instances + * of DLM locks still exist in the lockspace. If that + * happens try to release the lockspace forcibly. + */ + res = dlm_release_lockspace(ls, 2); + if (res) + PRINT_ERROR("forcibly releasing lockspace for %s" + " failed: %d", cl_dev_id, res); + } + if (res == 0) + PRINT_INFO("released lockspace for %s", cl_dev_id); +} + +/* Initialize DLM lockspace. */ +static dlm_lockspace_t *get_lockspace(struct scst_device *dev) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls; + struct scst_lksb pr_lksb; + struct pr_lvb *lvb = (void *)pr_dlm->lvb; + char lsp_name[32], lock_name[32]; + int res; + bool modified_lvb = false; + + if (pr_dlm->ls || !pr_dlm->cl_dev_id || in_interrupt() || + time_is_after_jiffies(pr_dlm->latest_lscr_attempt + 1 * HZ)) + goto out; + + mutex_lock(&pr_dlm->ls_cr_mutex); + if (pr_dlm->ls) + goto out_unlock_ls_cr; + + pr_dlm->latest_lscr_attempt = jiffies; + + mutex_lock(&pr_dlm->ls_mutex); + + res = scst_dlm_update_nodeids(pr_dlm); + if (res < 0) { + PRINT_ERROR("scst_dlm_update_nodeids(%s) failed: %d", + dev->virt_name, res); + goto out_unlock_ls; + } + if (pr_dlm->participants == 0) + goto out_unlock_ls; + + snprintf(lsp_name, sizeof(lsp_name), "%s%s", SCST_DLM_LOCKSPACE_PFX, + pr_dlm->cl_dev_id); + res = scst_dlm_new_lockspace(lsp_name, strlen(lsp_name), &ls, + DLM_LSFL_NEWEXCL | DLM_LSFL_FS, + PR_DLM_LVB_LEN); + if (res) { + PRINT_ERROR("Creating DLM lockspace %s failed: %d", lsp_name, + res); + goto out_unlock_ls; + } + + PRINT_INFO("Created DLM lockspace %s for %s", lsp_name, dev->virt_name); + + memset(&pr_lksb, 0, sizeof(pr_lksb)); + res = scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_lksb, 0, PR_LOCK, + NULL); + if (res < 0) + goto unlock_dlm_pr; + + snprintf(lock_name, sizeof(lock_name), PR_POST_JOIN_LOCK, + pr_dlm->local_nodeid); + pr_dlm->post_join_lksb.pr_dlm = pr_dlm; + res = scst_dlm_lock_wait(ls, DLM_LOCK_NL, + &pr_dlm->post_join_lksb, 0, lock_name, + scst_dlm_post_bast); + if (res < 0) + goto release_lockspace; + + snprintf(lock_name, sizeof(lock_name), PR_PRE_JOIN_LOCK, + pr_dlm->local_nodeid); + pr_dlm->pre_join_lksb.pr_dlm = pr_dlm; + res = scst_dlm_lock_wait(ls, DLM_LOCK_EX, + &pr_dlm->pre_join_lksb, 0, lock_name, + scst_dlm_pre_bast); + if (res < 0) + goto release_lockspace; + + res = scst_dlm_lock_wait(ls, DLM_LOCK_PW, &pr_dlm->data_lksb, + DLM_LKF_VALBLK, PR_DATA_LOCK, NULL); + if (res < 0) + goto release_lockspace; + + if (pr_dlm->data_lksb.lksb.sb_status & DLM_SBF_VALNOTVALID) { + PRINT_ERROR("%s.%s lock value block not valid", dev->virt_name, + PR_DATA_LOCK); + memset(pr_dlm->lvb, 0, sizeof(pr_dlm->lvb)); + } + + snprintf(lock_name, sizeof(lock_name), PR_POST_UPDATE_LOCK, + pr_dlm->local_nodeid); + pr_dlm->post_upd_lksb.pr_dlm = pr_dlm; + res = scst_dlm_lock_wait(ls, DLM_LOCK_NL, + &pr_dlm->post_upd_lksb, 0, lock_name, + scst_dlm_post_bast); + if (res < 0) + goto release_lockspace; + + snprintf(lock_name, sizeof(lock_name), PR_PRE_UPDATE_LOCK, + pr_dlm->local_nodeid); + pr_dlm->pre_upd_lksb.pr_dlm = pr_dlm; + res = scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_dlm->pre_upd_lksb, + 0, lock_name, scst_dlm_pre_bast); + if (res < 0) + goto release_lockspace; + + switch (lvb->version) { + case 0: + scst_copy_to_dlm(dev, ls); + break; + case 1: + res = scst_copy_from_dlm(dev, ls, &modified_lvb); + break; + default: + PRINT_ERROR("%s: Wrong PR LVB version %d", dev->virt_name, + lvb->version); + goto release_lockspace; + } + + scst_dlm_lock_wait(ls, DLM_LOCK_CR, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + + if (res == -EINVAL) + scst_trigger_lvb_update(pr_dlm, ls); + else if (modified_lvb) + scst_trigger_reread_lvb(pr_dlm, ls); + + scst_dlm_unlock_wait(ls, &pr_lksb); + + /* + * Only store the lockspace pointer in pr_dlm->ls after the lockspace + * has been fully initialized. Storing it earlier would create a risk + * that a concurrent get_lockspace() call returns a pointer to the + * lockspace that is under creation. + */ + pr_dlm->ls = ls; + +out_unlock_ls: + mutex_unlock(&pr_dlm->ls_mutex); + +out_unlock_ls_cr: + mutex_unlock(&pr_dlm->ls_cr_mutex); + +out: + return pr_dlm->ls; + +release_lockspace: + scst_dlm_remove_locks(pr_dlm, ls); +unlock_dlm_pr: + scst_dlm_remove_lock(ls, &pr_lksb, PR_LOCK); + mutex_unlock(&pr_dlm->ls_mutex); + + cancel_work_sync(&pr_dlm->copy_from_dlm_work); + cancel_work_sync(&pr_dlm->copy_to_dlm_work); + cancel_work_sync(&pr_dlm->lvb_upd_work); + cancel_work_sync(&pr_dlm->reread_lvb_work); + + release_lockspace(ls, pr_dlm->cl_dev_id); + goto out_unlock_ls_cr; +} + +static bool scst_dlm_pr_is_set(struct scst_device *dev) +{ + get_lockspace(dev); + return dev->pr_is_set; +} + +static void scst_dlm_pr_write_lock(struct scst_device *dev, + struct scst_lksb *pr_lksb) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls; + + memset(pr_lksb, 0, sizeof(*pr_lksb)); + + ls = get_lockspace(dev); + if (!ls) + goto out; + + scst_dlm_lock_wait(ls, DLM_LOCK_EX, pr_lksb, 0, PR_LOCK, NULL); + if (pr_lksb->lksb.sb_lkid) { + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_PRE_UPDATE_LOCK); + scst_dlm_lock_wait(ls, DLM_LOCK_PW, + &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, + PR_DATA_LOCK, NULL); + } + +out: + /* + * Note: invoking scst_copy_from_dlm(dev) here is not necessary + * because that function is already invoked after joining the + * lockspace and from inside post_bast(). + */ + scst_pr_write_lock(dev); +} + +static void scst_dlm_pr_write_unlock(struct scst_device *dev, + struct scst_lksb *pr_lksb) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls = pr_dlm->ls; + + scst_pr_write_unlock(dev); + + if (!pr_lksb->lksb.sb_lkid) + return; + + scst_copy_to_dlm(dev, ls); + scst_dlm_lock_wait(ls, DLM_LOCK_CR, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + scst_dlm_unlock_wait(ls, pr_lksb); +} + +static bool scst_dlm_reserved(struct scst_device *dev) +{ + EXTRACHECKS_BUG_ON(in_irq() || irqs_disabled()); + + get_lockspace(dev); + return dev->reserved_by || dev->pr_dlm->reserved_by_nodeid; +} + +static void scst_dlm_res_lock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __acquires(&dev->dev_lock) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls; + + EXTRACHECKS_BUG_ON(in_irq() || irqs_disabled()); + memset(pr_lksb, 0, sizeof(*pr_lksb)); + ls = get_lockspace(dev); + if (!ls) + goto out; + + scst_dlm_lock_wait(ls, DLM_LOCK_EX, pr_lksb, 0, PR_LOCK, NULL); + if (pr_lksb->lksb.sb_lkid) { + scst_dlm_lock_wait(ls, DLM_LOCK_PW, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, + PR_DATA_LOCK, NULL); + } + +out: + spin_lock_bh(&dev->dev_lock); +} + +static void scst_dlm_res_unlock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __releases(&dev->dev_lock) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls = pr_dlm->ls; + struct pr_lvb *lvb = (void *)pr_dlm->lvb; + bool update_lvb; + + spin_unlock_bh(&dev->dev_lock); + + if (!pr_lksb->lksb.sb_lkid) + return; + + update_lvb = (be32_to_cpu(lvb->reserved_by_nodeid) != + pr_dlm->reserved_by_nodeid); + + if (update_lvb) + scst_copy_to_dlm(dev, ls); + scst_dlm_lock_wait(ls, DLM_LOCK_CR, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + if (update_lvb) { + scst_pr_toggle_lock(pr_dlm, ls, PR_PRE_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + } + scst_dlm_unlock_wait(ls, pr_lksb); +} + +static bool scst_dlm_is_rsv_holder(struct scst_device *dev, + struct scst_session *sess) +{ + return dev->reserved_by == sess; +} + +static bool scst_dlm_is_not_rsv_holder(struct scst_device *dev, + struct scst_session *sess) +{ + return dev->pr_dlm->reserved_by_nodeid && dev->reserved_by != sess; +} + +static void scst_dlm_reserve(struct scst_device *dev, struct scst_session *sess) +{ + dev->reserved_by = sess; + dev->pr_dlm->reserved_by_nodeid = sess ? dev->pr_dlm->local_nodeid : 0; +} + +static void scst_dlm_pre_bast(void *bastarg, int mode) +{ + struct scst_lksb *pre_lksb = bastarg; + struct scst_pr_dlm_data *pr_dlm = pre_lksb->pr_dlm; + const bool join = pre_lksb == &pr_dlm->pre_join_lksb; + + /* An AST must not block, so execute further work asynchronously. */ + if (join) + queue_work(pr_dlm->to_wq, &pr_dlm->pre_join_work); + else + queue_work(pr_dlm->from_wq, &pr_dlm->pre_upd_work); +} + +static void scst_pre_join_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, pre_join_work); + dlm_lockspace_t *ls; + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (ls) { + scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_dlm->post_join_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_post_bast); + scst_dlm_lock_wait(ls, DLM_LOCK_NL, &pr_dlm->pre_join_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_pre_bast); + } + mutex_unlock(&pr_dlm->ls_mutex); +} + +static void scst_pre_upd_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, pre_upd_work); + dlm_lockspace_t *ls; + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (ls) { + scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_dlm->post_upd_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_post_bast); + scst_dlm_lock_wait(ls, DLM_LOCK_NL, &pr_dlm->pre_upd_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_pre_bast); + } + mutex_unlock(&pr_dlm->ls_mutex); +} + +static void scst_dlm_post_bast(void *bastarg, int mode) +{ + struct scst_lksb *post_lksb = bastarg; + struct scst_pr_dlm_data *pr_dlm = post_lksb->pr_dlm; + const bool join = post_lksb == &pr_dlm->post_join_lksb; + + /* An AST must not block, so execute further work asynchronously. */ + if (join) + queue_work(pr_dlm->to_wq, &pr_dlm->copy_to_dlm_work); + else + queue_work(pr_dlm->from_wq, &pr_dlm->copy_from_dlm_work); +} + +/* + * Note: the node that has invoked scst_trigger_lvb_update() holds PR_LOCK + * in EX mode and waits until this function has finished. + */ +static void scst_copy_to_dlm_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, copy_to_dlm_work); + struct scst_device *dev = pr_dlm->dev; + dlm_lockspace_t *ls; + int res; + + PRINT_INFO("Copying PR state to the DLM"); + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (!ls) + goto unlock_ls; + scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_dlm->pre_join_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_pre_bast); + res = scst_dlm_lock_wait(ls, DLM_LOCK_PW, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + if (res < 0) { + PRINT_WARNING("dlm_lock(%s.%s) returned %d", dev->virt_name, + PR_DATA_LOCK, res); + goto unlock_pr; + } + + /* + * Note: whether or not the PR_DATA_LOCK LVB is valid does not matter + * here since we are going to overwrite it anyway. + */ + if (pr_dlm->data_lksb.lksb.sb_flags & DLM_SBF_VALNOTVALID) + PRINT_INFO("%s.%s LVB not valid\n", dev->virt_name, + PR_DATA_LOCK); + + scst_copy_to_dlm(dev, ls); + scst_dlm_lock_wait(ls, DLM_LOCK_CR, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + +unlock_pr: + dlm_lock(ls, DLM_LOCK_NL, &pr_dlm->post_join_lksb.lksb, + DLM_LKF_CONVERT, NULL, 0, 0, scst_dlm_post_ast, + &pr_dlm->post_join_lksb, scst_dlm_post_bast); + + PRINT_INFO("Finished copying PR state to the DLM"); + + scst_dlm_update_nodeids(pr_dlm); + + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_PRE_UPDATE_LOCK); + scst_pr_toggle_lock(pr_dlm, ls, PR_POST_UPDATE_LOCK); + +unlock_ls: + mutex_unlock(&pr_dlm->ls_mutex); + + PRINT_INFO("Finished notifying other nodes about the new PR state"); +} + +/* + * Note: the scst_copy_from_dlm() call below runs outside command context. It + * is protected against device removal because scst_pr_dlm_cleanup() is + * invoked before a device is removed and that last function waits until this + * function has finished and additionally prevents new invocations of this + * function. The scst_copy_from_dlm() call below is protected against tgt_dev + * addition or removal (e.g. due to a cable pull) because + * scst_pr_init_tgt_dev() and scst_pr_clear_tgt_dev() in scst_pres.c protect + * these manipulations by locking the PR data structures for writing. + */ +static void scst_copy_from_dlm_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, copy_from_dlm_work); + struct scst_device *dev = pr_dlm->dev; + dlm_lockspace_t *ls; + int res = -ENOENT; + bool modified_lvb = false; + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (!ls) + goto unlock_ls; + scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_dlm->pre_upd_lksb, + DLM_LKF_CONVERT, NULL, scst_dlm_pre_bast); + res = scst_dlm_lock_wait(ls, DLM_LOCK_PW, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + if (res < 0) { + PRINT_WARNING("dlm_lock(%s.%s) returned %d", dev->virt_name, + PR_DATA_LOCK, res); + goto unlock_pr; + } + if (pr_dlm->data_lksb.lksb.sb_flags & DLM_SBF_VALNOTVALID) { + PRINT_WARNING("%s.%s has an invalid lock value block", + dev->virt_name, PR_DATA_LOCK); + res = -EINVAL; + goto unlock_pr; + } + res = scst_copy_from_dlm(dev, ls, &modified_lvb); + scst_dlm_lock_wait(ls, DLM_LOCK_CR, &pr_dlm->data_lksb, + DLM_LKF_CONVERT | DLM_LKF_VALBLK, PR_DATA_LOCK, + NULL); + +unlock_pr: + dlm_lock(ls, DLM_LOCK_NL, &pr_dlm->post_upd_lksb.lksb, + DLM_LKF_CONVERT, NULL, 0, 0, scst_dlm_post_ast, + &pr_dlm->post_upd_lksb, scst_dlm_post_bast); + + scst_dlm_update_nodeids(pr_dlm); + +unlock_ls: + mutex_unlock(&pr_dlm->ls_mutex); + + if (res == -EINVAL) + queue_work(pr_dlm->upd_wq, &pr_dlm->lvb_upd_work); + else if (modified_lvb) + queue_work(pr_dlm->upd_wq, &pr_dlm->reread_lvb_work); +} + +static void scst_dlm_post_ast(void *astarg) +{ +} + +/* Tell other nodes to refresh their local state from the lock value blocks. */ +static void scst_reread_lvb_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, reread_lvb_work); + dlm_lockspace_t *ls; + struct scst_lksb pr_lksb; + int res; + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (!ls) + goto unlock_ls; + memset(&pr_lksb, 0, sizeof(pr_lksb)); + res = scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_lksb, 0, PR_LOCK, + NULL); + if (res >= 0) + scst_trigger_reread_lvb(pr_dlm, ls); + if (pr_lksb.lksb.sb_lkid) + scst_dlm_unlock_wait(ls, &pr_lksb); + +unlock_ls: + mutex_unlock(&pr_dlm->ls_mutex); +} + +/* Tell other nodes to update the DLM lock value blocks. */ +static void scst_lvb_upd_work(struct work_struct *work) +{ + struct scst_pr_dlm_data *pr_dlm = container_of(work, + struct scst_pr_dlm_data, lvb_upd_work); + dlm_lockspace_t *ls; + struct scst_lksb lksb; + int res; + + mutex_lock(&pr_dlm->ls_mutex); + ls = pr_dlm->ls; + if (!ls) + goto unlock_ls; + memset(&lksb, 0, sizeof(lksb)); + res = scst_dlm_lock_wait(ls, DLM_LOCK_EX, &lksb, 0, PR_LOCK, NULL); + if (res >= 0) + scst_trigger_lvb_update(pr_dlm, ls); + if (lksb.lksb.sb_lkid) + scst_dlm_unlock_wait(ls, &lksb); + +unlock_ls: + mutex_unlock(&pr_dlm->ls_mutex); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +static struct workqueue_struct * +alloc_workqueue_backport(const char *fmt, unsigned flags, unsigned max_active, + ...) +{ + struct workqueue_struct *wq = NULL; + va_list ap; + char *name; + + va_start(ap, fmt); + name = kvasprintf(GFP_KERNEL, fmt, ap); + va_end(ap); + if (name) + wq = alloc_workqueue(name, flags, max_active); + kfree(name); + return wq; +} +#undef alloc_workqueue +#define alloc_workqueue alloc_workqueue_backport +#endif + +/* + * Caller must ensure that no commands are being executed for device @dev, + * e.g. by suspending commands before calling this function. + */ +static int scst_pr_dlm_init(struct scst_device *dev, const char *cl_dev_id) +{ + int res = -ENOMEM; + + compile_time_size_checks(); + dev->pr_dlm = kzalloc(sizeof(*dev->pr_dlm), GFP_KERNEL); + if (!dev->pr_dlm) + goto out; + dev->pr_dlm->dev = dev; + mutex_init(&dev->pr_dlm->ls_cr_mutex); + mutex_init(&dev->pr_dlm->ls_mutex); + dev->pr_dlm->data_lksb.lksb.sb_lvbptr = dev->pr_dlm->lvb; + INIT_WORK(&dev->pr_dlm->pre_join_work, scst_pre_join_work); + INIT_WORK(&dev->pr_dlm->pre_upd_work, scst_pre_upd_work); + INIT_WORK(&dev->pr_dlm->copy_from_dlm_work, scst_copy_from_dlm_work); + INIT_WORK(&dev->pr_dlm->copy_to_dlm_work, scst_copy_to_dlm_work); + INIT_WORK(&dev->pr_dlm->lvb_upd_work, scst_lvb_upd_work); + INIT_WORK(&dev->pr_dlm->reread_lvb_work, scst_reread_lvb_work); + dev->pr_dlm->latest_lscr_attempt = jiffies - 100 * HZ; + + res = -ENOMEM; + dev->pr_dlm->cl_dev_id = kstrdup(cl_dev_id, GFP_KERNEL); + if (!dev->pr_dlm->cl_dev_id) + goto err_free; + + dev->pr_dlm->from_wq = alloc_ordered_workqueue("%s_from_dlm", 0, + dev->virt_name); + if (IS_ERR(dev->pr_dlm->from_wq)) { + res = PTR_ERR(dev->pr_dlm->from_wq); + dev->pr_dlm->from_wq = NULL; + goto err_free; + } + + dev->pr_dlm->to_wq = alloc_ordered_workqueue("%s_to_dlm", 0, + dev->virt_name); + if (IS_ERR(dev->pr_dlm->to_wq)) { + res = PTR_ERR(dev->pr_dlm->to_wq); + dev->pr_dlm->to_wq = NULL; + goto err_free; + } + + dev->pr_dlm->upd_wq = alloc_ordered_workqueue("%s_upd_dlm", 0, + dev->virt_name); + if (IS_ERR(dev->pr_dlm->upd_wq)) { + res = PTR_ERR(dev->pr_dlm->upd_wq); + dev->pr_dlm->upd_wq = NULL; + goto err_free; + } + + res = 0; + +out: + return res; + +err_free: + scst_pr_dlm_cleanup(dev); + goto out; +} + +/* + * Note: The caller must ensure that get_lockspace() is not invoked + * concurrently with scst_pr_dlm_cleanup(). This can be realized by suspending + * command execution and by holding scst_mutex. The get_lockspace() callers are: + * - scst_dlm_pr_is_set(); + * - scst_dlm_pr_write_lock(); + * - scst_dlm_reserved(); + * - scst_dlm_res_lock(). + * The first three functions are invoked from command context only. The last + * function is either invoked from command context or is invoked with + * scst_mutex held (from scst_clear_reservation(), + * scst_reassign_persistent_sess_states() and scst_obtain_device_parameters()). + */ +static void scst_pr_dlm_cleanup(struct scst_device *dev) +{ + struct scst_pr_dlm_data *const pr_dlm = dev->pr_dlm; + dlm_lockspace_t *ls; + struct scst_lksb pr_lksb; + + if (!pr_dlm) + return; + ls = pr_dlm->ls; + if (ls) { + memset(&pr_lksb, 0, sizeof(pr_lksb)); + + mutex_lock(&pr_dlm->ls_mutex); + scst_dlm_lock_wait(ls, DLM_LOCK_EX, &pr_lksb, 0, PR_LOCK, NULL); + scst_dlm_remove_locks(pr_dlm, ls); + scst_dlm_unlock_wait(ls, &pr_lksb); + pr_dlm->ls = NULL; + mutex_unlock(&pr_dlm->ls_mutex); + + if (pr_dlm->from_wq) + cancel_work_sync(&pr_dlm->copy_from_dlm_work); + if (pr_dlm->to_wq) + cancel_work_sync(&pr_dlm->copy_to_dlm_work); + if (pr_dlm->upd_wq) { + cancel_work_sync(&pr_dlm->lvb_upd_work); + cancel_work_sync(&pr_dlm->reread_lvb_work); + } + release_lockspace(ls, pr_dlm->cl_dev_id); + } + if (pr_dlm->upd_wq) + destroy_workqueue(pr_dlm->upd_wq); + if (pr_dlm->to_wq) + destroy_workqueue(pr_dlm->to_wq); + if (pr_dlm->from_wq) + destroy_workqueue(pr_dlm->from_wq); + kfree(pr_dlm->nodeid); + kfree(pr_dlm->cl_dev_id); + kfree(pr_dlm); + dev->pr_dlm = NULL; +} + +const struct scst_cl_ops scst_dlm_cl_ops = { + .pr_init = scst_pr_dlm_init, + .pr_cleanup = scst_pr_dlm_cleanup, + .pr_is_set = scst_dlm_pr_is_set, + .pr_init_reg = scst_dlm_pr_init_reg, + .pr_rm_reg = scst_dlm_pr_rm_reg, + .pr_write_lock = scst_dlm_pr_write_lock, + .pr_write_unlock = scst_dlm_pr_write_unlock, + .reserved = scst_dlm_reserved, + .res_lock = scst_dlm_res_lock, + .res_unlock = scst_dlm_res_unlock, + .is_rsv_holder = scst_dlm_is_rsv_holder, + .is_not_rsv_holder = scst_dlm_is_not_rsv_holder, + .reserve = scst_dlm_reserve, +}; diff --git a/scst/src/scst_dlm.h b/scst/src/scst_dlm.h new file mode 100644 index 000000000..0e0956060 --- /dev/null +++ b/scst/src/scst_dlm.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2013 - 2014 Fusion-io, Inc. All rights reserved. + * Copyright (C) 2014 - 2015 SanDisk Corporation. + * + * Synchronization of persistent registration data with DLM lock value blocks. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * 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. + */ + +#ifndef __SCST_PRES_DLM_H +#define __SCST_PRES_DLM_H + +#include +#include +#include + +#define SCST_DLM_LOCKSPACE_PFX "scst-" + +/* + * DLM lock names + */ +#define PR_LOCK "pr" +#define PR_DATA_LOCK "pr_data" +#define PR_PRE_JOIN_LOCK "pr_pre_join_%d" +#define PR_POST_JOIN_LOCK "pr_post_join_%d" +#define PR_PRE_UPDATE_LOCK "pr_pre_%d" +#define PR_POST_UPDATE_LOCK "pr_post_%d" +#define PR_REG_LOCK "pr_reg_%02d" + +/* + * Data members needed for managing PR data via the DLM. + * + * Lock order when using the DLM (from outer to inner): + * - scst_mutex; + * - ls_cr_mutex; + * - ls_mutex; + * - PR_LOCK; + * - PR_PRE_UPDATE_LOCK, PR_POST_UPDATE_LOCK, PR_PRE_JOIN_LOCK, + * PR_POST_JOIN_LOCK; + * - PR_DATA_LOCK; + * - PR_REG_LOCK; + * - dev_pr_mutex / dev_lock. + */ +struct scst_pr_dlm_data { + /* Backpointer to the SCST device. */ + struct scst_device *dev; + + /* Lockspace name suffix. */ + const char *cl_dev_id; + + /* Mutex that protects initialization of the lockspace pointer. */ + struct mutex ls_cr_mutex; + + /* Mutex that protects the lock status blocks. */ + struct mutex ls_mutex; + + /* + * Pointer to the DLM lockspace that contains the persistent + * reservation and SPC-2 reservation data for device @dev. + */ + dlm_lockspace_t *ls; + + /* Time of the latest lockspace creation attempt. */ + unsigned long latest_lscr_attempt; + + /* Corosync node ID of the local node. */ + uint32_t local_nodeid; + + /* Number of elements in the nodeid array. */ + int participants; + + /* Corosync cluster node ID's. Protected by ls_mutex. */ + uint32_t *nodeid; + + /* Workqueue for copy_from_dlm_work. */ + struct workqueue_struct *from_wq; + /* Workqueue for copy_to_dlm_work. */ + struct workqueue_struct *to_wq; + /* Workqueue for lvb_upd_work. */ + struct workqueue_struct *upd_wq; + + struct work_struct pre_join_work; + struct work_struct pre_upd_work; + struct work_struct copy_from_dlm_work; + struct work_struct copy_to_dlm_work; + struct work_struct lvb_upd_work; + struct work_struct reread_lvb_work; + + /* + * DLM lock IDs of the locks used for persistent reservation data and + * the associated notification protocol. + */ + struct scst_lksb pre_join_lksb; + struct scst_lksb post_join_lksb; + struct scst_lksb data_lksb; + struct scst_lksb pre_upd_lksb; + struct scst_lksb post_upd_lksb; + + /* PR_DATA_LOCK LVB. */ + uint8_t lvb[PR_DLM_LVB_LEN]; + + /* SPC-2 reservation state information. */ + uint32_t reserved_by_nodeid; +}; + +/** + * struct pr_lvb - PR_DATA_LOCK LVB data format + * @nr_registrants: number of reservation keys that have been registered + * @pr_generation: persistent reservation generation + * @version: version of this structure + * @pr_is_set: whether the device has been reserved persistently + * @pr_type: persistent reservation type + * @pr_scope: persistent reservation scope + * @pr_aptpl: persistent reservation APTPL + * @reserved_by_nodeid: Corosync node ID of the node holding an SPC-2 + * reservation. Zero if no SPC-2 reservation is held. + */ +struct pr_lvb { + __be32 nr_registrants; + __be32 pr_generation; + u8 version; + u8 pr_is_set; + u8 pr_type; + u8 pr_scope; + u8 pr_aptpl; + u8 reserved[3]; + __be32 reserved_by_nodeid; +}; + +/** + * struct pr_reg_lvb - PR_REG_LOCK LVB data format + * @key: reservation key + * @rel_tgt_id: relative target id + * @version: version of this structure + * @is_holder: whether or not holding the reservation + * @tid: transport ID - up to 228 bytes for iSCSI + */ +struct pr_reg_lvb { + __be64 key; + __be16 rel_tgt_id; + u8 version; + u8 is_holder; + u8 tid[228]; +}; + +#endif /* __SCST_PRES_DLM_H */ diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 9f7118c73..f9f8139d5 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -6090,17 +6090,18 @@ out: static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev) { struct scst_device *dev = tgt_dev->dev; + struct scst_lksb pr_lksb; int release = 0; TRACE_ENTRY(); - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &pr_lksb); if (scst_is_reservation_holder(dev, tgt_dev->sess)) { /* This is one who holds the reservation */ scst_clear_dev_reservation(dev); release = 1; } - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); if (release) scst_send_release(dev); @@ -11729,11 +11730,12 @@ static bool __scst_dev_check_set_UA(struct scst_device *dev, void scst_dev_check_set_UA(struct scst_device *dev, struct scst_cmd *exclude, const uint8_t *sense, int sense_len) { + struct scst_lksb lksb; bool rc; - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &lksb); rc = __scst_dev_check_set_UA(dev, exclude, sense, sense_len); - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &lksb); if (rc) scst_unblock_aborted_cmds(NULL, NULL, dev, false); @@ -12480,6 +12482,7 @@ void scst_reassign_retained_sess_states(struct scst_session *new_sess, list_for_each_entry(dev, &scst_dev_list, dev_list_entry) { struct scst_tgt_dev *tgt_dev; struct scst_tgt_dev *new_tgt_dev = NULL, *old_tgt_dev = NULL; + struct scst_lksb pr_lksb; TRACE_DBG("Processing dev %s", dev->virt_name); @@ -12506,11 +12509,13 @@ void scst_reassign_retained_sess_states(struct scst_session *new_sess, /** Reassign regular reservations **/ + scst_res_lock(dev, &pr_lksb); if (scst_is_reservation_holder(dev, old_sess)) { scst_reserve_dev(dev, new_sess); TRACE_DBG("Reservation reassigned from old_tgt_dev %p " "to new_tgt_dev %p", old_tgt_dev, new_tgt_dev); } + scst_res_unlock(dev, &pr_lksb); /** Reassign PRs **/ diff --git a/scst/src/scst_no_dlm.c b/scst/src/scst_no_dlm.c new file mode 100644 index 000000000..2c6d639d4 --- /dev/null +++ b/scst/src/scst_no_dlm.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2013 - 2014 Fusion-io, Inc. All rights reserved. + * Copyright (C) 2014 - 2015 SanDisk Corporation. + * + * Synchronization framework of persistent registration data without DLM lock. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * 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. + */ + +#ifdef INSIDE_KERNEL_TREE +#include +#include +#else +#include "scst.h" +#include "scst_const.h" +#endif +#include "scst_priv.h" +#include "scst_pres.h" + +static int scst_no_dlm_pr_init(struct scst_device *dev, const char *cl_dev_id) +{ + return 0; +} + +static void scst_no_dlm_pr_cleanup(struct scst_device *dev) +{ +} + +static bool scst_no_dlm_pr_is_set(struct scst_device *dev) +{ + return dev->pr_is_set; +} + +static void scst_no_dlm_pr_init_reg(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ +} + +static void scst_no_dlm_pr_rm_reg(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ +} + +static void scst_no_dlm_pr_write_lock(struct scst_device *dev, + struct scst_lksb *pr_lksb) +{ + scst_pr_write_lock(dev); +} + +static void scst_no_dlm_pr_write_unlock(struct scst_device *dev, + struct scst_lksb *pr_lksb) +{ + scst_pr_write_unlock(dev); +} + +static bool scst_no_dlm_reserved(struct scst_device *dev) +{ + return dev->reserved_by; +} + +static void scst_no_dlm_res_lock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __acquires(&dev->dev_lock) +{ + EXTRACHECKS_BUG_ON(in_irq() || irqs_disabled()); + spin_lock_bh(&dev->dev_lock); +} + +static void scst_no_dlm_res_unlock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __releases(&dev->dev_lock) +{ + spin_unlock_bh(&dev->dev_lock); +} + +static bool scst_no_dlm_is_rsv_holder(struct scst_device *dev, + struct scst_session *sess) +{ + EXTRACHECKS_BUG_ON(sess == NULL); + return dev->reserved_by == sess; +} + +static bool scst_no_dlm_is_not_rsv_holder(struct scst_device *dev, + struct scst_session *sess) +{ + EXTRACHECKS_BUG_ON(sess == NULL); + return dev->reserved_by && dev->reserved_by != sess; +} + +static void scst_no_dlm_reserve(struct scst_device *dev, + struct scst_session *sess) +{ + dev->reserved_by = sess; +} + +const struct scst_cl_ops scst_no_dlm_cl_ops = { + .pr_init = scst_no_dlm_pr_init, + .pr_cleanup = scst_no_dlm_pr_cleanup, + .pr_is_set = scst_no_dlm_pr_is_set, + .pr_init_reg = scst_no_dlm_pr_init_reg, + .pr_rm_reg = scst_no_dlm_pr_rm_reg, + .pr_write_lock = scst_no_dlm_pr_write_lock, + .pr_write_unlock = scst_no_dlm_pr_write_unlock, + .reserved = scst_no_dlm_reserved, + .res_lock = scst_no_dlm_res_lock, + .res_unlock = scst_no_dlm_res_unlock, + .is_rsv_holder = scst_no_dlm_is_rsv_holder, + .is_not_rsv_holder = scst_no_dlm_is_not_rsv_holder, + .reserve = scst_no_dlm_reserve, +}; diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index 6e6fcad9d..acc04481d 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -88,7 +88,7 @@ static inline void scst_assert_pr_mutex_held(struct scst_device *dev) } #endif -static inline int scst_tid_size(const uint8_t *tid) +int scst_tid_size(const uint8_t *tid) { sBUG_ON(tid == NULL); @@ -185,7 +185,7 @@ out_error: } /* Must be called under dev_pr_mutex */ -static inline void scst_pr_set_holder(struct scst_device *dev, +void scst_pr_set_holder(struct scst_device *dev, struct scst_dev_registrant *holder, uint8_t scope, uint8_t type) { scst_assert_pr_mutex_held(dev); @@ -336,7 +336,7 @@ static void scst_pr_find_registrants_list_key(struct scst_device *dev, } /* dev_pr_mutex must be locked */ -static struct scst_dev_registrant *scst_pr_find_reg( +struct scst_dev_registrant *scst_pr_find_reg( struct scst_device *dev, const uint8_t *transport_id, const uint16_t rel_tgt_id) { @@ -379,7 +379,7 @@ static void scst_pr_clear_reservation(struct scst_device *dev) } /* Must be called under dev_pr_mutex */ -static void scst_pr_clear_holder(struct scst_device *dev) +void scst_pr_clear_holder(struct scst_device *dev) { TRACE_ENTRY(); @@ -401,7 +401,7 @@ static void scst_pr_clear_holder(struct scst_device *dev) } /* Must be called under dev_pr_mutex */ -static struct scst_dev_registrant *scst_pr_add_registrant( +struct scst_dev_registrant *scst_pr_add_registrant( struct scst_device *dev, const uint8_t *transport_id, const uint16_t rel_tgt_id, __be64 key, bool dev_lock_locked) @@ -441,6 +441,8 @@ static struct scst_dev_registrant *scst_pr_add_registrant( goto out; } + dev->cl_ops->pr_init_reg(dev, reg); + reg->transport_id = kmemdup(transport_id, scst_tid_size(transport_id), gfp_flags); if (reg->transport_id == NULL) { @@ -496,7 +498,7 @@ out_free: } /* Must be called under dev_pr_mutex */ -static void scst_pr_remove_registrant(struct scst_device *dev, +void scst_pr_remove_registrant(struct scst_device *dev, struct scst_dev_registrant *reg) { TRACE_ENTRY(); @@ -510,6 +512,8 @@ static void scst_pr_remove_registrant(struct scst_device *dev, list_del(®->dev_registrants_list_entry); + dev->cl_ops->pr_rm_reg(dev, reg); + if (scst_pr_is_holder(dev, reg)) scst_pr_clear_holder(dev); @@ -1142,6 +1146,7 @@ out: int scst_pr_init(struct scst_device *dev) { mutex_init(&dev->dev_pr_mutex); + dev->cl_ops = &scst_no_dlm_cl_ops; dev->pr_generation = 0; dev->pr_is_set = 0; dev->pr_holder = NULL; @@ -1155,8 +1160,41 @@ int scst_pr_init(struct scst_device *dev) /* Free the resources allocated by scst_pr_init(). */ void scst_pr_cleanup(struct scst_device *dev) { + dev->cl_ops->pr_cleanup(dev); } +/* Caller must hold scst_mutex and activity must be suspended. */ +int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode, + const char *cl_dev_id) +{ + bool cluster_mode_enabled = false; + int res = 0; + +#if defined(CONFIG_DLM) || defined(CONFIG_DLM_MODULE) + cluster_mode_enabled = dev->cl_ops == &scst_dlm_cl_ops; + + if (cluster_mode_enabled == cluster_mode) + goto out; + + PRINT_INFO("%s: changing cluster_mode from %d into %d", dev->virt_name, + cluster_mode_enabled, cluster_mode); + dev->cl_ops->pr_cleanup(dev); + dev->cl_ops = cluster_mode ? &scst_dlm_cl_ops : &scst_no_dlm_cl_ops; + res = dev->cl_ops->pr_init(dev, cl_dev_id); + if (res) { + PRINT_ERROR("%s: changing cluster_mode into %d failed: %d", + dev->virt_name, cluster_mode, res); + dev->cl_ops = &scst_no_dlm_cl_ops; + } +#else + res = cluster_mode ? -ENOTSUPP : 0; +#endif + +out: + return res; +} +EXPORT_SYMBOL(scst_pr_set_cluster_mode); + /* Must be called under dev_pr_mutex or before dev is on the device list. */ int scst_pr_init_dev(struct scst_device *dev) { diff --git a/scst/src/scst_pres.h b/scst/src/scst_pres.h index 3cbecacc5..2decf5b4c 100644 --- a/scst/src/scst_pres.h +++ b/scst/src/scst_pres.h @@ -79,6 +79,11 @@ static inline void scst_pr_read_unlock(struct scst_device *dev) mutex_unlock(&dev->dev_pr_mutex); } +static inline void lockdep_assert_pr_read_lock_held(struct scst_device *dev) +{ + lockdep_assert_held(&dev->dev_pr_mutex); +} + static inline void scst_pr_write_lock(struct scst_device *dev) { mutex_lock(&dev->dev_pr_mutex); @@ -89,6 +94,11 @@ static inline void scst_pr_write_unlock(struct scst_device *dev) mutex_unlock(&dev->dev_pr_mutex); } +static inline void lockdep_assert_pr_write_lock_held(struct scst_device *dev) +{ + lockdep_assert_held(&dev->dev_pr_mutex); +} + int scst_pr_set_file_name(struct scst_device *dev, char **prev, const char *fmt, ...) __printf(3, 4); @@ -120,6 +130,21 @@ void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +int scst_tid_size(const uint8_t *tid); +struct scst_dev_registrant *scst_pr_find_reg(struct scst_device *dev, + const uint8_t *transport_id, const uint16_t rel_tgt_id); +struct scst_dev_registrant *scst_pr_add_registrant(struct scst_device *dev, + const uint8_t *transport_id, + const uint16_t rel_tgt_id, + __be64 key, + bool dev_lock_locked); +void scst_pr_remove_registrant(struct scst_device *dev, + struct scst_dev_registrant *reg); +void scst_pr_set_holder(struct scst_device *dev, + struct scst_dev_registrant *holder, uint8_t scope, + uint8_t type); +void scst_pr_clear_holder(struct scst_device *dev); + #ifndef CONFIG_SCST_PROC void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd); #endif diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 33056bd41..7d4ef5b33 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -170,6 +170,9 @@ extern struct list_head scst_dev_type_list; extern struct list_head scst_virtual_dev_type_list; extern wait_queue_head_t scst_dev_cmd_waitQ; +extern const struct scst_cl_ops scst_no_dlm_cl_ops; +extern const struct scst_cl_ops scst_dlm_cl_ops; + #ifdef CONFIG_SCST_PROC extern struct list_head scst_acg_list; extern struct scst_acg *scst_default_acg; @@ -398,6 +401,19 @@ void scst_free_session_callback(struct scst_session *sess); void scst_check_retries(struct scst_tgt *tgt); +static inline int scst_dlm_new_lockspace(const char *name, int namelen, + dlm_lockspace_t **lockspace, + uint32_t flags, + int lvblen) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + return dlm_new_lockspace(name, namelen, lockspace, flags, lvblen); +#else + return dlm_new_lockspace(name, NULL, flags, lvblen, NULL, NULL, NULL, + lockspace); +#endif +} + #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) static inline int scst_exec_req(struct scsi_device *sdev, const unsigned char *cmd, int cmd_len, int data_direction, @@ -599,7 +615,23 @@ void scst_acn_sysfs_del(struct scst_acn *acn); */ static inline bool scst_dev_reserved(struct scst_device *dev) { - return dev->reserved_by; + return dev->cl_ops->reserved(dev); +} + + +/* Protect SPC-2 reservation state against concurrent modifications. */ +static inline void scst_res_lock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __acquires(&dev->dev_lock) +{ + dev->cl_ops->res_lock(dev, pr_lksb); +} + +static inline void scst_res_unlock(struct scst_device *dev, + struct scst_lksb *pr_lksb) + __releases(&dev->dev_lock) +{ + dev->cl_ops->res_unlock(dev, pr_lksb); } /* @@ -610,7 +642,7 @@ static inline bool scst_is_reservation_holder(struct scst_device *dev, struct scst_session *sess) { EXTRACHECKS_BUG_ON(sess == NULL); - return dev->reserved_by == sess; + return dev->cl_ops->is_rsv_holder(dev, sess); } /* @@ -620,23 +652,22 @@ static inline bool scst_is_reservation_holder(struct scst_device *dev, static inline bool scst_is_not_reservation_holder(struct scst_device *dev, struct scst_session *sess) { - struct scst_session *reserved_by = dev->reserved_by; - EXTRACHECKS_BUG_ON(sess == NULL); - return reserved_by != NULL && reserved_by != sess; + return dev->cl_ops->is_not_rsv_holder(dev, sess); } static inline void scst_reserve_dev(struct scst_device *dev, struct scst_session *sess) { + lockdep_assert_held(&dev->dev_lock); EXTRACHECKS_BUG_ON(sess == NULL); - dev->reserved_by = sess; + dev->cl_ops->reserve(dev, sess); } static inline void scst_clear_dev_reservation(struct scst_device *dev) { lockdep_assert_held(&dev->dev_lock); - dev->reserved_by = NULL; + dev->cl_ops->reserve(dev, NULL); } void scst_tgt_dev_del_free_UA(struct scst_tgt_dev *tgt_dev, diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 7a4a033d7..614ba3312 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -3139,12 +3139,16 @@ static ssize_t scst_dev_sysfs_pr_file_name_store(struct kobject *kobj, { struct scst_sysfs_work_item *work; struct scst_device *dev; - char *pr_file_name, *p; - int res = -ENOMEM; + char *pr_file_name = NULL, *p; + int res = -EPERM; bool def = false; dev = container_of(kobj, struct scst_device, dev_kobj); + if (dev->cluster_mode) + goto out; + + res = -ENOMEM; pr_file_name = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!pr_file_name) { PRINT_ERROR("Unable to kasprintf() PR file name"); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index d0216b238..44539099b 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -2379,6 +2379,7 @@ static int scst_reserve_local(struct scst_cmd *cmd) { int res = SCST_EXEC_NOT_COMPLETED; struct scst_device *dev; + struct scst_lksb pr_lksb; TRACE_ENTRY(); @@ -2419,14 +2420,14 @@ static int scst_reserve_local(struct scst_cmd *cmd) } } - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &pr_lksb); if (scst_is_not_reservation_holder(dev, cmd->sess)) { - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); goto out_done; } scst_reserve_dev(dev, cmd->sess); - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); out: TRACE_EXIT_RES(res); @@ -2446,6 +2447,7 @@ static int scst_release_local(struct scst_cmd *cmd) { int res = SCST_EXEC_NOT_COMPLETED; struct scst_device *dev; + struct scst_lksb pr_lksb; TRACE_ENTRY(); @@ -2466,7 +2468,7 @@ static int scst_release_local(struct scst_cmd *cmd) } } - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &pr_lksb); /* * The device could be RELEASED behind us, if RESERVING session @@ -2489,7 +2491,7 @@ static int scst_release_local(struct scst_cmd *cmd) scst_clear_dev_reservation(dev); } - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); if (res == SCST_EXEC_COMPLETED) goto out_done; @@ -2616,6 +2618,7 @@ static int scst_persistent_reserve_out_local(struct scst_cmd *cmd) int action; uint8_t *buffer; int buffer_size; + struct scst_lksb pr_lksb; bool aborted = false; TRACE_ENTRY(); @@ -2651,7 +2654,7 @@ static int scst_persistent_reserve_out_local(struct scst_cmd *cmd) if (unlikely(buffer_size <= 0)) goto out_done; - scst_pr_write_lock(dev); + dev->cl_ops->pr_write_lock(dev, &pr_lksb); /* * Check if tgt_dev already registered. Also by this check we make @@ -2742,7 +2745,7 @@ static int scst_persistent_reserve_out_local(struct scst_cmd *cmd) res = SCST_EXEC_NOT_COMPLETED; out_unlock: - scst_pr_write_unlock(dev); + dev->cl_ops->pr_write_unlock(dev, &pr_lksb); scst_put_buf_full(cmd, buffer); @@ -2816,7 +2819,7 @@ int __scst_check_local_events(struct scst_cmd *cmd, bool preempt_tests_only) } if (!preempt_tests_only) { - if (dev->pr_is_set) { + if (dev->cl_ops->pr_is_set(dev)) { if (unlikely(!scst_pr_is_cmd_allowed(cmd))) { scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); @@ -5980,14 +5983,15 @@ static int scst_target_reset(struct scst_mgmt_cmd *mcmd) list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { struct scst_device *d; struct scst_tgt_dev *tgt_dev; + struct scst_lksb pr_lksb; int found = 0; dev = acg_dev->dev; - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &pr_lksb); scst_block_dev(dev); scst_process_reset(dev, mcmd->sess, NULL, mcmd, true); - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { @@ -6076,6 +6080,7 @@ static int scst_lun_reset(struct scst_mgmt_cmd *mcmd) int res, rc; struct scst_tgt_dev *tgt_dev = mcmd->mcmd_tgt_dev; struct scst_device *dev = tgt_dev->dev; + struct scst_lksb pr_lksb; TRACE_ENTRY(); @@ -6084,10 +6089,10 @@ static int scst_lun_reset(struct scst_mgmt_cmd *mcmd) mcmd->needs_unblocking = 1; - spin_lock_bh(&dev->dev_lock); + scst_res_lock(dev, &pr_lksb); scst_block_dev(dev); scst_process_reset(dev, mcmd->sess, NULL, mcmd, true); - spin_unlock_bh(&dev->dev_lock); + scst_res_unlock(dev, &pr_lksb); scst_call_dev_task_mgmt_fn_received(mcmd, tgt_dev); From 297d268a7a0d5eb3379bbfe41aabea6492027629 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:26:01 +0000 Subject: [PATCH 09/82] SCST events infrastructure Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6574 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst_event.h | 131 +++++ scst/src/Makefile | 1 + scst/src/scst_event.c | 1066 +++++++++++++++++++++++++++++++++++++ scst/src/scst_main.c | 11 +- scst/src/scst_priv.h | 8 + 5 files changed, 1216 insertions(+), 1 deletion(-) create mode 100644 scst/include/scst_event.h create mode 100644 scst/src/scst_event.c diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h new file mode 100644 index 000000000..33ab06e86 --- /dev/null +++ b/scst/include/scst_event.h @@ -0,0 +1,131 @@ +/* + * include/scst_event.h + * + * Copyright (C) 2014 - 2015 SanDisk Corporation + * + * Contains constants and data structures for scst_event module. + */ + +#ifndef __SCST_EVENT_H +#define __SCST_EVENT_H + +#ifdef __KERNEL__ +#include +#endif + +#ifdef INSIDE_KERNEL_TREE +#include +#else +#include "scst_const.h" +#endif + +#define SCST_EVENT_NAME "scst_event" +#define SCST_EVENT_PATH "/dev/" +#define SCST_EVENT_DEV SCST_EVENT_PATH SCST_EVENT_NAME +#define SCST_EVENT_VERSION_NAME SCST_VERSION_NAME +#define SCST_EVENT_VERSION \ + SCST_EVENT_VERSION_NAME "$Revision: 2454 $" SCST_CONST_VERSION + +#ifndef aligned_u64 +#define aligned_u64 uint64_t __attribute__((aligned(8))) +#endif + +/* + * Due to variable size, this structure must always be last in any outer + * structure! + */ +struct scst_event { + int32_t payload_len; + + uint32_t event_code; + + uint32_t event_id; /* ID uniquely identifying this event */ + + /* + * Event's issuer's name, i.e. SCST name of the corresponding module + * (target driver or dev handler). SCST_EVENT_SCST_CORE_ISSUER for SCST + * core. + */ + char issuer_name[SCST_MAX_NAME]; + + uint8_t payload[]; /* event's payload */ +}; + +#ifdef __KERNEL__ +typedef void (*scst_event_done_notify_fn) (struct scst_event *event, + void *priv, int status); + +struct scst_event_entry { + struct list_head events_list_entry; + scst_event_done_notify_fn event_notify_fn; + void *notify_fn_priv; + unsigned long event_timeout; /* in jiffies */ + int *pqueued_events_cnt; + union { + struct work_struct scst_event_queue_work; + struct delayed_work event_timeout_work; + }; + + struct scst_event event; + /* event's payload */ +}; +#endif + +struct scst_event_user { + int32_t max_event_size; + struct scst_event out_event; +}; + +struct scst_event_notify_done { + uint32_t event_id; + int status; +}; + +/* IOCTLs */ +#define SCST_EVENT_ALLOW_EVENT _IOW('u', 1, struct scst_event) +#define SCST_EVENT_DISALLOW_EVENT _IOW('u', 2, struct scst_event) +#define SCST_EVENT_GET_NEXT_EVENT _IOWR('u', 3, struct scst_event_user) +#define SCST_EVENT_NOTIFY_DONE _IOW('u', 4, struct scst_event_notify_done) + +#ifdef __KERNEL__ +void scst_event_queue(uint32_t event_code, const char *issuer_name, + struct scst_event_entry *e); +#endif + +/************************************************************* + ** SCST events + *************************************************************/ + +/* SCST core issuer name */ +#define SCST_EVENT_SCST_CORE_ISSUER "SCST core" + +/** SCST core's events **/ + +#define SCST_EVENT_LUN_NOT_FOUND 1 +struct scst_event_lun_not_found_payload { + aligned_u64 lun; + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; +}; + +#define SCST_EVENT_NEGATIVE_LUNS_INQUIRY 2 +struct scst_event_negative_luns_inquiry_payload { + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; +}; + +#define SCST_EVENT_TM_FN_RECEIVED 4 +struct scst_event_tm_fn_received_payload { + uint32_t fn; /* TM fn */ + aligned_u64 lun; + uint8_t device_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t session_sysfs_name[SCST_MAX_EXTERNAL_NAME]; + struct { /* if ABORT TASK, then tag and CDB of cmd to abort */ + aligned_u64 cmd_to_abort_tag; + uint8_t cdb[SCST_MAX_CDB_SIZE]; + }; +}; + +#endif /* __SCST_EVENT_H */ diff --git a/scst/src/Makefile b/scst/src/Makefile index 351bb956e..7a6604030 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -56,6 +56,7 @@ ifdef CONFIG_DLM_MODULE scst-y += scst_dlm.o endif scst-y += scst_tg.o +scst-y += scst_event.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ obj-$(BUILD_DEV) += $(DEV_HANDLERS_DIR)/ diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c new file mode 100644 index 000000000..fb108cd71 --- /dev/null +++ b/scst/src/scst_event.c @@ -0,0 +1,1066 @@ +/* + * scst_event.c + * + * Copyright (C) 2014 - 2015 SanDisk Corporation + * + */ + +#include +#include +#include +#include +#include +#include + +#ifdef INSIDE_KERNEL_TREE +#include +#include +#else +#include "scst.h" +#include "scst_event.h" +#endif + +#include "scst_priv.h" + +static struct class *scst_event_sysfs_class; + +static int scst_event_major; + +#define SCST_MAX_EVENTS 2048 +#define SCST_MAX_PAYLOAD 3*1024 +#define SCST_DEFAULT_EVENT_TIMEOUT (60*HZ) + +struct scst_event_priv { + struct list_head privs_list_entry; + int allowed_events_cnt; + int queued_events_cnt; + unsigned int going_to_exit:1; + unsigned int blocking:1; + pid_t owner_pid; + /* + * WARNING: payloads in events in the allowed list queued AS IS from the + * user space, so they are UNTRUSTED and can NOT be used in any other + * way, except as BLOBs for comparison! + */ + struct list_head allowed_events_list; + struct list_head queued_events_list; + wait_queue_head_t queued_events_waitQ; + struct list_head processing_events_list; +}; + +static DEFINE_MUTEX(scst_event_mutex); +static LIST_HEAD(scst_event_privs_list); + +/* + * Compares events e1_wild and e2, where e1_wild can have wildcard matching, + * i.e.: + * - event_code 0 - any event code + * - issuer_name "*" - any issuer name + * - payload_len 0 - any payload + */ +static bool scst_event_cmp(const struct scst_event *e1_wild, + const struct scst_event *e2) +{ + int res = false; + + TRACE_ENTRY(); + + if ((e1_wild->event_code != e2->event_code) && + (e1_wild->event_code != 0)) + goto out; + + if ((strcmp(e1_wild->issuer_name, e2->issuer_name) != 0) && + (strcmp(e1_wild->issuer_name, "*") != 0)) + goto out; + + if (e1_wild->payload_len == 0) + goto out_true; + + if ((e1_wild->payload_len != e2->payload_len) || + (memcmp(e1_wild->payload, e2->payload, e1_wild->payload_len) != 0)) + goto out; + +out_true: + res = true; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void scst_event_timeout_fn(struct delayed_work *work) +{ + struct scst_event_entry *event_entry = container_of(work, + struct scst_event_entry, event_timeout_work); + + TRACE_ENTRY(); + + TRACE_MGMT_DBG("Timeout of event %d (issuer %s, id %u, entry %p)", + event_entry->event.event_code, event_entry->event.issuer_name, + event_entry->event.event_id, event_entry); + + mutex_lock(&scst_event_mutex); + + if (list_empty(&event_entry->events_list_entry)) { + /* It's done already and about to be freed */ + mutex_unlock(&scst_event_mutex); + goto out; + } + list_del_init(&event_entry->events_list_entry); + + (*event_entry->pqueued_events_cnt)--; + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Calling notify_fn of event_entry %p", event_entry); + event_entry->event_notify_fn(&event_entry->event, + event_entry->notify_fn_priv, -ETIMEDOUT); + + TRACE_MEM("Freeing event entry %p", event_entry); + kfree(event_entry); + +out: + TRACE_EXIT(); + return; +} + +static int scst_clone_event(const struct scst_event_entry *orig_entry, + struct scst_event_entry **new_event_entry) +{ + int res = 0; + const struct scst_event *event = &orig_entry->event; + struct scst_event_entry *event_entry; + int event_entry_len = sizeof(*event_entry) + event->payload_len; + + TRACE_ENTRY(); + + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to clone event entry (size %d, event %d, " + "issuer %s", event_entry_len, event->event_code, + event->issuer_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, event_entry_len); + + memcpy(&event_entry->event, event, sizeof(*event) + event->payload_len); + + WARN_ON(orig_entry->event_notify_fn != NULL); + + *new_event_entry = event_entry; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void __scst_event_queue(struct scst_event_entry *event_entry) +{ + const struct scst_event *event = &event_entry->event; + struct scst_event_priv *priv; + struct scst_event_entry *allowed_entry = NULL; + bool queued = false; + int rc = 0; + static atomic_t base_event_id = ATOMIC_INIT(0); + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + list_for_each_entry(priv, &scst_event_privs_list, privs_list_entry) { + list_for_each_entry(allowed_entry, &priv->allowed_events_list, + events_list_entry) { + if (scst_event_cmp(&allowed_entry->event, event)) { + struct scst_event_entry *new_event_entry; + + if (priv->queued_events_cnt >= SCST_MAX_EVENTS) { + PRINT_ERROR("Too many queued events %d, " + "event %d, issuer %s is lost.", + priv->queued_events_cnt, + event->event_code, + event->issuer_name); + rc = -EMFILE; + break; + } + + if (!queued) + new_event_entry = event_entry; + else if (event_entry->event_notify_fn == NULL) { + rc = scst_clone_event(event_entry, &new_event_entry); + if (rc != 0) + goto done; + } else { + PRINT_WARNING("Event %d can be queued only once, " + "dublicated receiver pid %d will miss it!", + event->event_code, priv->owner_pid); + break; + } + + INIT_DELAYED_WORK(&new_event_entry->event_timeout_work, + (void (*)(struct work_struct *))scst_event_timeout_fn); + if (new_event_entry->event_notify_fn != NULL) { + new_event_entry->event.event_id = atomic_inc_return(&base_event_id); + if (new_event_entry->event_timeout == 0) + new_event_entry->event_timeout = SCST_DEFAULT_EVENT_TIMEOUT; + schedule_delayed_work(&new_event_entry->event_timeout_work, + new_event_entry->event_timeout); + } + + list_add_tail(&new_event_entry->events_list_entry, + &priv->queued_events_list); + priv->queued_events_cnt++; + new_event_entry->pqueued_events_cnt = &priv->queued_events_cnt; + queued = true; + + TRACE_DBG("event %d queued (issuer %s, id %u, " + "entry %p)", new_event_entry->event.event_code, + new_event_entry->event.issuer_name, + new_event_entry->event.event_id, new_event_entry); + + wake_up_all(&priv->queued_events_waitQ); + break; + } + } + } +done: + mutex_unlock(&scst_event_mutex); + + if (!queued) { + if (event_entry->event_notify_fn != NULL) { + if (rc == 0) + rc = -ENOENT; + TRACE_DBG("Calling notify_fn of event_entry %p (rc %d)", + event_entry, rc); + event_entry->event_notify_fn(&event_entry->event, + event_entry->notify_fn_priv, rc); + } + + TRACE_MEM("Freeing orphan event entry %p", event_entry); + kfree(event_entry); + } + + TRACE_EXIT(); + return; +} + +static void scst_event_queue_work_fn(struct work_struct *work) +{ + struct scst_event_entry *e = container_of(work, + struct scst_event_entry, scst_event_queue_work); + + TRACE_ENTRY(); + + __scst_event_queue(e); + + TRACE_EXIT(); + return; +} + +/* Can be called on IRQ with any lock held */ +void scst_event_queue(uint32_t event_code, const char *issuer_name, + struct scst_event_entry *e) +{ + TRACE_ENTRY(); + + INIT_WORK(&e->scst_event_queue_work, scst_event_queue_work_fn); + + TRACE_DBG("Scheduling event entry %p", e); + + e->event.event_code = event_code; + strlcpy(e->event.issuer_name, issuer_name, sizeof(e->event.issuer_name)); + + schedule_work(&e->scst_event_queue_work); + + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_event_queue); + +/* Might be called on IRQ with any lock held */ +int scst_event_queue_lun_not_found(const struct scst_cmd *cmd) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_lun_not_found_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). LUN not found " + "event is lost (LUN %lld, initiator %s, target %s)!", + event_entry_len, (unsigned long long int)cmd->lun, + cmd->sess->initiator_name, cmd->tgt->tgt_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_lun_not_found_payload *)event->payload; + + payload->lun = cmd->lun; + strlcpy(payload->initiator_name, cmd->sess->initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, cmd->tgt->tgt_name, + sizeof(payload->target_name)); + + scst_event_queue(SCST_EVENT_LUN_NOT_FOUND, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt, + const char *initiator_name) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_negative_luns_inquiry_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). NEGATIVE LUNS " + "INQUIRY event is lost (initiator %s, target %s)!", + event_entry_len, initiator_name, tgt->tgt_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_negative_luns_inquiry_payload *)event->payload; + + strlcpy(payload->initiator_name, initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, tgt->tgt_name, + sizeof(payload->target_name)); + + scst_event_queue(SCST_EVENT_NEGATIVE_LUNS_INQUIRY, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* No locks */ +int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_tm_fn_received_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_CRIT_ERROR("Unable to allocate event (size %d). External " + "TM fn received event is lost!", event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_tm_fn_received_payload *)event->payload; + + payload->fn = mcmd->fn; + payload->lun = mcmd->lun; + if (mcmd->mcmd_tgt_dev != NULL) + strlcpy(payload->device_name, mcmd->mcmd_tgt_dev->dev->virt_name, + sizeof(payload->device_name)); + strlcpy(payload->initiator_name, mcmd->sess->initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, mcmd->sess->tgt->tgt_name, + sizeof(payload->target_name)); + strlcpy(payload->session_sysfs_name, mcmd->sess->sess_name, + sizeof(payload->session_sysfs_name)); + if (mcmd->cmd_to_abort != NULL) { + payload->cmd_to_abort_tag = mcmd->cmd_to_abort->tag; + strlcpy(payload->cdb, mcmd->cmd_to_abort->cdb, + sizeof(payload->cdb)); + } + + scst_event_queue(SCST_EVENT_TM_FN_RECEIVED, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Can release/reacquire it inside */ +static void scst_release_event_entry(struct scst_event_entry *e) +{ + TRACE_ENTRY(); + + TRACE_DBG("Deleting event entry %p", e); + list_del(&e->events_list_entry); + + if (e->event_notify_fn != NULL) { + mutex_unlock(&scst_event_mutex); + + cancel_delayed_work_sync(&e->event_timeout_work); + + TRACE_DBG("Calling notify_fn of event_entry %p", e); + e->event_notify_fn(&e->event, e->notify_fn_priv, -EFAULT); + + mutex_lock(&scst_event_mutex); + } + + TRACE_MEM("Freeing notified event entry %p", e); + kfree(e); + + TRACE_EXIT(); + return; +} + +static int scst_event_release(struct inode *inode, struct file *file) +{ + struct scst_event_priv *priv; + struct scst_event_entry *e, *et; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (priv == NULL) { + mutex_unlock(&scst_event_mutex); + goto out; + } + file->private_data = NULL; + + list_del(&priv->privs_list_entry); + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Going to release event priv %p", priv); + + priv->going_to_exit = 1; + wake_up_all(&priv->queued_events_waitQ); + + list_for_each_entry_safe(e, et, &priv->allowed_events_list, + events_list_entry) { + TRACE_MEM("Deleting allowed event entry %p", e); + list_del(&e->events_list_entry); + kfree(e); + } + + mutex_lock(&scst_event_mutex); /* to sync with timeout_work */ + while (!list_empty(&priv->queued_events_list)) { + e = list_entry(priv->queued_events_list.next, + typeof(*e), events_list_entry); + scst_release_event_entry(e); + } + while (!list_empty(&priv->processing_events_list)) { + e = list_entry(priv->processing_events_list.next, + typeof(*e), events_list_entry); + scst_release_event_entry(e); + } + mutex_unlock(&scst_event_mutex); + + TRACE_MEM("Deleting priv %p", priv); + kfree(priv); + + module_put(THIS_MODULE); + +out: + TRACE_EXIT(); + return 0; +} + +/* + * scst_event_mutex supposed to be held. Caller supposed to free returned + * out_event_entry using kfree(). This function returnes event_entry, not + * plain event, because this entry can then be queued in some list. + */ +static int scst_event_get_event_from_user(void __user *arg, + struct scst_event_entry **out_event_entry) +{ + int res, rc, event_entry_len; + int32_t payload_len; + struct scst_event_entry *event_entry; + + TRACE_ENTRY(); + + res = get_user(payload_len, (int32_t __user *)arg); + if (res != 0) { + PRINT_ERROR("Failed to get payload len: %d", res); + goto out; + } + + if (payload_len > SCST_MAX_PAYLOAD) { + PRINT_ERROR("Payload len %d is too big (max %d)", payload_len, + SCST_MAX_PAYLOAD); + res = -EINVAL; + goto out; + } + + TRACE_DBG("payload_len %d", payload_len); + + event_entry_len = sizeof(*event_entry) + payload_len; + + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event entry (size %d)", + event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("Allocated event entry %p", event_entry); + + rc = copy_from_user(&event_entry->event, arg, + sizeof(event_entry->event) + payload_len); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d user's bytes", rc); + res = -EFAULT; + goto out_free; + } + + event_entry->event.issuer_name[sizeof(event_entry->event.issuer_name)-1] = '\0'; + + TRACE_DBG("user event: event_code %d, issuer_name %s", + event_entry->event.event_code, event_entry->event.issuer_name); + + *out_event_entry = event_entry; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + TRACE_MEM("Deleting event entry %p", event_entry); + kfree(event_entry); + goto out; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_allow_event(struct scst_event_priv *priv, void __user *arg) +{ + int res; + struct scst_event_entry *event_entry, *e; + + TRACE_ENTRY(); + + res = scst_event_get_event_from_user(arg, &event_entry); + if (res != 0) + goto out; + + list_for_each_entry(e, &priv->allowed_events_list, events_list_entry) { + if (scst_event_cmp(&event_entry->event, &e->event)) { + PRINT_WARNING("Allowed event (event_code %d, " + "issuer_name %s) already exists", + e->event.event_code, e->event.issuer_name); + res = -EEXIST; + goto out_free; + } + } + + if (priv->allowed_events_cnt >= SCST_MAX_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + priv->allowed_events_cnt); + res = -EMFILE; + goto out_free; + } + + list_add_tail(&event_entry->events_list_entry, + &priv->allowed_events_list); + priv->allowed_events_cnt++; + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + TRACE_MEM("Deleting event entry %p", event_entry); + kfree(event_entry); + goto out; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_disallow_event(struct scst_event_priv *priv, + void __user *arg) +{ + int res; + struct scst_event_entry *event_entry, *e, *et; + bool found = false; + + TRACE_ENTRY(); + + res = scst_event_get_event_from_user(arg, &event_entry); + if (res != 0) + goto out; + + /* For wildcard events we might delete several events */ + list_for_each_entry_safe(e, et, &priv->allowed_events_list, + events_list_entry) { + if (scst_event_cmp(&event_entry->event, &e->event)) { + PRINT_INFO("Deleting allowed event (event_code %d, " + "issuer_name %s)", e->event.event_code, + e->event.issuer_name); + TRACE_MEM("Deleting event entry %p", e); + list_del(&e->events_list_entry); + kfree(e); + priv->allowed_events_cnt--; + found = true; + } + } + if (!found) { + PRINT_WARNING("Allowed event (event_code %d, issuer_name %s) " + "not found", event_entry->event.event_code, + event_entry->event.issuer_name); + res = -ENOENT; + } else + res = 0; + + TRACE_MEM("Deleting event entry %p", e); + kfree(event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Might drop it, then get back. */ +static int scst_event_user_next_event(struct scst_event_priv *priv, + void __user *arg) +{ + int res, rc; + int32_t max_event_size, needed_size; + struct scst_event_entry *event_entry; + struct scst_event_user __user *event_user = arg; + + TRACE_ENTRY(); + + res = get_user(max_event_size, (int32_t __user *)arg); + if (res != 0) { + PRINT_ERROR("Failed to get max event size: %d", res); + goto out; + }; + + /* Waiting for at least one event, if blocking */ + while (list_empty(&priv->queued_events_list)) { + mutex_unlock(&scst_event_mutex); + wait_event_interruptible(priv->queued_events_waitQ, + (!list_empty(&priv->queued_events_list) || priv->going_to_exit || + !priv->blocking || signal_pending(current))); + mutex_lock(&scst_event_mutex); + if (priv->going_to_exit || signal_pending(current)) { + res = -EINTR; + TRACE_DBG("Signal pending or going_to_exit (%d), returning", + priv->going_to_exit); + goto out; + } else if (list_empty(&priv->queued_events_list) && !priv->blocking) { + res = -EAGAIN; + TRACE_DBG("Nothing pending, returning %d", res); + goto out; + } + } + + EXTRACHECKS_BUG_ON(list_empty(&priv->queued_events_list)); + + event_entry = list_entry(priv->queued_events_list.next, + struct scst_event_entry, events_list_entry); + + needed_size = sizeof(event_entry->event) + event_entry->event.payload_len; + + if (needed_size > max_event_size) { + TRACE_DBG("Too big event (size %d, max size %d)", needed_size, + max_event_size); + res = put_user(needed_size, (int32_t __user *)arg); + if (res == 0) + res = -ENOSPC; + goto out; + } + + rc = copy_to_user(&event_user->out_event, &event_entry->event, needed_size); + if (rc != 0) { + PRINT_ERROR("Copy to user failed (%d)", rc); + res = -EFAULT; + goto out; + } + + if (event_entry->event_notify_fn) { + TRACE_DBG("Moving event entry %p to processing events list", + event_entry); + list_move_tail(&event_entry->events_list_entry, + &priv->processing_events_list); + } else { + TRACE_MEM("Deleting event entry %p", event_entry); + list_del(&event_entry->events_list_entry); + kfree(event_entry); + } + + priv->queued_events_cnt--; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Might drop it, then get back. */ +static int scst_event_user_notify_done(struct scst_event_priv *priv, + void __user *arg) +{ + int res, rc; + struct scst_event_notify_done n; + struct scst_event_entry *e; + bool found = false; + + TRACE_ENTRY(); + + rc = copy_from_user(&n, arg, sizeof(n)); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d user's bytes of notify done", rc); + res = -EFAULT; + goto out; + } + + res = 0; + + list_for_each_entry(e, &priv->processing_events_list, events_list_entry) { + if (e->event.event_id == n.event_id) { + found = true; + break; + } + } + if (!found) { + PRINT_ERROR("Waiting event for id %u not found", n.event_id); + res = -ENOENT; + goto out; + } + + list_del_init(&e->events_list_entry); + + mutex_unlock(&scst_event_mutex); + + cancel_delayed_work_sync(&e->event_timeout_work); + + if (e->event_notify_fn != NULL) { + TRACE_DBG("Calling notify_fn of event_entry %p", e); + e->event_notify_fn(&e->event, e->notify_fn_priv, n.status); + } + + TRACE_MEM("Freeing event entry %p", e); + kfree(e); + + /* Lock it back, because we expected to do so */ + mutex_lock(&scst_event_mutex); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_create_priv(struct file *file) +{ + int res; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(file->private_data != NULL); + + if (!try_module_get(THIS_MODULE)) { + PRINT_ERROR("Fail to get module"); + res = -ETXTBSY; + goto out; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + PRINT_ERROR("Unable to allocate priv (size %zd)", + sizeof(*priv)); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("priv %p allocated", priv); + + priv->owner_pid = current->pid; + INIT_LIST_HEAD(&priv->allowed_events_list); + init_waitqueue_head(&priv->queued_events_waitQ); + INIT_LIST_HEAD(&priv->queued_events_list); + INIT_LIST_HEAD(&priv->processing_events_list); + if (file->f_flags & O_NONBLOCK) { + TRACE_DBG("%s", "Non-blocking operations"); + priv->blocking = 0; + } else + priv->blocking = 1; + + list_add_tail(&priv->privs_list_entry, &scst_event_privs_list); + + file->private_data = priv; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static long scst_event_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long res; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (unlikely(priv == NULL)) { + /* This is the first time we are here */ + res = scst_event_create_priv(file); + if (res != 0) + goto out_unlock; + priv = file->private_data; + } + + TRACE_DBG("priv %p", priv); + + /* + * Handler functions called under scst_event_mutex for their + * convenience only, because they would need to reacquire it back again + * anyway. It has nothing common with protecting private_data, which + * protected from release() by the file reference counting. + */ + + switch (cmd) { + case SCST_EVENT_ALLOW_EVENT: + TRACE_DBG("%s", "ALLOW_EVENT"); + res = scst_event_allow_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_DISALLOW_EVENT: + TRACE_DBG("%s", "DISALLOW_EVENT"); + res = scst_event_disallow_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_GET_NEXT_EVENT: + TRACE_DBG("%s", "GET_NEXT_EVENT"); + res = scst_event_user_next_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_NOTIFY_DONE: + TRACE_DBG("%s", "NOTIFY_DONE"); + res = scst_event_user_notify_done(priv, (void __user *)arg); + break; + + default: + PRINT_ERROR("Invalid ioctl cmd %x", cmd); + res = -EINVAL; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&scst_event_mutex); + + TRACE_EXIT_RES(res); + return res; +} + +static unsigned int scst_event_poll(struct file *file, poll_table *wait) +{ + int res = 0; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (unlikely(priv == NULL)) { + PRINT_ERROR("At least one allowed event must be set"); + res = -EINVAL; + goto out_unlock; + } + + if (!list_empty(&priv->queued_events_list)) { + res |= POLLIN | POLLRDNORM; + goto out_unlock; + } + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Before poll_wait() (priv %p)", priv); + poll_wait(file, &priv->queued_events_waitQ, wait); + TRACE_DBG("After poll_wait() (priv %p)", priv); + + mutex_lock(&scst_event_mutex); + + if (!list_empty(&priv->queued_events_list)) { + res |= POLLIN | POLLRDNORM; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&scst_event_mutex); + + TRACE_EXIT_HRES(res); + return res; +} + +#if 0 +#define CONFIG_EVENTS_WAIT_TEST +#endif + +#ifdef CONFIG_EVENTS_WAIT_TEST +static void scst_event_test_notify_fn(struct scst_event *event, + void *priv, int status) +{ + TRACE_ENTRY(); + + PRINT_INFO("Notification for event %u (id %d) received with status %d " + "(priv %p)", event->event_code, event->event_id, status, + priv); + + TRACE_EXIT(); + return; +} + +static ssize_t event_wait_test_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry); + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). Test " + "event is lost!", event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event_entry->event_notify_fn = scst_event_test_notify_fn; + event_entry->event_timeout = 10*HZ; + + scst_event_queue(0x12345, SCST_EVENT_SCST_CORE_ISSUER, event_entry); + + if (res == 0) + res = count; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute event_wait_test_attr = + __ATTR(event_wait_test, S_IWUSR, NULL, event_wait_test_store); + +#endif /* #ifdef CONFIG_EVENTS_WAIT_TEST */ + +static const struct file_operations scst_event_fops = { + .poll = scst_event_poll, + .unlocked_ioctl = scst_event_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = scst_event_ioctl, +#endif + .release = scst_event_release, +}; + +int scst_event_init(void) +{ + int res = 0; + struct device *dev; + + TRACE_ENTRY(); + + scst_event_sysfs_class = class_create(THIS_MODULE, SCST_EVENT_NAME); + if (IS_ERR(scst_event_sysfs_class)) { + PRINT_ERROR("%s", "Unable create sysfs class for SCST event"); + res = PTR_ERR(scst_event_sysfs_class); + goto out; + } + + scst_event_major = register_chrdev(0, SCST_EVENT_NAME, &scst_event_fops); + if (scst_event_major < 0) { + PRINT_ERROR("register_chrdev() failed: %d", res); + res = scst_event_major; + goto out_class; + } + + dev = device_create(scst_event_sysfs_class, NULL, + MKDEV(scst_event_major, 0), + NULL, + SCST_EVENT_NAME); + if (IS_ERR(dev)) { + res = PTR_ERR(dev); + goto out_chrdev; + } + +#ifdef CONFIG_EVENTS_WAIT_TEST + sysfs_create_file(kernel_kobj, &event_wait_test_attr.attr); +#endif + +out: + TRACE_EXIT_RES(res); + return res; + + +out_chrdev: + unregister_chrdev(scst_event_major, SCST_EVENT_NAME); + +out_class: + class_destroy(scst_event_sysfs_class); + goto out; +} + +void scst_event_exit(void) +{ + TRACE_ENTRY(); + +#ifdef CONFIG_EVENTS_WAIT_TEST + sysfs_remove_file(kernel_kobj, &event_wait_test_attr.attr); +#endif + + unregister_chrdev(scst_event_major, SCST_EVENT_NAME); + + device_destroy(scst_event_sysfs_class, MKDEV(scst_event_major, 0)); + class_destroy(scst_event_sysfs_class); + + /* Wait for all pending being queued events to process */ + flush_scheduled_work(); + + TRACE_EXIT(); + return; +} diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 2f681724e..ebfb05803 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -2582,10 +2582,14 @@ static int __init init_scst(void) goto out_destroy_sense_mempool; } - res = scst_sysfs_init(); + res = scst_event_init(); if (res != 0) goto out_destroy_aen_mempool; + res = scst_sysfs_init(); + if (res != 0) + goto out_event_exit; + scst_tg_init(); if (scst_max_cmd_mem == 0) { @@ -2682,6 +2686,9 @@ out_destroy_sgv_pool: out_sysfs_cleanup: scst_sysfs_cleanup(); +out_event_exit: + scst_event_exit(); + out_destroy_aen_mempool: mempool_destroy(scst_aen_mempool); @@ -2763,6 +2770,8 @@ static void __exit exit_scst(void) scst_sysfs_cleanup(); + scst_event_exit(); + #define DEINIT_CACHEP(p) do { \ kmem_cache_destroy(p); \ p = NULL; \ diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 7d4ef5b33..0c3afadf2 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -851,6 +851,14 @@ int gen_relative_target_port_id(uint16_t *id); bool scst_is_relative_target_port_id_unique(uint16_t id, const struct scst_tgt *t); +int scst_event_init(void); +void scst_event_exit(void); + +int scst_event_queue_lun_not_found(const struct scst_cmd *cmd); +int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt, + const char *initiator_name); +int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd); + typedef void __printf(2, 3) (*scst_show_fn)(void *arg, const char *fmt, ...); void scst_trace_cmds(scst_show_fn show, void *arg); void scst_trace_mcmds(scst_show_fn show, void *arg); From 2fdbaf0fae48deff43e2fcb23870856c2f40c8f7 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:27:39 +0000 Subject: [PATCH 10/82] Devices external blocking support Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6575 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 32 +++++ scst/README_in-tree | 32 +++++ scst/include/scst.h | 32 +++++ scst/include/scst_event.h | 7 ++ scst/src/scst_event.c | 41 +++++++ scst/src/scst_lib.c | 242 ++++++++++++++++++++++++++++++++++++++ scst/src/scst_priv.h | 7 ++ scst/src/scst_sysfs.c | 129 ++++++++++++++++++++ scst/src/scst_targ.c | 8 ++ 9 files changed, 530 insertions(+) diff --git a/scst/README b/scst/README index 73331a9ec..b14a4a510 100644 --- a/scst/README +++ b/scst/README @@ -508,6 +508,8 @@ documentation for your dev handlers for more info about it as well as SysfsRules file for more info about common to all dev handlers rules. SCST dev handlers can have the following common entries: + - block - allows to temporary block and unblock this device. See below. + - exported - subdirectory containing links to all LUNs where this device was exported. @@ -531,6 +533,35 @@ SCST dev handlers can have the following common entries: - type - SCSI type of this device +Attribute "block" allows to temporary block and unblock this device. +"Blocking" means that no new commands for this device will go into the +execution stage, but instead will be suspended just before it. The +blocked state is not reached until queue of the corresponding device is +completely drained. You can also call this state "frozen". It is useful +in many cases, like consistent snapshots and graceful shutdown. + +On write "block" entry allows the following 3 types of parameters: + + - 1 - block device synchronously, i.e. don't return until this device + becomes blocked, i.e. until queue of it is not completely drained. Can + be called as many times as needed. + + - 11 params - block device asynchronously, i.e. return immediately. + Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE + event. "Params" delivered to it as is in "data" payload. Can be + called as many times as needed. Alternatively, status of blocking could be + polled by reading this attributes until the second number reaches 0 + (see below). + + - 0 - unblock this device. + +Reading from "block" entry returns two numbers separated by space: + +1. How many times this device was blocked, i.e. how many times writing +"0" to it is needed to unblock this device. + +2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1). + See below for more information about other entries of this subdirectory of the standard SCST dev handlers. @@ -1257,6 +1288,7 @@ Each vdisk_fileio's device has the following attributes in For example: /sys/kernel/scst_tgt/devices/disk1 +|-- block |-- blocksize |-- exported | |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0 diff --git a/scst/README_in-tree b/scst/README_in-tree index 14255f774..667d82709 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -367,6 +367,8 @@ documentation for your dev handlers for more info about it as well as SysfsRules file for more info about common to all dev handlers rules. SCST dev handlers can have the following common entries: + - block - allows to temporary block and unblock this device. See below. + - exported - subdirectory containing links to all LUNs where this device was exported. @@ -390,6 +392,35 @@ SCST dev handlers can have the following common entries: - type - SCSI type of this device +Attribute "block" allows to temporary block and unblock this device. +"Blocking" means that no new commands for this device will go into the +execution stage, but instead will be suspended just before it. The +blocked state is not reached until queue of the corresponding device is +completely drained. You can also call this state "frozen". It is useful +in many cases, like consistent snapshots and graceful shutdown. + +On write "block" entry allows the following 3 types of parameters: + + - 1 - block device synchronously, i.e. don't return until this device + becomes blocked, i.e. until queue of it is not completely drained. Can + be called as many times as needed. + + - 11 params - block device asynchronously, i.e. return immediately. + Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE + event. "Params" delivered to it as is in "data" payload. Can be + called as many times as needed. Alternatively, status of blocking could be + polled by reading this attributes until the second number reaches 0 + (see below). + + - 0 - unblock this device. + +Reading from "block" entry returns two numbers separated by space: + +1. How many times this device was blocked, i.e. how many times writing +"0" to it is needed to unblock this device. + +2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1). + See below for more information about other entries of this subdirectory of the standard SCST dev handlers. @@ -1110,6 +1141,7 @@ Each vdisk_fileio's device has the following attributes in For example: /sys/kernel/scst_tgt/devices/disk1 +|-- block |-- blocksize |-- exported | |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0 diff --git a/scst/include/scst.h b/scst/include/scst.h index feb76eff7..69ccd12ba 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2607,6 +2607,20 @@ struct scst_cl_ops { void (*reserve)(struct scst_device *dev, struct scst_session *sess); }; +/* + * Extended, e.g. via sysfs, blockers + */ +typedef void (*ext_blocker_done_fn_t) (struct scst_device *dev, + uint8_t *data, int len); + +struct scst_ext_blocker { + struct list_head ext_blockers_list_entry; + + ext_blocker_done_fn_t ext_blocker_done_fn; + int ext_blocker_data_len; + uint8_t ext_blocker_data[]; +}; + /* * SCST device */ @@ -2635,6 +2649,15 @@ struct scst_device { */ unsigned int dev_unregistering:1; + /* + * Set if ext blocking is pending. It if just shortcut for + * !list_empty(&dev->ext_blockers_list) to save a cache miss. + */ + unsigned int ext_blocking_pending:1; + + /* Used to serialize invocations of __scst_ext_blocking_done() */ + unsigned int ext_unblock_scheduled:1; + /* Set if this device does not support DIF IP checking */ unsigned int dev_dif_ip_not_supported:1; @@ -2858,6 +2881,15 @@ struct scst_device { /* List of blocked commands, protected by dev_lock. */ struct list_head blocked_cmd_list; + /* Number of ext blocking requests, protected by dev_lock */ + int ext_blocks_cnt; + + /* List of ext blockers, protected by dev_lock */ + struct list_head ext_blockers_list; + + /* Work to notify ext blockers out of dev_lock context */ + struct work_struct ext_blockers_work; + /* MAXIMUM WRITE SAME LENGTH in bytes */ uint64_t max_write_same_len; diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h index 33ab06e86..f77300126 100644 --- a/scst/include/scst_event.h +++ b/scst/include/scst_event.h @@ -114,6 +114,13 @@ struct scst_event_negative_luns_inquiry_payload { uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; }; +#define SCST_EVENT_EXT_BLOCKING_DONE 3 +struct scst_event_ext_blocking_done_payload { + uint8_t device_name[SCST_MAX_EXTERNAL_NAME]; + uint32_t data_len; + uint8_t data[]; +}; + #define SCST_EVENT_TM_FN_RECEIVED 4 struct scst_event_tm_fn_received_payload { uint32_t fn; /* TM fn */ diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c index fb108cd71..15aad6a6d 100644 --- a/scst/src/scst_event.c +++ b/scst/src/scst_event.c @@ -362,6 +362,47 @@ out: return res; } +/* No locks */ +int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len) +{ + int res, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_ext_blocking_done_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload) + len; + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_CRIT_ERROR("Unable to allocate event. Ext blocking " + "done event is lost (device %s, size %zd)!", dev->virt_name, + sizeof(*event_entry) + sizeof(*payload) + len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload) + len; + payload = (struct scst_event_ext_blocking_done_payload *)event->payload; + + strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name)); + if (len > 0) + memcpy(payload->data, data, len); + + scst_event_queue(SCST_EVENT_EXT_BLOCKING_DONE, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + /* No locks */ int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd) { diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index f9f8139d5..4da51033a 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -3932,6 +3932,8 @@ static void scst_init_order_data(struct scst_order_data *order_data) return; } +static void scst_ext_blocking_done_fn(struct work_struct *work); + static int scst_dif_none(struct scst_cmd *cmd); #ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS static int scst_dif_none_type1(struct scst_cmd *cmd); @@ -3963,6 +3965,8 @@ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) INIT_LIST_HEAD(&dev->blocked_cmd_list); INIT_LIST_HEAD(&dev->dev_tgt_dev_list); INIT_LIST_HEAD(&dev->dev_acg_dev_list); + INIT_LIST_HEAD(&dev->ext_blockers_list); + INIT_WORK(&dev->ext_blockers_work, scst_ext_blocking_done_fn); dev->dev_double_ua_possible = 1; dev->queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER; @@ -3997,6 +4001,9 @@ void scst_free_device(struct scst_device *dev) } #endif + /* Ensure that ext_blockers_work is done */ + flush_scheduled_work(); + scst_deinit_threads(&dev->dev_cmd_threads); scst_pr_cleanup(dev); @@ -13111,6 +13118,241 @@ out_too_many: } EXPORT_SYMBOL_GPL(scst_restore_global_mode_pages); +/* Must be called under dev_lock and BHs off. Might release it, then reacquire. */ +void __scst_ext_blocking_done(struct scst_device *dev) +{ + bool stop; + + TRACE_ENTRY(); + + TRACE_BLOCK("Notifying ext blockers for dev %s (ext_blocks_cnt %d)", + dev->virt_name, dev->ext_blocks_cnt); + + stop = list_empty(&dev->ext_blockers_list); + while (!stop) { + struct scst_ext_blocker *b; + + b = list_first_entry(&dev->ext_blockers_list, typeof(*b), + ext_blockers_list_entry); + + TRACE_DBG("Notifying async ext blocker %p (cnt %d)", b, + dev->ext_blocks_cnt); + + list_del(&b->ext_blockers_list_entry); + + stop = list_empty(&dev->ext_blockers_list); + if (stop) + dev->ext_blocking_pending = 0; + + spin_unlock_bh(&dev->dev_lock); + + b->ext_blocker_done_fn(dev, b->ext_blocker_data, + b->ext_blocker_data_len); + + kfree(b); + + spin_lock_bh(&dev->dev_lock); + } + + TRACE_EXIT(); + return; +} + +static void scst_ext_blocking_done_fn(struct work_struct *work) +{ + struct scst_device *dev = container_of(work, struct scst_device, + ext_blockers_work); + + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + + WARN_ON(!dev->ext_unblock_scheduled); + + /* We might have new ext blocker any time w/o dev_lock */ + while (!list_empty(&dev->ext_blockers_list)) + __scst_ext_blocking_done(dev); + + WARN_ON(!dev->ext_unblock_scheduled); + dev->ext_unblock_scheduled = 0; + + spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); + return; +} + +/* Must be called under dev_lock and BHs off */ +void scst_ext_blocking_done(struct scst_device *dev) +{ + TRACE_ENTRY(); + + lockdep_assert_held(&dev->dev_lock); + + if (dev->ext_unblock_scheduled) + goto out; + + TRACE_DBG("Scheduling ext_blockers_work for dev %s", dev->virt_name); + + dev->ext_unblock_scheduled = 1; + schedule_work(&dev->ext_blockers_work); + +out: + TRACE_EXIT(); + return; +} + +static void scst_sync_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + wait_queue_head_t *w; + + TRACE_ENTRY(); + + w = (void *)*((unsigned long *)data); + wake_up_all(w); + + TRACE_EXIT(); + return; +} + +int scst_ext_block_dev(struct scst_device *dev, bool sync, + ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len) +{ + int res; + struct scst_ext_blocker *b; + + TRACE_ENTRY(); + + if (sync) + priv_len = sizeof(void *); + + b = kzalloc(sizeof(*b) + priv_len, GFP_KERNEL); + if (b == NULL) { + PRINT_ERROR("Unable to alloc struct scst_ext_blocker with data " + "(size %zd)", sizeof(*b) + priv_len); + res = -ENOMEM; + goto out; + } + + TRACE_MGMT_DBG("New (%d) %s ext blocker %p for dev %s", dev->ext_blocks_cnt+1, + sync ? "sync" : "async", b, dev->virt_name); + + spin_lock_bh(&dev->dev_lock); + + if (dev->strictly_serialized_cmd_waiting) { + /* + * Avoid deadlock when this strictly serialized cmd + * will not proceed stopped on our blocking, so our + * blocking does not proceed as well. + */ + TRACE_DBG("Unstrictlyserialize dev %s", dev->virt_name); + dev->strictly_serialized_cmd_waiting = 0; + /* We will reuse blocking done by the strictly serialized cmd */ + } else + scst_block_dev(dev); + + dev->ext_blocks_cnt++; + TRACE_DBG("ext_blocks_cnt %d", dev->ext_blocks_cnt); + + if (sync && (dev->on_dev_cmd_count == 0)) { + TRACE_DBG("No commands to wait for sync blocking (dev %s)", + dev->virt_name); + spin_unlock_bh(&dev->dev_lock); + goto out_free_success; + } + + list_add_tail(&b->ext_blockers_list_entry, &dev->ext_blockers_list); + dev->ext_blocking_pending = 1; + + if (sync) { + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(w); + + b->ext_blocker_done_fn = scst_sync_ext_blocking_done; + *((void **)&b->ext_blocker_data[0]) = &w; + + wait_event_locked(w, (dev->on_dev_cmd_count == 0), + lock_bh, dev->dev_lock); + + spin_unlock_bh(&dev->dev_lock); + } else { + b->ext_blocker_done_fn = done_fn; + if (priv_len > 0) { + b->ext_blocker_data_len = priv_len; + memcpy(b->ext_blocker_data, priv, priv_len); + } + if (dev->on_dev_cmd_count == 0) { + TRACE_DBG("No commands to wait for async blocking " + "(dev %s)", dev->virt_name); + if (!dev->ext_unblock_scheduled) + __scst_ext_blocking_done(dev); + spin_unlock_bh(&dev->dev_lock); + } else + spin_unlock_bh(&dev->dev_lock); + } + +out_success: + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_success: + sBUG_ON(!sync); + kfree(b); + goto out_success; +} + +void scst_ext_unblock_dev(struct scst_device *dev) +{ + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + + if (dev->ext_blocks_cnt == 0) { + TRACE_DBG("Nothing to unblock (dev %p)", dev); + goto out_unlock; + } + + dev->ext_blocks_cnt--; + TRACE_MGMT_DBG("Ext unblocking for dev %s (left: %d, pending %d)", + dev->virt_name, dev->ext_blocks_cnt, + dev->ext_blocking_pending); + + if ((dev->ext_blocks_cnt == 0) && dev->ext_blocking_pending) { + int rc; + /* Wait pending ext blocking to finish */ + spin_unlock_bh(&dev->dev_lock); + TRACE_DBG("Ext unblock (dev %s): still pending...", + dev->virt_name); + rc = scst_ext_block_dev(dev, true, NULL, NULL, 0); + if (rc != 0) { + /* Oops, have to poll */ + PRINT_WARNING("scst_ext_block_dev(dev %s) failed, " + "switch to polling", dev->virt_name); + spin_lock_bh(&dev->dev_lock); + while (dev->ext_blocking_pending) { + spin_unlock_bh(&dev->dev_lock); + msleep(10); + spin_lock_bh(&dev->dev_lock); + } + spin_unlock_bh(&dev->dev_lock); + } else { + TRACE_DBG("Ext unblock: pending done, unblocking..."); + scst_ext_unblock_dev(dev); + } + spin_lock_bh(&dev->dev_lock); + } + + scst_unblock_dev(dev); + +out_unlock: + spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); + return; +} /* Abstract vfs_unlink() for different kernel versions (as possible) */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 0c3afadf2..308c9d9f8 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -715,6 +715,12 @@ void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev, void scst_block_dev(struct scst_device *dev); void scst_unblock_dev(struct scst_device *dev); +int scst_ext_block_dev(struct scst_device *dev, bool sync, + ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len); +void scst_ext_unblock_dev(struct scst_device *dev); +void __scst_ext_blocking_done(struct scst_device *dev); +void scst_ext_blocking_done(struct scst_device *dev); + bool __scst_check_blocked_dev(struct scst_cmd *cmd); /* @@ -857,6 +863,7 @@ void scst_event_exit(void); int scst_event_queue_lun_not_found(const struct scst_cmd *cmd); int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt, const char *initiator_name); +int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len); int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd); typedef void __printf(2, 3) (*scst_show_fn)(void *arg, const char *fmt, ...); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 614ba3312..6f406c900 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -3471,8 +3471,137 @@ static struct kobj_attribute dev_threads_pool_type_attr = scst_dev_sysfs_threads_pool_type_show, scst_dev_sysfs_threads_pool_type_store); +static ssize_t scst_dev_block_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_device *dev; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + + pos = sprintf(buf, "%d %d\n", ACCESS_ONCE(dev->ext_blocks_cnt), + dev->ext_blocking_pending); + + TRACE_EXIT_RES(pos); + return pos; +} + +static void scst_sysfs_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + scst_event_queue_ext_blocking_done(dev, data, len); +} + +static ssize_t scst_dev_block_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res, data_len = 0, pos = 0; + struct scst_device *dev; + const char *p = buf, *data_start = NULL; + bool sync; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + + switch (*p) { + case '0': + p++; + pos++; + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos != count) && (*p != '\0')) { + PRINT_ERROR("Parse error on %c", *p); + res = -EINVAL; + goto out; + } + + TRACE_DBG("Sysfs unblocking (dev %s)", dev->virt_name); + + scst_ext_unblock_dev(dev); + res = 0; + goto out; + case '1': + p++; + pos++; + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos == count) || (*p == '\0')) { + data_len = sizeof(void *); + sync = true; + break; + } else if (*p != '1') { + PRINT_ERROR("Parse error on %c", *p); + res = -EINVAL; + goto out; + } + + sync = false; + + p++; + pos++; + if ((pos == count) || (*p == '\0')) + break; + + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos == count) || (*p == '\0')) + break; + + data_start = p; + while ((pos < count) && (*p != '\0')) { + p++; + pos++; + data_len++; + } + /* Skip trailing spaces, if any */ + while (isspace(*(p-1))) { + p--; + data_len--; + } + break; + default: + PRINT_ERROR("Illegal blocking value %c", *p); + res = -EINVAL; + goto out; + } + + TRACE_DBG("Sysfs blocking dev %s (sync %d, data_start %p, " + "data_len %d)", dev->virt_name, sync, data_start, data_len); + + if (sync) + res = scst_ext_block_dev(dev, true, NULL, NULL, 0); + else + res = scst_ext_block_dev(dev, false, scst_sysfs_ext_blocking_done, + data_start, data_len); + if (res != 0) + goto out; + + res = 0; + +out: + if (res == 0) + res = count; + + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute dev_block_attr = + __ATTR(block, S_IRUGO | S_IWUSR, scst_dev_block_show, + scst_dev_block_store); + static struct attribute *scst_dev_attrs[] = { &dev_type_attr.attr, + &dev_block_attr.attr, NULL, }; diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 44539099b..2c5c3e9ee 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -176,6 +176,14 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd) } } + if (unlikely(dev->ext_blocking_pending)) { + if (dev->on_dev_cmd_count == 0) { + TRACE_MGMT_DBG("Releasing pending dev %s extended " + "blocks", dev->virt_name); + scst_ext_blocking_done(dev); + } + } + spin_unlock_bh(&dev->dev_lock); return; } From e50f240d3304369978be0a6ca88f7943e7fc6321 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:28:53 +0000 Subject: [PATCH 11/82] Explicit ALUA support Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6576 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 73 +++-- scst/include/scst.h | 11 +- scst/include/scst_const.h | 7 +- scst/include/scst_event.h | 19 ++ scst/src/dev_handlers/scst_vdisk.c | 113 ++++++- scst/src/scst_event.c | 2 +- scst/src/scst_lib.c | 34 +- scst/src/scst_pres.c | 2 +- scst/src/scst_pres.h | 2 + scst/src/scst_priv.h | 2 +- scst/src/scst_sysfs.c | 2 +- scst/src/scst_tg.c | 490 ++++++++++++++++++++++++++++- 12 files changed, 695 insertions(+), 62 deletions(-) diff --git a/scst/README b/scst/README index b14a4a510..61592401e 100644 --- a/scst/README +++ b/scst/README @@ -1452,16 +1452,18 @@ persistent reservations from this device are released, upon reconnect the initiators will see it. -Implicit ALUA Support ---------------------- +ALUA Support +------------ -SCST supports implicit asymmetric logical unit access (ALUA). Implicit ALUA is -a feature defined by the ANSI T10 SCSI committee that allows a target to tell -the initiator which path to use in a multipath setup. The redundant paths -between initiator and target can be used either for redundancy or for load -sharing purposes. The target can either be a single target system running SCST -with multiple communication interfaces or two target systems each running SCST -and configured in a high availability setup. +SCST supports both implicit and explicit asymmetric logical unit access +(ALUA). ALUA is a feature defined by the ANSI T10 SCSI committee. It +allows a target to tell the initiator which path to use in a multipath +setup plus, in the explicit case, control state of each path via SET +TARGET PORT GROUPS SCSI command. The redundant paths between initiator +and target can be used either for redundancy or for load sharing +purposes. The target can either be a single target system running SCST +with multiple communication interfaces or two target systems each +running SCST and configured in a high availability setup. In the SPC-4 standard the following concepts are defined related to ALUA: * Relative target port ID. A number between 1 and 65535 that uniquely @@ -1482,17 +1484,17 @@ ANSI T10 standard called SPC-4. ALUA support in SCST .................... -SCST allows to define implicit ALUA settings for each unique combination of -SCST device and SCST target. An initiator however queries ALUA settings by -sending an appropriate SCSI command to a specific LUN of an SCST target. Each -such LUN maps uniquely to an SCST device. For hardware SCST target drivers, -e.g. ib_srpt, there is a one-to-one correspondence between SCST target and -SCSI target port. With other SCST targets, e.g. iSCSI-SCST, by default the -only relationship between SCST targets and SCSI target ports is that all SCST -targets defined on a system are visible via all SCSI target ports. See also -the iSCSI-SCST documentation about the allowed_portal attribute for -information about how to associate iSCSI targets with a single physical -interface. +SCST allows to define ALUA settings for each unique combination of SCST +device and SCST target. An initiator however queries ALUA settings by +sending an appropriate SCSI command to a specific LUN of an SCST target. +Each such LUN maps uniquely to an SCST device. For hardware SCST target +drivers, e.g. ib_srpt, there is a one-to-one correspondence between SCST +target and SCSI target port. With other SCST targets, e.g. iSCSI-SCST, +by default the only relationship between SCST targets and SCSI target +ports is that all SCST targets defined on a system are visible via all +SCSI target ports. See also the iSCSI-SCST documentation about the +allowed_portal attribute for information about how to associate iSCSI +targets with a single physical interface. Notes: - In a H.A. setup it is the responsibility of the user to synchronize ALUA @@ -1515,7 +1517,7 @@ Notes: Configuring ALUA in SCST ........................ -SCST allows to configure the following settings related to implicit ALUA +SCST allows to configure the following settings related to ALUA for each unique combination of SCST target and virtual SCST device (vdisk_fileio, vdisk_blockio, vcdrom, ...): * The target port group asymmetric access state. SCST supports all ALUA port @@ -1613,15 +1615,26 @@ DEVICE_GROUP dgroup2 { } } +Explicit ALUA +............. + +To enable explicit ALUA you need in addition to the above settings set +expl_alua device attribute to 1 (by default it is 0). Also you need to +run stpgd and supply to it path to a script or program, which will +perform actual path state switching on SET TARGET PORT GROUPS command, +for instance, by calling drbdadm. For more information see stpgd README +as well as sample script scst_on_stpg. + Checking the Target Configuration ................................. -One way to verify the implicit ALUA configuration from a Linux initiator is -via the commands provided in the sg3_utils package. The first step is to -verify whether for a certain LUN implicit ALUA has been configured on the -target. This is possible by checking whether the TPGS=1 text appears in the -sg_inq output, where /dev/sdb is a device node created by the ib_srp initiator: +One way to verify the ALUA configuration from a Linux initiator is via +the commands provided in the sg3_utils package. The first step is to +verify whether for a certain LUN ALUA has been configured on the target. +This is possible by checking whether the TPGS=1 text appears in the +sg_inq output, where /dev/sdb is a device node created by the ib_srp +initiator: # sg_inq /dev/sdb standard INQUIRY: @@ -1681,9 +1694,11 @@ Device Identification VPD page: Initiator Support ................. -On Linux systems implicit ALUA support is provided by the scsi_dh_alua kernel -driver in combination with the user space multipathd daemon. You will have to -modify at least the following in /etc/multipath.conf to enable implicit ALUA: +On Linux systems ALUA support is provided by the scsi_dh_alua kernel +driver in combination with the user space multipathd daemon. You will +have to modify at least the following in /etc/multipath.conf to enable +ALUA: + * hardware_handler "1 alua" * prio alua * path_grouping_policy group_by_prio diff --git a/scst/include/scst.h b/scst/include/scst.h index 69ccd12ba..7f1b7ebb2 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2658,6 +2658,9 @@ struct scst_device { /* Used to serialize invocations of __scst_ext_blocking_done() */ unsigned int ext_unblock_scheduled:1; + /* Set if this device was blocked during STPG command processing */ + unsigned int stpg_ext_blocked:1; + /* Set if this device does not support DIF IP checking */ unsigned int dev_dif_ip_not_supported:1; @@ -3212,6 +3215,8 @@ struct scst_acn { * @kobj: For making this object visible in sysfs. * @dev_kobj: Sysfs devices directory. * @tg_kobj: Sysfs target groups directory. + * @stpg_transport_id Initiator transport ID for STPG originating I_T nexus, if any + * @stpg_rel_tgt_id Relative target ID for STPG originating I_T nexus, if any * * Each device is member of zero or one device groups. With each device group * there are zero or more target groups associated. @@ -3224,6 +3229,8 @@ struct scst_dev_group { struct kobject kobj; struct kobject *dev_kobj; struct kobject *tg_kobj; + uint8_t *stpg_transport_id; + uint16_t stpg_rel_tgt_id; }; /** @@ -3362,7 +3369,6 @@ extern const struct scst_opcode_descriptor scst_op_descr_report_supp_opcodes; &scst_op_descr_report_supp_opcodes, \ &scst_op_descr_report_supp_tm_fns, - #ifndef smp_mb__after_set_bit /* There is no smp_mb__after_set_bit() in the kernel */ #define smp_mb__after_set_bit() smp_mb() @@ -3691,9 +3697,10 @@ static inline void scst_sess_set_tgt_priv(struct scst_session *sess, } uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt); -bool scst_impl_alua_configured(struct scst_device *dev); +bool scst_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); +int scst_tg_set_group_info(struct scst_cmd *cmd); /* * Get/set functions for dev's static DIF APP TAG diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 39a829b58..1552b8e87 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -290,9 +290,9 @@ static inline int scst_sense_response_code(const uint8_t *sense) /* NOT_READY is 2 */ #define scst_sense_format_in_progress NOT_READY, 0x04, 0x04 -#define scst_sense_tp_transitioning NOT_READY, 0x04, 0x0A -#define scst_sense_tp_standby NOT_READY, 0x04, 0x0B -#define scst_sense_tp_unav NOT_READY, 0x04, 0x0C +#define scst_sense_alua_transitioning NOT_READY, 0x04, 0x0A +#define scst_sense_alua_standby NOT_READY, 0x04, 0x0B +#define scst_sense_alua_unav NOT_READY, 0x04, 0x0C #define scst_sense_no_medium NOT_READY, 0x3a, 0 /* MEDIUM_ERROR is 3 */ @@ -301,6 +301,7 @@ static inline int scst_sense_response_code(const uint8_t *sense) /* HARDWARE_ERROR is 4 */ #define scst_sense_hardw_error HARDWARE_ERROR, 0x44, 0 /* non-retriable */ +#define scst_sense_set_target_pgs_failed HARDWARE_ERROR, 0x67, 0xA /* ILLEGAL_REQUEST is 5 */ #define scst_sense_invalid_opcode ILLEGAL_REQUEST, 0x20, 0 diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h index f77300126..c13f2b47f 100644 --- a/scst/include/scst_event.h +++ b/scst/include/scst_event.h @@ -135,4 +135,23 @@ struct scst_event_tm_fn_received_payload { }; }; +#define SCST_EVENT_STPG_USER_INVOKE 5 +struct scst_event_stpg_descr { + uint16_t group_id; + /* + * Better to keep below fields as small as possible to fit + * in single page as many descriptors as possible. + */ + uint8_t prev_state[32]; + uint8_t new_state[32]; + uint8_t dg_name[64]; + uint8_t tg_name[64]; +}; +struct scst_event_stpg_payload { + aligned_u64 stpg_cmd_tag; + uint8_t device_name[64]; + uint16_t stpg_descriptors_cnt; + struct scst_event_stpg_descr stpg_descriptors[0]; +}; + #endif /* __SCST_EVENT_H */ diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 6a5181061..be6f489c2 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -112,6 +112,7 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define DEF_REMOVABLE 0 #define DEF_ROTATIONAL 1 #define DEF_THIN_PROVISIONED 0 +#define DEF_EXPL_ALUA 0 #define VDISK_NULLIO_SIZE (5LL*1024*1024*1024*1024/2) @@ -178,6 +179,7 @@ struct scst_vdisk_dev { unsigned int tst:3; unsigned int format_active:1; unsigned int discard_zeroes_data:1; + unsigned int expl_alua:1; struct file *fd; struct file *dif_fd; @@ -322,6 +324,7 @@ static enum compl_status_e vdisk_exec_read_capacity(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_get_lba_status(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_report_tpgs(struct vdisk_cmd_params *p); +static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_request_sense(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p); @@ -382,6 +385,10 @@ static ssize_t vdisk_sysfs_tst_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdisk_sysfs_rotational_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj, @@ -474,6 +481,9 @@ static struct kobj_attribute vdisk_tst_attr = __ATTR(tst, S_IRUGO, vdisk_sysfs_tst_show, NULL); static struct kobj_attribute vdisk_rotational_attr = __ATTR(rotational, S_IRUGO, vdisk_sysfs_rotational_show, NULL); +static struct kobj_attribute vdisk_expl_alua_attr = + __ATTR(expl_alua, S_IWUSR|S_IRUGO, vdisk_sysfs_expl_alua_show, + vdisk_sysfs_expl_alua_store); static struct kobj_attribute vdisk_nv_cache_attr = __ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL); static struct kobj_attribute vdisk_o_direct_attr = @@ -543,6 +553,7 @@ static const struct attribute *vdisk_fileio_attrs[] = { &vdisk_tp_attr.attr, &vdisk_tst_attr.attr, &vdisk_rotational_attr.attr, + &vdisk_expl_alua_attr.attr, &vdisk_nv_cache_attr.attr, &vdisk_o_direct_attr.attr, &vdisk_removable_attr.attr, @@ -570,6 +581,7 @@ static const struct attribute *vdisk_blockio_attrs[] = { &vdisk_blocksize_attr.attr, &vdisk_rd_only_attr.attr, &vdisk_wt_attr.attr, + &vdisk_expl_alua_attr.attr, &vdisk_nv_cache_attr.attr, &vdisk_tst_attr.attr, &vdisk_removable_attr.attr, @@ -1874,6 +1886,17 @@ static enum compl_status_e vdisk_exec_maintenance_in(struct vdisk_cmd_params *p) return CMD_SUCCEEDED; } +static enum compl_status_e vdisk_exec_maintenance_out(struct vdisk_cmd_params *p) +{ + switch (p->cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + return vdisk_exec_set_tpgs(p); + } + scst_set_invalid_field_in_cdb(p->cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + return CMD_SUCCEEDED; +} + static enum compl_status_e vdisk_exec_send_diagnostic(struct vdisk_cmd_params *p) { return CMD_SUCCEEDED; @@ -2590,6 +2613,7 @@ static const struct scst_opcode_descriptor scst_op_descr_read_toc = { [WRITE_SAME_16] = vdisk_exec_write_same, \ [COMPARE_AND_WRITE] = vdisk_exec_caw, \ [MAINTENANCE_IN] = vdisk_exec_maintenance_in, \ + [MAINTENANCE_OUT] = vdisk_exec_maintenance_out, \ [SEND_DIAGNOSTIC] = vdisk_exec_send_diagnostic, \ [FORMAT_UNIT] = vdisk_exec_format_unit, @@ -2714,6 +2738,7 @@ static const struct scst_opcode_descriptor *vdisk_opcode_descriptors[] = { SHARED_OPCODE_DESCRIPTORS VDISK_OPCODE_DESCRIPTORS SCST_OPCODE_DESCRIPTORS + &scst_op_descr_stpg, /* must be last, see vdisk_get_supported_opcodes()! */ }; static const struct scst_opcode_descriptor *vdisk_opcode_descriptors_type2[] = { @@ -2738,6 +2763,9 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd, const struct scst_opcode_descriptor ***out_supp_opcodes, int *out_supp_opcodes_cnt) { + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + if (cmd->dev->dev_dif_type != 2) { *out_supp_opcodes = vdisk_opcode_descriptors; *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors); @@ -2745,6 +2773,10 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd, *out_supp_opcodes = vdisk_opcode_descriptors_type2; *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors_type2); } + if (!virt_dev->expl_alua) { + (*out_supp_opcodes_cnt)--; + sBUG_ON((*out_supp_opcodes)[*out_supp_opcodes_cnt]->od_serv_action != MO_SET_TARGET_PGS); + } return 0; } @@ -4091,8 +4123,11 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */ if (cmd->dev->dev_dif_mode != SCST_DIF_MODE_NONE) buf[5] |= 1; /* PROTECT */ - if (scst_impl_alua_configured(cmd->dev)) + if (scst_alua_configured(cmd->dev)) { buf[5] |= SCST_INQ_TPGS_MODE_IMPLICIT; + if (virt_dev->expl_alua) + buf[5] |= SCST_INQ_TPGS_MODE_EXPLICIT; + } buf[6] = 0x10; /* MultiP 1 */ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ @@ -5237,6 +5272,34 @@ out: return CMD_SUCCEEDED; } +/* SPC-4 SET TARGET PORT GROUPS command */ +static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + int res = CMD_SUCCEEDED, rc; + + TRACE_ENTRY(); + + if (!virt_dev->expl_alua) { + PRINT_ERROR("SET TARGET PORT GROUPS: not explicit ALUA mode " + "(dev %s)", dev->virt_name); + /* Invalid opcode, i.e. SA field */ + scst_set_invalid_field_in_cdb(cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + goto out; + } + + rc = scst_tg_set_group_info(cmd); + if (rc == 0) + res = RUNNING_ASYNC; + +out: + TRACE_EXIT_RES(res); + return res; +} + static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; @@ -7462,6 +7525,7 @@ static int vdev_create(struct scst_dev_type *devt, virt_dev->thin_provisioned = DEF_THIN_PROVISIONED; virt_dev->tst = DEF_TST; virt_dev->caw_len_lim = DEF_CAW_LEN_LIM; + virt_dev->expl_alua = DEF_EXPL_ALUA; virt_dev->blk_shift = DEF_DISK_BLOCK_SHIFT; @@ -8574,6 +8638,53 @@ static ssize_t vdisk_sysfs_tp_show(struct kobject *kobj, return pos; } +static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_device *dev; + struct scst_vdisk_dev *virt_dev; + int pos; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + virt_dev = dev->dh_priv; + pos = sprintf(buf, "%d\n%s", virt_dev->expl_alua, + virt_dev->expl_alua != DEF_EXPL_ALUA ? + SCST_SYSFS_KEY_MARK "\n" : ""); + + TRACE_EXIT_RES(pos); + return pos; +} + +static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct scst_device *dev; + struct scst_vdisk_dev *virt_dev; + char ch[16]; + bool expl_alua; + int res; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + virt_dev = dev->dh_priv; + sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); + expl_alua = !!simple_strtoul(ch, NULL, 0); + + spin_lock(&virt_dev->flags_lock); + virt_dev->expl_alua = expl_alua; + spin_unlock(&virt_dev->flags_lock); + + res = count; + + TRACE_EXIT_RES(res); + return res; +} + static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c index 15aad6a6d..52cec1121 100644 --- a/scst/src/scst_event.c +++ b/scst/src/scst_event.c @@ -160,7 +160,7 @@ static void __scst_event_queue(struct scst_event_entry *event_entry) { const struct scst_event *event = &event_entry->event; struct scst_event_priv *priv; - struct scst_event_entry *allowed_entry = NULL; + struct scst_event_entry *allowed_entry; bool queued = false; int rc = 0; static atomic_t base_event_id = ATOMIC_INIT(0); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 4da51033a..a707c5129 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -248,7 +248,7 @@ const struct scst_opcode_descriptor scst_op_descr_stpg = { .od_cdb_size = 12, .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, .od_recommended_timeout = SCST_GENERIC_DISK_SMALL_TIMEOUT/HZ, - .od_cdb_usage_bits = { MAINTENANCE_IN, MO_SET_TARGET_PGS, 0, 0, 0, 0, + .od_cdb_usage_bits = { MAINTENANCE_OUT, MO_SET_TARGET_PGS, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0, SCST_OD_DEFAULT_CONTROL_BYTE }, }; EXPORT_SYMBOL(scst_op_descr_stpg); @@ -453,6 +453,8 @@ static int get_cdb_info_apt(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_min(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_mo(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_var_len(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); @@ -1410,12 +1412,12 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = FLAG_NONE, .info_len_off = 8, .info_len_len = 2, .get_cdb_info = get_cdb_info_len_2}, - {.ops = 0xA4, .devkey = " O ", + {.ops = 0xA4, .devkey = "OOO O OOOO MO O", .info_op_name = "MAINTENANCE(OUT)", .info_data_direction = SCST_DATA_WRITE, .info_op_flags = FLAG_NONE, .info_len_off = 6, .info_len_len = 4, - .get_cdb_info = get_cdb_info_len_4}, + .get_cdb_info = get_cdb_info_mo}, {.ops = 0xA5, .devkey = " M ", .info_op_name = "MOVE MEDIUM", .info_data_direction = SCST_DATA_NONE, @@ -10518,6 +10520,20 @@ static int get_cdb_info_min(struct scst_cmd *cmd, return get_cdb_info_len_4(cmd, sdbops); } +/* Parse MAINTENANCE (OUT) */ +static int get_cdb_info_mo(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + cmd->op_name = "SET TARGET PORT GROUPS"; + cmd->op_flags |= SCST_STRICTLY_SERIALIZED; + break; + } + + return get_cdb_info_len_4(cmd, sdbops); +} + /** * scst_get_cdb_info() - fill various info about the command's CDB * @@ -13304,14 +13320,20 @@ out_free_success: goto out_success; } -void scst_ext_unblock_dev(struct scst_device *dev) +void scst_ext_unblock_dev(struct scst_device *dev, bool stpg) { TRACE_ENTRY(); spin_lock_bh(&dev->dev_lock); if (dev->ext_blocks_cnt == 0) { - TRACE_DBG("Nothing to unblock (dev %p)", dev); + TRACE_DBG("Nothing to unblock (dev %s)", dev->virt_name); + goto out_unlock; + } + + if ((dev->ext_blocks_cnt == 1) && dev->stpg_ext_blocked && !stpg) { + TRACE_DBG("Can not unblock internal STPG ext block (dev %s)", + dev->virt_name); goto out_unlock; } @@ -13340,7 +13362,7 @@ void scst_ext_unblock_dev(struct scst_device *dev) spin_unlock_bh(&dev->dev_lock); } else { TRACE_DBG("Ext unblock: pending done, unblocking..."); - scst_ext_unblock_dev(dev); + scst_ext_unblock_dev(dev, stpg); } spin_lock_bh(&dev->dev_lock); } diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index acc04481d..037c0d462 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -110,7 +110,7 @@ static inline void tid_secure(uint8_t *tid) } /* Returns false if tid's are not equal, true otherwise */ -static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) +bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) { int len; diff --git a/scst/src/scst_pres.h b/scst/src/scst_pres.h index 2decf5b4c..5ee98a3ef 100644 --- a/scst/src/scst_pres.h +++ b/scst/src/scst_pres.h @@ -131,6 +131,8 @@ void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); int scst_tid_size(const uint8_t *tid); +bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b); + struct scst_dev_registrant *scst_pr_find_reg(struct scst_device *dev, const uint8_t *transport_id, const uint16_t rel_tgt_id); struct scst_dev_registrant *scst_pr_add_registrant(struct scst_device *dev, diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 308c9d9f8..17462a38a 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -717,7 +717,7 @@ void scst_unblock_dev(struct scst_device *dev); int scst_ext_block_dev(struct scst_device *dev, bool sync, ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len); -void scst_ext_unblock_dev(struct scst_device *dev); +void scst_ext_unblock_dev(struct scst_device *dev, bool stpg); void __scst_ext_blocking_done(struct scst_device *dev); void scst_ext_blocking_done(struct scst_device *dev); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 6f406c900..cf88bea63 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -3522,7 +3522,7 @@ static ssize_t scst_dev_block_store(struct kobject *kobj, TRACE_DBG("Sysfs unblocking (dev %s)", dev->virt_name); - scst_ext_unblock_dev(dev); + scst_ext_unblock_dev(dev, false); res = 0; goto out; case '1': diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 3dc891a7a..68dc6a668 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -16,6 +16,8 @@ */ #include +#include +#include #include #ifdef INSIDE_KERNEL_TREE #include @@ -23,6 +25,8 @@ #include "scst.h" #endif #include "scst_priv.h" +#include "scst_event.h" +#include "scst_pres.h" struct alua_state_and_name { enum scst_tg_state s; @@ -127,8 +131,8 @@ static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg, } /* Look up a target group by name in the given device group. */ -static struct scst_target_group * -__lookup_tg_by_name(struct scst_dev_group *dg, const char *name) +static struct scst_target_group *__lookup_tg_by_name(struct scst_dev_group *dg, + const char *name) { struct scst_target_group *tg; @@ -141,9 +145,24 @@ __lookup_tg_by_name(struct scst_dev_group *dg, const char *name) return NULL; } +/* Look up a target group by group ID. */ +static struct scst_target_group *__lookup_tg_by_group_id(struct scst_dev_group *dg, + uint16_t group_id) +{ + struct scst_target_group *tg; + + lockdep_assert_held(&scst_mutex); + + list_for_each_entry(tg, &dg->tg_list, entry) + if (tg->group_id == group_id) + return tg; + + return NULL; +} + /* Look up a target group by target port. */ -static struct scst_target_group * -__lookup_tg_by_tgt(struct scst_dev_group *dg, const struct scst_tgt *tgt) +static struct scst_target_group *__lookup_tg_by_tgt(struct scst_dev_group *dg, + const struct scst_tgt *tgt) { struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; @@ -319,12 +338,11 @@ static bool scst_tg_accept_standby(struct scst_cmd *cmd) return true; } - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_standby)); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_standby)); return false; } - /* * Whether or not to accept a command in the ALUA unavailable state. */ @@ -333,7 +351,7 @@ static bool scst_tg_accept_unav(struct scst_cmd *cmd) bool process_cmd = scst_tg_accept(cmd); if (!process_cmd) - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_unav)); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav)); return process_cmd; } @@ -347,7 +365,7 @@ static bool scst_tg_accept_transitioning(struct scst_cmd *cmd) if (!process_cmd) scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_tp_transitioning)); + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); return process_cmd; } @@ -424,7 +442,9 @@ static void scst_tg_change_tgt_dev_state(struct scst_tgt_dev *tgt_dev, { lockdep_assert_held(&scst_dg_mutex); - TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed", tgt_dev); + TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed (gen_ua %d)", + tgt_dev, gen_ua); + scst_update_tgt_dev_alua_filter(tgt_dev, state); if (gen_ua) scst_gen_aen_or_ua(tgt_dev, @@ -724,14 +744,86 @@ out: return res; } +static void scst_event_stpg_notify_fn(struct scst_event *event, + void *priv, int status) +{ + struct scst_dev_group *dg; + struct scst_cmd *cmd = (struct scst_cmd *)priv; + struct scst_event_stpg_payload *p = + (struct scst_event_stpg_payload *)event->payload; + struct scst_event_stpg_descr *d; + struct scst_dg_dev *dgd; + int i; + + TRACE_ENTRY(); + + PRINT_INFO("Notification for event %u (id %d) received " + "with status %d (priv %p)", event->event_code, + event->event_id, status, priv); + + mutex_lock(&scst_mutex); + mutex_lock(&scst_dg_mutex); + + dg = __lookup_dg_by_dev(cmd->dev); + if (!dg) { + PRINT_ERROR("STPG: unable to find DG for device %s", + cmd->dev->virt_name); + goto out_fail; + } + + list_for_each_entry(dgd, &dg->dev_list, entry) { + if (dgd->dev->stpg_ext_blocked) { + TRACE_DBG("STPG: ext unblocking dev %s", + dgd->dev->virt_name); + scst_ext_unblock_dev(dgd->dev, true); + dgd->dev->stpg_ext_blocked = 0; + } + } + + kfree(dg->stpg_transport_id); + dg->stpg_transport_id = NULL; + + if (status != 0) { + PRINT_ERROR("on_stpg script for device group %s failed with status %d", + dg->name, status); + goto out_fail; + } + + for (i = 0, d = &p->stpg_descriptors[0]; i < p->stpg_descriptors_cnt; i++, d++) { + struct scst_target_group *tg = __lookup_tg_by_group_id(dg, d->group_id); + if (!tg) { + PRINT_ERROR("STPG: unable to find TG %d", d->group_id); + goto out_fail; + } else if (tg->state == scst_alua_name_to_state(d->prev_state)) { + PRINT_ERROR("on_stpg script did not change ALUA state" + " for device group %s / target group %s", + dg->name, tg->name); + goto out_fail; + } + } + +out_unlock: + mutex_unlock(&scst_dg_mutex); + mutex_unlock(&scst_mutex); + + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; + +out_fail: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + goto out_unlock; +} + /* * Update the ALUA filter of those LUNs (tgt_dev) whose target port is a member * of target group @tg and that export a device that is a member of the device * group @tg->dg. */ static void __scst_tg_set_state(struct scst_target_group *tg, - enum scst_tg_state state, - struct scst_tgt *no_ua_tgt) + enum scst_tg_state state) { struct scst_dg_dev *dg_dev; struct scst_device *dev; @@ -754,8 +846,12 @@ static void __scst_tg_set_state(struct scst_target_group *tg, tgt = tgt_dev->sess->tgt; list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { + bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING); + if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && + tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) + gen_ua = false; scst_tg_change_tgt_dev_state(tgt_dev, - state, tgt != no_ua_tgt); + state, gen_ua); break; } } @@ -780,7 +876,7 @@ int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state) if (res) goto out; - __scst_tg_set_state(tg, state, NULL); + __scst_tg_set_state(tg, state); mutex_unlock(&scst_dg_mutex); out: @@ -945,9 +1041,16 @@ out_free: goto out; } +/* scst_dg_mutex supposed to be locked */ static void __scst_dg_dev_remove(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) { + if (dgdev->dev->stpg_ext_blocked) { + TRACE_DBG("DG %s remove: unblocking STPG ext blocked " + "dev %s", dg->name, dgdev->dev->virt_name); + scst_ext_unblock_dev(dgdev->dev, true); + dgdev->dev->stpg_ext_blocked = 0; + } list_del(&dgdev->entry); scst_dg_dev_sysfs_del(dg, dgdev); scst_reset_dev_alua_filter(dgdev->dev); @@ -1080,7 +1183,7 @@ static void __scst_dg_remove(struct scst_dev_group *dg) list_del(&dg->entry); scst_dg_sysfs_del(dg); list_for_each_entry(tg, &dg->tg_list, entry) - __scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED, NULL); + __scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED); while (!list_empty(&dg->dev_list)) { dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev, entry); @@ -1201,10 +1304,10 @@ out_unlock: EXPORT_SYMBOL_GPL(scst_lookup_tg_id); /** - * scst_impl_alua_configured() - Whether implicit ALUA has been configured. + * scst_alua_configured() - Whether implicit ALUA has been configured. * @dev: Pointer to the SCST device to verify. */ -bool scst_impl_alua_configured(struct scst_device *dev) +bool scst_alua_configured(struct scst_device *dev) { struct scst_dev_group *dg; @@ -1214,7 +1317,7 @@ bool scst_impl_alua_configured(struct scst_device *dev) return dg != NULL; } -EXPORT_SYMBOL_GPL(scst_impl_alua_configured); +EXPORT_SYMBOL_GPL(scst_alua_configured); /** * scst_tg_get_group_info() - Build REPORT TARGET GROUPS response. @@ -1329,3 +1432,356 @@ out: return res; } EXPORT_SYMBOL_GPL(scst_tg_get_group_info); + +struct scst_stpg_wait { + atomic_t stpg_wait_left; + int status; + struct scst_dev_group *dg; + struct scst_event_entry *event_entry; +}; + +/* No locks */ +static void scst_stpg_check_blocking_done(struct scst_stpg_wait *wait) +{ + TRACE_ENTRY(); + + TRACE_DBG("wait %p, left %d", wait, atomic_read(&wait->stpg_wait_left)); + + if (atomic_dec_and_test(&wait->stpg_wait_left)) { + if (wait->status == 0) + scst_event_queue(SCST_EVENT_STPG_USER_INVOKE, + SCST_EVENT_SCST_CORE_ISSUER, wait->event_entry); + else { + wait->event_entry->event_notify_fn(&wait->event_entry->event, + wait->event_entry->notify_fn_priv, wait->status); + } + kfree(wait); + } + + TRACE_EXIT(); + return; +} + +/* No locks */ +static void scst_stpg_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + sBUG_ON(len != sizeof(data)); + scst_stpg_check_blocking_done(*((struct scst_stpg_wait **)data)); +} + +/** + * scst_tg_set_group_info - SET TARGET PORT GROUPS implementation. + * + * Returns >=0 upon success or negative error code otherwise, for instance, + * if either an invalid group ID has been specified or the group ID + * of a target group with one, or more non-local target ports has been + * specified. In the error case the cmd has its sense set. + * + * In case of returned 0 the command completed asynchronously, i.e. upon + * return might be already dead!! + */ +int scst_tg_set_group_info(struct scst_cmd *cmd) +{ + struct scst_device *dev = cmd->dev; + uint8_t *buf; + int len; + int i, j, res = 1, tpg_desc_count, valid_desc_count; + struct scst_dev_group *dg; + struct osi { + uint16_t group_id; + struct scst_target_group *tg; + enum scst_tg_state prev_state; + enum scst_tg_state new_state; + } *osi = NULL; + int event_entry_len, payload_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_stpg_payload *payload; + struct scst_event_stpg_descr *descr; + + TRACE_ENTRY(); + + len = scst_get_buf_full(cmd, &buf); + if (len < 0) { + PRINT_ERROR("scst_get_buf_full() failed: %d", len); + res = len; + if (len == -ENOMEM) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + /* + * From SPC-4: "A parameter list length of zero specifies that no data + * shall be transferred, and that no change shall be made in the + * target port asymmetric access state of any target port groups or + * target ports". + */ + if (len == 0) + goto out_put; + + tpg_desc_count = (len - 4) / 4; + /* Check for some reasonable limit */ + if (tpg_desc_count > 64) { + PRINT_ERROR("Too many STPG descriptors (%d) for dev %s", + tpg_desc_count, dev->virt_name); + res = -EINVAL; + scst_set_invalid_field_in_cdb(cmd, 6, 0); + goto out_put; + } + + TRACE_DBG("tpg_desc_count %d", tpg_desc_count); + + osi = kcalloc(tpg_desc_count, sizeof(*osi), GFP_KERNEL); + if (!osi) { + res = -ENOMEM; + scst_set_busy(cmd); + goto out_put; + } + + res = mutex_lock_interruptible(&scst_mutex); + if (res) { + PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " + "cmd %p", res, cmd); + scst_set_busy(cmd); + goto out_put; + } + + res = mutex_lock_interruptible(&scst_dg_mutex); + if (res) { + PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " + "cmd %p", res, cmd); + scst_set_busy(cmd); + goto out_unlock_sm_fail; + } + + dg = __lookup_dg_by_dev(dev); + if (!dg) { + res = -EINVAL; + goto out_unlock_fail; + } + + TRACE_DBG("dg %s (%p) found, dev %s", dg->name, dg, dev->virt_name); + + for (i = 4, j = 0; i + 4 <= len; i += 4, j++) { + WARN_ON_ONCE(j >= tpg_desc_count); + osi[j].new_state = buf[i] & 0x1f; + switch (osi[j].new_state) { + case SCST_TG_STATE_OPTIMIZED: + case SCST_TG_STATE_NONOPTIMIZED: + case SCST_TG_STATE_STANDBY: + case SCST_TG_STATE_UNAVAILABLE: + case SCST_TG_STATE_OFFLINE: + break; + default: + TRACE_MGMT_DBG("Incorrect new state %d", osi[j].new_state); + res = -EINVAL; + goto out_unlock_fail; + } + + osi[j].group_id = get_unaligned_be16(&buf[i + 2]); + if (!osi[j].group_id) { + TRACE_MGMT_DBG("Invalid group_id %d", osi[j].group_id); + res = -EINVAL; + goto out_unlock_fail; + } + + osi[j].tg = __lookup_tg_by_group_id(dg, osi[j].group_id); + if (!osi[j].tg) { + TRACE_MGMT_DBG("No TG for group_id %d", osi[j].group_id); + res = -ESRCH; + goto out_unlock_fail; + } + + if (osi[j].tg->state == SCST_TG_STATE_TRANSITIONING) { + TRACE_MGMT_DBG("TG %p is transitioning", osi[j].tg); + res = -EBUSY; + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + /* second sense will not override the set one */ + goto out_unlock_fail; + } + osi[j].prev_state = osi[j].tg->state; + + TRACE_DBG("j %d, group_id %u, tg %s (%p), state %d", j, osi[j].group_id, + osi[j].tg->name, osi[j].tg, osi[j].tg->state); + } + + mutex_unlock(&scst_dg_mutex); + mutex_unlock(&scst_mutex); + + scst_put_buf_full(cmd, buf); + + payload_len = sizeof(*payload) + sizeof(*descr) * tpg_desc_count; + event_entry_len = sizeof(*event_entry) + payload_len; + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d)", event_entry_len); + res = -ENOMEM; + scst_set_busy(cmd); + goto out_free; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + event->payload_len = payload_len; + + payload = (struct scst_event_stpg_payload *)event->payload; + payload->stpg_cmd_tag = cmd->tag; + + res = 1; + + if (strlen(dev->virt_name) >= sizeof(payload->device_name)) { + PRINT_ERROR("Device name %s too long", dev->virt_name); + goto out_too_long; + } + strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name)); + + valid_desc_count = 0; + for (j = 0, descr = &payload->stpg_descriptors[0]; j < tpg_desc_count; j++) { + if (osi[j].prev_state == osi[j].new_state) + continue; + + if (strlen(scst_alua_state_name(osi[j].prev_state)) >= sizeof(descr->prev_state)) { + PRINT_ERROR("prev state too long (%d)", osi[j].prev_state); + goto out_too_long; + } + strlcpy(descr->prev_state, scst_alua_state_name(osi[j].prev_state), + sizeof(descr->prev_state)); + + if (strlen(scst_alua_state_name(osi[j].new_state)) >= sizeof(descr->new_state)) { + PRINT_ERROR("new state too long (%d)", osi[j].new_state); + goto out_too_long; + } + strlcpy(descr->new_state, scst_alua_state_name(osi[j].new_state), + sizeof(descr->new_state)); + + if (strlen(dg->name) >= sizeof(descr->dg_name)) { + PRINT_ERROR("dg_name too long (%s)", dg->name); + goto out_too_long; + } + strlcpy(descr->dg_name, dg->name, sizeof(descr->dg_name)); + + if (strlen(osi[j].tg->name) >= sizeof(descr->tg_name)) { + PRINT_ERROR("tg_name too long (%s)", osi[j].tg->name); + goto out_too_long; + } + strlcpy(descr->tg_name, osi[j].tg->name, + sizeof(descr->tg_name)); + + descr->group_id = osi[j].group_id; + + TRACE_DBG("group_id %u, prev_state %s, new_state %s, dg_name %s, " + "tg_name %s", descr->group_id, descr->prev_state, + descr->new_state, descr->dg_name, descr->tg_name); + + valid_desc_count++; + descr++; + } + + payload->stpg_descriptors_cnt = valid_desc_count; + + if (valid_desc_count > 0) { + struct scst_dg_dev *dgd; + struct scst_stpg_wait *wait; + int rc; + + dg->stpg_rel_tgt_id = cmd->tgt->rel_tgt_id; + dg->stpg_transport_id = kmemdup(cmd->sess->transport_id, + scst_tid_size(cmd->sess->transport_id), GFP_KERNEL); + if (dg->stpg_transport_id == NULL) { + PRINT_ERROR("Unable to duplicate stpg_transport_id"); + goto out_free_event; + } + + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (wait == NULL) { + PRINT_ERROR("Unable to allocate STPG wait struct " + "(size %zd)", sizeof(*wait)); + scst_set_busy(cmd); + res = -ENOMEM; + goto out_free_tr_id; + } + + atomic_set(&wait->stpg_wait_left, 1); + wait->event_entry = event_entry; + + event_entry->event_notify_fn = scst_event_stpg_notify_fn; + event_entry->notify_fn_priv = cmd; + + mutex_lock(&scst_dg_mutex); + list_for_each_entry(dgd, &dg->dev_list, entry) { + if (dgd->dev == dev) + continue; + + TRACE_DBG("STPG: ext blocking dev %s", dgd->dev->virt_name); + + atomic_inc(&wait->stpg_wait_left); + + spin_lock_bh(&dev->dev_lock); + WARN_ON(dgd->dev->stpg_ext_blocked); + dgd->dev->stpg_ext_blocked = 1; + spin_unlock_bh(&dev->dev_lock); + + rc = scst_ext_block_dev(dgd->dev, false, + scst_stpg_ext_blocking_done, (uint8_t *)&wait, + sizeof(wait)); + if (rc != 0) { + TRACE_DBG("scst_ext_block_dev() returned %d, " + "stepping back (cmd %p)", rc, cmd); + wait->status = rc; + wait->dg = dg; + atomic_dec(&wait->stpg_wait_left); + spin_lock_bh(&dev->dev_lock); + WARN_ON(dgd->dev->stpg_ext_blocked); + dgd->dev->stpg_ext_blocked = 0; + spin_unlock_bh(&dev->dev_lock); + break; + } + } + mutex_unlock(&scst_dg_mutex); + + scst_stpg_check_blocking_done(wait); + /* !! cmd can be already dead here !! */ + } else { + TRACE_DBG("Nothing to do"); + goto out_free_event; + } + + res = 0; + +out_free: + kfree(osi); + +out: + TRACE_EXIT_RES(res); + return res; + +out_unlock_fail: + mutex_unlock(&scst_dg_mutex); + +out_unlock_sm_fail: + mutex_unlock(&scst_mutex); + + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + +out_put: + scst_put_buf_full(cmd, buf); + goto out_free; + +out_too_long: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + res = -EOVERFLOW; + +out_free_tr_id: + kfree(dg->stpg_transport_id); + +out_free_event: + kfree(event_entry); + goto out_free; +} +EXPORT_SYMBOL_GPL(scst_tg_set_group_info); From 9ce3d2989ae50d74f8c6769c5a3a10046423bede Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:31:04 +0000 Subject: [PATCH 12/82] Missed piece from the previous commit git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6577 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README_in-tree | 73 +++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/scst/README_in-tree b/scst/README_in-tree index 667d82709..b7b920b64 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -1300,16 +1300,18 @@ persistent reservations from this device are released, upon reconnect the initiators will see it. -Implicit ALUA Support ---------------------- +ALUA Support +------------ -SCST supports implicit asymmetric logical unit access (ALUA). Implicit ALUA is -a feature defined by the ANSI T10 SCSI committee that allows a target to tell -the initiator which path to use in a multipath setup. The redundant paths -between initiator and target can be used either for redundancy or for load -sharing purposes. The target can either be a single target system running SCST -with multiple communication interfaces or two target systems each running SCST -and configured in a high availability setup. +SCST supports both implicit and explicit asymmetric logical unit access +(ALUA). ALUA is a feature defined by the ANSI T10 SCSI committee. It +allows a target to tell the initiator which path to use in a multipath +setup plus, in the explicit case, control state of each path via SET +TARGET PORT GROUPS SCSI command. The redundant paths between initiator +and target can be used either for redundancy or for load sharing +purposes. The target can either be a single target system running SCST +with multiple communication interfaces or two target systems each +running SCST and configured in a high availability setup. In the SPC-4 standard the following concepts are defined related to ALUA: * Relative target port ID. A number between 1 and 65535 that uniquely @@ -1330,17 +1332,17 @@ ANSI T10 standard called SPC-4. ALUA support in SCST .................... -SCST allows to define implicit ALUA settings for each unique combination of -SCST device and SCST target. An initiator however queries ALUA settings by -sending an appropriate SCSI command to a specific LUN of an SCST target. Each -such LUN maps uniquely to an SCST device. For hardware SCST target drivers, -e.g. ib_srpt, there is a one-to-one correspondence between SCST target and -SCSI target port. With other SCST targets, e.g. iSCSI-SCST, by default the -only relationship between SCST targets and SCSI target ports is that all SCST -targets defined on a system are visible via all SCSI target ports. See also -the iSCSI-SCST documentation about the allowed_portal attribute for -information about how to associate iSCSI targets with a single physical -interface. +SCST allows to define ALUA settings for each unique combination of SCST +device and SCST target. An initiator however queries ALUA settings by +sending an appropriate SCSI command to a specific LUN of an SCST target. +Each such LUN maps uniquely to an SCST device. For hardware SCST target +drivers, e.g. ib_srpt, there is a one-to-one correspondence between SCST +target and SCSI target port. With other SCST targets, e.g. iSCSI-SCST, +by default the only relationship between SCST targets and SCSI target +ports is that all SCST targets defined on a system are visible via all +SCSI target ports. See also the iSCSI-SCST documentation about the +allowed_portal attribute for information about how to associate iSCSI +targets with a single physical interface. Notes: - In a H.A. setup it is the responsibility of the user to synchronize ALUA @@ -1363,7 +1365,7 @@ Notes: Configuring ALUA in SCST ........................ -SCST allows to configure the following settings related to implicit ALUA +SCST allows to configure the following settings related to ALUA for each unique combination of SCST target and virtual SCST device (vdisk_fileio, vdisk_blockio, vcdrom, ...): * The target port group asymmetric access state. SCST supports all ALUA port @@ -1461,15 +1463,26 @@ DEVICE_GROUP dgroup2 { } } +Explicit ALUA +............. + +To enable explicit ALUA you need in addition to the above settings set +expl_alua device attribute to 1 (by default it is 0). Also you need to +run stpgd and supply to it path to a script or program, which will +perform actual path state switching on SET TARGET PORT GROUPS command, +for instance, by calling drbdadm. For more information see stpgd README +as well as sample script scst_on_stpg. + Checking the Target Configuration ................................. -One way to verify the implicit ALUA configuration from a Linux initiator is -via the commands provided in the sg3_utils package. The first step is to -verify whether for a certain LUN implicit ALUA has been configured on the -target. This is possible by checking whether the TPGS=1 text appears in the -sg_inq output, where /dev/sdb is a device node created by the ib_srp initiator: +One way to verify the ALUA configuration from a Linux initiator is via +the commands provided in the sg3_utils package. The first step is to +verify whether for a certain LUN ALUA has been configured on the target. +This is possible by checking whether the TPGS=1 text appears in the +sg_inq output, where /dev/sdb is a device node created by the ib_srp +initiator: # sg_inq /dev/sdb standard INQUIRY: @@ -1529,9 +1542,11 @@ Device Identification VPD page: Initiator Support ................. -On Linux systems implicit ALUA support is provided by the scsi_dh_alua kernel -driver in combination with the user space multipathd daemon. You will have to -modify at least the following in /etc/multipath.conf to enable implicit ALUA: +On Linux systems ALUA support is provided by the scsi_dh_alua kernel +driver in combination with the user space multipathd daemon. You will +have to modify at least the following in /etc/multipath.conf to enable +ALUA: + * hardware_handler "1 alua" * prio alua * path_grouping_policy group_by_prio From 2ecfcdc643551671d82ab1a87fbcccdc9afce0de Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:35:50 +0000 Subject: [PATCH 13/82] Improve logging git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6578 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_sysfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index cf88bea63..c67891ee3 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -3520,7 +3520,7 @@ static ssize_t scst_dev_block_store(struct kobject *kobj, goto out; } - TRACE_DBG("Sysfs unblocking (dev %s)", dev->virt_name); + TRACE_MGMT_DBG("Sysfs unblocking (dev %s)", dev->virt_name); scst_ext_unblock_dev(dev, false); res = 0; @@ -3574,7 +3574,7 @@ static ssize_t scst_dev_block_store(struct kobject *kobj, goto out; } - TRACE_DBG("Sysfs blocking dev %s (sync %d, data_start %p, " + TRACE_MGMT_DBG("Sysfs blocking dev %s (sync %d, data_start %p, " "data_len %d)", dev->virt_name, sync, data_start, data_len); if (sync) From ae38d284e086733f50e0722ddd748fb0ffdf1a72 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:40:37 +0000 Subject: [PATCH 14/82] Explicit ALUA user space git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6579 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- Makefile | 4 +- usr/Makefile | 78 +++++ usr/fileio/Makefile | 3 +- usr/fileio/debug.c | 149 +-------- usr/fileio/fileio.c | 5 +- usr/include/debug.c | 147 +++++++++ usr/{fileio => include}/debug.h | 257 +++++++-------- usr/include/version.h | 24 ++ usr/stpgd/Makefile | 104 ++++++ usr/stpgd/README | 9 + usr/stpgd/debug.c | 1 + usr/stpgd/scst_on_stpg | 11 + usr/stpgd/stpgd_main.c | 562 ++++++++++++++++++++++++++++++++ 13 files changed, 1066 insertions(+), 288 deletions(-) create mode 100644 usr/Makefile create mode 100644 usr/include/debug.c rename usr/{fileio => include}/debug.h (54%) create mode 100644 usr/include/version.h create mode 100644 usr/stpgd/Makefile create mode 100644 usr/stpgd/README create mode 100644 usr/stpgd/debug.c create mode 100644 usr/stpgd/scst_on_stpg create mode 100644 usr/stpgd/stpgd_main.c diff --git a/Makefile b/Makefile index c984d1a62..7263ec5d6 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ QLA_DIR=qla2x00t_git/qla2x00-target QLA_OLD_INI_DIR=qla2x00t QLA_OLD_DIR=qla2x00t/qla2x00-target LSI_DIR=mpt -USR_DIR=usr/fileio +USR_DIR=usr SRP_DIR=srpt SCST_LOCAL_DIR=scst_local MVSAS_DIR=mvsas_tgt @@ -129,7 +129,7 @@ help: @echo " scst_local_install : scst_local target: install" @echo " scst_local_uninstall : scst_local target: uninstall" @echo "" - @echo " usr : make user space fileio_tgt target" + @echo " usr : make user space targets" @echo " usr_clean : usr target: clean " @echo " usr_extraclean : usr target: clean + clean dependencies" @echo " usr_install : usr target: install" diff --git a/usr/Makefile b/usr/Makefile new file mode 100644 index 000000000..77cb77135 --- /dev/null +++ b/usr/Makefile @@ -0,0 +1,78 @@ +# +# Common makefile for SCSI target mid-level and its drivers +# +# Copyright (C) 2004 - 2015 Vladislav Bolkhovitin +# Copyright (C) 2004 - 2005 Leonid Stoljar +# Copyright (C) 2007 - 2015 SanDisk Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, version 2 +# of the License. +# +# 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. +# +# + +SHELL=/bin/bash +# Decide to use which kernel src. If not specified, is current running kernel. +#export KDIR=/usr/src/linux-2.6 + +FILEIO_DIR=fileio +STPGD_DIR=stpgd + +all: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +install: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +uninstall: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +clean: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +extraclean: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +2release: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +2debug: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +2perf: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +disable_proc: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +enable_proc: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + +help: + @echo " all (the default) : make all" + @echo " clean : clean files" + @echo " extraclean : clean + clean dependencies" + @echo " install : install" + @echo " uninstall : uninstall" + @echo " Notes :" + @echo " - install and uninstall must be made as root." + @echo " - be sure to compile qla against the correct initiator" + @echo " driver. Read its README for details." + +.PHONY: all install uninstall clean extraclean help 2release 2debug 2perf disable_proc enable_proc diff --git a/usr/fileio/Makefile b/usr/fileio/Makefile index 7f6b23695..4789564ef 100644 --- a/usr/fileio/Makefile +++ b/usr/fileio/Makefile @@ -29,10 +29,11 @@ OBJS_F = $(SRCS_F:.c=.o) SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ then echo "$$PWD/../../scst/include"; \ else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin/scst CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ - -I$(SCST_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ -D__USE_LARGEFILE64 PROGS = fileio_tgt LIBS = -lpthread diff --git a/usr/fileio/debug.c b/usr/fileio/debug.c index 296025302..03c08a556 100644 --- a/usr/fileio/debug.c +++ b/usr/fileio/debug.c @@ -1,148 +1 @@ -/* - * debug.c - * - * Copyright (C) 2004 - 2015 Vladislav Bolkhovitin - * Copyright (C) 2004 - 2005 Leonid Stoljar - * Copyright (C) 2007 - 2015 SanDisk Corporation - * - * Contains helper functions for execution tracing and error reporting. - * Intended to be included in main .c file. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, version 2 - * of the License. - * - * 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 -#include -#include -#include -#include -#include - -#include "debug.h" - -pid_t gettid (void) -{ - return syscall(__NR_gettid); -} - -#if defined(DEBUG) || defined(TRACING) - -#define TRACE_BUF_SIZE 512 - -static char trace_buf[TRACE_BUF_SIZE]; -static pthread_spinlock_t trace_buf_lock; - -int debug_print_prefix(unsigned long trace_flag, const char *prefix, - const char *func, int line) -{ - int i = 0; - - pthread_spin_lock(&trace_buf_lock); - - if (trace_flag & TRACE_TIME) { - struct tm t; - time_t tt; - time(&tt); - localtime_r(&tt, &t); - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%d:%d:%d ", - t.tm_hour, t.tm_min, t.tm_sec); - } - if (trace_flag & TRACE_PID) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "[%d]: ", - gettid()); - if (prefix != NULL) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", prefix); - if (trace_flag & TRACE_FUNCTION) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", func); - if (trace_flag & TRACE_LINE) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%i:", line); - - if (i > 0) - PRINTN("%s", trace_buf); - - pthread_spin_unlock(&trace_buf_lock); - - return i; -} - -void debug_print_buffer(const void *data, int len) -{ - int z, z1, i; - const unsigned char *buf = (const unsigned char *) data; - int f = 0; - - if (buf == NULL) - return; - - pthread_spin_lock(&trace_buf_lock); - - PRINT(" (h)___0__1__2__3__4__5__6__7__8__9__A__B__C__D__E__F"); - for (z = 0, z1 = 0, i = 0; z < len; z++) { - if (z % 16 == 0) { - if (z != 0) { - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, - " "); - for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); - z1++) { - if ((buf[z1] >= 0x20) && - (buf[z1] < 0x80)) - trace_buf[i++] = buf[z1]; - else - trace_buf[i++] = '.'; - } - trace_buf[i] = '\0'; - PRINT("%s", trace_buf); - i = 0; - f = 1; - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, - "%4x: ", z); - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%02x ", - buf[z]); - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, " "); - for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); z1++) { - if ((buf[z1] > 0x20) && (buf[z1] < 0x80)) - trace_buf[i++] = buf[z1]; - else - trace_buf[i++] = '.'; - } - trace_buf[i] = '\0'; - if (f) { - PRINT("%s", trace_buf) - } else { - PRINT("%s", trace_buf); - } - - pthread_spin_unlock(&trace_buf_lock); - return; -} - -int debug_init(void) -{ - int res; - - res = pthread_spin_init(&trace_buf_lock, PTHREAD_PROCESS_PRIVATE); - if (res != 0) { - res = errno; - PRINT_ERROR("pthread_spin_init() failed: %s", strerror(res)); - } - - return res; -} - -void debug_done(void) -{ - pthread_spin_destroy(&trace_buf_lock); -} - -#endif /* DEBUG || TRACING */ +#include "../include/debug.c" diff --git a/usr/fileio/fileio.c b/usr/fileio/fileio.c index 9c2d91fe6..c9ca1aaf1 100644 --- a/usr/fileio/fileio.c +++ b/usr/fileio/fileio.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,9 @@ char *app_name; +#include "version.h" #include "common.h" +#include "debug.h" #if defined(DEBUG) || defined(TRACING) @@ -62,11 +65,11 @@ char *app_name; # endif #endif /* DEBUG */ +bool log_daemon = false; unsigned long trace_flag = DEFAULT_LOG_FLAGS; #endif /* defined(DEBUG) || defined(TRACING) */ #define DEF_BLOCK_SHIFT 9 -#define VERSION_STR "3.1.0-pre1" #define THREADS 7 #define MAX_VDEVS 10 diff --git a/usr/include/debug.c b/usr/include/debug.c new file mode 100644 index 000000000..8d77e475b --- /dev/null +++ b/usr/include/debug.c @@ -0,0 +1,147 @@ +/* + * debug.c + * + * Copyright (C) 2004 - 2015 Vladislav Bolkhovitin + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2007 - 2015 SanDisk Corporation + * + * Contains helper functions for execution tracing and error reporting. + * Intended to be included in main .c file. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * 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 +#include +#include +#include +#include +#include + +#include "debug.h" + +pid_t gettid(void) +{ + return syscall(__NR_gettid); +} + +#if defined(DEBUG) || defined(TRACING) + +#define TRACE_BUF_SIZE 512 + +static char trace_buf[TRACE_BUF_SIZE]; +static pthread_spinlock_t trace_buf_lock; + +int debug_print_prefix(unsigned long trace_flag, const char *prefix, + const char *func, int line) +{ + int i = 0; + + pthread_spin_lock(&trace_buf_lock); + + if (trace_flag & TRACE_TIME) { + struct tm t; + time_t tt; + + time(&tt); + localtime_r(&tt, &t); + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%d:%d:%d ", + t.tm_hour, t.tm_min, t.tm_sec); + } + if (trace_flag & TRACE_PID) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "[%d]: ", + gettid()); + if (prefix != NULL) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", prefix); + if (trace_flag & TRACE_FUNCTION) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", func); + if (trace_flag & TRACE_LINE) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%i:", line); + + if (i > 0) + PRINTN(LOG_INFO, "%s", trace_buf); + + pthread_spin_unlock(&trace_buf_lock); + + return i; +} + +void debug_print_buffer(const void *data, int len) +{ + int z, z1, i; + const unsigned char *buf = (const unsigned char *) data; + int f = 0; + + if (buf == NULL) + return; + + pthread_spin_lock(&trace_buf_lock); + + PRINT(LOG_INFO, " (h)___0__1__2__3__4__5__6__7__8__9__A__B__C__D__E__F"); + for (z = 0, z1 = 0, i = 0; z < len; z++) { + if (z % 16 == 0) { + if (z != 0) { + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, + " "); + for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); + z1++) { + if ((buf[z1] >= 0x20) && + (buf[z1] < 0x80)) + trace_buf[i++] = buf[z1]; + else + trace_buf[i++] = '.'; + } + trace_buf[i] = '\0'; + PRINT(LOG_INFO, "%s", trace_buf); + i = 0; + f = 1; + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, + "%4x: ", z); + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%02x ", + buf[z]); + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, " "); + for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); z1++) { + if ((buf[z1] > 0x20) && (buf[z1] < 0x80)) + trace_buf[i++] = buf[z1]; + else + trace_buf[i++] = '.'; + } + trace_buf[i] = '\0'; + if (f) + PRINT(LOG_INFO, "%s", trace_buf); + else + PRINT(LOG_INFO, "%s", trace_buf); + + pthread_spin_unlock(&trace_buf_lock); +} + +int debug_init(void) +{ + int res; + + res = pthread_spin_init(&trace_buf_lock, PTHREAD_PROCESS_PRIVATE); + if (res != 0) { + res = errno; + PRINT_ERROR("pthread_spin_init() failed: %s", strerror(res)); + } + + return res; +} + +void debug_done(void) +{ + pthread_spin_destroy(&trace_buf_lock); +} + +#endif /* DEBUG || TRACING */ diff --git a/usr/fileio/debug.h b/usr/include/debug.h similarity index 54% rename from usr/fileio/debug.h rename to usr/include/debug.h index dbd0b8af1..bba07f5ef 100644 --- a/usr/fileio/debug.h +++ b/usr/include/debug.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include extern pid_t gettid(void); @@ -55,8 +57,21 @@ extern pid_t gettid(void); #define TRACE_ORDER 0x00008000 #define TRACE_ALL 0xffffffff -#define PRINT(format, args...) fprintf(stdout, format "\n", ## args); -#define PRINTN(format, args...) fprintf(stdout, format, ## args); +#define PRINT(priority, format, args...) \ +do { \ + if (log_daemon) \ + syslog(priority, format "\n", ## args); \ + else \ + fprintf(stdout, format "\n", ## args); \ +} while (0) + +#define PRINTN(priority, format, args...) \ +do { \ + if (log_daemon) \ + syslog(priority, format, ## args); \ + else \ + fprintf(stdout, format, ## args); \ +} while (0) extern char *app_name; #define LOG_PREFIX app_name @@ -69,6 +84,7 @@ extern char *app_name; #if defined(DEBUG) || defined(TRACING) +extern bool log_daemon; extern unsigned long trace_flag; extern int debug_init(void); @@ -80,24 +96,23 @@ extern void debug_done(void); */ extern int debug_print_prefix(unsigned long trace_flag, const char *prefix, - const char *func, int line); + const char *func, int line); extern void debug_print_buffer(const void *data, int len); #define TRACE(trace, format, args...) \ do { \ - if (trace_flag & (trace)) \ - { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & (trace)) { \ + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define PRINT_BUFFER(message, buff, len) \ do { \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ -} while(0) + PRINT(LOG_INFO, "%s:", message); \ + debug_print_buffer(buff, len); \ +} while (0) #else /* DEBUG || TRACING */ @@ -115,142 +130,126 @@ static inline void debug_done(void) {} #define TRACE_MEM(format, args...) \ do { \ - if (trace_flag & TRACE_MEMORY) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_MEMORY) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_DBG(format, args...) \ do { \ - if (trace_flag & TRACE_DEBUG) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_DEBUG) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_DBG_SPECIAL(args...) TRACE(TRACE_DEBUG|TRACE_SPECIAL, args) #define TRACE_MGMT_DBG(format, args...) \ do { \ - if (trace_flag & TRACE_MGMT_DEBUG) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_MGMT_DEBUG) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_BUFFER(message, buff, len) \ do { \ - if (trace_flag & TRACE_BUFF) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ - } \ -} while(0) + if (trace_flag & TRACE_BUFF) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, "%s:", message); \ + debug_print_buffer(buff, len); \ + } \ +} while (0) #define TRACE_BUFF_FLAG(flag, message, buff, len) \ do { \ - if (trace_flag & (flag)) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ - } \ -} while(0) + if (trace_flag & (flag)) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, "%s:", message); \ + debug_print_buffer(buff, len); \ + } \ +} while (0) #define PRINT_INFO(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_INFO, format, args); \ +} while (0) #define PRINT_WARNING(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT("***WARNING*** " format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_WARNING, "***WARNING*** " format, args); \ +} while (0) #define PRINT_ERROR(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT("***ERROR*** " format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_ERR, "***ERROR*** " format, args); \ +} while (0) #define TRACE_ENTRY() \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: ENTRY %s", gettid(), \ - __FUNCTION__); \ - } \ - else \ - { \ - PRINT("ENTRY %s", __FUNCTION__); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: ENTRY %s", \ + gettid(), __func__); \ + } else { \ + PRINT(LOG_DEBUG, "ENTRY %s", \ + __func__); \ + } \ + } \ +} while (0) #define TRACE_EXIT() \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s", gettid(), \ - __FUNCTION__); \ - } \ - else \ - { \ - PRINT("EXIT %s", __FUNCTION__); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s", \ + gettid(), __func__); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s", __func__); \ + } \ + } \ +} while (0) #define TRACE_EXIT_RES(res) \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s: %ld", gettid(), \ - __FUNCTION__, (long)(res)); \ - } \ - else \ - { \ - PRINT("EXIT %s: %ld", __FUNCTION__, (long)(res)); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s: %ld", \ + gettid(), __func__, \ + (long)(res)); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s: %ld", \ + __func__, (long)(res)); \ + } \ + } \ +} while (0) #define TRACE_EXIT_HRES(res) \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s: 0x%lx", gettid(), \ - __FUNCTION__, (long)(res)); \ - } \ - else \ - { \ - PRINT("EXIT %s: %lx", __FUNCTION__, (long)(res)); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s: 0x%lx",\ + gettid(), __func__, \ + (long)(res)); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s: %lx", \ + __func__, (long)(res)); \ + } \ + } \ +} while (0) #else /* DEBUG */ @@ -271,39 +270,25 @@ do { \ #ifdef LOG_PREFIX #define PRINT_INFO(format, args...) \ -do { \ - PRINT("%s: " format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_INFO, "%s: " format, LOG_PREFIX, args) \ #define PRINT_WARNING(format, args...) \ -do { \ - PRINT("%s: ***WARNING*** " \ - format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_WARNING, "%s: ***WARNING*** " format, \ + LOG_PREFIX, args) \ #define PRINT_ERROR(format, args...) \ -do { \ - PRINT("%s: ***ERROR*** " \ - format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_ERR, "%s: ***ERROR*** " format, \ + LOG_PREFIX, args) \ #else -#define PRINT_INFO(format, args...) \ -do { \ - PRINT(format, args); \ -} while(0) +#define PRINT_INFO(format, args...) PRINT(LOG_INFO, format, args) #define PRINT_WARNING(format, args...) \ -do { \ - PRINT("***WARNING*** " format, args); \ -} while(0) - + PRINT(LOG_WARNING, "***WARNING*** " format, args) \ #define PRINT_ERROR(format, args...) \ -do { \ - PRINT("***ERROR*** " format, args); \ -} while(0) + PRINT(LOG_ERR, "***ERROR*** " format, args) \ #endif /* LOG_PREFIX */ diff --git a/usr/include/version.h b/usr/include/version.h new file mode 100644 index 000000000..f4626c8c5 --- /dev/null +++ b/usr/include/version.h @@ -0,0 +1,24 @@ +/* + * version.h + * + * Copyright (C) 2015 SanDisk Corporation + * + * Contains macroses for execution tracing and error reporting + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * 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. + */ + +#ifndef __VERSION_H +#define __VERSION_H + +#define VERSION_STR "3.1.0-pre1" + +#endif /* __VERSION_H */ diff --git a/usr/stpgd/Makefile b/usr/stpgd/Makefile new file mode 100644 index 000000000..0f8336c95 --- /dev/null +++ b/usr/stpgd/Makefile @@ -0,0 +1,104 @@ +ifndef PREFIX + PREFIX=/usr/local +endif + +SHELL=/bin/bash + +SRCS_F = stpgd_main.c debug.c +OBJS_F = $(SRCS_F:.c=.o) + +#SRCS_C = +#OBJS_C = $(SRCS_C:.c=.o) + +SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ + then echo "$$PWD/../../scst/include"; \ + else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include +INSTALL_DIR := $(DESTDIR)$(PREFIX)/sbin +ON_STPG := $(DESTDIR)$(PREFIX)/bin/scst/scst_on_stpg + +CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -D__USE_LARGEFILE64 +PROGS = stpgd +LIBS = -lpthread + +CFLAGS += -DEXTRACHECKS +#CFLAGS += -DTRACING +CFLAGS += -DDEBUG -g -fno-inline -fno-inline-functions +CFLAGS += -W -Wno-unused-parameter +CFLAGS += $(LOCAL_CFLAGS) + +#CFLAGS += -DDEBUG_NOMEM + +all: $(PROGS) + +stpgd: .depend_f $(OBJS_F) + $(CC) $(OBJS_F) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +#cdrom_tgt: .depend_c $(OBJS_C) +# $(CC) $(OBJS_C) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +ifeq (.depend_f,$(wildcard .depend_f)) +-include .depend_f +endif + +#ifeq (.depend_c,$(wildcard .depend_c)) +#-include .depend_c +#endif + +%.o: %.c Makefile + $(CC) -c -o $(@) $(CFLAGS) $(<) + +.depend_f: + $(CC) -M $(CFLAGS) $(SRCS_F) >$(@) + +#.depend_c: +# $(CC) -M $(CFLAGS) $(SRCS_C) >$(@) + +install: all + install -d $(INSTALL_DIR) + install -m 700 $(PROGS) $(INSTALL_DIR) + install -m 700 scst_on_stpg $(ON_STPG) + +uninstall: + rm -f $(INSTALL_DIR)/$(PROGS) + rm -rf $(INSTALL_DIR) + +clean: + rm -f *.o $(PROGS) .depend* + +extraclean: clean + rm -f *.orig *.rej + +2release: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DTRACING"/"CFLAGS += \-DTRACING"/ Makefile + grep "^CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2debug: + sed -i.aa s/"^#\?CFLAGS += \-DEXTRACHECKS"/"CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2perf: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +release-archive: + ../../scripts/generate-release-archive stpgd "$$(sed -n 's/^#define[[:blank:]]VERSION_STR[[:blank:]]*\"\([^\"]*\)\".*/\1/p' stpgd_main.c)" + +.PHONY: all install uninstall clean extraclean 2release 2debug 2perf diff --git a/usr/stpgd/README b/usr/stpgd/README new file mode 100644 index 000000000..64b5ad98b --- /dev/null +++ b/usr/stpgd/README @@ -0,0 +1,9 @@ +This is stpgd service called by the SCST core upon receiving SET TARGET +PORT GROUPS command via SCST events subsystem. In turn, stpgd calls +external script to actually modify ALUA state as requested by the SET +TARGET PORT GROUPS command via cluster manager and SCST sysfs. You can +find example of such script in scst_on_stpg file. + +Reason why such dual stage approach is used is, because there is no way +from inside the kernel to control execution of external programs and +there is no way to write a service calling IOCTLs on shell. diff --git a/usr/stpgd/debug.c b/usr/stpgd/debug.c new file mode 100644 index 000000000..03c08a556 --- /dev/null +++ b/usr/stpgd/debug.c @@ -0,0 +1 @@ +#include "../include/debug.c" diff --git a/usr/stpgd/scst_on_stpg b/usr/stpgd/scst_on_stpg new file mode 100644 index 000000000..cb86767b0 --- /dev/null +++ b/usr/stpgd/scst_on_stpg @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Script invoked by SCST for processing a SCSI SET TARGET PORT GROUPS command +# +# Arguments: SCST_DEVICE_NAME (device name where this STPG command was +# received on), SCST_PREV_ALUA_STATE, SCST_ALUA_STATE, +# SCST_DEVICE_GROUP and SCST_TARGET_GROUP + environmental variables +# with the same names set +# + +echo $SCST_ALUA_STATE >/sys/kernel/scst_tgt/device_groups/$SCST_DEVICE_GROUP/target_groups/$SCST_TARGET_GROUP/state diff --git a/usr/stpgd/stpgd_main.c b/usr/stpgd/stpgd_main.c new file mode 100644 index 000000000..625879cfc --- /dev/null +++ b/usr/stpgd/stpgd_main.c @@ -0,0 +1,562 @@ +/* + * stpgd_main.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "debug.h" +#include "scst_event.h" + +char *app_name; + +#define DEFAULT_TRANSITION_TIME 17 + +#if defined(DEBUG) || defined(TRACING) + +#ifdef DEBUG + +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \ + TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | \ + TRACE_TIME) + +#define TRACE_SN(args...) TRACE(TRACE_SCSI_SERIALIZING, args) + +#else /* DEBUG */ + +# ifdef TRACING +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \ + TRACE_TIME | TRACE_SPECIAL) +# else +#define DEFAULT_LOG_FLAGS 0 +# endif +#endif /* DEBUG */ + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +bool log_daemon = true; +unsigned long trace_flag = DEFAULT_LOG_FLAGS; +#endif /* defined(DEBUG) || defined(TRACING) */ + +int transition_timeout = DEFAULT_TRANSITION_TIME; + +static struct option const long_options[] = { + {"path", required_argument, 0, 'p'}, + {"timeout", required_argument, 0, 't'}, + {"foreground", no_argument, 0, 'f'}, +#if defined(DEBUG) || defined(TRACING) + {"debug", required_argument, 0, 'd'}, +#endif + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +int stpg_init_report_pipe[2]; +char *stpg_path; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try '%s --help' for more information.\n", app_name); + else { + printf("Usage: %s [OPTIONS]\n", app_name); + printf("STPG target daemon\n"); + printf(" -f, --foreground make the program run in the foreground\n"); + printf(" -p, --path absolute path to the STPG script\n"); + printf(" -t, --timeout transition timeout\n"); +#if defined(DEBUG) || defined(TRACING) + printf(" -d, --debug=level debug tracing level\n"); +#endif + printf(" -h, --help display this help and exit\n"); + } +} + +static void stpg_handle_tm_received(struct scst_event_user *event_user) +{ + /* + * Put code to abort state transition here, if this STPG cmd, + * identified by cmd_to_abort_tag or RESET, requested to be aborted + */ +} + +int invoke_stpg(const uint8_t *device_name, + const struct scst_event_stpg_descr *descr, pid_t *out_pid) +{ + char *args[7], *env[7]; + int res = 0, ret, i; + pid_t c_pid; + + args[0] = stpg_path; + args[1] = (char *)device_name; + args[2] = (char *)descr->prev_state; + args[3] = (char *)descr->new_state; + args[4] = (char *)descr->dg_name; + args[5] = (char *)descr->tg_name; + args[6] = NULL; + + env[0] = "PATH=/bin:/usr/bin:/sbin:/usr/sbin"; + ret = asprintf(&env[1], "SCST_DEVICE_NAME=%s", device_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[2], "SCST_PREV_ALUA_STATE=%s", descr->prev_state); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[3], "SCST_ALUA_STATE=%s", descr->new_state); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[4], "SCST_DEVICE_GROUP=%s", descr->dg_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[5], "SCST_TARGET_GROUP=%s", descr->tg_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + env[6] = NULL; + + PRINT_INFO("Invoking script %s with parameters: %s %s %s %s %s and environment: " + "%s %s %s %s %s", stpg_path, args[1], args[2], args[3], + args[4], args[5], env[1], env[2], env[3], env[4], env[5]); + + c_pid = fork(); + if (c_pid == 0) { + ret = setpgid(getpid(), getpid()); + if (ret < 0) { + res = -errno; + PRINT_ERROR("setgid failed %d (%s)", ret, strerror(-ret)); + } + TRACE_DBG("pgid %d (pid %d)", getpgid(getpid()), getpid()); + ret = execve(stpg_path, args, env); + if (ret < 0) { + res = -errno; + PRINT_ERROR("EXEC failed %d (%s)", ret, strerror(-ret)); + } + exit(0); + } else if (c_pid < 0) { + res = -errno; + PRINT_ERROR("fork() failed: %d (%s)", res, strerror(-res)); + } + + *out_pid = c_pid; + + for (i = 1; i < (signed)ARRAY_SIZE(env); i++) + free(env[i]); + +out: + return res; +} + +/* Returns 0, if the pid is still running, >0 if it was exited or <0 error code */ +int wait_until_finished(pid_t pid, unsigned long deadline, int *status, int child) +{ + int res; + time_t start, end; + double elapsed; + + TRACE_ENTRY(); + + time(&start); + do { + res = waitpid(pid, status, WNOHANG); + if (res != 0) { + if (res < 0) { + res = -errno; + PRINT_ERROR("Waitpid for pid %d (child %d) " + "failed: %d (%s)", pid, child, + errno, strerror(errno)); + } + break; + } + sleep(0.1); + time(&end); + elapsed = difftime(end, start); + } while (elapsed < deadline); + + TRACE_EXIT_RES(res); + return res; +} + +int handle_stpg_received(struct scst_event_user *event_user) +{ + const struct scst_event_stpg_payload *p = (struct scst_event_stpg_payload *)event_user->out_event.payload; + int num, k; + int res = 0; + pid_t pids[p->stpg_descriptors_cnt]; + + TRACE_DBG("device name %s, stpg_descriptors_cnt %d", p->device_name, + p->stpg_descriptors_cnt); + + for (num = 0; num < p->stpg_descriptors_cnt; num++) { + res = invoke_stpg(p->device_name, &p->stpg_descriptors[num], &pids[num]); + TRACE_DBG("num %d, res %d, pid %d", num, res, pids[num]); + if (res != 0) + break; + } + + TRACE_DBG("num %d", num); + for (k = 0; k < num; k++) { + int status = 0, rc; + + TRACE_DBG("k %d, pid %d", k, pids[k]); + + rc = wait_until_finished(pids[k], transition_timeout, &status, k); + TRACE_DBG("rc %d, status %d", rc, WEXITSTATUS(status)); + if (rc > 0) { + if (res == 0) + res = WEXITSTATUS(status); + continue; + } else if (rc < 0) { + if (res == 0) + res = rc; + continue; + } + + PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - " + "sending SIGTERM", k, pids[k]); + if (res == 0) + res = -ETIMEDOUT; + rc = killpg(pids[k], SIGTERM); + if (rc < 0) + PRINT_ERROR("Failed to send SIGTERM to child %d (pid %d): %d/%s", + k, pids[k], errno, strerror(errno)); + + rc = wait_until_finished(pids[k], 1, &status, k); + if (rc != 0) + continue; + + while (1) { + PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - " + "sending SIGKILL", k, pids[k]); + rc = killpg(pids[k], SIGKILL); + if (rc < 0) { + PRINT_ERROR("Failed to send SIGKILL to child %d " + "(pid %d): %d/%s", k, pids[k], errno, + strerror(errno)); + break; + } + rc = wait_until_finished(pids[k], 1, &status, k); + if (rc != 0) + break; + }; + } + + TRACE_EXIT_RES(res); + return res; +} + +static int stpg_event_loop(void) +{ + int res = 0, status; + int event_fd; + uint8_t event_user_buf[1024*1024]; + pid_t c_pid = 0; + struct pollfd pl; + struct scst_event_user *event_user = + (struct scst_event_user *)event_user_buf; + struct scst_event e1; + bool first_error = true; + + event_fd = open(SCST_EVENT_DEV, O_RDWR); + if (event_fd < 0) { + res = -errno; + PRINT_ERROR("Unable to open SCST event device %s (%s)", + SCST_EVENT_DEV, strerror(-res)); + goto out; + } + + close(stpg_init_report_pipe[0]); + + if (log_daemon) + res = write(stpg_init_report_pipe[1], &res, sizeof(res)); + + close(stpg_init_report_pipe[1]); + + memset(&pl, 0, sizeof(pl)); + pl.fd = event_fd; + pl.events = POLLIN; + + memset(&e1, 0, sizeof(e1)); + e1.event_code = SCST_EVENT_STPG_USER_INVOKE; + strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER, + sizeof(e1.issuer_name)); + e1.issuer_name[sizeof(e1.issuer_name)-1] = '\0'; + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + e1.event_code, e1.issuer_name); + + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)", + res, strerror(-res)); + goto out; + } + + e1.event_code = SCST_EVENT_TM_FN_RECEIVED; + strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER, + sizeof(e1.issuer_name)); + e1.issuer_name[sizeof(e1.issuer_name)-1] = '\0'; + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + e1.event_code, e1.issuer_name); + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)", + res, strerror(-res)); + goto out; + } + + while (1) { + memset(event_user_buf, 0, sizeof(event_user_buf)); + event_user->max_event_size = sizeof(event_user_buf); + res = ioctl(event_fd, SCST_EVENT_GET_NEXT_EVENT, event_user); + if (res != 0) { + res = -errno; + switch (-res) { + case ESRCH: + case EBUSY: + TRACE_MGMT_DBG("SCST_EVENT_GET_NEXT_EVENT " + "returned %d (%s)", res, strerror(res)); + /* go through */ + case EINTR: + continue; + case EAGAIN: + TRACE_DBG("SCST_EVENT_GET_NEXT_EVENT, " + "returned EAGAIN (%d)", -res); + continue; + default: + PRINT_ERROR("SCST_EVENT_GET_NEXT_EVENT " + "failed: %d (%s)", res, strerror(-res)); + if (!first_error) + goto out; + first_error = false; + continue; + } + first_error = true; +again_poll: + res = poll(&pl, 1, c_pid > 0 ? 1 : 0); + if (res > 0) + continue; + else if (res == 0) + goto again_poll; + else { + res = -errno; + switch (res) { + case ESRCH: + case EBUSY: + case EAGAIN: + TRACE_MGMT_DBG("poll() returned %d " + "(%s)", res, strerror(-res)); + case EINTR: + goto again_poll; + default: + PRINT_ERROR("poll() failed: %d (%s)", + res, strerror(-res)); + goto again_poll; + } + } + } + first_error = true; +#ifdef DEBUG + PRINT_INFO("event_code %d, issuer_name %s", + event_user->out_event.event_code, + event_user->out_event.issuer_name); +#endif + if (event_user->out_event.payload_len != 0) + TRACE_BUFFER("payload", event_user->out_event.payload, + event_user->out_event.payload_len); + + if (event_user->out_event.event_code == SCST_EVENT_STPG_USER_INVOKE) { + c_pid = fork(); + if (c_pid == -1) + PRINT_ERROR("Failed to fork: %d", c_pid); + else if (c_pid == 0) { + struct scst_event_notify_done d; + + signal(SIGCHLD, SIG_DFL); + + status = handle_stpg_received(event_user); + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = status; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE " + "failed: %s (res %d)", + strerror(-res), res); + } else + PRINT_INFO("STPG event completed with status %d", status); + exit(res); + } + } else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) + stpg_handle_tm_received(event_user); + else + PRINT_ERROR("Unknown event %d received", event_user->out_event.event_code); + } +out: + return res; +} + +void sig_chld(int signal) +{ + /* Check just in case */ + if (signal == SIGCHLD) { + TRACE_DBG("Cleanup zombie (pid %d)", getpid()); + wait(NULL); + } +} + +int main(int argc, char **argv) +{ + int res = 0, ch, longindex; + pid_t pid; + struct sigaction sa; + + setlinebuf(stdout); + + openlog(argv[0], LOG_PID, LOG_USER); + + res = pipe(stpg_init_report_pipe); + if (res == -1) { + res = -errno; + PRINT_ERROR("Pipe failed: %d (%s)", res, strerror(-res)); + goto out; + } + + sa.sa_handler = &sig_chld; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + PRINT_ERROR("sigaction() failed: %d/%s", errno, strerror(errno)); + exit(1); + } + + /* + * Otherwise we could die in some later write() during the event_loop() + * instead of getting EPIPE! + */ + signal(SIGPIPE, SIG_IGN); + + res = debug_init(); + if (res != 0) + goto out; + + app_name = argv[0]; + + while ((ch = getopt_long(argc, argv, "+d:fp:t:hv", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'p': + stpg_path = optarg; + break; +#if defined(DEBUG) || defined(TRACING) + case 'd': + trace_flag = strtol(optarg, (char **)NULL, 0); + break; +#endif + case 'v': + printf("%s version %s\n", app_name, VERSION_STR); + goto out_done; + case 'f': + log_daemon = false; + break; + case 't': + transition_timeout = strtol(optarg, (char **)NULL, 0); + if (transition_timeout < 0) { + printf("Invalid timeout %d\n", transition_timeout); + res = -EINVAL; + goto out_done; + } + break; + case 'h': + usage(0); + goto out_done; + default: + goto out_usage; + } + } + + if (!stpg_path) + stpg_path = "/usr/local/bin/scst/scst_on_stpg"; + + if (access(stpg_path, X_OK) == -1) { + PRINT_ERROR("Script file \" %s \"does not exist or not " + "executable", stpg_path); + res = -1; + goto out_done; + } + +#ifdef DEBUG + PRINT_INFO("trace_flag %lx", trace_flag); +#endif + if (log_daemon) { + trace_flag &= ~TRACE_TIME; + trace_flag &= ~TRACE_PID; + + pid = fork(); + if (pid < 0) { + PRINT_ERROR("starting daemon failed(%d)", pid); + res = pid; + goto out_done; + } else if (pid) { + int res1 = -1; + + close(stpg_init_report_pipe[1]); + if ((unsigned)read(stpg_init_report_pipe[0], &res1, sizeof(res1)) < sizeof(res1)) { + res = -1; + goto out_done; + } else { + res = res1; + goto out_done; + } + } + + close(0); + open("/dev/null", O_RDWR); + dup2(0, 1); + dup2(0, 2); + setsid(); + } + + res = stpg_event_loop(); + +out_done: + debug_done(); + +out: + closelog(); + return res; + +out_usage: + usage(1); + goto out_done; +} From a2556e2e0196afd42fb5332aec5acc00a793c79b Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:45:10 +0000 Subject: [PATCH 15/82] svn:ignore added git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6580 d57e44dd-8a1f-0410-8b47-8ef2f437770f From c619567bbaab6902934b7da77c4f33a299557d6e Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:48:24 +0000 Subject: [PATCH 16/82] SCST events sample user space consumer Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6581 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- usr/Makefile | 11 ++ usr/events/Makefile | 102 ++++++++++++++ usr/events/README | 2 + usr/events/debug.c | 1 + usr/events/events.c | 326 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 442 insertions(+) create mode 100644 usr/events/Makefile create mode 100644 usr/events/README create mode 100644 usr/events/debug.c create mode 100644 usr/events/events.c diff --git a/usr/Makefile b/usr/Makefile index 77cb77135..85bed6073 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -23,46 +23,57 @@ SHELL=/bin/bash FILEIO_DIR=fileio STPGD_DIR=stpgd +EVENTS_DIR=events all: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ +# cd $(EVENTS_DIR) && $(MAKE) $@ install: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ +# cd $(EVENTS_DIR) && $(MAKE) $@ uninstall: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ clean: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ extraclean: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ 2release: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ 2debug: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ 2perf: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ disable_proc: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ enable_proc: cd $(FILEIO_DIR) && $(MAKE) $@ cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ help: @echo " all (the default) : make all" diff --git a/usr/events/Makefile b/usr/events/Makefile new file mode 100644 index 000000000..ffce7d2d7 --- /dev/null +++ b/usr/events/Makefile @@ -0,0 +1,102 @@ +ifndef PREFIX + PREFIX=/usr/local +endif + +SHELL=/bin/bash + +SRCS_F = events.c debug.c +OBJS_F = $(SRCS_F:.c=.o) + +#SRCS_C = +#OBJS_C = $(SRCS_C:.c=.o) + +SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ + then echo "$$PWD/../../scst/include"; \ + else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include +INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin/scst + +CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -D__USE_LARGEFILE64 +PROGS = events +LIBS = -lpthread + +CFLAGS += -DEXTRACHECKS +#CFLAGS += -DTRACING +CFLAGS += -DDEBUG -g -fno-inline -fno-inline-functions +CFLAGS += -W -Wno-unused-parameter +CFLAGS += $(LOCAL_CFLAGS) + +#CFLAGS += -DDEBUG_NOMEM + +all: $(PROGS) + +events: .depend_f $(OBJS_F) + $(CC) $(OBJS_F) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +#cdrom_tgt: .depend_c $(OBJS_C) +# $(CC) $(OBJS_C) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +ifeq (.depend_f,$(wildcard .depend_f)) +-include .depend_f +endif + +#ifeq (.depend_c,$(wildcard .depend_c)) +#-include .depend_c +#endif + +%.o: %.c Makefile + $(CC) -c -o $(@) $(CFLAGS) $(<) + +.depend_f: + $(CC) -M $(CFLAGS) $(SRCS_F) >$(@) + +#.depend_c: +# $(CC) -M $(CFLAGS) $(SRCS_C) >$(@) + +install: all + install -d $(INSTALL_DIR) + install -m 755 $(PROGS) $(INSTALL_DIR) + +uninstall: + rm -f $(INSTALL_DIR)/$(PROGS) + rm -rf $(INSTALL_DIR) + +clean: + rm -f *.o $(PROGS) .depend* + +extraclean: clean + rm -f *.orig *.rej + +2release: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DTRACING"/"CFLAGS += \-DTRACING"/ Makefile + grep "^CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2debug: + sed -i.aa s/"^#\?CFLAGS += \-DEXTRACHECKS"/"CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2perf: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +release-archive: + ../../scripts/generate-release-archive events "$$(sed -n 's/^#define[[:blank:]]VERSION_STR[[:blank:]]*\"\([^\"]*\)\".*/\1/p' events.c)" + +.PHONY: all install uninstall clean extraclean 2release 2debug 2perf diff --git a/usr/events/README b/usr/events/README new file mode 100644 index 000000000..e354bb4dd --- /dev/null +++ b/usr/events/README @@ -0,0 +1,2 @@ +This is test/demo application for SCST events subsystem. For more +information about this subsystem, see scst_event.h file. diff --git a/usr/events/debug.c b/usr/events/debug.c new file mode 100644 index 000000000..03c08a556 --- /dev/null +++ b/usr/events/debug.c @@ -0,0 +1 @@ +#include "../include/debug.c" diff --git a/usr/events/events.c b/usr/events/events.c new file mode 100644 index 000000000..4cdf896e4 --- /dev/null +++ b/usr/events/events.c @@ -0,0 +1,326 @@ +/* + * events.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "version.h" +#include "debug.h" +#include "scst_event.h" + +char *app_name; + +#if defined(DEBUG) || defined(TRACING) + +#ifdef DEBUG +/*#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF \ + & ~TRACE_FUNCTION) +#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF & \ + ~TRACE_SCSI & ~TRACE_SCSI_SERIALIZING & ~TRACE_DEBUG) +*/ +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \ + TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | \ + TRACE_TIME) + +#define TRACE_SN(args...) TRACE(TRACE_SCSI_SERIALIZING, args) + +#else /* DEBUG */ + +# ifdef TRACING +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \ + TRACE_TIME | TRACE_SPECIAL) +# else +#define DEFAULT_LOG_FLAGS 0 +# endif +#endif /* DEBUG */ + +bool log_daemon = false; /* needed for the tracing infrastructure */ +unsigned long trace_flag = DEFAULT_LOG_FLAGS; +#endif /* defined(DEBUG) || defined(TRACING) */ + +static struct option const long_options[] = { + {"allowed_event", required_argument, 0, 'e'}, + {"allowed_issuer", required_argument, 0, 'i'}, + {"non_blocking", no_argument, 0, 'n'}, +#if defined(DEBUG) || defined(TRACING) + {"debug", required_argument, 0, 'd'}, +#endif + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +#define MAX_ALLOWED_EVENTS 16 +static struct scst_event allowed_events[MAX_ALLOWED_EVENTS]; +static int allowed_events_num; +static int non_blocking; + +static void usage(void) +{ + printf("Usage: %s [OPTIONS]\n", app_name); + printf("\nSCST events testing program\n"); + printf(" -e, --allowed_event=code Allowed event code, 0 - any event\n"); + printf(" -i, --allowed_issuer=issuer Allowed issuer, * - any issuer\n"); + printf(" -n, --non_blocking Use non-blocking operations\n"); +#if defined(DEBUG) || defined(TRACING) + printf(" -d, --debug=level Debug tracing level\n"); +#endif +} + +static void handle_tm_received(struct scst_event_user *event_user) +{ + struct scst_event_tm_fn_received_payload *p = (struct scst_event_tm_fn_received_payload *)event_user->out_event.payload; + + printf("fn %d, device %s\n", p->fn, p->device_name); + + return; +} + +int main(int argc, char **argv) +{ + int res = 0, i; + int ch, longindex; + int event_fd; + uint8_t event_user_buf[10240]; + struct scst_event_user *event_user = (struct scst_event_user *)event_user_buf; + struct pollfd pl; + + setlinebuf(stdout); + + res = debug_init(); + if (res != 0) + goto out; + + app_name = argv[0]; + + while ((ch = getopt_long(argc, argv, "+e:i:d:nhv", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'e': + if (allowed_events[allowed_events_num].event_code != 0) { + allowed_events_num++; + if (allowed_events_num >= MAX_ALLOWED_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + allowed_events_num); + exit(1); + } + } + allowed_events[allowed_events_num].event_code = atoi(optarg); + break; + case 'i': + if (allowed_events[allowed_events_num].issuer_name[0] != '\0') { + allowed_events_num++; + if (allowed_events_num >= MAX_ALLOWED_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + allowed_events_num); + exit(1); + } + } + strncpy(allowed_events[allowed_events_num].issuer_name, + optarg, sizeof(allowed_events[allowed_events_num].issuer_name)); + allowed_events[allowed_events_num].issuer_name[ + sizeof(allowed_events[allowed_events_num].issuer_name)-1] = '\0'; + break; + case 'n': + non_blocking = 1; + break; +#if defined(DEBUG) || defined(TRACING) + case 'd': + trace_flag = strtol(optarg, (char **)NULL, 0); + break; +#endif + case 'v': + printf("%s version %s\n", app_name, VERSION_STR); + goto out_done; + default: + goto out_usage; + } + } + + if (allowed_events_num == 0) { + if ((allowed_events[0].event_code == 0) && + (allowed_events[0].issuer_name[0] == '\0')) + allowed_events[0].issuer_name[0] = '*'; + } + + allowed_events_num++; + +#ifdef DEBUG + PRINT_INFO("trace_flag %lx", trace_flag); +#endif + + if (non_blocking) + PRINT_INFO("%s", "NON-BLOCKING"); + + event_fd = open(SCST_EVENT_DEV, O_RDWR | (non_blocking ? O_NONBLOCK : 0)); + if (event_fd < 0) { + res = -errno; + PRINT_ERROR("Unable to open SCST event device %s (%s)", + SCST_EVENT_DEV, strerror(-res)); + goto out_done; + } + + memset(&pl, 0, sizeof(pl)); + pl.fd = event_fd; + pl.events = POLLIN; + + for (i = 0; i < allowed_events_num; i++) { + struct scst_event e; + + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + allowed_events[i].event_code, + allowed_events[i].issuer_name); + memset(&e, 0, sizeof(e)); + e.event_code = allowed_events[i].event_code; + strncpy(e.issuer_name, allowed_events[i].issuer_name, + sizeof(e.issuer_name)); + e.issuer_name[sizeof(e.issuer_name)-1] = '\0'; + + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e); + if (res != 0) { + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %s (res %d)", + strerror(errno), res); + goto out_done; + } + } + + while (1) { + memset(event_user_buf, 0, sizeof(event_user_buf)); + event_user->max_event_size = sizeof(event_user_buf); + res = ioctl(event_fd, SCST_EVENT_GET_NEXT_EVENT, event_user); + if (res != 0) { + res = errno; + switch (res) { + case ESRCH: + case EBUSY: + TRACE_MGMT_DBG("SCST_EVENT_GET_NEXT_EVENT returned " + "%d (%s)", res, strerror(res)); + /* go through */ + case EINTR: + continue; + case EAGAIN: + TRACE_DBG("SCST_EVENT_GET_NEXT_EVENT, returned " + "EAGAIN (%d)", res); + if (non_blocking) + break; + else + continue; + default: + PRINT_ERROR("SCST_EVENT_GET_NEXT_EVENT failed: %s (res %d)", + strerror(errno), res); + goto out_done; + } +again_poll: + res = poll(&pl, 1, 2000); + if (res > 0) + continue; + else if (res == 0) + goto again_poll; + else { + res = errno; + switch (res) { + case ESRCH: + case EBUSY: + case EAGAIN: + TRACE_MGMT_DBG("poll() returned %d " + "(%s)", res, strerror(res)); + case EINTR: + goto again_poll; + default: + PRINT_ERROR("poll() failed: %s", strerror(res)); +#if 1 + goto again_poll; +#else + goto out_close; +#endif + } + } + + } + + PRINT_INFO("\nevent_code %d, issuer_name %s", + event_user->out_event.event_code, + event_user->out_event.issuer_name); + if (event_user->out_event.payload_len != 0) + PRINT_BUFFER("payoad", event_user->out_event.payload, + event_user->out_event.payload_len); + PRINT_INFO("%s", ""); + + if (event_user->out_event.event_code == 0x12345) { + struct scst_event_notify_done d; + + PRINT_INFO("%s", "Press any key to send reply to event " + "0x12345"); + getchar(); + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = -19; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE failed: %s " + "(res %d)", strerror(errno), res); + } else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) + handle_tm_received(event_user); + else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) { + struct scst_event_notify_done d; + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = -20; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE failed: %s " + "(res %d)", strerror(errno), res); + } + +#if 0 + { + struct scst_event e; + + PRINT_INFO("Deleting allowed event code %d, issuer_name %s", + allowed_events[0].event_code, + allowed_events[0].issuer_name); + memset(&e, 0, sizeof(e)); + e.event_code = allowed_events[0].event_code; + strncpy(e.issuer_name, allowed_events[0].issuer_name, + sizeof(e.issuer_name)); + e.issuer_name[sizeof(e.issuer_name)-1] = '\0'; + + res = ioctl(event_fd, SCST_EVENT_DISALLOW_EVENT, &e); + if (res != 0) { + PRINT_ERROR("SCST_EVENT_DISALLOW_EVENT failed: " + "%s (res %d)", strerror(errno), res); + } + } +#endif + } + +out_done: + debug_done(); + +out: + return res; + +out_usage: + usage(); + goto out_done; +} From 363666407101489f76b0d092cdd2d9ed4a19d35a Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:49:14 +0000 Subject: [PATCH 17/82] Various SCST events producers Prepared with help from Prasidh Srikanth git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6582 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_targ.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 2c5c3e9ee..4f39cd43d 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -4492,6 +4492,7 @@ static int scst_translate_lun(struct scst_cmd *cmd) "unexisting LU (initiator %s, target %s)?", (unsigned long long int)cmd->lun, cmd->sess->initiator_name, cmd->tgt->tgt_name); + scst_event_queue_lun_not_found(cmd); } scst_put(cmd->cpu_cmd_counter); } @@ -5966,6 +5967,8 @@ static int scst_mgmt_cmd_init(struct scst_mgmt_cmd *mcmd) } out: + scst_event_queue_tm_fn_received(mcmd); + TRACE_EXIT_RES(res); return res; } @@ -7101,6 +7104,9 @@ bool scst_initiator_has_luns(struct scst_tgt *tgt, const char *initiator_name) res = !list_empty(&acg->acg_dev_list); + if (!res) + scst_event_queue_negative_luns_inquiry(tgt, initiator_name); + mutex_unlock(&scst_mutex); TRACE_EXIT_RES(res); From 55c46e410659e1f910e3df101e60e207458127d4 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:50:37 +0000 Subject: [PATCH 18/82] Delay SCSI commands in TRANSITIONING ALUA state to ease initiators load git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6583 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 8 +- scst/src/scst_targ.c | 39 ++++++-- scst/src/scst_tg.c | 215 +++++++++++++++++++++++++++++++++---------- 3 files changed, 201 insertions(+), 61 deletions(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index 7f1b7ebb2..c7b489ee3 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2204,6 +2204,9 @@ struct scst_cmd { /* Set if cmd was pre-alloced by target driver */ unsigned int pre_alloced:1; + /* Set if cmd was already ALUA checked in TRANSITIONING state */ + unsigned int already_transitioning:1; + /* Set if scst_cmd_set_write_not_received_data_len() was called */ unsigned int write_not_received_set:1; @@ -3008,7 +3011,10 @@ struct scst_tgt_dev { atomic_t tgt_dev_cmd_count ____cacheline_aligned_in_smp; /* ALUA command filter */ - bool (*alua_filter)(struct scst_cmd *cmd); +#define SCST_ALUA_CHECK_OK 0 +#define SCST_ALUA_CHECK_DELAYED 1 +#define SCST_ALUA_CHECK_ERROR -1 + int (*alua_filter)(struct scst_cmd *cmd); struct scst_order_data *curr_order_data; struct scst_order_data tgt_dev_order_data; diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 4f39cd43d..6732a7649 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -3247,15 +3247,40 @@ out_done: goto out; } +static inline bool scst_check_alua(struct scst_cmd *cmd, int *out_res) +{ + int (*alua_filter)(struct scst_cmd *cmd); + bool res = false; + + alua_filter = ACCESS_ONCE(cmd->tgt_dev->alua_filter); + if (unlikely(alua_filter)) { + int ac = alua_filter(cmd); + if (ac != SCST_ALUA_CHECK_OK) { + if (ac != SCST_ALUA_CHECK_DELAYED) { + EXTRACHECKS_BUG_ON(cmd->status == 0); + scst_set_cmd_abnormal_done_state(cmd); + *out_res = SCST_CMD_STATE_RES_CONT_SAME; + } + res = true; + } + } + + return res; +} + static int scst_exec_check_blocking(struct scst_cmd **active_cmd) { struct scst_cmd *cmd = *active_cmd; struct scst_cmd *ref_cmd; + int res = SCST_CMD_STATE_RES_CONT_NEXT; TRACE_ENTRY(); cmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + if (unlikely(scst_check_alua(cmd, &res))) + goto out; + if (unlikely(scst_check_blocked_dev(cmd))) goto out; @@ -3319,6 +3344,9 @@ done: cmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + if (unlikely(scst_check_alua(cmd, &res))) + goto out; + if (unlikely(scst_check_blocked_dev(cmd))) break; @@ -3338,8 +3366,8 @@ done: /* !! At this point sess, dev and tgt_dev can be already freed !! */ out: - TRACE_EXIT(); - return SCST_CMD_STATE_RES_CONT_NEXT; + TRACE_EXIT_RES(res); + return res; } static int scst_exec_check_sn(struct scst_cmd **active_cmd) @@ -4514,7 +4542,6 @@ static int scst_translate_lun(struct scst_cmd *cmd) */ static int __scst_init_cmd(struct scst_cmd *cmd) { - bool (*alua_filter)(struct scst_cmd *cmd); int res = 0; TRACE_ENTRY(); @@ -4555,12 +4582,6 @@ static int __scst_init_cmd(struct scst_cmd *cmd) if (unlikely(failure)) goto out_busy; - alua_filter = ACCESS_ONCE(cmd->tgt_dev->alua_filter); - if (unlikely(alua_filter && !alua_filter(cmd))) { - scst_set_cmd_abnormal_done_state(cmd); - goto out; - } - /* * SCST_IMPLICIT_HQ for unknown commands not implemented for * case when set_sn_on_restart_cmd not set, because custom parse diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 68dc6a668..e2fd4018f 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -272,11 +272,14 @@ static struct kobj_type scst_tg_tgt_ktype = { }; /* - * Whether or not to accept a command in the ALUA unavailable and transitioning - * states. + * Whether or not to accept a command in the ALUA standby state. */ -static bool scst_tg_accept(struct scst_cmd *cmd) +static int scst_tg_accept_standby(struct scst_cmd *cmd) { + int res; + + TRACE_ENTRY(); + switch (cmd->cdb[0]) { case TEST_UNIT_READY: case GET_EVENT_STATUS_NOTIFICATION: @@ -292,41 +295,6 @@ static bool scst_tg_accept(struct scst_cmd *cmd) case RESERVE_10: case READ_BUFFER: case WRITE_BUFFER: - return true; - case SERVICE_ACTION_IN_16: - switch (cmd->cdb[1] & 0x1f) { - case SAI_READ_CAPACITY_16: - return true; - } - break; - case MAINTENANCE_IN: - switch (cmd->cdb[1] & 0x1f) { - case MI_REPORT_TARGET_PGS: - return true; - } - break; - case MAINTENANCE_OUT: - switch (cmd->cdb[1] & 0x1f) { - case MO_SET_TARGET_PGS: - return true; - } - break; - } - - return false; -} - -/* - * Whether or not to accept a command in the ALUA standby state. - */ -static bool scst_tg_accept_standby(struct scst_cmd *cmd) -{ - bool process_cmd = scst_tg_accept(cmd); - - if (process_cmd) - return process_cmd; - - switch (cmd->cdb[0]) { case MODE_SELECT: case MODE_SELECT_10: case LOG_SELECT: @@ -335,42 +303,187 @@ static bool scst_tg_accept_standby(struct scst_cmd *cmd) case SEND_DIAGNOSTIC: case PERSISTENT_RESERVE_IN: case PERSISTENT_RESERVE_OUT: - return true; + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; } scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_standby)); + res = SCST_ALUA_CHECK_ERROR; - return false; +out: + TRACE_EXIT_RES(res); + return res; } /* * Whether or not to accept a command in the ALUA unavailable state. */ -static bool scst_tg_accept_unav(struct scst_cmd *cmd) +static int scst_tg_accept_unav(struct scst_cmd *cmd) { - bool process_cmd = scst_tg_accept(cmd); + int res; - if (!process_cmd) - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav)); + TRACE_ENTRY(); - return process_cmd; + switch (cmd->cdb[0]) { + case TEST_UNIT_READY: + case GET_EVENT_STATUS_NOTIFICATION: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case RELEASE: + case RELEASE_10: + case RESERVE: + case RESERVE_10: + case READ_BUFFER: + case WRITE_BUFFER: + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + } + + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav)); + res = SCST_ALUA_CHECK_ERROR; + +out: + TRACE_EXIT_RES(res); + return res; +} + +struct scst_alua_retry { + struct scst_cmd *alua_retry_cmd; + struct delayed_work alua_retry_work; +}; + +static void scst_alua_transitioning_work_fn(struct delayed_work *work) +{ + struct scst_alua_retry *retry = container_of(work, struct scst_alua_retry, + alua_retry_work); + struct scst_cmd *cmd = retry->alua_retry_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("Retrying transitioning cmd %p", cmd); + + spin_lock_irq(&cmd->cmd_threads->cmd_list_lock); + list_add(&cmd->cmd_list_entry, + &cmd->cmd_threads->active_cmd_list); + wake_up(&cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock); + + kfree(retry); + + TRACE_EXIT(); + return; } /* * Whether or not to accept a command in the ALUA transitioning state. */ -static bool scst_tg_accept_transitioning(struct scst_cmd *cmd) +static int scst_tg_accept_transitioning(struct scst_cmd *cmd) { - bool process_cmd = scst_tg_accept(cmd); + int res; - if (!process_cmd) - scst_set_cmd_error(cmd, + TRACE_ENTRY(); + + switch (cmd->cdb[0]) { + case INQUIRY: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case READ_BUFFER: + case WRITE_BUFFER: + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + } + + if (cmd->already_transitioning) + TRACE_DBG("cmd %p already transitioned checked, failing", cmd); + else { + struct scst_alua_retry *retry; + + TRACE_DBG("ALUA transitioning: delaying cmd %p", cmd); + + retry = kzalloc(sizeof(*retry), GFP_KERNEL); + if (retry == NULL) { + TRACE_DBG("Unable to allocate ALUA retry " + "struct, failing cmd %p", cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + res = SCST_ALUA_CHECK_ERROR; + goto out; + } + + /* No get is needed, because cmd is sync here */ + retry->alua_retry_cmd = cmd; + INIT_DELAYED_WORK(&retry->alua_retry_work, + (void (*)(struct work_struct *))scst_alua_transitioning_work_fn); + cmd->already_transitioning = 1; + schedule_delayed_work(&retry->alua_retry_work, HZ/2); + res = SCST_ALUA_CHECK_DELAYED; + goto out; + } + + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + res = SCST_ALUA_CHECK_ERROR; - return process_cmd; +out: + TRACE_EXIT_RES(res); + return res; } -static bool (*scst_alua_filter[])(struct scst_cmd *cmd) = { +static int (*scst_alua_filter[])(struct scst_cmd *cmd) = { [SCST_TG_STATE_OPTIMIZED] = NULL, [SCST_TG_STATE_NONOPTIMIZED] = NULL, [SCST_TG_STATE_STANDBY] = scst_tg_accept_standby, From ecbe9d17a1fb547eb783e924dd05b44a6e18c625 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:51:35 +0000 Subject: [PATCH 19/82] Serialize STPG commands git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6584 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 12 +++ scst/src/dev_handlers/scst_vdisk.c | 2 + scst/src/scst_lib.c | 113 +++++++++++++++++++++++++++-- scst/src/scst_targ.c | 14 +++- scst/src/scst_tg.c | 2 + 5 files changed, 133 insertions(+), 10 deletions(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index c7b489ee3..00d3a36ea 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2213,6 +2213,12 @@ struct scst_cmd { /* Set if cmd has LINK bit set in CDB */ unsigned int cmd_linked:1; + /* Set if cmd is on scst_global_stpg_list */ + unsigned int cmd_on_global_stpg_list:1; + + /* Set if cmd was globally STPG blocked in __scst_check_blocked_dev() */ + unsigned int cmd_global_stpg_blocked:1; + /**************************************************************/ /* cmd's async flags */ @@ -2428,6 +2434,11 @@ struct scst_cmd { void *cmd_data_descriptors; int cmd_data_descriptors_cnt; }; + + /* STPG commands global serialization */ + struct { + struct list_head global_stpg_list_entry; + }; }; #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) @@ -3707,6 +3718,7 @@ bool scst_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); int scst_tg_set_group_info(struct scst_cmd *cmd); +void scst_stpg_del_unblock_next(struct scst_cmd *cmd); /* * Get/set functions for dev's static DIF APP TAG diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index be6f489c2..f758c9f30 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -5294,6 +5294,8 @@ static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p) rc = scst_tg_set_group_info(cmd); if (rc == 0) res = RUNNING_ASYNC; + else + scst_stpg_del_unblock_next(cmd); out: TRACE_EXIT_RES(res); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index a707c5129..ddb64c354 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -61,6 +61,19 @@ #include "scst_mem.h" #include "scst_pres.h" +/* + * List and IRQ lock to globally serialize all STPG commands. Needed to + * prevent deadlock, if (1) a device group contains multiple devices and + * (2) STPG commands comes to 2 or more of them at about the same time. + * In this case they will be waiting for each other to finish all pending + * commands, i.e. the STPG commands waiting for each other. Strict + * serialization is per device, so can not help here. + * + * ToDo: make it per device group. + */ +static DEFINE_SPINLOCK(scst_global_stpg_list_lock); +static LIST_HEAD(scst_global_stpg_list); + static void scst_put_acg_work(struct work_struct *work); static void scst_del_acn(struct scst_acn *acn); static void scst_free_acn(struct scst_acn *acn, bool reassign); @@ -6472,6 +6485,60 @@ static void scst_destroy_cmd(struct scst_cmd *cmd) return; } +/* No locks */ +void scst_stpg_del_unblock_next(struct scst_cmd *cmd) +{ + struct scst_cmd *c; + + TRACE_ENTRY(); + + spin_lock_irq(&scst_global_stpg_list_lock); + + EXTRACHECKS_BUG_ON(!cmd->cmd_on_global_stpg_list); + + TRACE_DBG("STPG cmd %p: unblocking next", cmd); + + list_del(&cmd->global_stpg_list_entry); + cmd->cmd_on_global_stpg_list = 0; + + if (list_empty(&scst_global_stpg_list)) { + TRACE_DBG("No more STPG commands to unblock"); + spin_unlock_irq(&scst_global_stpg_list_lock); + goto out; + } + + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + + spin_unlock_irq(&scst_global_stpg_list_lock); + + spin_lock_bh(&c->dev->dev_lock); + + if (!c->cmd_global_stpg_blocked) { + TRACE_DBG("STPG cmd %p is not cmd_global_stpg_blocked", c); + spin_unlock_bh(&c->dev->dev_lock); + goto out; + } + + TRACE_BLOCK("Unblocking serialized STPG cmd %p", c); + + list_del(&c->blocked_cmd_list_entry); + c->cmd_global_stpg_blocked = 0; + + spin_unlock_bh(&c->dev->dev_lock); + + spin_lock_irq(&c->cmd_threads->cmd_list_lock); + list_add(&c->cmd_list_entry, + &c->cmd_threads->active_cmd_list); + wake_up(&c->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&c->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_stpg_del_unblock_next); + /* No locks supposed to be held */ void scst_free_cmd(struct scst_cmd *cmd) { @@ -10526,10 +10593,18 @@ static int get_cdb_info_mo(struct scst_cmd *cmd, { switch (cmd->cdb[1] & 0x1f) { case MO_SET_TARGET_PGS: + { + unsigned long flags; cmd->op_name = "SET TARGET PORT GROUPS"; cmd->op_flags |= SCST_STRICTLY_SERIALIZED; + spin_lock_irqsave(&scst_global_stpg_list_lock, flags); + TRACE_DBG("Adding STPG cmd %p to global_stpg_list", cmd); + cmd->cmd_on_global_stpg_list = 1; + list_add_tail(&cmd->global_stpg_list_entry, &scst_global_stpg_list); + spin_unlock_irqrestore(&scst_global_stpg_list_lock, flags); break; } + } return get_cdb_info_len_4(cmd, sdbops); } @@ -11963,10 +12038,27 @@ bool __scst_check_blocked_dev(struct scst_cmd *cmd) scst_get_opcode_name(cmd), dev->virt_name); goto out_block; } else if ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED) { - TRACE_BLOCK("cmd %p (tag %llu, op %s): blocking further " - "cmds on dev %s due to strict serialization", cmd, - (unsigned long long int)cmd->tag, + TRACE_BLOCK("Strictly serialized cmd %p (tag %llu, op %s, dev %s)", + cmd, (unsigned long long int)cmd->tag, scst_get_opcode_name(cmd), dev->virt_name); + + if ((cmd->cdb[0] == MAINTENANCE_OUT) && + ((cmd->cdb[1] & 0x1f) == MO_SET_TARGET_PGS)) { + const struct scst_cmd *c; + /* + * It's OK to do that without lock, because we + * are interested only in comparison + */ + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + if (cmd != c) { + TRACE_BLOCK("Blocking serialized STPG cmd %p " + "(head %p)", cmd, c); + cmd->cmd_global_stpg_blocked = 1; + goto out_block; + } + } + scst_block_dev(dev); if (dev->on_dev_cmd_count > 1) { TRACE_BLOCK("Delaying strictly serialized cmd %p " @@ -12019,10 +12111,17 @@ void scst_unblock_dev(struct scst_device *dev) local_irq_save_nort(flags); list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list, blocked_cmd_list_entry) { - bool strictly_serialized; + bool strictly_serialized = + ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); + + if (dev->strictly_serialized_cmd_waiting && + !strictly_serialized) + continue; + list_del(&cmd->blocked_cmd_list_entry); - TRACE_BLOCK("Adding blocked cmd %p to active cmd " - "list", cmd); + cmd->cmd_global_stpg_blocked = 0; + + TRACE_BLOCK("Adding blocked cmd %p to active cmd list", cmd); spin_lock(&cmd->cmd_threads->cmd_list_lock); if (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE) list_add(&cmd->cmd_list_entry, @@ -12030,9 +12129,9 @@ void scst_unblock_dev(struct scst_device *dev) else list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); - strictly_serialized = ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); wake_up(&cmd->cmd_threads->cmd_list_waitQ); spin_unlock(&cmd->cmd_threads->cmd_list_lock); + if (dev->strictly_serialized_cmd_waiting && strictly_serialized) break; } diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 6732a7649..512311068 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -4261,6 +4261,12 @@ static int scst_finish_cmd(struct scst_cmd *cmd) spin_unlock_irq(&sess->sess_list_lock); + if (unlikely(cmd->cmd_on_global_stpg_list)) { + TRACE_DBG("Unlisting being freed STPG cmd %p", cmd); + EXTRACHECKS_BUG_ON(cmd->cmd_global_stpg_blocked); + scst_stpg_del_unblock_next(cmd); + } + if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) scst_finish_cmd_mgmt(cmd); @@ -5620,11 +5626,13 @@ static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd) /* IRQs supposed to be disabled */ static bool __scst_check_unblock_aborted_cmd(struct scst_cmd *cmd, - struct list_head *list_entry) + struct list_head *list_entry, bool blocked) { bool res; if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { list_del(list_entry); + if (blocked) + cmd->cmd_global_stpg_blocked = 0; spin_lock(&cmd->cmd_threads->cmd_list_lock); list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); @@ -5667,7 +5675,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->blocked_cmd_list_entry)) { + &cmd->blocked_cmd_list_entry, true)) { TRACE_MGMT_DBG("Unblock aborted blocked cmd %p", cmd); } } @@ -5689,7 +5697,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->deferred_cmd_list_entry)) { + &cmd->deferred_cmd_list_entry, false)) { TRACE_MGMT_DBG("Unblocked aborted SN " "cmd %p (sn %u)", cmd, cmd->sn); order_data->def_cmd_count--; diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index e2fd4018f..a6ce4783c 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -919,6 +919,8 @@ out_unlock: mutex_unlock(&scst_dg_mutex); mutex_unlock(&scst_mutex); + scst_stpg_del_unblock_next(cmd); + cmd->completed = 1; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); From 2b202209cad4898d7e973df5eca07d975bfb03d6 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:52:52 +0000 Subject: [PATCH 20/82] ALUA DRBD compatibility git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6585 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 36 ++++ scst/README_in-tree | 38 +++- scst/include/scst.h | 25 +++ scst/src/dev_handlers/scst_vdisk.c | 291 +++++++++++++++++++++++++---- scst/src/scst_priv.h | 1 - scst/src/scst_tg.c | 6 + 6 files changed, 359 insertions(+), 38 deletions(-) diff --git a/scst/README b/scst/README index 61592401e..ea045e79f 100644 --- a/scst/README +++ b/scst/README @@ -1625,6 +1625,42 @@ perform actual path state switching on SET TARGET PORT GROUPS command, for instance, by calling drbdadm. For more information see stpgd README as well as sample script scst_on_stpg. +DRBD compatibility +.................. + +DRBD does not allow to open its device on the secondary as well as does +not allow to perform primary to secondary transition, if this device is +open. + +SCST BLOCKIO handler has all the necessary support for this behavior. If +you write new ALUA state in the "state" attribute, SCST BLOCKIO handler +before transition closes the open devices' handles and after transition +reopens them, if the new state is active or nonoptimized. + +Thus, the recommended implicit ALUA state change procedure for primary +to secondary transition is: + +1. Block all involved SCST devices using "block" sysfs attribute (see +above). Wait until the blocking finished. + +2. Change the ALUA state to "transitioning". At this moment all open +file handles will be closed. + +3. Perform the DRBD state transition + +4. Change the ALUA state to your desired secondary state. + +5. Unblock the blocked on step 1 devices. + +Optionally, if your initiators support Transitioning ALUA state, for +more responsive behavior the blocked devices can be unblocked +immediately after step (2). However, not all initiators correctly +behave, if they receive ASYMMETRIC STATE TRANSITION sense. + +For the secondary to primary transition procedure is similar. + +In case of explicit ALUA, SCST automatically performs the necessary +devices blocking around sending SCST_EVENT_STPG_USER_INVOKE event. Checking the Target Configuration ................................. diff --git a/scst/README_in-tree b/scst/README_in-tree index b7b920b64..f2a1eb4ef 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -1463,6 +1463,43 @@ DEVICE_GROUP dgroup2 { } } +DRBD compatibility +.................. + +DRBD does not allow to open its device on the secondary as well as does +not allow to perform primary to secondary transition, if this device is +open. + +SCST BLOCKIO handler has all the necessary support for this behavior. If +you write new ALUA state in the "state" attribute, SCST BLOCKIO handler +before transition closes the open devices' handles and after transition +reopens them, if the new state is active or nonoptimized. + +Thus, the recommended implicit ALUA state change procedure for primary +to secondary transition is: + +1. Block all involved SCST devices using "block" sysfs attribute (see +above). Wait until the blocking finished. + +2. Change the ALUA state to "transitioning". At this moment all open +file handles will be closed. + +3. Perform the DRBD state transition + +4. Change the ALUA state to your desired secondary state. + +5. Unblock the blocked on step 1 devices. + +Optionally, if your initiators support Transitioning ALUA state, for +more responsive behavior the blocked devices can be unblocked +immediately after step (2). However, not all initiators correctly +behave, if they receive ASYMMETRIC STATE TRANSITION sense. + +For the secondary to primary transition procedure is similar. + +In case of explicit ALUA, SCST automatically performs the necessary +devices blocking around sending SCST_EVENT_STPG_USER_INVOKE event. + Explicit ALUA ............. @@ -1473,7 +1510,6 @@ perform actual path state switching on SET TARGET PORT GROUPS command, for instance, by calling drbdadm. For more information see stpgd README as well as sample script scst_on_stpg. - Checking the Target Configuration ................................. diff --git a/scst/include/scst.h b/scst/include/scst.h index 00d3a36ea..32f0376ad 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -1420,6 +1420,30 @@ struct scst_dev_type { */ void (*on_free_cmd)(struct scst_cmd *cmd); + /* + * Called to notify dev handler that a ALUA state change is about to + * be started. Can be used to close open file handlers, which might + * prevent the state switch. + * + * Called under scst_dg_mutex and no activities on the dev handler level. + * + * OPTIONAL + */ + void (*on_alua_state_change_start)(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); + + /* + * Called to notify dev handler that a ALUA state change is about to + * be finished. Can be used to (re)open file handlers closed in + * on_alua_state_change_start(). + * + * Called under scst_dg_mutex and no activities on the dev handler level. + * + * OPTIONAL + */ + void (*on_alua_state_change_finish)(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); + /* * Called to notify dev handler that a task management command received * @@ -3718,6 +3742,7 @@ bool scst_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); int scst_tg_set_group_info(struct scst_cmd *cmd); +const char *scst_alua_state_name(enum scst_tg_state s); void scst_stpg_del_unblock_next(struct scst_cmd *cmd); /* diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index f758c9f30..f62f71a4a 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -180,6 +180,8 @@ struct scst_vdisk_dev { unsigned int format_active:1; unsigned int discard_zeroes_data:1; unsigned int expl_alua:1; + unsigned int reexam_pending:1; + unsigned int size_key:1; struct file *fd; struct file *dif_fd; @@ -299,9 +301,14 @@ static int fileio_alloc_data_buf(struct scst_cmd *cmd); static int vdisk_parse(struct scst_cmd *); static int vcdrom_parse(struct scst_cmd *); static int non_fileio_parse(struct scst_cmd *); -static int vdisk_exec(struct scst_cmd *cmd); +static int fileio_exec(struct scst_cmd *cmd); static int vcdrom_exec(struct scst_cmd *cmd); -static int non_fileio_exec(struct scst_cmd *cmd); +static int blockio_exec(struct scst_cmd *cmd); +static int nullio_exec(struct scst_cmd *cmd); +static void blockio_on_alua_state_change_start(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); +static void blockio_on_alua_state_change_finish(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); static void fileio_on_free_cmd(struct scst_cmd *cmd); static enum compl_status_e nullio_exec_read(struct vdisk_cmd_params *p); static enum compl_status_e blockio_exec_read(struct vdisk_cmd_params *p); @@ -576,8 +583,8 @@ static const struct attribute *vdisk_fileio_attrs[] = { }; static const struct attribute *vdisk_blockio_attrs[] = { - &vdev_size_ro_attr.attr, - &vdev_size_mb_ro_attr.attr, + &vdev_size_rw_attr.attr, + &vdev_size_mb_rw_attr.attr, &vdisk_blocksize_attr.attr, &vdisk_rd_only_attr.attr, &vdisk_wt_attr.attr, @@ -684,7 +691,7 @@ static struct scst_dev_type vdisk_file_devtype = { .detach_tgt = vdisk_detach_tgt, .parse = vdisk_parse, .dev_alloc_data_buf = fileio_alloc_data_buf, - .exec = vdisk_exec, + .exec = fileio_exec, .on_free_cmd = fileio_on_free_cmd, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, .get_supported_opcodes = vdisk_get_supported_opcodes, @@ -740,7 +747,9 @@ static struct scst_dev_type vdisk_blk_devtype = { .attach_tgt = vdisk_attach_tgt, .detach_tgt = vdisk_detach_tgt, .parse = non_fileio_parse, - .exec = non_fileio_exec, + .exec = blockio_exec, + .on_alua_state_change_start = blockio_on_alua_state_change_start, + .on_alua_state_change_finish = blockio_on_alua_state_change_finish, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, .get_supported_opcodes = vdisk_get_supported_opcodes, .devt_priv = (void *)blockio_ops, @@ -788,7 +797,7 @@ static struct scst_dev_type vdisk_null_devtype = { .attach_tgt = vdisk_attach_tgt, .detach_tgt = vdisk_detach_tgt, .parse = non_fileio_parse, - .exec = non_fileio_exec, + .exec = nullio_exec, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, .devt_priv = (void *)nullio_ops, .get_supported_opcodes = vdisk_get_supported_opcodes, @@ -905,8 +914,13 @@ static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev, TRACE_DBG("Opening file %s, flags 0x%x", name, open_flags); fd = filp_open(name, O_LARGEFILE | open_flags, 0600); - if (IS_ERR(fd)) - PRINT_ERROR("filp_open(%s) failed: %d", name, (int)PTR_ERR(fd)); + if (IS_ERR(fd)) { + if (PTR_ERR(fd) == -EMEDIUMTYPE) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", name); + else + PRINT_ERROR("filp_open(%s) failed: %d", name, (int)PTR_ERR(fd)); + } TRACE_EXIT(); return fd; @@ -924,8 +938,12 @@ static void vdisk_blockio_check_flush_support(struct scst_vdisk_dev *virt_dev) fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600); if (IS_ERR(fd)) { - PRINT_ERROR("filp_open(%s) failed: %ld", - virt_dev->filename, PTR_ERR(fd)); + if ((PTR_ERR(fd) == -EMEDIUMTYPE) && virt_dev->blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", virt_dev->filename); + else + PRINT_ERROR("filp_open(%s) failed: %ld", + virt_dev->filename, PTR_ERR(fd)); goto out; } @@ -965,8 +983,12 @@ static void vdisk_check_tp_support(struct scst_vdisk_dev *virt_dev) fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600); if (IS_ERR(fd)) { - PRINT_ERROR("filp_open(%s) failed: %ld", - virt_dev->filename, PTR_ERR(fd)); + if ((PTR_ERR(fd) == -EMEDIUMTYPE) && virt_dev->blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", virt_dev->filename); + else + PRINT_ERROR("filp_open(%s) failed: %ld", + virt_dev->filename, PTR_ERR(fd)); goto check; } fd_open = true; @@ -1068,7 +1090,11 @@ static int vdisk_get_file_size(const char *filename, bool blockio, fd = filp_open(filename, O_LARGEFILE | O_RDONLY, 0600); if (IS_ERR(fd)) { res = PTR_ERR(fd); - PRINT_ERROR("filp_open(%s) failed: %d", filename, res); + if ((res == -EMEDIUMTYPE) && blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", filename); + else + PRINT_ERROR("filp_open(%s) failed: %d", filename, res); goto out; } @@ -1491,8 +1517,14 @@ static int vdisk_reexamine(struct scst_vdisk_dev *virt_dev) res = vdisk_get_file_size(virt_dev->filename, virt_dev->blockio, &file_size); - if (res < 0) + if (res < 0) { + if ((res == -EMEDIUMTYPE) && virt_dev->blockio) { + TRACE_DBG("Reexam pending (dev %s)", virt_dev->name); + virt_dev->reexam_pending = 1; + res = 0; + } goto out; + } virt_dev->file_size = file_size; vdisk_blockio_check_flush_support(virt_dev); vdisk_check_tp_support(virt_dev); @@ -1706,15 +1738,12 @@ static int vdisk_open_fd(struct scst_vdisk_dev *virt_dev, bool read_only) { int res; - lockdep_assert_held(&scst_mutex); sBUG_ON(!virt_dev->filename); virt_dev->fd = vdev_open_fd(virt_dev, virt_dev->filename, read_only); if (IS_ERR(virt_dev->fd)) { res = PTR_ERR(virt_dev->fd); virt_dev->fd = NULL; - PRINT_ERROR("filp_open(%s) failed: %d", - virt_dev->filename, res); goto out; } virt_dev->bdev = virt_dev->blockio ? file_inode(virt_dev->fd)->i_bdev : @@ -1742,8 +1771,6 @@ out_close_fd: static void vdisk_close_fd(struct scst_vdisk_dev *virt_dev) { - lockdep_assert_held(&scst_mutex); - if (virt_dev->fd) { filp_close(virt_dev->fd, NULL); virt_dev->fd = NULL; @@ -1765,14 +1792,21 @@ static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev) lockdep_assert_held(&scst_mutex); - if (virt_dev->tgt_dev_cnt++ > 0) + virt_dev->tgt_dev_cnt++; + + if (virt_dev->fd != NULL) goto out; if (!virt_dev->nullio && !virt_dev->cdrom_empty) { res = vdisk_open_fd(virt_dev, tgt_dev->dev->dev_rd_only); if (res != 0) { - virt_dev->tgt_dev_cnt--; - goto out; + if ((res == -EMEDIUMTYPE) && virt_dev->blockio) { + /* It's OK, it will be reopen on exec */ + res = 0; + } else { + virt_dev->tgt_dev_cnt--; + goto out; + } } } else { virt_dev->fd = NULL; @@ -3444,7 +3478,7 @@ out_invalid_opcode: goto out_compl; } -static int vdisk_exec(struct scst_cmd *cmd) +static int fileio_exec(struct scst_cmd *cmd) { struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; @@ -3487,8 +3521,112 @@ out: return; } -/* blockio and nullio */ -static int non_fileio_exec(struct scst_cmd *cmd) +/* + * Functionally identical to scst_tg_accept_standby(), but separated, because, + * generally, they are checking for different things. Better to keep different + * things separately. + */ +static bool vdisk_no_fd_allowed_commands(const struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + switch (cmd->cdb[0]) { + case TEST_UNIT_READY: + case GET_EVENT_STATUS_NOTIFICATION: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case RELEASE: + case RELEASE_10: + case RESERVE: + case RESERVE_10: + case READ_BUFFER: + case WRITE_BUFFER: + case MODE_SELECT: + case MODE_SELECT_10: + case LOG_SELECT: + case LOG_SENSE: + case RECEIVE_DIAGNOSTIC: + case SEND_DIAGNOSTIC: + case PERSISTENT_RESERVE_IN: + case PERSISTENT_RESERVE_OUT: + res = true; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = true; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = true; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = true; + goto out; + } + break; + } + + res = false; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int blockio_exec(struct scst_cmd *cmd) +{ + struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; + struct vdisk_cmd_params p; + int res; + + EXTRACHECKS_BUG_ON(!ops); + + memset(&p, 0, sizeof(p)); + if (unlikely(!vdisk_parse_offset(&p, cmd))) + goto err; + + if (unlikely(virt_dev->fd == NULL)) { + if (!vdisk_no_fd_allowed_commands(cmd)) { + /* We should not get here */ + PRINT_WARNING("Closed FD on exec. Secondary DRBD or not " + "blocked dev before ALUA state change? " + "(cmd %p, op %s, dev %s)", cmd, cmd->op_name, + cmd->dev->virt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_no_medium)); + goto err; + } + } + + cmd->dh_priv = &p; + res = vdev_do_job(cmd, ops); + cmd->dh_priv = NULL; + +out: + return res; + +err: + res = SCST_EXEC_COMPLETED; + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + goto out; +} + +static int nullio_exec(struct scst_cmd *cmd) { struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; @@ -4710,6 +4848,9 @@ static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) virt_dev->wt_flag = wt; spin_unlock(&virt_dev->flags_lock); + if (virt_dev->fd == NULL) + goto out; + /* * MODE SELECT is strictly serialized command, so it's safe here * to reopen fd. @@ -4729,8 +4870,7 @@ static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) } } - if (virt_dev->fd) - filp_close(virt_dev->fd, NULL); + filp_close(virt_dev->fd, NULL); if (virt_dev->dif_fd) filp_close(virt_dev->dif_fd, NULL); @@ -7280,6 +7420,71 @@ static enum compl_status_e nullio_exec_verify(struct vdisk_cmd_params *p) return CMD_SUCCEEDED; } +static void blockio_on_alua_state_change_start(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state) +{ + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /* + * As required for on_alua_state_change_* callbacks, + * no parallel fd activities could be here. + */ + + TRACE_MGMT_DBG("ALUA state change from %s to %s started, closing FD (dev %s)", + scst_alua_state_name(old_state), scst_alua_state_name(new_state), + dev->virt_name); + + /* Just in case always close */ + vdisk_close_fd(virt_dev); + + TRACE_EXIT(); + return; +} + +static void blockio_on_alua_state_change_finish(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state) +{ + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /* + * As required for on_alua_state_change_* callbacks, + * no parallel fd activities could be here. + */ + + if ((new_state == SCST_TG_STATE_OPTIMIZED) || + (new_state == SCST_TG_STATE_NONOPTIMIZED)) { + /* Try non-optimized as well, it might be new redirection device */ + int rc; + + TRACE_MGMT_DBG("ALUA state change from %s to %s finished (dev %s), " + "reopenning FD", scst_alua_state_name(old_state), + scst_alua_state_name(new_state), dev->virt_name); + + rc = vdisk_open_fd(virt_dev, dev->dev_rd_only); + if (rc == 0) { + if (virt_dev->reexam_pending) { + rc = vdisk_reexamine(virt_dev); + WARN_ON(rc != 0); + virt_dev->reexam_pending = 0; + } + } else { + PRINT_ERROR("Unable to open fd on ALUA state change " + "to %s (dev %s)", dev->virt_name, + scst_alua_state_name(new_state)); + } + } else + TRACE_DBG("ALUA state change from %s to %s finished (dev %s)", + scst_alua_state_name(old_state), scst_alua_state_name(new_state), + dev->virt_name); + + TRACE_EXIT(); + return; +} + static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, struct scst_tgt_dev *tgt_dev) { @@ -7412,6 +7617,11 @@ static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev) sBUG_ON(virt_dev->nullio); sBUG_ON(!virt_dev->filename); + if (virt_dev->fd == NULL) { + res = -EMEDIUMTYPE; + goto out; + } + res = vdisk_get_file_size(virt_dev->filename, virt_dev->blockio, &file_size); if (res != 0) @@ -7430,6 +7640,8 @@ static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev) virt_dev->file_size = file_size; virt_dev->nblocks = virt_dev->file_size >> virt_dev->dev->block_shift; + virt_dev->size_key = 0; + PRINT_INFO("New size of SCSI target virtual disk %s " "(fs=%lldMB, bs=%d, nblocks=%lld, cyln=%lld%s)", virt_dev->name, virt_dev->file_size >> 20, @@ -8288,7 +8500,7 @@ static int vcdrom_change(struct scst_vdisk_dev *virt_dev, virt_dev->blockio, &err); if (res != 0) goto out_free_fn; - if (virt_dev->tgt_dev_cnt > 0) { + if (virt_dev->fd == NULL) { res = vdisk_open_fd(virt_dev, true); if (res != 0) goto out_free_fn; @@ -8443,6 +8655,7 @@ static int vdev_size_process_store(struct scst_sysfs_work_item *work) struct scst_vdisk_dev *virt_dev; unsigned long long new_size; int size_shift, res = -EINVAL; + bool queue_ua; if (sscanf(work->buf, "%d %lld", &size_shift, &new_size) != 2 || new_size > (ULLONG_MAX >> size_shift)) @@ -8460,19 +8673,20 @@ static int vdev_size_process_store(struct scst_sysfs_work_item *work) goto resume; virt_dev = dev->dh_priv; - if (!virt_dev->nullio) { - res = -EPERM; - sBUG(); - } else if ((new_size & ((1 << virt_dev->blk_shift) - 1)) == 0) { + + queue_ua = (virt_dev->fd != NULL); + + if ((new_size & ((1 << virt_dev->blk_shift) - 1)) == 0) { virt_dev->file_size = new_size; virt_dev->nblocks = virt_dev->file_size >> dev->block_shift; + virt_dev->size_key = 1; } else { res = -EINVAL; } mutex_unlock(&scst_mutex); - if (res == 0) + if ((res == 0) || queue_ua) scst_capacity_data_changed(dev); resume: @@ -8532,14 +8746,19 @@ static ssize_t vdev_size_show(struct kobject *kobj, struct kobj_attribute *attr, struct scst_device *dev; struct scst_vdisk_dev *virt_dev; unsigned long long size; + bool key; dev = container_of(kobj, struct scst_device, dev_kobj); virt_dev = dev->dh_priv; size = ACCESS_ONCE(virt_dev->file_size); + if (virt_dev->nullio && size == VDISK_NULLIO_SIZE) + key = false; + else + key = virt_dev->size_key; + return sprintf(buf, "%llu\n%s", size >> size_shift, - virt_dev->nullio && size != VDISK_NULLIO_SIZE ? - SCST_SYSFS_KEY_MARK "\n" : ""); + key ? SCST_SYSFS_KEY_MARK "\n" : ""); } static ssize_t vdev_sysfs_size_show(struct kobject *kobj, diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 17462a38a..02dcd96e0 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -454,7 +454,6 @@ 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); -const char *scst_alua_state_name(enum scst_tg_state s); enum scst_tg_state scst_alua_name_to_state(const char *n); 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); diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index a6ce4783c..20a469939 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -69,6 +69,7 @@ const char *scst_alua_state_name(enum scst_tg_state s) return NULL; } +EXPORT_SYMBOL(scst_alua_state_name); enum scst_tg_state scst_alua_name_to_state(const char *n) { @@ -945,6 +946,7 @@ static void __scst_tg_set_state(struct scst_target_group *tg, struct scst_tgt_dev *tgt_dev; struct scst_tg_tgt *tg_tgt; struct scst_tgt *tgt; + enum scst_tg_state old_state = tg->state; sBUG_ON(state >= ARRAY_SIZE(scst_alua_filter)); lockdep_assert_held(&scst_dg_mutex); @@ -956,6 +958,8 @@ static void __scst_tg_set_state(struct scst_target_group *tg, list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) { dev = dg_dev->dev; + if (dev->handler->on_alua_state_change_start != NULL) + dev->handler->on_alua_state_change_start(dev, old_state, state); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tgt = tgt_dev->sess->tgt; @@ -971,6 +975,8 @@ static void __scst_tg_set_state(struct scst_target_group *tg, } } } + if (dev->handler->on_alua_state_change_finish != NULL) + dev->handler->on_alua_state_change_finish(dev, old_state, state); } scst_check_alua_invariant(); From daee4dcb7ec2aa64215eb90a557de60617545b09 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:54:23 +0000 Subject: [PATCH 21/82] WRITE SAME command improvements Prepared with help from Sushma Gurram git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6586 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 2 +- scst/include/scst_user.h | 22 +- scst/src/dev_handlers/scst_user.c | 107 +++++++++ scst/src/dev_handlers/scst_vdisk.c | 2 +- scst/src/scst_lib.c | 339 +++++++++++++++++++---------- usr/fileio/common.c | 36 +++ usr/fileio/common.h | 4 + 7 files changed, 396 insertions(+), 116 deletions(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index 32f0376ad..d968aa16f 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -5539,7 +5539,7 @@ struct scst_data_descriptor { uint64_t sdd_blocks; }; -void scst_write_same(struct scst_cmd *cmd); +void scst_write_same(struct scst_cmd *cmd, struct scst_data_descriptor *where); __be64 scst_pack_lun(const uint64_t lun, enum scst_lun_addr_method addr_method); uint64_t scst_unpack_lun(const uint8_t *lun, int len); diff --git a/scst/include/scst_user.h b/scst/include/scst_user.h index 0a2e53f92..435efb3d7 100644 --- a/scst/include/scst_user.h +++ b/scst/include/scst_user.h @@ -253,6 +253,15 @@ struct scst_user_scsi_cmd_reply_alloc_mem { aligned_u64 pbuf; }; +/* + * Same as struct scst_data_descriptor, but suitable to pass kernel/user + * space boundary + */ +struct scst_user_data_descriptor { + aligned_u64 usdd_lba; + aligned_u64 usdd_blocks; +}; + /* Be careful adding new members here, this structure is allocated on stack! */ struct scst_user_scsi_cmd_reply_exec { int32_t resp_data_len; @@ -260,11 +269,20 @@ struct scst_user_scsi_cmd_reply_exec { #define SCST_EXEC_REPLY_BACKGROUND 0 #define SCST_EXEC_REPLY_COMPLETED 1 +#define SCST_EXEC_REPLY_DO_WRITE_SAME 2 uint8_t reply_type; uint8_t status; - uint8_t sense_len; - aligned_u64 psense_buffer; + union { + struct { + uint8_t sense_len; + aligned_u64 psense_buffer; + }; + struct { + uint16_t ws_descriptors_len; + aligned_u64 ws_descriptors; + }; + }; }; /* Be careful adding new members here, this structure is allocated on stack! */ diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index dc53e0591..035664925 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -1444,6 +1444,110 @@ static int dev_user_process_reply_on_cache_free(struct scst_user_cmd *ucmd) return res; } +static int dev_user_process_ws_reply(struct scst_user_cmd *ucmd, + struct scst_user_scsi_cmd_reply_exec *ereply) +{ + int res = 0, rc, count, i; + struct scst_cmd *cmd = ucmd->cmd; + uint8_t *buf; + struct scst_user_data_descriptor *uwhere; + struct scst_data_descriptor *where; + + TRACE_ENTRY(); + + if (unlikely(cmd->cdb[0] != WRITE_SAME) && + unlikely(cmd->cdb[0] != WRITE_SAME_16)) { + PRINT_ERROR("Request to process WRITE SAME for not WRITE SAME " + "CDB (ucmd %p, cmd %p, op %s)", ucmd, cmd, + scst_get_opcode_name(cmd)); + res = -EINVAL; + goto out_hw_err; + } + + if (unlikely(ereply->status != 0)) { + PRINT_ERROR("Request to process WRITE SAME with not 0 status " + "(ucmd %p, cmd %p, status %d)", ucmd, cmd, ereply->status); + res = -EINVAL; + goto out_hw_err; + } + + if (ereply->ws_descriptors_len == 0) { + scst_write_same(cmd, NULL); + goto out; + } + + if (unlikely(ereply->ws_descriptors_len > PAGE_SIZE)) { + PRINT_ERROR("Too many WRITE SAME descriptors (len %d, ucmd %p, " + "cmd %p)", ereply->ws_descriptors_len, ucmd, cmd); + res = -EOVERFLOW; + goto out_hw_err; + } + + buf = kzalloc(ereply->ws_descriptors_len, GFP_KERNEL); + if (unlikely(buf == NULL)) { + PRINT_ERROR("Unable to alloc WS descriptors buf (size %d)", + ereply->ws_descriptors_len); + goto out_busy; + } + + rc = copy_from_user(buf, + (void __user *)(unsigned long)ereply->ws_descriptors, + ereply->ws_descriptors_len); + if (unlikely(rc != 0)) { + PRINT_ERROR("Failed to copy %d WS descriptors' bytes", rc); + res = -EFAULT; + goto out_free_buf; + } + + uwhere = (struct scst_user_data_descriptor *)buf; + count = ereply->ws_descriptors_len / sizeof(*uwhere); + if (unlikely((ereply->ws_descriptors_len % sizeof(*uwhere)) != 0) || + unlikely(uwhere[count-1].usdd_blocks != 0)) { + PRINT_ERROR("Invalid WS descriptors (ucmd %p, cmd %p)", + ucmd, cmd); + res = -EINVAL; + goto out_free_buf; + } + + where = kmalloc(sizeof(*where) * count, GFP_KERNEL); + if (unlikely(where == NULL)) { + PRINT_ERROR("Unable to alloc WS descriptors where (size %zd)", + sizeof(*where) * count); + goto out_busy_free_buf; + } + + for (i = 0; i < count; i++) { + where[i].sdd_lba = uwhere[i].usdd_lba; + where[i].sdd_blocks = uwhere[i].usdd_blocks; + } + kfree(buf); + + scst_write_same(cmd, where); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_buf: + kfree(buf); + +out_hw_err: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + +out_compl: + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_DIRECT); + /* !! At this point cmd can be already freed !! */ + goto out; + +out_busy_free_buf: + kfree(buf); + +out_busy: + scst_set_busy(cmd); + goto out_compl; +} + static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, struct scst_user_reply_cmd *reply) { @@ -1480,6 +1584,9 @@ static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, ucmd->background_exec = 1; TRACE_DBG("Background ucmd %p", ucmd); goto out_compl; + } else if (ereply->reply_type == SCST_EXEC_REPLY_DO_WRITE_SAME) { + res = dev_user_process_ws_reply(ucmd, ereply); + goto out; } else goto out_inval; diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index f62f71a4a..981bff641 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -3905,7 +3905,7 @@ static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p) if (cmd->cdb[ctrl_offs] & 0x8) vdisk_exec_write_same_unmap(p); else { - scst_write_same(cmd); + scst_write_same(cmd, NULL); res = RUNNING_ASYNC; } diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index ddb64c354..91130a5e8 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -5320,13 +5320,12 @@ int scst_acg_remove_name(struct scst_acg *acg, const char *name, bool reassign) } #endif -static struct scst_cmd *scst_create_prepare_internal_cmd( - struct scst_cmd *orig_cmd, const uint8_t *cdb, - unsigned int cdb_len, enum scst_cmd_queue_type queue_type) +struct scst_cmd *__scst_create_prepare_internal_cmd(const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type, + struct scst_tgt_dev *tgt_dev, gfp_t gfp_mask, bool fantom) { struct scst_cmd *res; int rc; - gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : orig_cmd->cmd_gfp_mask; unsigned long flags; TRACE_ENTRY(); @@ -5335,30 +5334,34 @@ static struct scst_cmd *scst_create_prepare_internal_cmd( if (res == NULL) goto out; - res->cmd_threads = orig_cmd->cmd_threads; - res->sess = orig_cmd->sess; - res->atomic = scst_cmd_atomic(orig_cmd); + res->cmd_threads = tgt_dev->active_cmd_threads; + res->sess = tgt_dev->sess; res->internal = 1; - res->tgtt = orig_cmd->tgtt; - res->tgt = orig_cmd->tgt; - res->dev = orig_cmd->dev; - res->devt = orig_cmd->devt; - res->tgt_dev = orig_cmd->tgt_dev; - res->cur_order_data = orig_cmd->tgt_dev->curr_order_data; - res->lun = orig_cmd->lun; + res->tgtt = tgt_dev->sess->tgt->tgtt; + res->tgt = tgt_dev->sess->tgt; + res->dev = tgt_dev->dev; + res->devt = tgt_dev->dev->handler; + res->tgt_dev = tgt_dev; + res->cur_order_data = tgt_dev->curr_order_data; + res->lun = tgt_dev->lun; res->queue_type = queue_type; res->data_direction = SCST_DATA_UNKNOWN; - /* - * We need to keep it here to be able to abort during TM processing. - * They should be aborted to (1) speed up TM processing and (2) to - * guarantee that after a TM command finished the affected device(s) - * is/are in a quiescent state with all affected commands finished and - * others - blocked. - */ - spin_lock_irqsave(&res->sess->sess_list_lock, flags); - list_add_tail(&res->sess_cmd_list_entry, &res->sess->sess_cmd_list); - spin_unlock_irqrestore(&res->sess->sess_list_lock, flags); + if (!fantom) { + /* + * We need to keep it here to be able to abort during TM + * processing. They should be aborted to (1) speed up TM + * processing and (2) to guarantee that after a TM command + * finished the affected device(s) is/are in a quiescent state + * with all affected commands finished and others - blocked. + * + * Fantom commands are exception, because they don't do any + * real work. + */ + spin_lock_irqsave(&res->sess->sess_list_lock, flags); + list_add_tail(&res->sess_cmd_list_entry, &res->sess->sess_cmd_list); + spin_unlock_irqrestore(&res->sess->sess_list_lock, flags); + } scst_sess_get(res->sess); if (res->tgt_dev != NULL) @@ -5379,6 +5382,27 @@ out: return res; } +static struct scst_cmd *scst_create_prepare_internal_cmd( + struct scst_cmd *orig_cmd, const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type) +{ + struct scst_cmd *res; + gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : orig_cmd->cmd_gfp_mask; + + TRACE_ENTRY(); + + res = __scst_create_prepare_internal_cmd(cdb, cdb_len, queue_type, + orig_cmd->tgt_dev, gfp_mask, false); + if (res == NULL) + goto out; + + res->atomic = scst_cmd_atomic(orig_cmd); + +out: + TRACE_EXIT_HRES((unsigned long)res); + return res; +} + static void scst_prelim_finish_internal_cmd(struct scst_cmd *cmd) { unsigned long flags; @@ -5492,6 +5516,11 @@ static void scst_complete_request_sense(struct scst_cmd *req_cmd) return; } +struct scst_ws_sg_tail { + struct scatterlist *sg; + int sg_cnt; +}; + struct scst_write_same_priv { /* Must be the first for scst_finish_internal_cmd()! */ scst_i_finish_fn_t ws_finish_fn; @@ -5500,8 +5529,13 @@ struct scst_write_same_priv { struct mutex ws_mutex; - int64_t ws_cur_lba; /* in blocks */ + /* 0 len terminated */ + struct scst_data_descriptor *ws_descriptors; + + int ws_cur_descr; + int ws_left_to_send; /* in blocks */ + int64_t ws_cur_lba; /* in blocks */ __be16 guard_tag; __be16 app_tag; @@ -5510,10 +5544,12 @@ struct scst_write_same_priv { unsigned int inc_ref_tag:1; int ws_max_each;/* in blocks */ - int ws_cur_in_flight; + int ws_cur_in_flight; /* commands */ - int ws_sg_cnt; - struct scatterlist *ws_sg; + struct scst_ws_sg_tail *ws_sg_tails; + + struct scatterlist *ws_sg_full; + int ws_sg_full_cnt; }; #ifdef CONFIG_SCST_EXTRACHECKS @@ -5536,7 +5572,8 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; struct scst_device *dev = ws_cmd->dev; - struct scatterlist *ws_sg = wsp->ws_sg; + struct scatterlist *ws_sg; + int ws_sg_cnt; int res; uint8_t write_cdb[32]; int write_cdb_len; @@ -5546,10 +5583,18 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, TRACE_ENTRY(); + if (blocks == wsp->ws_max_each) { + ws_sg = wsp->ws_sg_full; + ws_sg_cnt = wsp->ws_sg_full_cnt; + } else { + ws_sg = wsp->ws_sg_tails[wsp->ws_cur_descr].sg; + ws_sg_cnt = wsp->ws_sg_tails[wsp->ws_cur_descr].sg_cnt; + } + #ifdef CONFIG_SCST_EXTRACHECKS - if (len != sg_data_length(wsp->ws_sg, wsp->ws_sg_cnt)) + if (len != sg_data_length(ws_sg, ws_sg_cnt)) WARN_ONCE(true, "lba %lld: %d <> %lld\n", lba, len, - sg_data_length(wsp->ws_sg, wsp->ws_sg_cnt)); + sg_data_length(ws_sg, ws_sg_cnt)); #endif if (unlikely(test_bit(SCST_CMD_ABORTED, &ws_cmd->cmd_flags)) || @@ -5654,7 +5699,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, } cmd->tgt_i_sg = ws_sg; - cmd->tgt_i_sg_cnt = wsp->ws_sg_cnt; + cmd->tgt_i_sg_cnt = ws_sg_cnt; cmd->tgt_i_data_buf_alloced = 1; wsp->ws_cur_lba += blocks; @@ -5683,6 +5728,7 @@ out_busy: static void scst_ws_finished(struct scst_write_same_priv *wsp) { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; + int i; TRACE_ENTRY(); @@ -5690,9 +5736,21 @@ static void scst_ws_finished(struct scst_write_same_priv *wsp) sBUG_ON(wsp->ws_cur_in_flight != 0); - if (sg_page(&wsp->ws_sg[0]) != sg_page(ws_cmd->sg)) - __free_page(sg_page(&wsp->ws_sg[0])); - kfree(wsp->ws_sg); + if (wsp->ws_sg_full && + sg_page(&wsp->ws_sg_full[0]) != sg_page(ws_cmd->sg)) + __free_page(sg_page(&wsp->ws_sg_full[0])); + else if (wsp->ws_sg_tails && wsp->ws_sg_tails[0].sg_cnt != 0 && + sg_page(&wsp->ws_sg_tails[0].sg[0]) != sg_page(ws_cmd->sg)) + __free_page(sg_page(&wsp->ws_sg_tails[0].sg[0])); + if (wsp->ws_sg_full) + kfree(wsp->ws_sg_full); + if (wsp->ws_sg_tails) { + for (i = 0; wsp->ws_descriptors[i].sdd_blocks != 0; i++) + if (wsp->ws_sg_tails[i].sg_cnt != 0) + kfree(wsp->ws_sg_tails[i].sg); + kfree(wsp->ws_sg_tails); + } + kfree(wsp->ws_descriptors); kfree(wsp); ws_cmd->completed = 1; /* for success */ @@ -5702,51 +5760,6 @@ static void scst_ws_finished(struct scst_write_same_priv *wsp) return; } -/* - * If there is a tail with fewer segments than ws_max_each, adjust the SG - * vector and submit a WRITE command for the tail after all other in-flight - * commands have finished. - */ -static void scst_ws_process_tail(struct scst_write_same_priv *wsp) -{ - struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; - struct scatterlist *sg; - unsigned left; - int i; - - TRACE_ENTRY(); - - lockdep_assert_held(&wsp->ws_mutex); - EXTRACHECKS_BUG_ON(wsp->ws_cur_in_flight > 0); - EXTRACHECKS_BUG_ON(wsp->ws_left_to_send >= wsp->ws_max_each); - - wsp->ws_max_each = wsp->ws_left_to_send; - left = wsp->ws_left_to_send << ws_cmd->dev->block_shift; - for_each_sg(wsp->ws_sg, sg, wsp->ws_sg_cnt, i) { - u32 len = min(left, sg->length); - - if (sg->length > len) { - TRACE_DBG("Processing WS tail of %d << %d = %d bytes - adjusted length of element %d from %d to %d", - wsp->ws_left_to_send, - ws_cmd->dev->block_shift, - wsp->ws_left_to_send << ws_cmd->dev->block_shift, - i, sg->length, len); - sg->length = len; - sg_mark_end(sg); -#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0) - /* Old versions of sg_mark_end() clear page_link. */ - BUG_ON(sg_page(sg) == NULL); -#endif - wsp->ws_sg_cnt = i + 1; - break; - } - left -= len; - } - - TRACE_EXIT(); - return; -} - /* Must be called in a thread context and no locks */ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) { @@ -5788,14 +5801,17 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) } } - if (wsp->ws_left_to_send == 0) - goto out_check_finish; - - if (wsp->ws_left_to_send < wsp->ws_max_each) { - if (wsp->ws_cur_in_flight > 0) + if (wsp->ws_left_to_send == 0) { + if (wsp->ws_descriptors[wsp->ws_cur_descr+1].sdd_blocks != 0) { + wsp->ws_cur_descr++; + wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; + wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; + TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", + wsp, wsp->ws_cur_descr, (long long )wsp->ws_cur_lba, + wsp->ws_left_to_send); + } + if (wsp->ws_left_to_send == 0) goto out_check_finish; - else - scst_ws_process_tail(wsp); } blocks = min_t(int, wsp->ws_left_to_send, wsp->ws_max_each); @@ -5827,18 +5843,15 @@ static void scst_ws_gen_writes(struct scst_write_same_priv *wsp) { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; int cnt = 0; + int rc; TRACE_ENTRY(); mutex_lock(&wsp->ws_mutex); - if (wsp->ws_left_to_send < wsp->ws_max_each) - scst_ws_process_tail(wsp); - +again: while (wsp->ws_left_to_send >= wsp->ws_max_each && wsp->ws_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) { - int rc; - rc = scst_ws_push_single_write(wsp, wsp->ws_cur_lba, wsp->ws_max_each); if (rc != 0) @@ -5846,6 +5859,26 @@ static void scst_ws_gen_writes(struct scst_write_same_priv *wsp) cnt++; } + if (wsp->ws_left_to_send > 0 && + wsp->ws_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) { + rc = scst_ws_push_single_write(wsp, wsp->ws_cur_lba, + wsp->ws_left_to_send); + if (rc != 0) + goto out_err; + + cnt++; + } + + if ((wsp->ws_left_to_send == 0) && + (wsp->ws_descriptors[wsp->ws_cur_descr+1].sdd_blocks != 0)) { + wsp->ws_cur_descr++; + wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; + wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; + TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", + wsp, wsp->ws_cur_descr, (long long )wsp->ws_cur_lba, + wsp->ws_left_to_send); + goto again; + } out_wake: if (cnt != 0) @@ -5867,21 +5900,77 @@ out_err: } } +static int scst_ws_sg_init(struct scatterlist **ws_sg, int ws_sg_cnt, + struct page *pg, unsigned int offset, unsigned int length, + unsigned int total_bytes) +{ + struct scatterlist *sg; + int i; + + *ws_sg = kmalloc(ws_sg_cnt * sizeof(**ws_sg), GFP_KERNEL); + if (*ws_sg == NULL) { + PRINT_ERROR("Unable to alloc sg for %d entries", ws_sg_cnt); + return -ENOMEM; + } + sg_init_table(*ws_sg, ws_sg_cnt); + for_each_sg(*ws_sg, sg, ws_sg_cnt, i) { + u32 len = min(total_bytes, length); + sg_set_page(sg, pg, len, offset); + total_bytes -= len; + } + sBUG_ON(total_bytes != 0); /* crash here to avoid data corruption */ + + return 0; +} + +static int scst_ws_sg_tails_get(struct scst_data_descriptor *where, struct scst_write_same_priv *wsp) +{ + int i; + + TRACE_ENTRY(); + + for (i = 0; where[i].sdd_blocks != 0; i++) { + if (where[i].sdd_blocks / wsp->ws_max_each != 0) { + wsp->ws_sg_full_cnt = wsp->ws_max_each; + break; + } + } + while (where[i].sdd_blocks != 0) + i++; + + wsp->ws_sg_tails = kzalloc(sizeof(*(wsp->ws_sg_tails))*(i+1), GFP_KERNEL); + if (wsp->ws_sg_tails == NULL) { + PRINT_ERROR("Unable to allocate ws_sg_tails (size %zd)", + sizeof(*wsp->ws_sg_tails)*i); + return -ENOMEM; + } + for (i = 0; where[i].sdd_blocks != 0; i++) + wsp->ws_sg_tails[i].sg_cnt = where[i].sdd_blocks % wsp->ws_max_each; + + TRACE_EXIT(); + return 0; +} + /* * Library function to perform WRITE SAME in a generic manner. On exit, cmd * always completed with sense set, if necessary. + * + * Parameter "where" is an array of descriptors where to write the same + * block. Last element in this array has len 0. It must be allocated by + * k?alloc() and will be kfree() by this function on finish. */ -void scst_write_same(struct scst_cmd *cmd) +void scst_write_same(struct scst_cmd *cmd, struct scst_data_descriptor *where) { struct scst_write_same_priv *wsp; int i, rc; struct page *pg = NULL; - struct scatterlist *sg; - unsigned int offset, length, mult, ws_sg_blocks, left; + unsigned int offset, length, mult, ws_sg_full_blocks, ws_sg_tail_blocks; uint8_t ctrl_offs = (cmd->cdb_len < 32) ? 1 : 10; TRACE_ENTRY(); + scst_set_exec_time(cmd); + if (unlikely(cmd->data_len <= 0)) { scst_set_invalid_field_in_cdb(cmd, cmd->len_off, 0); goto out_done; @@ -5910,6 +5999,20 @@ void scst_write_same(struct scst_cmd *cmd) goto out_done; } + if (where == NULL) { + where = kzalloc(sizeof(*where) << 1, GFP_KERNEL); + if (where == NULL) { + PRINT_ERROR("Unable to allocate ws_priv (size %zd, cmd %p)", + sizeof(*where) << 1, cmd); + goto out_busy; + } + where->sdd_lba = cmd->lba; + where->sdd_blocks = cmd->data_len >> cmd->dev->block_shift; + } + + if (unlikely(where->sdd_blocks == 0)) + goto out_done; + rc = scst_dif_process_write(cmd); if (unlikely(rc != 0)) goto out_done; @@ -5925,10 +6028,15 @@ void scst_write_same(struct scst_cmd *cmd) wsp->ws_finish_fn = scst_ws_write_cmd_finished; wsp->ws_orig_cmd = cmd; - wsp->ws_cur_lba = cmd->lba; - wsp->ws_left_to_send = cmd->data_len >> cmd->dev->block_shift; + wsp->ws_descriptors = where; + wsp->ws_cur_descr = 0; + wsp->ws_cur_lba = where[0].sdd_lba; + wsp->ws_left_to_send = where[0].sdd_blocks; wsp->ws_max_each = SCST_MAX_EACH_INTERNAL_IO_SIZE >> cmd->dev->block_shift; + if (scst_ws_sg_tails_get(where, wsp) == -ENOMEM) + goto out_busy; + if (cmd->bufflen <= PAGE_SIZE / 2) pg = alloc_page(GFP_KERNEL); if (pg) { @@ -5953,22 +6061,28 @@ void scst_write_same(struct scst_cmd *cmd) mult = 1; } - ws_sg_blocks = min_t(int, wsp->ws_left_to_send, wsp->ws_max_each); - wsp->ws_sg_cnt = (ws_sg_blocks + mult - 1) / mult; - wsp->ws_sg = kmalloc(wsp->ws_sg_cnt * sizeof(*wsp->ws_sg), GFP_KERNEL); - if (wsp->ws_sg == NULL) { - PRINT_ERROR("Unable to alloc sg for %d entries", wsp->ws_sg_cnt); - goto out_free; + if (wsp->ws_sg_full_cnt != 0) { + ws_sg_full_blocks = wsp->ws_sg_full_cnt; + wsp->ws_sg_full_cnt = (ws_sg_full_blocks + mult - 1) / mult; + TRACE_DBG("Allocating %d SGs", wsp->ws_sg_full_cnt); + rc = scst_ws_sg_init(&wsp->ws_sg_full, wsp->ws_sg_full_cnt, pg, + offset, length, + ws_sg_full_blocks << cmd->dev->block_shift); + if (rc != 0) + goto out_free; } - sg_init_table(wsp->ws_sg, wsp->ws_sg_cnt); - left = ws_sg_blocks << cmd->dev->block_shift; - for_each_sg(wsp->ws_sg, sg, wsp->ws_sg_cnt, i) { - u32 len = min(left, length); - - sg_set_page(sg, pg, len, offset); - left -= len; + for (i = 0; wsp->ws_descriptors[i].sdd_blocks != 0; i++) { + if (wsp->ws_sg_tails[i].sg_cnt != 0) { + ws_sg_tail_blocks = wsp->ws_sg_tails[i].sg_cnt; + wsp->ws_sg_tails[i].sg_cnt = (ws_sg_tail_blocks + mult - 1) / mult; + TRACE_DBG("Allocating %d tail SGs for descriptor[%d] ", wsp->ws_sg_tails[i].sg_cnt, i); + rc = scst_ws_sg_init(&wsp->ws_sg_tails[i].sg, wsp->ws_sg_tails[i].sg_cnt, pg, + offset, length, + ws_sg_tail_blocks << cmd->dev->block_shift); + if (rc != 0) + goto out_free; + } } - sBUG_ON(left != 0); /* crash here to avoid data corruption */ if (scst_cmd_needs_dif_buf(cmd)) { struct t10_pi_tuple *t; @@ -5991,7 +6105,7 @@ void scst_write_same(struct scst_cmd *cmd) wsp->inc_ref_tag = 0; break; default: - sBUG_ON(1); + sBUG(); break; } @@ -6013,6 +6127,7 @@ out_busy: scst_set_busy(cmd); out_done: + kfree(where); cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); goto out; } diff --git a/usr/fileio/common.c b/usr/fileio/common.c index 47b673fcc..41400c44e 100644 --- a/usr/fileio/common.c +++ b/usr/fileio/common.c @@ -47,6 +47,7 @@ static int exec_fsync(struct vdisk_cmd *vcmd); static void exec_read(struct vdisk_cmd *vcmd, loff_t loff); static void exec_write(struct vdisk_cmd *vcmd, loff_t loff); static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff); +static void exec_write_same(struct vdisk_cmd *vcmd); static int open_dev_fd(struct vdisk_dev *dev) { @@ -408,6 +409,10 @@ static int do_exec(struct vdisk_cmd *vcmd) case READ_CAPACITY: exec_read_capacity(vcmd); break; + case WRITE_SAME_10: + case WRITE_SAME_16: + exec_write_same(vcmd); + break; case SERVICE_ACTION_IN_16: if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) exec_read_capacity16(vcmd); @@ -1761,3 +1766,34 @@ out: TRACE_EXIT(); return; } + +static void exec_write_same(struct vdisk_cmd *vcmd) +{ + struct vdisk_dev *dev = vcmd->dev; + struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; + uint64_t blocks = cmd->data_len >> dev->block_shift; + static struct scst_user_data_descriptor uwhere[3]; + + TRACE_ENTRY(); + + if (blocks < 2) + goto out; + + /* It's only a debug code, so it's OK to use static descr */ + + memset(uwhere, 0, sizeof(uwhere)); + + uwhere[0].usdd_lba = cmd->lba; + uwhere[0].usdd_blocks = 1; + uwhere[1].usdd_lba = cmd->lba+1; + uwhere[1].usdd_blocks = blocks-1; + + reply->reply_type = SCST_EXEC_REPLY_DO_WRITE_SAME; + reply->ws_descriptors_len = sizeof(uwhere); + reply->ws_descriptors = (unsigned long)uwhere; + +out: + TRACE_EXIT(); + return; +} diff --git a/usr/fileio/common.h b/usr/fileio/common.h index f526bd8ca..8fb1f14aa 100644 --- a/usr/fileio/common.h +++ b/usr/fileio/common.h @@ -23,6 +23,10 @@ #include "debug.h" +#ifndef WRITE_SAME_10 +#define WRITE_SAME_10 0x41 +#endif + /* 8 byte ASCII Vendor */ #define VENDOR "SCST_USR" /* 4 byte ASCII Product Revision Level - left aligned */ From aff6012d441483a672238f64d56d87cc391fd9c3 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:55:35 +0000 Subject: [PATCH 22/82] Remove old COMPARE AND WRITE implementation Prepared with help from Sushma Gurram git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6587 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 167 +---------------------------- 1 file changed, 1 insertion(+), 166 deletions(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 981bff641..8271555f8 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -119,9 +119,6 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define DEF_TST SCST_TST_1_SEP_TASK_SETS #define DEF_TMF_ONLY 0 -#define NO_CAW_LEN_LIM 0xff -#define DEF_CAW_LEN_LIM 0xfe - /* * Since we can't control backstorage device's reordering, we have to always * report unrestricted reordering. @@ -225,10 +222,6 @@ struct scst_vdisk_dev { /* Unmap INQUIRY parameters */ uint32_t unmap_opt_gran, unmap_align, unmap_max_lba_cnt; - /* Block limits INQUIRY parameters */ - uint8_t caw_len_lim; - struct mutex caw_mutex; - struct scst_device *dev; struct list_head vdev_list_entry; @@ -341,7 +334,6 @@ static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_prevent_allow_medium_removal(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_unmap(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p); -static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p); static int vdisk_fsync(loff_t loff, loff_t len, struct scst_device *dev, gfp_t gfp_flags, struct scst_cmd *cmd, bool async); @@ -2645,7 +2637,6 @@ static const struct scst_opcode_descriptor scst_op_descr_read_toc = { [UNMAP] = vdisk_exec_unmap, \ [WRITE_SAME] = vdisk_exec_write_same, \ [WRITE_SAME_16] = vdisk_exec_write_same, \ - [COMPARE_AND_WRITE] = vdisk_exec_caw, \ [MAINTENANCE_IN] = vdisk_exec_maintenance_in, \ [MAINTENANCE_OUT] = vdisk_exec_maintenance_out, \ [SEND_DIAGNOSTIC] = vdisk_exec_send_diagnostic, \ @@ -2932,7 +2923,6 @@ static bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) case WRITE_10: case WRITE_12: case WRITE_16: - case COMPARE_AND_WRITE: fua = (cdb[1] & 0x8); if (fua) { TRACE(TRACE_ORDER, "FUA: loff=%lld, " @@ -4178,7 +4168,7 @@ static int vdisk_block_limits(uint8_t *buf, struct scst_cmd *cmd, buf[1] = 0xB0; buf[3] = 0x3C; buf[4] = 1; /* WSNZ set */ - buf[5] = virt_dev->caw_len_lim; + buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ /* Optimal transfer granuality is PAGE_SIZE */ put_unaligned_be16(max_t(int, PAGE_SIZE / dev->block_size, 1), &buf[6]); @@ -7118,21 +7108,6 @@ static ssize_t fileio_read_sync(struct file *fd, void *buf, size_t len, return ret; } -/* Note: Updates *@loff if writing succeeded. */ -static ssize_t fileio_write_sync(struct file *fd, void *buf, size_t len, - loff_t *loff) -{ - mm_segment_t old_fs; - ssize_t ret; - - old_fs = get_fs(); - set_fs(get_ds()); - ret = vfs_write(fd, (char __force __user *)buf, len, loff); - set_fs(old_fs); - - return ret; -} - /* Note: Updates *@loff if reading succeeded except for NULLIO devices. */ static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, size_t len, loff_t *loff) @@ -7154,27 +7129,6 @@ static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, } } -/* Note: Updates *@loff if reading succeeded except for NULLIO devices. */ -static ssize_t vdev_write_sync(struct scst_vdisk_dev *virt_dev, void *buf, - size_t len, loff_t *loff) -{ - ssize_t written, res; - - if (virt_dev->nullio) { - return len; - } else if (virt_dev->blockio) { - for (written = 0; written < len; written += res) { - res = blockio_rw_sync(virt_dev, buf + written, - len - written, loff, WRITE_SYNC); - if (res < 0) - return res; - } - return written; - } else { - return fileio_write_sync(virt_dev->fd, buf, len, loff); - } -} - static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; @@ -7276,123 +7230,6 @@ out: return CMD_SUCCEEDED; } -/* COMPARE AND WRITE */ -static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p) -{ - struct scst_cmd *cmd = p->cmd; - struct scst_device *dev = cmd->dev; - struct scst_vdisk_dev *virt_dev = dev->dh_priv; - uint32_t data_len = scst_cmd_get_data_len(cmd); - int length, i; - uint8_t *caw_buf = NULL, *read_buf = NULL; - loff_t loff, read, written; - - if (unlikely(cmd->cdb[1] & 0xE0)) { - TRACE_DBG("%s", "WRPROTECT not supported"); - scst_set_invalid_field_in_cdb(cmd, 1, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 5); - goto out; - } - - /* - * A NUMBER OF LOGICAL BLOCKS field set to zero specifies that no read - * operations shall be performed, no logical block data shall be - * transferred from the Data-Out Buffer, no compare operations shall - * be performed, and no write operations shall be performed. This - * condition shall not be considered an error. - */ - if (data_len == 0) - goto out; - - if (virt_dev->caw_len_lim != NO_CAW_LEN_LIM && - (data_len > virt_dev->caw_len_lim << dev->block_shift)) { - PRINT_ERROR("COMPARE AND WRITE: data length %u exceeds" - " limit %u << %u = %u", data_len, - virt_dev->caw_len_lim, dev->block_shift, - virt_dev->caw_len_lim << dev->block_shift); - scst_set_invalid_field_in_cdb(cmd, 13, 0); - goto out; - } - - length = scst_get_buf_full(cmd, &caw_buf); - read_buf = vmalloc(data_len); - if (length < 0 || !read_buf) { - PRINT_ERROR("scst_get_buf_full() failed: %d", length); - if (length == -ENOMEM || !read_buf) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_internal_failure)); - goto out; - } - - if (length != 2 * data_len) { - scst_set_invalid_field_in_cdb(cmd, 13, 0); - goto out; - } - - mutex_lock(&virt_dev->caw_mutex); - - loff = p->loff; - read = vdev_read_sync(virt_dev, read_buf, data_len, &loff); - if (read < data_len) { - PRINT_ERROR("COMPARE AND WRITE / READ returned %lld from %d", - read, data_len); - if (read == -EAGAIN) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_read_error)); - goto unlock; - } - - if (memcmp(caw_buf, read_buf, data_len) != 0) { - for (i = 0; i < data_len && caw_buf[i] == read_buf[i]; i++) - ; - /* - * SBC-3 $5.2: if the compare operation does not indicate a - * match, then terminate the command with CHECK CONDITION - * status with the sense key set to MISCOMPARE and the - * additional sense code set to MISCOMPARE DURING VERIFY - * OPERATION. In the sense data (see 4.18 and SPC-4) the - * offset from the start of the Data-Out Buffer to the first - * byte of data that was not equal shall be reported in the - * INFORMATION field. - */ - scst_set_cmd_error_and_inf(cmd, - SCST_LOAD_SENSE(scst_sense_miscompare_error), i); - goto unlock; - } - - loff = p->loff; - written = vdev_write_sync(virt_dev, caw_buf + data_len, data_len, - &loff); - if (written < data_len) { - PRINT_ERROR("COMPARE AND WRITE / WRITE wrote %lld / %d", - written, data_len); - if (written == -EAGAIN) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_write_error)); - goto unlock; - } - if (p->fua) - vdisk_fsync(p->loff, scst_cmd_get_data_len(cmd), cmd->dev, - cmd->cmd_gfp_mask, cmd, false); - -unlock: - mutex_unlock(&virt_dev->caw_mutex); - -out: - if (read_buf) - vfree(read_buf); - if (caw_buf) - scst_put_buf_full(cmd, caw_buf); - - return CMD_SUCCEEDED; -} - static enum compl_status_e blockio_exec_write_verify(struct vdisk_cmd_params *p) { /* Not yet implemented */ @@ -7727,7 +7564,6 @@ static int vdev_create(struct scst_dev_type *devt, } spin_lock_init(&virt_dev->flags_lock); - mutex_init(&virt_dev->caw_mutex); virt_dev->vdev_devt = devt; @@ -7738,7 +7574,6 @@ static int vdev_create(struct scst_dev_type *devt, virt_dev->rotational = DEF_ROTATIONAL; virt_dev->thin_provisioned = DEF_THIN_PROVISIONED; virt_dev->tst = DEF_TST; - virt_dev->caw_len_lim = DEF_CAW_LEN_LIM; virt_dev->expl_alua = DEF_EXPL_ALUA; virt_dev->blk_shift = DEF_DISK_BLOCK_SHIFT; From 54cf21076abd9c4c829b5781bf355a5a2c7e3be9 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:56:48 +0000 Subject: [PATCH 23/82] New COMPARE AND WRITE implementation Prepared with help from Sushma Gurram git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6588 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 38 +++ scst/include/scst_const.h | 7 +- scst/src/scst_lib.c | 652 +++++++++++++++++++++++++++++++++++++- scst/src/scst_priv.h | 23 ++ scst/src/scst_targ.c | 402 ++++++++++++++++++++++- 5 files changed, 1099 insertions(+), 23 deletions(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index d968aa16f..80645c437 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2136,6 +2136,12 @@ struct scst_cmd { /* Set if scst_dec_on_dev_cmd() call is needed on the cmd's finish */ unsigned int dec_on_dev_needed:1; + /* Set if cmd is on dev's exec_cmd_list */ + unsigned int on_dev_exec_list:1; + + /* Set if this cmd passed check for SCSI atomicity */ + unsigned int scsi_atomicity_checked:1; + /* Set if cmd is queued as hw pending */ unsigned int cmd_hw_pending:1; @@ -2298,6 +2304,15 @@ struct scst_cmd { unsigned short cdb_len; uint8_t cdb_buf[SCST_MAX_CDB_SIZE]; + /* List entry for dev's dev_exec_cmd_list */ + struct list_head dev_exec_cmd_list_entry; + + /* + * Array of blocked by this cmd SCSI atomic cmds with size + * scsi_atomic_blocked_cmds_count. Protected by dev->dev_lock. + */ + struct scst_cmd **scsi_atomic_blocked_cmds; + uint8_t lba_off; /* LBA offset in cdb */ uint8_t lba_len; /* LBA length in cdb */ uint8_t len_off; /* length offset in cdb */ @@ -2429,6 +2444,18 @@ struct scst_cmd { /* Used for storage of dev handler private stuff */ void *dh_priv; + /* + * Number of waiting for this cmd to finish commands + * with SCSI atomic guarantees. Protected by dev->dev_lock. + */ + int scsi_atomic_blockers; + + /* + * How many SCSI atomic cmds this cmd blocked, i.e. size of array + * scsi_atomic_blocked_cmds. Protected by dev->dev_lock. + */ + int scsi_atomic_blocked_cmds_count; + /* List entry for dev's blocked_cmd_list */ struct list_head blocked_cmd_list_entry; @@ -2812,6 +2839,17 @@ struct scst_device { */ int on_dev_cmd_count; + /* + * How many atomic SCSI commands being executed. Protected by dev_lock. + */ + int dev_scsi_atomic_cmd_active; + + /* + * List of all being executed on the dev commands. + * Protected by dev_lock. + */ + struct list_head dev_exec_cmd_list; + /* Memory limits for this device */ struct scst_mem_lim dev_mem_lim; diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 1552b8e87..88327e234 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -228,9 +228,10 @@ enum scst_cdb_flags { #ifdef CONFIG_SCST_TEST_IO_IN_SIRQ SCST_TEST_IO_IN_SIRQ_ALLOWED = 0x8000, #endif - SCST_SERIALIZED = 0x10000, - SCST_STRICTLY_SERIALIZED = 0x20000|SCST_SERIALIZED, - SCST_DESCRIPTORS_BASED = 0x40000, + SCST_SERIALIZED = 0x10000, + SCST_STRICTLY_SERIALIZED = 0x20000|SCST_SERIALIZED, + SCST_DESCRIPTORS_BASED = 0x40000, + SCST_SCSI_ATOMIC = 0x100000, }; /************************************************************* diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 91130a5e8..f883c4633 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -155,6 +155,12 @@ static int sg_copy(struct scatterlist *dst_sg, struct scatterlist *src_sg, #endif static void scst_free_descriptors(struct scst_cmd *cmd); +static bool sg_cmp(struct scatterlist *dst_sg, struct scatterlist *src_sg, + int nents_to_cmp, size_t cmp_len, int *miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , enum km_type d_km_type, enum km_type s_km_type +#endif + ); const struct scst_opcode_descriptor scst_op_descr_inquiry = { .od_opcode = INQUIRY, @@ -757,7 +763,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_name = "RESERVE", .info_data_direction = SCST_DATA_NONE, .info_op_flags = SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED| - SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED| + SCST_SCSI_ATOMIC/* see comment in scst_cmd_overlap() */, .get_cdb_info = get_cdb_info_none}, {.ops = 0x17, .devkey = "MMMMMMMMMMMMMMMM", .info_op_name = "RELEASE", @@ -1170,7 +1177,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_name = "RESERVE(10)", .info_data_direction = SCST_DATA_NONE, .info_op_flags = SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED| - SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED| + SCST_SCSI_ATOMIC/* see comment in scst_cmd_overlap() */, .get_cdb_info = get_cdb_info_none}, {.ops = 0x57, .devkey = "OOOOOOOOOOOOOOOO", .info_op_name = "RELEASE(10)", @@ -1292,8 +1300,9 @@ static const struct scst_sdbops scst_scsi_op_table[] = { {.ops = 0x89, .devkey = "O ", .info_op_name = "COMPARE AND WRITE", .info_data_direction = SCST_DATA_WRITE, - .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM| - SCST_SERIALIZED, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD| + SCST_WRITE_MEDIUM|SCST_SCSI_ATOMIC, .info_lba_off = 2, .info_lba_len = 8, .info_len_off = 13, .info_len_len = 1, .get_cdb_info = get_cdb_info_compare_and_write}, @@ -1999,10 +2008,13 @@ int scst_set_cmd_error_and_inf(struct scst_cmd *cmd, int key, int asc, switch (cmd->sense[0] & 0x7f) { case 0x70: + { /* Fixed format */ + uint32_t i = information; cmd->sense[0] |= 0x80; /* Information field is valid */ - put_unaligned_be32(information, &cmd->sense[3]); + put_unaligned_be32(i, &cmd->sense[3]); break; + } case 0x72: /* Descriptor format */ cmd->sense[7] = 12; /* additional sense length */ @@ -3286,6 +3298,140 @@ static void scst_adjust_sg(struct scst_cmd *cmd, bool reg_sg, return; } +static bool __scst_adjust_sg_get_tail(struct scst_cmd *cmd, + struct scatterlist *sg, int *sg_cnt, + struct scatterlist **res_sg, int *res_sg_cnt, + int adjust_len, struct scst_orig_sg_data *orig_sg, int must_left) +{ + int res = -ENOENT, i, j, l; + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p, sg_cnt %d, sg %p", cmd, *sg_cnt, sg); + + l = 0; + for (i = 0, j = 0; i < *sg_cnt; i++, j++) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + TRACE_DBG("i %d, j %d, sg %p, page_link %lx, len %d", i, j, + sg, sg[j].page_link, sg->length); +#else + TRACE_DBG("i %d, j %d, sg %p", i, j, sg); +#endif + if (unlikely(sg_is_chain(&sg[j]))) { + sg = sg_chain_ptr(&sg[j]); + j = 0; + } + l += sg[j].length; + if (l >= adjust_len) { + int offs = adjust_len - (l - sg[j].length); + + TRACE_DBG_FLAG(TRACE_SG_OP|TRACE_MEMORY|TRACE_DEBUG, + "cmd %p (tag %llu), sg %p, adjust_len %d, i %d, " + "j %d, sg[j].length %d, offs %d", + cmd, (long long unsigned int)cmd->tag, + sg, adjust_len, i, j, sg[j].length, offs); + + if (offs == sg[j].length) { + j++; + offs = 0; + } + + orig_sg->p_orig_sg_cnt = sg_cnt; + orig_sg->orig_sg_cnt = *sg_cnt; + orig_sg->orig_sg_entry = &sg[j]; + orig_sg->orig_entry_offs = sg[j].offset; + orig_sg->orig_entry_len = sg[j].length; + + sg[j].offset += offs; + sg[j].length -= offs; + *res_sg = &sg[j]; + *res_sg_cnt = *sg_cnt - j; + + TRACE_DBG("j %d, sg %p, off %d, len %d, cnt %d " + "(offs %d)", j, &sg[j], sg[j].offset, + sg[j].length, *res_sg_cnt, offs); + + res = 0; + break; + } + } + + if (res != 0) + goto out; + +#ifdef CONFIG_SCST_EXTRACHECKS + l = 0; + sg = *res_sg; + for (i = 0; i < *res_sg_cnt; i++) + l += sg[i].length; + + if (l != must_left) { + PRINT_ERROR("Incorrect length %d of adjusted sg (cmd %p, " + "expected %d)", l, cmd, must_left); + res = -EINVAL; + scst_check_restore_sg_buff(cmd); + goto out; + } +#endif + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns in res_sg the tail of cmd's adjusted on adjust_len, i.e. tail + * of it. In res_sg_cnt sg_cnt of res_sg returned. Cmd only used to store + * cmd->sg restore information. + * + * Parameter must_left defines how many bytes must left in res_sg to consider + * operation successful. + * + * Returns 0 on success or error code otherwise. + * + * NOTE! Before scst_restore_sg_buff() called cmd->sg is corrupted and + * can NOT be used! + */ +static int scst_adjust_sg_get_tail(struct scst_cmd *cmd, + struct scatterlist **res_sg, int *res_sg_cnt, + struct scatterlist **res_dif_sg, int *res_dif_sg_cnt, + int adjust_len, int must_left) +{ + int res; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->sg_buff_modified || cmd->dif_sg_buff_modified); + + res = __scst_adjust_sg_get_tail(cmd, cmd->sg, &cmd->sg_cnt, res_sg, + res_sg_cnt, adjust_len, &cmd->orig_sg, must_left); + if (res != 0) + goto out; + + cmd->sg_buff_modified = 1; + + if (cmd->dif_sg != NULL) { + adjust_len >>= (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + must_left >>= (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + + TRACE_DBG("DIF adjust_len %d, must_left %d", adjust_len, must_left); + + res = __scst_adjust_sg_get_tail(cmd, cmd->dif_sg, &cmd->dif_sg_cnt, + res_dif_sg, res_dif_sg_cnt, adjust_len, + &cmd->orig_dif_sg, must_left); + if (res != 0) { + scst_restore_sg_buff(cmd); + goto out; + } + + cmd->dif_sg_buff_modified = 1; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + /** * scst_restore_sg_buff() - restores modified sg buffer * @@ -3977,6 +4123,7 @@ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) #endif scst_init_mem_lim(&dev->dev_mem_lim); spin_lock_init(&dev->dev_lock); + INIT_LIST_HEAD(&dev->dev_exec_cmd_list); INIT_LIST_HEAD(&dev->blocked_cmd_list); INIT_LIST_HEAD(&dev->dev_tgt_dev_list); INIT_LIST_HEAD(&dev->dev_acg_dev_list); @@ -4007,6 +4154,9 @@ void scst_free_device(struct scst_device *dev) { TRACE_ENTRY(); + EXTRACHECKS_BUG_ON(dev->dev_scsi_atomic_cmd_active != 0); + EXTRACHECKS_BUG_ON(!list_empty(&dev->dev_exec_cmd_list)); + #ifdef CONFIG_SCST_EXTRACHECKS if (!list_empty(&dev->dev_tgt_dev_list) || !list_empty(&dev->dev_acg_dev_list)) { @@ -6133,6 +6283,258 @@ out_done: } EXPORT_SYMBOL_GPL(scst_write_same); +struct scst_cwr_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cwr_finish_fn; + + struct scst_cmd *cwr_orig_cmd; +}; + +static void scst_cwr_finished(struct scst_cwr_priv *cwrp) +{ + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("cwr cmd %p finished with status %d", cwr_cmd, cwr_cmd->status); + + kfree(cwrp); + + cwr_cmd->completed = 1; /* for success */ + cwr_cmd->scst_cmd_done(cwr_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; +} + +static void scst_cwr_write_cmd_finished(struct scst_cmd *cmd) +{ + struct scst_cwr_priv *cwrp = cmd->tgt_i_priv; + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("WRITE cmd %p finished (cwr cmd %p)", cmd, cwr_cmd); + + EXTRACHECKS_BUG_ON(cmd->cdb[0] != WRITE_16); + + if (cmd->status != 0) { + int rc; + TRACE_DBG("WRITE cmd %p (cwr cmd %p) finished not successfully", + cmd, cwr_cmd); + sBUG_ON(cmd->resp_data_len != 0); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(cwr_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(cwr_cmd, cmd->status); + } + if (rc != 0) { + /* Requeue possible UA */ + if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) + scst_requeue_ua(cmd, NULL, 0); + } + } + + scst_cwr_finished(cwrp); + + TRACE_EXIT(); + return; +} + +static void scst_cwr_read_cmd_finished(struct scst_cmd *cmd) +{ + struct scst_cwr_priv *cwrp = cmd->tgt_i_priv; + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + bool c, dif; + uint8_t write16_cdb[16]; + struct scst_cmd *wcmd; + int data_len, miscompare_offs, rc; + + TRACE_ENTRY(); + + TRACE_DBG("READ cmd %p finished (cwr cmd %p)", cmd, cwr_cmd); + + if (cmd->status != 0) { + int rc; + TRACE_DBG("Read cmd %p (cwr cmd %p) finished not successfully", + cmd, cwr_cmd); + sBUG_ON(cmd->resp_data_len != 0); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(cwr_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(cwr_cmd, cmd->status); + } + if (rc != 0) { + /* Requeue possible UA */ + if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) + scst_requeue_ua(cmd, NULL, 0); + } + goto out_finish; + } + + if (unlikely(test_bit(SCST_CMD_ABORTED, &cwr_cmd->cmd_flags))) { + TRACE_MGMT_DBG("cwr cmd %p aborted", cwr_cmd); + goto out_finish; + } + + c = sg_cmp(cmd->sg, cwr_cmd->sg, 0, 0, &miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , KM_USER0, KM_USER1 +#endif + ); + if (!c) { + scst_set_cmd_error_and_inf(cwr_cmd, + SCST_LOAD_SENSE(scst_sense_miscompare_error), miscompare_offs); + goto out_finish; + } + + data_len = cwr_cmd->data_len; + + /* We ignore the read PI checks as required by SBC */ + rc = scst_dif_process_write(cmd); + if (unlikely(rc != 0)) + goto out_finish; + + memset(write16_cdb, 0, sizeof(write16_cdb)); + write16_cdb[0] = WRITE_16; + write16_cdb[1] = cwr_cmd->cdb[1]; + put_unaligned_be64(cwr_cmd->lba, &write16_cdb[2]); + put_unaligned_be32(data_len >> cmd->dev->block_shift, &write16_cdb[10]); + + dif = scst_cmd_needs_dif_buf(cwr_cmd) && ((cwr_cmd->cdb[1] & 0xE0) != 0); + if (dif) + write16_cdb[1] |= cwr_cmd->cdb[1] & 0xE0; + + wcmd = scst_create_prepare_internal_cmd(cwr_cmd, write16_cdb, + sizeof(write16_cdb), SCST_CMD_QUEUE_HEAD_OF_QUEUE); + if (wcmd == NULL) + goto out_busy; + + wcmd->expected_data_direction = SCST_DATA_WRITE; + wcmd->expected_transfer_len_full = data_len; + if (dif) + wcmd->expected_transfer_len_full += + data_len >> (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + wcmd->expected_values_set = 1; + + wcmd->tgt_i_priv = cwrp; + + rc = scst_adjust_sg_get_tail(cwr_cmd, &wcmd->tgt_i_sg, + &wcmd->tgt_i_sg_cnt, &wcmd->tgt_i_dif_sg, + &wcmd->tgt_i_dif_sg_cnt, data_len, data_len); + /* + * It must not happen, because get_cdb_info_compare_and_write() + * supposed to ensure that. + */ + EXTRACHECKS_BUG_ON(rc != 0); + + wcmd->tgt_i_data_buf_alloced = 1; + + cwrp->cwr_finish_fn = scst_cwr_write_cmd_finished; + + TRACE_DBG("Adding WRITE(16) wcmd %p to head of active cmd list", wcmd); + spin_lock_irq(&wcmd->cmd_threads->cmd_list_lock); + list_add(&wcmd->cmd_list_entry, &wcmd->cmd_threads->active_cmd_list); + wake_up(&wcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&wcmd->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT(); + return; + +out_busy: + scst_set_busy(cwr_cmd); + +out_finish: + scst_cwr_finished(cwrp); + goto out; +} + +int scst_cmp_wr_local(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED; + struct scst_cwr_priv *cwrp; + uint8_t read16_cdb[16]; + struct scst_cmd *rcmd; + int data_len; + + TRACE_ENTRY(); + + /* COMPARE AND WRITE is SBC only command */ + EXTRACHECKS_BUG_ON(cmd->dev->type != TYPE_DISK); + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + if (unlikely(cmd->bufflen == 0)) { + TRACE(TRACE_MINOR, "Zero bufflen (cmd %p)", cmd); + goto out_done; + } + + /* ToDo: HWALIGN'ed kmem_cache */ + cwrp = kzalloc(sizeof(*cwrp), GFP_KERNEL); + if (cwrp == NULL) { + PRINT_ERROR("Unable to allocate cwr_priv (size %zd, cmd %p)", + sizeof(*cwrp), cmd); + goto out_busy; + } + + cwrp->cwr_orig_cmd = cmd; + cwrp->cwr_finish_fn = scst_cwr_read_cmd_finished; + + data_len = cmd->data_len; + + /* + * As required by SBC, DIF PI, if any, is not checked for the read part + */ + + memset(read16_cdb, 0, sizeof(read16_cdb)); + read16_cdb[0] = READ_16; + read16_cdb[1] = cmd->cdb[1] & ~0xE0; /* as required, see above */ + put_unaligned_be64(cmd->lba, &read16_cdb[2]); + put_unaligned_be32(data_len >> cmd->dev->block_shift, &read16_cdb[10]); + + rcmd = scst_create_prepare_internal_cmd(cmd, read16_cdb, + sizeof(read16_cdb), SCST_CMD_QUEUE_HEAD_OF_QUEUE); + if (rcmd == NULL) { + res = -ENOMEM; + goto out_free; + } + + rcmd->expected_data_direction = SCST_DATA_READ; + rcmd->expected_transfer_len_full = data_len; + rcmd->expected_values_set = 1; + + rcmd->tgt_i_priv = cwrp; + + TRACE_DBG("Adding READ(16) cmd %p to head of active cmd list", rcmd); + spin_lock_irq(&rcmd->cmd_threads->cmd_list_lock); + list_add(&rcmd->cmd_list_entry, &rcmd->cmd_threads->active_cmd_list); + wake_up(&rcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&rcmd->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + kfree(cwrp); + +out_busy: + scst_set_busy(cmd); + +out_done: + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + goto out; +} + + int scst_finish_internal_cmd(struct scst_cmd *cmd) { int res; @@ -6667,7 +7069,8 @@ void scst_free_cmd(struct scst_cmd *cmd) if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) TRACE_MGMT_DBG("Freeing aborted cmd %p", cmd); - EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed); + EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed || + cmd->on_dev_exec_list); /* * Target driver can already free sg buffer before calling @@ -7703,6 +8106,243 @@ EXPORT_SYMBOL(scst_scsi_exec_async); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) */ +/* + * Can switch to the next dst_sg element, so, to cmp to strictly only + * one dst_sg element, it must be either last in the chain, or + * cmp_len == dst_sg->length. + */ +static int sg_cmp_elem(struct scatterlist **pdst_sg, size_t *pdst_len, + size_t *pdst_offs, struct scatterlist *src_sg, + size_t cmp_len, int *miscompare_offs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + enum km_type d_km_type, enum km_type s_km_type, +#endif + bool *cmp_res) +{ + int res = 0; + struct scatterlist *dst_sg; + size_t src_len, dst_len, src_offs, dst_offs; + struct page *src_page, *dst_page; + void *saddr, *daddr; + + *cmp_res = true; + + dst_sg = *pdst_sg; + dst_len = *pdst_len; + dst_offs = *pdst_offs; + dst_page = sg_page(dst_sg); + + src_page = sg_page(src_sg); + src_len = src_sg->length; + src_offs = src_sg->offset; + + do { + size_t n; + int rc; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + saddr = kmap_atomic(src_page + (src_offs >> PAGE_SHIFT), s_km_type) + + (src_offs & ~PAGE_MASK); + daddr = kmap_atomic(dst_page + (dst_offs >> PAGE_SHIFT), d_km_type) + + (dst_offs & ~PAGE_MASK); +#else + saddr = kmap_atomic(src_page + (src_offs >> PAGE_SHIFT)) + + (src_offs & ~PAGE_MASK); + daddr = kmap_atomic(dst_page + (dst_offs >> PAGE_SHIFT)) + + (dst_offs & ~PAGE_MASK); +#endif + + if (((src_offs & ~PAGE_MASK) == 0) && + ((dst_offs & ~PAGE_MASK) == 0) && + (src_len >= PAGE_SIZE) && (dst_len >= PAGE_SIZE) && + (cmp_len >= PAGE_SIZE)) + n = PAGE_SIZE; + else { + n = min_t(size_t, PAGE_SIZE - (dst_offs & ~PAGE_MASK), + PAGE_SIZE - (src_offs & ~PAGE_MASK)); + n = min(n, src_len); + n = min(n, dst_len); + n = min_t(size_t, n, cmp_len); + } + + /* Optimized for rare miscompares */ + + rc = memcmp(daddr, saddr, n); + if (rc != 0) { + *cmp_res = false; + if (miscompare_offs != NULL) { + int i, len = n; + unsigned long saddr_int = (unsigned long)saddr; + unsigned long daddr_int = (unsigned long)daddr; + int align_size = sizeof(unsigned long long); + int align_mask = align_size-1; + + *miscompare_offs = -1; + + if (((saddr_int & align_mask) == 0) && + ((daddr_int & align_mask) == 0) && + ((len & align_mask) == 0)) { + /* Fast path: both buffers and len aligned */ + unsigned long long *s = saddr; + unsigned long long *d = daddr; + EXTRACHECKS_BUG_ON(len % align_size != 0); + len /= align_size; + for (i = 0; i < len; i++) { + if (s[i] != d[i]) { + uint8_t *s8 = (uint8_t *)&s[i]; + uint8_t *d8 = (uint8_t *)&d[i]; + int j; + for (j = 0; j < align_size; j++) { + if (s8[j] != d8[j]) { + *miscompare_offs = i * align_size + j; + break; + } + } + break; + } + } + } else { + uint8_t *s = saddr; + uint8_t *d = daddr; + for (i = 0; i < len; i++) { + if (s[i] != d[i]) { + *miscompare_offs = i; + break; + } + } + } + EXTRACHECKS_BUG_ON(*miscompare_offs == -1); + } + goto out_unmap; + } + + dst_offs += n; + src_offs += n; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + kunmap_atomic(saddr, s_km_type); + kunmap_atomic(daddr, d_km_type); +#else + kunmap_atomic(saddr); + kunmap_atomic(daddr); +#endif + + res += n; + cmp_len -= n; + if (cmp_len == 0) + goto out; + + src_len -= n; + dst_len -= n; + if (dst_len == 0) { + dst_sg = sg_next_inline(dst_sg); + if (dst_sg == NULL) + goto out; + dst_page = sg_page(dst_sg); + dst_len = dst_sg->length; + dst_offs = dst_sg->offset; + } + } while (src_len > 0); + +out: + *pdst_sg = dst_sg; + *pdst_len = dst_len; + *pdst_offs = dst_offs; + return res; + +out_unmap: +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + kunmap_atomic(saddr, s_km_type); + kunmap_atomic(daddr, d_km_type); +#else + kunmap_atomic(saddr); + kunmap_atomic(daddr); +#endif + goto out; +} + +/** + * sg_cmp - compare 2 SG vectors + * @dst_sg: SG 1 + * @src_sg: SG 2 + * @nents_to_cmp: maximum number of entries to compare + * @cmp_len: maximum amount of data to compare. If 0, then compare all. + * @miscompare_offs: offset of the first miscompare. Can be NULL. + * @d_km_type: kmap_atomic type for SG 1 + * @s_km_type: kmap_atomic type for SG 2 + * + * Description: + * Data from the first SG vector will be compired with the second SG + * vector. End of the vectors will be determined by sg_next() returning + * NULL. Returns true if both vectors have identical data, false otherwise. + * + * Note! It ignores size of the vectors, so SGs with different size with + * the same data in min(sg1_size, sg2_size) size will match! + */ +static bool sg_cmp(struct scatterlist *dst_sg, struct scatterlist *src_sg, + int nents_to_cmp, size_t cmp_len, int *miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , enum km_type d_km_type, enum km_type s_km_type +#endif + ) +{ + bool res = true; + size_t dst_len, dst_offs; + int compared_all = 0; + + TRACE_ENTRY(); + + TRACE_DBG("dst_sg %p, src_sg %p, nents_to_cmp %d, cmp_len %zd", + dst_sg, src_sg, nents_to_cmp, cmp_len); + + if (cmp_len == 0) + cmp_len = 0x7FFFFFFF; /* cmp all */ + + if (nents_to_cmp == 0) + nents_to_cmp = 0x7FFFFFFF; /* cmp all */ + + dst_len = dst_sg->length; + dst_offs = dst_sg->offset; + + do { + int compared; + + TRACE_DBG("dst_sg %p, dst_len %zd, dst_offs %zd, src_sg %p, " + "nents_to_cmp %d, cmp_len %zd, compared_all %d", + dst_sg, dst_len, dst_offs, src_sg, nents_to_cmp, + cmp_len, compared_all); + + compared = sg_cmp_elem(&dst_sg, &dst_len, &dst_offs, + src_sg, cmp_len, miscompare_offs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + d_km_type, s_km_type, +#endif + &res); + if (!res) { + if (miscompare_offs != NULL) { + *miscompare_offs += compared_all; + TRACE_DBG("miscompare_offs %d", + *miscompare_offs); + } + goto out; + } + cmp_len -= compared; + compared_all += compared; + if ((cmp_len == 0) || (dst_sg == NULL)) + goto out; + + nents_to_cmp--; + if (nents_to_cmp == 0) + goto out; + + src_sg = sg_next_inline(src_sg); + } while (src_sg != NULL); + +out: + TRACE_EXIT_RES(res); + return res; +} + /** * scst_copy_sg() - copy data between the command's SGs * diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 02dcd96e0..f6010f85f 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -808,6 +808,8 @@ void scst_unthrottle_cmd(struct scst_cmd *cmd); int scst_do_internal_parsing(struct scst_cmd *cmd); int scst_parse_descriptors(struct scst_cmd *cmd); +int scst_cmp_wr_local(struct scst_cmd *cmd); + int scst_pr_init(struct scst_device *dev); void scst_pr_cleanup(struct scst_device *dev); @@ -819,6 +821,27 @@ void scst_vfs_unlink_and_put(struct path *path); int scst_copy_file(const char *src, const char *dest); +struct scst_cmd *__scst_create_prepare_internal_cmd(const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type, + struct scst_tgt_dev *tgt_dev, gfp_t gfp_mask, bool fantom); + +static inline bool scst_lba1_inside_lba2(int64_t lba1, + int64_t lba2, int64_t lba2_blocks) +{ + bool res; + + TRACE_DBG("lba1 %lld, lba2 %lld, lba2_blocks %lld", (long long)lba1, + (long long)lba2, (long long)lba2_blocks); + + if ((lba1 >= lba2) && (lba1 < (lba2 + lba2_blocks))) + res = true; + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + #ifdef CONFIG_SCST_DEBUG_TM extern void tm_dbg_check_released_cmds(void); extern int tm_dbg_check_cmd(struct scst_cmd *cmd); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 512311068..e2218d793 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -103,25 +103,296 @@ static inline void scst_schedule_tasklet(struct scst_cmd *cmd) return; } -/* No locks */ -static bool scst_check_blocked_dev(struct scst_cmd *cmd) +static bool scst_unmap_overlap(struct scst_cmd *cmd, int64_t lba2, + int64_t lba2_blocks) +{ + bool res = false; + struct scst_data_descriptor *pd = cmd->cmd_data_descriptors; + int i; + + TRACE_ENTRY(); + + if (pd == NULL) + goto out; + + for (i = 0; pd[i].sdd_blocks != 0; i++) { + struct scst_data_descriptor *d = &pd[i]; + TRACE_DBG("i %d, lba %lld, blocks %lld", i, + (long long)d->sdd_lba, (long long)d->sdd_blocks); + res = scst_lba1_inside_lba2(d->sdd_lba, lba2, lba2_blocks); + if (res) + goto out; + res = scst_lba1_inside_lba2(lba2, d->sdd_lba, d->sdd_blocks); + if (res) + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_cwr(struct scst_cmd *cwr_cmd, struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("cwr_cmd %p, cmd %p (op %s, LBA valid %d, lba %lld, " + "len %lld)", cwr_cmd, cmd, scst_get_opcode_name(cmd), + (cmd->op_flags & SCST_LBA_NOT_VALID) == 0, + (long long)cmd->lba, (long long)cmd->data_len); + + EXTRACHECKS_BUG_ON(cwr_cmd->cdb[0] != COMPARE_AND_WRITE); + + /* + * In addition to requirements listed in "Model for uninterrupted + * sequences on LBA ranges" (SBC) VMware wants that COMPARE AND WRITE + * be atomic against RESERVEs, as well as RESERVEs be atomic against + * all COMPARE AND WRITE commands and only against them. + */ + + if (cmd->op_flags & SCST_LBA_NOT_VALID) { + switch (cmd->cdb[0]) { + case RESERVE: + case RESERVE_10: + res = true; + break; + case UNMAP: + res = scst_unmap_overlap(cmd, cwr_cmd->lba, + cwr_cmd->data_len); + break; + default: + res = false; + break; + } + goto out; + } + + /* If LBA valid, block_shift must be valid */ + EXTRACHECKS_BUG_ON(cmd->dev->block_shift <= 0); + + res = scst_lba1_inside_lba2(cwr_cmd->lba, cmd->lba, + cmd->data_len >> cmd->dev->block_shift); + if (res) + goto out; + + res = scst_lba1_inside_lba2(cmd->lba, cwr_cmd->lba, + cwr_cmd->data_len >> cwr_cmd->dev->block_shift); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_reserve(struct scst_cmd *reserve_cmd, + struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("reserve_cmd %p, cmd %p (op %s, LBA valid %d, lba %lld, " + "len %lld)", reserve_cmd, cmd, scst_get_opcode_name(cmd), + (cmd->op_flags & SCST_LBA_NOT_VALID) == 0, + (long long)cmd->lba, (long long)cmd->data_len); + + EXTRACHECKS_BUG_ON((reserve_cmd->cdb[0] != RESERVE) && + (reserve_cmd->cdb[0] != RESERVE_10)); + + /* + * In addition to requirements listed in "Model for uninterrupted + * sequences on LBA ranges" (SBC) VMware wants that COMPARE AND WRITE + * be atomic against RESERVEs, as well as RESERVEs be atomic against + * all COMPARE AND WRITE commands and only against them. + */ + + if (cmd->cdb[0] == COMPARE_AND_WRITE) + res = true; + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_atomic(struct scst_cmd *atomic_cmd, struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("atomic_cmd %p (op %s), cmd %p (op %s)", atomic_cmd, + scst_get_opcode_name(atomic_cmd), cmd, scst_get_opcode_name(cmd)); + + EXTRACHECKS_BUG_ON((atomic_cmd->op_flags & SCST_SCSI_ATOMIC) == 0); + + switch (atomic_cmd->cdb[0]) { + case COMPARE_AND_WRITE: + res = scst_cmd_overlap_cwr(atomic_cmd, cmd); + break; + case RESERVE: + case RESERVE_10: + res = scst_cmd_overlap_reserve(atomic_cmd, cmd); + break; + default: + res = false; + break; + } + + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap(struct scst_cmd *chk_cmd, struct scst_cmd *cmd) +{ + bool res = false; + + TRACE_ENTRY(); + + TRACE_DBG("chk_cmd %p, cmd %p", chk_cmd, cmd); + + if ((chk_cmd->op_flags & SCST_SCSI_ATOMIC) != 0) + res = scst_cmd_overlap_atomic(chk_cmd, cmd); + else if ((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) + res = scst_cmd_overlap_atomic(cmd, chk_cmd); + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +/* + * dev_lock supposed to be held and BH disabled. Returns true if cmd blocked, + * hence stop processing it and go to the next command. + */ +static bool scst_check_scsi_atomicity(struct scst_cmd *chk_cmd) +{ + bool res = false; + struct scst_device *dev = chk_cmd->dev; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + /* + * chk_cmd isn't necessary SCSI atomic! For instance, if another SCSI + * atomic cmd is waiting blocked. + */ + + TRACE_DBG("chk_cmd %p (op %s, internal %d, lba %lld, len %lld)", + chk_cmd, scst_get_opcode_name(chk_cmd), chk_cmd->internal, + (long long)chk_cmd->lba, (long long)chk_cmd->data_len); + + list_for_each_entry(cmd, &dev->dev_exec_cmd_list, dev_exec_cmd_list_entry) { + if (chk_cmd == cmd) + continue; + if (scst_cmd_overlap(chk_cmd, cmd)) { + struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + /* + * kmalloc() allocates by at least 32 bytes increments, + * hence krealloc() on 8 bytes increments, if not all + * that space is used, does nothing. + */ + p = krealloc(p, sizeof(*p) * (cmd->scsi_atomic_blocked_cmds_count + 1), + GFP_ATOMIC); + if (p == NULL) + goto out_busy_undo; + p[cmd->scsi_atomic_blocked_cmds_count] = chk_cmd; + cmd->scsi_atomic_blocked_cmds = p; + cmd->scsi_atomic_blocked_cmds_count++; + + chk_cmd->scsi_atomic_blockers++; + + TRACE_BLOCK("Delaying cmd %p (op %s, lba %lld, " + "len %lld, blockers %d) due to overlap with " + "cmd %p (op %s, lba %lld, len %lld, blocked " + "cmds %d)", chk_cmd, scst_get_opcode_name(chk_cmd), + (long long)chk_cmd->lba, + (long long)chk_cmd->data_len, + chk_cmd->scsi_atomic_blockers, cmd, + scst_get_opcode_name(cmd), (long long)cmd->lba, + (long long)cmd->data_len, + cmd->scsi_atomic_blocked_cmds_count); + res = true; + } + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy_undo: + list_for_each_entry(cmd, &dev->dev_exec_cmd_list, dev_exec_cmd_list_entry) { + struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + if ((p != NULL) && (p[cmd->scsi_atomic_blocked_cmds_count-1] == chk_cmd)) { + cmd->scsi_atomic_blocked_cmds_count--; + chk_cmd->scsi_atomic_blockers--; + } + } + sBUG_ON(chk_cmd->scsi_atomic_blockers != 0); + + scst_set_busy(chk_cmd); + scst_set_cmd_abnormal_done_state(chk_cmd); + + spin_lock_irq(&chk_cmd->cmd_threads->cmd_list_lock); + TRACE_MGMT_DBG("Adding on error chk_cmd %p back to head of active cmd " + "list", chk_cmd); + list_add(&chk_cmd->cmd_list_entry, &chk_cmd->cmd_threads->active_cmd_list); + wake_up(&chk_cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&chk_cmd->cmd_threads->cmd_list_lock); + + res = false; + goto out; +} + +/* + * dev_lock supposed to be BH locked. Returns true if cmd blocked, hence stop + * processing it and go to the next command. + */ +bool scst_do_check_blocked_dev(struct scst_cmd *cmd) { bool res; struct scst_device *dev = cmd->dev; TRACE_ENTRY(); - if (unlikely(cmd->internal)) { - /* - * The original command can already block the device and must - * hold reference to it, so internal command should always pass. - */ - sBUG_ON(dev->on_dev_cmd_count == 0); - res = false; - goto out; + /* + * We want to have fairness between just unblocked previously blocked + * SCSI atomic cmds and new cmds came after them. Otherwise, the new + * cmds can bypass the SCSI atomic cmds and make them unfairly wait + * again. So, we need to always, from the beginning, have blocked SCSI + * atomic cmds on the exec list, even if they blocked, as well + * as dev's SCSI atomic cmds counter incremented. + */ + + if (likely(!cmd->on_dev_exec_list)) { + list_add_tail(&cmd->dev_exec_cmd_list_entry, &dev->dev_exec_cmd_list); + cmd->on_dev_exec_list = 1; } - spin_lock_bh(&dev->dev_lock); + /* + * After a cmd passed SCSI atomicy check, there's no need to recheck SCSI + * atomicity for this cmd in future entrances here, because then all + * future overlapping with this cmd cmds will be blocked on it. + */ + + if (unlikely(((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) || + (dev->dev_scsi_atomic_cmd_active != 0)) && + !cmd->scsi_atomicity_checked) { + cmd->scsi_atomicity_checked = 1; + if ((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) { + dev->dev_scsi_atomic_cmd_active++; + TRACE_DBG("cmd %p (dev %p), scsi atomic_cmd_active %d", + cmd, dev, dev->dev_scsi_atomic_cmd_active); + } + + res = scst_check_scsi_atomicity(cmd); + if (res) { + EXTRACHECKS_BUG_ON(dev->dev_scsi_atomic_cmd_active == 0); + goto out; + } + } dev->on_dev_cmd_count++; cmd->dec_on_dev_needed = 1; @@ -140,8 +411,37 @@ static bool scst_check_blocked_dev(struct scst_cmd *cmd) cmd->dec_on_dev_needed = 0; TRACE_DBG("New dec on_dev_count %d (cmd %p)", dev->on_dev_cmd_count, cmd); + goto out; } +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * No locks. Returns true if cmd blocked, hence stop processing it and go to + * the next command. + */ +static bool scst_check_blocked_dev(struct scst_cmd *cmd) +{ + bool res; + struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (unlikely(cmd->internal)) { + /* + * The original command can already block the device and must + * hold reference to it, so internal command should always pass. + */ + sBUG_ON(dev->on_dev_cmd_count == 0); + res = false; + goto out; + } + + spin_lock_bh(&dev->dev_lock); + res = scst_do_check_blocked_dev(cmd); spin_unlock_bh(&dev->dev_lock); out: @@ -149,12 +449,67 @@ out: return res; } -/* No locks */ -static void scst_check_unblock_dev(struct scst_cmd *cmd) +/* dev_lock supposed to be held and BH disabled */ +static void scst_check_unblock_scsi_atomic_cmds(struct scst_cmd *cmd) +{ + int i; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->scsi_atomic_blocked_cmds_count == 0); + + for (i = 0; i < cmd->scsi_atomic_blocked_cmds_count; i++) { + struct scst_cmd *acmd = cmd->scsi_atomic_blocked_cmds[i]; + acmd->scsi_atomic_blockers--; + if (acmd->scsi_atomic_blockers == 0) { + TRACE_BLOCK("Unblocking blocked acmd %p (blocker " + "cmd %p)", acmd, cmd); + spin_lock_irq(&acmd->cmd_threads->cmd_list_lock); + if (acmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE) + list_add(&acmd->cmd_list_entry, + &acmd->cmd_threads->active_cmd_list); + else + list_add_tail(&acmd->cmd_list_entry, + &acmd->cmd_threads->active_cmd_list); + wake_up(&acmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&acmd->cmd_threads->cmd_list_lock); + } + } + + kfree(cmd->scsi_atomic_blocked_cmds); + cmd->scsi_atomic_blocked_cmds = NULL; + cmd->scsi_atomic_blocked_cmds_count = 0; + + TRACE_EXIT(); + return; +} + +/* dev_lock supposed to be BH locked */ +void __scst_check_unblock_dev(struct scst_cmd *cmd) { struct scst_device *dev = cmd->dev; - spin_lock_bh(&dev->dev_lock); + TRACE_ENTRY(); + + /* + * We might be called here as part of Copy Manager's check blocking + * undo, so restore all flags in the previous state to allow + * restart of this cmd. + */ + + if (likely(cmd->on_dev_exec_list)) { + list_del(&cmd->dev_exec_cmd_list_entry); + cmd->on_dev_exec_list = 0; + } + + if (unlikely((cmd->op_flags & SCST_SCSI_ATOMIC) != 0)) { + if (likely(cmd->scsi_atomicity_checked)) { + dev->dev_scsi_atomic_cmd_active--; + TRACE_DBG("cmd %p, scsi atomic_cmd_active %d", + cmd, dev->dev_scsi_atomic_cmd_active); + cmd->scsi_atomicity_checked = 0; + } + } if (likely(cmd->dec_on_dev_needed)) { dev->on_dev_cmd_count--; @@ -163,6 +518,9 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd) dev->on_dev_cmd_count, cmd); } + if (unlikely(cmd->scsi_atomic_blocked_cmds != NULL)) + scst_check_unblock_scsi_atomic_cmds(cmd); + if (unlikely(cmd->unblock_dev)) { TRACE_BLOCK("cmd %p (tag %llu): unblocking dev %s", cmd, (unsigned long long int)cmd->tag, dev->virt_name); @@ -184,7 +542,22 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd) } } + TRACE_EXIT(); + return; +} + +/* No locks */ +void scst_check_unblock_dev(struct scst_cmd *cmd) +{ + struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + __scst_check_unblock_dev(cmd); spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); return; } @@ -3145,6 +3518,7 @@ static scst_local_exec_fn scst_local_fns[256] = { [PERSISTENT_RESERVE_OUT] = scst_persistent_reserve_out_local, [REPORT_LUNS] = scst_report_luns_local, [REQUEST_SENSE] = scst_request_sense_local, + [COMPARE_AND_WRITE] = scst_cmp_wr_local, [MAINTENANCE_IN] = scst_maintenance_in, }; From 3fc775c75af650817a0f3b68a2dc2ac94ea002bd Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 03:58:00 +0000 Subject: [PATCH 24/82] EXTENDED COPY support Prepared with help from Sushma Gurram git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6589 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 71 + scst/include/scst_const.h | 23 +- scst/include/scst_user.h | 37 +- scst/src/Makefile | 1 + scst/src/dev_handlers/Makefile | 2 + scst/src/dev_handlers/scst_user.c | 221 +- scst/src/dev_handlers/scst_vdisk.c | 81 + scst/src/scst_copy_mgr.c | 3741 ++++++++++++++++++++++++++++ scst/src/scst_debug.c | 4 + scst/src/scst_lib.c | 57 +- scst/src/scst_main.c | 28 + scst/src/scst_priv.h | 68 +- scst/src/scst_targ.c | 52 +- usr/fileio/Makefile | 1 + usr/fileio/common.c | 173 +- usr/fileio/common.h | 1 + usr/fileio/fileio.c | 3 + 17 files changed, 4511 insertions(+), 53 deletions(-) create mode 100644 scst/src/scst_copy_mgr.c diff --git a/scst/include/scst.h b/scst/include/scst.h index 80645c437..aeaef440f 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -684,6 +684,7 @@ struct scst_acg; struct scst_acg_dev; struct scst_acn; struct scst_aen; +struct scst_ext_copy_seg_descr; struct scst_opcode_descriptor; /* @@ -1330,6 +1331,12 @@ struct scst_dev_type { */ unsigned pr_cmds_notifications:1; + /* + * Set if the Copy Manager can auto assing to devices of this + * template on their registration. + */ + unsigned auto_cm_assignment_possible:1; + /* * Called to parse CDB from the cmd and initialize * cmd->bufflen and cmd->data_direction (both - REQUIRED). @@ -1420,6 +1427,26 @@ struct scst_dev_type { */ void (*on_free_cmd)(struct scst_cmd *cmd); + /* + * Called during EXTENDED COPY command processing to let dev hander + * try to remap blocks at first. Upon finish, the dev handler supposed + * to call scst_ext_copy_remap_done(). See description of this + * function for more details. + * + * In case of error, the dev handler should set the corresponding sense + * to cmd and then call scst_ext_copy_remap_done(cmd, NULL, 0). + * + * It is highly recommended that in the normal circumstances + * scst_ext_copy_remap_done() called from another thread context, + * because otherwise there will be recursion in the segments processing. + * Hopefully, this thread context switch is natural for such + * potentially long operation. + * + * OPTIONAL + */ + void (*ext_copy_remap)(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *descr); + /* * Called to notify dev handler that a ALUA state change is about to * be started. Can be used to close open file handlers, which might @@ -1938,6 +1965,14 @@ struct scst_session { /* Used if scst_unregister_session() called in wait mode */ struct completion *shutdown_compl; + /* + * Keep list IDs for Extended Copy commands sent over this session. + * Protected by scst_cm_lock. + */ + struct list_head sess_cm_list_id_list; + + struct delayed_work sess_cm_list_id_cleanup_work; + /* sysfs release completion */ struct completion *sess_kobj_release_cmpl; @@ -2130,6 +2165,12 @@ struct scst_cmd { /* Set if cmd is internally generated */ unsigned int internal:1; + /* Set if local events should be checked for internally generated cmd */ + unsigned int internal_check_local_events:1; + + /* Set if the blocking machinery should be bypassed for this cmd */ + unsigned int bypass_blocking:1; + /* Set if the device was blocked by scst_check_blocked_dev() */ unsigned int unblock_dev:1; @@ -2501,6 +2542,7 @@ struct scst_cmd { uint64_t restart_waiting_time, rdy_to_xfer_time; uint64_t pre_exec_time, exec_time, dev_done_time; uint64_t xmit_time; + bool exec_time_counting; #endif #ifdef CONFIG_SCST_DEBUG_TM @@ -3422,6 +3464,7 @@ extern const struct scst_opcode_descriptor scst_op_descr_stpg; extern const struct scst_opcode_descriptor scst_op_descr_send_diagnostic; extern const struct scst_opcode_descriptor scst_op_descr_inquiry; +extern const struct scst_opcode_descriptor scst_op_descr_extended_copy; extern const struct scst_opcode_descriptor scst_op_descr_tur; extern const struct scst_opcode_descriptor scst_op_descr_reserve6; extern const struct scst_opcode_descriptor scst_op_descr_release6; @@ -5602,4 +5645,32 @@ int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode, int scst_pr_init_dev(struct scst_device *dev); void scst_pr_clear_dev(struct scst_device *dev); +struct scst_ext_copy_data_descr { + uint64_t src_lba; + uint64_t dst_lba; + int data_len; /* in bytes */ +}; + +struct scst_ext_copy_seg_descr { +#define SCST_EXT_COPY_SEG_DATA 0 +#if 0 /* not implemented yet */ +#define SCST_EXT_COPY_SEG_PR_REG 1 +#define SCST_EXT_COPY_SEG_PR_MOVE 2 +#endif + int type; + union { + struct { + struct scst_tgt_dev *src_tgt_dev; + struct scst_tgt_dev *dst_tgt_dev; + struct scst_ext_copy_data_descr data_descr; + }; + }; + /* Internal, don't touch! */ + int tgt_descr_offs; +}; + +void scst_ext_copy_remap_done(struct scst_cmd *ec_cmd, + struct scst_ext_copy_data_descr *dds, int dds_cnt); +int scst_ext_copy_get_cur_seg_data_len(struct scst_cmd *ec_cmd); + #endif /* __SCST_H */ diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 88327e234..42f5b9e0c 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -230,7 +230,8 @@ enum scst_cdb_flags { #endif SCST_SERIALIZED = 0x10000, SCST_STRICTLY_SERIALIZED = 0x20000|SCST_SERIALIZED, - SCST_DESCRIPTORS_BASED = 0x40000, + SCST_CAN_GEN_3PARTY_COMMANDS = 0x40000, + SCST_DESCRIPTORS_BASED = 0x80000, SCST_SCSI_ATOMIC = 0x100000, }; @@ -305,6 +306,8 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define scst_sense_set_target_pgs_failed HARDWARE_ERROR, 0x67, 0xA /* ILLEGAL_REQUEST is 5 */ +#define scst_sense_operation_in_progress ILLEGAL_REQUEST, 0x0, 0x16 +#define scst_sense_parameter_list_length_invalid ILLEGAL_REQUEST, 0x1A, 0 #define scst_sense_invalid_opcode ILLEGAL_REQUEST, 0x20, 0 #define scst_sense_block_out_range_error ILLEGAL_REQUEST, 0x21, 0 /* Don't use it directly, use scst_set_invalid_field_in_cdb() instead! */ @@ -314,6 +317,10 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define scst_sense_invalid_field_in_parm_list ILLEGAL_REQUEST, 0x26, 0 #define scst_sense_parameter_value_invalid ILLEGAL_REQUEST, 0x26, 2 #define scst_sense_invalid_release ILLEGAL_REQUEST, 0x26, 4 +#define scst_sense_too_many_target_descriptors ILLEGAL_REQUEST, 0x26, 6 +#define scst_sense_unsupported_tgt_descr_type ILLEGAL_REQUEST, 0x26, 7 +#define scst_sense_unsupported_seg_descr_type ILLEGAL_REQUEST, 0x26, 9 +#define scst_sense_inline_data_length_exceeded ILLEGAL_REQUEST, 0x26, 0xB #define scst_sense_saving_params_unsup ILLEGAL_REQUEST, 0x39, 0 #define scst_sense_invalid_message ILLEGAL_REQUEST, 0x49, 0 #define scst_sense_parameter_list_length_invalid ILLEGAL_REQUEST, 0x1A, 0 @@ -421,6 +428,14 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define WRITE_SAME_16 0x93 #endif +#ifndef EXTENDED_COPY +#define EXTENDED_COPY 0x83 +#endif + +#ifndef RECEIVE_COPY_RESULTS +#define RECEIVE_COPY_RESULTS 0x84 +#endif + #ifndef SYNCHRONIZE_CACHE_16 #define SYNCHRONIZE_CACHE_16 0x91 #endif @@ -590,6 +605,12 @@ enum { #define SCSI_TRANSPORTID_PROTOCOLID_ISCSI 5 #define SCSI_TRANSPORTID_PROTOCOLID_SAS 6 +/* + * SCST extension. Added, because there is no TransportID for Copy Manager + * defined anywhere in the SCSI standards. + */ +#define SCST_TRANSPORTID_PROTOCOLID_COPY_MGR 0xC + /** * enum scst_tg_state - SCSI target port group asymmetric access state. * diff --git a/scst/include/scst_user.h b/scst/include/scst_user.h index 435efb3d7..1ec529b11 100644 --- a/scst/include/scst_user.h +++ b/scst/include/scst_user.h @@ -71,10 +71,11 @@ #define UCMD_STATE_BUF_ALLOCING 2 #define UCMD_STATE_EXECING 3 #define UCMD_STATE_ON_FREEING 4 -#define UCMD_STATE_ON_FREE_SKIPPED 5 -#define UCMD_STATE_ON_CACHE_FREEING 6 -#define UCMD_STATE_TM_RECEIVED_EXECING 7 -#define UCMD_STATE_TM_DONE_EXECING 8 +#define UCMD_STATE_ON_CACHE_FREEING 5 +#define UCMD_STATE_EXT_COPY_REMAPPING 6 +#define UCMD_STATE_ON_FREE_SKIPPED 7 +#define UCMD_STATE_TM_RECEIVED_EXECING 8 +#define UCMD_STATE_TM_DONE_EXECING 9 #define UCMD_STATE_ATTACH_SESS 0x20 #define UCMD_STATE_DETACH_SESS 0x21 @@ -96,6 +97,8 @@ struct scst_user_opt { uint8_t d_sense; uint8_t has_own_order_mgmt; + + uint8_t ext_copy_remap_supported; }; struct scst_user_dev_desc { @@ -212,6 +215,19 @@ struct scst_user_tm { uint8_t cmd_sn_set; }; +struct scst_user_ext_copy_data_descr { + aligned_u64 src_lba; + aligned_u64 dst_lba; + int32_t data_len; /* in bytes */ +}; + +struct scst_user_ext_copy_remap { + aligned_u64 sess_h; + aligned_u64 src_sess_h; + aligned_u64 dst_sess_h; + struct scst_user_ext_copy_data_descr data_descr; +}; + struct scst_user_get_cmd { uint32_t cmd_h; uint32_t subcode; @@ -224,6 +240,7 @@ struct scst_user_get_cmd { struct scst_user_scsi_on_free_cmd on_free_cmd; struct scst_user_on_cached_mem_free on_cached_mem_free; struct scst_user_tm tm_cmd; + struct scst_user_ext_copy_remap remap_cmd; }; }; @@ -285,6 +302,15 @@ struct scst_user_scsi_cmd_reply_exec { }; }; +/* Be careful adding new members here, this structure is allocated on stack! */ +struct scst_user_ext_copy_reply_remap { + aligned_u64 remap_descriptors; + uint16_t remap_descriptors_len; + uint8_t status; + uint8_t sense_len; + aligned_u64 psense_buffer; +}; + /* Be careful adding new members here, this structure is allocated on stack! */ struct scst_user_reply_cmd { uint32_t cmd_h; @@ -294,6 +320,7 @@ struct scst_user_reply_cmd { struct scst_user_scsi_cmd_reply_parse parse_reply; struct scst_user_scsi_cmd_reply_alloc_mem alloc_reply; struct scst_user_scsi_cmd_reply_exec exec_reply; + struct scst_user_ext_copy_reply_remap remap_reply; }; }; @@ -362,5 +389,7 @@ struct scst_user_get_multi { _IOWR('s', UCMD_STATE_TM_RECEIVED_EXECING, struct scst_user_tm) #define SCST_USER_TASK_MGMT_DONE \ _IOWR('s', UCMD_STATE_TM_DONE_EXECING, struct scst_user_tm) +#define SCST_USER_EXT_COPY_REMAP \ + _IOWR('s', UCMD_STATE_EXT_COPY_REMAPPING, struct scst_user_ext_copy_remap) #endif /* __SCST_USER_H */ diff --git a/scst/src/Makefile b/scst/src/Makefile index 7a6604030..51d83b986 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -57,6 +57,7 @@ scst-y += scst_dlm.o endif scst-y += scst_tg.o scst-y += scst_event.o +scst-y += scst_copy_mgr.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ obj-$(BUILD_DEV) += $(DEV_HANDLERS_DIR)/ diff --git a/scst/src/dev_handlers/Makefile b/scst/src/dev_handlers/Makefile index d5187a677..c59b81f88 100644 --- a/scst/src/dev_handlers/Makefile +++ b/scst/src/dev_handlers/Makefile @@ -92,6 +92,8 @@ EXTRA_CFLAGS += -DCONFIG_SCST_EXTRACHECKS #EXTRA_CFLAGS += -DCONFIG_SCST_TRACING EXTRA_CFLAGS += -DCONFIG_SCST_DEBUG -g -fno-inline -fno-inline-functions +#EXTRA_CFLAGS += -DCONFIG_DEBUG_EXT_COPY_REMAP + clean: rm -f *.o *.ko .*.cmd *.mod.c .*.d .depend Modules.symvers \ Module.symvers Module.markers modules.order diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index 035664925..c4c65a2a2 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -70,6 +70,7 @@ struct scst_user_dev { unsigned int swp:1; unsigned int d_sense:1; unsigned int has_own_order_mgmt:1; + unsigned int ext_copy_remap_supported:1; int (*generic_parse)(struct scst_cmd *cmd); @@ -949,6 +950,37 @@ static int dev_user_exec(struct scst_cmd *cmd) return res; } +static void dev_user_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *seg) +{ + struct scst_user_cmd *ucmd = cmd->dh_priv; + + TRACE_ENTRY(); + + TRACE_DBG("Preparing EXT_COPY_REMAP for user space (ucmd=%p, h=%d, " + "seg %p)", ucmd, ucmd->h, seg); + + ucmd->user_cmd_payload_len = + offsetof(struct scst_user_get_cmd, remap_cmd) + + sizeof(ucmd->user_cmd.remap_cmd); + ucmd->user_cmd.cmd_h = ucmd->h; + ucmd->user_cmd.subcode = SCST_USER_EXT_COPY_REMAP; + ucmd->user_cmd.remap_cmd.sess_h = (unsigned long)cmd->tgt_dev; + + ucmd->user_cmd.remap_cmd.src_sess_h = (unsigned long)seg->src_tgt_dev; + ucmd->user_cmd.remap_cmd.dst_sess_h = (unsigned long)seg->dst_tgt_dev; + ucmd->user_cmd.remap_cmd.data_descr.src_lba = seg->data_descr.src_lba; + ucmd->user_cmd.remap_cmd.data_descr.dst_lba = seg->data_descr.dst_lba; + ucmd->user_cmd.remap_cmd.data_descr.data_len = seg->data_descr.data_len; + + ucmd->state = UCMD_STATE_EXT_COPY_REMAPPING; + + dev_user_add_to_ready(ucmd); + + TRACE_EXIT(); + return; +} + static void dev_user_free_sgv(struct scst_user_cmd *ucmd) { if (ucmd->sgv != NULL) { @@ -1180,7 +1212,8 @@ static void dev_user_add_to_ready(struct scst_user_cmd *ucmd) &dev->ready_cmd_list); } do_wake |= ((ucmd->state == UCMD_STATE_ON_CACHE_FREEING) || - (ucmd->state == UCMD_STATE_ON_FREEING)); + (ucmd->state == UCMD_STATE_ON_FREEING) || + (ucmd->state == UCMD_STATE_EXT_COPY_REMAPPING)); } if (do_wake) { @@ -1444,6 +1477,150 @@ static int dev_user_process_reply_on_cache_free(struct scst_user_cmd *ucmd) return res; } +static int dev_user_process_reply_ext_copy_remap(struct scst_user_cmd *ucmd, + struct scst_user_reply_cmd *reply) +{ + int res = 0, rc, count = 0, i, len; + struct scst_user_ext_copy_reply_remap *rreply = &reply->remap_reply; + struct scst_cmd *cmd = ucmd->cmd; + uint8_t *buf; + struct scst_user_ext_copy_data_descr *uleft; + struct scst_ext_copy_data_descr *left = NULL; + + TRACE_ENTRY(); + + if (unlikely((rreply->status != 0) || (rreply->sense_len != 0))) + goto out_status; + + if (rreply->remap_descriptors_len == 0) { + TRACE_DBG("No remap leftovers (ucmd %p, cmd %p)", ucmd, cmd); + goto out_done; + } + + if (unlikely(rreply->remap_descriptors_len > PAGE_SIZE)) { + PRINT_ERROR("Too many leftover REMAP descriptors (len %d, ucmd %p, " + "cmd %p)", rreply->remap_descriptors_len, ucmd, cmd); + res = -EOVERFLOW; + goto out_hw_err; + } + + buf = kzalloc(rreply->remap_descriptors_len, GFP_KERNEL); + if (unlikely(buf == NULL)) { + PRINT_ERROR("Unable to alloc leftover remap descriptors buf " + "(size %d)", rreply->remap_descriptors_len); + goto out_busy; + } + + rc = copy_from_user(buf, + (void __user *)(unsigned long)rreply->remap_descriptors, + rreply->remap_descriptors_len); + if (unlikely(rc != 0)) { + PRINT_ERROR("Failed to copy %d leftover remap descriptors' " + "bytes", rc); + res = -EFAULT; + goto out_free_buf; + } + + uleft = (struct scst_user_ext_copy_data_descr *)buf; + count = rreply->remap_descriptors_len / sizeof(*uleft); + if (unlikely((rreply->remap_descriptors_len % sizeof(*uleft)) != 0)) { + PRINT_ERROR("Invalid leftover remap descriptors (ucmd %p, " + "cmd %p, count %d)", ucmd, cmd, count); + res = -EINVAL; + goto out_hw_err_free_buf; + } + + left = kmalloc(sizeof(*left) * count, GFP_KERNEL); + if (unlikely(left == NULL)) { + PRINT_ERROR("Unable to alloc leftover remap descriptors " + "(size %zd, count %d)", sizeof(*left) * count, count); + goto out_busy_free_buf; + } + + TRACE_DBG("count %d", count); + + len = 0; + for (i = 0; i < count; i++) { + TRACE_DBG("src_lba %lld, dst_lba %lld, data_len %d (len %d)", + (long long)uleft[i].src_lba, (long long)uleft[i].dst_lba, + uleft[i].data_len, len); + if (unlikely(uleft[i].data_len == 0)) { + PRINT_ERROR("Invalid data descr %d len %d (cmd %p)", i, + uleft[i].data_len, cmd); + res = -EINVAL; + goto out_hw_err_free_buf; + } + left[i].src_lba = uleft[i].src_lba; + left[i].dst_lba = uleft[i].dst_lba; + left[i].data_len = uleft[i].data_len; + len += uleft[i].data_len; + } + + if (unlikely(len > scst_ext_copy_get_cur_seg_data_len(cmd))) { + PRINT_ERROR("Invalid data descr len %d (cmd %p, seg descr len %d)", + len, cmd, scst_ext_copy_get_cur_seg_data_len(cmd)); + res = -EINVAL; + goto out_hw_err_free_buf; + } + +out_free_buf: + kfree(buf); + +out_done: + if (unlikely(res != 0)) { + kfree(left); + left = NULL; + count = 0; + } + + scst_ext_copy_remap_done(cmd, left, count); + + TRACE_EXIT_RES(res); + return res; + +out_hw_err: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_done; + +out_hw_err_free_buf: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_free_buf; + +out_busy_free_buf: + scst_set_busy(cmd); + goto out_free_buf; + +out_busy: + scst_set_busy(cmd); + goto out_done; + +out_status: + TRACE_DBG("Remap finished with status %d (ucmd %p, cmd %p)", + rreply->status, ucmd, cmd); + + if (rreply->sense_len != 0) { + int sense_len; + + res = scst_alloc_sense(cmd, 0); + if (res != 0) + goto out_hw_err; + + sense_len = min_t(int, cmd->sense_buflen, rreply->sense_len); + + rc = copy_from_user(cmd->sense, + (void __user *)(unsigned long)rreply->psense_buffer, + sense_len); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d sense's bytes", rc); + res = -EFAULT; + goto out_hw_err; + } + cmd->sense_valid_len = sense_len; + } + scst_set_cmd_error_status(cmd, rreply->status); + goto out_done; +} + static int dev_user_process_ws_reply(struct scst_user_cmd *ucmd, struct scst_user_scsi_cmd_reply_exec *ereply) { @@ -1764,6 +1941,10 @@ unlock_process: res = dev_user_process_reply_on_cache_free(ucmd); break; + case UCMD_STATE_EXT_COPY_REMAPPING: + res = dev_user_process_reply_ext_copy_remap(ucmd, reply); + break; + case UCMD_STATE_TM_RECEIVED_EXECING: case UCMD_STATE_TM_DONE_EXECING: res = dev_user_process_reply_tm_exec(ucmd, @@ -2460,6 +2641,7 @@ static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy, break; case UCMD_STATE_EXECING: + case UCMD_STATE_EXT_COPY_REMAPPING: if (flags != NULL) spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock, *flags); @@ -2478,8 +2660,13 @@ static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy, SCST_LOAD_SENSE(scst_sense_lun_not_supported)); } - ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT, - SCST_CONTEXT_THREAD); + if (state == UCMD_STATE_EXECING) + ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT, + SCST_CONTEXT_THREAD); + else { + sBUG_ON(state != UCMD_STATE_EXT_COPY_REMAPPING); + scst_ext_copy_remap_done(ucmd->cmd, NULL, 0); + } /* !! At this point cmd and ucmd can be already freed !! */ if (flags != NULL) @@ -3018,6 +3205,11 @@ static void dev_user_setup_functions(struct scst_user_dev *dev) dev->devtype.dev_alloc_data_buf = dev_user_alloc_data_buf; dev->devtype.dev_done = NULL; + if (dev->ext_copy_remap_supported) + dev->devtype.ext_copy_remap = dev_user_ext_copy_remap; + else + dev->devtype.ext_copy_remap = NULL; + if (dev->parse_type != SCST_USER_PARSE_CALL) { switch (dev->devtype.type) { case TYPE_DISK: @@ -3471,9 +3663,10 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, " "memory_reuse_type %x, partial_transfers_type %x, " - "partial_len %d", dev->name, opt->parse_type, - opt->on_free_cmd_type, opt->memory_reuse_type, - opt->partial_transfers_type, opt->partial_len); + "partial_len %d, opt->ext_copy_remap_supported %d", + dev->name, opt->parse_type, opt->on_free_cmd_type, + opt->memory_reuse_type, opt->partial_transfers_type, + opt->partial_len, opt->ext_copy_remap_supported); if (opt->parse_type > SCST_USER_MAX_PARSE_OPT || opt->on_free_cmd_type > SCST_USER_MAX_ON_FREE_CMD_OPT || @@ -3492,12 +3685,13 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, ((opt->qerr == SCST_QERR_2_RESERVED) || (opt->qerr > SCST_QERR_3_ABORT_THIS_NEXUS_ONLY)) || (opt->swp > 1) || (opt->tas > 1) || (opt->has_own_order_mgmt > 1) || - (opt->d_sense > 1)) { + (opt->d_sense > 1) || (opt->ext_copy_remap_supported > 1)) { PRINT_ERROR("Invalid SCSI option (tst %x, tmf_only %x, " "queue_alg %x, qerr %x, swp %x, tas %x, d_sense %d, " - "has_own_order_mgmt %x)", + "has_own_order_mgmt %x, ext_copy_remap_supported %d)", opt->tst, opt->tmf_only, opt->queue_alg, opt->qerr, - opt->swp, opt->tas, opt->d_sense, opt->has_own_order_mgmt); + opt->swp, opt->tas, opt->d_sense, opt->has_own_order_mgmt, + opt->ext_copy_remap_supported); res = -EINVAL; goto out; } @@ -3526,6 +3720,7 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, dev->tas = opt->tas; dev->d_sense = opt->d_sense; dev->has_own_order_mgmt = opt->has_own_order_mgmt; + dev->ext_copy_remap_supported = opt->ext_copy_remap_supported; if (dev->sdev != NULL) { dev->sdev->tst = opt->tst; dev->sdev->tmf_only = opt->tmf_only; @@ -3595,12 +3790,14 @@ static int dev_user_get_opt(struct file *file, void __user *arg) opt.swp = dev->swp; opt.d_sense = dev->d_sense; opt.has_own_order_mgmt = dev->has_own_order_mgmt; + opt.ext_copy_remap_supported = dev->ext_copy_remap_supported; TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, " "memory_reuse_type %x, partial_transfers_type %x, " - "partial_len %d", dev->name, opt.parse_type, - opt.on_free_cmd_type, opt.memory_reuse_type, - opt.partial_transfers_type, opt.partial_len); + "partial_len %d, ext_copy_remap_supported %d", dev->name, + opt.parse_type, opt.on_free_cmd_type, opt.memory_reuse_type, + opt.partial_transfers_type, opt.partial_len, + opt.ext_copy_remap_supported); rc = copy_to_user(arg, &opt, sizeof(opt)); if (unlikely(rc != 0)) { diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 8271555f8..e47d79631 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -357,6 +357,10 @@ static ssize_t vcdrom_del_device(const char *device_name); static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, struct scst_tgt_dev *tgt_dev); static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name); +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP +static void vdev_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *descr); +#endif static int vdisk_unmap_range(struct scst_cmd *cmd, struct scst_vdisk_dev *virt_dev, uint64_t start_lba, uint32_t blocks); @@ -677,6 +681,7 @@ static struct scst_dev_type vdisk_file_devtype = { .threads_num = -1, .parse_atomic = 1, .dev_done_atomic = 1, + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, @@ -686,6 +691,9 @@ static struct scst_dev_type vdisk_file_devtype = { .exec = fileio_exec, .on_free_cmd = fileio_on_free_cmd, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP + .ext_copy_remap = vdev_ext_copy_remap, +#endif .get_supported_opcodes = vdisk_get_supported_opcodes, .devt_priv = (void *)fileio_ops, #ifdef CONFIG_SCST_PROC @@ -734,6 +742,7 @@ static struct scst_dev_type vdisk_blk_devtype = { #ifdef CONFIG_SCST_PROC .no_proc = 1, #endif + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, @@ -784,6 +793,7 @@ static struct scst_dev_type vdisk_null_devtype = { #ifdef CONFIG_SCST_PROC .no_proc = 1, #endif + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, @@ -827,6 +837,7 @@ static struct scst_dev_type vcdrom_devtype = { .threads_num = -1, .parse_atomic = 1, .dev_done_atomic = 1, + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, @@ -2757,6 +2768,7 @@ static vdisk_op_fn nullio_ops[256] = { &scst_op_descr_write_same16, \ &scst_op_descr_unmap, \ &scst_op_descr_format_unit, \ + &scst_op_descr_extended_copy, \ &scst_op_descr_cwr, static const struct scst_opcode_descriptor *vdisk_opcode_descriptors[] = { @@ -4256,6 +4268,7 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, if (virt_dev->expl_alua) buf[5] |= SCST_INQ_TPGS_MODE_EXPLICIT; } + buf[5] |= 8; /* 3PC */ buf[6] = 0x10; /* MultiP 1 */ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ @@ -7365,6 +7378,74 @@ static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, return; } +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP +static void vdev_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *seg) +{ + struct scst_ext_copy_data_descr *d; + static int shift = 0; + static DEFINE_SPINLOCK(lock); + int s; + + TRACE_ENTRY(); + + if (seg->data_descr.data_len <= 4096) { + /* No way to split */ + goto out_done; + } + + d = kzalloc(sizeof(*d)*2, GFP_KERNEL); + if (d == NULL) + goto out_busy; + + spin_lock(&lock); + + shift += 4096; + + if (shift >= seg->data_descr.data_len) { + shift = 0; + s = 0; + } else + s = shift; + + TRACE_DBG("cmd %p, seg %p, data_len %d, shift %d, s %d", cmd, seg, + seg->data_descr.data_len, shift, s); + + spin_unlock(&lock); + + if (s == 0) + goto out_free_done; + + d[0].data_len = s; + d[0].src_lba = seg->data_descr.src_lba; + d[0].dst_lba = seg->data_descr.dst_lba; + + d[1].data_len = seg->data_descr.data_len - s; + d[1].src_lba = seg->data_descr.src_lba + (s >> seg->src_tgt_dev->dev->block_shift); + d[1].dst_lba = seg->data_descr.dst_lba + (s >> seg->dst_tgt_dev->dev->block_shift); + + scst_ext_copy_remap_done(cmd, d, 2); + +out: + TRACE_EXIT(); + return; + +out_busy: + scst_set_busy(cmd); + +out_free_done: + kfree(d); + +out_done: +#if 1 + scst_ext_copy_remap_done(cmd, &seg->data_descr, 1); +#else + scst_ext_copy_remap_done(cmd, NULL, 0); +#endif + goto out; +} +#endif + static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev) { enum { buf_size = 256 }; diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c new file mode 100644 index 000000000..63183f206 --- /dev/null +++ b/scst/src/scst_copy_mgr.c @@ -0,0 +1,3741 @@ +/* + * Copyright (C) 2013 - 2015 SanDisk Corporation + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "scst.h" +#include "scst_priv.h" +#include "scst_pres.h" + +#ifdef CONFIG_SCST_PROC +#warning In the PROCFS build EXTENDED COPY not supported +#else + +#define SCST_CM_NAME "copy_manager" +#define SCST_CM_TGT_NAME (SCST_CM_NAME "_tgt") +#define SCST_CM_SESS_NAME (SCST_CM_NAME "_sess") + +#define SCST_CM_TID_SIZE 24 +#define SCST_CM_TID_ID "COPY_MGR" + +#define SCST_CM_RETRIES_WAIT HZ +#define SCST_CM_MAX_RETRIES_TIME (30*HZ) +#define SCST_CM_ID_KEEP_TIME (5*HZ) + +#define SCST_CM_MAX_EACH_IO_SIZE (512*1024) + +/* Too big value is not too good for the blocking machinery */ +#define SCST_CM_MAX_TGT_DESCR_CNT 5 + +static struct scst_tgt *scst_cm_tgt; +static struct scst_session *scst_cm_sess; + +/* Protected by scst_mutex */ +static unsigned int scst_cm_next_lun; + +static DEFINE_MUTEX(scst_cm_mutex); + +/* Protected by scst_cm_mutex */ +static LIST_HEAD(scst_cm_desig_list); + +struct scst_cm_desig { + struct list_head cm_desig_list_entry; + + struct scst_tgt_dev *desig_tgt_dev; + + int desig_len; + uint8_t desig[]; +}; + +/* It's IRQ and inner for sess_list_lock */ +static spinlock_t scst_cm_lock; + +/* Necessary fields protected by scst_cm_lock */ +struct scst_cm_list_id { + struct list_head sess_cm_list_id_entry; + + int cm_lid; + +#define SCST_CM_LIST_ID_STATE_ACTIVE 0 +#define SCST_CM_LIST_ID_STATE_DONE 1 +#define SCST_CM_LIST_ID_STATE_PENDING_FREE 2 + int cm_list_id_state; + + unsigned int cm_done:1; + unsigned int cm_can_be_immed_free:1; + + int cm_segs_processed; + int64_t cm_written_size; /* in bytes */ + + unsigned long cm_time_to_free; /* in jiffies */ + + int cm_status; + unsigned short cm_sense_len; + uint8_t cm_sense[SCST_SENSE_BUFFERSIZE]; +}; + +struct scst_cm_internal_cmd_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cm_finish_fn; + + /* Internal cmd itself. Needed for unified cleanups and aborts. */ + struct scst_cmd *cm_cmd; + + /* E.g. rcmd for WRITEs, ec_cmd for READs, etc. */ + struct scst_cmd *cm_orig_cmd; + + struct list_head cm_internal_cmd_list_entry; +}; + +struct scst_cm_dev_entry { + struct list_head cm_sorted_devs_list_entry; + struct scst_cmd *cm_fcmd; /* can point to the real EC cmd as well! */ +}; + +/* + * Most fields here not protected, since can be accessed only from a single + * thread at time. + */ +struct scst_cm_ec_cmd_priv { + struct scst_cm_list_id *cm_list_id; + + /* + * List of all devices involved in this EC cmd (structs + * scst_cm_dev_entry), sorted by their dev's address. + */ + struct list_head cm_sorted_devs_list; + + /* + * List of all generated internal commands. Where needed, protected + * by scst_cm_lock. + */ + struct list_head cm_internal_cmd_list; + +#define SCST_CM_ERROR_NONE 0 +#define SCST_CM_ERROR_READ 1 +#define SCST_CM_ERROR_WRITE 2 + int cm_error; + + struct mutex cm_mutex; + + int cm_cur_in_flight; /* commands */ + + /** + ** READ commands stuff + **/ + struct scst_tgt_dev *cm_read_tgt_dev; + int64_t cm_start_read_lba; /* in blocks */ + int64_t cm_cur_read_lba; /* in blocks */ + int cm_left_to_read; /* in blocks */ + int cm_max_each_read;/* in blocks */ + + /** + ** WRITE commands stuff + **/ + struct scst_tgt_dev *cm_write_tgt_dev; + int64_t cm_start_write_lba; /* in blocks */ + int64_t cm_written; /* in bytes */ + + /** + ** Current data descriptors and their count + **/ + const struct scst_ext_copy_data_descr *cm_data_descrs; + int cm_data_descrs_cnt; + int cm_cur_data_descr; + + int cm_cur_seg_descr; + + /** + ** Parsed descriptors. Number of them is in + ** cmd->cmd_data_descriptors_cnt + **/ + struct scst_ext_copy_seg_descr cm_seg_descrs[]; +}; + +#define SCST_ALLOW_NOT_CONN_COPY_DEF 0 +/* Not protected, because no need */ +static bool scst_cm_allow_not_connected_copy = SCST_ALLOW_NOT_CONN_COPY_DEF; + +#define SCST_CM_STATUS_CMD_SUCCEEDED 0 +#define SCST_CM_STATUS_RETRY 1 +#define SCST_CM_STATUS_CMD_FAILED -1 + +typedef void (*scst_cm_retry_fn_t)(struct scst_cmd *cmd); + +struct scst_cm_retry { + struct scst_cmd *cm_retry_cmd; + struct delayed_work cm_retry_work; + scst_cm_retry_fn_t cm_retry_fn; +}; + +static void scst_cm_retry_work_fn(struct delayed_work *work) +{ + struct scst_cm_retry *retry = container_of(work, struct scst_cm_retry, + cm_retry_work); + + TRACE_ENTRY(); + + TRACE_DBG("Retrying cmd %p", retry->cm_retry_cmd); + + retry->cm_retry_fn(retry->cm_retry_cmd); + + __scst_cmd_put(retry->cm_retry_cmd); + + kfree(retry); + + TRACE_EXIT(); + return; +} + +/* + * Checks if cmd finished successfully and performs/schedules retry, if necessary. + * Returns one of SCST_CM_STATUS_* codes. + */ +static int scst_cm_err_check_retry(struct scst_cmd *cmd, + unsigned long start_time, scst_cm_retry_fn_t retry_fn) +{ + int res = SCST_CM_STATUS_CMD_SUCCEEDED; + unsigned long cur_time, max_retry_time, next_retry_time; + struct scst_cm_retry *retry; + bool imm_retry = false; + + TRACE_ENTRY(); + + /* cmd->dev and tgt_dev can be NULL here! */ + + TRACE_DBG("cmd %p, status %d, aborted %d", cmd, cmd->status, + scst_cmd_aborted(cmd)); + + if (likely((cmd->status == 0) && !scst_cmd_aborted(cmd))) + goto out; + + cur_time = jiffies; + max_retry_time = start_time + SCST_CM_MAX_RETRIES_TIME; + + mutex_lock(&scst_cm_mutex); + + if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { + if (test_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags)) { + TRACE_MGMT_DBG("Cmd %p aborted by other initiator, " + "retry possible", cmd); + goto try_retry; + } else { + TRACE_MGMT_DBG("Cmd %p aborted, no retry ", cmd); + goto out_err_unlock; + } + } + + if ((cmd->status == SAM_STAT_BUSY) || + (cmd->status == SAM_STAT_TASK_SET_FULL) || + (cmd->status == SAM_STAT_RESERVATION_CONFLICT) || + (cmd->status == SAM_STAT_ACA_ACTIVE)) { + TRACE_DBG("Cmd %p finished with status %d, retry possible", + cmd, cmd->status); + goto try_retry; + } + + if ((cmd->status == SAM_STAT_CHECK_CONDITION) && + scst_sense_valid(cmd->sense) && + scst_analyze_sense(cmd->sense, cmd->sense_valid_len, + SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0)) { + TRACE_DBG("Cmd %p finished with UA, immediate retry " + "possible", cmd); + imm_retry = true; + goto try_retry; + } + +out_err_unlock: + mutex_unlock(&scst_cm_mutex); + +out_failed: + res = SCST_CM_STATUS_CMD_FAILED; + +out: + TRACE_EXIT_RES(res); + return res; + +try_retry: + if (time_after_eq(cur_time, max_retry_time)) + goto out_err_unlock; + + next_retry_time = cur_time + SCST_CM_RETRIES_WAIT; + + TRACE_DBG("Retrying cmd %p (imm_retry %d, next_retry_time %ld, " + "cur_time %ld, " "start_time %ld, max_retry_time %ld): going " + "to sleep", cmd, imm_retry, next_retry_time, cur_time, + start_time, max_retry_time); + + mutex_unlock(&scst_cm_mutex); + + if (retry_fn == NULL) + goto out_retry_done; + + /* Wait before retry */ + + retry = kzalloc(sizeof(*retry), GFP_KERNEL); + if (retry == NULL) { + PRINT_ERROR("Unable to allocate retry struct"); + scst_set_busy(cmd); + goto out_failed; + } + retry->cm_retry_cmd = cmd; + __scst_cmd_get(cmd); + INIT_DELAYED_WORK(&retry->cm_retry_work, + (void (*)(struct work_struct *))scst_cm_retry_work_fn); + retry->cm_retry_fn = retry_fn; + + if (imm_retry) { + /* Let's use work to avoid possible recursion */ + TRACE_DBG("Immediate retry (cmd %p)", cmd); + schedule_work(&retry->cm_retry_work.work); + } else { + TRACE_DBG("Scheduling cmd %p retry", cmd); + schedule_delayed_work(&retry->cm_retry_work, + next_retry_time - cur_time); + } + +out_retry_done: + res = SCST_CM_STATUS_RETRY; + goto out; +} + +static bool scst_cm_is_ec_cmd_done(struct scst_cmd *ec_cmd) +{ + bool res; + + TRACE_ENTRY(); + + if (unlikely(test_bit(SCST_CMD_ABORTED, &ec_cmd->cmd_flags))) { + TRACE_MGMT_DBG("EC cmd %p aborted", ec_cmd); + res = true; + } else if (unlikely(ec_cmd->completed)) { + TRACE_MGMT_DBG("EC cmd %p already completed with status (%d)", + ec_cmd, ec_cmd->status); + res = true; + } else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_this_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd; + const struct scst_ext_copy_data_descr *dd; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_data_descr %d", ec_cmd, + priv->cm_cur_data_descr); + + EXTRACHECKS_BUG_ON(priv->cm_cur_data_descr > priv->cm_data_descrs_cnt); + + if (priv->cm_cur_data_descr == priv->cm_data_descrs_cnt) { + TRACE_DBG("No more data desriptors for ec_cmd %p", ec_cmd); + res = -ENOENT; + goto out; + } + + sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + dd = &priv->cm_data_descrs[priv->cm_cur_data_descr]; + + EXTRACHECKS_BUG_ON(dd->data_len == 0); + + priv->cm_read_tgt_dev = sd->src_tgt_dev; + priv->cm_start_read_lba = dd->src_lba; + priv->cm_cur_read_lba = dd->src_lba; + priv->cm_left_to_read = dd->data_len >> sd->src_tgt_dev->dev->block_shift; + priv->cm_max_each_read = SCST_CM_MAX_EACH_IO_SIZE >> sd->src_tgt_dev->dev->block_shift; + + priv->cm_write_tgt_dev = sd->dst_tgt_dev; + priv->cm_start_write_lba = dd->dst_lba; + + TRACE_DBG("len %d, src_lba %lld, dst_lba %lld", dd->data_len, + (long long)dd->src_lba, (long long)dd->dst_lba); + + if (unlikely((dd->data_len & (sd->src_tgt_dev->dev->block_size-1)) != 0) || + unlikely((dd->data_len & (sd->dst_tgt_dev->dev->block_size-1)) != 0)) { + PRINT_ERROR("Data len %d is not even for block size (src block " + "size %d, dst block size %d)", dd->data_len, + sd->src_tgt_dev->dev->block_size, + sd->dst_tgt_dev->dev->block_size); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + res = -EINVAL; + goto out; + } + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_next_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + priv->cm_cur_data_descr++; + + res = scst_cm_setup_this_data_descr(ec_cmd); + + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there are no data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_first_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + priv->cm_cur_data_descr = 0; + + res = scst_cm_setup_this_data_descr(ec_cmd); + + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_destroy_data_descrs(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, data_descrs %p, data_descrs_cnt %d ", ec_cmd, + priv->cm_data_descrs, priv->cm_data_descrs_cnt); + + if (priv->cm_data_descrs != &priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr) { + TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_MEMORY, "Freeing " + "data_descrs %p", priv->cm_data_descrs); + kfree(priv->cm_data_descrs); + } + + priv->cm_data_descrs = NULL; + priv->cm_data_descrs_cnt = 0; + + TRACE_EXIT(); + return; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there are no data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_data_descrs(struct scst_cmd *ec_cmd, + const struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, dds %p, dds_cnt %d", ec_cmd, dds, dds_cnt); + + EXTRACHECKS_BUG_ON(dds_cnt == 0); + + priv->cm_data_descrs = dds; + priv->cm_data_descrs_cnt = dds_cnt; + + res = scst_cm_setup_first_data_descr(ec_cmd); + if (unlikely(res != 0)) + goto out_destr; + +out: + TRACE_EXIT_RES(res); + return res; + +out_destr: + scst_cm_destroy_data_descrs(ec_cmd); + goto out; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more seg descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_seg_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_seg_descr %d", ec_cmd, + priv->cm_cur_seg_descr); + + EXTRACHECKS_BUG_ON(priv->cm_cur_seg_descr > ec_cmd->cmd_data_descriptors_cnt); + + while (1) { + if (priv->cm_cur_seg_descr == ec_cmd->cmd_data_descriptors_cnt) + goto out_enoent; + + sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + EXTRACHECKS_BUG_ON(sd->type != SCST_EXT_COPY_SEG_DATA); + if (sd->data_descr.data_len != 0) + break; + + priv->cm_cur_seg_descr++; + TRACE_DBG("ec_cmd %p, cm_cur_seg_descr %d", ec_cmd, + priv->cm_cur_seg_descr); + } + + if (priv->cm_list_id != NULL) { + /* SCSI: including the being processed one */ + priv->cm_list_id->cm_segs_processed = priv->cm_cur_seg_descr + 1; + } + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_enoent: + TRACE_DBG("ec_cmd %p finished", ec_cmd); + res = -ENOENT; + goto out; +} + +/* cm_mutex suppose to be locked or no activities on this ec_cmd's priv */ +static void scst_cm_advance_seg_descr(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + EXTRACHECKS_BUG_ON(priv->cm_cur_in_flight != 0); + + scst_cm_destroy_data_descrs(ec_cmd); + + priv->cm_cur_seg_descr++; + + TRACE_EXIT(); + return; +} + +static void scst_cm_prepare_final_sense(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t *fsense = NULL; + int d_sense = scst_get_cmd_dev_d_sense(ec_cmd); + bool copy_sense = false; + int sense_to_copy = ec_cmd->sense_valid_len; + int sense_len = ec_cmd->sense_valid_len; + + TRACE_ENTRY(); + + if (likely(priv->cm_error == SCST_CM_ERROR_NONE)) + goto out; + + TRACE_DBG("ec_cmd %p, cm_error %d, sense_to_copy %d", ec_cmd, + priv->cm_error, sense_to_copy); + + if (sense_to_copy > (SCST_SENSE_BUFFERSIZE - 18)) { + PRINT_WARNING("Too small sense buffer, %d bytes will be " + "truncated (ec_cmd %p)", + sense_to_copy - (SCST_SENSE_BUFFERSIZE-18), ec_cmd); + sense_to_copy = SCST_SENSE_BUFFERSIZE - 18; + } + + if ((priv->cm_error == SCST_CM_ERROR_WRITE) && + (ec_cmd->status != SAM_STAT_CHECK_CONDITION)) { + int rc; + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + + /* THIRD PARTY DEVICE FAILURE */ + + rc = scst_alloc_sense(ec_cmd, 0); + if (rc != 0) + goto out; + + TRACE_DBG("d_sense %d, cm_cur_seg_descr %d, cur_data_descr %d, " + "tgt_descr_offs %d", d_sense, priv->cm_cur_seg_descr, + priv->cm_cur_data_descr, sd->tgt_descr_offs); + + if (d_sense) { + /* Descriptor format */ + ec_cmd->sense[0] = 0x72; + ec_cmd->sense[1] = COPY_ABORTED; + ec_cmd->sense[2] = 0xD; /* ASC */ + ec_cmd->sense[3] = 1; /* ASCQ */ + ec_cmd->sense[7] = 20; /* additional Sense Length */ + + ec_cmd->sense[8] = 1; /* Command specific descriptor */ + ec_cmd->sense[9] = 0xA; + put_unaligned_be16(priv->cm_cur_seg_descr, &ec_cmd->sense[14]); + + ec_cmd->sense[20] = 2; /* Sense key specific descriptor */ + ec_cmd->sense[21] = 6; + ec_cmd->sense[24] = 0x80; + put_unaligned_be16(sd->tgt_descr_offs, &ec_cmd->sense[25]); + + ec_cmd->sense_valid_len = 16; + } else { + /* Fixed format */ + ec_cmd->sense[0] = 0x70; + ec_cmd->sense[2] = COPY_ABORTED; + ec_cmd->sense[7] = 0x0a; /* additional Sense Length */ + ec_cmd->sense[12] = 0xD; /* ASC */ + ec_cmd->sense[13] = 1; /* ASCQ */ + + put_unaligned_be16(priv->cm_cur_seg_descr, &ec_cmd->sense[10]); + + ec_cmd->sense[15] = 0x80; + put_unaligned_be16(sd->tgt_descr_offs, &ec_cmd->sense[16]); + + ec_cmd->sense_valid_len = 18; + } + + ec_cmd->status = SAM_STAT_CHECK_CONDITION; + goto out; + } + + fsense = mempool_alloc(scst_sense_mempool, GFP_KERNEL); + if (fsense == NULL) { + PRINT_ERROR("Allocation of the intermediate Extended Copy " + "sense buffer failed. Reported sense data can be " + "incorrect (ec_cmd %p)", ec_cmd); + goto out; + } + memset(fsense, 0, SCST_SENSE_BUFFERSIZE); + + if (d_sense) { + /* Descriptor format */ + fsense[0] = 0x72; + fsense[1] = COPY_ABORTED; + fsense[7] = 12; /* additional Sense Length */ + + fsense[8] = 1; /* Command specific descriptor */ + fsense[9] = 0xA; + put_unaligned_be16(priv->cm_cur_seg_descr, &fsense[14]); + + sense_len = 20; + } else { + /* Fixed format */ + fsense[0] = 0x70; + fsense[2] = COPY_ABORTED; + fsense[7] = 0x0a; /* additional Sense Length */ + + put_unaligned_be16(priv->cm_cur_seg_descr, &fsense[10]); + + if (priv->cm_error == SCST_CM_ERROR_READ) { + fsense[8] = 18; + copy_sense = scst_sense_valid(ec_cmd->sense); + } else if (priv->cm_error == SCST_CM_ERROR_WRITE) { + fsense[9] = 18; + copy_sense = scst_sense_valid(ec_cmd->sense); + } else + sBUG(); + + if (copy_sense) { + TRACE_DBG("Copying %db of old sense", sense_to_copy); + fsense[7] += 1 + sense_to_copy; + fsense[17] = ec_cmd->status; + memcpy(&fsense[18], ec_cmd->sense, sense_to_copy); + } + + sense_len = fsense[7] + 8; + TRACE_DBG("New sense len %d", sense_len); + } + + ec_cmd->status = SAM_STAT_CHECK_CONDITION; + if (ec_cmd->sense != NULL) { + memcpy(ec_cmd->sense, fsense, sense_len); + ec_cmd->sense_valid_len = sense_len; + } else + scst_alloc_set_sense(ec_cmd, 0, fsense, sense_len); + + mempool_free(fsense, scst_sense_mempool); + +out: + TRACE_EXIT(); + return; +} + +static void scst_cm_store_list_id_details(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_list_id *l = priv->cm_list_id; + + TRACE_ENTRY(); + + if (l != NULL) { + TRACE_DBG("List id %p done (status %d, sense valid %d, sense " + "len %d)", l, ec_cmd->status, scst_sense_valid(ec_cmd->sense), + ec_cmd->sense_valid_len); + spin_lock_irq(&scst_cm_lock); + l->cm_list_id_state = SCST_CM_LIST_ID_STATE_DONE; + if (ec_cmd->status != 0) { + l->cm_status = ec_cmd->status; + if (scst_sense_valid(ec_cmd->sense)) { + int len = ec_cmd->sense_valid_len; + if (len > sizeof(l->cm_sense)) { + PRINT_WARNING("EC command's sense is " + "too big (%d) with max allowed " + "%d, truncating", len, + (int)sizeof(l->cm_sense)); + len = sizeof(l->cm_sense); + } + l->cm_sense_len = ec_cmd->sense_valid_len; + memcpy(l->cm_sense, ec_cmd->sense, len); + } + } + spin_unlock_irq(&scst_cm_lock); + } + + TRACE_EXIT(); + return; +} + +static void scst_cm_ec_cmd_done(struct scst_cmd *ec_cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; +#endif + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p finished with status %d", ec_cmd, ec_cmd->status); + + EXTRACHECKS_BUG_ON(priv->cm_cur_in_flight != 0); + EXTRACHECKS_BUG_ON(priv->cm_data_descrs != NULL); + + scst_cm_prepare_final_sense(ec_cmd); + scst_cm_store_list_id_details(ec_cmd); + + ec_cmd->completed = 1; /* for success */ + ec_cmd->scst_cmd_done(ec_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; +} + +static void scst_cm_ec_sched_next_seg(struct scst_cmd *ec_cmd) +{ + TRACE_ENTRY(); + + /* No lock is needed, because it's all supposed to be calm */ + + scst_cm_advance_seg_descr(ec_cmd); + + /* + * There's recursion possible here, if dev handlers call after + * remapping scst_ext_copy_remap_done() from the same thread context. + */ + scst_cm_ext_copy_exec(ec_cmd); + + TRACE_EXIT(); + return; +} + +static void scst_cm_in_flight_cmd_finished(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int f; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + priv->cm_cur_in_flight--; + f = priv->cm_cur_in_flight; + + mutex_unlock(&priv->cm_mutex); + + TRACE_DBG("ec_cmd %p, priv->cm_cur_in_flight %d", ec_cmd, f); + + if (f > 0) + goto out; + + if (priv->cm_list_id != NULL) + priv->cm_list_id->cm_written_size += priv->cm_written; + + scst_cm_ec_sched_next_seg(ec_cmd); + +out: + TRACE_EXIT(); + return; +} + +static int scst_cm_add_to_internal_cmd_list(struct scst_cmd *cmd, + struct scst_cmd *ec_cmd, struct scst_cmd *orig_cmd, + scst_i_finish_fn_t finish_fn) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_internal_cmd_priv *p; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd == cmd); + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + PRINT_ERROR("Unable to alloc scst_cm_internal_cmd_priv " + "(size %d)", (int)sizeof(*p)); + goto out_enomem; + } + + p->cm_finish_fn = finish_fn; + p->cm_orig_cmd = orig_cmd; + p->cm_cmd = cmd; + + TRACE_DBG("Adding internal cmd %p (priv %p, ec_cmd %p, orig_cmd %p)", + cmd, p, ec_cmd, orig_cmd); + spin_lock_irq(&scst_cm_lock); + list_add_tail(&p->cm_internal_cmd_list_entry, &priv->cm_internal_cmd_list); + spin_unlock_irq(&scst_cm_lock); + + cmd->tgt_i_priv = p; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_enomem: + scst_set_busy(ec_cmd); + res = -ENOMEM; + goto out; +} + +static void scst_cm_del_free_from_internal_cmd_list(struct scst_cmd *cmd, + bool unblock_dev) +{ + struct scst_cm_internal_cmd_priv *p = cmd->tgt_i_priv; + + TRACE_ENTRY(); + + TRACE_DBG("Deleting/freeing internal cmd %p (op %s, priv %p, " + "orig_cmd %p)", cmd, scst_get_opcode_name(cmd), p, + p->cm_orig_cmd); + + spin_lock_irq(&scst_cm_lock); + list_del(&p->cm_internal_cmd_list_entry); + spin_unlock_irq(&scst_cm_lock); + + if (unblock_dev) { + TRACE_DBG("dev %p (internal cmd %p)", cmd->dev, cmd); + scst_check_unblock_dev(cmd); + } + + cmd->tgt_i_priv = NULL; + + kfree(p); + + TRACE_EXIT(); + return; +} + +static void scst_cm_read_cmd_finished(struct scst_cmd *rcmd); + +/* cm_mutex suppose to be locked */ +static int __scst_cm_push_single_read(struct scst_cmd *ec_cmd, + int64_t lba, int blocks) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t read_cdb[32]; + struct scst_device *rdev = priv->cm_read_tgt_dev->dev; + int block_shift = rdev->block_shift; + int len = blocks << block_shift; + struct scst_cmd *rcmd; + int cdb_len; + bool check_dif = (rdev->dev_dif_mode & SCST_DIF_MODE_DEV); + + TRACE_ENTRY(); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) { + TRACE_MGMT_DBG("EC cmd %p done: aborting further read " + "commands", ec_cmd); + priv->cm_left_to_read = 0; + res = -EPIPE; + goto out; + } + + memset(read_cdb, 0, sizeof(read_cdb)); + if (rdev->dev_dif_type != 2 || !check_dif) { + read_cdb[0] = READ_16; + if (check_dif) + read_cdb[1] |= 0x20; + put_unaligned_be64(lba, &read_cdb[2]); + put_unaligned_be32(blocks, &read_cdb[10]); + cdb_len = 16; + } else { + read_cdb[0] = VARIABLE_LENGTH_CMD; + put_unaligned_be16(SUBCODE_READ_32, &read_cdb[8]); + read_cdb[7] = 0x18; + cdb_len = 32; + read_cdb[10] = 0x20; + put_unaligned_be64(lba, &read_cdb[12]); + put_unaligned_be32(blocks, &read_cdb[28]); + put_unaligned_be32(lba & 0xFFFF, &read_cdb[20]); + /* No app tag check */ + } + + rcmd = __scst_create_prepare_internal_cmd(read_cdb, + cdb_len, SCST_CMD_QUEUE_SIMPLE, + priv->cm_read_tgt_dev, GFP_KERNEL, false); + if (rcmd == NULL) { + res = -ENOMEM; + goto out_busy; + } + + rcmd->internal_check_local_events = 1; + + rcmd->expected_data_direction = SCST_DATA_READ; + rcmd->expected_transfer_len_full = len; + if (check_dif != 0) + rcmd->expected_transfer_len_full += len >> (block_shift - SCST_DIF_TAG_SHIFT); + rcmd->expected_values_set = 1; + + res = scst_cm_add_to_internal_cmd_list(rcmd, ec_cmd, ec_cmd, + scst_cm_read_cmd_finished); + if (res != 0) + goto out_free_rcmd; + + TRACE_DBG("Adding ec_cmd's (%p) READ rcmd %p (lba %lld, blocks %d, " + "check_dif %d) to active cmd list", ec_cmd, rcmd, + (long long)rcmd->lba, blocks, check_dif); + spin_lock_irq(&rcmd->cmd_threads->cmd_list_lock); + list_add_tail(&rcmd->cmd_list_entry, &rcmd->cmd_threads->active_cmd_list); + spin_unlock_irq(&rcmd->cmd_threads->cmd_list_lock); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy: + scst_set_busy(ec_cmd); + goto out; + +out_free_rcmd: + __scst_cmd_put(rcmd); + goto out; +} + +static void scst_cm_read_retry_fn(struct scst_cmd *rcmd) +{ + struct scst_cm_internal_cmd_priv *p = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = p->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + rc = __scst_cm_push_single_read(ec_cmd, rcmd->lba, + rcmd->data_len >> priv->cm_read_tgt_dev->dev->block_shift); + + /* ec_cmd can get dead after we will drop cm_mutex! */ + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + mutex_unlock(&priv->cm_mutex); + + if (rc == 0) + wake_up(&priv->cm_read_tgt_dev->active_cmd_threads->cmd_list_waitQ); + else + scst_cm_in_flight_cmd_finished(ec_cmd); + + TRACE_EXIT(); + return; +} + +static int scst_cm_push_single_write(struct scst_cmd *ec_cmd, + int64_t lba, int blocks, struct scst_cmd *rcmd); + +static void scst_cm_write_retry_fn(struct scst_cmd *wcmd) +{ + struct scst_cm_internal_cmd_priv *p = wcmd->tgt_i_priv; + struct scst_cmd *rcmd = p->cm_orig_cmd; + struct scst_cm_internal_cmd_priv *rp = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = rp->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + rc = scst_cm_push_single_write(ec_cmd, wcmd->lba, + wcmd->data_len >> priv->cm_write_tgt_dev->dev->block_shift, + rcmd); + + /* ec_cmd can get dead after we will drop cm_mutex! */ + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + + mutex_unlock(&priv->cm_mutex); + + __scst_cmd_put(rcmd); + + if (rc != 0) + scst_cm_in_flight_cmd_finished(ec_cmd); + + TRACE_EXIT(); + return; +} + +static int scst_cm_push_single_read(struct scst_cmd *ec_cmd, int blocks, + bool inc_cur_in_flight); + +static void scst_cm_write_cmd_finished(struct scst_cmd *wcmd) +{ + struct scst_cm_internal_cmd_priv *p = wcmd->tgt_i_priv; + struct scst_cmd *rcmd = p->cm_orig_cmd; + struct scst_cm_internal_cmd_priv *rp = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = rp->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc, blocks; + + TRACE_ENTRY(); + + TRACE_DBG("Write cmd %p finished (ec_cmd %p, rcmd %p, cm_cur_in_flight %d)", + wcmd, rcmd, ec_cmd, priv->cm_cur_in_flight); + + EXTRACHECKS_BUG_ON(wcmd->cdb[0] != WRITE_16); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) + goto out_finished; + + rc = scst_cm_err_check_retry(wcmd, ec_cmd->start_time, scst_cm_write_retry_fn); + if (likely(rc == SCST_CM_STATUS_CMD_SUCCEEDED)) + goto cont; + else if (rc == SCST_CM_STATUS_RETRY) + goto out; + else { + TRACE_DBG("Write cmd %p (ec_cmd %p) finished not successfully", + wcmd, ec_cmd); + if (wcmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, wcmd->sense, + wcmd->sense_valid_len); + else { + sBUG_ON(wcmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, wcmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(wcmd->sense, wcmd->sense_valid_len)); + sBUG_ON(priv->cm_error == SCST_CM_ERROR_NONE); + } else + priv->cm_error = SCST_CM_ERROR_WRITE; + goto out_finished; + } + +cont: + priv->cm_written += wcmd->data_len; + TRACE_DBG("ec_cmd %p, cm_written %lld (data_len %lld)", ec_cmd, + (long long)priv->cm_written, (long long)wcmd->data_len); + + wcmd->sg = NULL; + wcmd->sg_cnt = 0; + + mutex_lock(&priv->cm_mutex); + + if (priv->cm_left_to_read == 0) { + if (priv->cm_cur_data_descr >= priv->cm_data_descrs_cnt) + goto out_unlock_finished; + + rc = scst_cm_setup_next_data_descr(ec_cmd); + if (rc != 0) + goto out_unlock_finished; + } + + EXTRACHECKS_BUG_ON(priv->cm_left_to_read == 0); + + blocks = min_t(int, priv->cm_left_to_read, priv->cm_max_each_read); + + rc = scst_cm_push_single_read(ec_cmd, blocks, false); + if (rc != 0) + goto out_unlock_finished; + + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + mutex_unlock(&priv->cm_mutex); + + wake_up(&rcmd->cmd_threads->cmd_list_waitQ); + +out_put: + __scst_cmd_put(rcmd); + +out: + TRACE_EXIT(); + return; + +out_unlock_finished: + mutex_unlock(&priv->cm_mutex); + +out_finished: + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + scst_cm_in_flight_cmd_finished(ec_cmd); + goto out_put; +} + +static int scst_cm_push_single_write(struct scst_cmd *ec_cmd, + int64_t lba, int blocks, struct scst_cmd *rcmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t write16_cdb[16]; + struct scst_cmd *wcmd; + int len; + + TRACE_ENTRY(); + + len = blocks << priv->cm_write_tgt_dev->dev->block_shift; + + /* + * ToDo: if rcmd is coming with tags SG, use it after updating ref and + * app tags instead of regenerating guard tags again with WRITE(16) + */ + + memset(write16_cdb, 0, sizeof(write16_cdb)); + write16_cdb[0] = WRITE_16; + put_unaligned_be64(lba, &write16_cdb[2]); + put_unaligned_be32(blocks, &write16_cdb[10]); + + wcmd = __scst_create_prepare_internal_cmd(write16_cdb, + sizeof(write16_cdb), SCST_CMD_QUEUE_SIMPLE, + priv->cm_write_tgt_dev, GFP_KERNEL, false); + if (wcmd == NULL) { + res = -ENOMEM; + goto out_busy; + } + + wcmd->internal_check_local_events = 1; + + wcmd->expected_data_direction = SCST_DATA_WRITE; + wcmd->expected_transfer_len_full = len; + wcmd->expected_values_set = 1; + + res = scst_cm_add_to_internal_cmd_list(wcmd, ec_cmd, rcmd, + scst_cm_write_cmd_finished); + if (res != 0) + goto out_free_wcmd; + + __scst_cmd_get(rcmd); + + wcmd->tgt_i_sg = rcmd->sg; + wcmd->tgt_i_sg_cnt = rcmd->sg_cnt; + wcmd->tgt_i_data_buf_alloced = 1; + + TRACE_DBG("Adding EC (%p) WRITE(16) cmd %p (lba %lld, blocks %d) to " + "active cmd list", ec_cmd, wcmd, (long long)wcmd->lba, blocks); + spin_lock_irq(&wcmd->cmd_threads->cmd_list_lock); + list_add_tail(&wcmd->cmd_list_entry, &wcmd->cmd_threads->active_cmd_list); + wake_up(&wcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&wcmd->cmd_threads->cmd_list_lock); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy: + scst_set_busy(ec_cmd); + goto out; + +out_free_wcmd: + __scst_cmd_put(wcmd); + goto out; +} + +static void scst_cm_read_cmd_finished(struct scst_cmd *rcmd) +{ + struct scst_cm_internal_cmd_priv *p = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = p->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int64_t lba; + int rc, len, blocks; + + TRACE_ENTRY(); + + TRACE_DBG("READ cmd %p finished (ec_cmd %p, p %p)", rcmd, ec_cmd, p); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) + goto out_finished; + + rc = scst_cm_err_check_retry(rcmd, ec_cmd->start_time, scst_cm_read_retry_fn); + if (likely(rc == SCST_CM_STATUS_CMD_SUCCEEDED)) + goto cont; + else if (rc == SCST_CM_STATUS_RETRY) + goto out; + else { + TRACE_DBG("Read cmd %p (ec_cmd %p) finished not successfully", + rcmd, ec_cmd); + if (rcmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, rcmd->sense, + rcmd->sense_valid_len); + else { + sBUG_ON(rcmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, rcmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(rcmd->sense, rcmd->sense_valid_len)); + sBUG_ON(priv->cm_error == SCST_CM_ERROR_NONE); + } else + priv->cm_error = SCST_CM_ERROR_READ; + goto out_finished; + } + +cont: + lba = rcmd->lba - priv->cm_start_read_lba; + lba <<= priv->cm_read_tgt_dev->dev->block_shift; + lba >>= priv->cm_write_tgt_dev->dev->block_shift; + lba += priv->cm_start_write_lba; + + len = rcmd->data_len; + blocks = len >> priv->cm_write_tgt_dev->dev->block_shift; + + TRACE_DBG("rcmd->lba %lld, start_read_lba %lld, read shift %d, write " + "shift %d, start_write_lba %lld, lba %lld, len %d, blocks %d", + (long long)rcmd->lba, (long long)priv->cm_start_read_lba, + priv->cm_read_tgt_dev->dev->block_shift, + priv->cm_write_tgt_dev->dev->block_shift, + (long long)priv->cm_start_write_lba, lba, len, blocks); + + rc = scst_cm_push_single_write(ec_cmd, lba, blocks, rcmd); + if (rc != 0) + goto out_finished; + +out: + TRACE_EXIT(); + return; + +out_finished: + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + scst_cm_in_flight_cmd_finished(ec_cmd); + goto out; +} + +/* cm_mutex suppose to be locked */ +static int scst_cm_push_single_read(struct scst_cmd *ec_cmd, int blocks, + bool inc_cur_in_flight) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_read_lba %lld, cm_left_to_read %d, " + "blocks %d", ec_cmd, (long long)priv->cm_cur_read_lba, + priv->cm_left_to_read, blocks); + + res = __scst_cm_push_single_read(ec_cmd, priv->cm_cur_read_lba, blocks); + if (res != 0) + goto out; + + priv->cm_cur_read_lba += blocks; + priv->cm_left_to_read -= blocks; + + if (inc_cur_in_flight) { + priv->cm_cur_in_flight++; + TRACE_DBG("ec_cmd %p, new cm_cur_in_flight %d", ec_cmd, + priv->cm_cur_in_flight); + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Generates original bunch of internal READ commands. In case of error + * directly finishes ec_cmd, so it might be dead upon return! + */ +static void scst_cm_gen_reads(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int cnt = 0; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + while (1) { + int rc; + while ((priv->cm_left_to_read > 0) && + (priv->cm_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS)) { + int blocks; + + blocks = min_t(int, priv->cm_left_to_read, priv->cm_max_each_read); + + rc = scst_cm_push_single_read(ec_cmd, blocks, true); + if (rc != 0) + goto out_err; + + cnt++; + } + + if (priv->cm_cur_in_flight == SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) + break; + + rc = scst_cm_setup_next_data_descr(ec_cmd); + if (rc != 0) + goto out_err; + } + + EXTRACHECKS_BUG_ON(cnt == 0); + +out_wake: + if (cnt != 0) + wake_up(&priv->cm_read_tgt_dev->active_cmd_threads->cmd_list_waitQ); + + mutex_unlock(&priv->cm_mutex); + +out: + TRACE_EXIT(); + return; + +out_err: + if (priv->cm_cur_in_flight != 0) + goto out_wake; + else { + mutex_unlock(&priv->cm_mutex); + scst_cm_ec_cmd_done(ec_cmd); + } + goto out; +} + +/* cm_mutex suppose to be locked or no activities on this ec_cmd's priv */ +static void scst_cm_process_data_descrs(struct scst_cmd *ec_cmd, + const struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + int rc; + + TRACE_ENTRY(); + + rc = scst_cm_setup_data_descrs(ec_cmd, dds, dds_cnt); + if (rc != 0) + goto out_done; + + scst_cm_gen_reads(ec_cmd); + + /* ec_cmd can be dead here! */ + +out: + TRACE_EXIT(); + return; + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +/** + * scst_ext_copy_get_cur_seg_data_len() - return current segment data len + * @ec_cmd: EXTENDED COPY command + */ +int scst_ext_copy_get_cur_seg_data_len(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + res = priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr.data_len; + + TRACE_EXIT_RES(res); + return res; +} +EXPORT_SYMBOL_GPL(scst_ext_copy_get_cur_seg_data_len); + +/** + * scst_ext_copy_remap_done() - dev handler done with remapping this segment + * @ec_cmd: EXTENDED COPY command + * @dds: Leftover data descriptors + * @dds_cnt: Count of the leftover data descriptors + * + * Called by dev handlers from inside ext_copy_remap() callback upon finish. + * All not finished spaces should be k?alloc() as array of data descriptors + * in dds argument with count dds_count. SCST core then will copy them using + * internal copy machine and then kfree() dds. + * + * dds can point to &descr->data_descr, where descr is pointer supplied to + * ext_copy_remap(). In this case SCST core will not kfree() it. + * + * If dds is NULL, then all data have been remapped, so SCST core will switch + * to the next segment descriptor, if any. + */ +void scst_ext_copy_remap_done(struct scst_cmd *ec_cmd, + struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + TRACE_ENTRY(); + + scst_set_exec_time(ec_cmd); + + if (dds == NULL) + scst_cm_ec_sched_next_seg(ec_cmd); + else + scst_cm_process_data_descrs(ec_cmd, dds, dds_cnt); + + /* ec_cmd can be dead here! */ + + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_ext_copy_remap_done); + +static int scst_cm_try_to_remap(struct scst_cmd *ec_cmd); + +static void scst_cm_remap_retry_fn(struct scst_cmd *cmd) +{ + struct scst_cmd *ec_cmd = cmd->tgt_i_priv; + int rc; + + TRACE_ENTRY(); + + rc = scst_cm_try_to_remap(ec_cmd); + sBUG_ON(rc != 0); + + TRACE_EXIT(); + return; +} + +/* + * Tries to remap data before copying. + * + * Returns !=0 if remapping is not possible, or not 0 otherwise. + */ +static int scst_cm_try_to_remap(struct scst_cmd *ec_cmd) +{ + int res, rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + struct scst_ext_copy_data_descr *dd = &sd->data_descr; + struct scst_dev_type *handler = ec_cmd->dev->handler; + uint8_t cdb[16]; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + if (handler->ext_copy_remap == NULL) { + res = 1; + goto out; + } + + res = 0; + + /* !! priv data descriptors fields are not setup yet !! */ + + TRACE_DBG("Checking reservations on read dev %s (ec_cmd %p)", + sd->src_tgt_dev->dev->virt_name, ec_cmd); + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = READ_16; + put_unaligned_be64(dd->src_lba, &cdb[2]); + put_unaligned_be32(dd->data_len >> sd->src_tgt_dev->dev->block_shift, &cdb[10]); + + cmd = __scst_create_prepare_internal_cmd(cdb, + sizeof(cdb), SCST_CMD_QUEUE_SIMPLE, + sd->src_tgt_dev, GFP_KERNEL, true); + if (cmd == NULL) + goto out_busy; + + cmd->internal_check_local_events = 1; + cmd->tgt_i_priv = ec_cmd; /* needed for retries */ + + rc = __scst_check_local_events(cmd, false); + if (unlikely(rc != 0)) + goto out_check_retry; + + __scst_cmd_put(cmd); + + TRACE_DBG("Checking reservations on write dev %s (ec_cmd %p)", + sd->dst_tgt_dev->dev->virt_name, ec_cmd); + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = WRITE_16; + put_unaligned_be64(dd->dst_lba, &cdb[2]); + put_unaligned_be32(dd->data_len >> sd->dst_tgt_dev->dev->block_shift, &cdb[10]); + + cmd = __scst_create_prepare_internal_cmd(cdb, + sizeof(cdb), SCST_CMD_QUEUE_SIMPLE, + sd->dst_tgt_dev, GFP_KERNEL, true); + if (cmd == NULL) + goto out_busy; + + cmd->internal_check_local_events = 1; + cmd->tgt_i_priv = ec_cmd; /* needed for retries */ + + rc = __scst_check_local_events(cmd, false); + if (unlikely(rc != 0)) + goto out_check_retry; + + __scst_cmd_put(cmd); + + TRACE_DBG("Calling ext_copy_remap() for dev %s (ec_cmd %p)", + sd->dst_tgt_dev->dev->virt_name, ec_cmd); + + scst_set_exec_start(ec_cmd); + handler->ext_copy_remap(ec_cmd, sd); + +out: + TRACE_EXIT_RES(res); + return res; + +out_check_retry: + rc = scst_cm_err_check_retry(cmd, ec_cmd->start_time, scst_cm_remap_retry_fn); + sBUG_ON(rc == SCST_CM_STATUS_CMD_SUCCEEDED); + if (rc == SCST_CM_STATUS_CMD_FAILED) { + TRACE_DBG("Remap check cmd %p (ec_cmd %p, op %s) failed", + cmd, ec_cmd, scst_get_opcode_name(cmd)); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, cmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)); + } else { + if (cmd->cdb[0] == READ_16) + priv->cm_error = SCST_CM_ERROR_READ; + else { + EXTRACHECKS_BUG_ON(cmd->cdb[0] != WRITE_16); + priv->cm_error = SCST_CM_ERROR_WRITE; + } + } + __scst_cmd_put(cmd); + goto out_done; + } + __scst_cmd_put(cmd); + goto out; + +out_busy: + res = -ENOMEM; + scst_set_busy(ec_cmd); + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +static void scst_cm_process_cur_seg_descr(struct scst_cmd *ec_cmd) +{ + int rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + rc = scst_cm_try_to_remap(ec_cmd); + if (rc == 0) + goto out; + + /* No remapping supported */ + + scst_ext_copy_remap_done(ec_cmd, + &priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr, 1); + +out: + TRACE_EXIT(); + return; +} + +int scst_cm_ext_copy_exec(struct scst_cmd *ec_cmd) +{ + int res = SCST_EXEC_COMPLETED, rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + if (unlikely(priv == NULL)) + goto out_local_done; + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) { + TRACE_DBG("ec_cmd %p done", ec_cmd); + goto out_done; + } + + rc = scst_cm_setup_seg_descr(ec_cmd); + if (rc != 0) + goto out_err_done; + + scst_cm_process_cur_seg_descr(ec_cmd); + +out: + TRACE_EXIT(); + return res; + +out_local_done: + ec_cmd->completed = 1; /* for success */ + ec_cmd->scst_cmd_done(ec_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + goto out; + +out_err_done: + sBUG_ON((rc != -ENOENT) && !ec_cmd->completed); + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd) +{ + bool res = false; + int i; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cmd %p", ec_cmd, cmd); + + EXTRACHECKS_BUG_ON(ec_cmd->cdb[0] != EXTENDED_COPY); + + if ((cmd->op_flags & SCST_LBA_NOT_VALID) != 0) + goto out; + + for (i = 0; i < ec_cmd->cmd_data_descriptors_cnt; i++) { + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[i]; + + TRACE_DBG("type %d, dst_dev %p, dev %p", sd->type, + sd->dst_tgt_dev->dev, cmd->dev); + + if (sd->type != SCST_EXT_COPY_SEG_DATA) + continue; + if (sd->dst_tgt_dev->dev != cmd->dev) + continue; + + res = scst_lba1_inside_lba2(sd->data_descr.dst_lba, cmd->lba, + cmd->data_len >> cmd->dev->block_shift); + if (res) + goto out; + res = scst_lba1_inside_lba2(cmd->lba, sd->data_descr.dst_lba, + sd->data_descr.data_len >> sd->dst_tgt_dev->dev->block_shift); + if (res) + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * No locks. Returns true if cmd should be blocked. + */ +bool scst_cm_check_block_all_devs(struct scst_cmd *cmd) +{ + bool res = false; + struct scst_cmd *ec_cmd; + struct scst_cm_ec_cmd_priv *d; + struct scst_cm_dev_entry *e; + + TRACE_ENTRY(); + + /* + * EXTENDED COPY command creates a new challenge: it can operate on + * several devices at the same time. Hence, it opens a wide possibility + * of deadlocks like the following. + * + * Device A received EXTENDED COPY command EC1 to copy data from device + * A to device B. It started doing the job by generating SCSI READ and + * WRITE commands to devices A and B. In the middle of the work device B + * received its EXTENDED COPY command EC2 to copy data from device B + * to device A. It also started doing its work in parallel with EC1. + * Then, device A received a serialized or SCSI atomic command, which + * can not proceed until EC1 finished and don't let EC2 generated + * commands to proceed ahead of it. Then, if device B received similar + * serialized or SCSI atomic command, blocking EC2 generated commands, + * we would have a deadlock. + * + * So, we can not allow EXTENDED COPY generated SCSI commands be + * blocked. From other side, we have to honor serialized and SCSI atomic + * commands as well as all other devices blocking events. + * + * To handle it, we, from one side, generate all EXTENDED COPY read and + * writes commands as internal commands, so they bypass blocking + * checking, and from another side, create "fantom" EXTENDED COPY + * commands on all participating devices, then pass "check blocking" on + * all them at once. If any device blocked its command, then undo check + * blocking on all previously processed devices. After the blocked + * command resumed, we retry again. For SCSI atomic commands we check + * each if any of the (fantom) EXTENDED COPY commands conflict with + * existing SCSI atomic commands. If yes, then restart on the conflicting + * (fantom) EXTENDED COPY resume as described above. Then we check all + * newly coming SCSI atomic commands if they conflict with existing + * (fantom) EXTENDED COPY commands. + */ + + if (cmd->internal) { + struct scst_cm_internal_cmd_priv *p = cmd->tgt_i_priv; + + /* cmd is a resumed phantom EXTENDED COPY command */ + + ec_cmd = p->cm_orig_cmd; + + TRACE_BLOCK("Rewaking blocked EC cmd %p (fcmd %p)", + ec_cmd, cmd); + + scst_check_unblock_dev(cmd); + + spin_lock_irq(&ec_cmd->cmd_threads->cmd_list_lock); + list_add_tail(&ec_cmd->cmd_list_entry, + &ec_cmd->cmd_threads->active_cmd_list); + wake_up(&ec_cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&ec_cmd->cmd_threads->cmd_list_lock); + + res = true; + goto out; + } + + /* cmd is a real ready for exec EXTENDED COPY command */ + + ec_cmd = cmd; + /* + * This could be restart of previously blocked ec_cmd, so + * check unblock it. + */ + TRACE_DBG("Check unblocking ec_cmd %p", ec_cmd); + scst_check_unblock_dev(ec_cmd); + + d = ec_cmd->cmd_data_descriptors; + if (d == NULL) { + spin_lock_bh(&ec_cmd->dev->dev_lock); + res = scst_do_check_blocked_dev(ec_cmd); + spin_unlock_bh(&ec_cmd->dev->dev_lock); + goto out; + } + + local_bh_disable(); + +#if !defined(__CHECKER__) + list_for_each_entry(e, &d->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + spin_lock(&e->cm_fcmd->dev->dev_lock); + } +#endif + + list_for_each_entry(e, &d->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + TRACE_DBG("dev %p (fcmd %p)", e->cm_fcmd->dev, e->cm_fcmd); + res = scst_do_check_blocked_dev(e->cm_fcmd); + if (unlikely(res)) { + TRACE_BLOCK("fcmd %p (ec_cmd %p) blocked, undo " + "check blocking devices", e->cm_fcmd, ec_cmd); + break; + } + } + + if (unlikely(res)) { + struct scst_cmd *blocked_cmd = e->cm_fcmd; + list_for_each_entry(e, &d->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + if (e->cm_fcmd == blocked_cmd) + break; + __scst_check_unblock_dev(e->cm_fcmd); + e->cm_fcmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + } + } + +#if !defined(__CHECKER__) + list_for_each_entry_reverse(e, &d->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + spin_unlock(&e->cm_fcmd->dev->dev_lock); + } +#endif + + local_bh_enable(); + +out: + TRACE_EXIT_RES(res); + return res; +} + +void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *p = ec_cmd->cmd_data_descriptors; + struct scst_cm_internal_cmd_priv *ip; + unsigned long flags; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd->cdb[0] != EXTENDED_COPY); + + spin_lock_irqsave(&scst_cm_lock, flags); + + if (p == NULL) + goto out_unlock; + + TRACE_MGMT_DBG("Aborting fantom and internal commands of ec_cmd %p", + ec_cmd); + + list_for_each_entry(ip, &p->cm_internal_cmd_list, + cm_internal_cmd_list_entry) { + struct scst_cmd *c = ip->cm_cmd; + TRACE_MGMT_DBG("Aborting (f)cmd %p", c); + set_bit(SCST_CMD_ABORTED, &c->cmd_flags); + } + +out_unlock: + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_EXIT(); + return; +} + +static void scst_cm_del_free_list_id(struct scst_cm_list_id *l, bool locked) +{ + unsigned long flags = 0; + + TRACE_ENTRY(); + + TRACE_DBG("Freeing list id %p", l); + +#if !defined(__CHECKER__) + if (!locked) + spin_lock_irqsave(&scst_cm_lock, flags); +#endif + +#ifdef CONFIG_SMP + EXTRACHECKS_BUG_ON(!spin_is_locked(&scst_cm_lock)); +#endif + + list_del(&l->sess_cm_list_id_entry); + +#if !defined(__CHECKER__) + if (!locked) + spin_unlock_irqrestore(&scst_cm_lock, flags); +#endif + + kfree(l); + + TRACE_EXIT(); + return; +} + +static void scst_cm_sched_del_list_id(struct scst_cmd *ec_cmd) +{ + struct scst_session *sess = ec_cmd->sess; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_list_id *l = priv->cm_list_id; + unsigned long flags; + + TRACE_ENTRY(); + + spin_lock_irqsave(&scst_cm_lock, flags); + + if (l->cm_list_id_state != SCST_CM_LIST_ID_STATE_DONE) { + /* + * It can happen for preliminary EC command finish, e.g., + * because of local event like UA. + */ + l->cm_can_be_immed_free = 1; + } + + l->cm_done = 1; + + /* + * Barrier to sync with scst_abort_cmd() and + * scst_mgmt_affected_cmds_done() calling scst_cm_free_pending_list_ids(). + * It has nothing common with cm_done set above. Just in case, actually. + */ + smp_rmb(); + if (test_bit(SCST_CMD_ABORTED, &ec_cmd->cmd_flags) || + l->cm_can_be_immed_free) { + TRACE_DBG("List id %p can be immed freed", l); + scst_cm_del_free_list_id(l, true); + spin_unlock_irqrestore(&scst_cm_lock, flags); + goto out; + } + + l->cm_list_id_state = SCST_CM_LIST_ID_STATE_PENDING_FREE; + l->cm_time_to_free = jiffies + SCST_CM_ID_KEEP_TIME; + + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_DBG("Schedule pending free list id %p", l); + + schedule_delayed_work(&sess->sess_cm_list_id_cleanup_work, + SCST_CM_ID_KEEP_TIME); + +out: + TRACE_EXIT(); + return; +} + +static struct scst_cm_list_id *scst_cm_add_list_id(struct scst_cmd *cmd, + int list_id) +{ + struct scst_cm_list_id *res; + struct scst_session *sess = cmd->sess; + struct scst_cm_list_id *l; + + TRACE_ENTRY(); + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (res == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate list_id"); + scst_set_busy(cmd); + goto out; + } + + res->cm_lid = list_id; + res->cm_list_id_state = SCST_CM_LIST_ID_STATE_ACTIVE; + + spin_lock_irq(&scst_cm_lock); + + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_PENDING_FREE) { + scst_cm_del_free_list_id(l, true); + break; + } else { + TRACE_DBG("List id %d already exists", list_id); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_operation_in_progress)); + goto out_unlock_free; + } + } + } + + TRACE_DBG("Adding list id %p (id %d)", res, list_id); + list_add_tail(&res->sess_cm_list_id_entry, &sess->sess_cm_list_id_list); + + spin_unlock_irq(&scst_cm_lock); + +out: + TRACE_EXIT(); + return res; + +out_unlock_free: + spin_unlock_irq(&scst_cm_lock); + kfree(res); + res = NULL; + goto out; +} + +void sess_cm_list_id_cleanup_work_fn(struct delayed_work *work) +{ + struct scst_session *sess = container_of((struct delayed_work *)work, + struct scst_session, sess_cm_list_id_cleanup_work); + struct scst_cm_list_id *l, *t; + unsigned long cur_time = jiffies; + unsigned long flags; + + TRACE_ENTRY(); + + /* + * We assume that EC commands finish _more or less_ in order, so + * there's no need to scan the whole list each time. + */ + + spin_lock_irqsave(&scst_cm_lock, flags); + list_for_each_entry_safe(l, t, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_list_id_state != SCST_CM_LIST_ID_STATE_PENDING_FREE) + break; + if (time_after_eq(cur_time, l->cm_time_to_free)) + scst_cm_del_free_list_id(l, true); + else { + TRACE_DBG("Reschedule pending free list ids cleanup"); + schedule_delayed_work(&sess->sess_cm_list_id_cleanup_work, + l->cm_time_to_free - cur_time); + } + } + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_EXIT(); + return; +} + +void scst_cm_free_pending_list_ids(struct scst_session *sess) +{ + struct scst_cm_list_id *l, *t; + + TRACE_ENTRY(); + + cancel_delayed_work_sync(&sess->sess_cm_list_id_cleanup_work); + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry_safe(l, t, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + TRACE_DBG("List id %p, state %d", l, l->cm_list_id_state); + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_PENDING_FREE) + scst_cm_del_free_list_id(l, true); + } + spin_unlock_irq(&scst_cm_lock); + + TRACE_EXIT(); + return; +} + +static void scst_cm_copy_status(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, tbuf[12]; + int list_id; + struct scst_cm_list_id *l; + struct scst_session *sess = cmd->sess; + bool found = false; + + TRACE_ENTRY(); + + list_id = cmd->cdb[2]; + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + TRACE_DBG("list id %p found (id %d)", l, list_id); + found = true; + break; + } + } + if (found) { + l->cm_can_be_immed_free = 1; + + memset(tbuf, 0, sizeof(tbuf)); + put_unaligned_be32(8, &tbuf[0]); + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_ACTIVE) + tbuf[4] = 0; /* in progress */ + else if (l->cm_status == 0) + tbuf[4] = 1; /* finished, no errors */ + else + tbuf[4] = 2; /* finished with errors */ + put_unaligned_be16(l->cm_segs_processed, &tbuf[5]); + tbuf[7] = 1; /* KBs */ + put_unaligned_be32(l->cm_written_size >> 10, &tbuf[8]); + + if (l->cm_done) + scst_cm_del_free_list_id(l, true); + } + + l = NULL; /* after unlock it can be immediately get dead */ + + spin_unlock_irq(&scst_cm_lock); + + if (!found) + goto out_list_id_not_found; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out; + + length = min_t(int, (int)sizeof(tbuf), length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; + +out_list_id_not_found: + TRACE_DBG("list_id %d not found", list_id); + scst_set_invalid_field_in_cdb(cmd, 2, 0); + goto out; +} + +static void scst_cm_failed_seg_details(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, *tbuf; + int list_id, size; + struct scst_cm_list_id *l; + struct scst_session *sess = cmd->sess; + bool found = false; + + TRACE_ENTRY(); + + list_id = cmd->cdb[2]; + + size = 60 + SCST_SENSE_BUFFERSIZE; + + tbuf = kzalloc(size, GFP_KERNEL); + if (tbuf == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate FAILED SEGMENTS " + "DETAILS buffer (size %d)", size); + goto out_busy; + } + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + TRACE_DBG("list id %p found (id %d)", l, list_id); + found = true; + break; + } + } + if (found) { + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_ACTIVE) { + found = false; + goto skip; + } + + if ((cmd->bufflen == 0) || + ((l->cm_status == 0) && (cmd->bufflen >= 60)) || + ((l->cm_status != 0) && (cmd->bufflen >= 60 + SCST_SENSE_BUFFERSIZE))) + l->cm_can_be_immed_free = 1; + + if (l->cm_status == 0) + size = 60; + else + size = 60 + l->cm_sense_len; + + TRACE_DBG("l %p, status %d, sense_len %d, size %d", l, + l->cm_status, l->cm_sense_len, size); + + put_unaligned_be32(size-3, &tbuf[0]); + tbuf[56] = l->cm_status; + EXTRACHECKS_BUG_ON(l->cm_sense_len > SCST_SENSE_BUFFERSIZE); + BUILD_BUG_ON(sizeof(l->cm_sense) != SCST_SENSE_BUFFERSIZE); + put_unaligned_be16(l->cm_sense_len, &tbuf[58]); + if (l->cm_sense_len > 0) + memcpy(&tbuf[60], l->cm_sense, l->cm_sense_len); + + if (l->cm_can_be_immed_free && l->cm_done) + scst_cm_del_free_list_id(l, true); + } + +skip: + l = NULL; /* after unlock it can be immediately get dead */ + + spin_unlock_irq(&scst_cm_lock); + + if (!found) + goto out_list_id_not_found; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out_free; + + length = min_t(int, size, length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out_free: + kfree(tbuf); + + TRACE_EXIT(); + return; + +out_list_id_not_found: + TRACE_DBG("list_id %d not found", list_id); + scst_set_invalid_field_in_cdb(cmd, 2, 0); + goto out_free; + +out_busy: + scst_set_busy(cmd); + goto out_free; +} + +static void scst_cm_oper_parameters(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, tbuf[44+2] /* 2 descriptors implemented */; + + TRACE_ENTRY(); + + memset(tbuf, 0, sizeof(tbuf)); + + /* AVAILABLE DATA */ + put_unaligned_be32(sizeof(tbuf) - 4, &tbuf[0]); + + /* SNLID */ + tbuf[4] = 1; + + /* MAXIMUM TARGET DESCRIPTOR COUNT */ + put_unaligned_be16(SCST_CM_MAX_TGT_DESCR_CNT, &tbuf[8]); + + /* MAXIMUM SEGMENT DESCRIPTOR COUNT */ + put_unaligned_be16(((PAGE_SIZE * 2) - sizeof(struct scst_cm_ec_cmd_priv)) / + sizeof(struct scst_ext_copy_seg_descr), &tbuf[10]); + + /* MAXIMUM DESCRIPTOR LIST LENGTH */ + put_unaligned_be32(0xFFFF, &tbuf[12]); + + /* MAXIMUM SEGMENT LENGTH: 256MB */ + put_unaligned_be32(256*1024*1024, &tbuf[16]); + + /* No inline and held data. No stream device max data size. */ + + /* TOTAL CONCURRENT COPIES */ + put_unaligned_be16(0xFFFF, &tbuf[34]); + + /* MAXIMUM CONCURRENT COPIES */ + tbuf[36] = 0xFF; + + /* DATA SEGMENT GRANULARITY */ + tbuf[37] = 16; /* 64K */ + + /* 2 descriptor codes implemented */ + tbuf[43] = 2; + + /* Implemented descriptor codes */ + tbuf[44] = 2; + tbuf[45] = 0xE4; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out; + + length = min_t(int, (int)sizeof(tbuf), length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; +} + +int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED, action; + + TRACE_ENTRY(); + + action = cmd->cdb[1] & 0x1F; + + switch (action) { + case 0: /* copy status */ + scst_cm_copy_status(cmd); + break; + case 3: /* operational parameters */ + scst_cm_oper_parameters(cmd); + break; + case 4: /* failed segment details */ + scst_cm_failed_seg_details(cmd); + break; + default: + TRACE(TRACE_MINOR, "%s: action %d not supported", cmd->op_name, + action); + scst_set_invalid_field_in_cdb(cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + break; + } + + cmd->completed = 1; /* for success */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT_RES(res); + return res; +} + +struct scst_cm_init_inq_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cm_init_inq_finish_fn; + + struct scst_device *dev; +}; + +static int scst_cm_send_init_inquiry(struct scst_device *dev, + unsigned int unpacked_lun, struct scst_cm_init_inq_priv *priv); + +static void scst_cm_inq_retry_fn(struct scst_cmd *cmd) +{ + struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv; + + TRACE_ENTRY(); + + /* cmd->dev can be NULL here! */ + + scst_cm_send_init_inquiry(priv->dev, cmd->lun, priv); + + TRACE_EXIT(); + return; +} + +static void scst_cm_init_inq_finish(struct scst_cmd *cmd) +{ + int length, page_len, off, rc; + uint8_t *buf; + struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv; + struct scst_device *dev = priv->dev; + + TRACE_ENTRY(); + + /* cmd->dev can be NULL here! */ + + rc = scst_cm_err_check_retry(cmd, cmd->start_time, scst_cm_inq_retry_fn); + if (rc == SCST_CM_STATUS_RETRY) + goto out; + + spin_lock_bh(&dev->dev_lock); + scst_unblock_dev(dev); + spin_unlock_bh(&dev->dev_lock); + + kfree(priv); + priv = NULL; + cmd->tgt_i_priv = NULL; + + if (rc != SCST_CM_STATUS_CMD_SUCCEEDED) { + PRINT_CRIT_ERROR("Unable to perform initial INQUIRY for device " + "%s. Copy manager for this device will be disabled", + dev->virt_name); + goto out; + } + + length = scst_get_buf_full(cmd, &buf); + TRACE_DBG("length %d", length); + if (unlikely(length <= 0)) { + if (length < 0) + PRINT_ERROR("scst_get_buf_full() failed: %d", length); + goto out; + } + + TRACE_BUFF_FLAG(TRACE_DEBUG, "buf", buf, length); + + if (buf[0] != 0) { + TRACE(TRACE_MINOR, "Not supported dev type %x, ignoring", buf[0]); + goto out_put; + } + + if (buf[1] != 0x83) { + PRINT_WARNING("Incorrect page code %x, ignoring", buf[1]); + goto out_put; + } + + page_len = get_unaligned_be16(&buf[2]); + if (page_len+3 > cmd->resp_data_len) { + PRINT_WARNING("Page len (%d) doesn't match resp len (%d), ignoring", + page_len+3, cmd->resp_data_len); + goto out_put; + } + + off = 4; + while (off < page_len) { + int des_len, des_alloc_len; + struct scst_cm_desig *des; + + if (off + 3 >= page_len) { + PRINT_WARNING("Too small page len %d, (off %d), ignoring", + page_len, off); + goto out_put; + } + + des_len = buf[off + 3]; + if ((off + des_len) > page_len) { + PRINT_WARNING("Too small buf len %d (off %d, des_len %d), " + "ignoring", page_len, off, des_len); + goto out_put; + } + + des_len += 4; + + if (((buf[off] & 0xF0) != 0) || ((buf[off+1] & 0xF0) != 0)) { + TRACE_DBG("Unsupported designator (%x, %x), " + "ignoring", buf[off] & 0xF0, buf[off+1] & 0xF0); + goto next; + } + + des_alloc_len = sizeof(*des) + des_len; + des = kzalloc(des_alloc_len, GFP_KERNEL); + if (des == NULL) { + PRINT_CRIT_ERROR("Unable to allocate designator (len %d, " + "type %x), ignoring it", des_alloc_len, + buf[off+1] & 0xF); + goto out_put; + } + + des->desig_tgt_dev = cmd->tgt_dev; + + des->desig_len = des_len; + memcpy(des->desig, &buf[off], des_len); + + TRACE_DBG("des %p, len %d", des, des->desig_len); + TRACE_BUFF_FLAG(TRACE_DEBUG, "des", des->desig, des->desig_len); + + mutex_lock(&scst_cm_mutex); + list_add_tail(&des->cm_desig_list_entry, &scst_cm_desig_list); + mutex_unlock(&scst_cm_mutex); +next: + off += des_len; + TRACE_DBG("off %d", off); + } + +out_put: + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; +} + +static int scst_cm_send_init_inquiry(struct scst_device *dev, + unsigned int unpacked_lun, struct scst_cm_init_inq_priv *priv) +{ + int res; + static const uint8_t inq_cdb[6] = { INQUIRY, 1, 0x83, 0x10, 0, 0 }; + __be64 lun; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + if (priv == NULL) { + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + PRINT_ERROR("Unable to alloc priv"); + res = -ENOMEM; + goto out; + } + priv->cm_init_inq_finish_fn = scst_cm_init_inq_finish; + priv->dev = dev; + } + + lun = scst_pack_lun(unpacked_lun, scst_cm_sess->acg->addr_method); + + cmd = scst_rx_cmd(scst_cm_sess, (const uint8_t *)&lun, + sizeof(lun), inq_cdb, sizeof(inq_cdb), false); + if (cmd == NULL) { + res = -ENOMEM; + goto out_free; + } + + scst_cmd_set_expected(cmd, SCST_DATA_READ, 4096); + scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE); + scst_cmd_set_tgt_priv(cmd, priv); + + cmd->bypass_blocking = 1; + + scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + kfree(priv); + goto out; +} + +/* scst_mutex supposed to be held and activities suspended */ +static bool scst_cm_is_lun_free(unsigned int lun) +{ + bool res = true; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(lun)]; + struct scst_tgt_dev *tgt_dev; + + TRACE_ENTRY(); + + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->lun == lun) { + res = false; + break; + } + } + + TRACE_EXIT_RES(res); + return res; +} + +/* scst_mutex supposed to be held and activities suspended */ +static int scst_cm_dev_register(struct scst_device *dev) +{ + int res, i; + unsigned int lun; + struct scst_acg_dev *acg_dev; + + TRACE_ENTRY(); + + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { + struct scst_tgt_dev *tgt_dev; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->dev == dev) { + PRINT_ERROR("Copy Manager already registered " + "device %s", dev->virt_name); + res = -EEXIST; + goto out; + } + } + } + + while (1) { + lun = scst_cm_next_lun++; + if (lun == SCST_MAX_LUN) + continue; + if (scst_cm_is_lun_free(lun)) + break; + }; + + res = scst_acg_add_lun(scst_cm_tgt->default_acg, + scst_cm_tgt->tgt_luns_kobj, dev, lun, false, false, &acg_dev); + if (res != 0) + goto out_err; + + spin_lock_bh(&dev->dev_lock); + scst_block_dev(dev); + spin_unlock_bh(&dev->dev_lock); + + res = scst_cm_send_init_inquiry(dev, lun, NULL); + if (res != 0) + goto out_unblock; + +out: + TRACE_EXIT_RES(res); + return res; + +out_unblock: + spin_lock_bh(&dev->dev_lock); + scst_unblock_dev(dev); + spin_unlock_bh(&dev->dev_lock); + scst_acg_del_lun(scst_cm_tgt->default_acg, lun, false); + +out_err: + scst_cm_next_lun--; + goto out; +} + +/* scst_mutex supposed to be held and activities suspended */ +static void scst_cm_dev_unregister(struct scst_device *dev) +{ + int i; + struct scst_cm_desig *des, *t; + + TRACE_ENTRY(); + + list_for_each_entry_safe(des, t, &scst_cm_desig_list, cm_desig_list_entry) { + if (des->desig_tgt_dev->dev == dev) { + TRACE_DBG("Deleting des %p", des); + list_del(&des->cm_desig_list_entry); + kfree(des); + } + } + + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { + struct scst_tgt_dev *tgt_dev; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->dev == dev) { + scst_acg_del_lun(scst_cm_tgt->default_acg, + tgt_dev->lun, false); + break; + } + } + } + + TRACE_EXIT(); + return; +} + +/* scst_mutex supposed to be held and activities suspended */ +int scst_cm_on_dev_register(struct scst_device *dev) +{ + int res = 0; + + TRACE_ENTRY(); + + if (!dev->handler->auto_cm_assignment_possible) + goto out; + + res = scst_cm_dev_register(dev); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_mutex supposed to be held and activities suspended */ +void scst_cm_on_dev_unregister(struct scst_device *dev) +{ + TRACE_ENTRY(); + + scst_cm_dev_unregister(dev); + + TRACE_EXIT(); + return; +} + +/* scst_mutex supposed to be locked */ +static bool scst_cm_check_access_acg(const char *initiator_name, + const struct scst_device *dev, const struct scst_acg *acg, + bool default_acg) +{ + bool res = true; + struct scst_acg_dev *acg_dev; + + TRACE_ENTRY(); + + list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { + if (acg_dev->dev == dev) { + struct scst_acn *acn; + if (default_acg) + goto found; + list_for_each_entry(acn, &acg->acn_list, acn_list_entry) { + if (strcmp(acn->name, initiator_name) == 0) + goto found; + } + } + } + + res = false; + +found: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cm_check_access(const char *initiator_name, + const struct scst_device *dev, bool *read_only) +{ + bool res = true; + struct scst_tgt_template *tgtt; + + TRACE_ENTRY(); + + if (scst_cm_allow_not_connected_copy) + goto out_rd_only; + + /* ToDo: make it hash based */ + + /* + * We can't use scst_mutex on commands processing path, because + * otherwise we can fall in a deadlock with kthread_stop() in + * scst_del_threads() waiting for this command to finish. + */ + mutex_lock(&scst_mutex2); + + list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { + struct scst_tgt *tgt; + + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + struct scst_acg *acg; + + if (tgt == scst_cm_tgt) + continue; + + TRACE_DBG("Checking tgt %s", tgt->tgt_name); + + if (scst_cm_check_access_acg(initiator_name, dev, tgt->default_acg, true)) + goto out_unlock_rd_only; + + list_for_each_entry(acg, &tgt->tgt_acg_list, acg_list_entry) { + if (scst_cm_check_access_acg(initiator_name, dev, acg, false)) + goto out_unlock_rd_only; + } + } + } + + res = false; + PRINT_WARNING("Initiator %s not allowed to use device %s in EXTENDED " + "COPY command", initiator_name, dev->virt_name); + +out_unlock_rd_only: + mutex_unlock(&scst_mutex2); + +out_rd_only: + *read_only = dev->dev_rd_only || dev->swp; + + TRACE_EXIT_RES(res); + return res; +} + +struct scst_cm_tgt_descr { + struct scst_tgt_dev *tgt_dev; + unsigned int read_only:1; + int param_offs; +}; + +static int scst_cm_parse_id_tgt_descr(struct scst_cmd *cmd, const uint8_t *seg, + int offs, struct scst_cm_tgt_descr *tgt_descr) +{ + int res = 32; + struct scst_cm_desig *des; + int block; + bool read_only = false; + + TRACE_ENTRY(); + + TRACE_BUFF_FLAG(TRACE_DEBUG, "seg", seg, 32); + + EXTRACHECKS_BUG_ON(seg[0] != 0xE4); + + if ((seg[1] & 0xC0) != 0) { + PRINT_WARNING("LU ID %x not supported", seg[1] & 0xC0); + scst_set_invalid_field_in_parm_list(cmd, offs+1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 6); + goto out_err; + } + + if ((seg[1] & 0x20) != 0) { + TRACE_DBG("NULL tgt descriptor"); + tgt_descr->tgt_dev = NULL; + goto out; + } + + if ((seg[1] & 0xF) != 0) { + PRINT_WARNING("PERIPHERAL DEVICE TYPE %d not supported", + seg[1] & 0xF); + scst_set_invalid_field_in_parm_list(cmd, offs+1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_err; + } + + block = get_unaligned_be24(&seg[29]); + + mutex_lock(&scst_cm_mutex); + + /* ToDo: make it hash based */ + + list_for_each_entry(des, &scst_cm_desig_list, cm_desig_list_entry) { + TRACE_DBG("des %p (tgt_dev %p, lun %lld)", des, des->desig_tgt_dev, + (unsigned long long)des->desig_tgt_dev->lun); + if (seg[4] != des->desig[0]) + continue; + if (seg[5] != des->desig[1]) + continue; + if (seg[7] > des->desig[3]) + continue; + if (memcmp(&des->desig[4], &seg[8], min_t(int, seg[7], des->desig[3])) == 0) { + TRACE_DBG("Tgt_dev %p (lun %lld) found", + des->desig_tgt_dev, + (unsigned long long)des->desig_tgt_dev->lun); + + mutex_unlock(&scst_cm_mutex); + + if (block != des->desig_tgt_dev->dev->block_size) { + PRINT_WARNING("Block size %d doesn't match %d", block, + des->desig_tgt_dev->dev->block_size); + scst_set_invalid_field_in_parm_list(cmd, offs+29, 0); + goto out_err; + } + + if (!scst_cm_check_access(cmd->sess->initiator_name, + des->desig_tgt_dev->dev, &read_only)) + goto out_not_found; + + tgt_descr->tgt_dev = des->desig_tgt_dev; + tgt_descr->read_only = read_only; + TRACE_DBG("Found des %p (tgt_dev %p, read_only %d)", + des, tgt_descr->tgt_dev, tgt_descr->read_only); + goto out; + } + } + + mutex_unlock(&scst_cm_mutex); + + TRACE(TRACE_MINOR|TRACE_SCSI, "Target descriptor designator not found " + "(initiator %s, offs %d)", cmd->sess->initiator_name, offs); + TRACE_BUFF_FLAG(TRACE_MINOR|TRACE_SCSI, "Designator", seg, 32); + +out_not_found: + scst_set_invalid_field_in_parm_list(cmd, offs, 0); + +out_err: + res = -1; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int scst_cm_set_seg_err_sense(struct scst_cmd *cmd, int asc, int ascq, + int seg_num, int offs) +{ + int res, d_sense = scst_get_cmd_dev_d_sense(cmd); + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p, seg %d, offs %d (d_sense %d)", cmd, seg_num, offs, + d_sense); + + res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION); + if (res != 0) + goto out; + + res = scst_alloc_sense(cmd, 1); + if (res != 0) { + PRINT_ERROR("Lost COPY ABORTED sense data"); + goto out; + } + + sBUG_ON(cmd->sense_buflen < 18); + BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < 18); + + if (d_sense) { + /* Descriptor format */ + cmd->sense[0] = 0x72; + cmd->sense[1] = COPY_ABORTED; + cmd->sense[2] = asc; + cmd->sense[3] = ascq; + cmd->sense[7] = 20; /* additional Sense Length */ + + cmd->sense[8] = 1; /* Command specific descriptor */ + cmd->sense[9] = 0x0A; + put_unaligned_be16(seg_num, &cmd->sense[14]); + + cmd->sense[20] = 2; /* Sense key specific descriptor */ + cmd->sense[21] = 6; + cmd->sense[24] = 0xA0; + put_unaligned_be16(offs, &cmd->sense[25]); + + cmd->sense_valid_len = 28; + } else { + /* Fixed format */ + cmd->sense[0] = 0x70; + cmd->sense[2] = COPY_ABORTED; + cmd->sense[7] = 0x0a; /* additional Sense Length */ + cmd->sense[12] = asc; + cmd->sense[13] = ascq; + + put_unaligned_be16(seg_num, &cmd->sense[10]); + + cmd->sense[15] = 0xA0; + put_unaligned_be16(offs, &cmd->sense[16]); + + cmd->sense_valid_len = 18; + } + + TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_fantom_cmd_finished(struct scst_cmd *cmd) +{ + TRACE_ENTRY(); + + TRACE_DBG("Fantom cmd %p", cmd); + + EXTRACHECKS_BUG_ON(!cmd->internal); + + /* Nothing to do */ + + TRACE_EXIT(); + return; +} + +static int scst_cm_add_to_descr_list(struct scst_cmd *ec_cmd, + struct scst_tgt_dev *tgt_dev) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_dev_entry *e, *t; + struct scst_cmd *fcmd; + bool added; + + TRACE_ENTRY(); + + /* Check if we already have this device in the list */ + list_for_each_entry(e, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + if (e->cm_fcmd->dev == tgt_dev->dev) { + TRACE_DBG("Dev %p is already in cm_sorted_devs_list", + tgt_dev->dev); + goto out_success; + } + } + + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (e == NULL) { + PRINT_ERROR("Unable to allocate scst_cm_dev_entry (size %d)", + (int)sizeof(*e)); + goto out_enomem; + } + + if (ec_cmd->dev == tgt_dev->dev) { + fcmd = ec_cmd; + goto skip_fcmd_create; + } + + fcmd = __scst_create_prepare_internal_cmd(ec_cmd->cdb, + ec_cmd->cdb_len, SCST_CMD_QUEUE_SIMPLE, tgt_dev, + GFP_KERNEL, true); + if (fcmd == NULL) { + goto out_enomem_free_e; + } + + fcmd->expected_data_direction = ec_cmd->expected_data_direction; + fcmd->expected_transfer_len_full = ec_cmd->expected_transfer_len_full; + fcmd->expected_values_set = 1; + + fcmd->cmd_data_descriptors = ec_cmd->cmd_data_descriptors; + fcmd->cmd_data_descriptors_cnt = ec_cmd->cmd_data_descriptors_cnt; + + fcmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + + res = scst_cm_add_to_internal_cmd_list(fcmd, ec_cmd, ec_cmd, + scst_cm_fantom_cmd_finished); + if (res != 0) + goto out_free_cmd; + +skip_fcmd_create: + TRACE_DBG("ec_cmd %p, e %p, fcmd %p, tgt_dev %p (dev %p)", ec_cmd, e, fcmd, + tgt_dev, tgt_dev->dev); + + e->cm_fcmd = fcmd; + + added = false; + list_for_each_entry_reverse(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + EXTRACHECKS_BUG_ON(t->cm_fcmd->dev == tgt_dev->dev); + if (((unsigned long)e->cm_fcmd->dev) > ((unsigned long)t->cm_fcmd->dev)) { + __list_add(&e->cm_sorted_devs_list_entry, + &t->cm_sorted_devs_list_entry, + t->cm_sorted_devs_list_entry.next); + added = true; + break; + } + } + if (!added) + list_add(&e->cm_sorted_devs_list_entry, + &priv->cm_sorted_devs_list); + +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_EXTRACHECKS) + { + struct scst_cm_dev_entry *tp = NULL; + list_for_each_entry(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + TRACE_DBG("t %p, cm dev %p", t, t->cm_fcmd->dev); + if (tp != NULL) { + if (((unsigned long)t->cm_fcmd->dev) <= ((unsigned long)tp->cm_fcmd->dev)) { + list_for_each_entry(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + printk(KERN_EMERG "%s: t %p, cm dev %p\n", __func__, + t, t->cm_fcmd->dev); + } + sBUG(); + break; + } + tp = t; + } + tp = t; + } + } +#endif + +out_success: + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_cmd: + __scst_cmd_put(fcmd); + +out_enomem_free_e: + kfree(e); + +out_enomem: + scst_set_busy(ec_cmd); + res = -ENOMEM; + goto out; +} + +static int scst_cm_parse_b2b_seg_descr(struct scst_cmd *ec_cmd, + const uint8_t *seg, const struct scst_cm_tgt_descr *tgt_descrs, + int tgt_descrs_cnt, int seg_num) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *d = &priv->cm_seg_descrs[seg_num]; + int res = 28, rc, len, src_des_idx, tgt_des_idx, blocks, dc; + const struct scst_cm_tgt_descr *src_des, *tgt_des; + + TRACE_ENTRY(); + + TRACE_BUFF_FLAG(TRACE_DEBUG, "seg", seg, 28); + + EXTRACHECKS_BUG_ON(seg[0] != 2); + + len = get_unaligned_be16(&seg[2]); + if (len != 0x18) { + PRINT_WARNING("Incorrect B2B segment descriptor len %d", len); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 2); + goto out_err; + } + + src_des_idx = get_unaligned_be16(&seg[4]); + if (src_des_idx >= tgt_descrs_cnt) { + PRINT_WARNING("Invalid src descriptor index %d", src_des_idx); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 4); + goto out_err; + } + + src_des = &tgt_descrs[src_des_idx]; + if (src_des->tgt_dev == NULL) { + PRINT_WARNING("Segment with NULL src tgt device"); + /* COPY TARGET DEVICE NOT REACHABLE */ + scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 4); + goto out_err; + } + + tgt_des_idx = get_unaligned_be16(&seg[6]); + if (tgt_des_idx >= tgt_descrs_cnt) { + PRINT_WARNING("Invalid tgt descriptor index %d", tgt_des_idx); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6); + goto out_err; + } + + tgt_des = &tgt_descrs[tgt_des_idx]; + if (tgt_des->tgt_dev == NULL) { + PRINT_WARNING("Segment with NULL tgt device"); + /* COPY TARGET DEVICE NOT REACHABLE */ + scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 6); + goto out_err; + } + if (tgt_des->read_only){ + PRINT_WARNING("Target descriptor refers to read-only device"); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6); + goto out_err; + } + + dc = (seg[1] >> 1) & 1; + blocks = get_unaligned_be16(&seg[10]); + if (dc) + len = blocks << tgt_des->tgt_dev->dev->block_shift; + else + len = blocks << src_des->tgt_dev->dev->block_shift; + + if (unlikely((len & (src_des->tgt_dev->dev->block_size-1)) != 0) || + unlikely((len & (tgt_des->tgt_dev->dev->block_size-1)) != 0)) { + PRINT_WARNING("Data len %d is not even for block size (src block " + "size %d, dst block size %d)", len, + src_des->tgt_dev->dev->block_size, + tgt_des->tgt_dev->dev->block_size); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 10); + goto out_err; + } + + d->type = SCST_EXT_COPY_SEG_DATA; + d->data_descr.data_len = len; + d->src_tgt_dev = src_des->tgt_dev; + d->data_descr.src_lba = get_unaligned_be64(&seg[12]); + d->dst_tgt_dev = tgt_des->tgt_dev; + d->data_descr.dst_lba = get_unaligned_be64(&seg[20]); + d->tgt_descr_offs = tgt_des->param_offs; + + TRACE(TRACE_DEBUG|TRACE_SCSI, "ec_cmd %p, src dev %s, dst dev %s, " + "len %d, src_lba %lld, dst_lba %lld", ec_cmd, + d->src_tgt_dev->dev->virt_name, d->dst_tgt_dev->dev->virt_name, + len, (long long)d->data_descr.src_lba, + (long long)d->data_descr.dst_lba); + + TRACE_DBG("src tgt_dev %p, dst tgt_dev %p, tgt_descr_offs %d", + d->src_tgt_dev, d->dst_tgt_dev, d->tgt_descr_offs); + + rc = scst_cm_add_to_descr_list(ec_cmd, src_des->tgt_dev); + if (rc != 0) { + res = rc; + goto out; + } + + rc = scst_cm_add_to_descr_list(ec_cmd, tgt_des->tgt_dev); + if (rc != 0) { + res = rc; + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_err: + res = -1; + goto out; +} + +static void scst_cm_free_ec_priv(struct scst_cmd *ec_cmd, bool unblock_dev) +{ + struct scst_cm_ec_cmd_priv *p = ec_cmd->cmd_data_descriptors; + struct scst_cm_dev_entry *e, *t; + struct scst_cm_internal_cmd_priv *ip, *it; + unsigned long flags; + + TRACE_ENTRY(); + + list_for_each_entry_safe(e, t, &p->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + TRACE_DBG("Deleting e %p", e); + list_del(&e->cm_sorted_devs_list_entry); + kfree(e); + } + + list_for_each_entry_safe(ip, it, &p->cm_internal_cmd_list, + cm_internal_cmd_list_entry) { + struct scst_cmd *c = ip->cm_cmd; + scst_cm_del_free_from_internal_cmd_list(c, unblock_dev); + __scst_cmd_put(c); + } + + /* Lock to sync with scst_cm_abort_ec_cmd() */ + spin_lock_irqsave(&scst_cm_lock, flags); + ec_cmd->cmd_data_descriptors = NULL; + ec_cmd->cmd_data_descriptors_cnt = 0; + spin_unlock_irqrestore(&scst_cm_lock, flags); + + kfree(p); + + TRACE_EXIT(); + return; +} + +int scst_cm_parse_descriptors(struct scst_cmd *ec_cmd) +{ + int res = 0, rc; + struct scst_cm_list_id *plist_id = NULL; + ssize_t length = 0; + uint8_t *buf; + int list_id, list_id_usage, len, tgt_len, seg_len; + struct scst_cm_ec_cmd_priv *p; + int tgt_cnt, seg_cnt, i, offs, t; + struct scst_cm_tgt_descr *tgt_descrs; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd->cmd_data_descriptors != NULL); + + length = scst_get_buf_full_sense(ec_cmd, &buf); + if (unlikely(length <= 0)) { + if (length == 0) + goto out_put; + else + goto out_abn; + } + + if (length < 16) { + PRINT_WARNING("Too small EXTENDED COPY data len %d", (int)length); + scst_set_invalid_field_in_cdb(ec_cmd, 10, 0); + goto out_abn_put; + } + + list_id = buf[0]; + list_id_usage = (buf[1] & 0x18) >> 3; + + TRACE_BUFF_FLAG(TRACE_DEBUG, "buf", buf, length); + + TRACE_DBG("list_id %d, list_id_usage %d", list_id, list_id_usage); + + switch (list_id_usage) { + case 0: + case 2: + plist_id = scst_cm_add_list_id(ec_cmd, list_id); + if (plist_id == NULL) + goto out_abn_put; + break; + case 3: + if (list_id != 0) { + PRINT_WARNING("Invalid list ID %d with list ID usage %d", + list_id, list_id_usage); + scst_set_invalid_field_in_parm_list(ec_cmd, 0, 0); + goto out_abn_put; + } + break; + default: + PRINT_WARNING("Invalid list ID usage %d, rejecting", list_id_usage); + scst_set_invalid_field_in_parm_list(ec_cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 3); + goto out_abn_put; + } + + len = get_unaligned_be32(&buf[12]); + if (len != 0) { + PRINT_WARNING("Inline data not supported (len %d)", len); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_inline_data_length_exceeded)); + goto out_del_abn_put; + } + + tgt_len = get_unaligned_be16(&buf[2]); + seg_len = get_unaligned_be32(&buf[8]); + + if (tgt_len == 0) { + if (seg_len == 0) + goto out_del_put; + else { + PRINT_WARNING("Zero target descriptors with none zero " + "segments len (%d)", seg_len); + scst_set_invalid_field_in_parm_list(ec_cmd, 2, 0); + goto out_del_abn_put; + } + } + + if ((tgt_len + seg_len + 16) > length) { + PRINT_WARNING("Parameters truncation"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out_del_abn_put; + } + + if ((tgt_len + seg_len + 16) != length) { + PRINT_WARNING("Unexpected inline data"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_inline_data_length_exceeded)); + goto out_del_abn_put; + } + + if ((tgt_len % 32) != 0) { + PRINT_WARNING("Invalid tgt len %d", tgt_len); + scst_set_invalid_field_in_parm_list(ec_cmd, 2, 0); + goto out_del_abn_put; + } + + tgt_cnt = tgt_len/32; + if (tgt_cnt > SCST_CM_MAX_TGT_DESCR_CNT) { + PRINT_WARNING("Too many target descriptors %d", tgt_cnt); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_too_many_target_descriptors)); + goto out_del_abn_put; + } + + TRACE_DBG("tgt_cnt %d", tgt_cnt); + + tgt_descrs = kzalloc(sizeof(*tgt_descrs) * tgt_cnt, GFP_KERNEL); + if (tgt_descrs == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate tgt_descrs " + "(count %d, size %zd)", tgt_cnt, + sizeof(*tgt_descrs) * tgt_cnt); + scst_set_busy(ec_cmd); + goto out_del_abn_put; + } + + offs = 16; + for (i = 0; i < tgt_cnt; i++) { + TRACE_DBG("offs %d", offs); + switch (buf[offs]) { + case 0xE4: /* identification descriptor target descriptor format */ + rc = scst_cm_parse_id_tgt_descr(ec_cmd, &buf[offs], offs, + &tgt_descrs[i]); + if (rc <= 0) + goto out_free_tgt_descr; + break; + default: + PRINT_WARNING("Not supported target descriptor %x", buf[offs]); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_unsupported_tgt_descr_type)); + goto out_free_tgt_descr; + } + tgt_descrs[i].param_offs = offs; + offs += rc; + } + + WARN_ON(offs != tgt_len + 16); + + t = offs; + seg_cnt = 0; + while (offs < length) { + switch (buf[offs]) { + case 2: /* block device to block device segment descriptor */ + offs += 28; + break; + default: + PRINT_WARNING("Not supported segment descriptor %x", buf[offs]); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_unsupported_seg_descr_type)); + goto out_free_tgt_descr; + } + seg_cnt++; + } + offs = t; + + TRACE_DBG("seg_cnt %d", seg_cnt); + + p = kzalloc(sizeof(*p) + seg_cnt * sizeof(struct scst_ext_copy_seg_descr), GFP_KERNEL); + if (p == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate Extended Copy " + "descriptors (seg_cnt %d)", seg_cnt); + scst_set_busy(ec_cmd); + goto out_free_tgt_descr; + } + + p->cm_list_id = plist_id; + plist_id = NULL; + INIT_LIST_HEAD(&p->cm_sorted_devs_list); + INIT_LIST_HEAD(&p->cm_internal_cmd_list); + p->cm_error = SCST_CM_ERROR_NONE; + mutex_init(&p->cm_mutex); + + ec_cmd->cmd_data_descriptors = p; + ec_cmd->cmd_data_descriptors_cnt = seg_cnt; + + res = scst_cm_add_to_descr_list(ec_cmd, ec_cmd->tgt_dev); + if (res != 0) + goto out_free_p; + + for (i = 0; i < seg_cnt; i++) { + TRACE_DBG("offs %d", offs); + switch (buf[offs]) { + case 2: /* block device to block device segment descriptor */ + rc = scst_cm_parse_b2b_seg_descr(ec_cmd, &buf[offs], + tgt_descrs, tgt_cnt, i); + if (rc <= 0) { + if (rc == -ENOMEM) + goto out_free_p; + else { + /* + * We may need to keep list_id for a + * while for further FAILED SEGMENT + * DETAILS of RECEIVE COPY RESULTS + */ + scst_cm_store_list_id_details(ec_cmd); + goto out_free_tgt_descr; + } + } + EXTRACHECKS_BUG_ON(rc != 28); + break; + default: + sBUG(); + } + offs += rc; + } + + kfree(tgt_descrs); + +out_del_put: + if (plist_id != NULL) + scst_cm_del_free_list_id(plist_id, false); + +out_put: + scst_put_buf_full(ec_cmd, buf); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_p: + scst_cm_free_ec_priv(ec_cmd, false); + +out_free_tgt_descr: + kfree(tgt_descrs); + +out_del_abn_put: + if (plist_id != NULL) + scst_cm_del_free_list_id(plist_id, false); + +out_abn_put: + scst_put_buf_full(ec_cmd, buf); + +out_abn: + scst_set_cmd_abnormal_done_state(ec_cmd); + res = -1; + goto out; +} + +void scst_cm_free_descriptors(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p (internal %d)", ec_cmd, ec_cmd->internal); + + if (priv == NULL) { + /* It can be for early errors */ + goto out; + } + + if (ec_cmd->internal) + goto out; + + if (priv->cm_list_id != NULL) + scst_cm_sched_del_list_id(ec_cmd); + + scst_cm_free_ec_priv(ec_cmd, true); + +out: + TRACE_EXIT(); + return; +} + +#ifndef CONFIG_SCST_PROC + +static ssize_t scst_cm_allow_not_conn_copy_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t res; + + TRACE_ENTRY(); + + res = sprintf(buf, "%d\n%s", scst_cm_allow_not_connected_copy, + (scst_cm_allow_not_connected_copy == SCST_ALLOW_NOT_CONN_COPY_DEF) ? + "" : SCST_SYSFS_KEY_MARK "\n"); + + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_cm_allow_not_conn_copy_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buffer, size_t size) +{ + ssize_t res; + unsigned long val; + + TRACE_ENTRY(); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) + res = kstrtoul(buffer, 0, &val); +#else + res = strict_strtoul(buffer, 0, &val); +#endif + if (res != 0) { + PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); + goto out; + } + + scst_cm_allow_not_connected_copy = val; + + res = size; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_cm_allow_not_conn_copy_attr = + __ATTR(allow_not_connected_copy, S_IRUGO|S_IWUSR, + scst_cm_allow_not_conn_copy_show, + scst_cm_allow_not_conn_copy_store); + +static ssize_t scst_cm_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + static const char help[] = + "Usage: echo \"add H:C:I:L\" >mgmt\n" + " echo \"add VNAME\" >mgmt\n" + " echo \"del H:C:I:L\" >mgmt\n" + " echo \"del VNAME\" >mgmt\n"; + + return sprintf(buf, "%s", help); +} + +static int scst_cm_mgmt(struct scst_sysfs_work_item *work) +{ + int res = 0; + char *pp, *action, *devstr; + unsigned int host, channel, id, lun; + char *buf = work->buf; + bool vdev; + struct scst_device *d, *dev = NULL; + + TRACE_ENTRY(); + + TRACE_DBG("buffer %s", buf); + + pp = buf; + action = scst_get_next_lexem(&pp); + devstr = scst_get_next_lexem(&pp); + if (*devstr == '\0') { + PRINT_ERROR("%s", "Device required"); + res = -EINVAL; + goto out; + } + + if (*scst_get_next_lexem(&pp) != '\0') { + PRINT_ERROR("%s", "Too many parameters"); + res = -EINVAL; + goto out_syntax_err; + } + + if (sscanf(devstr, "%u:%u:%u:%u", &host, &channel, &id, &lun) != 4) { + vdev = true; + TRACE_DBG("Virt dev %s", devstr); + } else { + vdev = false; + TRACE_DBG("Pass-through dev %d:%d:%d:%d", host, channel, id, lun); + } + + res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); + if (res != 0) + goto out; + + res = mutex_lock_interruptible(&scst_mutex); + if (res != 0) + goto out_resume; + + list_for_each_entry(d, &scst_dev_list, dev_list_entry) { + if (vdev) { + if ((d->scsi_dev == NULL) && + (strcmp(d->virt_name, devstr) == 0)) { + dev = d; + break; + } + } else if (d->scsi_dev != NULL && + d->scsi_dev->host->host_no == host && + d->scsi_dev->channel == channel && + d->scsi_dev->id == id && + d->scsi_dev->lun == lun) { + dev = d; + break; + } + } + if (dev == NULL) { + PRINT_ERROR("Device %s not found", devstr); + res = -EINVAL; + goto out_unlock; + } else + TRACE_DBG("Dev %p (%s) found", dev, dev->virt_name); + + if (strcasecmp("add", action) == 0) + res = scst_cm_dev_register(dev); + else if (strcasecmp("del", action) == 0) + scst_cm_dev_unregister(dev); + else { + PRINT_ERROR("Action %s not understood", action); + res = -EINVAL; + } + +out_unlock: + mutex_unlock(&scst_mutex); + +out_resume: + scst_resume_activity(); + +out: + TRACE_EXIT_RES(res); + return res; + +out_syntax_err: + PRINT_ERROR("Syntax error on \"%s\"", buf); + res = -EINVAL; + goto out; +} + +static ssize_t scst_cm_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buffer, size_t size) +{ + int res; + struct scst_sysfs_work_item *work; + char *i_buf; + + TRACE_ENTRY(); + + i_buf = kasprintf(GFP_KERNEL, "%.*s", (int)size, buffer); + if (i_buf == NULL) { + PRINT_ERROR("Unable to alloc intermediate buffer with size %zd", + size+1); + res = -ENOMEM; + goto out; + } + + res = scst_alloc_sysfs_work(scst_cm_mgmt, false, &work); + if (res != 0) + goto out; + + work->buf = i_buf; + + res = scst_sysfs_queue_wait_work(work); + if (res == 0) + res = size; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_cm_mgmt_attr = + __ATTR(mgmt, S_IRUGO|S_IWUSR, scst_cm_mgmt_show, scst_cm_mgmt_store); + +static const struct attribute *scst_cm_attrs[] = { + &scst_cm_allow_not_conn_copy_attr.attr, + &scst_cm_mgmt_attr.attr, + NULL, +}; + +#endif /* #ifndef CONFIG_SCST_PROC */ + +static int scst_cm_get_initiator_port_transport_id(struct scst_tgt *tgt, + struct scst_session *scst_sess, uint8_t **transport_id) +{ + int res = 0; + uint8_t *tid = NULL; + + TRACE_ENTRY(); + + BUILD_BUG_ON((sizeof(SCST_CM_TID_ID)+3) > SCST_CM_TID_SIZE); + BUILD_BUG_ON(TID_COMMON_SIZE != SCST_CM_TID_SIZE); + + if (scst_sess == NULL) { + res = SCST_TRANSPORTID_PROTOCOLID_COPY_MGR; + goto out; + } + + tid = kzalloc(SCST_CM_TID_SIZE, GFP_KERNEL); + if (tid == NULL) { + PRINT_ERROR("Allocation of TransportID (size %d) failed", + SCST_CM_TID_SIZE); + res = -ENOMEM; + goto out; + } + + tid[0] = SCST_TRANSPORTID_PROTOCOLID_COPY_MGR; + memcpy(&tid[2], SCST_CM_TID_ID, sizeof(SCST_CM_TID_ID)); + + *transport_id = tid; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int scst_cm_release(struct scst_tgt *tgt) +{ + TRACE_ENTRY(); + TRACE_EXIT(); + return 0; +} + +static int scst_cm_xmit_response(struct scst_cmd *cmd) +{ + int res = SCST_TGT_RES_SUCCESS; + scst_i_finish_fn_t f = (void *) *((unsigned long long **)cmd->tgt_i_priv); + + TRACE_ENTRY(); + + /* + * Used for CM-only generated commands, i.e. commands generated without + * any external command, like INQUIRY. + */ + + f(cmd); + scst_tgt_cmd_done(cmd, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd) +{ + /* Nothing to do */ + return; +} + +static struct scst_tgt_template scst_cm_tgtt = { + .name = SCST_CM_NAME, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + .sg_tablesize = SG_MAX_SINGLE_ALLOC, +#else + .sg_tablesize = 0xffff, +#endif +#ifndef CONFIG_SCST_PROC + .enabled_attr_not_needed = 1, +#endif + .dif_supported = 1, + .hw_dif_type1_supported = 1, + .hw_dif_type2_supported = 1, + .hw_dif_type3_supported = 1, + .release = scst_cm_release, + .xmit_response = scst_cm_xmit_response, + .task_mgmt_fn_done = scst_cm_task_mgmt_fn_done, + .get_initiator_port_transport_id = scst_cm_get_initiator_port_transport_id, +#ifndef CONFIG_SCST_PROC + .tgtt_attrs = scst_cm_attrs, +#endif +}; + +int __init scst_cm_init(void) +{ + int res = 0; + + TRACE_ENTRY(); + + spin_lock_init(&scst_cm_lock); + + res = scst_register_target_template(&scst_cm_tgtt); + if (res != 0) { + PRINT_ERROR("Unable to register copy manager template: %d", res); + goto out; + } + + scst_cm_tgt = scst_register_target(&scst_cm_tgtt, SCST_CM_TGT_NAME); + if (scst_cm_tgt == NULL) { + PRINT_ERROR("%s", "scst_register_target() failed"); + res = -EFAULT; + goto out_unreg_tgtt; + } + + scst_cm_sess = scst_register_session(scst_cm_tgt, false, + SCST_CM_SESS_NAME, NULL, NULL, NULL); + if (scst_cm_sess == NULL) { + PRINT_ERROR("%s", "scst_register_session() failed"); + res = -EFAULT; + goto out_unreg_tgt; + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_unreg_tgt: + scst_unregister_target(scst_cm_tgt); + +out_unreg_tgtt: + scst_unregister_target_template(&scst_cm_tgtt); + goto out; +} + +void __exit scst_cm_exit(void) +{ + TRACE_ENTRY(); + + scst_unregister_session(scst_cm_sess, true, NULL); + scst_unregister_target(scst_cm_tgt); + scst_unregister_target_template(&scst_cm_tgtt); + + TRACE_EXIT(); + return; +} + +#endif /* #ifndef CONFIG_SCST_PROC */ diff --git a/scst/src/scst_debug.c b/scst/src/scst_debug.c index e470cce10..176e6dcad 100644 --- a/scst/src/scst_debug.c +++ b/scst/src/scst_debug.c @@ -232,6 +232,10 @@ const char *debug_transport_id_to_initiator_name(const uint8_t *transport_id) transport_id[8], transport_id[9], transport_id[10], transport_id[11]); break; + case SCST_TRANSPORTID_PROTOCOLID_COPY_MGR: + scnprintf(name_buf, SIZEOF_NAME_BUF, + "%s", &transport_id[2]); + break; default: scnprintf(name_buf, SIZEOF_NAME_BUF, "(Not known protocol ID %x)", transport_id[0] & 0x0f); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index f883c4633..c447d2c26 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -172,6 +172,18 @@ const struct scst_opcode_descriptor scst_op_descr_inquiry = { }; EXPORT_SYMBOL(scst_op_descr_inquiry); +const struct scst_opcode_descriptor scst_op_descr_extended_copy = { + .od_opcode = EXTENDED_COPY, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 16, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { EXTENDED_COPY, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0, + SCST_OD_DEFAULT_CONTROL_BYTE }, +}; +EXPORT_SYMBOL(scst_op_descr_extended_copy); + const struct scst_opcode_descriptor scst_op_descr_tur = { .od_opcode = TEST_UNIT_READY, .od_support = 3, /* supported as in the standard */ @@ -468,6 +480,8 @@ static int get_cdb_info_write_same16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_ext_copy(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_apt(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_min(struct scst_cmd *cmd, @@ -1256,16 +1270,18 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_WRITE_MEDIUM, .info_len_off = 10, .info_len_len = 4, .get_cdb_info = get_cdb_info_len_4}, - {.ops = 0x83, .devkey = "OOOOOOOOOOOOOOOO", + {.ops = 0x83, .devkey = "O ", /* implemented only for disks */ .info_op_name = "EXTENDED COPY", .info_data_direction = SCST_DATA_WRITE, - .info_op_flags = SCST_WRITE_MEDIUM, + .info_op_flags = SCST_WRITE_MEDIUM|SCST_CAN_GEN_3PARTY_COMMANDS| + SCST_LOCAL_CMD|SCST_DESCRIPTORS_BASED, .info_len_off = 10, .info_len_len = 4, - .get_cdb_info = get_cdb_info_len_4}, - {.ops = 0x84, .devkey = "OOOOOOOOOOOOOOOO", + .get_cdb_info = get_cdb_info_ext_copy}, + {.ops = 0x84, .devkey = "O ", /* implemented only for disks */ .info_op_name = "RECEIVE COPY RESULT", .info_data_direction = SCST_DATA_READ, - .info_op_flags = SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + .info_op_flags = SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD| + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, .info_len_off = 10, .info_len_len = 4, .get_cdb_info = get_cdb_info_len_4}, {.ops = 0x85, .devkey = "O O O ", @@ -6675,6 +6691,9 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, sess->tgt = tgt; INIT_LIST_HEAD(&sess->init_deferred_cmd_list); INIT_LIST_HEAD(&sess->init_deferred_mcmd_list); + INIT_LIST_HEAD(&sess->sess_cm_list_id_list); + INIT_DELAYED_WORK(&sess->sess_cm_list_id_cleanup_work, + (void (*)(struct work_struct *))sess_cm_list_id_cleanup_work_fn); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)) INIT_DELAYED_WORK(&sess->hw_pending_work, scst_hw_pending_work_fn); #else @@ -11215,6 +11234,20 @@ static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, return scst_parse_wrprotect(cmd); } +static int get_cdb_info_ext_copy(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + if (unlikely(cmd->cdb[1] != 0)) { + PRINT_WARNING("Not supported %s service action 0x%x", + scst_get_opcode_name(cmd), cmd->cdb[1]); + scst_set_invalid_field_in_cdb(cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + return 1; + } + + return get_cdb_info_len_4(cmd, sdbops); +} + /** * get_cdb_info_apt() - Parse ATA PASS-THROUGH CDB. * @@ -12781,7 +12814,7 @@ bool __scst_check_blocked_dev(struct scst_cmd *cmd) TRACE_ENTRY(); EXTRACHECKS_BUG_ON(cmd->unblock_dev); - EXTRACHECKS_BUG_ON(cmd->internal); + EXTRACHECKS_BUG_ON(cmd->internal && cmd->cdb[0] != EXTENDED_COPY); if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) goto out; @@ -13616,6 +13649,9 @@ int scst_parse_descriptors(struct scst_cmd *cmd) case UNMAP: res = scst_parse_unmap_descriptors(cmd); break; + case EXTENDED_COPY: + res = scst_cm_parse_descriptors(cmd); + break; default: sBUG(); } @@ -13632,6 +13668,9 @@ static void scst_free_descriptors(struct scst_cmd *cmd) case UNMAP: scst_free_unmap_descriptors(cmd); break; + case EXTENDED_COPY: + scst_cm_free_descriptors(cmd); + break; default: sBUG(); break; @@ -15112,6 +15151,12 @@ void scst_set_pre_exec_time(struct scst_cmd *cmd) TRACE_DBG("cmd %p: pre_exec_time %lld", cmd, cmd->pre_exec_time); } +void scst_set_exec_start(struct scst_cmd *cmd) +{ + cmd->exec_time_counting = true; + scst_set_cur_start(cmd); +} + void scst_set_exec_time(struct scst_cmd *cmd) { cmd->exec_time += scst_get_usec() - cmd->curr_start; diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index ebfb05803..67cbc797a 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -1469,6 +1469,10 @@ int scst_register_virtual_device(struct scst_dev_type *dev_handler, list_add_tail(&dev->dev_list_entry, &scst_dev_list); + res = scst_cm_on_dev_register(dev); + if (res != 0) + goto out_unreg; + mutex_unlock(&scst_mutex); scst_resume_activity(); @@ -1480,6 +1484,12 @@ out: TRACE_EXIT_RES(res); return res; +out_unreg: + dev->dev_unregistering = 1; + list_del(&dev->dev_list_entry); + scst_assign_dev_handler(dev, &scst_null_devtype); + goto out_pr_clear_dev; + #ifndef CONFIG_SCST_PROC out_lock_pr_clear_dev: mutex_lock(&scst_mutex); @@ -1532,6 +1542,8 @@ void scst_unregister_virtual_device(int id) dev->dev_unregistering = 1; + scst_cm_on_dev_unregister(dev); + list_del_init(&dev->dev_list_entry); scst_pr_clear_dev(dev); @@ -2659,6 +2671,15 @@ static int __init init_scst(void) goto out_thread_free; #endif + res = scst_cm_init(); + if (res != 0) +#ifdef CONFIG_SCST_PROC + goto out_proc_cleanup; +#else + goto out_thread_free; +#endif + + PRINT_INFO("SCST version %s loaded successfully (max mem for " "commands %dMB, per device %dMB)", SCST_VERSION_STRING, scst_max_cmd_mem, scst_max_dev_cmd_mem); @@ -2669,6 +2690,11 @@ out: TRACE_EXIT_RES(res); return res; +#ifdef CONFIG_SCST_PROC +out_proc_cleanup: + scst_proc_cleanup_module(); +#endif + out_thread_free: scst_stop_global_threads(); @@ -2751,6 +2777,8 @@ static void __exit exit_scst(void) /* ToDo: unregister_cpu_notifier() */ + scst_cm_exit(); + #ifdef CONFIG_SCST_PROC scst_proc_cleanup_module(); #endif diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index f6010f85f..f0606d677 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -713,6 +713,10 @@ void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev, void scst_block_dev(struct scst_device *dev); void scst_unblock_dev(struct scst_device *dev); +bool scst_do_check_blocked_dev(struct scst_cmd *cmd); +bool __scst_check_blocked_dev(struct scst_cmd *cmd); +void __scst_check_unblock_dev(struct scst_cmd *cmd); +void scst_check_unblock_dev(struct scst_cmd *cmd); int scst_ext_block_dev(struct scst_device *dev, bool sync, ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len); @@ -720,8 +724,6 @@ void scst_ext_unblock_dev(struct scst_device *dev, bool stpg); void __scst_ext_blocking_done(struct scst_device *dev); void scst_ext_blocking_done(struct scst_device *dev); -bool __scst_check_blocked_dev(struct scst_cmd *cmd); - /* * Increases global SCST ref counters which prevent from entering into suspended * activities stage, so protects from any global management operations. @@ -842,6 +844,66 @@ static inline bool scst_lba1_inside_lba2(int64_t lba1, return res; } +#ifndef CONFIG_SCST_PROC + +int scst_cm_on_dev_register(struct scst_device *dev); +void scst_cm_on_dev_unregister(struct scst_device *dev); + +int scst_cm_parse_descriptors(struct scst_cmd *cmd); +void scst_cm_free_descriptors(struct scst_cmd *cmd); + +int scst_cm_ext_copy_exec(struct scst_cmd *cmd); +int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd); + +void sess_cm_list_id_cleanup_work_fn(struct delayed_work *work); +void scst_cm_free_pending_list_ids(struct scst_session *sess); + +bool scst_cm_check_block_all_devs(struct scst_cmd *cmd); +void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd); + +bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd); + +int scst_cm_init(void); +void scst_cm_exit(void); + +#else /* #ifndef CONFIG_SCST_PROC */ + +static inline int scst_cm_on_dev_register(struct scst_device *dev) { return 0; } +static inline void scst_cm_on_dev_unregister(struct scst_device *dev) {} + +static inline int scst_cm_parse_descriptors(struct scst_cmd *cmd) +{ + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + scst_set_cmd_abnormal_done_state(cmd); + return -1; +} +static inline void scst_cm_free_descriptors(struct scst_cmd *cmd) {} + +static inline int scst_cm_ext_copy_exec(struct scst_cmd *cmd) +{ + return SCST_EXEC_NOT_COMPLETED; +} +static inline int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd) +{ + return SCST_EXEC_NOT_COMPLETED; +} + +static inline void sess_cm_list_id_cleanup_work_fn(struct work_struct *work) {} +static inline void scst_cm_free_pending_list_ids(struct scst_session *sess) {} + +static inline bool scst_cm_check_block_all_devs(struct scst_cmd *cmd) { return false; } +static inline void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd) {} + +static inline bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd) +{ + return false; +} + +static inline int scst_cm_init(void) { return 0; } +static inline void scst_cm_exit(void) {} + +#endif /* #ifndef CONFIG_SCST_PROC */ + #ifdef CONFIG_SCST_DEBUG_TM extern void tm_dbg_check_released_cmds(void); extern int tm_dbg_check_cmd(struct scst_cmd *cmd); @@ -901,6 +963,7 @@ void scst_set_alloc_buf_time(struct scst_cmd *cmd); void scst_set_restart_waiting_time(struct scst_cmd *cmd); void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd); void scst_set_pre_exec_time(struct scst_cmd *cmd); +void scst_set_exec_start(struct scst_cmd *cmd); void scst_set_exec_time(struct scst_cmd *cmd); void scst_set_dev_done_time(struct scst_cmd *cmd); void scst_set_xmit_time(struct scst_cmd *cmd); @@ -915,6 +978,7 @@ static inline void scst_set_alloc_buf_time(struct scst_cmd *cmd) {} static inline void scst_set_restart_waiting_time(struct scst_cmd *cmd) {} static inline void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd) {} static inline void scst_set_pre_exec_time(struct scst_cmd *cmd) {} +static inline void scst_set_exec_start(struct scst_cmd *cmd) {} static inline void scst_set_exec_time(struct scst_cmd *cmd) {} static inline void scst_set_dev_done_time(struct scst_cmd *cmd) {} static inline void scst_set_xmit_time(struct scst_cmd *cmd) {} diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index e2218d793..f6d8a01e7 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -162,6 +162,9 @@ static bool scst_cmd_overlap_cwr(struct scst_cmd *cwr_cmd, struct scst_cmd *cmd) res = scst_unmap_overlap(cmd, cwr_cmd->lba, cwr_cmd->data_len); break; + case EXTENDED_COPY: + res = scst_cm_ec_cmd_overlap(cmd, cwr_cmd); + break; default: res = false; break; @@ -430,12 +433,21 @@ static bool scst_check_blocked_dev(struct scst_cmd *cmd) TRACE_ENTRY(); - if (unlikely(cmd->internal)) { + if (unlikely((cmd->op_flags & SCST_CAN_GEN_3PARTY_COMMANDS) != 0)) { + EXTRACHECKS_BUG_ON(cmd->cdb[0] != EXTENDED_COPY); + res = scst_cm_check_block_all_devs(cmd); + goto out; + } + + if (unlikely(cmd->internal || cmd->bypass_blocking)) { /* * The original command can already block the device and must * hold reference to it, so internal command should always pass. */ - sBUG_ON(dev->on_dev_cmd_count == 0); + + /* Copy Manager can send internal INQUIRYs, so don't BUG on them */ + sBUG_ON((dev->on_dev_cmd_count == 0) && (cmd->cdb[0] != INQUIRY)); + res = false; goto out; } @@ -2087,7 +2099,17 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) cmd->state = SCST_CMD_STATE_EXEC_CHECK_SN; - if ((cmd->tgtt->pre_exec == NULL) || unlikely(cmd->internal)) + if (unlikely(cmd->internal)) { + if (cmd->dh_data_buf_alloced && cmd->tgt_i_data_buf_alloced && + (scst_cmd_get_data_direction(cmd) & SCST_DATA_WRITE)) { + TRACE_DBG("Internal WRITE cmd %p with DH alloced data", + cmd); + scst_copy_sg(cmd, SCST_SG_COPY_FROM_TARGET); + } + goto out_descr; + } + + if (cmd->tgtt->pre_exec == NULL) goto out_descr; TRACE_DBG("Calling pre_exec(%p)", cmd); @@ -3161,7 +3183,7 @@ int __scst_check_local_events(struct scst_cmd *cmd, bool preempt_tests_only) TRACE_ENTRY(); - if (unlikely(cmd->internal)) { + if (unlikely(cmd->internal && !cmd->internal_check_local_events)) { if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) { TRACE_MGMT_DBG("ABORTED set, aborting internal " "cmd %p", cmd); @@ -3399,7 +3421,7 @@ static int scst_do_real_exec(struct scst_cmd *cmd) if (devt->exec) { TRACE_DBG("Calling dev handler %s exec(%p)", devt->name, cmd); - scst_set_cur_start(cmd); + scst_set_exec_start(cmd); res = devt->exec(cmd); TRACE_DBG("Dev handler %s exec() returned %d", devt->name, res); @@ -3425,7 +3447,7 @@ static int scst_do_real_exec(struct scst_cmd *cmd) goto out_error; } - scst_set_cur_start(cmd); + scst_set_exec_start(cmd); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) rc = scst_exec_req(scsi_dev, cmd->cdb, cmd->cdb_len, @@ -3519,6 +3541,8 @@ static scst_local_exec_fn scst_local_fns[256] = { [REPORT_LUNS] = scst_report_luns_local, [REQUEST_SENSE] = scst_request_sense_local, [COMPARE_AND_WRITE] = scst_cmp_wr_local, + [EXTENDED_COPY] = scst_cm_ext_copy_exec, + [RECEIVE_COPY_RESULTS] = scst_cm_rcv_copy_res_exec, [MAINTENANCE_IN] = scst_maintenance_in, }; @@ -5785,6 +5809,9 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, TRACE_ENTRY(); + /* Fantom EC commands must not leak here */ + sBUG_ON((cmd->cdb[0] == EXTENDED_COPY) && cmd->internal); + /* * Help Coverity recognize that mcmd != NULL if * call_dev_task_mgmt_fn_received == true. @@ -5834,6 +5861,9 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, */ smp_mb__after_set_bit(); + if (cmd->cdb[0] == EXTENDED_COPY) + scst_cm_abort_ec_cmd(cmd); + if (cmd->tgt_dev == NULL) { spin_lock_irqsave(&scst_init_lock, flags); scst_init_poll_cnt++; @@ -6911,6 +6941,16 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) tgt_done: scst_call_task_mgmt_affected_cmds_done(mcmd); + switch (mcmd->fn) { + case SCST_LUN_RESET: + case SCST_TARGET_RESET: + case SCST_NEXUS_LOSS_SESS: + case SCST_NEXUS_LOSS: + case SCST_UNREG_SESS_TM: + scst_cm_free_pending_list_ids(sess); + break; + } + res = scst_set_mcmd_next_state(mcmd); TRACE_EXIT_RES(res); diff --git a/usr/fileio/Makefile b/usr/fileio/Makefile index 4789564ef..b91948369 100644 --- a/usr/fileio/Makefile +++ b/usr/fileio/Makefile @@ -49,6 +49,7 @@ CFLAGS += $(LOCAL_CFLAGS) #CFLAGS += -DDEBUG_TM_IGNORE #CFLAGS += -DDEBUG_TM_IGNORE -DDEBUG_TM_FN_IGNORE #CFLAGS += -DDEBUG_TM_IGNORE_ALL +CFLAGS += -DDEBUG_EXT_COPY_REMAP all: $(PROGS) diff --git a/usr/fileio/common.c b/usr/fileio/common.c index 41400c44e..8c7938b47 100644 --- a/usr/fileio/common.c +++ b/usr/fileio/common.c @@ -69,11 +69,31 @@ static int open_dev_fd(struct vdisk_dev *dev) return res; } -static inline void set_cmd_error_status(struct scst_user_scsi_cmd_reply_exec *reply, +static void set_resp_data_len(struct vdisk_cmd *vcmd, int32_t resp_data_len) +{ + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; + + TRACE_ENTRY(); + + if (vcmd->may_need_to_free_pbuf && (resp_data_len == 0)) { + struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; + free((void *)(unsigned long)cmd->pbuf); + cmd->pbuf = 0; + reply->pbuf = 0; + } + + reply->resp_data_len = resp_data_len; + + TRACE_EXIT(); + return; +} + +static inline void set_cmd_error_status(struct vdisk_cmd *vcmd, int status) { + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; reply->status = status; - reply->resp_data_len = 0; + set_resp_data_len(vcmd, 0); return; } @@ -108,7 +128,7 @@ void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq) EXTRACHECKS_BUG_ON(vcmd->cmd->subcode != SCST_USER_EXEC); - set_cmd_error_status(reply, SAM_STAT_CHECK_CONDITION); + set_cmd_error_status(vcmd, SAM_STAT_CHECK_CONDITION); reply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), key, asc, ascq); reply->psense_buffer = (unsigned long)vcmd->sense; @@ -117,11 +137,11 @@ void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq) return; } -void set_busy(struct scst_user_scsi_cmd_reply_exec *reply) +void set_busy(struct vdisk_cmd *vcmd) { TRACE_ENTRY(); - set_cmd_error_status(reply, SAM_STAT_TASK_SET_FULL); + set_cmd_error_status(vcmd, SAM_STAT_TASK_SET_FULL); TRACE_MGMT_DBG("%s", "Sending QUEUE_FULL status"); TRACE_EXIT(); @@ -212,6 +232,9 @@ static int do_exec(struct vdisk_cmd *vcmd) TRACE_ENTRY(); + /* Must be reinitialized each time to avoid crash on stale value */ + vcmd->may_need_to_free_pbuf = 0; + switch(cmd->queue_type) { case SCST_CMD_QUEUE_ORDERED: TRACE(TRACE_ORDER, "ORDERED cmd_h %d", vcmd->cmd->cmd_h); @@ -251,6 +274,7 @@ static int do_exec(struct vdisk_cmd *vcmd) else #endif cmd->pbuf = (unsigned long)dev->alloc_fn(cmd->alloc_len); + vcmd->may_need_to_free_pbuf = 1; TRACE_MEM("Buf %"PRIx64" alloced, len %d", cmd->pbuf, cmd->alloc_len); reply->pbuf = cmd->pbuf; @@ -258,7 +282,7 @@ static int do_exec(struct vdisk_cmd *vcmd) TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer " "(len %d)", cmd->alloc_len); #ifndef DEBUG_NOMEM - set_busy(reply); + set_busy(vcmd); #endif goto out; } @@ -524,6 +548,49 @@ static int do_on_free_cmd(struct vdisk_cmd *vcmd) return res; } +#ifdef DEBUG_EXT_COPY_REMAP +static int do_ext_copy_remap(struct vdisk_cmd *vcmd) +{ + struct scst_user_get_cmd *cmd = vcmd->cmd; + struct scst_user_ext_copy_remap *rcmd = &cmd->remap_cmd; + struct scst_user_reply_cmd *reply = vcmd->reply; + struct scst_user_ext_copy_reply_remap *rreply = &reply->remap_reply; + static struct scst_user_ext_copy_data_descr d[1]; + int res = 0; + + TRACE_ENTRY(); + + TRACE_DBG("Ext copy remap cmd %d", cmd->cmd_h); + + memset(reply, 0, sizeof(*reply)); + reply->cmd_h = cmd->cmd_h; + reply->subcode = cmd->subcode; + + memset(rreply, 0, sizeof(*rreply)); + + /* It's only a debug code, so it's OK to use static descr */ + + memset(d, 0, sizeof(d)); + + d[0].data_len = rcmd->data_descr.data_len; + d[0].src_lba = rcmd->data_descr.src_lba; + d[0].dst_lba = rcmd->data_descr.dst_lba; + +#if 1 + rreply->remap_descriptors = (unsigned long)d; + rreply->remap_descriptors_len = sizeof(d); +#else + rreply->status = SAM_STAT_CHECK_CONDITION; + rreply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), + SCST_LOAD_SENSE(scst_sense_data_protect)); + rreply->psense_buffer = (unsigned long)vcmd->sense; +#endif + + TRACE_EXIT_RES(res); + return res; +} +#endif + static int do_tm(struct vdisk_cmd *vcmd, int done) { struct scst_user_get_cmd *cmd = vcmd->cmd; @@ -668,6 +735,12 @@ static int process_cmd(struct vdisk_cmd *vcmd) res = do_on_free_cmd(vcmd); break; +#ifdef DEBUG_EXT_COPY_REMAP + case SCST_USER_EXT_COPY_REMAP: + res = do_ext_copy_remap(vcmd); + break; +#endif + case SCST_USER_TASK_MGMT_RECEIVED: res = do_tm(vcmd, 0); break; @@ -704,7 +777,14 @@ void *main_loop(void *arg) struct vdisk_dev *dev = (struct vdisk_dev *)arg; struct scst_user_get_cmd cmd; struct scst_user_reply_cmd reply; - struct vdisk_cmd vcmd = { -1, &cmd, dev, &reply, {0}}; + struct vdisk_cmd vcmd = { + .fd = -1, + .cmd = &cmd, + .dev = dev, + .may_need_to_free_pbuf = 0, + .reply = &reply, + .sense = {0} + }; int scst_usr_fd = dev->scst_usr_fd; struct pollfd pl; #define MULTI_CMDS_CNT 2 @@ -746,6 +826,7 @@ void *main_loop(void *arg) } } #endif + if (use_multi) { TRACE_DBG("preplies %p (first: %p), replies_cnt %d, " "replies_done %d, cmds_cnt %d", (void *)multi.multi_cmd.preplies, @@ -906,11 +987,13 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) uint64_t dev_id_num = gen_dev_id_num(dev); if (0 == cmd->cdb[2]) { /* supported vital product data pages */ - buf[3] = 3; + buf[3] = 5; buf[4] = 0x0; /* this page */ buf[5] = 0x80; /* unit serial number */ buf[6] = 0x83; /* device identification */ - resp_len = buf[3] + 4; + buf[7] = 0xB0; /* block limits */ + buf[8] = 0xB1; /* block device characteristics */ + resp_len = buf[3] + 6; } else if (0x80 == cmd->cdb[2]) { /* unit serial number */ int usn_len = strlen(dev->usn); buf[1] = 0x80; @@ -957,6 +1040,55 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) buf[2] = (resp_len >> 8) & 0xFF; buf[3] = resp_len & 0xFF; resp_len += 4; + } else if (0xB0 == cmd->cdb[2]) { + /* Block Limits */ + int max_transfer; + buf[1] = 0xB0; + buf[3] = 0x3C; + buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ + /* Optimal transfer granuality is PAGE_SIZE */ + max_transfer = max((int)(4096/dev->block_size), (int)1); + buf[6] = (max_transfer >> 8) & 0xff; + buf[7] = max_transfer & 0xff; + /* + * Max transfer len is min of sg limit and 8M, but we + * don't have access to them here, so let's use 1M. + */ + max_transfer = 1*1024*1024; + buf[8] = (max_transfer >> 24) & 0xff; + buf[9] = (max_transfer >> 16) & 0xff; + buf[10] = (max_transfer >> 8) & 0xff; + buf[11] = max_transfer & 0xff; + /* + * Let's have optimal transfer len 512KB. Better to not + * set it at all, because we don't have such limit, + * but some initiators may not understand that (?). + * From other side, too big transfers are not optimal, + * because SGV cache supports only <4M buffers. + */ + max_transfer = min((int)max_transfer, (int)(512*1024 / dev->block_size)); + buf[12] = (max_transfer >> 24) & 0xff; + buf[13] = (max_transfer >> 16) & 0xff; + buf[14] = (max_transfer >> 8) & 0xff; + buf[15] = max_transfer & 0xff; + resp_len = buf[3] + 4; + } else if (0xB1 == cmd->cdb[2]) { + int r; + /* Block Device Characteristics */ + buf[1] = 0xB1; + buf[3] = 0x3C; +#if 0 + if (virt_dev->rotational) { +#endif + /* 15K RPM */ + r = 0x3A98; +#if 0 + } else + r = 1; +#endif + buf[4] = (r >> 8) & 0xff; + buf[5] = r & 0xff; + resp_len = buf[3] + 4; } else { TRACE_DBG("INQUIRY: Unsupported EVPD page %x", cmd->cdb[2]); @@ -1000,7 +1132,7 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) memcpy(address, buf, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); out: TRACE_EXIT(); @@ -1024,7 +1156,7 @@ static void exec_request_sense(struct vdisk_cmd *vcmd) memcpy(address, b, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1265,7 +1397,7 @@ static void exec_mode_sense(struct vdisk_cmd *vcmd) memcpy(address, buf, offset); if (offset < reply->resp_data_len) - reply->resp_data_len = offset; + set_resp_data_len(vcmd, offset); out: TRACE_EXIT(); @@ -1396,7 +1528,7 @@ static void exec_read_capacity(struct vdisk_cmd *vcmd) memcpy(address, buffer, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1455,7 +1587,7 @@ static void exec_read_capacity16(struct vdisk_cmd *vcmd) memcpy(address, buffer, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1534,7 +1666,7 @@ static void exec_read_toc(struct vdisk_cmd *vcmd) memcpy(address, buffer, off); if (off < reply->resp_data_len) - reply->resp_data_len = off; + set_resp_data_len(vcmd, off); out: TRACE_EXIT(); @@ -1577,7 +1709,6 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; int length = cmd->bufflen; uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf; int fd = vcmd->fd; @@ -1606,7 +1737,7 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) PRINT_ERROR("read() returned %"PRId64" from %d (errno %d)", (uint64_t)err, length, errno); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_read_error)); @@ -1614,7 +1745,7 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) goto out; } - reply->resp_data_len = cmd->bufflen; + set_resp_data_len(vcmd, cmd->bufflen); out: TRACE_EXIT(); @@ -1625,7 +1756,6 @@ static void exec_write(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; loff_t err; int length = cmd->bufflen; uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf; @@ -1658,7 +1788,7 @@ restart: PRINT_ERROR("write() returned %"PRId64" from %d (errno %d, " "cmd_h %x)", err, length, errno, vcmd->cmd->cmd_h); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_write_error)); @@ -1687,7 +1817,6 @@ static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; loff_t err; int64_t length = cmd->bufflen; uint8_t *address = (uint8_t *)(unsigned long)cmd->pbuf; @@ -1740,7 +1869,7 @@ static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff) PRINT_ERROR("read() returned %"PRId64" from %"PRId64" " "(errno %d)", (uint64_t)err, len_mem, errno); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_read_error)); diff --git a/usr/fileio/common.h b/usr/fileio/common.h index 8fb1f14aa..7ef29bb09 100644 --- a/usr/fileio/common.h +++ b/usr/fileio/common.h @@ -97,6 +97,7 @@ struct vdisk_cmd int fd; struct scst_user_get_cmd *cmd; struct vdisk_dev *dev; + unsigned int may_need_to_free_pbuf:1; struct scst_user_reply_cmd *reply; uint8_t sense[SCST_SENSE_BUFFERSIZE]; }; diff --git a/usr/fileio/fileio.c b/usr/fileio/fileio.c index c9ca1aaf1..ec19a1980 100644 --- a/usr/fileio/fileio.c +++ b/usr/fileio/fileio.c @@ -394,6 +394,9 @@ int start(int argc, char **argv) desc.opt.queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER; desc.opt.qerr = SCST_QERR_0_ALL_RESUME; desc.opt.d_sense = SCST_D_SENSE_0_FIXED_SENSE; +#ifdef DEBUG_EXT_COPY_REMAP + desc.opt.ext_copy_remap_supported = 1; +#endif res = ioctl(devs[i].scst_usr_fd, SCST_USER_REGISTER_DEVICE, &desc); if (res != 0) { From e00b83998fe7b2d59cab6eb242fd6f39d3542e08 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 04:01:29 +0000 Subject: [PATCH 25/82] VAAI docs git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6590 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 120 +++++++++++++++++++++++++++++++++++++ scst/README_in-tree | 140 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 250 insertions(+), 10 deletions(-) diff --git a/scst/README b/scst/README index ea045e79f..8779958e6 100644 --- a/scst/README +++ b/scst/README @@ -1783,6 +1783,126 @@ information about ALUA support in Windows Server, see also: (http://msdn.microsoft.com/en-us/library/gg607458%28v=vs.85%29.aspx). +VAAI +---- + +SCST supports all 3 VAAI SCSI commands: WRITE SAME, COMPARE AND WRITE +(ATS) and EXTENDED COPY. Additionally, it supports not directly related +to VAAI Thin Provisioning capabilities, particularly, UNMAP SCSI +commands, WRITE SAME with UNMAP bit as well as thin provisioning related +devices' sysfs attributes (see above). + +In some cases dev handlers should perform some manual actions to fully +benefit from SCST VAAI implementation. Those actions described in the +implementation notes below. For vdisk and fileio_tgt handlers they have +already been implemented. + + +Implementation notes +.................... + +WRITE SAME +~~~~~~~~~~ + +WRITE SAME command supports 2 modes: + +1. Manual writing mode. In this mode WRITE SAME generates a set of +internal WRITE(16) SCSI commands to perform requested writing. + +2. Remap mode. In this mode a dev handler, if supported, can remap being +written blocks to a single block and then tell SCST to manually write +parts of the requested area, which for some reason can not be remapped. + +In both cases dev handlers should call from WRITE SAME command handler +scst_write_same() function. This function as the second argument gets +array of descriptors where to write the requested block of data. Last +element in this array must have len 0. If this argument is NULL, then +the whole area will be manually written by SCST. This value should be +used by dev handlers not supporting remapping blocks. + +User space dev handlers should use SCST_EXEC_REPLY_DO_WRITE_SAME +reply_type of SCST_USER_EXEC subcommand. See scst_user doc for more +info. + + +COMPARE AND WRITE +~~~~~~~~~~~~~~~~~ + +COMPARE AND WRITE implemented by SCST a set of read, compare and write +actions done in atomic manner against affected blocks as well as regular +RESERVE SCSI commands. Particularly, COMPARE AND WRITE doesn't need any +queue flushing and unlimited number of COMPARE AND WRITE commands on +different blocks can be executed simultaneously. + +The read and write actions implemented as generation of internal +READ(16) and WRITE(16) SCSI commands. + +COMPARE AND WRITE command is completely transparent to dev handlers +(they only see the corresponding READ(16) and WRITE(16) commands), so +doesn't require any manual actions from them. + + +EXTENDED COPY +~~~~~~~~~~~~~ + +SCST implements EXTENDED COPY via internal Copy Manager target. This +target has the following specific attributes in its sysfs: + + - allow_not_connected_copy - if not set (default), an initiator can +perform copy only between devices it has direct access to via any +target/session. If set, any initiator can copy between any devices in +the system. + + - mgmt - this attribute allows to control data from which devices can +be copied using Copy Manager. By default, devices belonging to dev +handlers with flag auto_cm_assignment_possible set are auto assigned to +the Copy Manager on the registration. Currently, only vdisk has this +flag set, so all other devices (pass-through, user space, etc.) should +be assigned to the Copy Manager manually. + +Mgmt attribute supports the following commands: + + - add [vname|H:C:I:L] - adds device with name vname or H:C:I:L numbers +(pass-through) to the Copy Manager. + + - del [vname|H:C:I:L] - deletes device with name vname or H:C:I:L numbers +(pass-through) from the Copy Manager. + + +Internally SCST implements EXTENDED COPY as generation of sets of +internal READ(16) and WRITE(16) SCSI commands. Dev handlers don't need +any manual actions to use it. + + +Also SCST provides for dev handlers possibility to remap blocks instead +of copy them, if they support this feature. It allows them to perform +EXTENDED COPY command much faster by just metadata update of their +backend storage, which supposed to be nearly instantaneous. + +To use this feature, a dev handler should setup ext_copy_remap() +callback in its struct scst_dev_type. This callback is called by SCST +during EXTENDED COPY command processing to let the dev hander try to +remap affected blocks at first. + +Upon finish, the dev handler should call scst_ext_copy_remap_done(). In +case of error, the dev handler should set the corresponding sense to cmd +and then also call scst_ext_copy_remap_done(cmd, NULL, 0). + +If dev handler is not able to remap any part of the segment, if should +kmalloc(), then fill all leftover subsegments and supply them to +scst_ext_copy_remap_done(). SCST then will copy the subsegments using +internal copy machine, then kfree() the supplied array. If the dev +handler is not able to remap the whole segment, it can simply directly +supply the original segment to scst_ext_copy_remap_done(). + +It is highly recommended that in normal circumstances dev handlers call +scst_ext_copy_remap_done() from another thread context than one where +ext_copy_remap() callback was originally called, because otherwise there +could be recursion in the segments processing. Hopefully, this thread +context switch is natural for such potentially long operation as +EXTENDED COPY. + + Caching ------- diff --git a/scst/README_in-tree b/scst/README_in-tree index f2a1eb4ef..4a1e010a1 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -1463,6 +1463,16 @@ DEVICE_GROUP dgroup2 { } } +Explicit ALUA +............. + +To enable explicit ALUA you need in addition to the above settings set +expl_alua device attribute to 1 (by default it is 0). Also you need to +run stpgd and supply to it path to a script or program, which will +perform actual path state switching on SET TARGET PORT GROUPS command, +for instance, by calling drbdadm. For more information see stpgd README +as well as sample script scst_on_stpg. + DRBD compatibility .................. @@ -1500,16 +1510,6 @@ For the secondary to primary transition procedure is similar. In case of explicit ALUA, SCST automatically performs the necessary devices blocking around sending SCST_EVENT_STPG_USER_INVOKE event. -Explicit ALUA -............. - -To enable explicit ALUA you need in addition to the above settings set -expl_alua device attribute to 1 (by default it is 0). Also you need to -run stpgd and supply to it path to a script or program, which will -perform actual path state switching on SET TARGET PORT GROUPS command, -for instance, by calling drbdadm. For more information see stpgd README -as well as sample script scst_on_stpg. - Checking the Target Configuration ................................. @@ -1631,6 +1631,126 @@ information about ALUA support in Windows Server, see also: (http://msdn.microsoft.com/en-us/library/gg607458%28v=vs.85%29.aspx). +VAAI +---- + +SCST supports all 3 VAAI SCSI commands: WRITE SAME, COMPARE AND WRITE +(ATS) and EXTENDED COPY. Additionally, it supports not directly related +to VAAI Thin Provisioning capabilities, particularly, UNMAP SCSI +commands, WRITE SAME with UNMAP bit as well as thin provisioning related +devices' sysfs attributes (see above). + +In some cases dev handlers should perform some manual actions to fully +benefit from SCST VAAI implementation. Those actions described in the +implementation notes below. For vdisk and fileio_tgt handlers they have +already been implemented. + + +Implementation notes +.................... + +WRITE SAME +~~~~~~~~~~ + +WRITE SAME command supports 2 modes: + +1. Manual writing mode. In this mode WRITE SAME generates a set of +internal WRITE(16) SCSI commands to perform requested writing. + +2. Remap mode. In this mode a dev handler, if supported, can remap being +written blocks to a single block and then tell SCST to manually write +parts of the requested area, which for some reason can not be remapped. + +In both cases dev handlers should call from WRITE SAME command handler +scst_write_same() function. This function as the second argument gets +array of descriptors where to write the requested block of data. Last +element in this array must have len 0. If this argument is NULL, then +the whole area will be manually written by SCST. This value should be +used by dev handlers not supporting remapping blocks. + +User space dev handlers should use SCST_EXEC_REPLY_DO_WRITE_SAME +reply_type of SCST_USER_EXEC subcommand. See scst_user doc for more +info. + + +COMPARE AND WRITE +~~~~~~~~~~~~~~~~~ + +COMPARE AND WRITE implemented by SCST a set of read, compare and write +actions done in atomic manner against affected blocks as well as regular +RESERVE SCSI commands. Particularly, COMPARE AND WRITE doesn't need any +queue flushing and unlimited number of COMPARE AND WRITE commands on +different blocks can be executed simultaneously. + +The read and write actions implemented as generation of internal +READ(16) and WRITE(16) SCSI commands. + +COMPARE AND WRITE command is completely transparent to dev handlers +(they only see the corresponding READ(16) and WRITE(16) commands), so +doesn't require any manual actions from them. + + +EXTENDED COPY +~~~~~~~~~~~~~ + +SCST implements EXTENDED COPY via internal Copy Manager target. This +target has the following specific attributes in its sysfs: + + - allow_not_connected_copy - if not set (default), an initiator can +perform copy only between devices it has direct access to via any +target/session. If set, any initiator can copy between any devices in +the system. + + - mgmt - this attribute allows to control data from which devices can +be copied using Copy Manager. By default, devices belonging to dev +handlers with flag auto_cm_assignment_possible set are auto assigned to +the Copy Manager on the registration. Currently, only vdisk has this +flag set, so all other devices (pass-through, user space, etc.) should +be assigned to the Copy Manager manually. + +Mgmt attribute supports the following commands: + + - add [vname|H:C:I:L] - adds device with name vname or H:C:I:L numbers +(pass-through) to the Copy Manager. + + - del [vname|H:C:I:L] - deletes device with name vname or H:C:I:L numbers +(pass-through) from the Copy Manager. + + +Internally SCST implements EXTENDED COPY as generation of sets of +internal READ(16) and WRITE(16) SCSI commands. Dev handlers don't need +any manual actions to use it. + + +Also SCST provides for dev handlers possibility to remap blocks instead +of copy them, if they support this feature. It allows them to perform +EXTENDED COPY command much faster by just metadata update of their +backend storage, which supposed to be nearly instantaneous. + +To use this feature, a dev handler should setup ext_copy_remap() +callback in its struct scst_dev_type. This callback is called by SCST +during EXTENDED COPY command processing to let the dev hander try to +remap affected blocks at first. + +Upon finish, the dev handler should call scst_ext_copy_remap_done(). In +case of error, the dev handler should set the corresponding sense to cmd +and then also call scst_ext_copy_remap_done(cmd, NULL, 0). + +If dev handler is not able to remap any part of the segment, if should +kmalloc(), then fill all leftover subsegments and supply them to +scst_ext_copy_remap_done(). SCST then will copy the subsegments using +internal copy machine, then kfree() the supplied array. If the dev +handler is not able to remap the whole segment, it can simply directly +supply the original segment to scst_ext_copy_remap_done(). + +It is highly recommended that in normal circumstances dev handlers call +scst_ext_copy_remap_done() from another thread context than one where +ext_copy_remap() callback was originally called, because otherwise there +could be recursion in the segments processing. Hopefully, this thread +context switch is natural for such potentially long operation as +EXTENDED COPY. + + Caching ------- From 5f7ac7f4bf814fcf8386a56e5928b5f9cc0c848f Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 06:17:17 +0000 Subject: [PATCH 26/82] Version updated to 3.2.0 git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6593 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- fcst/fcst.h | 2 +- iscsi-scst/README | 2 +- iscsi-scst/include/iscsi_scst_ver.h | 2 +- qla2x00t/qla2x00-target/README | 4 ++-- qla2x00t/qla2x00-target/qla2x00t.h | 2 +- scst/README | 2 +- scst/include/scst_const.h | 2 +- scst/src/dev_handlers/scst_vdisk.c | 2 +- scst_local/scst_local.c | 2 +- srpt/src/ib_srpt.c | 2 +- usr/fileio/README | 2 +- usr/fileio/common.h | 2 +- usr/include/version.h | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fcst/fcst.h b/fcst/fcst.h index 9f2276b01..771937a3a 100644 --- a/fcst/fcst.h +++ b/fcst/fcst.h @@ -26,7 +26,7 @@ #include "scst.h" #endif -#define FT_VERSION "3.1.0-pre" +#define FT_VERSION "3.2.0-pre" #define FT_MODULE "fcst" #define FT_MAX_HW_PENDING_TIME 20 /* max I/O time in seconds */ diff --git a/iscsi-scst/README b/iscsi-scst/README index 95580266d..74a546018 100644 --- a/iscsi-scst/README +++ b/iscsi-scst/README @@ -1,7 +1,7 @@ iSCSI SCST target driver ======================== -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2014 ---------------------------- ISCSI-SCST is a deeply reworked fork of iSCSI Enterprise Target (IET) diff --git a/iscsi-scst/include/iscsi_scst_ver.h b/iscsi-scst/include/iscsi_scst_ver.h index 566425c85..9c60851f5 100644 --- a/iscsi-scst/include/iscsi_scst_ver.h +++ b/iscsi-scst/include/iscsi_scst_ver.h @@ -21,4 +21,4 @@ #define ISCSI_VERSION_STRING_SUFFIX #endif -#define ISCSI_VERSION_STRING "3.1.0-pre1" ISCSI_VERSION_STRING_SUFFIX +#define ISCSI_VERSION_STRING "3.2.0-pre1" ISCSI_VERSION_STRING_SUFFIX diff --git a/qla2x00t/qla2x00-target/README b/qla2x00t/qla2x00-target/README index ddd612033..129742c28 100644 --- a/qla2x00t/qla2x00-target/README +++ b/qla2x00t/qla2x00-target/README @@ -1,7 +1,7 @@ Target driver for QLogic 2[2-6]xx/8[1-3]xx Fibre Channel cards ============================================================== -Version 3.1.0, XX XXXXX 2015 +Version 3.2.0, XX XXXXX 2015 ---------------------------- This is target driver for QLogic 2[2-6]xx/8[1-3]xx Fibre Channel cards. @@ -20,7 +20,7 @@ necessary callbacks, but it's still capable to work as initiator only. Mode, when a host acts as the initiator and the target simultaneously, is supported as well. -This version is compatible with SCST core version 3.1.0 and higher and +This version is compatible with SCST core version 3.2.0 and higher and Linux kernel 2.6.26 and higher. Sorry, kernels below 2.6.26 are not supported, because it's too hard to backport used initiator driver to older kernels. diff --git a/qla2x00t/qla2x00-target/qla2x00t.h b/qla2x00t/qla2x00-target/qla2x00t.h index 5ff441f4b..242fe5554 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.h +++ b/qla2x00t/qla2x00-target/qla2x00t.h @@ -31,7 +31,7 @@ /* Version numbers, the same as for the kernel */ #define Q2T_VERSION(a, b, c, d) (((a) << 030) + ((b) << 020) + (c) << 010 + (d)) #define Q2T_VERSION_CODE Q2T_VERSION(4, 1, 0, 0) -#define Q2T_VERSION_STRING "3.1.0-pre1" +#define Q2T_VERSION_STRING "3.2.0-pre1" #define Q2T_PROC_VERSION_NAME "version" #define Q2T_MAX_CDB_LEN 16 diff --git a/scst/README b/scst/README index 8779958e6..e7991d892 100644 --- a/scst/README +++ b/scst/README @@ -1,7 +1,7 @@ Generic SCSI target mid-level for Linux (SCST) ============================================== -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2015 ---------------------------- SCST is designed to provide unified, consistent interface between SCSI diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 42f5b9e0c..6dc9597b0 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -50,7 +50,7 @@ #else #define SCST_VERSION_STRING_SUFFIX #endif -#define SCST_VERSION_NAME "3.1.0-pre1" +#define SCST_VERSION_NAME "3.2.0-pre1" #define SCST_VERSION_STRING SCST_VERSION_NAME SCST_VERSION_STRING_SUFFIX #define SCST_CONST_VERSION SCST_CONST_INTF_VER diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index e47d79631..04cdf447a 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -79,7 +79,7 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define SCST_FIO_VENDOR "SCST_FIO" #define SCST_BIO_VENDOR "SCST_BIO" /* 4 byte ASCII Product Revision Level - left aligned */ -#define SCST_FIO_REV " 310" +#define SCST_FIO_REV " 320" #define MAX_USN_LEN (20+1) /* For '\0' */ #define MAX_INQ_VEND_SPECIFIC_LEN (INQ_BUF_SZ - 96) diff --git a/scst_local/scst_local.c b/scst_local/scst_local.c index eca959e8b..1815ddadf 100644 --- a/scst_local/scst_local.c +++ b/scst_local/scst_local.c @@ -88,7 +88,7 @@ static unsigned long scst_local_trace_flag = SCST_LOCAL_DEFAULT_LOG_FLAGS; #define scsi_bufflen(cmd) ((cmd)->request_bufflen) #endif -#define SCST_LOCAL_VERSION "3.1" +#define SCST_LOCAL_VERSION "3.2" static const char *scst_local_version_date = "20110901"; /* Some statistics */ diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 6f34cbe13..e8b6eb6d3 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -80,7 +80,7 @@ /* Name of this kernel module. */ #define DRV_NAME "ib_srpt" -#define DRV_VERSION "3.1.0-pre#" __stringify(OFED_FLAVOR) +#define DRV_VERSION "3.2.0-pre#" __stringify(OFED_FLAVOR) #define DRV_RELDATE "(not yet released)" #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) /* Flags to be used in SCST debug tracing statements. */ diff --git a/usr/fileio/README b/usr/fileio/README index 2411bf479..31975bec8 100644 --- a/usr/fileio/README +++ b/usr/fileio/README @@ -1,7 +1,7 @@ User space FILEIO handler ========================= -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2014 ---------------------------- User space program fileio_tgt uses interface of SCST's scst_user dev diff --git a/usr/fileio/common.h b/usr/fileio/common.h index 7ef29bb09..aacdbc5da 100644 --- a/usr/fileio/common.h +++ b/usr/fileio/common.h @@ -30,7 +30,7 @@ /* 8 byte ASCII Vendor */ #define VENDOR "SCST_USR" /* 4 byte ASCII Product Revision Level - left aligned */ -#define FIO_REV " 310" +#define FIO_REV " 320" #define MAX_USN_LEN (20+1) /* For '\0' */ diff --git a/usr/include/version.h b/usr/include/version.h index f4626c8c5..d1a857555 100644 --- a/usr/include/version.h +++ b/usr/include/version.h @@ -19,6 +19,6 @@ #ifndef __VERSION_H #define __VERSION_H -#define VERSION_STR "3.1.0-pre1" +#define VERSION_STR "3.2.0-pre1" #endif /* __VERSION_H */ From db608b607549b151c378fb9662161bc0c070827f Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 6 Nov 2015 06:48:01 +0000 Subject: [PATCH 27/82] Web updates git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6594 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- www/downloads.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/downloads.html b/www/downloads.html index 75ddedd12..1b842330f 100644 --- a/www/downloads.html +++ b/www/downloads.html @@ -51,12 +51,14 @@ NOTE! Both those projects are very early in the development process, so not recommended for production use yet.

-

The latest development version of SCST is 3.1. You can download it as well as target drivers and user space +

The latest development version of SCST is 3.2. You can download it as well as target drivers and user space utilities directly from the SCST SVN. You can access it using either web-based SVN repository viewer or using anonymous access:

svn checkout svn://svn.code.sf.net/p/scst/svn/trunk scst-trunk

+

Version 3.1 is currently in the RC features freeze phase and going to be released in the next few weeks. You can download it from 3.1.x branch.

+

Also you can find in the SCST SVN the latest updates for the stable branches. More information about accessing SVN repository may be found here. Or, alternatively, you can download it as a GNU tarball from here.

From f38dbc2dff37644e80cb80ae414d983f57903128 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 17:36:46 +0000 Subject: [PATCH 28/82] scst_dlm: Build fix for older kernels (2.6.32) git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6595 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_dlm.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/scst/src/scst_dlm.c b/scst/src/scst_dlm.c index 369c98c24..438486244 100644 --- a/scst/src/scst_dlm.c +++ b/scst/src/scst_dlm.c @@ -742,12 +742,7 @@ static void dump_lockspace(const char *cl_dev_id) PRINT_INFO("Invoking %s", argv2); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) call_usermodehelper(argv0, argv, envp, UMH_WAIT_PROC); -#else - call_usermodehelper_fns(argv0, argv, envp, UMH_WAIT_PROC, NULL, NULL, - NULL); -#endif out: kfree(envp[0]); @@ -1303,10 +1298,7 @@ unlock_ls: mutex_unlock(&pr_dlm->ls_mutex); } -#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) -static struct workqueue_struct * -alloc_workqueue_backport(const char *fmt, unsigned flags, unsigned max_active, - ...) +static struct workqueue_struct *create_st_wq(const char *fmt, ...) { struct workqueue_struct *wq = NULL; va_list ap; @@ -1316,13 +1308,10 @@ alloc_workqueue_backport(const char *fmt, unsigned flags, unsigned max_active, name = kvasprintf(GFP_KERNEL, fmt, ap); va_end(ap); if (name) - wq = alloc_workqueue(name, flags, max_active); + wq = create_singlethread_workqueue(name); kfree(name); return wq; } -#undef alloc_workqueue -#define alloc_workqueue alloc_workqueue_backport -#endif /* * Caller must ensure that no commands are being executed for device @dev, @@ -1353,24 +1342,21 @@ static int scst_pr_dlm_init(struct scst_device *dev, const char *cl_dev_id) if (!dev->pr_dlm->cl_dev_id) goto err_free; - dev->pr_dlm->from_wq = alloc_ordered_workqueue("%s_from_dlm", 0, - dev->virt_name); + dev->pr_dlm->from_wq = create_st_wq("%s_from_dlm", dev->virt_name); if (IS_ERR(dev->pr_dlm->from_wq)) { res = PTR_ERR(dev->pr_dlm->from_wq); dev->pr_dlm->from_wq = NULL; goto err_free; } - dev->pr_dlm->to_wq = alloc_ordered_workqueue("%s_to_dlm", 0, - dev->virt_name); + dev->pr_dlm->to_wq = create_st_wq("%s_to_dlm", dev->virt_name); if (IS_ERR(dev->pr_dlm->to_wq)) { res = PTR_ERR(dev->pr_dlm->to_wq); dev->pr_dlm->to_wq = NULL; goto err_free; } - dev->pr_dlm->upd_wq = alloc_ordered_workqueue("%s_upd_dlm", 0, - dev->virt_name); + dev->pr_dlm->upd_wq = create_st_wq("%s_upd_dlm", dev->virt_name); if (IS_ERR(dev->pr_dlm->upd_wq)) { res = PTR_ERR(dev->pr_dlm->upd_wq); dev->pr_dlm->upd_wq = NULL; From c5aa1ce1e0b3d274f54b53afcdc37b168be2f32a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 17:37:13 +0000 Subject: [PATCH 29/82] scst_dlm: Add __printf attribute to create_st_wq() git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6596 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_dlm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scst/src/scst_dlm.c b/scst/src/scst_dlm.c index 438486244..3ba9f60c4 100644 --- a/scst/src/scst_dlm.c +++ b/scst/src/scst_dlm.c @@ -1298,7 +1298,8 @@ unlock_ls: mutex_unlock(&pr_dlm->ls_mutex); } -static struct workqueue_struct *create_st_wq(const char *fmt, ...) +static struct workqueue_struct *__printf(1, 2) +create_st_wq(const char *fmt, ...) { struct workqueue_struct *wq = NULL; va_list ap; From d9bd3f56cfab21c184f95c16f4d9c7a97de50f20 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 17:46:28 +0000 Subject: [PATCH 30/82] scst_lib: Fix return type of __scst_adjust_sg_get_tail() git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6597 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index c447d2c26..65012a41a 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -3314,7 +3314,7 @@ static void scst_adjust_sg(struct scst_cmd *cmd, bool reg_sg, return; } -static bool __scst_adjust_sg_get_tail(struct scst_cmd *cmd, +static int __scst_adjust_sg_get_tail(struct scst_cmd *cmd, struct scatterlist *sg, int *sg_cnt, struct scatterlist **res_sg, int *res_sg_cnt, int adjust_len, struct scst_orig_sg_data *orig_sg, int must_left) From b6440a0d3b1f31d1ef8446b343214e998c64d72e Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 17:48:37 +0000 Subject: [PATCH 31/82] scst_tg: Suppress a smatch complaint git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6598 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_tg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 20a469939..7ecf67c3e 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -1687,7 +1687,13 @@ int scst_tg_set_group_info(struct scst_cmd *cmd) TRACE_DBG("dg %s (%p) found, dev %s", dg->name, dg, dev->virt_name); for (i = 4, j = 0; i + 4 <= len; i += 4, j++) { +#ifndef __CHECKER__ + /* + * Hide the statement below for smatch because otherwise it + * triggers a false positive. + */ WARN_ON_ONCE(j >= tpg_desc_count); +#endif osi[j].new_state = buf[i] & 0x1f; switch (osi[j].new_state) { case SCST_TG_STATE_OPTIMIZED: From 40db391c12caa8be67f4ce5ac7a3eb2718f2ed2f Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 17:54:00 +0000 Subject: [PATCH 32/82] scst_event: Avoid that payload lengths above 0x80000000 cause trouble (detected by smatch) git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6599 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_event.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c index 52cec1121..368f726c5 100644 --- a/scst/src/scst_event.c +++ b/scst/src/scst_event.c @@ -545,12 +545,12 @@ static int scst_event_get_event_from_user(void __user *arg, struct scst_event_entry **out_event_entry) { int res, rc, event_entry_len; - int32_t payload_len; + uint32_t payload_len; struct scst_event_entry *event_entry; TRACE_ENTRY(); - res = get_user(payload_len, (int32_t __user *)arg); + res = get_user(payload_len, (uint32_t __user *)arg); if (res != 0) { PRINT_ERROR("Failed to get payload len: %d", res); goto out; From 5d73f89bb07b5f558f83c2269ef4407fddc69844 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:03:36 +0000 Subject: [PATCH 33/82] isert-scst: Remove unreachable code Code after BUG() is unreachable both with CONFIG_BUG=y and with CONFIG_BUG=n. Hence remove such code. Detected by smatch. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6600 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/isert_login.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/isert_login.c b/iscsi-scst/kernel/isert-scst/isert_login.c index f92eafe30..1e15f89c8 100644 --- a/iscsi-scst/kernel/isert-scst/isert_login.c +++ b/iscsi-scst/kernel/isert-scst/isert_login.c @@ -804,11 +804,8 @@ int isert_login_req_rx(struct iscsi_cmnd *login_req) switch (dev->state) { case CS_INIT: case CS_RSP_FINISHED: - if (unlikely(dev->login_req != NULL)) { + if (unlikely(dev->login_req != NULL)) sBUG(); - res = -EINVAL; - goto out; - } break; case CS_REQ_BHS: /* Got login request before done handling old one */ @@ -825,8 +822,6 @@ int isert_login_req_rx(struct iscsi_cmnd *login_req) default: sBUG(); - res = -EINVAL; - goto out; } From 5dd3194e66437319b5f79c236cabb91e1aa4791e Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:04:49 +0000 Subject: [PATCH 34/82] scst_user: Avoid leaking kernel data to user space There is a hole in struct scst_user_opt so that structure must be zero-initialized to avoid leaking kernel data to user space. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6601 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_user.c | 1 + 1 file changed, 1 insertion(+) diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index c4c65a2a2..45288befd 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -3777,6 +3777,7 @@ static int dev_user_get_opt(struct file *file, void __user *arg) if (unlikely(res != 0)) goto out; + memset(&opt, 0, sizeof(opt)); opt.parse_type = dev->parse_type; opt.on_free_cmd_type = dev->on_free_cmd_type; opt.memory_reuse_type = dev->memory_reuse_type; From e556a79e3d0c2ee2cfea1e11396806fd278344d9 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:14:54 +0000 Subject: [PATCH 35/82] scst_vdisk: Do not initialize static variables to zero Detected by checkpatch. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6602 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 04cdf447a..ff73a2e77 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -7383,7 +7383,7 @@ static void vdev_ext_copy_remap(struct scst_cmd *cmd, struct scst_ext_copy_seg_descr *seg) { struct scst_ext_copy_data_descr *d; - static int shift = 0; + static int shift; static DEFINE_SPINLOCK(lock); int s; From e4fccdcc13a8ca0d2c08d3cdcbc6573f5eb4fb51 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:16:04 +0000 Subject: [PATCH 36/82] scst_lib: Remove space before closing parenthesis Detected by checkpatch. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6603 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 65012a41a..d13c39ca4 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -5973,7 +5973,7 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", - wsp, wsp->ws_cur_descr, (long long )wsp->ws_cur_lba, + wsp, wsp->ws_cur_descr, (long long)wsp->ws_cur_lba, wsp->ws_left_to_send); } if (wsp->ws_left_to_send == 0) @@ -6041,7 +6041,7 @@ again: wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", - wsp, wsp->ws_cur_descr, (long long )wsp->ws_cur_lba, + wsp, wsp->ws_cur_descr, (long long)wsp->ws_cur_lba, wsp->ws_left_to_send); goto again; } From 48c2bd8903bd37b91ae4ececdda13eda092c2a59 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:16:42 +0000 Subject: [PATCH 37/82] scst_vdisk: Remove trailing whitespace git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6604 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index ff73a2e77..58b7076fa 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -4180,7 +4180,7 @@ static int vdisk_block_limits(uint8_t *buf, struct scst_cmd *cmd, buf[1] = 0xB0; buf[3] = 0x3C; buf[4] = 1; /* WSNZ set */ - buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ + buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ /* Optimal transfer granuality is PAGE_SIZE */ put_unaligned_be16(max_t(int, PAGE_SIZE / dev->block_size, 1), &buf[6]); From 35a3a034289dea7e958ae21dc2b9df559d81bf98 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:18:01 +0000 Subject: [PATCH 38/82] scst: Spelling fix - change handler into handler git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6605 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/README | 2 +- scst/README_in-tree | 2 +- scst/include/scst.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scst/README b/scst/README index e7991d892..1177aba07 100644 --- a/scst/README +++ b/scst/README @@ -1881,7 +1881,7 @@ backend storage, which supposed to be nearly instantaneous. To use this feature, a dev handler should setup ext_copy_remap() callback in its struct scst_dev_type. This callback is called by SCST -during EXTENDED COPY command processing to let the dev hander try to +during EXTENDED COPY command processing to let the dev handler try to remap affected blocks at first. Upon finish, the dev handler should call scst_ext_copy_remap_done(). In diff --git a/scst/README_in-tree b/scst/README_in-tree index 4a1e010a1..8f8205f61 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -1729,7 +1729,7 @@ backend storage, which supposed to be nearly instantaneous. To use this feature, a dev handler should setup ext_copy_remap() callback in its struct scst_dev_type. This callback is called by SCST -during EXTENDED COPY command processing to let the dev hander try to +during EXTENDED COPY command processing to let the dev handler try to remap affected blocks at first. Upon finish, the dev handler should call scst_ext_copy_remap_done(). In diff --git a/scst/include/scst.h b/scst/include/scst.h index aeaef440f..24e533f8d 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -1428,7 +1428,7 @@ struct scst_dev_type { void (*on_free_cmd)(struct scst_cmd *cmd); /* - * Called during EXTENDED COPY command processing to let dev hander + * Called during EXTENDED COPY command processing to let dev handler * try to remap blocks at first. Upon finish, the dev handler supposed * to call scst_ext_copy_remap_done(). See description of this * function for more details. From 8e119def8d18d8cff0d0b05160edea3160000bb2 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:20:08 +0000 Subject: [PATCH 39/82] scst.h: Reformat a comment to suppress a checkpatch complaint git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6606 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index 24e533f8d..b4168b601 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2915,9 +2915,11 @@ struct scst_device { int (*dev_dif_fn)(struct scst_cmd *cmd); __be16 dev_dif_static_app_tag; /* fixed APP TAG for all blocks in dev */ - __be32 dev_dif_static_app_ref_tag; /* fixed APP TAG part from REF - * TAG for all blocks in dev. - * Valid only with dif type 3 */ + /* + * Fixed APP TAG part from REF TAG for all blocks in dev. Valid only + * with dif type 3. + */ + __be32 dev_dif_static_app_ref_tag; /* Cache to optimize scst_parse_*protect() routines */ enum scst_dif_actions dev_dif_rd_actions; From 90d901fab757bf97813b91c24c2c2f15e8c2bd63 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:25:59 +0000 Subject: [PATCH 40/82] scst: Fix block comment formatting git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6607 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_rdma.c | 14 ++++++++---- iscsi-scst/kernel/isert-scst/isert.c | 8 ++++--- scst/src/dev_handlers/scst_vdisk.c | 14 +++++++----- scst/src/scst_lib.c | 29 ++++++++++++------------ scst/src/scst_targ.c | 6 +++-- 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser_rdma.c b/iscsi-scst/kernel/isert-scst/iser_rdma.c index 1b3e849a9..1f1c56e66 100644 --- a/iscsi-scst/kernel/isert-scst/iser_rdma.c +++ b/iscsi-scst/kernel/isert-scst/iser_rdma.c @@ -136,8 +136,10 @@ void isert_post_drain(struct isert_connection *isert_conn) err = ib_post_send(isert_conn->qp, &isert_conn->drain_wr.send_wr, &bad_wr); if (unlikely(err)) { pr_err("Failed to post drain wr, err:%d\n", err); - /* We need to decrement iser_conn->kref in order to be able to cleanup - * the connection */ + /* + * We need to decrement iser_conn->kref in order to be + * able to cleanup the connection. + */ set_bit(ISERT_DRAIN_FAILED, &isert_conn->flags); isert_conn_free(isert_conn); } @@ -633,9 +635,11 @@ static void isert_handle_wc_error(struct ib_wc *wc) isert_buf->dma_dir); isert_buf->sg_cnt = 0; } - /* RDMA-WR and SEND response of a READ task - are sent together, so when receiving RDMA-WR error, - wait until SEND error arrives to complete the task */ + /* + * RDMA-WR and SEND response of a READ task + * are sent together, so when receiving RDMA-WR error, + * wait until SEND error arrives to complete the task. + */ break; default: pr_err("unexpected opcode %d, wc:%p wr_id:%p conn:%p\n", diff --git a/iscsi-scst/kernel/isert-scst/isert.c b/iscsi-scst/kernel/isert-scst/isert.c index 5cdc28901..a8a382ca2 100644 --- a/iscsi-scst/kernel/isert-scst/isert.c +++ b/iscsi-scst/kernel/isert-scst/isert.c @@ -282,9 +282,11 @@ static void isert_free_conn(struct iscsi_conn *conn) int isert_handle_close_connection(struct iscsi_conn *conn) { isert_mark_conn_closed(conn, 0); - /* Take care of case where our connection is being closed - * without being connected to a session - if connection allocation - * failed for some reason */ + /* + * Take care of case where our connection is being closed without + * being connected to a session - if connection allocation failed for + * some reason. + */ if (unlikely(!conn->session)) isert_free_connection(conn); else diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 58b7076fa..37b7ef1b3 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -190,10 +190,10 @@ struct scst_vdisk_dev { uint64_t format_progress_to_do, format_progress_done; int virt_id; - char name[64+1]; /* Name of the virtual device, - must be <= SCSI Model + 1 */ - char *filename; /* File name, protected by - scst_mutex and suspended activities */ + /* Name of the virtual device, must be <= SCSI Model + 1 */ + char name[64+1]; + /* File name, protected by scst_mutex and suspended activities */ + char *filename; uint16_t command_set_version; /* All 14 protected by vdisk_serial_rwlock */ @@ -5494,8 +5494,10 @@ static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p) off = 4; if (cmd->cdb[6] <= 1) { /* Fistr TOC Track Descriptor */ - /* ADDR 0x10 - Q Sub-channel encodes current position data - CONTROL 0x04 - Data track, recoreded uninterrupted */ + /* + * ADDR 0x10 - Q Sub-channel encodes current position data + * CONTROL 0x04 - Data track, recoreded uninterrupted + */ buffer[off+1] = 0x14; /* Track Number */ buffer[off+2] = 0x01; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index d13c39ca4..e85c9e688 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -492,19 +492,19 @@ static int get_cdb_info_var_len(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); /* -+=====================================-============-======- -| Command name | Operation | Type | -| | code | | -|-------------------------------------+------------+------+ - -+=========================================================+ -|Key: M = command implementation is mandatory. | -| O = command implementation is optional. | -| V = Vendor-specific | -| R = Reserved | -| ' '= DON'T use for this device | -+=========================================================+ -*/ + * +=====================================-============-======- + * | Command name | Operation | Type | + * | | code | | + * |-------------------------------------+------------+------+ + * + * +=========================================================+ + * |Key: M = command implementation is mandatory. | + * | O = command implementation is optional. | + * | V = Vendor-specific | + * | R = Reserved | + * | ' '= DON'T use for this device | + * +=========================================================+ + */ #define SCST_CDB_MANDATORY 'M' /* mandatory */ #define SCST_CDB_OPTIONAL 'O' /* optional */ @@ -581,7 +581,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { * |||||||||| |||+----> Optical card (F) * |||||||||| |||| * |||||||||| |||| - * 0123456789ABCDEF -> TYPE_???? */ + * 0123456789ABCDEF -> TYPE_???? + */ /* 6-bytes length CDB */ {.ops = 0x00, .devkey = "MMMMMMMMMMMMMMMM", diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index f6d8a01e7..c2c2841f1 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -6306,8 +6306,10 @@ static int scst_clear_task_set(struct scst_mgmt_cmd *mcmd) return res; } -/* Returns 0 if the command processing should be continued, - * >0, if it should be requeued, <0 otherwise */ +/* + * Returns 0 if the command processing should be continued, + * >0, if it should be requeued, <0 otherwise. + */ static int scst_mgmt_cmd_init(struct scst_mgmt_cmd *mcmd) { int res = 0, rc, t; From 26049b246fda537e9150b3b572e40d22113d6ef7 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:54:27 +0000 Subject: [PATCH 41/82] scst: Place constants at the right-hand-side in comparisons This patch suppresses a class of kernel v4.3 checkpatch complaints. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6608 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 32 +++++++++++++++--------------- scst/src/scst_main.c | 2 +- scst/src/scst_targ.c | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 37b7ef1b3..79ef50208 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -4382,19 +4382,19 @@ static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p) SCSI_INQ_PQ_CON << 5 | dev->type; /* Vital Product */ if (cmd->cdb[1] & EVPD) { - if (0 == cmd->cdb[2]) { + if (cmd->cdb[2] == 0) { resp_len = vdisk_sup_vpd(buf, cmd, virt_dev); - } else if (0x80 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x80) { resp_len = vdisk_usn_vpd(buf, cmd, virt_dev); - } else if (0x83 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x83) { resp_len = vdisk_dev_id_vpd(buf, cmd, virt_dev); - } else if (0x86 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x86) { resp_len = vdisk_ext_inq(buf, cmd, virt_dev); - } else if ((0xB0 == cmd->cdb[2]) && (dev->type == TYPE_DISK)) { + } else if (cmd->cdb[2] == 0xB0 && dev->type == TYPE_DISK) { resp_len = vdisk_block_limits(buf, cmd, virt_dev); - } else if ((0xB1 == cmd->cdb[2]) && (dev->type == TYPE_DISK)) { + } else if (cmd->cdb[2] == 0xB1 && dev->type == TYPE_DISK) { resp_len = vdisk_bdev_char(buf, cmd, virt_dev); - } else if ((0xB2 == cmd->cdb[2]) && (dev->type == TYPE_DISK) && + } else if (cmd->cdb[2] == 0xB2 && dev->type == TYPE_DISK && virt_dev->thin_provisioned) { resp_len = vdisk_tp_vpd(buf, cmd, virt_dev); } else { @@ -4501,7 +4501,7 @@ static int vdisk_err_recov_pg(unsigned char *p, int pcontrol, 1, 0, 0xff, 0xff}; memcpy(p, err_recov_pg, sizeof(err_recov_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(err_recov_pg) - 2); return sizeof(err_recov_pg); } @@ -4513,7 +4513,7 @@ static int vdisk_disconnect_pg(unsigned char *p, int pcontrol, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(p, disconnect_pg, sizeof(disconnect_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(disconnect_pg) - 2); return sizeof(disconnect_pg); } @@ -4540,7 +4540,7 @@ static int vdisk_rigid_geo_pg(unsigned char *p, int pcontrol, memcpy(&n, p + 2, sizeof(u32)); n = n | ((__force u32)cpu_to_be32(ncyl) >> 8); memcpy(p + 2, &n, sizeof(u32)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(geo_m_pg) - 2); return sizeof(geo_m_pg); } @@ -4555,7 +4555,7 @@ static int vdisk_format_pg(unsigned char *p, int pcontrol, memcpy(p, format_pg, sizeof(format_pg)); put_unaligned_be16(DEF_SECTORS, &p[10]); put_unaligned_be16(virt_dev->dev->block_size, &p[12]); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(format_pg) - 2); return sizeof(format_pg); } @@ -4674,7 +4674,7 @@ static int vdisk_iec_m_pg(unsigned char *p, int pcontrol, const unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0}; memcpy(p, iec_m_pg, sizeof(iec_m_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(iec_m_pg) - 2); return sizeof(iec_m_pg); } @@ -4711,7 +4711,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) pcontrol = (cmd->cdb[2] & 0xc0) >> 6; pcode = cmd->cdb[2] & 0x3f; subpcode = cmd->cdb[3]; - msense_6 = (MODE_SENSE == cmd->cdb[0]); + msense_6 = (cmd->cdb[0] == MODE_SENSE); dev_spec = cmd->tgt_dev->tgt_dev_rd_only ? WP : 0; if (type != TYPE_ROM) @@ -4721,7 +4721,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) if (unlikely(length <= 0)) goto out_free; - if (!vdev_saved_mode_pages_enabled && (0x3 == pcontrol)) { + if (!vdev_saved_mode_pages_enabled && (pcontrol == 0x3)) { TRACE_DBG("%s", "MODE SENSE: Saving values not supported"); scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_saving_params_unsup)); @@ -4738,7 +4738,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) offset = 8; } - if (0 != subpcode) { + if (subpcode != 0) { /* TODO: Control Extension page */ TRACE_DBG("%s", "MODE SENSE: Only subpage 0 is supported"); scst_set_invalid_field_in_cdb(cmd, 3, 0); @@ -5123,7 +5123,7 @@ static enum compl_status_e vdisk_exec_mode_select(struct vdisk_cmd_params *p) goto out; } - mselect_6 = (MODE_SELECT == cmd->cdb[0]); + mselect_6 = (cmd->cdb[0] == MODE_SELECT); type = cmd->dev->type; length = scst_get_buf_full_sense(cmd, &address); diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 67cbc797a..709cc1a20 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -2447,7 +2447,7 @@ static int __init init_scst(void) struct scsi_sense_hdr *shdr; struct scst_order_data *o; struct scst_cmd *c; - BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < sizeof(*shdr)); + BUILD_BUG_ON(sizeof(*shdr) > SCST_SENSE_BUFFERSIZE); BUILD_BUG_ON(sizeof(o->curr_sn) != sizeof(o->expected_sn)); BUILD_BUG_ON(sizeof(c->sn) != sizeof(o->expected_sn)); } diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index c2c2841f1..41713a72c 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -3153,7 +3153,7 @@ out_unlock: scst_put_buf_full(cmd, buffer); out_done: - if (SCST_EXEC_COMPLETED == res) { + if (res == SCST_EXEC_COMPLETED) { if (!aborted) cmd->completed = 1; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, From 999240bcd6ab3bce57e00b80d5e3b432be3be616 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:55:03 +0000 Subject: [PATCH 42/82] scst_local: Place constants at the right-hand-side in comparisons git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6609 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst_local/scst_local.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scst_local/scst_local.c b/scst_local/scst_local.c index 1815ddadf..7002ab698 100644 --- a/scst_local/scst_local.c +++ b/scst_local/scst_local.c @@ -897,8 +897,7 @@ static void scst_local_copy_sense(struct scsi_cmnd *cmnd, struct scst_cmd *scst_ TRACE_ENTRY(); - scst_cmnd_sense_len = (SCSI_SENSE_BUFFERSIZE < scst_cmnd_sense_len ? - SCSI_SENSE_BUFFERSIZE : scst_cmnd_sense_len); + scst_cmnd_sense_len = min(scst_cmnd_sense_len, SCSI_SENSE_BUFFERSIZE); memcpy(cmnd->sense_buffer, scst_cmd_get_sense_buffer(scst_cmnd), scst_cmnd_sense_len); @@ -1671,7 +1670,7 @@ static int scst_local_driver_probe(struct device *dev) TRACE_DBG("sess %p", sess); hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template, sizeof(*sess)); - if (NULL == hpnt) { + if (hpnt == NULL) { PRINT_ERROR("%s", "scsi_register() failed"); ret = -ENODEV; goto out; @@ -1828,7 +1827,7 @@ static int __scst_local_add_adapter(struct scst_local_tgt *tgt, /* It's read-mostly, so cache alignment isn't needed */ sess = kzalloc(sizeof(*sess), GFP_KERNEL); - if (NULL == sess) { + if (sess == NULL) { PRINT_ERROR("Unable to alloc scst_lcl_host (size %zu)", sizeof(*sess)); res = -ENOMEM; @@ -1947,7 +1946,7 @@ static int scst_local_add_target(const char *target_name, TRACE_ENTRY(); tgt = kzalloc(sizeof(*tgt), GFP_KERNEL); - if (NULL == tgt) { + if (tgt == NULL) { PRINT_ERROR("Unable to alloc tgt (size %zu)", sizeof(*tgt)); res = -ENOMEM; goto out; From 8ac8060d69f53a10ff5ad42d4829a6e06da40603 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:55:17 +0000 Subject: [PATCH 43/82] ib_srpt: Place constants at the right-hand-side in comparisons git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6610 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/src/ib_srpt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index e8b6eb6d3..900240e72 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -1640,7 +1640,7 @@ static int srpt_build_cmd_rsp(struct srpt_rdma_ch *ch, if (!scst_sense_valid(sense_data)) { sense_data_len = 0; } else { - BUILD_BUG_ON(MIN_MAX_RSP_SIZE <= sizeof(*srp_rsp)); + BUILD_BUG_ON(sizeof(*srp_rsp) >= MIN_MAX_RSP_SIZE); max_sense_len = ch->max_ti_iu_len - sizeof(*srp_rsp); if (sense_data_len > max_sense_len) { pr_warn("truncated sense data from %d to %d bytes\n", From b1cb5ebe798aa31298b4a27750234c8c9300ec3c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 18:55:30 +0000 Subject: [PATCH 44/82] qla2x00t: Place constants at the right-hand-side in comparisons git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6611 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x00-target/qla2x00t.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qla2x00t/qla2x00-target/qla2x00t.c b/qla2x00t/qla2x00-target/qla2x00t.c index a817faf0c..4735b13bb 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.c +++ b/qla2x00t/qla2x00-target/qla2x00t.c @@ -610,7 +610,7 @@ static void q24_atio_pkt_all_vps(scsi_qla_host_t *vha, atio7_entry_t *atio) case ATIO_TYPE7: { scsi_qla_host_t *host = q2t_find_host_by_d_id(vha, atio->fcp_hdr.d_id); - if (unlikely(NULL == host)) { + if (unlikely(host == NULL)) { /* * It might happen, because there is a small gap between * requesting the DPC thread to update loop and actual @@ -704,7 +704,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) scsi_qla_host_t *host = vha; if (IS_FWI2_CAPABLE(ha)) { nack24xx_entry_t *entry = (nack24xx_entry_t *)pkt; - if (0xFF != entry->vp_index) { + if (entry->vp_index != 0xFF) { host = q2t_find_host_by_vp_idx(vha, entry->vp_index); if (unlikely(!host)) { From 36a89b1916893654c8432eb9fd697a7fe5dcad81 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 19:41:04 +0000 Subject: [PATCH 45/82] scst: Fix processing of scst_event.h in nightly build git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6612 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scripts/generate-kernel-patch | 3 ++- scst/src/scst_tg.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index af2f3a825..ddb35ebf1 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -302,7 +302,8 @@ do done -scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h scst/include/backport.h" +scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h \ +scst/include/scst_event.h scst/include/backport.h" scst_04_main="scst/src/scst_main.c scst/src/scst_module.c scst/src/scst_priv.h" scst_05_targ="scst/src/scst_targ.c" scst_06_lib="scst/src/scst_lib.c" diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 7ecf67c3e..300900b6a 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -21,11 +21,12 @@ #include #ifdef INSIDE_KERNEL_TREE #include +#include #else #include "scst.h" +#include "scst_event.h" #endif #include "scst_priv.h" -#include "scst_event.h" #include "scst_pres.h" struct alua_state_and_name { From 819ada124e2cd91a28c39aced85f422553817b76 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 19:46:08 +0000 Subject: [PATCH 46/82] scst: Use tabs instead of spaces for indentation git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6613 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst_event.h | 2 +- scst/src/scst_tg.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h index c13f2b47f..9d7750e8b 100644 --- a/scst/include/scst_event.h +++ b/scst/include/scst_event.h @@ -137,7 +137,7 @@ struct scst_event_tm_fn_received_payload { #define SCST_EVENT_STPG_USER_INVOKE 5 struct scst_event_stpg_descr { - uint16_t group_id; + uint16_t group_id; /* * Better to keep below fields as small as possible to fit * in single page as many descriptors as possible. diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 300900b6a..86e61883f 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -967,9 +967,9 @@ static void __scst_tg_set_state(struct scst_target_group *tg, list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING); - if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && - tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) - gen_ua = false; + if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && + tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) + gen_ua = false; scst_tg_change_tgt_dev_state(tgt_dev, state, gen_ua); break; From e5bbd925fc2d3f3343b7b9bb37b7eab65f8f48a3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 6 Nov 2015 23:23:38 +0000 Subject: [PATCH 47/82] scst_local: Insert a blank line after declarations git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6614 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst_local/scst_local.c | 1 + 1 file changed, 1 insertion(+) diff --git a/scst_local/scst_local.c b/scst_local/scst_local.c index 7002ab698..f25a127e0 100644 --- a/scst_local/scst_local.c +++ b/scst_local/scst_local.c @@ -778,6 +778,7 @@ static ssize_t scst_local_sysfs_mgmt_cmd(char *buf) res = __scst_local_add_adapter(tgt, session_name, true); } else if (strcasecmp("del_session", command) == 0) { struct scst_local_sess *s, *sess = NULL; + list_for_each_entry(s, &tgt->sessions_list, sessions_list_entry) { if (strcmp(s->scst_sess->initiator_name, session_name) == 0) { From 55f1a2e3981b5123432b67e6178a6bf57bf024bb Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:00:27 +0000 Subject: [PATCH 48/82] isert-scst: Insert a blank line after declarations git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6615 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_buf.c | 1 + iscsi-scst/kernel/isert-scst/iser_datamover.c | 1 + iscsi-scst/kernel/isert-scst/iser_pdu.c | 1 + iscsi-scst/kernel/isert-scst/isert_login.c | 1 + 4 files changed, 4 insertions(+) diff --git a/iscsi-scst/kernel/isert-scst/iser_buf.c b/iscsi-scst/kernel/isert-scst/iser_buf.c index b84a68f68..9d6488760 100644 --- a/iscsi-scst/kernel/isert-scst/iser_buf.c +++ b/iscsi-scst/kernel/isert-scst/iser_buf.c @@ -294,6 +294,7 @@ out: void isert_wr_release(struct isert_wr *wr) { struct isert_buf *isert_buf = wr->buf; + if (isert_buf && isert_buf->is_alloced) { struct isert_device *isert_dev = wr->isert_dev; struct ib_device *ib_dev; diff --git a/iscsi-scst/kernel/isert-scst/iser_datamover.c b/iscsi-scst/kernel/isert-scst/iser_datamover.c index 19fc58640..4b0c287bc 100644 --- a/iscsi-scst/kernel/isert-scst/iser_datamover.c +++ b/iscsi-scst/kernel/isert-scst/iser_datamover.c @@ -107,6 +107,7 @@ int isert_portal_remove(void *portal_h) void isert_free_connection(struct iscsi_conn *iscsi_conn) { struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + isert_post_drain(isert_conn); isert_conn_free(isert_conn); } diff --git a/iscsi-scst/kernel/isert-scst/iser_pdu.c b/iscsi-scst/kernel/isert-scst/iser_pdu.c index 4a23d06c9..bf94209c6 100644 --- a/iscsi-scst/kernel/isert-scst/iser_pdu.c +++ b/iscsi-scst/kernel/isert-scst/iser_pdu.c @@ -82,6 +82,7 @@ static int isert_rx_pdu_init(struct isert_cmnd *isert_pdu, { struct iscsi_cmnd *iscsi_cmnd = &isert_pdu->iscsi; int err = isert_pdu_rx_buf_init(isert_pdu, isert_conn); + if (unlikely(err < 0)) return err; iscsi_cmnd->conn = &isert_conn->iscsi; diff --git a/iscsi-scst/kernel/isert-scst/isert_login.c b/iscsi-scst/kernel/isert-scst/isert_login.c index 1e15f89c8..517a6f257 100644 --- a/iscsi-scst/kernel/isert-scst/isert_login.c +++ b/iscsi-scst/kernel/isert-scst/isert_login.c @@ -567,6 +567,7 @@ static ssize_t isert_read(struct file *filp, char __user *buf, size_t count, if (will_read_block(dev)) { int ret; + if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_freezable(dev->waitqueue, From 7614eafd64254f41b61684022fd5fea86ad435a9 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:00:42 +0000 Subject: [PATCH 49/82] iscsi-scst: Insert a blank line after declarations git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6616 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/config.c | 1 + iscsi-scst/kernel/conn.c | 2 ++ iscsi-scst/kernel/digest.c | 1 + iscsi-scst/kernel/iscsi.c | 9 +++++++++ iscsi-scst/kernel/iscsi.h | 1 + iscsi-scst/kernel/nthread.c | 14 ++++++++++++++ iscsi-scst/kernel/session.c | 2 ++ iscsi-scst/kernel/target.c | 2 ++ 8 files changed, 32 insertions(+) diff --git a/iscsi-scst/kernel/config.c b/iscsi-scst/kernel/config.c index f774ebcd3..2e39e2523 100644 --- a/iscsi-scst/kernel/config.c +++ b/iscsi-scst/kernel/config.c @@ -1297,6 +1297,7 @@ unsigned long iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(struct iscsi_cmnd *cmnd) flag = TRACE_MGMT_DEBUG; else { int status = scst_cmd_get_status(cmnd->scst_cmd); + if ((status == SAM_STAT_TASK_SET_FULL) || (status == SAM_STAT_BUSY)) flag = TRACE_FLOW_CONTROL; diff --git a/iscsi-scst/kernel/conn.c b/iscsi-scst/kernel/conn.c index 057747296..bbced03c2 100644 --- a/iscsi-scst/kernel/conn.c +++ b/iscsi-scst/kernel/conn.c @@ -525,6 +525,7 @@ static void conn_rsp_timer_fn(unsigned long arg) if (!list_empty(&conn->write_timeout_list)) { unsigned long timeout_time; + cmnd = list_first_entry(&conn->write_timeout_list, struct iscsi_cmnd, write_timeout_list_entry); @@ -834,6 +835,7 @@ void conn_free(struct iscsi_conn *conn) /* Just in case if new conn gets freed before the old one */ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags)) { struct iscsi_conn *c; + TRACE_MGMT_DBG("Freeing being reinstated conn %p", conn); list_for_each_entry(c, &session->conn_list, conn_list_entry) { diff --git a/iscsi-scst/kernel/digest.c b/iscsi-scst/kernel/digest.c index 5d682aab5..cbfff039e 100644 --- a/iscsi-scst/kernel/digest.c +++ b/iscsi-scst/kernel/digest.c @@ -76,6 +76,7 @@ static __be32 evaluate_crc32_from_sg(struct scatterlist *sg, int nbytes, while (nbytes > 0) { int d = min(nbytes, (int)(sg->length)); + crc = crc32c(crc, sg_virt(sg), d); nbytes -= d; sg++; diff --git a/iscsi-scst/kernel/iscsi.c b/iscsi-scst/kernel/iscsi.c index cca896e3f..b00deb720 100644 --- a/iscsi-scst/kernel/iscsi.c +++ b/iscsi-scst/kernel/iscsi.c @@ -138,6 +138,7 @@ static inline int cmnd_read_size(struct iscsi_cmnd *cmnd) if (ahdr != NULL) { uint8_t *p = (uint8_t *)ahdr; unsigned int size = 0; + do { int s; @@ -469,6 +470,7 @@ void cmnd_done(struct iscsi_cmnd *cmnd) { /* It can be for some aborted commands */ struct scst_cmd *scst_cmd = cmnd->scst_cmd; + TRACE_DBG("cmd %p AFTER_PREPROC", cmnd); cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED; cmnd->scst_cmd = NULL; @@ -1087,6 +1089,7 @@ static void iscsi_tcp_send_data_rsp(struct iscsi_cmnd *req, u8 *sense, send_data_rsp(req, status, is_send_status); } else { struct iscsi_cmnd *rsp; + send_data_rsp(req, 0, 0); if (is_send_status) { rsp = create_status_rsp(req, status, sense, @@ -1221,6 +1224,7 @@ static int create_reject_rsp(struct iscsi_cmnd *req, int reason, bool get_data) if (req->scst_cmd == NULL) { /* BUSY status must be already set */ struct iscsi_scsi_rsp_hdr *rsp_hdr1; + rsp_hdr1 = (struct iscsi_scsi_rsp_hdr *)&req->main_rsp->pdu.bhs; sBUG_ON(rsp_hdr1->cmd_status == 0); /* @@ -1523,6 +1527,7 @@ int iscsi_preliminary_complete(struct iscsi_cmnd *req, #ifdef CONFIG_SCST_DEBUG { struct iscsi_hdr *req_hdr = &req->pdu.bhs; + TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(orig_req), "Prelim completed req %p, orig_req %p (FINAL %x, " "outstanding_r2t %d)", req, orig_req, @@ -2047,6 +2052,7 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req) if ((req_hdr->flags & ISCSI_CMD_READ) && (req_hdr->flags & ISCSI_CMD_WRITE)) { int sz = cmnd_read_size(req); + if (unlikely(sz < 0)) { PRINT_ERROR("%s", "BIDI data transfer, but initiator " "not supplied Bidirectional Read Expected Data " @@ -2109,6 +2115,7 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req) if (ahdr != NULL) { uint8_t *p = (uint8_t *)ahdr; unsigned int size = 0; + do { int s; @@ -2509,6 +2516,7 @@ static void iscsi_cmnd_abort_fn(struct work_struct *work) */ list_for_each_entry(conn, &session->conn_list, conn_list_entry) { struct iscsi_cmnd *c; + spin_lock_bh(&conn->cmd_list_lock); list_for_each_entry(c, &conn->cmd_list, cmd_list_entry) { if (c == cmnd) { @@ -3566,6 +3574,7 @@ static int iscsi_xmit_response(struct scst_cmd *scst_cmd) is_send_status); } else if (is_send_status) { struct iscsi_cmnd *rsp; + rsp = create_status_rsp(req, status, sense, sense_len); iscsi_cmnd_init_write(rsp, 0); } diff --git a/iscsi-scst/kernel/iscsi.h b/iscsi-scst/kernel/iscsi.h index 7a4ca36ab..986f5132c 100644 --- a/iscsi-scst/kernel/iscsi.h +++ b/iscsi-scst/kernel/iscsi.h @@ -693,6 +693,7 @@ static inline bool cmnd_get_check(struct iscsi_cmnd *cmnd) { int r = atomic_inc_return(&cmnd->ref_cnt); int res; + if (unlikely(r == 1)) { TRACE_DBG("cmnd %p is being destroyed", cmnd); atomic_dec(&cmnd->ref_cnt); diff --git a/iscsi-scst/kernel/nthread.c b/iscsi-scst/kernel/nthread.c index 00b9d4165..4adfc2ff5 100644 --- a/iscsi-scst/kernel/nthread.c +++ b/iscsi-scst/kernel/nthread.c @@ -100,6 +100,7 @@ again: for (i = 0; i < cmnd->sg_cnt; i++) { struct page *page = sg_page(&cmnd->sg[i]); + TRACE_CONN_CLOSE_DBG("page %p, net_priv %p, " "_count %d", page, page->net_priv, atomic_read(&page->_count)); @@ -282,8 +283,10 @@ static void trace_conn_close(struct iscsi_conn *conn) cmnd->sg); if (cmnd->sg != NULL) { int i; + for (i = 0; i < cmnd->sg_cnt; i++) { struct page *page = sg_page(&cmnd->sg[i]); + TRACE_CONN_CLOSE_DBG("page %p, " "net_priv %p, _count %d", page, page->net_priv, @@ -301,6 +304,7 @@ static void trace_conn_close(struct iscsi_conn *conn) atomic_read(&rsp->net_ref_cnt), rsp->sg); if (rsp->sg != cmnd->sg && rsp->sg) { int i; + for (i = 0; i < rsp->sg_cnt; i++) { TRACE_CONN_CLOSE_DBG(" page %p, " "net_priv %p, _count %d", @@ -459,6 +463,7 @@ static void close_conn(struct iscsi_conn *conn) spin_lock(&session->sn_lock); if (session->tm_rsp && session->tm_rsp->conn == conn) { struct iscsi_cmnd *tm_rsp = session->tm_rsp; + iscsi_drop_delayed_tm_rsp(tm_rsp); spin_unlock(&session->sn_lock); mutex_unlock(&target->target_mutex); @@ -795,6 +800,7 @@ static int iscsi_rx_check_ddigest(struct iscsi_conn *conn) res = digest_rx_data(cmnd); if (unlikely(res != 0)) { struct iscsi_cmnd *orig_req; + if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_DATA_OUT) orig_req = cmnd->cmd_req; else @@ -914,6 +920,7 @@ static int process_read_io(struct iscsi_conn *conn, int *closed) res = do_recv(conn); if (res == 0) { int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize; + if (psz != 0) { TRACE_DBG("padding %d bytes", psz); iscsi_conn_init_read(conn, @@ -1154,8 +1161,10 @@ static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) if (atomic_dec_and_test(&cmd->net_ref_cnt)) { int i, sg_cnt = cmd->sg_cnt; + for (i = 0; i < sg_cnt; i++) { struct page *page = sg_page(&cmd->sg[i]); + TRACE_NET_PAGE("Clearing page %p", page); if (page->net_priv == cmd) page->net_priv = NULL; @@ -1222,9 +1231,11 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) unsigned long req_tt = iscsi_get_timeout_time(req); struct iscsi_cmnd *r; bool inserted = false; + list_for_each_entry(r, &conn->write_timeout_list, write_timeout_list_entry) { unsigned long tt = iscsi_get_timeout_time(r); + if (time_after(tt, req_tt)) { TRACE_DBG("Add NOP IN req %p (tt %ld) before " "req %p (tt %ld)", req, req_tt, r, tt); @@ -1256,6 +1267,7 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) if (!timer_pending(&conn->rsp_timer)) { unsigned long timeout_time; + if (unlikely(conn->conn_tm_active || test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags))) { @@ -1276,6 +1288,7 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) &req->prelim_compl_flags))) { unsigned long timeout_time = jiffies + ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME; + set_conn_tm_active = true; if (time_after(conn->rsp_timer.expires, timeout_time)) { TRACE_MGMT_DBG("Mod timer on %ld (conn %p)", @@ -1454,6 +1467,7 @@ retry: #if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION) { static DEFINE_SPINLOCK(net_priv_lock); + spin_lock(&net_priv_lock); if (unlikely(page->net_priv != NULL)) { if (page->net_priv != ref_cmd) { diff --git a/iscsi-scst/kernel/session.c b/iscsi-scst/kernel/session.c index 52dd682be..5b28d0fe4 100644 --- a/iscsi-scst/kernel/session.c +++ b/iscsi-scst/kernel/session.c @@ -199,6 +199,7 @@ int __add_session(struct iscsi_target *target, list_for_each_entry_reverse(sess, &target->session_list, session_list_entry) { union iscsi_sid s = *(union iscsi_sid *)&sess->sid; + s.id.tsih = 0; if ((sid.id64 == s.id64) && (strcmp(info->initiator_name, sess->initiator_name) == 0)) { @@ -332,6 +333,7 @@ int session_free(struct iscsi_session *session, bool del) if (session->sess_reinstating) { struct iscsi_session *s; + TRACE_MGMT_DBG("Freeing being reinstated sess %p", session); list_for_each_entry(s, &session->target->session_list, session_list_entry) { diff --git a/iscsi-scst/kernel/target.c b/iscsi-scst/kernel/target.c index 0677b7428..7a3e0a546 100644 --- a/iscsi-scst/kernel/target.c +++ b/iscsi-scst/kernel/target.c @@ -298,6 +298,7 @@ void target_del_session(struct iscsi_target *target, if (!list_empty(&session->conn_list)) { struct iscsi_conn *conn, *tc; + list_for_each_entry_safe(conn, tc, &session->conn_list, conn_list_entry) { TRACE_MGMT_DBG("Del session: closing conn %p", conn); @@ -616,6 +617,7 @@ ssize_t iscsi_sysfs_del_target(const char *target_name) /* We don't want to have tgt visible after the mutex unlock */ { struct iscsi_target *tgt; + mutex_lock(&target_mgmt_mutex); tgt = target_lookup_by_name(target_name); if (tgt == NULL) { From 4862b1d3f5984f9bf8729adbf9cf4e89e6bb087d Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:01:05 +0000 Subject: [PATCH 50/82] qla2x00t: Insert a blank line after declarations git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6617 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x00-target/qla2x00t.c | 62 ++++++++++++++++++++++++++++++ qla2x00t/qla2x_tgt.h | 2 + 2 files changed, 64 insertions(+) diff --git a/qla2x00t/qla2x00-target/qla2x00t.c b/qla2x00t/qla2x00-target/qla2x00t.c index 4735b13bb..07aff2092 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.c +++ b/qla2x00t/qla2x00-target/qla2x00t.c @@ -308,6 +308,7 @@ static inline struct q2t_sess *q2t_find_sess_by_loop_id(struct q2t_tgt *tgt, uint16_t loop_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if (loop_id == sess->loop_id) { EXTRACHECKS_BUG_ON(sess->deleted); @@ -322,6 +323,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id_include_deleted( struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[2]) && (sess->s_id.b.area == s_id[1]) && @@ -346,6 +348,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id(struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[2]) && (sess->s_id.b.area == s_id[1]) && @@ -362,6 +365,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id_le(struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[0]) && (sess->s_id.b.area == s_id[1]) && @@ -378,6 +382,7 @@ static inline struct q2t_sess *q2t_find_sess_by_port_name(struct q2t_tgt *tgt, const uint8_t *port_name) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->port_name[0] == port_name[0]) && (sess->port_name[1] == port_name[1]) && @@ -399,6 +404,7 @@ static inline struct q2t_sess *q2t_find_sess_by_port_name_include_deleted( struct q2t_tgt *tgt, const uint8_t *port_name) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->port_name[0] == port_name[0]) && (sess->port_name[1] == port_name[1]) && @@ -446,6 +452,7 @@ static inline int q2t_issue_marker(scsi_qla_host_t *vha, int vha_locked) /* Send marker if required */ if (unlikely(vha->marker_needed != 0)) { int rc = qla2x00_issue_marker(vha, vha_locked); + if (rc != QLA_SUCCESS) { PRINT_ERROR("qla2x00t(%ld): issue_marker() " "failed", vha->host_no); @@ -468,6 +475,7 @@ static inline scsi_qla_host_t *q2t_find_host_by_d_id(scsi_qla_host_t *vha, if (IS_FWI2_CAPABLE(ha)) { uint8_t vp_idx; + sBUG_ON(ha->tgt_vp_map == NULL); vp_idx = ha->tgt_vp_map[d_id[2]].idx; if (likely(test_bit(vp_idx, ha->vp_idx_map))) @@ -547,6 +555,7 @@ static void q24_try_to_dequeue_unknown_atios(struct qla_hw_data *ha) list_for_each_entry_safe(u, t, &ha->unknown_atio_list, unknown_atio_list_entry) { scsi_qla_host_t *host, *vha = u->vha; + sBUG_ON(vha->hw != ha); host = q2t_find_host_by_d_id(vha, u->atio7.fcp_hdr.d_id); if (host != NULL) { @@ -610,6 +619,7 @@ static void q24_atio_pkt_all_vps(scsi_qla_host_t *vha, atio7_entry_t *atio) case ATIO_TYPE7: { scsi_qla_host_t *host = q2t_find_host_by_d_id(vha, atio->fcp_hdr.d_id); + if (unlikely(host == NULL)) { /* * It might happen, because there is a small gap between @@ -628,8 +638,10 @@ static void q24_atio_pkt_all_vps(scsi_qla_host_t *vha, atio7_entry_t *atio) case IMMED_NOTIFY_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *entry = (notify24xx_entry_t *)atio; + if ((entry->vp_index != 0xFF) && (entry->nport_handle != 0xFFFF)) { host = q2t_find_host_by_vp_idx(vha, @@ -671,6 +683,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt (CTIO_TYPE7) " "received, with unknown vp_index %d", @@ -684,8 +697,10 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) case IMMED_NOTIFY_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *entry = (notify24xx_entry_t *)pkt; + host = q2t_find_host_by_vp_idx(vha, entry->vp_index); if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " @@ -702,8 +717,10 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) case NOTIFY_ACK_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { nack24xx_entry_t *entry = (nack24xx_entry_t *)pkt; + if (entry->vp_index != 0xFF) { host = q2t_find_host_by_vp_idx(vha, entry->vp_index); @@ -726,6 +743,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) abts24_recv_entry_t *entry = (abts24_recv_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " "(ABTS_RECV_24XX) received, with unknown " @@ -741,6 +759,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) abts24_resp_entry_t *entry = (abts24_resp_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " "(ABTS_RECV_24XX) received, with unknown " @@ -882,6 +901,7 @@ static int q2t_reset(scsi_qla_host_t *vha, void *iocb, int mcmd) if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *n = (notify24xx_entry_t *)iocb; + if ((le16_to_cpu(n->status) == IMM_NTFY_ELS) && ((n->status_subcode == ELS_TPRLO) || (n->status_subcode == ELS_LOGO))) { @@ -1154,6 +1174,7 @@ retry: res = -1; for (i = 0; i < entries; i++) { struct gid_list_info *gid = (struct gid_list_info *)id_iter; + if ((gid->al_pa == s_id[2]) && (gid->area == s_id[1]) && (gid->domain == s_id[0])) { @@ -2053,6 +2074,7 @@ static int __q24_handle_abts(scsi_qla_host_t *vha, abts24_recv_entry_t *abts, */ while (!list_empty(&ha->unknown_atio_list)) { struct q2t_unknown_atio *u; + u = list_first_entry(&ha->unknown_atio_list, struct q2t_unknown_atio, unknown_atio_list_entry); TRACE_MGMT_DBG("qla2x00t(%ld): Clearing unknown " @@ -3304,6 +3326,7 @@ static int __q2t_rdy_to_xfer(struct q2t_cmd *cmd) if (IS_FWI2_CAPABLE(ha)) { ctio7_status0_entry_t *pkt; + res = q24_build_ctio_pkt(&prm); if (unlikely(res != SCST_TGT_RES_SUCCESS)) goto out_unlock_free_unmap; @@ -3314,6 +3337,7 @@ static int __q2t_rdy_to_xfer(struct q2t_cmd *cmd) p = pkt; } else { ctio_common_entry_t *pkt; + q2x_build_ctio_pkt(&prm); pkt = (ctio_common_entry_t *)prm.pkt; pkt->flags = cpu_to_le16(OF_FAST_POST | OF_DATA_OUT); @@ -3573,6 +3597,7 @@ static int q2t_prepare_srr_ctio(scsi_qla_host_t *vha, struct q2t_cmd *cmd, sc, sc->srr_id); if (tgt->imm_srr_id == tgt->ctio_srr_id) { int found = 0; + list_for_each_entry(imm, &tgt->srr_imm_list, srr_list_entry) { if (imm->srr_id == sc->srr_id) { @@ -3600,6 +3625,7 @@ static int q2t_prepare_srr_ctio(scsi_qla_host_t *vha, struct q2t_cmd *cmd, spin_unlock(&tgt->srr_lock); } else { struct srr_imm *ti; + PRINT_ERROR("qla2x00t(%ld): Unable to allocate SRR CTIO entry", vha->host_no); spin_lock(&tgt->srr_lock); @@ -3643,6 +3669,7 @@ static bool q2t_term_ctio_exchange(scsi_qla_host_t *vha, void *ctio, } if (ctio != NULL) { ctio7_fw_entry_t *c = (ctio7_fw_entry_t *)ctio; + term = !(c->flags & cpu_to_le16(OF_TERM_EXCH)); } else term = true; @@ -3654,6 +3681,7 @@ static bool q2t_term_ctio_exchange(scsi_qla_host_t *vha, void *ctio, #if 0 /* Seems, it isn't needed. If enable it, add support for NULL cmd! */ if (ctio != NULL) { ctio_common_entry_t *c = (ctio_common_entry_t *)ctio; + term = !(c->flags & cpu_to_le16(CTIO7_FLAGS_TERMINATE)); } else term = true; @@ -3673,6 +3701,7 @@ static inline struct q2t_cmd *q2t_get_cmd(scsi_qla_host_t *vha, uint32_t handle) handle--; if (vha->cmds[handle] != NULL) { struct q2t_cmd *cmd = vha->cmds[handle]; + vha->cmds[handle] = NULL; return cmd; } else @@ -3728,6 +3757,7 @@ static struct q2t_cmd *q2t_ctio_to_cmd(scsi_qla_host_t *vha, uint32_t handle, goto out; } else { ctio_common_entry_t *c = (ctio_common_entry_t *)ctio; + loop_id = GET_TARGET_ID(ha, c); tag = c->rx_id; } @@ -4103,6 +4133,7 @@ static int q2t_send_cmd_to_scst(scsi_qla_host_t *vha, atio_t *atio) if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)atio; + sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id); if (unlikely(sess == NULL)) { TRACE_MGMT_DBG("qla2x00t(%ld): Unable to find " @@ -4166,6 +4197,7 @@ static int q2t_issue_task_mgmt(struct q2t_sess *sess, uint8_t *lun, */ while (!list_empty(&ha->unknown_atio_list)) { struct q2t_unknown_atio *u; + u = list_first_entry(&ha->unknown_atio_list, struct q2t_unknown_atio, unknown_atio_list_entry); TRACE_MGMT_DBG("qla2x00t(%ld): Clearing unknown " @@ -4300,12 +4332,14 @@ static int q2t_handle_task_mgmt(scsi_qla_host_t *vha, void *iocb) tgt = vha->tgt; if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)iocb; + lun = (uint8_t *)&a->fcp_cmnd.lun; lun_size = sizeof(a->fcp_cmnd.lun); fn = a->fcp_cmnd.task_mgmt_flags; sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id); } else { notify_entry_t *n = (notify_entry_t *)iocb; + /* make it be in network byte order */ lun_data = swab16(le16_to_cpu(n->lun)); lun = (uint8_t *)&lun_data; @@ -4468,6 +4502,7 @@ static int q24_handle_els(scsi_qla_host_t *vha, notify24xx_entry_t *iocb) case ELS_ADISC: { struct q2t_tgt *tgt = vha->tgt; + if (tgt->link_reinit_iocb_pending) { q24_send_notify_ack(vha, &tgt->link_reinit_iocb, 0, 0, 0); tgt->link_reinit_iocb_pending = 0; @@ -4506,6 +4541,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) l += cmd->sg[i].length; if (l > offset) { int sg_offs = l - cmd->sg[i].length; + first_sg = i; if (cmd->sg[i].offset == 0) { first_page_offs = offset % PAGE_SIZE; @@ -4555,6 +4591,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) cur_src = first_sg; if (first_page_offs != 0) { int fpgs; + sg_set_page(&sg[cur_dst], &sg_page(&cmd->sg[cur_src])[first_page], PAGE_SIZE - first_page_offs, first_page_offs); bufflen += sg[cur_dst].length; @@ -4657,6 +4694,7 @@ static void q24_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, (scst_cmd_get_data_direction(&cmd->scst_cmd) & SCST_DATA_READ)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4681,6 +4719,7 @@ static void q24_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, (scst_cmd_get_data_direction(&cmd->scst_cmd) & SCST_DATA_WRITE)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4751,6 +4790,7 @@ static void q2x_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, if (q2t_has_data(cmd)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4773,6 +4813,7 @@ static void q2x_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, if (q2t_has_data(cmd)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4949,6 +4990,7 @@ static void q2t_prepare_srr_imm(scsi_qla_host_t *vha, void *iocb) imm->srr_id, iocb24->srr_ui); if (tgt->imm_srr_id == tgt->ctio_srr_id) { int found = 0; + list_for_each_entry(sctio, &tgt->srr_ctio_list, srr_list_entry) { if (sctio->srr_id == imm->srr_id) { @@ -5064,6 +5106,7 @@ static void q2t_handle_imm_notify(scsi_qla_host_t *vha, void *iocb) case IMM_NTFY_LIP_LINK_REINIT: { struct q2t_tgt *tgt = vha->tgt; + TRACE(TRACE_MGMT, "qla2x00t(%ld): LINK REINIT (loop %#x, " "subcode %x)", vha->host_no, le16_to_cpu(iocb24->nport_handle), @@ -5359,6 +5402,7 @@ static void q24_atio_pkt(scsi_qla_host_t *vha, atio7_entry_t *atio) case IMMED_NOTIFY_TYPE: { notify_entry_t *pkt = (notify_entry_t *)atio; + if (unlikely(pkt->entry_status != 0)) { PRINT_ERROR("qla2x00t(%ld): Received ATIO packet %x " "with error status %x", vha->host_no, @@ -5426,6 +5470,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CTIO_TYPE7: { ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt; + TRACE_DBG("CTIO_TYPE7: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO7 packet data", entry, @@ -5440,6 +5485,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) { atio_entry_t *atio; int rc; + atio = (atio_entry_t *)pkt; TRACE_DBG("ACCEPT_TGT_IO instance %ld status %04x " "lun %04x read/write %d data_length %04x " @@ -5481,6 +5527,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CONTINUE_TGT_IO_TYPE: { ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt; + TRACE_DBG("CONTINUE_TGT_IO: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO packet data", entry, REQUEST_ENTRY_SIZE); @@ -5493,6 +5540,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CTIO_A64_TYPE: { ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt; + TRACE_DBG("CTIO_A64: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO_A64 packet data", entry, REQUEST_ENTRY_SIZE); @@ -5510,6 +5558,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case NOTIFY_ACK_TYPE: if (tgt->notify_ack_expected > 0) { nack_entry_t *entry = (nack_entry_t *)pkt; + TRACE_DBG("NOTIFY_ACK seq %08x status %x", le16_to_cpu(entry->seq_id), le16_to_cpu(entry->status)); @@ -5538,6 +5587,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) if (tgt->abts_resp_expected > 0) { abts24_resp_fw_entry_t *entry = (abts24_resp_fw_entry_t *)pkt; + TRACE_DBG("ABTS_RESP_24XX: compl_status %x", entry->compl_status); TRACE_BUFF_FLAG(TRACE_BUFF, "Incoming ABTS_RESP " @@ -5574,6 +5624,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case MODIFY_LUN_TYPE: if (tgt->modify_lun_expected > 0) { modify_lun_entry_t *entry = (modify_lun_entry_t *)pkt; + TRACE_DBG("MODIFY_LUN %x, imm %c%d, cmd %c%d", entry->status, (entry->operators & MODIFY_LUN_IMM_ADD) ? '+' @@ -5599,6 +5650,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case ENABLE_LUN_TYPE: { elun_entry_t *entry = (elun_entry_t *)pkt; + TRACE_DBG("ENABLE_LUN %x imm %u cmd %u ", entry->status, entry->immed_notify_count, entry->command_count); @@ -5835,8 +5887,10 @@ static void q2t_exec_sess_work(struct q2t_tgt *tgt, case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)&cmd->atio; + s_id = a->fcp_hdr.s_id; } else loop_id = GET_TARGET_ID(ha, (atio_entry_t *)&cmd->atio); @@ -5896,6 +5950,7 @@ send: case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + if (tgt->tm_to_unknown) { /* * Cmd might be already aborted behind us, so be safe @@ -5923,12 +5978,14 @@ send: if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = &prm->tm_iocb2; + iocb = a; lun = (uint8_t *)&a->fcp_cmnd.lun; lun_size = sizeof(a->fcp_cmnd.lun); fn = a->fcp_cmnd.task_mgmt_flags; } else { notify_entry_t *n = &prm->tm_iocb; + iocb = n; /* make it be in network byte order */ lun_data = swab16(le16_to_cpu(n->lun)); @@ -5962,6 +6019,7 @@ out_term: case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + TRACE_MGMT_DBG("Terminating work cmd %p", cmd); /* * cmd has not sent to SCST yet, so pass NULL as the second @@ -6195,8 +6253,10 @@ static int q2t_add_target(scsi_qla_host_t *vha) scst_get_tgt_name(tgt->scst_tgt)); if (vha->vp_idx == 0) { int i = 0; + while (1) { const struct attribute *a = q2t_hw_tgt_attrs[i]; + if (a == NULL) break; rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt), a); @@ -6209,8 +6269,10 @@ static int q2t_add_target(scsi_qla_host_t *vha) } } else { int i = 0; + while (1) { const struct attribute *a = q2t_npiv_tgt_attrs[i]; + if (a == NULL) break; rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt), a); diff --git a/qla2x00t/qla2x_tgt.h b/qla2x00t/qla2x_tgt.h index 4ce125311..99956b747 100644 --- a/qla2x00t/qla2x_tgt.h +++ b/qla2x00t/qla2x_tgt.h @@ -124,6 +124,7 @@ qla2x00_send_enable_lun(scsi_qla_host_t *vha, bool enable) if (!IS_FWI2_CAPABLE(ha)) { unsigned long flags; + spin_lock_irqsave(&ha->hardware_lock, flags); __qla2x00_send_enable_lun(vha, enable); spin_unlock_irqrestore(&ha->hardware_lock, flags); @@ -159,6 +160,7 @@ static inline bool qla_firmware_active(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; struct scsi_qla_host *base_vha = pci_get_drvdata(ha->pdev); + return qla_tgt_mode_enabled(base_vha) || qla_ini_mode_enabled(base_vha); } From 566827010dfc26edf058889909620cfbacc862db Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:01:57 +0000 Subject: [PATCH 51/82] scst: Insert a blank line after declarations git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6618 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_cdrom.c | 1 + scst/src/dev_handlers/scst_disk.c | 2 ++ scst/src/dev_handlers/scst_modisk.c | 1 + scst/src/dev_handlers/scst_tape.c | 3 ++ scst/src/dev_handlers/scst_user.c | 12 +++++++ scst/src/dev_handlers/scst_vdisk.c | 19 ++++++++++ scst/src/scst_lib.c | 55 ++++++++++++++++++++++++++++- scst/src/scst_main.c | 6 ++++ scst/src/scst_mem.c | 5 +++ scst/src/scst_pres.c | 6 ++++ scst/src/scst_priv.h | 1 + scst/src/scst_sysfs.c | 18 ++++++++-- scst/src/scst_targ.c | 38 ++++++++++++++++++++ scst/src/scst_tg.c | 2 ++ 14 files changed, 166 insertions(+), 3 deletions(-) diff --git a/scst/src/dev_handlers/scst_cdrom.c b/scst/src/dev_handlers/scst_cdrom.c index a08999b4f..f1860f8d7 100644 --- a/scst/src/dev_handlers/scst_cdrom.c +++ b/scst/src/dev_handlers/scst_cdrom.c @@ -123,6 +123,7 @@ static int cdrom_attach(struct scst_device *dev) if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = CDROM_DEF_BLOCK_SHIFT; else diff --git a/scst/src/dev_handlers/scst_disk.c b/scst/src/dev_handlers/scst_disk.c index a56cc204c..d694633fd 100644 --- a/scst/src/dev_handlers/scst_disk.c +++ b/scst/src/dev_handlers/scst_disk.c @@ -217,6 +217,7 @@ static int disk_attach(struct scst_device *dev) } if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = DISK_DEF_BLOCK_SHIFT; else @@ -474,6 +475,7 @@ split: if (unlikely(sg_is_chain(&sg[j]))) { bool reset_start_sg = (start_sg == &sg[j]); + sg = sg_chain_ptr(&sg[j]); j = 0; if (reset_start_sg) diff --git a/scst/src/dev_handlers/scst_modisk.c b/scst/src/dev_handlers/scst_modisk.c index 850f8efe2..80913010c 100644 --- a/scst/src/dev_handlers/scst_modisk.c +++ b/scst/src/dev_handlers/scst_modisk.c @@ -222,6 +222,7 @@ static int modisk_attach(struct scst_device *dev) if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = MODISK_DEF_BLOCK_SHIFT; else diff --git a/scst/src/dev_handlers/scst_tape.c b/scst/src/dev_handlers/scst_tape.c index 4daa84d84..1b56b01ed 100644 --- a/scst/src/dev_handlers/scst_tape.c +++ b/scst/src/dev_handlers/scst_tape.c @@ -206,6 +206,7 @@ static int tape_attach(struct scst_device *dev) if (rc == 0) { int medium_type, mode, speed, density; + if (buffer[3] == 8) dev->block_size = get_unaligned_be24(&buffer[9]); else @@ -299,6 +300,7 @@ static int tape_done(struct scst_cmd *cmd) (cmd->sense[2] & 0xe0)) { /* EOF, EOM, or ILI */ unsigned int TransferLength, Residue = 0; + if ((cmd->sense[2] & 0x0f) == BLANK_CHECK) /* No need for EOM in this case */ cmd->sense[2] &= 0xcf; @@ -312,6 +314,7 @@ static int tape_done(struct scst_cmd *cmd) cmd->cdb[1], TransferLength, Residue); if (TransferLength > Residue) { int resp_data_len = TransferLength - Residue; + if (cmd->cdb[1] & 1) { /* * No need for locks here, since diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index 45288befd..3b90d38d1 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -261,6 +261,7 @@ static inline bool ucmd_get_check(struct scst_user_cmd *ucmd) { int r = atomic_inc_return(&ucmd->ucmd_ref); int res; + if (unlikely(r == 1)) { TRACE_DBG("ucmd %p is being destroyed", ucmd); atomic_dec(&ucmd->ucmd_ref); @@ -551,6 +552,7 @@ static int dev_user_alloc_sg(struct scst_user_cmd *ucmd, int cached_buff) } else { /* Make out_sg->offset 0 */ int len = cmd->bufflen + ucmd->first_page_offset; + out_sg_pages = (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0); orig_bufflen = (out_sg_pages << PAGE_SHIFT) + cmd->out_bufflen; pool = dev->pool; @@ -612,6 +614,7 @@ static int dev_user_alloc_sg(struct scst_user_cmd *ucmd, int cached_buff) if (unlikely(cmd->sg_cnt > cmd->tgt_dev->max_sg_cnt)) { static int ll; + if ((ll < 10) || TRACING_MINOR()) { PRINT_INFO("Unable to complete command due to " "SG IO count limitation (requested %d, " @@ -1090,6 +1093,7 @@ static void dev_user_set_block_size(struct scst_cmd *cmd, int block_size) dev->block_size = block_size; else { struct scst_user_dev *udev = cmd->dev->dh_priv; + dev->block_size = udev->def_block_size; } dev->block_shift = -1; /* not used */ @@ -1311,6 +1315,7 @@ static int dev_user_process_reply_alloc(struct scst_user_cmd *ucmd, if (likely(reply->alloc_reply.pbuf != 0)) { int pages; + if (ucmd->buff_cached) { if (unlikely((reply->alloc_reply.pbuf & ~PAGE_MASK) != 0)) { PRINT_ERROR("Supplied pbuf %llx isn't " @@ -1775,6 +1780,7 @@ static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, if (ereply->resp_data_len != 0) { if (ucmd->ubuff == 0) { int pages, rc; + if (unlikely(ereply->pbuf == 0)) goto out_busy; if (ucmd->buff_cached) { @@ -2324,6 +2330,7 @@ static int dev_user_reply_get_cmd(struct file *file, void __user *arg) if (ureply != 0) { unsigned long u = (unsigned long)ureply; + rc = copy_from_user(&reply, (void __user *)u, sizeof(reply)); if (unlikely(rc != 0)) { PRINT_ERROR("Failed to copy %d user's bytes", rc); @@ -2476,6 +2483,7 @@ static long dev_user_ioctl(struct file *file, unsigned int cmd, case SCST_USER_REGISTER_DEVICE: { struct scst_user_dev_desc *dev_desc; + TRACE_DBG("%s", "REGISTER_DEVICE"); dev_desc = kmalloc(sizeof(*dev_desc), GFP_KERNEL); if (dev_desc == NULL) { @@ -2511,6 +2519,7 @@ static long dev_user_ioctl(struct file *file, unsigned int cmd, case SCST_USER_SET_OPTIONS: { struct scst_user_opt opt; + TRACE_DBG("%s", "SET_OPTIONS"); rc = copy_from_user(&opt, (void __user *)arg, sizeof(opt)); if (rc != 0) { @@ -3868,6 +3877,7 @@ static int dev_user_exit_dev(struct scst_user_dev *dev) static int __dev_user_release(void *arg) { struct scst_user_dev *dev = arg; + dev_user_exit_dev(dev); kmem_cache_free(user_dev_cachep, dev); return 0; @@ -3938,6 +3948,7 @@ static int dev_user_process_cleanup(struct scst_user_dev *dev) #ifdef CONFIG_SCST_EXTRACHECKS { int i; + for (i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++) { struct list_head *head = &dev->ucmd_hash[i]; struct scst_user_cmd *ucmd2, *tmp; @@ -3979,6 +3990,7 @@ static ssize_t dev_user_sysfs_commands_show(struct kobject *kobj, for (i = 0; i < (int)ARRAY_SIZE(udev->ucmd_hash); i++) { struct list_head *head = &udev->ucmd_hash[i]; struct scst_user_cmd *ucmd; + list_for_each_entry(ucmd, head, hash_list_entry) { ppos = pos; pos += scnprintf(&buf[pos], diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 79ef50208..e8437ff55 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -1034,6 +1034,7 @@ check: if (virt_dev->thin_provisioned) { int block_shift = virt_dev->dev->block_shift; + if (virt_dev->blockio) { struct request_queue *q; @@ -1856,6 +1857,7 @@ static enum compl_status_e vdisk_synchronize_cache(struct vdisk_cmd_params *p) if (data_len == 0) { struct scst_vdisk_dev *virt_dev = dev->dh_priv; + data_len = virt_dev->file_size - ((loff_t)scst_cmd_get_lba(cmd) << dev->block_shift); } @@ -2000,6 +2002,7 @@ static int vdisk_format_dif(struct scst_cmd *cmd, uint64_t start_lba, i = -1; while (1) { int len = min_t(size_t, (size_t)left, PAGE_SIZE); + full_len += len; i++; iv_count++; @@ -2209,6 +2212,7 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) if (virt_dev->thin_provisioned) { int rc = vdisk_unmap_range(cmd, virt_dev, 0, virt_dev->nblocks); + if (rc != 0) goto finished; } @@ -3003,6 +3007,7 @@ out: static int vcdrom_parse(struct scst_cmd *cmd) { int res, rc; + rc = scst_cdrom_generic_parse(cmd); if (rc != 0) { res = scst_get_cmd_abnormal_done_state(cmd); @@ -4310,6 +4315,7 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, /* Physical transport */ if (cmd->tgtt->get_phys_transport_version != NULL) { uint16_t v = cmd->tgtt->get_phys_transport_version(cmd->tgt); + if (v != 0) { put_unaligned_be16(v, &buf[58 + num]); num += 2; @@ -5230,6 +5236,7 @@ static enum compl_status_e vdisk_exec_read_capacity(struct vdisk_cmd_params *p) if ((cmd->cdb[8] & 1) == 0) { uint32_t lba = get_unaligned_be32(&cmd->cdb[2]); + if (lba != 0) { TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd); scst_set_invalid_field_in_cdb(cmd, 2, 0); @@ -5305,6 +5312,7 @@ static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p if ((cmd->cdb[14] & 1) == 0) { uint32_t lba = get_unaligned_be32(&cmd->cdb[2]); + if (lba != 0) { TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd); scst_set_invalid_field_in_cdb(cmd, 2, 0); @@ -5788,6 +5796,7 @@ static int vdev_read_dif_tags(struct vdisk_cmd_params *p) iv = vdisk_alloc_iv(cmd, p); if (iv == NULL) { unsigned long flags; + /* To protect sense setting against blockio data reads */ spin_lock_irqsave(&vdev_err_lock, flags); scst_set_busy(cmd); @@ -5836,6 +5845,7 @@ static int vdev_read_dif_tags(struct vdisk_cmd_params *p) &loff); if ((err < 0) || (err < full_len)) { unsigned long flags; + PRINT_ERROR("DIF readv() returned %lld from %zd " "(offs %lld, dev %s)", (long long)err, full_len, (long long)loff, cmd->dev->virt_name); @@ -5919,6 +5929,7 @@ static int vdev_write_dif_tags(struct vdisk_cmd_params *p) iv = vdisk_alloc_iv(cmd, p); if (iv == NULL) { unsigned long flags; + /* To protect sense setting against blockio data writes */ spin_lock_irqsave(&vdev_err_lock, flags); scst_set_busy(cmd); @@ -5969,6 +5980,7 @@ restart: &loff); if (err < 0) { unsigned long flags; + PRINT_ERROR("DIF write() returned %lld from %zd", (long long)err, full_len); /* To protect sense setting with blockio */ @@ -5988,6 +6000,7 @@ restart: * value less, than requested. Let's restart. */ int e = eiv_count; + TRACE_MGMT_DBG("DIF write() returned %d from %zd " "(iv_count=%d)", (int)err, full_len, eiv_count); @@ -6322,6 +6335,7 @@ restart: * value less, than requested. Let's restart. */ int e = eiv_count; + TRACE_MGMT_DBG("write() returned %d from %zd " "(iv_count=%d)", (int)err, full_len, eiv_count); @@ -6688,6 +6702,7 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) #if 0 { static int err_inj_cntr; + if (++err_inj_cntr % 256 == 0) { PRINT_INFO("blockio_exec_rw() error injection"); scst_set_busy(cmd); @@ -6913,6 +6928,7 @@ static int vdisk_blockio_flush(struct block_device *bdev, gfp_t gfp_mask, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) if (async) { struct bio *bio = bio_alloc(gfp_mask, 0); + if (bio == NULL) { res = -ENOMEM; goto out_rep; @@ -7371,6 +7387,7 @@ static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, } else if (mcmd->fn == SCST_PR_ABORT_ALL) { struct scst_device *dev = tgt_dev->dev; struct scst_vdisk_dev *virt_dev = dev->dh_priv; + spin_lock(&virt_dev->flags_lock); virt_dev->prevent_allow_medium_removal = 0; spin_unlock(&virt_dev->flags_lock); @@ -8406,6 +8423,7 @@ static int vcdrom_change(struct scst_vdisk_dev *virt_dev, if (!virt_dev->cdrom_empty) { char *fn = kstrdup(filename, GFP_KERNEL); + if (fn == NULL) { PRINT_ERROR("%s", "Allocation of filename failed"); res = -ENOMEM; @@ -10625,6 +10643,7 @@ static void exit_scst_vdisk(struct scst_dev_type *devtype) static void init_ops(vdisk_op_fn *ops, int count) { int i; + for (i = 0; i < count; i++) if (ops[i] == NULL) ops[i] = vdisk_invalid_opcode; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index e85c9e688..aa5aa0d1b 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -2028,6 +2028,7 @@ int scst_set_cmd_error_and_inf(struct scst_cmd *cmd, int key, int asc, { /* Fixed format */ uint32_t i = information; + cmd->sense[0] |= 0x80; /* Information field is valid */ put_unaligned_be32(i, &cmd->sense[3]); break; @@ -2931,6 +2932,7 @@ next: something_freed = false; for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct scst_tgt_dev *t; + head = &sess->sess_tgt_dev_list[i]; list_for_each_entry_safe(tgt_dev, t, head, @@ -3001,8 +3003,10 @@ void scst_check_reassign_sessions(void) list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { struct scst_session *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { scst_check_reassign_sess(sess); @@ -3737,6 +3741,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) if (cmd->expected_data_direction & SCST_DATA_READ) { int resp = cmd->resp_data_len; + if (cmd->tgt_dif_data_expected) resp += (resp >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; *resid = cmd->expected_transfer_len_full - resp; @@ -3749,6 +3754,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) } } else if (cmd->expected_data_direction & SCST_DATA_WRITE) { int wl = cmd->write_len; + if (cmd->tgt_dif_data_expected) wl += (wl >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; if (wl < cmd->expected_transfer_len_full) @@ -3757,6 +3763,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) *resid = cmd->write_len - cmd->bufflen; if (cmd->tgt_dif_data_expected) { int r = *resid; + if (r < 0) r = -r; r += (r >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; @@ -3953,6 +3960,7 @@ static bool __scst_is_relative_target_port_id_unique(uint16_t id, list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { if (tgt == t) continue; @@ -4098,6 +4106,7 @@ void scst_free_tgt(struct scst_tgt *tgt) static void scst_init_order_data(struct scst_order_data *order_data) { int i; + spin_lock_init(&order_data->sn_lock); INIT_LIST_HEAD(&order_data->deferred_cmd_list); INIT_LIST_HEAD(&order_data->skipped_sn_list); @@ -4589,6 +4598,7 @@ static void scst_free_acg(struct scst_acg *acg) list_for_each_entry_safe(acg_dev, acg_dev_tmp, &acg->acg_dev_list, acg_dev_list_entry) { struct scst_tgt_dev *tgt_dev, *tt; + list_for_each_entry_safe(tgt_dev, tt, &acg_dev->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { @@ -4893,6 +4903,7 @@ int scst_tgt_dev_setup_threads(struct scst_tgt_dev *tgt_dev) if (dev->threads_num == 0) { struct scst_tgt_dev *shared_io_tgt_dev; + tgt_dev->active_cmd_threads = &scst_main_cmd_threads; shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev); @@ -5337,6 +5348,7 @@ void scst_sess_free_tgt_devs(struct scst_session *sess) /* The session is going down, no users, so no locks */ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry_safe(tgt_dev, t, head, sess_tgt_dev_list_entry) { scst_free_tgt_dev(tgt_dev); @@ -5780,6 +5792,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, write_cdb[1] = ws_cmd->cdb[1]; if (needs_dif) { uint8_t wrprotect = ws_cmd->cdb[1] & 0xE0; + if (wrprotect == 0) wrprotect = 0x20; write_cdb[1] |= wrprotect; @@ -5789,6 +5802,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, } else { /* There might be WRITE SAME(16) with WRPROTECT 0 here */ uint8_t wrprotect; + if (ws_cmd->cdb_len != 32) wrprotect = ws_cmd->cdb[1] & 0xE0; else @@ -5851,6 +5865,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, for_each_sg(dif_sg, s, dif_sg_cnt, i) { int left = (s->length - s->offset) >> SCST_DIF_TAG_SHIFT; struct t10_pi_tuple *t = sg_virt(s); + TRACE_DBG("sg %p, offset %d, length %d, left %d", s, s->offset, s->length, left); while (left > 0) { @@ -5951,6 +5966,7 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) if (cmd->status != 0) { int rc; + TRACE_DBG("Write cmd %p (ws cmd %p) finished not successfully", cmd, ws_cmd); sBUG_ON(cmd->resp_data_len != 0); @@ -6082,6 +6098,7 @@ static int scst_ws_sg_init(struct scatterlist **ws_sg, int ws_sg_cnt, sg_init_table(*ws_sg, ws_sg_cnt); for_each_sg(*ws_sg, sg, ws_sg_cnt, i) { u32 len = min(total_bytes, length); + sg_set_page(sg, pg, len, offset); total_bytes -= len; } @@ -6337,6 +6354,7 @@ static void scst_cwr_write_cmd_finished(struct scst_cmd *cmd) if (cmd->status != 0) { int rc; + TRACE_DBG("WRITE cmd %p (cwr cmd %p) finished not successfully", cmd, cwr_cmd); sBUG_ON(cmd->resp_data_len != 0); @@ -6375,6 +6393,7 @@ static void scst_cwr_read_cmd_finished(struct scst_cmd *cmd) if (cmd->status != 0) { int rc; + TRACE_DBG("Read cmd %p (cwr cmd %p) finished not successfully", cmd, cwr_cmd); sBUG_ON(cmd->resp_data_len != 0); @@ -6583,6 +6602,7 @@ int scst_finish_internal_cmd(struct scst_cmd *cmd) scst_complete_request_sense(cmd); else { scst_i_finish_fn_t f = (void *) *((unsigned long long **)cmd->tgt_i_priv); + f(cmd); } @@ -6685,6 +6705,7 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, atomic_set(&sess->refcnt, 0); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + INIT_LIST_HEAD(head); } spin_lock_init(&sess->sess_list_lock); @@ -6900,6 +6921,7 @@ int scst_pre_init_cmd(struct scst_cmd *cmd, const uint8_t *cdb, { int i; uint8_t *b = (uint8_t *)cmd; + for (i = 0; i < sizeof(*cmd); i++) EXTRACHECKS_BUG_ON(b[i] != 0); } @@ -7090,7 +7112,7 @@ void scst_free_cmd(struct scst_cmd *cmd) TRACE_MGMT_DBG("Freeing aborted cmd %p", cmd); EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed || - cmd->on_dev_exec_list); + cmd->on_dev_exec_list); /* * Target driver can already free sg buffer before calling @@ -7101,6 +7123,7 @@ void scst_free_cmd(struct scst_cmd *cmd) if (likely(cmd->dev != NULL)) { struct scst_dev_type *devt = cmd->devt; + if (devt->on_free_cmd != NULL) { TRACE_DBG("Calling dev handler %s on_free_cmd(%p)", devt->name, cmd); @@ -8205,6 +8228,7 @@ static int sg_cmp_elem(struct scatterlist **pdst_sg, size_t *pdst_len, /* Fast path: both buffers and len aligned */ unsigned long long *s = saddr; unsigned long long *d = daddr; + EXTRACHECKS_BUG_ON(len % align_size != 0); len /= align_size; for (i = 0; i < len; i++) { @@ -8212,6 +8236,7 @@ static int sg_cmp_elem(struct scatterlist **pdst_sg, size_t *pdst_len, uint8_t *s8 = (uint8_t *)&s[i]; uint8_t *d8 = (uint8_t *)&d[i]; int j; + for (j = 0; j < align_size; j++) { if (s8[j] != d8[j]) { *miscompare_offs = i * align_size + j; @@ -8224,6 +8249,7 @@ static int sg_cmp_elem(struct scatterlist **pdst_sg, size_t *pdst_len, } else { uint8_t *s = saddr; uint8_t *d = daddr; + for (i = 0; i < len; i++) { if (s[i] != d[i]) { *miscompare_offs = i; @@ -8693,6 +8719,7 @@ static int scst_verify_dif_type1(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -9015,6 +9042,7 @@ static int scst_do_dif(struct scst_cmd *cmd, case SCST_DIF_ACTION_PASS_CHECK: { enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + if (likely(checks != SCST_DIF_ACTION_NONE)) res = verify_fn(cmd); else @@ -9141,6 +9169,7 @@ static int scst_verify_dif_type2(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -9358,6 +9387,7 @@ static int scst_verify_dif_type3(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -10813,10 +10843,12 @@ static int get_cdb_info_lba_3_len_1_256_read(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_3_len_1_256(cmd, sdbops); + if (res != 0) goto out; else { struct scst_device *dev = cmd->dev; + if ((dev->dev_dif_mode != SCST_DIF_MODE_NONE) && !dev->dpicz) cmd->cmd_dif_actions = dev->dev_dif_rd_prot0_actions | SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | @@ -10830,10 +10862,12 @@ static int get_cdb_info_lba_3_len_1_256_write(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_3_len_1_256(cmd, sdbops); + if (res != 0) goto out; else { struct scst_device *dev = cmd->dev; + if (dev->dev_dif_mode != SCST_DIF_MODE_NONE) cmd->cmd_dif_actions = dev->dev_dif_wr_prot0_actions | SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | @@ -10941,6 +10975,7 @@ static int get_cdb_info_read_10(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_2(cmd, sdbops); + if (res != 0) return res; else { @@ -10956,6 +10991,7 @@ static int get_cdb_info_lba_4_len_2_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_2(cmd, sdbops); + if (res != 0) return res; else @@ -10981,6 +11017,7 @@ static int get_cdb_info_lba_4_len_4_rdprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10991,6 +11028,7 @@ static int get_cdb_info_lba_4_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -11016,6 +11054,7 @@ static int get_cdb_info_read_16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else { @@ -11031,6 +11070,7 @@ static int get_cdb_info_lba_8_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -11041,6 +11081,7 @@ static int get_cdb_info_lba_8_len_4_wrprotect32(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -11051,6 +11092,7 @@ static int get_cdb_info_lba_8_len_4_rdprotect32(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -11384,6 +11426,7 @@ static int get_cdb_info_mo(struct scst_cmd *cmd, case MO_SET_TARGET_PGS: { unsigned long flags; + cmd->op_name = "SET TARGET PORT GROUPS"; cmd->op_flags |= SCST_STRICTLY_SERIALIZED; spin_lock_irqsave(&scst_global_stpg_list_lock, flags); @@ -11749,6 +11792,7 @@ int scst_tape_generic_parse(struct scst_cmd *cmd) if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED && cmd->cdb[1] & 1) { int block_size = cmd->dev->block_size; + cmd->bufflen = cmd->bufflen * block_size; cmd->data_len = cmd->data_len * block_size; cmd->out_bufflen = cmd->out_bufflen * block_size; @@ -12194,6 +12238,7 @@ static void scst_check_internal_sense(struct scst_device *dev, int result, if (host_byte(result) == DID_RESET) { int sl; + TRACE(TRACE_MGMT, "DID_RESET received for device %s, " "triggering reset UA", dev->virt_name); sl = scst_set_sense(sense, sense_len, dev->d_sense, @@ -12383,6 +12428,7 @@ again: for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { /* Lockdep triggers here a false positive.. */ @@ -12425,6 +12471,7 @@ again: list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { struct scst_tgt_dev_UA *ua; + list_for_each_entry(ua, &tgt_dev->UA_list, UA_list_entry) { if (ua->global_UA && @@ -12456,6 +12503,7 @@ out_unlock: for (i = SESS_TGT_DEV_LIST_HASH_SIZE-1; i >= 0; i--) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_reverse(tgt_dev, head, sess_tgt_dev_list_entry) { spin_unlock(&tgt_dev->tgt_dev_lock); @@ -13601,6 +13649,7 @@ static int scst_parse_unmap_descriptors(struct scst_cmd *cmd) i = 0; while ((offset - 8) < descriptor_len) { struct scst_data_descriptor *d = &pd[i]; + d->sdd_lba = get_unaligned_be64(&address[offset]); offset += 8; d->sdd_blocks = get_unaligned_be32(&address[offset]); @@ -14823,6 +14872,7 @@ static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev) { if (tm_dbg_tgt_dev == tgt_dev) { unsigned long flags; + TRACE_MGMT_DBG("Deinit TM debugging tgt_dev %p", tgt_dev); del_timer_sync(&tm_dbg_timer); spin_lock_irqsave(&scst_tm_dbg_lock, flags); @@ -14847,6 +14897,7 @@ static void tm_dbg_delay_cmd(struct scst_cmd *cmd) case TM_DBG_STATE_ABORT: if (tm_dbg_delayed_cmds_count == 0) { unsigned long d = 58*HZ + (scst_random() % (4*HZ)); + TRACE_MGMT_DBG("STATE ABORT: delaying cmd %p (tag %llu)" " for %ld.%ld seconds (%ld HZ), " "tm_dbg_on_state_passes=%d", cmd, cmd->tag, @@ -14892,6 +14943,7 @@ void tm_dbg_check_released_cmds(void) { if (tm_dbg_flags.tm_dbg_release) { struct scst_cmd *cmd, *tc; + spin_lock_irq(&scst_tm_dbg_lock); list_for_each_entry_safe_reverse(cmd, tc, &tm_dbg_delayed_cmd_list, cmd_list_entry) { @@ -15073,6 +15125,7 @@ void scst_check_debug_sn(struct scst_cmd *cmd) /* To simulate from time to time queue flushing */ if (!in_interrupt() && (scst_random() % 120) == 8) { int t = scst_random() % 1200; + TRACE_SN("Delaying IO on %d ms", t); msleep(t); } diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 709cc1a20..63d2e8e37 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -596,6 +596,7 @@ EXPORT_SYMBOL(scst_register_target); static inline int test_sess_list(struct scst_tgt *tgt) { int res; + mutex_lock(&scst_mutex); res = list_empty(&tgt->sysfs_sess_list); mutex_unlock(&scst_mutex); @@ -637,6 +638,7 @@ void scst_unregister_target(struct scst_tgt *tgt) again: { struct scst_session *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if (sess->shut_phase == SCST_SESS_SPH_READY) { /* @@ -870,6 +872,7 @@ static void __printf(2, 3) scst_to_syslog(void *arg, const char *fmt, ...) int scst_get_cmd_counter(void) { int i, res = 0; + for (i = 0; i < (int)ARRAY_SIZE(scst_percpu_infos); i++) res += atomic_read(&scst_percpu_infos[i].cpu_cmd_count); return res; @@ -1877,6 +1880,7 @@ int scst_add_threads(struct scst_cmd_threads *cmd_threads, if (tgt_dev != NULL) { struct scst_tgt_dev *t; + list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { if (t == tgt_dev) @@ -2447,6 +2451,7 @@ static int __init init_scst(void) struct scsi_sense_hdr *shdr; struct scst_order_data *o; struct scst_cmd *c; + BUILD_BUG_ON(sizeof(*shdr) > SCST_SENSE_BUFFERSIZE); BUILD_BUG_ON(sizeof(o->curr_sn) != sizeof(o->expected_sn)); BUILD_BUG_ON(sizeof(c->sn) != sizeof(o->expected_sn)); @@ -2606,6 +2611,7 @@ static int __init init_scst(void) if (scst_max_cmd_mem == 0) { struct sysinfo si; + si_meminfo(&si); #if BITS_PER_LONG == 32 scst_max_cmd_mem = min( diff --git a/scst/src/scst_mem.c b/scst/src/scst_mem.c index 1706acb78..488132c07 100644 --- a/scst/src/scst_mem.c +++ b/scst/src/scst_mem.c @@ -675,6 +675,7 @@ static int sgv_alloc_sg_entries(struct scatterlist *sg, int pages, pg = 0; for (i = 0; i < pages; i++) { int n = PAGE_ALIGN(sg[i].length) >> PAGE_SHIFT; + trans_tbl[i].pg_count = pg; for (j = 0; j < n; j++) trans_tbl[pg++].sg_num = i+1; @@ -972,6 +973,7 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, pages = PAGE_ALIGN(size) >> PAGE_SHIFT; if (pool->single_alloc_pages == 0) { int pages_order = get_order(size); + cache_num = pages_order; pages_to_alloc = (1 << pages_order); } else { @@ -1128,6 +1130,7 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, success: if (cache_num >= 0) { int sg; + atomic_inc(&pool->cache_acc[cache_num].total_alloc); if (sgv_pool_clustered(pool)) cnt = obj->trans_tbl[pages-1].sg_num; @@ -1255,10 +1258,12 @@ void sgv_pool_free(struct sgv_pool_obj *obj, struct scst_mem_lim *mem_lim) { struct scatterlist *sg = obj->sg_entries; int i; + for (i = 0; i < obj->sg_count; i++) { struct page *p = sg_page(&sg[i]); int len = sg[i].length; int pages = PAGE_ALIGN(len) >> PAGE_SHIFT; + while (pages > 0) { if (page_count(p) != 1) { PRINT_WARNING("Freeing page %p with " diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index 037c0d462..8d48ab86f 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -103,6 +103,7 @@ static inline void tid_secure(uint8_t *tid) { if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) { int size = scst_tid_size(tid); + tid[size - 1] = '\0'; } @@ -137,6 +138,7 @@ bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) else if (tid_a_fmt == 0x40) { if (tid_a_fmt != tid_b_fmt) { uint8_t *p = strnchr(tid_a, tid_a_max, ','); + if (p == NULL) goto out_error; tid_a_len = p - tid_a; @@ -153,6 +155,7 @@ bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) else if (tid_b_fmt == 0x40) { if (tid_a_fmt != tid_b_fmt) { uint8_t *p = strnchr(tid_b, tid_b_max, ','); + if (p == NULL) goto out_error; tid_b_len = p - tid_b; @@ -243,6 +246,7 @@ void scst_pr_dump_prs(struct scst_device *dev, bool force) else { struct scst_dev_registrant *reg; int i = 0; + list_for_each_entry(reg, &dev->dev_registrants_list, dev_registrants_list_entry) { PRINT_INFO(" [%d] registrant %s/%d, key %016llx " @@ -256,6 +260,7 @@ void scst_pr_dump_prs(struct scst_device *dev, bool force) if (dev->pr_is_set) { struct scst_dev_registrant *holder = dev->pr_holder; + if (holder != NULL) PRINT_INFO("Reservation holder is %s/%d (key %016llx, " "scope %x, type %x, reg %p, tgt_dev %p)", @@ -629,6 +634,7 @@ static void scst_pr_abort_reg(struct scst_device *dev, if ((reg->tgt_dev != pr_cmd->tgt_dev) && !dev->tas) { uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN]; int sl; + sl = scst_set_sense(sense_buffer, sizeof(sense_buffer), dev->d_sense, SCST_LOAD_SENSE(scst_sense_cleared_by_another_ini_UA)); diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index f0606d677..0fe533b40 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -753,6 +753,7 @@ static inline atomic_t *scst_get(void) static inline void scst_put(atomic_t *a) { int f; + f = atomic_dec_and_test(a); /* See comment about smp_mb() in scst_suspend_activity() */ if (unlikely(test_bit(SCST_FLAG_SUSPENDED, &scst_flags)) && f) { diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index c67891ee3..1538066c4 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -668,9 +668,11 @@ static int scst_check_tgt_acg_ptrs(struct scst_tgt *tgt, struct scst_acg *acg) list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *t; + list_for_each_entry(t, &tgtt->tgt_list, tgt_list_entry) { if (t == tgt) { struct scst_acg *a; + if (acg == NULL) goto out; if (acg == tgt->default_acg) @@ -783,8 +785,8 @@ static ssize_t scst_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kobj_attr; - kobj_attr = container_of(attr, struct kobj_attribute, attr); + kobj_attr = container_of(attr, struct kobj_attribute, attr); return kobj_attr->show(kobj, kobj_attr, buf); } @@ -792,8 +794,8 @@ static ssize_t scst_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kobj_attr; - kobj_attr = container_of(attr, struct kobj_attribute, attr); + kobj_attr = container_of(attr, struct kobj_attribute, attr); if (kobj_attr->store) return kobj_attr->store(kobj, kobj_attr, buf, count); else @@ -1073,6 +1075,7 @@ static ssize_t scst_tgtt_dif_capable_show(struct kobject *kobj, if (tgtt->supported_dif_block_sizes) { const int *p = tgtt->supported_dif_block_sizes; int j; + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; @@ -1893,9 +1896,11 @@ static ssize_t __scst_acg_black_hole_store(struct scst_acg *acg, list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) { int i; + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (t != SCST_ACG_BLACK_HOLE_NONE) set_bit(SCST_TGT_DEV_BLACK_HOLE, &tgt_dev->tgt_dev_flags); @@ -1991,9 +1996,11 @@ static int __scst_acg_process_cpu_mask_store(struct scst_tgt *tgt, list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) { int i; + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct scst_tgt_dev *tgt_dev; struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { int rc; @@ -2562,9 +2569,11 @@ static ssize_t scst_tgt_forwarding_store(struct kobject *kobj, list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { int i; + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (tgt->tgt_forwarding) set_bit(SCST_TGT_DEV_FORWARDING, &tgt_dev->tgt_dev_flags); @@ -2727,6 +2736,7 @@ static ssize_t scst_tgt_dif_capable_show(struct kobject *kobj, if (tgt->tgt_supported_dif_block_sizes) { const int *p = tgt->tgt_supported_dif_block_sizes; int j; + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; @@ -4463,6 +4473,7 @@ static int scst_sess_zero_latency(struct scst_sysfs_work_item *work) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { tgt_dev->scst_time = 0; tgt_dev->tgt_time = 0; @@ -4546,6 +4557,7 @@ static int scst_sysfs_sess_get_active_commands(struct scst_session *sess) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count); } @@ -4615,6 +4627,7 @@ static int scst_sysfs_sess_get_dif_checks_failed_work_fn(struct scst_sysfs_work_ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { app_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_tgt); ref_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_tgt); @@ -4695,6 +4708,7 @@ static int scst_sess_zero_dif_checks_failed(struct scst_sysfs_work_item *work) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_tgt, 0); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 41713a72c..b9453278d 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -117,6 +117,7 @@ static bool scst_unmap_overlap(struct scst_cmd *cmd, int64_t lba2, for (i = 0; pd[i].sdd_blocks != 0; i++) { struct scst_data_descriptor *d = &pd[i]; + TRACE_DBG("i %d, lba %lld, blocks %lld", i, (long long)d->sdd_lba, (long long)d->sdd_blocks); res = scst_lba1_inside_lba2(d->sdd_lba, lba2, lba2_blocks); @@ -292,6 +293,7 @@ static bool scst_check_scsi_atomicity(struct scst_cmd *chk_cmd) continue; if (scst_cmd_overlap(chk_cmd, cmd)) { struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + /* * kmalloc() allocates by at least 32 bytes increments, * hence krealloc() on 8 bytes increments, if not all @@ -328,6 +330,7 @@ out: out_busy_undo: list_for_each_entry(cmd, &dev->dev_exec_cmd_list, dev_exec_cmd_list_entry) { struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + if ((p != NULL) && (p[cmd->scsi_atomic_blocked_cmds_count-1] == chk_cmd)) { cmd->scsi_atomic_blocked_cmds_count--; chk_cmd->scsi_atomic_blockers--; @@ -472,6 +475,7 @@ static void scst_check_unblock_scsi_atomic_cmds(struct scst_cmd *cmd) for (i = 0; i < cmd->scsi_atomic_blocked_cmds_count; i++) { struct scst_cmd *acmd = cmd->scsi_atomic_blocked_cmds[i]; + acmd->scsi_atomic_blockers--; if (acmd->scsi_atomic_blockers == 0) { TRACE_BLOCK("Unblocking blocked acmd %p (blocker " @@ -766,6 +770,7 @@ out_redirect: msleep(50); } else { unsigned long flags; + spin_lock_irqsave(&scst_init_lock, flags); TRACE_DBG("Adding cmd %p to init cmd list", cmd); list_add_tail(&cmd->cmd_list_entry, &scst_init_cmd_list); @@ -1401,6 +1406,7 @@ out_check_compl: if (unlikely(test_bit(SCST_TGT_DEV_BLACK_HOLE, &cmd->tgt_dev->tgt_dev_flags))) { struct scst_session *sess = cmd->sess; bool abort = false; + switch (sess->acg->acg_black_hole_type) { case SCST_ACG_BLACK_HOLE_CMD: case SCST_ACG_BLACK_HOLE_ALL: @@ -1803,6 +1809,7 @@ static int scst_rdy_to_xfer(struct scst_cmd *cmd) if (tgtt->on_hw_pending_cmd_timeout != NULL) { struct scst_session *sess = cmd->sess; + cmd->hw_pending_start = jiffies; cmd->cmd_hw_pending = 1; if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) { @@ -2062,6 +2069,7 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) if (unlikely(cmd->resid_possible)) { if (cmd->data_direction & SCST_DATA_WRITE) { bool remainder = false; + if (cmd->data_direction & SCST_DATA_READ) { if (cmd->write_len != cmd->out_bufflen) remainder = true; @@ -2141,6 +2149,7 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) out_descr: if (unlikely(cmd->op_flags & SCST_DESCRIPTORS_BASED)) { int r = scst_parse_descriptors(cmd); + if (unlikely(r != 0)) goto out; } @@ -2314,6 +2323,7 @@ static int scst_report_luns_local(struct scst_cmd *cmd) */ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &cmd->sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (!overflow) { if ((buffer_size - offs) < 8) { @@ -3388,6 +3398,7 @@ static struct scst_cmd *scst_post_exec_sn(struct scst_cmd *cmd, if (inc_expected_sn) { bool rc = scst_inc_expected_sn(cmd); + if (!rc) goto out; if (make_active) @@ -3653,6 +3664,7 @@ static inline bool scst_check_alua(struct scst_cmd *cmd, int *out_res) alua_filter = ACCESS_ONCE(cmd->tgt_dev->alua_filter); if (unlikely(alua_filter)) { int ac = alua_filter(cmd); + if (ac != SCST_ALUA_CHECK_OK) { if (ac != SCST_ALUA_CHECK_DELAYED) { EXTRACHECKS_BUG_ON(cmd->status == 0); @@ -3692,6 +3704,7 @@ static int scst_exec_check_blocking(struct scst_cmd **active_cmd) #ifdef CONFIG_SCST_DEBUG_SN if ((scst_random() % 120) == 7) { int t = scst_random() % 200; + TRACE_SN("Delaying IO on %d ms", t); msleep(t); } @@ -3865,6 +3878,7 @@ static int scst_check_sense(struct scst_cmd *cmd) } else { int sl; uint8_t sense[SCST_STANDARD_SENSE_LEN]; + TRACE(TRACE_MGMT, "DID_RESET received for device %s, " "triggering reset UA", dev->virt_name); sl = scst_set_sense(sense, sizeof(sense), dev->d_sense, @@ -4041,6 +4055,7 @@ next: if (likely(scst_cmd_completed_good(cmd))) { if (cmd->deferred_dif_read_check) { int rc = scst_dif_process_read(cmd); + if (unlikely(rc != 0)) { cmd->deferred_dif_read_check = 0; goto again; @@ -4153,6 +4168,7 @@ static int scst_mode_select_checks(struct scst_cmd *cmd) if (likely(scsi_status_is_good(cmd->status))) { int atomic = scst_cmd_atomic(cmd); + if (unlikely((cmd->cdb[0] == MODE_SELECT) || (cmd->cdb[0] == MODE_SELECT_10) || (cmd->cdb[0] == LOG_SELECT))) { @@ -4206,6 +4222,7 @@ static int scst_mode_select_checks(struct scst_cmd *cmd) SCST_SENSE_ASC_VALID, 0, 0x2F, 0))) { int atomic = scst_cmd_atomic(cmd); + if (atomic) { TRACE_DBG("Possible parameters changed UA %x: " "thread context required", cmd->sense[12]); @@ -4315,6 +4332,7 @@ static int scst_dev_done(struct scst_cmd *cmd) if (cmd->inc_expected_sn_on_done && cmd->sent_for_exec && cmd->sn_set) { bool rc = scst_inc_expected_sn(cmd); + if (rc) scst_make_deferred_commands_active(cmd->cur_order_data); } @@ -4478,6 +4496,7 @@ static int scst_xmit_response(struct scst_cmd *cmd) (cmd->data_direction & SCST_DATA_READ)) { int i, sg_cnt; struct scatterlist *sg, *sgi; + if (cmd->tgt_i_sg != NULL) { sg = cmd->tgt_i_sg; sg_cnt = cmd->tgt_i_sg_cnt; @@ -4502,6 +4521,7 @@ static int scst_xmit_response(struct scst_cmd *cmd) if (tgtt->on_hw_pending_cmd_timeout != NULL) { struct scst_session *sess = cmd->sess; + cmd->hw_pending_start = jiffies; cmd->cmd_hw_pending = 1; if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) { @@ -4762,6 +4782,7 @@ again: order_data->cur_sn_slot = order_data->sn_slots; if (unlikely(atomic_read(order_data->cur_sn_slot) != 0)) { static int q; + if (q++ < 10) PRINT_WARNING("Not enough SN slots " "(dev %s)", cmd->dev->virt_name); @@ -5002,6 +5023,7 @@ static int __scst_init_cmd(struct scst_cmd *cmd) else { struct scst_order_data *order_data = cmd->cur_order_data; unsigned long flags; + spin_lock_irqsave(&order_data->init_done_lock, flags); scst_cmd_set_sn(cmd); spin_unlock_irqrestore(&order_data->init_done_lock, flags); @@ -5046,6 +5068,7 @@ restart: list_for_each_entry(cmd, &scst_init_cmd_list, cmd_list_entry) { int rc; + if (susp && !test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) continue; if (!test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { @@ -5922,6 +5945,7 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, if (mstb->done_counted || mstb->finish_counted) { unsigned long t; char state_name[32]; + if (mcmd->fn != SCST_PR_ABORT_ALL) t = TRACE_MGMT; else @@ -6009,6 +6033,7 @@ static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd) default: { char fn_name[16], state_name[32]; + PRINT_CRIT_ERROR("Wrong mcmd %p state %s (fn %s, " "cmd_finish_wait_count %d, cmd_done_wait_count %d)", mcmd, scst_get_mcmd_state_name(state_name, @@ -6033,6 +6058,7 @@ static bool __scst_check_unblock_aborted_cmd(struct scst_cmd *cmd, struct list_head *list_entry, bool blocked) { bool res; + if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { list_del(list_entry); if (blocked) @@ -6090,6 +6116,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { struct scst_order_data *order_data = tgt_dev->curr_order_data; + spin_lock(&order_data->sn_lock); list_for_each_entry_safe(cmd, tcmd, &order_data->deferred_cmd_list, @@ -6590,6 +6617,7 @@ static void scst_do_nexus_loss_sess(struct scst_mgmt_cmd *mcmd) for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { scst_nexus_loss(tgt_dev, (mcmd->fn != SCST_UNREG_SESS_TM)); @@ -6623,6 +6651,7 @@ static int scst_abort_all_nexus_loss_sess(struct scst_mgmt_cmd *mcmd, for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { __scst_abort_task_set(mcmd, tgt_dev); @@ -6657,6 +6686,7 @@ static void scst_do_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd) for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { scst_nexus_loss(tgt_dev, true); @@ -6692,6 +6722,7 @@ static int scst_abort_all_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd, for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { __scst_abort_task_set(mcmd, tgt_dev); @@ -6884,6 +6915,7 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) { struct scst_acg *acg = sess->acg; struct scst_acg_dev *acg_dev; + mutex_lock(&scst_mutex); list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { dev = acg_dev->dev; @@ -6905,6 +6937,7 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) mutex_lock(&scst_mutex); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { scst_call_dev_task_mgmt_fn_done(mcmd, tgt_dev); } @@ -6917,11 +6950,13 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) { struct scst_session *s; struct scst_tgt *tgt = sess->tgt; + mutex_lock(&scst_mutex); list_for_each_entry(s, &tgt->sess_list, sess_list_entry) { for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &s->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (mcmd->sess == tgt_dev->sess) @@ -7073,6 +7108,7 @@ static int scst_process_mgmt_cmd(struct scst_mgmt_cmd *mcmd) default: { char fn_name[16], state_name[32]; + PRINT_CRIT_ERROR("Wrong mcmd %p state %s (fn %s, " "cmd_finish_wait_count %d, cmd_done_wait_count " "%d)", mcmd, scst_get_mcmd_state_name(state_name, @@ -7116,6 +7152,7 @@ int scst_tm_thread(void *arg) while (!list_empty(&scst_active_mgmt_cmd_list)) { int rc; struct scst_mgmt_cmd *mcmd; + mcmd = list_first_entry(&scst_active_mgmt_cmd_list, typeof(*mcmd), mgmt_cmd_list_entry); TRACE_MGMT_DBG("Deleting mgmt cmd %p from active cmd " @@ -8114,6 +8151,7 @@ struct scst_cmd *scst_find_cmd_by_tag(struct scst_session *sess, { unsigned long flags; struct scst_cmd *cmd; + spin_lock_irqsave(&sess->sess_list_lock, flags); cmd = __scst_find_cmd_by_tag(sess, tag, false); spin_unlock_irqrestore(&sess->sess_list_lock, flags); diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 86e61883f..e9f818095 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -906,6 +906,7 @@ static void scst_event_stpg_notify_fn(struct scst_event *event, for (i = 0, d = &p->stpg_descriptors[0]; i < p->stpg_descriptors_cnt; i++, d++) { struct scst_target_group *tg = __lookup_tg_by_group_id(dg, d->group_id); + if (!tg) { PRINT_ERROR("STPG: unable to find TG %d", d->group_id); goto out_fail; @@ -967,6 +968,7 @@ static void __scst_tg_set_state(struct scst_target_group *tg, list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING); + if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) gen_ua = false; From e1b30aba0edc91fb1b71367f1df341d6ba751d14 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:03:53 +0000 Subject: [PATCH 52/82] scst_lib: Fix three more checkpatch complaints about whitespace git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6619 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_lib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index aa5aa0d1b..a284f2e97 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -496,7 +496,7 @@ static int get_cdb_info_var_len(struct scst_cmd *cmd, * | Command name | Operation | Type | * | | code | | * |-------------------------------------+------------+------+ - * + * * +=========================================================+ * |Key: M = command implementation is mandatory. | * | O = command implementation is optional. | @@ -15207,8 +15207,8 @@ void scst_set_pre_exec_time(struct scst_cmd *cmd) void scst_set_exec_start(struct scst_cmd *cmd) { - cmd->exec_time_counting = true; - scst_set_cur_start(cmd); + cmd->exec_time_counting = true; + scst_set_cur_start(cmd); } void scst_set_exec_time(struct scst_cmd *cmd) From ee4c5fcd7f1e250f4122c1022ec5eb1241d29bda Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:10:01 +0000 Subject: [PATCH 53/82] scst_lib: Avoid that checkpatch reports that kfree(NULL) is safe git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6620 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_lib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index a284f2e97..03ae0060f 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -5924,8 +5924,7 @@ static void scst_ws_finished(struct scst_write_same_priv *wsp) else if (wsp->ws_sg_tails && wsp->ws_sg_tails[0].sg_cnt != 0 && sg_page(&wsp->ws_sg_tails[0].sg[0]) != sg_page(ws_cmd->sg)) __free_page(sg_page(&wsp->ws_sg_tails[0].sg[0])); - if (wsp->ws_sg_full) - kfree(wsp->ws_sg_full); + kfree(wsp->ws_sg_full); if (wsp->ws_sg_tails) { for (i = 0; wsp->ws_descriptors[i].sdd_blocks != 0; i++) if (wsp->ws_sg_tails[i].sg_cnt != 0) From da8016b953a4f11ad510756f31c553183728f102 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sat, 7 Nov 2015 00:58:18 +0000 Subject: [PATCH 54/82] scst: Avoid that checkpatch complains about suspect code indentation git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6621 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 8 ++++---- scst/src/scst_lib.c | 18 +++++++++--------- scst/src/scst_pres.c | 8 ++++---- scst/src/scst_targ.c | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index e8437ff55..b55fb9bcd 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -1110,11 +1110,11 @@ static int vdisk_get_file_size(const char *filename, bool blockio, goto out_close; } - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { res = -EINVAL; goto out_close; } diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 03ae0060f..dab85cedb 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -3349,7 +3349,7 @@ static int __scst_adjust_sg_get_tail(struct scst_cmd *cmd, TRACE_DBG_FLAG(TRACE_SG_OP|TRACE_MEMORY|TRACE_DEBUG, "cmd %p (tag %llu), sg %p, adjust_len %d, i %d, " "j %d, sg[j].length %d, offs %d", - cmd, (long long unsigned int)cmd->tag, + cmd, (unsigned long long)cmd->tag, sg, adjust_len, i, j, sg[j].length, offs); if (offs == sg[j].length) { @@ -14397,11 +14397,11 @@ int scst_copy_file(const char *src, const char *dest) inode = file_inode(file_src); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); res = -EINVAL; set_fs(old_fs); @@ -14596,11 +14596,11 @@ static int __scst_read_file_transactional(const char *file_name, inode = file_inode(file); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); res = -EINVAL; goto out_close; diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index 8d48ab86f..8ce467d6e 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -694,11 +694,11 @@ static int scst_pr_do_load_device_file(struct scst_device *dev, inode = file_inode(file); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); goto out_close; } diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index b9453278d..b6198f78b 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -3734,9 +3734,9 @@ static int scst_exec_check_blocking(struct scst_cmd **active_cmd) cmd->state = SCST_CMD_STATE_LOCAL_EXEC; rc = scst_do_local_exec(cmd); - if (likely(rc == SCST_EXEC_NOT_COMPLETED)) - /* Nothing to do */; - else { + if (likely(rc == SCST_EXEC_NOT_COMPLETED)) { + /* Nothing to do */ + } else { sBUG_ON(rc != SCST_EXEC_COMPLETED); goto done; } From 2610dd0962f55bbd1b64681c63b19cfa5b0653ce Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Sat, 7 Nov 2015 02:27:47 +0000 Subject: [PATCH 55/82] Clarify README for qla2x00t_git as suggested by Bart Van Assche git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6622 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- README | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README b/README index 04f0fd0b5..aa14955cd 100644 --- a/README +++ b/README @@ -48,9 +48,15 @@ qla2x00t_git 3. Symlink drivers/scsi/qla2xxx subdirectory in the cloned git tree to the qla2x00t_git subdirectory -Thats all. Now "make all" and other common and QLA specific root -Makefile targets "magically" start working. For detail instructions how -to setup QLA target build environment see its README or HOWTO. +Thats all. Now run either "make all" to build the SCST core and all +target drivers with in-tree QLogic drivers, or the following command +with standalone QLogic drivers: + +BUILD_2X_MODULE=y CONFIG_SCSI_QLA_FC=y CONFIG_SCSI_QLA2XXX_TARGET=y make all + +and other common and QLA specific root Makefile targets "magically" start +working. For detail instructions how to setup QLA target build environment see +its README or HOWTO. You can still build the old driver using qla_old* root Makefile targets. From 8c55d451812874637a2a9625c35fbbb9fec7015b Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Sat, 7 Nov 2015 04:04:48 +0000 Subject: [PATCH 56/82] LSI MPT web update git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6623 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- www/target_lsi.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/www/target_lsi.html b/www/target_lsi.html index b460cef78..f086d30ac 100644 --- a/www/target_lsi.html +++ b/www/target_lsi.html @@ -60,9 +60,15 @@

It supports parallel SCSI (SPI), including Wide SCSI, and Fibre Channel, but also should work with SAS. This driver is on the alpha stage and available for download from the SCST SVN repository. See the download page how to setup access to it. -




- + +

Recently Theodore Vaida updated it for the latest hardware generation, including 12G support. You can download current version + from Github.

+ +


+
 
From 51acfdb0ac49825070fedca9f28fe9b1ab4695a1 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 17:26:16 +0000 Subject: [PATCH 57/82] isert-scst: Use kcalloc() instead of kzalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6624 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_rdma.c | 3 ++- iscsi-scst/kernel/isert-scst/isert_login.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser_rdma.c b/iscsi-scst/kernel/isert-scst/iser_rdma.c index 1f1c56e66..6a1e9279d 100644 --- a/iscsi-scst/kernel/isert-scst/iser_rdma.c +++ b/iscsi-scst/kernel/isert-scst/iser_rdma.c @@ -870,7 +870,8 @@ static struct isert_device *isert_device_create(struct ib_device *ib_dev) isert_dev->num_cqs = min_t(int, num_online_cpus(), ib_dev->num_comp_vectors); - isert_dev->cq_qps = kzalloc(sizeof(*isert_dev->cq_qps) * isert_dev->num_cqs, + isert_dev->cq_qps = kcalloc(isert_dev->num_cqs, + sizeof(*isert_dev->cq_qps), GFP_KERNEL); if (unlikely(isert_dev->cq_qps == NULL)) { pr_err("Failed to allocate iser cq_qps\n"); diff --git a/iscsi-scst/kernel/isert-scst/isert_login.c b/iscsi-scst/kernel/isert-scst/isert_login.c index 517a6f257..0a3720306 100644 --- a/iscsi-scst/kernel/isert-scst/isert_login.c +++ b/iscsi-scst/kernel/isert-scst/isert_login.c @@ -952,7 +952,7 @@ int __init isert_init_login_devs(unsigned int ndevs) * allocate the devices -- we can't have them static, as the number * can be specified at load time */ - isert_conn_devices = kzalloc(n_devs * sizeof(struct isert_conn_dev), + isert_conn_devices = kcalloc(n_devs, sizeof(struct isert_conn_dev), GFP_KERNEL); if (unlikely(!isert_conn_devices)) { res = -ENOMEM; From a6ff47ce0ac02b884ca56fde500bd92885c34d14 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 17:29:20 +0000 Subject: [PATCH 58/82] scst: Use kcalloc() instead of kzalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6625 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_copy_mgr.c | 2 +- scst/src/scst_lib.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index 63183f206..a9ef255f6 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -3244,7 +3244,7 @@ int scst_cm_parse_descriptors(struct scst_cmd *ec_cmd) TRACE_DBG("tgt_cnt %d", tgt_cnt); - tgt_descrs = kzalloc(sizeof(*tgt_descrs) * tgt_cnt, GFP_KERNEL); + tgt_descrs = kcalloc(tgt_cnt, sizeof(*tgt_descrs), GFP_KERNEL); if (tgt_descrs == NULL) { TRACE(TRACE_OUT_OF_MEM, "Unable to allocate tgt_descrs " "(count %d, size %zd)", tgt_cnt, diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index dab85cedb..7c626aa65 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -6121,7 +6121,8 @@ static int scst_ws_sg_tails_get(struct scst_data_descriptor *where, struct scst_ while (where[i].sdd_blocks != 0) i++; - wsp->ws_sg_tails = kzalloc(sizeof(*(wsp->ws_sg_tails))*(i+1), GFP_KERNEL); + wsp->ws_sg_tails = kcalloc(i + 1, sizeof(*wsp->ws_sg_tails), + GFP_KERNEL); if (wsp->ws_sg_tails == NULL) { PRINT_ERROR("Unable to allocate ws_sg_tails (size %zd)", sizeof(*wsp->ws_sg_tails)*i); @@ -13636,7 +13637,7 @@ static int scst_parse_unmap_descriptors(struct scst_cmd *cmd) if (cnt == 0) goto out_put; - pd = kzalloc(sizeof(*pd) * cnt, GFP_KERNEL); + pd = kcalloc(cnt, sizeof(*pd), GFP_KERNEL); if (pd == NULL) { PRINT_ERROR("Unable to kmalloc UNMAP %d descriptors", cnt+1); scst_set_busy(cmd); From 5296640e9708efd3cacdea0ab7e1921dca47857e Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 17:40:07 +0000 Subject: [PATCH 59/82] qla2x00t: Use kcalloc() instead of kzalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6626 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla_isr.c | 8 ++++---- qla2x00t/qla_os.c | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/qla2x00t/qla_isr.c b/qla2x00t/qla_isr.c index 8e3652d2a..5f2923dbe 100644 --- a/qla2x00t/qla_isr.c +++ b/qla2x00t/qla_isr.c @@ -2632,8 +2632,8 @@ qla24xx_enable_msix(struct qla_hw_data *ha, struct rsp_que *rsp) struct qla_msix_entry *qentry; scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); - entries = kzalloc(sizeof(struct msix_entry) * ha->msix_count, - GFP_KERNEL); + entries = kcalloc(ha->msix_count, sizeof(struct msix_entry), + GFP_KERNEL); if (!entries) { ql_log(ql_log_warn, vha, 0x00bc, "Failed to allocate memory for msix_entry.\n"); @@ -2664,8 +2664,8 @@ msix_failed: } ha->max_rsp_queues = ha->msix_count - 1; } - ha->msix_entries = kzalloc(sizeof(struct qla_msix_entry) * - ha->msix_count, GFP_KERNEL); + ha->msix_entries = kcalloc(ha->msix_count, + sizeof(struct qla_msix_entry), GFP_KERNEL); if (!ha->msix_entries) { ql_log(ql_log_fatal, vha, 0x00c8, "Failed to allocate memory for ha->msix_entries.\n"); diff --git a/qla2x00t/qla_os.c b/qla2x00t/qla_os.c index 401778dcf..e5434f236 100644 --- a/qla2x00t/qla_os.c +++ b/qla2x00t/qla_os.c @@ -367,7 +367,7 @@ static int qla2x00_alloc_queues(struct qla_hw_data *ha, struct req_que *req, struct rsp_que *rsp) { scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); - ha->req_q_map = kzalloc(sizeof(struct req_que *) * ha->max_req_queues, + ha->req_q_map = kcalloc(ha->max_req_queues, sizeof(struct req_que *), GFP_KERNEL); if (!ha->req_q_map) { ql_log(ql_log_fatal, vha, 0x003b, @@ -375,7 +375,7 @@ static int qla2x00_alloc_queues(struct qla_hw_data *ha, struct req_que *req, goto fail_req_map; } - ha->rsp_q_map = kzalloc(sizeof(struct rsp_que *) * ha->max_rsp_queues, + ha->rsp_q_map = kcalloc(ha->max_rsp_queues, sizeof(struct rsp_que *), GFP_KERNEL); if (!ha->rsp_q_map) { ql_log(ql_log_fatal, vha, 0x003c, @@ -3286,8 +3286,9 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, #ifdef CONFIG_SCSI_QLA2XXX_TARGET if (IS_FWI2_CAPABLE(ha)) { - ha->tgt_vp_map = kzalloc(sizeof(struct qla_tgt_vp_map) * - MAX_MULTI_ID_FABRIC, GFP_KERNEL); + ha->tgt_vp_map = kcalloc(MAX_MULTI_ID_FABRIC, + sizeof(struct qla_tgt_vp_map), + GFP_KERNEL); if (!ha->tgt_vp_map) goto fail_free_init_cb; @@ -3433,8 +3434,9 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, (*rsp)->ring); /* Allocate memory for NVRAM data for vports */ if (ha->nvram_npiv_size) { - ha->npiv_info = kzalloc(sizeof(struct qla_npiv_entry) * - ha->nvram_npiv_size, GFP_KERNEL); + ha->npiv_info = kcalloc(ha->nvram_npiv_size, + sizeof(struct qla_npiv_entry), + GFP_KERNEL); if (!ha->npiv_info) { ql_log_pci(ql_log_fatal, ha->pdev, 0x002d, "Failed to allocate memory for npiv_info.\n"); From dff29eda4b4d880fbb8cc62be5fc709186bdd82a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 17:55:03 +0000 Subject: [PATCH 60/82] scst.spec: Improve robustness of kernel version query code git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6627 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst.spec.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scst.spec.in b/scst.spec.in index b6b528f7b..af674bf2d 100644 --- a/scst.spec.in +++ b/scst.spec.in @@ -8,14 +8,16 @@ # version number when building on a koji build server. # - Otherwise use the version number of the running kernel. %{!?kversion: - %{?kdir:%define kversion %{expand:%%(make -sC %{kdir} kernelversion)}} + %{?kdir:%define kversion %{expand:%%( + make -sC "%{kdir}" kernelversion | grep -v ^make)}} %{!?kdir: %define kversion %{expand:%%( if rpm --quiet -q kernel-headers; then rpm -q --qf '%%%%{BUILDTIME} %%%%{version}-%%%%{release}.%%%%{arch}\\n' \\ kernel-headers | sort | tail -n1 | { read a b; echo $b; }; else - uname -r; + make -sC "/lib/modules/$(uname -r)/build/" kernelversion | + grep -v ^make; fi )}}} %{echo:kversion=%{kversion} From 6a491c8cda715c4583f5b9a4b2817c15279b2558 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:09:44 +0000 Subject: [PATCH 61/82] ib_srpt: Avoid that checkpatch complains about using ENOSYS git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6628 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/src/ib_srpt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 900240e72..a7be356c1 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -4173,7 +4173,7 @@ static void srpt_add_one(struct ib_device *device) #endif sdev->srq = use_srq ? ib_create_srq(sdev->pd, &srq_attr) : - ERR_PTR(-ENOSYS); + ERR_PTR(-EOPNOTSUPP); if (IS_ERR(sdev->srq)) { pr_debug("ib_create_srq() failed: %ld\n", PTR_ERR(sdev->srq)); From ceee6fc7e1c425637244294c860c9425e273d8d3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:13:24 +0000 Subject: [PATCH 62/82] scst.spec: Follow-up for r6627 git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6629 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst.spec.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scst.spec.in b/scst.spec.in index af674bf2d..bf73f49a1 100644 --- a/scst.spec.in +++ b/scst.spec.in @@ -16,8 +16,7 @@ rpm -q --qf '%%%%{BUILDTIME} %%%%{version}-%%%%{release}.%%%%{arch}\\n' \\ kernel-headers | sort | tail -n1 | { read a b; echo $b; }; else - make -sC "/lib/modules/$(uname -r)/build/" kernelversion | - grep -v ^make; + uname -r; fi )}}} %{echo:kversion=%{kversion} From 8a09beb37de51fc2d1896ccf8dadc9e3ad7ff8b3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:19:41 +0000 Subject: [PATCH 63/82] isert-scst: Fix isert conn tx_free_list resources leak The get and put of resources from the tx_free commands pool was not symmetric. Commands that are self issued by the target (e.g. nop outs) didn't release their resources, although they acquired them earlier. Signed-off-by: Ariel Nahum Signed-off-by: Alex Lyakas git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6630 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_datamover.c | 3 +-- iscsi-scst/kernel/isert-scst/isert.c | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser_datamover.c b/iscsi-scst/kernel/isert-scst/iser_datamover.c index 4b0c287bc..fbb119c6d 100644 --- a/iscsi-scst/kernel/isert-scst/iser_datamover.c +++ b/iscsi-scst/kernel/isert-scst/iser_datamover.c @@ -168,8 +168,7 @@ void isert_release_rx_pdu(struct iscsi_cmnd *iscsi_pdu) { struct isert_cmnd *isert_pdu = (struct isert_cmnd *)iscsi_pdu; - if (likely(!isert_pdu->is_fake_rx)) - isert_rx_pdu_done(isert_pdu); + isert_rx_pdu_done(isert_pdu); } /* if last transition into FF (Fully Featured) state */ diff --git a/iscsi-scst/kernel/isert-scst/isert.c b/iscsi-scst/kernel/isert-scst/isert.c index a8a382ca2..3d81e52c6 100644 --- a/iscsi-scst/kernel/isert-scst/isert.c +++ b/iscsi-scst/kernel/isert-scst/isert.c @@ -46,6 +46,7 @@ #endif #include "isert_dbg.h" #include "isert.h" +#include "iser.h" #include "iser_datamover.h" #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) @@ -211,7 +212,7 @@ static void isert_cmnd_free(struct iscsi_cmnd *cmnd) sBUG(); } #endif - if (cmnd->parent_req) + if (cmnd->parent_req || ((struct isert_cmnd *)cmnd)->is_fake_rx) isert_release_tx_pdu(cmnd); else isert_release_rx_pdu(cmnd); From 072c508384c00d2dca1c18bb8aad818d9db8808d Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:40:04 +0000 Subject: [PATCH 64/82] scst_copy_mgr: Remove a superfluous cast git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6631 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_copy_mgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index a9ef255f6..e00b38d71 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -1976,7 +1976,7 @@ out_unlock_free: void sess_cm_list_id_cleanup_work_fn(struct delayed_work *work) { - struct scst_session *sess = container_of((struct delayed_work *)work, + struct scst_session *sess = container_of(work, struct scst_session, sess_cm_list_id_cleanup_work); struct scst_cm_list_id *l, *t; unsigned long cur_time = jiffies; From 706d4cec4030b001f378ad97c90a7ed0bc3021f4 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:44:58 +0000 Subject: [PATCH 65/82] isert-scst: Convert casts into container_of() where appropriate git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6632 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_datamover.c | 60 ++++++++++++------- iscsi-scst/kernel/isert-scst/iser_pdu.c | 6 +- iscsi-scst/kernel/isert-scst/iser_rdma.c | 4 +- iscsi-scst/kernel/isert-scst/isert.c | 5 +- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser_datamover.c b/iscsi-scst/kernel/isert-scst/iser_datamover.c index fbb119c6d..165ab071b 100644 --- a/iscsi-scst/kernel/isert-scst/iser_datamover.c +++ b/iscsi-scst/kernel/isert-scst/iser_datamover.c @@ -63,7 +63,8 @@ int isert_get_peer_addr(struct iscsi_conn *iscsi_conn, struct sockaddr *sa, size_t *addr_len) { int ret; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct sockaddr *peer_sa = (struct sockaddr *)&isert_conn->peer_addr; ret = isert_get_addr_size(peer_sa, addr_len); @@ -79,7 +80,8 @@ int isert_get_target_addr(struct iscsi_conn *iscsi_conn, struct sockaddr *sa, size_t *addr_len) { int ret; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct sockaddr *self_sa = (struct sockaddr *)&isert_conn->self_addr; ret = isert_get_addr_size(self_sa, addr_len); @@ -106,7 +108,8 @@ int isert_portal_remove(void *portal_h) void isert_free_connection(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_post_drain(isert_conn); isert_conn_free(isert_conn); @@ -114,7 +117,8 @@ void isert_free_connection(struct iscsi_conn *iscsi_conn) struct iscsi_cmnd *isert_alloc_login_rsp_pdu(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct isert_cmnd *isert_pdu = isert_conn->login_rsp_pdu; isert_tx_pdu_init(isert_pdu, isert_conn); @@ -124,7 +128,8 @@ struct iscsi_cmnd *isert_alloc_login_rsp_pdu(struct iscsi_conn *iscsi_conn) static struct iscsi_cmnd *isert_alloc_scsi_pdu(struct iscsi_conn *iscsi_conn, int fake) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct isert_cmnd *isert_pdu; again: @@ -154,8 +159,10 @@ struct iscsi_cmnd *isert_alloc_scsi_fake_pdu(struct iscsi_conn *iscsi_conn) void isert_release_tx_pdu(struct iscsi_cmnd *iscsi_pdu) { - struct isert_cmnd *isert_pdu = (struct isert_cmnd *)iscsi_pdu; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_pdu->conn; + struct isert_cmnd *isert_pdu = container_of(iscsi_pdu, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_pdu->conn, + struct isert_connection, iscsi); isert_tx_pdu_init_iscsi(isert_pdu); @@ -166,7 +173,8 @@ void isert_release_tx_pdu(struct iscsi_cmnd *iscsi_pdu) void isert_release_rx_pdu(struct iscsi_cmnd *iscsi_pdu) { - struct isert_cmnd *isert_pdu = (struct isert_cmnd *)iscsi_pdu; + struct isert_cmnd *isert_pdu = container_of(iscsi_pdu, + struct isert_cmnd, iscsi); isert_rx_pdu_done(isert_pdu); } @@ -174,7 +182,8 @@ void isert_release_rx_pdu(struct iscsi_cmnd *iscsi_pdu) /* if last transition into FF (Fully Featured) state */ int isert_login_rsp_tx(struct iscsi_cmnd *login_rsp, int last, int discovery) { - struct isert_connection *isert_conn = (struct isert_connection *)login_rsp->conn; + struct isert_connection *isert_conn = container_of(login_rsp->conn, + struct isert_connection, iscsi); int err; if (last && !discovery) { @@ -202,7 +211,8 @@ int isert_set_session_params(struct iscsi_conn *iscsi_conn, struct iscsi_sess_params *sess_params, struct iscsi_tgt_params *tgt_params) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn->queue_depth = tgt_params->queued_cmnds; @@ -217,8 +227,10 @@ int isert_set_session_params(struct iscsi_conn *iscsi_conn, int isert_pdu_tx(struct iscsi_cmnd *iscsi_cmnd) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); int err; isert_tx_pdu_convert_from_iscsi(isert_cmnd, iscsi_cmnd); @@ -229,8 +241,10 @@ int isert_pdu_tx(struct iscsi_cmnd *iscsi_cmnd) int isert_request_data_out(struct iscsi_cmnd *iscsi_cmnd) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); int ret; ret = isert_prepare_rdma(isert_cmnd, isert_conn, ISER_WR_RDMA_READ); @@ -245,9 +259,12 @@ int isert_request_data_out(struct iscsi_cmnd *iscsi_cmnd) int isert_send_data_in(struct iscsi_cmnd *iscsi_cmnd, struct iscsi_cmnd *iscsi_rsp) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; - struct isert_cmnd *isert_rsp = (struct isert_cmnd *)iscsi_rsp; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); + struct isert_cmnd *isert_rsp = container_of(iscsi_rsp, + struct isert_cmnd, iscsi); int ret; ret = isert_prepare_rdma(isert_cmnd, isert_conn, ISER_WR_RDMA_WRITE); @@ -262,7 +279,8 @@ int isert_send_data_in(struct iscsi_cmnd *iscsi_cmnd, int isert_close_connection(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn_disconnect(isert_conn); @@ -276,14 +294,16 @@ int isert_task_abort(struct iscsi_cmnd *cmnd) void *isert_get_priv(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); return isert_conn->priv_data; } void isert_set_priv(struct iscsi_conn *iscsi_conn, void *priv) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn->priv_data = priv; } diff --git a/iscsi-scst/kernel/isert-scst/iser_pdu.c b/iscsi-scst/kernel/isert-scst/iser_pdu.c index bf94209c6..b2b490f17 100644 --- a/iscsi-scst/kernel/isert-scst/iser_pdu.c +++ b/iscsi-scst/kernel/isert-scst/iser_pdu.c @@ -479,7 +479,8 @@ clean_pdus: static int isert_reinit_rx_pdu(struct isert_cmnd *pdu) { - struct isert_connection *isert_conn = (struct isert_connection *)pdu->iscsi.conn; + struct isert_connection *isert_conn = container_of(pdu->iscsi.conn, + struct isert_connection, iscsi); pdu->is_rstag_valid = 0; pdu->is_wstag_valid = 0; @@ -492,7 +493,8 @@ static int isert_reinit_rx_pdu(struct isert_cmnd *pdu) int isert_rx_pdu_done(struct isert_cmnd *pdu) { int err; - struct isert_connection *isert_conn = (struct isert_connection *)pdu->iscsi.conn; + struct isert_connection *isert_conn = container_of(pdu->iscsi.conn, + struct isert_connection, iscsi); TRACE_ENTRY(); diff --git a/iscsi-scst/kernel/isert-scst/iser_rdma.c b/iscsi-scst/kernel/isert-scst/iser_rdma.c index 6a1e9279d..8fd02ab4a 100644 --- a/iscsi-scst/kernel/isert-scst/iser_rdma.c +++ b/iscsi-scst/kernel/isert-scst/iser_rdma.c @@ -354,7 +354,9 @@ static void isert_send_completion_handler(struct isert_wr *wr) struct isert_cmnd *isert_pdu = wr->pdu; struct iscsi_cmnd *iscsi_pdu = &isert_pdu->iscsi; struct iscsi_cmnd *iscsi_req_pdu = iscsi_pdu->parent_req; - struct isert_cmnd *isert_req_pdu = (struct isert_cmnd *)iscsi_req_pdu; + struct isert_cmnd *isert_req_pdu = container_of(iscsi_req_pdu, + struct isert_cmnd, iscsi); + TRACE_ENTRY(); diff --git a/iscsi-scst/kernel/isert-scst/isert.c b/iscsi-scst/kernel/isert-scst/isert.c index 3d81e52c6..f16792555 100644 --- a/iscsi-scst/kernel/isert-scst/isert.c +++ b/iscsi-scst/kernel/isert-scst/isert.c @@ -191,6 +191,9 @@ static struct iscsi_cmnd *isert_cmnd_alloc(struct iscsi_conn *conn, static void isert_cmnd_free(struct iscsi_cmnd *cmnd) { + struct isert_cmnd *isert_cmnd = container_of(cmnd, struct isert_cmnd, + iscsi); + TRACE_ENTRY(); #ifdef CONFIG_SCST_EXTRACHECKS @@ -212,7 +215,7 @@ static void isert_cmnd_free(struct iscsi_cmnd *cmnd) sBUG(); } #endif - if (cmnd->parent_req || ((struct isert_cmnd *)cmnd)->is_fake_rx) + if (cmnd->parent_req || isert_cmnd->is_fake_rx) isert_release_tx_pdu(cmnd); else isert_release_rx_pdu(cmnd); From 469fe4a42c834ad73e560a50cdb905fb3d9b164a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 19:51:17 +0000 Subject: [PATCH 66/82] iscsi-scst: Remove superfluous casts The C language does not require to use an explicit cast when assigning a void * pointer to a pointer of another type. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6633 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/conn.c | 2 +- iscsi-scst/kernel/iscsi.c | 12 ++++-------- iscsi-scst/kernel/nthread.c | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/iscsi-scst/kernel/conn.c b/iscsi-scst/kernel/conn.c index bbced03c2..641d1bf79 100644 --- a/iscsi-scst/kernel/conn.c +++ b/iscsi-scst/kernel/conn.c @@ -584,7 +584,7 @@ static void conn_nop_in_delayed_work_fn(struct work_struct *work) #endif { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) - struct iscsi_conn *conn = (struct iscsi_conn *)p; + struct iscsi_conn *conn = p; #else struct iscsi_conn *conn = container_of(work, struct iscsi_conn, nop_in_delayed_work.work); diff --git a/iscsi-scst/kernel/iscsi.c b/iscsi-scst/kernel/iscsi.c index b00deb720..ff3c616f3 100644 --- a/iscsi-scst/kernel/iscsi.c +++ b/iscsi-scst/kernel/iscsi.c @@ -1745,8 +1745,7 @@ out: static int iscsi_pre_exec(struct scst_cmd *scst_cmd) { int res = SCST_PREPROCESS_STATUS_SUCCESS; - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); struct iscsi_cmnd *c, *t; TRACE_ENTRY(); @@ -3376,8 +3375,7 @@ static void iscsi_tcp_preprocessing_done(struct iscsi_cmnd *req) static void iscsi_preprocessing_done(struct scst_cmd *scst_cmd) { - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); req->conn->transport->iscsit_preprocessing_done(req); } @@ -3489,8 +3487,7 @@ static void iscsi_tcp_conn_close(struct iscsi_conn *conn, int flags) static int iscsi_xmit_response(struct scst_cmd *scst_cmd) { int is_send_status = scst_cmd_get_is_send_status(scst_cmd); - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); struct iscsi_conn *conn = req->conn; int status = scst_cmd_get_status(scst_cmd); u8 *sense = scst_cmd_get_sense_buffer(scst_cmd); @@ -3769,8 +3766,7 @@ static inline int iscsi_get_mgmt_response(int status) static void iscsi_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd) { int fn = scst_mgmt_cmd_get_fn(scst_mcmd); - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_mgmt_cmd_get_tgt_priv(scst_mcmd); + struct iscsi_cmnd *req = scst_mgmt_cmd_get_tgt_priv(scst_mcmd); int status = iscsi_get_mgmt_response(scst_mgmt_cmd_get_status(scst_mcmd)); if ((status == ISCSI_RESPONSE_UNKNOWN_TASK) && diff --git a/iscsi-scst/kernel/nthread.c b/iscsi-scst/kernel/nthread.c index 4adfc2ff5..2d6333229 100644 --- a/iscsi-scst/kernel/nthread.c +++ b/iscsi-scst/kernel/nthread.c @@ -334,7 +334,7 @@ void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd) switch (fn) { case SCST_NEXUS_LOSS_SESS: { - struct iscsi_conn *conn = (struct iscsi_conn *)priv; + struct iscsi_conn *conn = priv; struct iscsi_session *sess = conn->session; struct iscsi_conn *c; @@ -584,7 +584,7 @@ static void close_conn(struct iscsi_conn *conn) static int close_conn_thr(void *arg) { - struct iscsi_conn *conn = (struct iscsi_conn *)arg; + struct iscsi_conn *conn = arg; TRACE_ENTRY(); @@ -1145,7 +1145,7 @@ static inline void __iscsi_get_page_callback(struct iscsi_cmnd *cmd) void iscsi_get_page_callback(struct page *page) { - struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv; + struct iscsi_cmnd *cmd = page->net_priv; TRACE_NET_PAGE("page %p, _count %d", page, atomic_read(&page->_count)); @@ -1176,7 +1176,7 @@ static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) void iscsi_put_page_callback(struct page *page) { - struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv; + struct iscsi_cmnd *cmd = page->net_priv; TRACE_NET_PAGE("page %p, _count %d", page, atomic_read(&page->_count)); From 37c4911d4f87230c39522b57fa7ecf28401519af Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 20:08:07 +0000 Subject: [PATCH 67/82] ib_srpt: Use kmalloc_array() instead of kmalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6634 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/src/ib_srpt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index a7be356c1..7ecae5592 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -852,7 +852,7 @@ static struct srpt_ioctx **srpt_alloc_ioctx_ring(struct srpt_device *sdev, WARN_ON(ioctx_size != sizeof(struct srpt_recv_ioctx) && ioctx_size != sizeof(struct srpt_send_ioctx)); - ring = kmalloc(ring_size * sizeof(ring[0]), GFP_KERNEL); + ring = kmalloc_array(ring_size, sizeof(ring[0]), GFP_KERNEL); if (!ring) goto out; for (i = 0; i < ring_size; ++i) { @@ -1154,8 +1154,8 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx, if (ioctx->n_rbuf == 1) ioctx->rbufs = &ioctx->single_rbuf; else { - ioctx->rbufs = - kmalloc(ioctx->n_rbuf * sizeof(*db), GFP_ATOMIC); + ioctx->rbufs = kmalloc_array(ioctx->n_rbuf, + sizeof(*db), GFP_ATOMIC); if (!ioctx->rbufs) { ioctx->n_rbuf = 0; ret = -ENOMEM; From 5554a2a8b182799e38471e9cd7f95aa0d5b2ceab Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 20:08:43 +0000 Subject: [PATCH 68/82] qla2x00t: Use kmalloc_array() instead of kmalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6635 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x00-target/qla2x00t.c | 2 +- qla2x00t/qla_nx.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qla2x00t/qla2x00-target/qla2x00t.c b/qla2x00t/qla2x00-target/qla2x00t.c index 07aff2092..dad9e2711 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.c +++ b/qla2x00t/qla2x00-target/qla2x00t.c @@ -4577,7 +4577,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) first_sg, first_page, first_page_offs, cmd->bufflen, cmd->sg_cnt); - sg = kmalloc(cnt * sizeof(sg[0]), GFP_KERNEL); + sg = kmalloc_array(cnt, sizeof(sg[0]), GFP_KERNEL); if (sg == NULL) { PRINT_ERROR("qla2x00t(%ld): Unable to allocate cut " "SG (len %zd)", cmd->tgt->vha->host_no, diff --git a/qla2x00t/qla_nx.c b/qla2x00t/qla_nx.c index f5b5d837e..6978785de 100644 --- a/qla2x00t/qla_nx.c +++ b/qla2x00t/qla_nx.c @@ -1222,7 +1222,7 @@ qla82xx_pinit_from_rom(scsi_qla_host_t *vha) ql_log(ql_log_info, vha, 0x0072, "%d CRB init values found in ROM.\n", n); - buf = kmalloc(n * sizeof(struct crb_addr_pair), GFP_KERNEL); + buf = kmalloc_array(n, sizeof(struct crb_addr_pair), GFP_KERNEL); if (buf == NULL) { ql_log(ql_log_fatal, vha, 0x010c, "Unable to allocate memory.\n"); From 9455a2b9dc09f39e19f6436cfcf0fa15249a09c6 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 20:09:14 +0000 Subject: [PATCH 69/82] isert-scst: Use kmalloc_array() instead of kmalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6636 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/isert-scst/iser_buf.c | 4 ++-- iscsi-scst/kernel/isert-scst/iser_pdu.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iscsi-scst/kernel/isert-scst/iser_buf.c b/iscsi-scst/kernel/isert-scst/iser_buf.c index 9d6488760..daa78c72a 100644 --- a/iscsi-scst/kernel/isert-scst/iser_buf.c +++ b/iscsi-scst/kernel/isert-scst/iser_buf.c @@ -49,8 +49,8 @@ static int isert_buf_alloc_pg(struct ib_device *ib_dev, struct page *page; isert_buf->sg_cnt = DIV_ROUND_UP(size, PAGE_SIZE); - isert_buf->sg = kmalloc(sizeof(*isert_buf->sg) * isert_buf->sg_cnt, - GFP_KERNEL); + isert_buf->sg = kmalloc_array(isert_buf->sg_cnt, sizeof(*isert_buf->sg), + GFP_KERNEL); if (unlikely(!isert_buf->sg)) { pr_err("Failed to allocate buffer SG\n"); res = -ENOMEM; diff --git a/iscsi-scst/kernel/isert-scst/iser_pdu.c b/iscsi-scst/kernel/isert-scst/iser_pdu.c index b2b490f17..0c6e81b32 100644 --- a/iscsi-scst/kernel/isert-scst/iser_pdu.c +++ b/iscsi-scst/kernel/isert-scst/iser_pdu.c @@ -175,14 +175,14 @@ static int isert_alloc_for_rdma(struct isert_cmnd *pdu, int sge_cnt, int i, ret = 0; int wr_cnt; - sg_pool = kmalloc(sizeof(*sg_pool) * sge_cnt, GFP_KERNEL); + sg_pool = kmalloc_array(sge_cnt, sizeof(*sg_pool), GFP_KERNEL); if (unlikely(sg_pool == NULL)) { ret = -ENOMEM; goto out; } wr_cnt = DIV_ROUND_UP(sge_cnt, isert_conn->max_sge); - wr = kmalloc(sizeof(*wr) * wr_cnt, GFP_KERNEL); + wr = kmalloc_array(wr_cnt, sizeof(*wr), GFP_KERNEL); if (unlikely(wr == NULL)) { ret = -ENOMEM; goto out_free_sg_pool; From 8d4c429d7948e688520453b1936400edc5063f91 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 20:09:48 +0000 Subject: [PATCH 70/82] scst: Use kmalloc_array() instead of kmalloc() with multiply git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6637 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_user.c | 9 +++++---- scst/src/dev_handlers/scst_vdisk.c | 5 +++-- scst/src/scst_lib.c | 2 +- scst/src/scst_mem.c | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index 3b90d38d1..74364ad51 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -1247,8 +1247,9 @@ static int dev_user_map_buf(struct scst_user_cmd *ucmd, unsigned long ubuff, ucmd->num_data_pages = num_pg; - ucmd->data_pages = kmalloc(sizeof(*ucmd->data_pages) * ucmd->num_data_pages, - GFP_KERNEL); + ucmd->data_pages = kmalloc_array(ucmd->num_data_pages, + sizeof(*ucmd->data_pages), + GFP_KERNEL); if (ucmd->data_pages == NULL) { TRACE(TRACE_OUT_OF_MEM, "Unable to allocate data_pages array " "(num_data_pages=%d)", ucmd->num_data_pages); @@ -1535,7 +1536,7 @@ static int dev_user_process_reply_ext_copy_remap(struct scst_user_cmd *ucmd, goto out_hw_err_free_buf; } - left = kmalloc(sizeof(*left) * count, GFP_KERNEL); + left = kmalloc_array(count, sizeof(*left), GFP_KERNEL); if (unlikely(left == NULL)) { PRINT_ERROR("Unable to alloc leftover remap descriptors " "(size %zd, count %d)", sizeof(*left) * count, count); @@ -1691,7 +1692,7 @@ static int dev_user_process_ws_reply(struct scst_user_cmd *ucmd, goto out_free_buf; } - where = kmalloc(sizeof(*where) * count, GFP_KERNEL); + where = kmalloc_array(count, sizeof(*where), GFP_KERNEL); if (unlikely(where == NULL)) { PRINT_ERROR("Unable to alloc WS descriptors where (size %zd)", sizeof(*where) * count); diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index b55fb9bcd..f3788afa0 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -3323,7 +3323,7 @@ static struct scatterlist *alloc_sg(size_t size, unsigned off, gfp_t gfp_mask, sg_cnt = PAGE_ALIGN(size + off) >> PAGE_SHIFT; sg = sg_cnt <= small_sg_size ? small_sg : - kmalloc(sg_cnt * sizeof(*sg), gfp_mask); + kmalloc_array(sg_cnt, sizeof(*sg), gfp_mask); if (!sg) goto out; @@ -5710,7 +5710,8 @@ static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd, p->iv_count = 0; /* It can't be called in atomic context */ p->iv = (iv_count <= ARRAY_SIZE(p->small_iv)) ? p->small_iv : - kmalloc(sizeof(*p->iv) * iv_count, cmd->cmd_gfp_mask); + kmalloc_array(iv_count, sizeof(*p->iv), + cmd->cmd_gfp_mask); if (p->iv == NULL) { PRINT_ERROR("Unable to allocate iv (%d)", iv_count); goto out; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 7c626aa65..316aaf18a 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -6089,7 +6089,7 @@ static int scst_ws_sg_init(struct scatterlist **ws_sg, int ws_sg_cnt, struct scatterlist *sg; int i; - *ws_sg = kmalloc(ws_sg_cnt * sizeof(**ws_sg), GFP_KERNEL); + *ws_sg = kmalloc_array(ws_sg_cnt, sizeof(**ws_sg), GFP_KERNEL); if (*ws_sg == NULL) { PRINT_ERROR("Unable to alloc sg for %d entries", ws_sg_cnt); return -ENOMEM; diff --git a/scst/src/scst_mem.c b/scst/src/scst_mem.c index 488132c07..9800d1a65 100644 --- a/scst/src/scst_mem.c +++ b/scst/src/scst_mem.c @@ -1332,7 +1332,7 @@ struct scatterlist *scst_alloc_sg(int size, gfp_t gfp_mask, int *count) } } - res = kmalloc(pages*sizeof(*res), gfp_mask); + res = kmalloc_array(pages, sizeof(*res), gfp_mask); if (res == NULL) { TRACE(TRACE_OUT_OF_MEM, "Unable to allocate sg for %d pages", pages); From b8872139338c835e9876b826a7d3b6cbe8e7a403 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 20:55:46 +0000 Subject: [PATCH 71/82] scst: Add a kmalloc_array() backport git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6638 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/backport.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scst/include/backport.h b/scst/include/backport.h index d8ce44ef3..53a2f55ab 100644 --- a/scst/include/backport.h +++ b/scst/include/backport.h @@ -401,6 +401,15 @@ static inline void sg_set_page(struct scatterlist *sg, struct page *page, (__flags), NULL, NULL) #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +static inline void *kmalloc_array(size_t n, size_t size, gfp_t flags) +{ + if (size != 0 && n > ULONG_MAX / size) + return NULL; + return kmalloc(n * size, flags); +} +#endif + /* */ #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) From 3d57bb3d162a1fb0f464594beb0cd1c50481ec9e Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 21:03:54 +0000 Subject: [PATCH 72/82] qla2x00t: Use __packed instead of __attribute__((packed)) git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6639 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla2x_tgt_def.h | 48 ++++++++++++++++++++-------------------- qla2x00t/qla_bsg.h | 8 +++---- qla2x00t/qla_def.h | 6 ++--- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/qla2x00t/qla2x_tgt_def.h b/qla2x00t/qla2x_tgt_def.h index 0957ecb31..d96d6f1b5 100644 --- a/qla2x00t/qla2x_tgt_def.h +++ b/qla2x00t/qla2x_tgt_def.h @@ -148,7 +148,7 @@ typedef struct { uint16_t reserved_5; uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */ uint16_t reserved_6[20]; -} __attribute__((packed)) elun_entry_t; +} __packed elun_entry_t; #define ENABLE_LUN_SUCCESS 0x01 #define ENABLE_LUN_RC_NONZERO 0x04 #define ENABLE_LUN_INVALID_REQUEST 0x06 @@ -179,7 +179,7 @@ typedef struct { uint16_t reserved_5; uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */ uint16_t reserved_7[20]; -} __attribute__((packed)) modify_lun_entry_t; +} __packed modify_lun_entry_t; #define MODIFY_LUN_SUCCESS 0x01 #define MODIFY_LUN_CMD_ADD BIT_0 #define MODIFY_LUN_CMD_SUB BIT_1 @@ -219,7 +219,7 @@ typedef struct { uint16_t srr_ox_id; uint8_t reserved_2[30]; uint16_t ox_id; -} __attribute__((packed)) notify_entry_t; +} __packed notify_entry_t; #endif #ifndef NOTIFY_ACK_TYPE @@ -250,7 +250,7 @@ typedef struct { uint8_t srr_reject_code_expl; uint8_t reserved_2[26]; uint16_t ox_id; -} __attribute__((packed)) nack_entry_t; +} __packed nack_entry_t; #define NOTIFY_ACK_SRR_FLAGS_ACCEPT 0 #define NOTIFY_ACK_SRR_FLAGS_REJECT 1 @@ -287,7 +287,7 @@ typedef struct { uint8_t initiator_port_name[WWN_SIZE]; /* on qla23xx */ uint16_t reserved_32[6]; uint16_t ox_id; -} __attribute__((packed)) atio_entry_t; +} __packed atio_entry_t; #endif #ifndef CONTINUE_TGT_IO_TYPE @@ -314,7 +314,7 @@ typedef struct { uint16_t scsi_status; uint32_t transfer_length; uint32_t dseg_0_address[0]; -} __attribute__((packed)) ctio_common_entry_t; +} __packed ctio_common_entry_t; #define ATIO_PATH_INVALID 0x07 #define ATIO_CANT_PROV_CAP 0x16 #define ATIO_CDB_VALID 0x3D @@ -333,7 +333,7 @@ typedef struct { uint32_t dseg_1_length; /* Data segment 1 length. */ uint32_t dseg_2_address; /* Data segment 2 address. */ uint32_t dseg_2_length; /* Data segment 2 length. */ -} __attribute__((packed)) ctio_entry_t; +} __packed ctio_entry_t; #define CTIO_SUCCESS 0x01 #define CTIO_ABORTED 0x02 #define CTIO_INVALID_RX_ID 0x08 @@ -371,7 +371,7 @@ typedef struct { uint16_t scsi_status; uint16_t response_length; uint8_t sense_data[26]; -} __attribute__((packed)) ctio_ret_entry_t; +} __packed ctio_ret_entry_t; #endif #define ATIO_TYPE7 0x06 /* Accept target I/O entry for 24xx */ @@ -389,7 +389,7 @@ typedef struct { uint16_t ox_id; uint16_t rx_id; uint32_t parameter; -} __attribute__((packed)) fcp_hdr_t; +} __packed fcp_hdr_t; typedef struct { uint8_t d_id[3]; @@ -404,7 +404,7 @@ typedef struct { uint16_t rx_id; uint16_t ox_id; uint32_t parameter; -} __attribute__((packed)) fcp_hdr_le_t; +} __packed fcp_hdr_le_t; #define F_CTL_EXCH_CONTEXT_RESP BIT_23 #define F_CTL_SEQ_CONTEXT_RESIP BIT_22 @@ -449,7 +449,7 @@ typedef struct { */ uint8_t add_cdb[4]; /* uint32_t data_length; */ -} __attribute__((packed)) fcp_cmnd_t; +} __packed fcp_cmnd_t; /* * ISP queue - Accept Target I/O (ATIO) type 7 entry for 24xx structure @@ -470,7 +470,7 @@ typedef struct { #define ATIO_EXCHANGE_ADDRESS_UNKNOWN 0xFFFFFFFF fcp_hdr_t fcp_hdr; fcp_cmnd_t fcp_cmnd; -} __attribute__((packed)) atio7_entry_t; +} __packed atio7_entry_t; #define CTIO_TYPE7 0x12 /* Continue target I/O entry (for 24xx) */ @@ -494,7 +494,7 @@ typedef struct { uint8_t initiator_id[3]; uint8_t reserved; uint32_t exchange_addr; -} __attribute__((packed)) ctio7_common_entry_t; +} __packed ctio7_common_entry_t; typedef struct { ctio7_common_entry_t common; @@ -509,7 +509,7 @@ typedef struct { uint32_t reserved3; uint32_t dseg_0_address[2]; /* Data segment 0 address. */ uint32_t dseg_0_length; /* Data segment 0 length. */ -} __attribute__((packed)) ctio7_status0_entry_t; +} __packed ctio7_status0_entry_t; typedef struct { ctio7_common_entry_t common; @@ -521,7 +521,7 @@ typedef struct { uint16_t response_len; uint16_t reserved; uint8_t sense_data[24]; -} __attribute__((packed)) ctio7_status1_entry_t; +} __packed ctio7_status1_entry_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -542,7 +542,7 @@ typedef struct { uint16_t reserved3; uint32_t relative_offset; uint8_t reserved4[24]; -} __attribute__((packed)) ctio7_fw_entry_t; +} __packed ctio7_fw_entry_t; /* CTIO7 flags values */ #define CTIO7_FLAGS_SEND_STATUS BIT_15 @@ -589,7 +589,7 @@ typedef struct { uint8_t reserved_7; uint16_t reserved_8; uint16_t ox_id; -} __attribute__((packed)) notify24xx_entry_t; +} __packed notify24xx_entry_t; #define ELS_PLOGI 0x3 #define ELS_FLOGI 0x4 @@ -627,7 +627,7 @@ typedef struct { uint8_t srr_reject_code; uint8_t reserved_5[7]; uint16_t ox_id; -} __attribute__((packed)) nack24xx_entry_t; +} __packed nack24xx_entry_t; /* * ISP queue - ABTS received/response entries structure definition for 24xx. @@ -655,7 +655,7 @@ typedef struct { fcp_hdr_le_t fcp_hdr_le; uint8_t reserved_4[16]; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_recv_entry_t; +} __packed abts24_recv_entry_t; #define ABTS_PARAM_ABORT_SEQ BIT_0 @@ -669,7 +669,7 @@ typedef struct { uint16_t ox_id; uint16_t high_seq_cnt; uint16_t low_seq_cnt; -} __attribute__((packed)) ba_acc_le_t; +} __packed ba_acc_le_t; typedef struct { uint8_t vendor_uniq; @@ -678,7 +678,7 @@ typedef struct { #define BA_RJT_REASON_CODE_INVALID_COMMAND 0x1 #define BA_RJT_REASON_CODE_UNABLE_TO_PERFORM 0x9 uint8_t reserved; -} __attribute__((packed)) ba_rjt_le_t; +} __packed ba_rjt_le_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -703,10 +703,10 @@ typedef struct { union { ba_acc_le_t ba_acct; ba_rjt_le_t ba_rjt; - } __attribute__((packed)) payload; + } __packed payload; uint32_t reserved_4; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_resp_entry_t; +} __packed abts24_resp_entry_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -734,7 +734,7 @@ typedef struct { #define ABTS_RESP_SUBCODE_ERR_ABORTED_EXCH_NOT_TERM 0x1E uint32_t error_subcode2; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_resp_fw_entry_t; +} __packed abts24_resp_fw_entry_t; /********************************************************************\ * Type Definitions used by initiator & target halves diff --git a/qla2x00t/qla_bsg.h b/qla2x00t/qla_bsg.h index 70caa63a8..1128cf0d1 100644 --- a/qla2x00t/qla_bsg.h +++ b/qla2x00t/qla_bsg.h @@ -126,12 +126,12 @@ struct qla84_msg_mgmt { struct qla_bsg_a84_mgmt { struct qla84_msg_mgmt mgmt; -} __attribute__ ((packed)); +} __packed; struct qla_scsi_addr { uint16_t bus; uint16_t target; -} __attribute__ ((packed)); +} __packed; struct qla_ext_dest_addr { union { @@ -144,13 +144,13 @@ struct qla_ext_dest_addr { #define EXT_DEF_TYPE_WWPN 2 uint16_t lun; uint16_t padding[2]; -} __attribute__ ((packed)); +} __packed; struct qla_port_param { struct qla_ext_dest_addr fc_scsi_addr; uint16_t mode; uint16_t speed; -} __attribute__ ((packed)); +} __packed; /* FRU VPD */ diff --git a/qla2x00t/qla_def.h b/qla2x00t/qla_def.h index 3f2dcb32d..83944e879 100644 --- a/qla2x00t/qla_def.h +++ b/qla2x00t/qla_def.h @@ -361,7 +361,7 @@ struct device_reg_2xxx { uint16_t mailbox6; uint16_t mailbox7; uint16_t unused_2[59]; /* Gap */ - } __attribute__((packed)) isp2100; + } __packed isp2100; struct { /* Request Queue */ uint16_t req_q_in; /* In-Pointer */ @@ -412,7 +412,7 @@ struct device_reg_2xxx { uint16_t mailbox31; uint16_t fb_cmd; uint16_t unused_4[10]; /* Gap */ - } __attribute__((packed)) isp2300; + } __packed isp2300; } u; uint16_t fpm_diag_config; @@ -469,7 +469,7 @@ struct device_reg_2xxx { uint16_t mailbox21; uint16_t mailbox22; uint16_t mailbox23; /* Also probe reg. */ - } __attribute__((packed)) isp2200; + } __packed isp2200; } u_end; }; From f4bd4294a55b2a0e897148bf25a32e189abda38a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 21:19:42 +0000 Subject: [PATCH 73/82] scst_vdisk: Make expl_alua sysfs attribute input validation more strict git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6640 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/backport.h | 26 ++++++++++++++++++++++++++ scst/src/dev_handlers/scst_vdisk.c | 9 ++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/scst/include/backport.h b/scst/include/backport.h index 53a2f55ab..f349bb80a 100644 --- a/scst/include/backport.h +++ b/scst/include/backport.h @@ -255,6 +255,32 @@ static inline void hex2bin(u8 *dst, const char *src, size_t count) } #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) +static inline int __must_check kstrtoull(const char *s, unsigned int base, + unsigned long long *res) +{ + return strict_strtoull(s, base, res); +} + +static inline int __must_check kstrtoll(const char *s, unsigned int base, + long long *res) +{ + return strict_strtoll(s, base, res); +} + +static inline int __must_check kstrtoul(const char *s, unsigned int base, + unsigned long *res) +{ + return strict_strtoul(s, base, res); +} + +static inline int __must_check kstrtol(const char *s, unsigned int base, + long *res) +{ + return strict_strtol(s, base, res); +} +#endif + /* */ #ifndef __list_for_each diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index f3788afa0..952b73d33 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -8823,7 +8823,7 @@ static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, struct scst_device *dev; struct scst_vdisk_dev *virt_dev; char ch[16]; - bool expl_alua; + unsigned long expl_alua; int res; TRACE_ENTRY(); @@ -8831,14 +8831,17 @@ static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); virt_dev = dev->dh_priv; sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); - expl_alua = !!simple_strtoul(ch, NULL, 0); + res = kstrtoul(ch, NULL, &expl_alua); + if (res < 0) + goto out; spin_lock(&virt_dev->flags_lock); - virt_dev->expl_alua = expl_alua; + virt_dev->expl_alua = !!expl_alua; spin_unlock(&virt_dev->flags_lock); res = count; +out: TRACE_EXIT_RES(res); return res; } From fecfbad5b7cf78c20aae91a2b4060e43da6ae515 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 21:30:36 +0000 Subject: [PATCH 74/82] scst, scst_local: Remove strict_strto*() calls git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6641 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 18 +------- scst/src/scst_copy_mgr.c | 4 -- scst/src/scst_lib.c | 4 -- scst/src/scst_sysfs.c | 66 +++--------------------------- scst_local/scst_local.c | 8 ---- 5 files changed, 6 insertions(+), 94 deletions(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 952b73d33..15b199e4b 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -1298,11 +1298,7 @@ static int __vdev_load_mode_pages(struct scst_vdisk_dev *virt_dev, char *params) if (scst_get_next_lexem(¶m)[0] != '\0') goto out_too_many; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(pp, 0, &val); -#else - res = strict_strtoul(pp, 0, &val); -#endif if (res != 0) goto out_strtoul_failed; @@ -7884,11 +7880,7 @@ static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev, continue; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoull(pp, 0, &val); -#else - res = strict_strtoull(pp, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoull() for %s failed: %d (device %s)", pp, res, virt_dev->name); @@ -8831,7 +8823,7 @@ static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); virt_dev = dev->dh_priv; sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); - res = kstrtoul(ch, NULL, &expl_alua); + res = kstrtoul(ch, 0, &expl_alua); if (res < 0) goto out; @@ -8921,11 +8913,7 @@ static ssize_t vdev_sysfs_rz_store(struct kobject *kobj, char ch[16]; sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(ch, 0, &read_zero); -#else - res = strict_strtol(ch, 0, &read_zero); -#endif if (res) goto out; res = -EINVAL; @@ -9140,11 +9128,7 @@ static int vdev_sysfs_process_cluster_mode_store( * have been dropped before invoking .detach(). */ virt_dev = dev->dh_priv; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(work->buf, 0, &clm); -#else - res = strict_strtol(work->buf, 0, &clm); -#endif if (res) goto unlock; res = -EINVAL; diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index e00b38d71..7889413eb 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -3425,11 +3425,7 @@ static ssize_t scst_cm_allow_not_conn_copy_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buffer, 0, &val); -#else - res = strict_strtoul(buffer, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 316aaf18a..849bdadcf 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -14021,11 +14021,7 @@ int scst_restore_global_mode_pages(struct scst_device *dev, char *params, if (scst_get_next_lexem(¶m)[0] != '\0') goto out_too_many; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(pp, 0, &val); -#else - res = strict_strtoul(pp, 0, &val); -#endif if (res != 0) goto out_strtoul_failed; diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 1538066c4..40e06743d 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -272,11 +272,7 @@ static int scst_write_trace(const char *buf, size_t length, } break; case SCST_TRACE_ACTION_VALUE: -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(p, 0, &level); -#else - res = strict_strtoul(p, 0, &level); -#endif if (res != 0) { PRINT_ERROR("Invalid trace value \"%s\"", p); res = -EINVAL; @@ -1333,11 +1329,7 @@ static int __scst_process_luns_mgmt_store(char *buffer, bool dev_replaced = false; e = scst_get_next_lexem(&pp); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(e, 0, &virt_lun); -#else - res = strict_strtoul(e, 0, &virt_lun); -#endif if (res != 0) { PRINT_ERROR("Valid LUN required for dev %s (res %d)", p, res); goto out_unlock; @@ -1378,13 +1370,9 @@ static int __scst_process_luns_mgmt_store(char *buffer, goto out_unlock; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(pp, 0, &val); -#else - res = strict_strtoul(pp, 0, &val); -#endif if (res != 0) { - PRINT_ERROR("strict_strtoul() for %s failed: %d " + PRINT_ERROR("kstrtoul() for %s failed: %d " "(device %s)", pp, res, dev->virt_name); goto out_unlock; } @@ -1451,11 +1439,7 @@ static int __scst_process_luns_mgmt_store(char *buffer, } case SCST_LUN_ACTION_DEL: p = scst_get_next_lexem(&pp); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(p, 0, &virt_lun); -#else - res = strict_strtoul(p, 0, &virt_lun); -#endif if (res != 0) goto out_unlock; @@ -1767,11 +1751,7 @@ static ssize_t __scst_acg_io_grouping_type_store(struct scst_acg *acg, min_t(int, strlen(SCST_IO_GROUPING_NEVER_STR), count)) == 0) io_grouping_type = SCST_IO_GROUPING_NEVER; else { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &io_grouping_type); -#else - res = strict_strtol(buf, 0, &io_grouping_type); -#endif if ((res != 0) || (io_grouping_type <= 0)) { PRINT_ERROR("Unknown or not allowed I/O grouping type " "%s", buf); @@ -2477,11 +2457,7 @@ static ssize_t scst_rel_tgt_id_store(struct kobject *kobj, tgt = container_of(kobj, struct scst_tgt, tgt_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &rel_tgt_id); -#else - res = strict_strtoul(buf, 0, &rel_tgt_id); -#endif if (res != 0) { PRINT_ERROR("%s", "Wrong rel_tgt_id"); res = -EINVAL; @@ -3347,13 +3323,9 @@ static ssize_t scst_dev_sysfs_threads_num_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &newtn); -#else - res = strict_strtol(buf, 0, &newtn); -#endif if (res != 0) { - PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn < 0) { @@ -3908,11 +3880,7 @@ static ssize_t scst_dev_sysfs_dif_static_app_tag_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoull(buf, 0, &val); -#else - res = strict_strtoull(buf, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %d (device %s)", buf, res, dev->virt_name); @@ -6311,11 +6279,7 @@ static ssize_t scst_tg_tgt_rel_tgt_id_store(struct kobject *kobj, 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); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(ch, 0, &rel_tgt_id); -#else - res = strict_strtoul(ch, 0, &rel_tgt_id); -#endif if (res) goto out; res = -EINVAL; @@ -6405,11 +6369,7 @@ static ssize_t scst_tg_group_id_store(struct kobject *kobj, TRACE_ENTRY(); tg = container_of(kobj, struct scst_target_group, kobj); snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(ch, 0, &group_id); -#else - res = strict_strtoul(ch, 0, &group_id); -#endif if (res) goto out; res = -EINVAL; @@ -6447,11 +6407,7 @@ static int scst_tg_preferred_store_work_fn(struct scst_sysfs_work_item *w) TRACE_ENTRY(); cmd = w->buf; tg = container_of(w->kobj, struct scst_target_group, kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(cmd, 0, &preferred); -#else - res = strict_strtoul(cmd, 0, &preferred); -#endif if (res) goto out; res = -EINVAL; @@ -6985,13 +6941,9 @@ static ssize_t scst_threads_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &newtn); -#else - res = strict_strtol(buf, 0, &newtn); -#endif if (res != 0) { - PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn <= 0) { @@ -7041,13 +6993,9 @@ static ssize_t scst_setup_id_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &val); -#else - res = strict_strtoul(buf, 0, &val); -#endif if (res != 0) { - PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } @@ -7088,13 +7036,9 @@ static ssize_t scst_max_tasklet_cmd_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &val); -#else - res = strict_strtoul(buf, 0, &val); -#endif if (res != 0) { - PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } diff --git a/scst_local/scst_local.c b/scst_local/scst_local.c index f25a127e0..cf1cd8e31 100644 --- a/scst_local/scst_local.c +++ b/scst_local/scst_local.c @@ -446,11 +446,7 @@ static ssize_t scst_local_scsi_transport_version_store(struct kobject *kobj, if (!tgt) goto out_up; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buffer, 0, &val); -#else - res = strict_strtoul(buffer, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; @@ -516,11 +512,7 @@ static ssize_t scst_local_phys_transport_version_store(struct kobject *kobj, if (!tgt) goto out_up; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buffer, 0, &val); -#else - res = strict_strtoul(buffer, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; From b739464b4c3c82a73b89a2f65c99eae7a7aec519 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 9 Nov 2015 21:37:33 +0000 Subject: [PATCH 75/82] scst_sysfs: Fix indentation git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6642 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_sysfs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 40e06743d..7e12e175f 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -7495,10 +7495,9 @@ int scst_wait_info_completion(struct scst_sysfs_user_info *info, break; } } else if (rc != -ERESTARTSYS) { - res = rc; - PRINT_ERROR("wait_for_completion() failed: %d", - res); - goto out; + res = rc; + PRINT_ERROR("wait_for_completion() failed: %d", res); + goto out; } else { TRACE_DBG("Waiting for info %p finished with %d, " "retrying", info, rc); From 3326253e149a6942714771bdb7c0cb0db413815a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 00:20:33 +0000 Subject: [PATCH 76/82] scst_copy_mgr: Introduce a symbol for the MAXIMUM DESCRIPTOR LIST LENGTH git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6643 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_copy_mgr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index 7889413eb..dabbbd603 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -35,6 +35,9 @@ /* Too big value is not too good for the blocking machinery */ #define SCST_CM_MAX_TGT_DESCR_CNT 5 +/* MAXIMUM DESCRIPTOR LIST LENGTH */ +#define SCST_MAX_SEG_DESC_LEN 0xFFFF + static struct scst_tgt *scst_cm_tgt; static struct scst_session *scst_cm_sess; @@ -2214,7 +2217,7 @@ static void scst_cm_oper_parameters(struct scst_cmd *cmd) sizeof(struct scst_ext_copy_seg_descr), &tbuf[10]); /* MAXIMUM DESCRIPTOR LIST LENGTH */ - put_unaligned_be32(0xFFFF, &tbuf[12]); + put_unaligned_be32(SCST_MAX_SEG_DESC_LEN, &tbuf[12]); /* MAXIMUM SEGMENT LENGTH: 256MB */ put_unaligned_be32(256*1024*1024, &tbuf[16]); From 6506586af8517c0042f236cbe9e4e53064f65231 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 00:27:33 +0000 Subject: [PATCH 77/82] scst_copy_mgr: Fix a spelling error git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6644 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/scst_copy_mgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index dabbbd603..517bbe5be 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -3210,7 +3210,7 @@ int scst_cm_parse_descriptors(struct scst_cmd *ec_cmd) if (seg_len == 0) goto out_del_put; else { - PRINT_WARNING("Zero target descriptors with none zero " + PRINT_WARNING("Zero target descriptors with non-zero " "segments len (%d)", seg_len); scst_set_invalid_field_in_parm_list(ec_cmd, 2, 0); goto out_del_abn_put; From 7d17a328eb77548f4d0f140bfb6549737073a6c9 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 01:07:26 +0000 Subject: [PATCH 78/82] scst_copy_mgr: Verify maximum number of segments git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6645 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst_const.h | 1 + scst/src/scst_copy_mgr.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 6dc9597b0..b0a2fc51b 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -319,6 +319,7 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define scst_sense_invalid_release ILLEGAL_REQUEST, 0x26, 4 #define scst_sense_too_many_target_descriptors ILLEGAL_REQUEST, 0x26, 6 #define scst_sense_unsupported_tgt_descr_type ILLEGAL_REQUEST, 0x26, 7 +#define scst_sense_too_many_segment_descriptors ILLEGAL_REQUEST, 0x26, 8 #define scst_sense_unsupported_seg_descr_type ILLEGAL_REQUEST, 0x26, 9 #define scst_sense_inline_data_length_exceeded ILLEGAL_REQUEST, 0x26, 0xB #define scst_sense_saving_params_unsup ILLEGAL_REQUEST, 0x39, 0 diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c index 517bbe5be..8b0ffb2f3 100644 --- a/scst/src/scst_copy_mgr.c +++ b/scst/src/scst_copy_mgr.c @@ -35,6 +35,10 @@ /* Too big value is not too good for the blocking machinery */ #define SCST_CM_MAX_TGT_DESCR_CNT 5 +#define SCST_CM_MAX_SEG_DESCR_CNT \ + (((PAGE_SIZE * 2) - sizeof(struct scst_cm_ec_cmd_priv)) / \ + sizeof(struct scst_ext_copy_seg_descr)) + /* MAXIMUM DESCRIPTOR LIST LENGTH */ #define SCST_MAX_SEG_DESC_LEN 0xFFFF @@ -2213,8 +2217,7 @@ static void scst_cm_oper_parameters(struct scst_cmd *cmd) put_unaligned_be16(SCST_CM_MAX_TGT_DESCR_CNT, &tbuf[8]); /* MAXIMUM SEGMENT DESCRIPTOR COUNT */ - put_unaligned_be16(((PAGE_SIZE * 2) - sizeof(struct scst_cm_ec_cmd_priv)) / - sizeof(struct scst_ext_copy_seg_descr), &tbuf[10]); + put_unaligned_be16(SCST_CM_MAX_SEG_DESCR_CNT, &tbuf[10]); /* MAXIMUM DESCRIPTOR LIST LENGTH */ put_unaligned_be32(SCST_MAX_SEG_DESC_LEN, &tbuf[12]); @@ -3280,6 +3283,13 @@ int scst_cm_parse_descriptors(struct scst_cmd *ec_cmd) t = offs; seg_cnt = 0; while (offs < length) { + if (seg_cnt == SCST_CM_MAX_SEG_DESCR_CNT) { + PRINT_WARNING("Too many segment descriptors"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE( + scst_sense_too_many_segment_descriptors)); + goto out_free_tgt_descr; + } switch (buf[offs]) { case 2: /* block device to block device segment descriptor */ offs += 28; From 948482551cde933433629a84916c839a533956a4 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 01:55:23 +0000 Subject: [PATCH 79/82] ib_srpt: Follow-up for r6628 git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6646 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/src/ib_srpt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 7ecae5592..8a8e09389 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -4173,7 +4173,7 @@ static void srpt_add_one(struct ib_device *device) #endif sdev->srq = use_srq ? ib_create_srq(sdev->pd, &srq_attr) : - ERR_PTR(-EOPNOTSUPP); + ERR_PTR(-ENOTSUPP); if (IS_ERR(sdev->srq)) { pr_debug("ib_create_srq() failed: %ld\n", PTR_ERR(sdev->srq)); From f606f9ef04549b2c13d9d88bb8fa818406895205 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 01:59:28 +0000 Subject: [PATCH 80/82] scst.spec: Update changelog git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6647 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst.spec.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scst.spec.in b/scst.spec.in index bf73f49a1..e7138ceef 100644 --- a/scst.spec.in +++ b/scst.spec.in @@ -201,6 +201,10 @@ rm -rf /usr/local/include/scst /usr/include/scst/scst_user.h %changelog +* Wed May 6 2015 Bart Van Assche +- Build in release mode instead of debug mode. +* Mon May 4 2015 Bart Van Assche +- Added iSER target driver. * Mon Feb 23 2015 Bart Van Assche - Split spec file into a non-DKMS and a DKMS spec file. * Fri Jan 16 2015 Bart Van Assche From 99d47d5ce2540418e88eec439a4a4d0e50efada5 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 02:01:57 +0000 Subject: [PATCH 81/82] scst, qla2x00t: Return ENOTSUPP instead of ENOSYS ENOSYS must only be used inside system calls. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6648 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- qla2x00t/qla_bsg.c | 2 +- scst/src/dev_handlers/scst_vdisk.c | 2 +- scst/src/scst_proc.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qla2x00t/qla_bsg.c b/qla2x00t/qla_bsg.c index 1b5922dfc..8d9b8244b 100644 --- a/qla2x00t/qla_bsg.c +++ b/qla2x00t/qla_bsg.c @@ -1594,7 +1594,7 @@ qla2x00_process_vendor_specific(struct fc_bsg_job *bsg_job) default: bsg_job->reply->result = (DID_ERROR << 16); bsg_job->job_done(bsg_job); - return -ENOSYS; + return -ENOTSUPP; } } diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 15b199e4b..5d471d4cf 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -7612,7 +7612,7 @@ static int vdisk_create_bioset(struct scst_vdisk_dev *virt_dev) /* The same, pool size doesn't really matter */ res = bioset_integrity_create(virt_dev->vdisk_bioset, 2); #else - res = -ENOSYS; + res = -ENOTSUPP; #endif if (res != 0) { PRINT_ERROR("Failed to create integrity bioset " diff --git a/scst/src/scst_proc.c b/scst/src/scst_proc.c index a7f2169b5..1d4dc3c25 100644 --- a/scst/src/scst_proc.c +++ b/scst/src/scst_proc.c @@ -1399,7 +1399,7 @@ static ssize_t scst_proc_scsi_tgt_write(struct file *file, TRACE_ENTRY(); if (vtt->tgtt->write_proc == NULL) { - res = -ENOSYS; + res = -ENOTSUPP; goto out; } @@ -1554,7 +1554,7 @@ static ssize_t scst_proc_scsi_dev_handler_write(struct file *file, TRACE_ENTRY(); if (dev_type->write_proc == NULL) { - res = -ENOSYS; + res = -ENOTSUPP; goto out; } From 5b92182215916291362da851c239eabbde264ce8 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 10 Nov 2015 02:16:20 +0000 Subject: [PATCH 82/82] scst_vdisk: Allow cluster_mode to be set during device creation git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6649 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/src/dev_handlers/scst_vdisk.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index 5d471d4cf..b53a2b312 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -196,6 +196,8 @@ struct scst_vdisk_dev { char *filename; uint16_t command_set_version; + unsigned int initial_cluster_mode:1; + /* All 14 protected by vdisk_serial_rwlock */ unsigned int t10_vend_id_set:1; /* true if t10_vend_id manually set */ /* true if vend_specific_id manually set */ @@ -1568,6 +1570,7 @@ static int vdisk_attach(struct scst_device *dev) dev->block_shift = virt_dev->blk_shift; dev->block_size = 1 << dev->block_shift; + dev->cluster_mode = virt_dev->initial_cluster_mode; if ((virt_dev->dif_type == 0) && ((virt_dev->dif_mode != SCST_DIF_MODE_NONE) || @@ -7938,6 +7941,10 @@ static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev, virt_dev->file_size = val; } else if (!strcasecmp("size_mb", p)) { virt_dev->file_size = val * 1024 * 1024; + } else if (!strcasecmp("cluster_mode", p)) { + virt_dev->initial_cluster_mode = val; + TRACE_DBG("CLUSTER_MODE %d", + virt_dev->initial_cluster_mode); } else if (!strcasecmp("blocksize", p)) { virt_dev->blk_shift = scst_calc_block_shift(val); if (virt_dev->blk_shift < 9) { @@ -8039,7 +8046,8 @@ static int vdev_blockio_add_device(const char *device_name, char *params) int res = 0; const char *const allowed_params[] = { "filename", "read_only", "write_through", "removable", "blocksize", "nv_cache", - "rotational", "thin_provisioned", "tst", + "rotational", "cluster_mode", + "thin_provisioned", "tst", "dif_mode", "dif_type", "dif_static_app_tag", "dif_filename", NULL }; struct scst_vdisk_dev *virt_dev;