From 906f4dfefb2e248801e58942ea28b242689a8cc4 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 1 May 2015 04:21:56 +0000 Subject: [PATCH] Merge t10-pi branch back git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6192 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- Makefile | 28 + fcst/ft_cmd.c | 3 +- iscsi-scst/kernel/config.c | 6 +- qla2x00t/qla_isr.c | 2 +- scst/README | 76 +- scst/README_in-tree | 76 +- scst/include/scst.h | 863 ++++++++- scst/include/scst_const.h | 49 +- scst/src/Makefile | 1 + scst/src/dev_handlers/scst_user.c | 2 +- scst/src/dev_handlers/scst_vdisk.c | 1442 +++++++++++++- scst/src/scst_lib.c | 2799 +++++++++++++++++++++++++++- scst/src/scst_priv.h | 6 + scst/src/scst_sysfs.c | 587 ++++++ scst/src/scst_targ.c | 93 +- srpt/src/ib_srpt.c | 2 +- 16 files changed, 5789 insertions(+), 246 deletions(-) diff --git a/Makefile b/Makefile index 44627e333..a8dc73bf2 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ SRP_DIR=srpt SCST_LOCAL_DIR=scst_local MVSAS_DIR=mvsas_tgt FCST_DIR=fcst +EMULEX_DIR=emulex ISCSI_DIR=iscsi-scst @@ -94,6 +95,12 @@ help: @echo " iscsi_install : ISCSI target: install" @echo " iscsi_uninstall : ISCSI target: uninstall" @echo "" + @echo " emulex : make Emulex target" + @echo " emulex_clean : Emulex target: clean " + @echo " emulex_extraclean : Emulex target: clean + clean dependencies" + @echo " emulex_install : Emulex target: install" + @echo " emulex_uninstall : Emulex target: uninstall" + @echo "" @echo " lsi : make LSI MPT target" @echo " lsi_clean : lsi target: clean " @echo " lsi_extraclean : lsi target: clean + clean dependencies" @@ -148,6 +155,7 @@ all: @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi @if [ -d $(SCST_LOCAL_DIR) ]; then cd $(SCST_LOCAL_DIR) && $(MAKE) $@; fi + @if [ -d $(EMULEX_DIR) ]; then cd $(EMULEX_DIR) && $(MAKE) $@; fi install: cd $(SCST_DIR) && $(MAKE) $@ @@ -159,6 +167,7 @@ install: @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi @if [ -d $(SCST_LOCAL_DIR) ]; then cd $(SCST_LOCAL_DIR) && $(MAKE) $@; fi + @if [ -d $(EMULEX_DIR) ]; then cd $(EMULEX_DIR) && $(MAKE) $@; fi uninstall: cd $(SCST_DIR) && $(MAKE) $@ @@ -170,6 +179,7 @@ uninstall: @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi @if [ -d $(SCST_LOCAL_DIR) ]; then cd $(SCST_LOCAL_DIR) && $(MAKE) $@; fi + @if [ -d $(EMULEX_DIR) ]; then cd $(EMULEX_DIR) && $(MAKE) $@; fi clean: cd $(SCST_DIR) && $(MAKE) $@ @@ -183,6 +193,7 @@ clean: @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi @if [ -d $(SCST_LOCAL_DIR) ]; then cd $(SCST_LOCAL_DIR) && $(MAKE) $@; fi + @if [ -d $(EMULEX_DIR) ]; then cd $(EMULEX_DIR) && $(MAKE) $@; fi extraclean: -rm -f TAGS @@ -197,6 +208,7 @@ extraclean: @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi @if [ -d $(SCST_LOCAL_DIR) ]; then cd $(SCST_LOCAL_DIR) && $(MAKE) $@; fi + @if [ -d $(EMULEX_DIR) ]; then cd $(EMULEX_DIR) && $(MAKE) $@; fi tags: find . -type f -name "*.[ch]" | ctags --c-kinds=+p --fields=+iaS --extra=+q -e -L- @@ -290,6 +302,21 @@ iscsi_clean: iscsi_extraclean: cd $(ISCSI_DIR) && $(MAKE) extraclean +emulex: + cd $(EMULEX_DIR) && $(MAKE) all + +emulex_install: + cd $(EMULEX_DIR) && $(MAKE) install + +emulex_uninstall: + cd $(EMULEX_DIR) && $(MAKE) uninstall + +emulex_clean: + cd $(EMULEX_DIR) && $(MAKE) clean + +emulex_extraclean: + cd $(EMULEX_DIR) && $(MAKE) extraclean + lsi: cd $(LSI_DIR) && $(MAKE) all @@ -506,6 +533,7 @@ disable_proc: extraclean qla_old qla_old_install qla_old_uninstall qla_old_clean qla_old_extraclean \ lsi lsi_install lsi_uninstall lsi_clean lsi_extraclean \ iscsi iscsi_install iscsi_uninstall iscsi_clean iscsi_extraclean \ + emulex emulex_install emulex_uninstall emulex_clean emulex_extraclean \ scst scst_install scst_uninstall scst_clean scst_extraclean \ docs docs_clean docs_extraclean \ scstadm scstadm_install scstadm_uninstall scstadm_clean scstadm_extraclean \ diff --git a/fcst/ft_cmd.c b/fcst/ft_cmd.c index 8c3509a78..3900e425d 100644 --- a/fcst/ft_cmd.c +++ b/fcst/ft_cmd.c @@ -57,7 +57,8 @@ void ft_cmd_dump(struct scst_cmd *cmd, const char *caller) prefix, cmd, fcmd->write_data_len, fcmd->read_data_len); pr_info("%s exp_dir %x exp_xfer_len %d exp_in_len %d\n", prefix, cmd->expected_data_direction, - cmd->expected_transfer_len, cmd->expected_out_transfer_len); + cmd->expected_transfer_len_full, + cmd->expected_out_transfer_len); pr_info("%s dir %x data_len %lld bufflen %d out_bufflen %d\n", prefix, cmd->data_direction, cmd->data_len, cmd->bufflen, cmd->out_bufflen); diff --git a/iscsi-scst/kernel/config.c b/iscsi-scst/kernel/config.c index be2611b3d..8916ce211 100644 --- a/iscsi-scst/kernel/config.c +++ b/iscsi-scst/kernel/config.c @@ -1270,18 +1270,18 @@ void iscsi_dump_pdu(struct iscsi_pdu *pdu) int i; buf = (void *)&pdu->bhs; - printk(KERN_DEBUG "BHS: (%p,%zd)\n", buf, sizeof(pdu->bhs)); + pr_debug("BHS: (%p,%zd)\n", buf, sizeof(pdu->bhs)); for (i = 0; i < (int)sizeof(pdu->bhs); i++) iscsi_dump_char(*buf++, text, &pos); iscsi_dump_char(-1, text, &pos); buf = (void *)pdu->ahs; - printk(KERN_DEBUG "AHS: (%p,%d)\n", buf, pdu->ahssize); + pr_debug("AHS: (%p,%d)\n", buf, pdu->ahssize); for (i = 0; i < pdu->ahssize; i++) iscsi_dump_char(*buf++, text, &pos); iscsi_dump_char(-1, text, &pos); - printk(KERN_DEBUG "Data: (%d)\n", pdu->datasize); + pr_debug("Data: (%d)\n", pdu->datasize); } } diff --git a/qla2x00t/qla_isr.c b/qla2x00t/qla_isr.c index a1595cb10..eccce01db 100644 --- a/qla2x00t/qla_isr.c +++ b/qla2x00t/qla_isr.c @@ -1723,7 +1723,7 @@ qla2x00_status_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, void *pkt) return; } - lscsi_status = scsi_status & STATUS_MASK; + lscsi_status = scsi_status & STATUS_MASK; fcport = sp->fcport; diff --git a/scst/README b/scst/README index d012bc60a..544f031f9 100644 --- a/scst/README +++ b/scst/README @@ -331,6 +331,11 @@ in/out in Makefile and scst.h: can clear already measured results by writing 0 in each file. Note, you need a non-preemptible kernel to have correct results. + - CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS - if defined, allows injection + of corrupted DIF tags according to the Oracle specification. This + functionality is working only if dif_mode doesn't contain dev_store + and dif_type is 1. + HIGHMEM kernel configurations are fully supported, but not recommended for performance reasons, except for scst_user, where they are not supported, because this module deals with user supplied memory on a @@ -613,6 +618,17 @@ Every target should have at least the following entries: particular target driver or not, you can find out by checking traces or the target driver's source code. + - dif_capabilities - if this target supports T10-PI, returns which + exact DIF capabilities this target supports. + + - dif_checks_failed - if this target supports T10-PI, returns + statistics how many DIF errors have been detected on the + corresponding processing stages on this target. It returns 3 rows of + numbers with 3 numbers in each row: for target driver stage, for SCST + stage and for dev handler stage. Numbers in each row: how many errors + detected checking application, reference and guard tags + correspondingly. Writing to this attribute resets the numbers. + - cpu_mask - defines CPU affinity mask for threads serving this target. For threads serving LUNs it is used only for devices with threads_pool_type "per_initiator". @@ -671,6 +687,17 @@ following entries: - commands - contains overall number of SCSI commands in this session. + - dif_checks_failed - if target of this session supports T10-PI, returns + statistics how many DIF errors have been detected on the + corresponding processing stages on all DIF-enabled LUNs in this + session. It returns 3 rows of numbers with 3 numbers in each row: for + target driver stage, for SCST stage and for dev handler stage. + Numbers in each row: how many errors detected checking application, + reference and guard tags correspondingly. Writing to this attribute + resets the numbers. Similar statistics returned in attribute with the + same name for each LUN in this session in this LUN's subdirectory, if + its device configured with dif_type > 0. + - read_cmd_count - number of READ SCSI commands received since beginning or last reset (writing 0 in this attribute) @@ -993,6 +1020,47 @@ cache. The following parameters possible for vdisk_fileio: - zero_copy - if set, then this device uses zero copy access to the page cache. At the moment, only read side zero copy is implemented. + - dif_mode - specifies which T10-PI, or DIF, mode this device will use. + See SCSI standards from more info about T10-PI. Available DIF modes + (can be combined using '|'): + + * tgt - DIF tags are checked on the target hardware, if supported + + * scst - DIF tags are checked inside SCST core + + * dev_check - DIF tags are checked inside backend device + + * dev_store - DIF tags are stored inside backend device on the WRITE + path and read from it on the READ path + + For instance, if only tgt DIF mode specified, then target driver, + serving this device, will inside hardware check, then STRIP DIF tags + from SCSI commands on the WRITE path and generate, then INSERT DIF + tags into SCSI commands on the READ path, so neither SCST core, nor + dev handler will see them. + + Similarly, if only scst DIF mode specified, then target driver will + PASS DIF tags into SCST core, which then check/STRIP/generate/INSERT + them, so dev handler will not see them. + + If only dev_check DIF mode specified, then both target driver and + SCST core will PASS DIF tags into the dev handler, which is then + responsible to check them in the backend hardware. If only dev_store + specified, then DIF tags will only be stored by the dev handler in + the backend hardware without checking at any level. + + If all "tgt|scst|dev_check|dev_store" DIF mode specified, then all + target driver, SCST core and dev handler will check DIF tags, then + dev handler will store them in the backend hardware. + + - dif_type - specifies which DIF SCSI type this device will use. + + - dif_static_app_tag - specifies fixed (static) DIF application tag for + this device. + + - dif_filename - specifies full path to filename, where DIF tags will + be stored. + Handler vdisk_blockio provides BLOCKIO mode to create virtual devices. This mode performs direct block I/O with a block device, bypassing the page cache for all operations. This mode works ideally with high-end @@ -1000,10 +1068,10 @@ storage HBAs and for applications that either do not need caching between application and disk or need the large block throughput. See below for more info. -The following common with vdisk_fileio parameters are possible for -vdisk_blockio: filename, blocksize, nv_cache, write_through, read_only, -removable, rotational, thin_provisioned, tst. See vdisk_fileio above for -description of those parameters. +The following parameters possible for vdisk_blockio: filename, +blocksize, nv_cache, read_only, removable, rotational, thin_provisioned, +tst, dif_mode, dif_type, dif_static_app_tag, dif_filename. See +vdisk_fileio above for description of those parameters. Handler vdisk_nullio provides NULLIO mode to create virtual devices. In this mode no real I/O is done, but success returned to initiators. diff --git a/scst/README_in-tree b/scst/README_in-tree index b93a434d6..7f8ff81a7 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -233,6 +233,11 @@ your favorite kernel configuration Makefile target, e.g. "make xconfig": can clear already measured results by writing 0 in each file. Note, you need a non-preemptible kernel to have correct results. + - CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS - if defined, allows injection + of corrupted DIF tags according to the Oracle specification. This + functionality is working only if dif_mode doesn't contain dev_store + and dif_type is 1. + HIGHMEM kernel configurations are fully supported, but not recommended for performance reasons. @@ -489,6 +494,17 @@ Every target should have at least the following entries: particular target driver or not, you can find out by checking traces or the target driver's source code. + - dif_capabilities - if this target supports T10-PI, returns which + exact DIF capabilities this target supports. + + - dif_checks_failed - if this target supports T10-PI, returns + statistics how many DIF errors have been detected on the + corresponding processing stages on this target. It returns 3 rows of + numbers with 3 numbers in each row: for target driver stage, for SCST + stage and for dev handler stage. Numbers in each row: how many errors + detected checking application, reference and guard tags + correspondingly. Writing to this attribute resets the numbers. + - cpu_mask - defines CPU affinity mask for threads serving this target. For threads serving LUNs it is used only for devices with threads_pool_type "per_initiator". @@ -547,6 +563,17 @@ following entries: - commands - contains overall number of SCSI commands in this session. + - dif_checks_failed - if target of this session supports T10-PI, returns + statistics how many DIF errors have been detected on the + corresponding processing stages on all DIF-enabled LUNs in this + session. It returns 3 rows of numbers with 3 numbers in each row: for + target driver stage, for SCST stage and for dev handler stage. + Numbers in each row: how many errors detected checking application, + reference and guard tags correspondingly. Writing to this attribute + resets the numbers. Similar statistics returned in attribute with the + same name for each LUN in this session in this LUN's subdirectory, if + its device configured with dif_type > 0. + - read_cmd_count - number of READ SCSI commands received since beginning or last reset (writing 0 in this attribute) @@ -865,6 +892,47 @@ cache. The following parameters possible for vdisk_fileio: - zero_copy - if set, then this device uses zero copy access to the page cache. At the moment, only read side zero copy is implemented. + - dif_mode - specifies which T10-PI, or DIF, mode this device will use. + See SCSI standards from more info about T10-PI. Available DIF modes + (can be combined using '|'): + + * tgt - DIF tags are checked on the target hardware, if supported + + * scst - DIF tags are checked inside SCST core + + * dev_check - DIF tags are checked inside backend device + + * dev_store - DIF tags are stored inside backend device on the WRITE + path and read from it on the READ path + + For instance, if only tgt DIF mode specified, then target driver, + serving this device, will inside hardware check, then STRIP DIF tags + from SCSI commands on the WRITE path and generate, then INSERT DIF + tags into SCSI commands on the READ path, so neither SCST core, nor + dev handler will see them. + + Similarly, if only scst DIF mode specified, then target driver will + PASS DIF tags into SCST core, which then check/STRIP/generate/INSERT + them, so dev handler will not see them. + + If only dev_check DIF mode specified, then both target driver and + SCST core will PASS DIF tags into the dev handler, which is then + responsible to check them in the backend hardware. If only dev_store + specified, then DIF tags will only be stored by the dev handler in + the backend hardware without checking at any level. + + If all "tgt|scst|dev_check|dev_store" DIF mode specified, then all + target driver, SCST core and dev handler will check DIF tags, then + dev handler will store them in the backend hardware. + + - dif_type - specifies which DIF SCSI type this device will use. + + - dif_static_app_tag - specifies fixed (static) DIF application tag for + this device. + + - dif_filename - specifies full path to filename, where DIF tags will + be stored. + Handler vdisk_blockio provides BLOCKIO mode to create virtual devices. This mode performs direct block I/O with a block device, bypassing the page cache for all operations. This mode works ideally with high-end @@ -872,10 +940,10 @@ storage HBAs and for applications that either do not need caching between application and disk or need the large block throughput. See below for more info. -The following common with vdisk_fileio parameters are possible for -vdisk_blockio: filename, blocksize, nv_cache, write_through, read_only, -removable, rotational, thin_provisioned, tst. See vdisk_fileio above for -description of those parameters. +The following parameters possible for vdisk_blockio: filename, +blocksize, nv_cache, read_only, removable, rotational, thin_provisioned, +tst, dif_mode, dif_type, dif_static_app_tag, dif_filename. See +vdisk_fileio above for description of those parameters. Handler vdisk_nullio provides NULLIO mode to create virtual devices. In this mode no real I/O is done, but success returned to initiators. diff --git a/scst/include/scst.h b/scst/include/scst.h index 4045f041b..e9e8c5dc9 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -27,6 +27,9 @@ /* #define CONFIG_SCST_DEBUG_TM */ /* #define CONFIG_SCST_TM_DBG_GO_OFFLINE */ +/** See README for description of those conditional defines **/ +#define CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + #include #ifndef INSIDE_KERNEL_TREE #include @@ -750,6 +753,159 @@ enum scst_exec_context { SCST_SENSE_ASC_VALID | \ SCST_SENSE_ASCQ_VALID) +/************************************************************* + ** T10-PI (DIF) support + *************************************************************/ + +struct scst_dif_tuple { + __be16 guard_tag; + __be16 app_tag; + __be32 ref_tag; +}; + +/* + * Defines where and how to deal with DIF tags. Can be OR'ed to get + * multilevel checks + */ +enum scst_dif_mode { + SCST_DIF_MODE_NONE = 0, + + /* STRIP/INSERT/CHECK inside target HW */ + SCST_DIF_MODE_TGT = 1, + + /* SCST checking or creating tags on the fly */ + SCST_DIF_MODE_SCST = 2, + + /* Backend device checking tags */ + SCST_DIF_MODE_DEV_CHECK = 4, + + /* Backend device storing tags, creating them on writes, if needed */ + SCST_DIF_MODE_DEV_STORE = 8, +}; +#define SCST_DIF_MODE_DEV (SCST_DIF_MODE_DEV_CHECK|SCST_DIF_MODE_DEV_STORE) + +#define SCST_DIF_MODE_TGT_STR "tgt" +#define SCST_DIF_MODE_SCST_STR "scst" +#define SCST_DIF_MODE_DEV_CHECK_STR "dev_check" +#define SCST_DIF_MODE_DEV_STORE_STR "dev_store" + +/* + * T10-PI actions. + * + * Don't use masks and shifts directly! Use helper functions instead! + */ +enum scst_dif_actions { + SCST_DIF_ACTION_NONE = 0, /* do nothing */ + +#define SCST_DIF_CHECKS_SHIFT 0 + /* OR'able tags to check */ + SCST_DIF_CHECK_GUARD_TAG = 1, + SCST_DIF_CHECK_APP_TAG = 2, + SCST_DIF_CHECK_REF_TAG = 4, +#define SCST_DIF_CHECKS_MASK (SCST_DIF_CHECK_GUARD_TAG|SCST_DIF_CHECK_APP_TAG| \ + SCST_DIF_CHECK_REF_TAG) + +#define SCST_DIF_ACTION_SHIFT 4 + /* How to handle PI */ + SCST_DIF_ACTION_STRIP = (1 << SCST_DIF_ACTION_SHIFT), /* check then strip, if OK */ + SCST_DIF_ACTION_INSERT = (2 << SCST_DIF_ACTION_SHIFT), + SCST_DIF_ACTION_PASS_CHECK = (3 << SCST_DIF_ACTION_SHIFT), /* check then pass, if OK */ + SCST_DIF_ACTION_PASS = (4 << SCST_DIF_ACTION_SHIFT), /* just pass, no check */ + +#define SCST_DIF_ACTION_MASK (SCST_DIF_ACTION_STRIP|SCST_DIF_ACTION_INSERT| \ + SCST_DIF_ACTION_PASS_CHECK|SCST_DIF_ACTION_PASS) +}; + +/* Shift for target driver DIF actions */ +#define SCST_DIF_TGT_ACTION_SHIFT 0 + +/* Shift for SCST DIF actions */ +#define SCST_DIF_SCST_ACTION_SHIFT (SCST_DIF_TGT_ACTION_SHIFT+4) + +/* Shift for dev handler DIF actions */ +#define SCST_DIF_DEV_ACTION_SHIFT (SCST_DIF_SCST_ACTION_SHIFT+4) + +/* Returns DIF checks by stripping the action part from a */ +static inline enum scst_dif_actions scst_get_dif_checks(enum scst_dif_actions a) +{ + return (a & SCST_DIF_CHECKS_MASK); +} + +/* Sets the DIF checks part in a */ +static inline void scst_set_dif_checks(enum scst_dif_actions *a, + enum scst_dif_actions checks) +{ + checks &= ~SCST_DIF_CHECKS_MASK; + *a |= checks << SCST_DIF_CHECKS_SHIFT; + return; +} + +/* Returns DIF action by stripping the checks part from a */ +static inline enum scst_dif_actions scst_get_dif_action(enum scst_dif_actions a) +{ + return (a & SCST_DIF_ACTION_MASK); +} + +/* Sets the DIF action part in a */ +static inline void scst_set_dif_action(enum scst_dif_actions *a, + enum scst_dif_actions action) +{ + action &= ~SCST_DIF_ACTION_MASK; + *a |= action; + return; +} + +/* Returns TGT DIF actions from a, including checks */ +static inline enum scst_dif_actions scst_get_tgt_dif_actions(enum scst_dif_actions a) +{ + BUILD_BUG_ON(SCST_DIF_CHECKS_SHIFT != 0); + return ((a >> SCST_DIF_TGT_ACTION_SHIFT) & SCST_DIF_ACTION_MASK) | + (a & SCST_DIF_CHECKS_MASK); +} + +/* Sets TGT DIF action in a. DIF checks in a are not affected by this function. */ +static inline void scst_set_tgt_dif_action(enum scst_dif_actions *a, + enum scst_dif_actions tgt_a) +{ + tgt_a &= SCST_DIF_ACTION_MASK; + *a |= tgt_a << SCST_DIF_TGT_ACTION_SHIFT; + return; +} + +/* Returns SCST DIF actions from a, including checks */ +static inline enum scst_dif_actions scst_get_scst_dif_actions(enum scst_dif_actions a) +{ + BUILD_BUG_ON(SCST_DIF_CHECKS_SHIFT != 0); + return ((a >> SCST_DIF_SCST_ACTION_SHIFT) & SCST_DIF_ACTION_MASK) | + (a & SCST_DIF_CHECKS_MASK); +} + +/* Sets SCST DIF action in a. DIF checks in a are not affected by this function. */ +static inline void scst_set_scst_dif_action(enum scst_dif_actions *a, + enum scst_dif_actions scst_a) +{ + scst_a &= SCST_DIF_ACTION_MASK; + *a |= scst_a << SCST_DIF_SCST_ACTION_SHIFT; + return; +} + +/* Returns DEV DIF actions from a, including checks */ +static inline enum scst_dif_actions scst_get_dev_dif_actions(enum scst_dif_actions a) +{ + BUILD_BUG_ON(SCST_DIF_CHECKS_SHIFT != 0); + return ((a >> SCST_DIF_DEV_ACTION_SHIFT) & SCST_DIF_ACTION_MASK) | + (a & SCST_DIF_CHECKS_MASK); +} + +/* Sets SCST DIF action in a. DIF checks in a are not affected by this function. */ +static inline void scst_set_dev_dif_action(enum scst_dif_actions *a, + enum scst_dif_actions dev_a) +{ + dev_a &= SCST_DIF_ACTION_MASK; + *a |= dev_a << SCST_DIF_DEV_ACTION_SHIFT; + return; +} + /************************************************************* * TYPES *************************************************************/ @@ -785,6 +941,33 @@ typedef enum dma_data_direction scst_data_direction; struct scst_tgt_template { /* public: */ + /* + * If not NULL, points to 0-terminated array of integers listing all + * supported for T10-PI DIF-enabled devices block sizes. SCST will not + * allow to add DIF-enabled devices with another block sizes to this + * target driver's initiator groups. + * + * Can be overiden by scst_tgt_set_supported_dif_block_sizes() + * + * OPTIONAL + */ + const int *const supported_dif_block_sizes; + + /* + * Preferred SCSI LUN addressing method. + */ + enum scst_lun_addr_method preferred_addr_method; + + /* + * The maximum time in seconds cmd can stay inside the target + * hardware, i.e. after rdy_to_xfer() and xmit_response(), before + * on_hw_pending_cmd_timeout() will be called, if defined. + * + * In the current implementation a cmd will be aborted in time t + * max_hw_pending_time <= t < 2*max_hw_pending_time. + */ + int max_hw_pending_time; + /* * SG tablesize allows to check whether scatter/gather can be used * or not. @@ -836,19 +1019,62 @@ struct scst_tgt_template { unsigned multithreaded_init_done:1; /* - * Preferred SCSI LUN addressing method. + * True, if this target driver supports T10-PI (DIF), i.e. sending and + * receiving DIF PI tags. If false, SCST will not allow to add + * DIF-enabled devices to this target driver's initiator groups. + * + * Can be overriden per target by scst_tgt_set_dif_supported() */ - enum scst_lun_addr_method preferred_addr_method; + unsigned dif_supported:1; /* - * The maximum time in seconds cmd can stay inside the target - * hardware, i.e. after rdy_to_xfer() and xmit_response(), before - * on_hw_pending_cmd_timeout() will be called, if defined. + * True, if this target driver supports T10-PI (DIF), i.e. INSERT/STRIPE + * mode, in hardware for type 1 protection. If false, SCST will not + * allow to add type 1 devices with TGT DIF mode to this target driver's + * initiator groups. * - * In the current implementation a cmd will be aborted in time t - * max_hw_pending_time <= t < 2*max_hw_pending_time. + * Can be overriden per target by scst_tgt_set_hw_dif_type1_supported() */ - int max_hw_pending_time; + unsigned hw_dif_type1_supported:1; + + /* + * True, if this target driver supports T10-PI (DIF), i.e. INSERT/STRIPE + * mode, in hardware for type 2 protection. If false, SCST will not + * allow to add type 2 devices with TGT DIF mode to this target driver's + * initiator groups. + * + * Can be overriden per target by scst_tgt_set_hw_dif_type2_supported() + */ + unsigned hw_dif_type2_supported:1; + + /* + * True, if this target driver supports T10-PI (DIF), i.e. INSERT/STRIPE + * mode, in hardware for type 3 protection. If false, SCST will not + * allow to add type 3 devices with TGT DIF mode to this target driver's + * initiator groups. + * + * Can be overriden per target by scst_tgt_set_hw_dif_type3_supported() + */ + unsigned hw_dif_type3_supported:1; + + /* + * True, if this target driver supports IP checksum format of T10-PI + * (DIF) guard tags, i.e. hardware translation of guard tags between IP + * and CRC on SCST/target driver boundary for better performance, + * because IP checksums are much cheaper for CPU, than CRC. + * + * Can be overriden per target by scst_tgt_set_hw_dif_ip_supported() + */ + unsigned hw_dif_ip_supported:1; + + /* + * True, if this target driver requires the same layout for both data + * and DIF tags SG vectors. Otherwise, for DIF tags minimally possible + * SG vector size will be allocated. + * + * Can be overriden per target by scst_tgt_set_hw_dif_same_sg_layout_required() + */ + unsigned hw_dif_same_sg_layout_required:1; /* * This function is equivalent to the SCSI @@ -1700,6 +1926,15 @@ struct scst_tgt { struct list_head tgt_acg_list; /* target ACG groups */ #endif + /* Per target analog of the corresponding driver's fields */ + unsigned tgt_dif_supported:1; + unsigned tgt_hw_dif_type1_supported:1; + unsigned tgt_hw_dif_type2_supported:1; + unsigned tgt_hw_dif_type3_supported:1; + unsigned tgt_hw_dif_ip_supported:1; + unsigned tgt_hw_dif_same_sg_layout_required:1; + const int *tgt_supported_dif_block_sizes; + /* * Maximum SG table size. Needed here, since different cards on the * same target template can have different SG table limitations. @@ -1737,6 +1972,11 @@ struct scst_tgt { uint16_t rel_tgt_id; + /* How many DIF failures detected on this target on the corresponding stage */ + atomic_t tgt_dif_app_failed_tgt, tgt_dif_ref_failed_tgt, tgt_dif_guard_failed_tgt; + atomic_t tgt_dif_app_failed_scst, tgt_dif_ref_failed_scst, tgt_dif_guard_failed_scst; + atomic_t tgt_dif_app_failed_dev, tgt_dif_ref_failed_dev, tgt_dif_guard_failed_dev; + #ifdef CONFIG_SCST_PROC /* Name of the default security group ("Default_target_name") */ char *default_group_name; @@ -2069,6 +2309,9 @@ struct scst_cmd { /* Set if this command was sent in double UA possible state */ unsigned int double_ua_possible:1; + /* Set if DIF check for just read data was deferred to thread context */ + unsigned int deferred_dif_read_check:1; + /* Set if this command contains status */ unsigned int is_send_status:1; @@ -2106,12 +2349,21 @@ struct scst_cmd { /* Set if custom data buffer allocated by dev handler */ unsigned int dh_data_buf_alloced:1; + /* + * Set length of each member of dif_sg was normalized to match + * tgtt->hw_dif_same_sg_layout_required requirements + */ + unsigned int dif_sg_normalized:1; + /* Set if the target driver called scst_set_expected() */ unsigned int expected_values_set:1; - /* Set if the SG buffer was modified by scst_adjust_sg() */ + /* Set if the SG buffer was modified by scst_adjust_sg*() */ unsigned int sg_buff_modified:1; + /* Set if the DIF SG buffer was modified by scst_adjust_sg*() */ + unsigned int dif_sg_buff_modified:1; + /* * Set if cmd buffer was vmallocated and copied from more * then one sg chunk @@ -2152,6 +2404,9 @@ struct scst_cmd { /* Set if any direction residual is possible */ unsigned int resid_possible:1; + /* Set if DIF data should be included in the residual considerations */ + unsigned int tgt_dif_data_expected:1; + /* Set if cmd is done */ unsigned int done:1; @@ -2245,7 +2500,7 @@ struct scst_cmd { /* Values supplied by the initiator in the transport layer header, if any */ scst_data_direction expected_data_direction; - int expected_transfer_len; + int expected_transfer_len_full; /* both data and DIF tags, if any */ int expected_out_transfer_len; /* for bidi writes */ int64_t lba; /* LBA of this cmd */ @@ -2261,11 +2516,15 @@ struct scst_cmd { void (*scst_cmd_done)(struct scst_cmd *cmd, int next_state, enum scst_exec_context pref_context); - struct sgv_pool_obj *sgv; /* sgv object */ + struct sgv_pool_obj *sgv; /* data sgv object */ int bufflen; /* cmd buffer length */ int sg_cnt; /* SG segments count */ struct scatterlist *sg; /* cmd data buffer SG vector */ + struct sgv_pool_obj *dif_sgv; /* DIF sgv object */ + struct scatterlist *dif_sg; /* cmd DIF tags buffer SG vector */ + int dif_sg_cnt; /* DIF SG segments count */ + /* * Response data length in data buffer. Must not be set * directly, use scst_set_resp_data_len() for that. @@ -2301,6 +2560,7 @@ struct scst_cmd { struct sgv_pool_obj *out_sgv; /* WRITE sgv object */ struct scatterlist *out_sg; /* WRITE data buffer SG vector */ int out_sg_cnt; /* WRITE SG segments count */ + /* No out DIF tags buffer, because there's no BIDI command with them */ /* * Used if both target driver or SCST core for internal commands and @@ -2313,8 +2573,15 @@ struct scst_cmd { */ struct scatterlist *tgt_i_sg; int tgt_i_sg_cnt; + int tgt_i_dif_sg_cnt; + struct scatterlist *tgt_i_dif_sg; /* DIF tags */ + /* + * There's no tgt_i_dif_sg_cnt, because it's supposed to be strictly + * bound to tgt_i_sg_cnt. + */ struct scatterlist *tgt_out_sg; /* bidirectional */ int tgt_out_sg_cnt; /* bidirectional */ + /* No out DIF tags buffer, because there's no BIDI command with them */ /* * The status fields in case of errors must be set using @@ -2325,6 +2592,12 @@ struct scst_cmd { uint8_t host_status; /* set by low-level driver to indicate status */ uint8_t driver_status; /* set by mid-level */ + /* DIF actions on this cmd */ + enum scst_dif_actions cmd_dif_actions; +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + uint32_t cmd_corrupt_dif_tag; +#endif + uint8_t *sense; /* pointer to sense buffer */ unsigned short sense_valid_len; /* length of valid sense data */ unsigned short sense_buflen; /* length of the sense buffer, if any */ @@ -2350,8 +2623,8 @@ struct scst_cmd { */ struct list_head mgmt_cmd_list; - /* Used to restore sg if it was modified by scst_adjust_sg() */ - struct scst_orig_sg_data orig_sg; + /* Used to restore sg if it was modified by scst_adjust_sg*() */ + struct scst_orig_sg_data orig_sg, orig_dif_sg; /* Per opcode stuff */ union { @@ -2530,6 +2803,9 @@ struct scst_device { */ unsigned int dev_unregistering:1; + /* Set if this device does not support DIF IP checking */ + unsigned int dev_dif_ip_not_supported:1; + /**************************************************************/ /************************************************************* @@ -2547,6 +2823,8 @@ struct scst_device { unsigned int tas:1; unsigned int swp:1; unsigned int d_sense:1; + unsigned int dpicz:1; + unsigned int ato:1; /** ** Saved and default versions of them, which supported. TST is not @@ -2575,6 +2853,9 @@ struct scst_device { unsigned int d_sense_saved:1; unsigned int d_sense_default:1; + unsigned int dpicz_saved:1; + unsigned int dpicz_default:1; + /* * Set if device implements own ordered commands management. If not set * and queue_alg is SCST_QUEUE_ALG_0_RESTRICTED_REORDER, expected_sn @@ -2584,6 +2865,22 @@ struct scst_device { /**************************************************************/ + /* + * SCST_DIF_CHECK_APP_TAG (to match usage in the xPROTECT parsing + * routines), if app tag is checked, or 0 otherwise. Might be used + * as bool. + */ + unsigned int dif_app_chk; + + /* + * SCST_DIF_CHECK_REF_TAG (to match usage in the xPROTECT parsing + * routines), if ref tag is checked, or 0 otherwise. Might be used + * as bool. + */ + unsigned int dif_ref_chk; + + /**************************************************************/ + /* * Device block size and block shift if fixed size blocks used. Supposed * to be read-only or serialized the same way as MODE pages changes. @@ -2625,6 +2922,34 @@ struct scst_device { /* List of commands with lock, if dedicated threads are used */ struct scst_cmd_threads dev_cmd_threads; + /************************************************************* + ** T10-PI fields. Read-only, hence no protection. + *************************************************************/ + + enum scst_dif_mode dev_dif_mode; + int dev_dif_type; /* SCSI DIF type */ + + /* + * Callback to process DIF tags by SCST as required by device + * formatting and *protect cmd's bits. Supposed to return 0 on + * success, i.e. when cmd processing should proceed normally, or + * negative error code otherwise, i.e. when cmd processing should + * be stopped and status send to its initiator. + */ + 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 */ + + /* Cache to optimize scst_parse_*protect() routines */ + enum scst_dif_actions dev_dif_rd_actions; + enum scst_dif_actions dev_dif_wr_actions; + enum scst_dif_actions dev_dif_rd_prot0_actions; + enum scst_dif_actions dev_dif_wr_prot0_actions; + enum scst_dif_actions dev_dif_vr_actions; + /* Set if reserved via the SPC-2 SCSI RESERVE command. */ struct scst_session *reserved_by; @@ -2777,6 +3102,9 @@ struct scst_tgt_dev { /* Set if tgt_dev uses clustered SGV pool */ unsigned int tgt_dev_clust_pool:1; + /* Taken from the target on initialization to save a cache miss */ + unsigned hw_dif_same_sg_layout_required:1; + /**************************************************************/ /* @@ -2788,6 +3116,15 @@ struct scst_tgt_dev { /* Used for storage of dev handler private stuff */ void *dh_priv; + /* Pointer to function to compute DIF guard tag */ + __be16 (*tgt_dev_dif_crc_fn)(const void *buffer, unsigned int len); + + /* + * Guard tags format, one of SCST_DIF_GUARD_FORMAT_* constants. + * Put here to save extra dereferences, this space isn't used anyway. + */ + int tgt_dev_dif_guard_format; + /* How many cmds alive on this dev in this session */ atomic_t tgt_dev_cmd_count ____cacheline_aligned_in_smp; @@ -2833,6 +3170,11 @@ struct scst_tgt_dev { /* Set if INQUIRY DATA HAS CHANGED UA is needed */ unsigned int inq_changed_ua_needed:1; + /* How many DIF failures detected on this tgt_dev on the corresponding stage */ + atomic_t tgt_dev_dif_app_failed_tgt, tgt_dev_dif_ref_failed_tgt, tgt_dev_dif_guard_failed_tgt; + atomic_t tgt_dev_dif_app_failed_scst, tgt_dev_dif_ref_failed_scst, tgt_dev_dif_guard_failed_scst; + atomic_t tgt_dev_dif_app_failed_dev, tgt_dev_dif_ref_failed_dev, tgt_dev_dif_guard_failed_dev; + /* * Stored Unit Attention sense and its length for possible * subsequent REQUEST SENSE. Both protected by tgt_dev_lock. @@ -2868,6 +3210,9 @@ struct scst_acg_dev { /* If set, the corresponding LU is read only */ unsigned int acg_dev_rd_only:1; + /* Guard tags format, one of SCST_DIF_GUARD_FORMAT_* constants */ + int acg_dev_dif_guard_format; + struct scst_acg *acg; /* parent acg */ /* List entry in dev->dev_acg_dev_list */ @@ -3360,6 +3705,99 @@ static inline void scst_tgt_set_tgt_priv(struct scst_tgt *tgt, void *val) tgt->tgt_priv = val; } +/* + * Get/Set functions for tgt's tgt_dif_supported + */ +static inline bool scst_tgt_get_dif_supported(struct scst_tgt *tgt) +{ + return tgt->tgt_dif_supported; +} + +static inline void scst_tgt_set_dif_supported(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_dif_supported = !!val; +} + +/* + * Get/Set functions for tgt's tgt_hw_dif_type1_supported + */ +static inline bool scst_tgt_get_hw_dif_type1_supported(struct scst_tgt *tgt) +{ + return tgt->tgt_hw_dif_type1_supported; +} + +static inline void scst_tgt_set_hw_dif_type1_supported(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_hw_dif_type1_supported = !!val; +} + +/* + * Get/Set functions for tgt's tgt_hw_dif_type2_supported + */ +static inline bool scst_tgt_get_hw_dif_type2_supported(struct scst_tgt *tgt) +{ + return tgt->tgt_hw_dif_type2_supported; +} + +static inline void scst_tgt_set_hw_dif_type2_supported(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_hw_dif_type2_supported = !!val; +} + +/* + * Get/Set functions for tgt's tgt_hw_dif_type3_supported + */ +static inline bool scst_tgt_get_hw_dif_type3_supported(struct scst_tgt *tgt) +{ + return tgt->tgt_hw_dif_type3_supported; +} + +static inline void scst_tgt_set_hw_dif_type3_supported(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_hw_dif_type3_supported = !!val; +} + +/* + * Get/Set functions for tgt's tgt_hw_dif_ip_supported + */ +static inline bool scst_tgt_get_hw_dif_ip_supported(struct scst_tgt *tgt) +{ + return tgt->tgt_hw_dif_ip_supported; +} + +static inline void scst_tgt_set_hw_dif_ip_supported(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_hw_dif_ip_supported = !!val; +} + +/* + * Get/Set functions for tgt's tgt_hw_dif_same_sg_layout_required + */ +static inline bool scst_tgt_get_hw_dif_same_sg_layout_required(struct scst_tgt *tgt) +{ + return tgt->tgt_hw_dif_same_sg_layout_required; +} + +static inline void scst_tgt_set_hw_dif_same_sg_layout_required(struct scst_tgt *tgt, bool val) +{ + tgt->tgt_hw_dif_same_sg_layout_required = !!val; +} + +/* + * Get/Set functions for tgt's tgt_supported_dif_block_sizes + */ +static inline const int *scst_tgt_get_supported_dif_block_sizes( + struct scst_tgt *tgt) +{ + return tgt->tgt_supported_dif_block_sizes; +} + +static inline void scst_tgt_set_supported_dif_block_sizes(struct scst_tgt *tgt, + const int *const val) +{ + tgt->tgt_supported_dif_block_sizes = val; +} + void scst_update_hw_pending_start(struct scst_cmd *cmd); /* @@ -3381,6 +3819,115 @@ bool scst_impl_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); +/* + * Get/set functions for dev's static DIF APP TAG + */ +static inline __be16 scst_dev_get_dif_static_app_tag(struct scst_device *dev) +{ + return dev->dev_dif_static_app_tag; +} + +static inline __be32 scst_dev_get_dif_static_app_ref_tag(struct scst_device *dev) +{ + return dev->dev_dif_static_app_ref_tag; +} + +static inline __be64 scst_dev_get_dif_static_app_tag_combined( + struct scst_device *dev) +{ + uint64_t a = (((uint64_t)be32_to_cpu(dev->dev_dif_static_app_ref_tag)) << 16) | + be16_to_cpu(dev->dev_dif_static_app_tag); + return cpu_to_be64(a); +} + +void scst_dev_set_dif_static_app_tag_combined(struct scst_device *dev, + __be64 app_tag); + +/* + * Get/set functions for dev's DIF APP TAG checking + */ +static inline bool scst_dev_get_dif_app_tag_check(struct scst_device *dev) +{ + return dev->dif_app_chk == SCST_DIF_CHECK_APP_TAG; +} + +int scst_set_dif_params(struct scst_device *dev, + enum scst_dif_mode dif_mode, int dif_type); + +/* + * Functions to account detected DIF errors on the corresponding stages + */ +static inline void scst_dif_acc_app_check_failed_tgt(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_app_failed_tgt); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_app_failed_tgt); +} + +static inline void scst_dif_acc_ref_check_failed_tgt(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_ref_failed_tgt); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_ref_failed_tgt); +} + +static inline void scst_dif_acc_guard_check_failed_tgt(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_guard_failed_tgt); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_guard_failed_tgt); +} + +static inline void scst_dif_acc_app_check_failed_scst(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_app_failed_scst); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_app_failed_scst); +} + +static inline void scst_dif_acc_ref_check_failed_scst(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_ref_failed_scst); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_ref_failed_scst); +} + +static inline void scst_dif_acc_guard_check_failed_scst(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_guard_failed_scst); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_guard_failed_scst); +} + +static inline void scst_dif_acc_app_check_failed_dev(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_app_failed_dev); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_app_failed_dev); +} + +static inline void scst_dif_acc_ref_check_failed_dev(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_ref_failed_dev); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_ref_failed_dev); +} + +static inline void scst_dif_acc_guard_check_failed_(struct scst_cmd *cmd) +{ + atomic_inc(&cmd->tgt->tgt_dif_guard_failed_dev); + atomic_inc(&cmd->tgt_dev->tgt_dev_dif_guard_failed_dev); +} + +/* + * Functions to process DIF tags by SCST as required by device + * formatting and *protect cmd's bits. + * + * Return 0 on success, i.e. when cmd processing should proceed normally, + * or negative error code otherwise, i.e. when cmd processing should be + * stopped and status send to its initiator. + */ +static inline int scst_dif_process_read(struct scst_cmd *cmd) +{ + return cmd->dev->dev_dif_fn(cmd); +} +static inline int scst_dif_process_write(struct scst_cmd *cmd) +{ + return cmd->dev->dev_dif_fn(cmd); +} + /** * Returns TRUE if cmd is being executed in atomic context. * @@ -3514,6 +4061,28 @@ static inline int scst_cmd_get_sg_cnt(struct scst_cmd *cmd) return cmd->sg_cnt; } +/* + * Returns pointer to cmd's DIF tags SG data buffer. + * + * Usage of this function is not recommended, use scst_get_dif_buf() + * function instead. + */ +static inline struct scatterlist *scst_cmd_get_dif_sg(struct scst_cmd *cmd) +{ + return cmd->dif_sg; +} + +/* + * Returns pointer to cmd's DIF tags SG data buffer elements count. + * + * Usage of this function is not recommended, use scst_get_dif_buf() + * function instead. + */ +static inline int scst_cmd_get_dif_sg_cnt(struct scst_cmd *cmd) +{ + return cmd->dif_sg_cnt; +} + /* Returns cmd's LBA */ static inline int64_t scst_cmd_get_lba(struct scst_cmd *cmd) { @@ -3541,6 +4110,27 @@ static inline int64_t scst_cmd_get_data_len(struct scst_cmd *cmd) return cmd->data_len; } +/* Returns true, if cmd needs DIF buffer */ +static inline bool scst_cmd_needs_dif_buf(struct scst_cmd *cmd) +{ + return (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE) || + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE); +} + +/* Returns length of DIF buffer of this cmd */ +static inline int __scst_cmd_get_bufflen_dif(struct scst_cmd *cmd) +{ + return (cmd->bufflen >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; +} + +static inline int scst_cmd_get_bufflen_dif(struct scst_cmd *cmd) +{ + if (scst_cmd_needs_dif_buf(cmd)) + return __scst_cmd_get_bufflen_dif(cmd); + else + return 0; +} + /* * Returns pointer to cmd's bidirectional in (WRITE) SG data buffer. * @@ -3568,7 +4158,7 @@ void scst_restore_sg_buff(struct scst_cmd *cmd); /* Restores modified sg buffer in the original state, if necessary */ static inline void scst_check_restore_sg_buff(struct scst_cmd *cmd) { - if (unlikely(cmd->sg_buff_modified)) + if (unlikely(cmd->sg_buff_modified || cmd->dif_sg_buff_modified)) scst_restore_sg_buff(cmd); } @@ -3614,6 +4204,35 @@ static inline void scst_cmd_set_tgt_sg(struct scst_cmd *cmd, cmd->tgt_i_data_buf_alloced = 1; } +/* + * Returns pointer to cmd's target's DIF tags SG data buffer. Since it's + * for target drivers, the "_i_" part is omitted. + */ +static inline struct scatterlist *scst_cmd_get_tgt_dif_sg(struct scst_cmd *cmd) +{ + return cmd->tgt_i_dif_sg; +} + +/* + * Returns cmd's target's DIF tags SG data buffer elements count. Since it's + * for target drivers, the "_i_" part is omitted. + */ +static inline int scst_cmd_get_tgt_dif_sg_cnt(struct scst_cmd *cmd) +{ + return cmd->tgt_i_dif_sg_cnt; +} + +/* + * Sets cmd's target's DIF tags SG data buffer. Since it's for target + * drivers, the "_i_" part is omitted. + */ +static inline void scst_cmd_set_tgt_dif_sg(struct scst_cmd *cmd, + struct scatterlist *dif_sg, int cnt) +{ + cmd->tgt_i_dif_sg = dif_sg; + cmd->tgt_i_dif_sg_cnt = cnt; +} + /* Returns pointer to cmd's target's OUT SG data buffer */ static inline struct scatterlist *scst_cmd_get_out_tgt_sg(struct scst_cmd *cmd) { @@ -3876,12 +4495,19 @@ static inline scst_data_direction scst_cmd_get_expected_data_direction( return cmd->expected_data_direction; } -static inline int scst_cmd_get_expected_transfer_len( +/* + * Returns full expected transfer length, i.e. including both data and + * DIF tags, if any. + */ +static inline int scst_cmd_get_expected_transfer_len_full( struct scst_cmd *cmd) { - return cmd->expected_transfer_len; + return cmd->expected_transfer_len_full; } +int scst_cmd_get_expected_transfer_len_data(struct scst_cmd *cmd); +int scst_cmd_get_expected_transfer_len_dif(struct scst_cmd *cmd); + static inline int scst_cmd_get_expected_out_transfer_len( struct scst_cmd *cmd) { @@ -3890,10 +4516,10 @@ static inline int scst_cmd_get_expected_out_transfer_len( static inline void scst_cmd_set_expected(struct scst_cmd *cmd, scst_data_direction expected_data_direction, - int expected_transfer_len) + int expected_transfer_len_full) { cmd->expected_data_direction = expected_data_direction; - cmd->expected_transfer_len = expected_transfer_len; + cmd->expected_transfer_len_full = expected_transfer_len_full; cmd->expected_values_set = 1; } @@ -3933,6 +4559,168 @@ static inline void scst_set_delivery_status(struct scst_cmd *cmd, cmd->delivery_status = delivery_status; } +/* + * Returns T10-PI actions, including checks, which target driver should do + * with this command on READ direction transfers (xmit_response()). Might + * be different from WRITE direction for BIDI commands. + */ +static inline enum scst_dif_actions scst_get_read_dif_tgt_actions( + struct scst_cmd *cmd) +{ + return scst_get_tgt_dif_actions(cmd->cmd_dif_actions); +} + +/* + * Returns T10-PI actions, including checks, which target driver should do + * with this command on WRITE direction transfers (rdy_to_xfer()). Might + * be different from READ direction for BIDI commands. + */ +static inline enum scst_dif_actions scst_get_write_dif_tgt_actions( + struct scst_cmd *cmd) +{ + return scst_get_tgt_dif_actions(cmd->cmd_dif_actions); +} + +/* + * Returns T10-PI actions, including checks, which dev handler should do + * with this command on READ direction transfers. Might be different + * from WRITE direction for BIDI commands. + */ +static inline enum scst_dif_actions scst_get_read_dif_dev_actions( + struct scst_cmd *cmd) +{ + return scst_get_dev_dif_actions(cmd->cmd_dif_actions); +} + +/* + * Returns T10-PI actions, including checks, which dev handler should do + * with this command on WRITE direction transfers. Might be different + * from READ direction for BIDI commands. + */ +static inline enum scst_dif_actions scst_get_write_dif_dev_actions( + struct scst_cmd *cmd) +{ + return scst_get_dev_dif_actions(cmd->cmd_dif_actions); +} + +/* + * Returns T10-PI protection type for this cmd's device. Supposed to be used + * by target drivers. + */ +static inline int scst_cmd_get_dif_prot_type(struct scst_cmd *cmd) +{ + return cmd->dev->dev_dif_type; +} + +/* + * Returns T10-PI application tag for this cmd's device's lba. + * + * If SCST_DIF_NO_CHECK_APP_TAG returned, the target driver should not + * check app tag for this lba (SCSI requirement). + * + * Parameter out_lba_end returns end LBA of the area, where this app tag + * is valid. Then, if necessary, the target driver supposed to call + * this function again for the next app tag area. This is intended to + * support Application Tag mode page. ToDo, not implemented yet. + */ +static inline __be16 scst_cmd_get_dif_app_tag(struct scst_cmd *cmd, + uint64_t lba_start/*, uint64_t *out_lba_end*/) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + WARN_ON(!(scst_get_dif_checks(cmd->cmd_dif_actions) & SCST_DIF_CHECK_APP_TAG)); +#endif + return cmd->dev->dev_dif_static_app_tag; +} + +/* + * Returns T10-PI type 2 expected initial reference tag as LBA, i.e. converted + * into CPU endianness. Valid only with protection type 2. + */ +static inline uint32_t scst_cmd_get_dif_exp_ref_tag(struct scst_cmd *cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + BUG_ON(cmd->dev->dev_dif_type != 2); + WARN_ON(cmd->cdb_len > 32); +#endif + if (cmd->cdb_len == 32) + return get_unaligned_be32(&cmd->cdb[20]); + else + return cmd->lba & 0xFFFFFFFF; +} + +/* + * Returns T10-PI type 2 expected logical block application tag converted + * into CPU endianness. Valid only with protection type 2. + */ +static inline uint16_t scst_cmd_get_dif_exp_app_tag(struct scst_cmd *cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + BUG_ON(cmd->dev->dev_dif_type != 2); + WARN_ON(cmd->cdb_len > 32); +#endif + if (cmd->cdb_len == 32) + return get_unaligned_be16(&cmd->cdb[24]); + else { + /* cmd->dev must be alive at this point */ + return be16_to_cpu(cmd->dev->dev_dif_static_app_tag); + } +} + +/* + * Returns T10-PI type 2 logical block application tag mask converted + * into CPU endianness. Valid only with protection type 2. + */ +static inline uint16_t scst_cmd_get_dif_app_tag_mask(struct scst_cmd *cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + BUG_ON(cmd->dev->dev_dif_type != 2); + WARN_ON(cmd->cdb_len > 32); +#endif + if (cmd->cdb_len == 32) + return get_unaligned_be16(&cmd->cdb[26]); + else { + if (scst_get_dif_checks(cmd->cmd_dif_actions) & SCST_DIF_CHECK_APP_TAG) + return 0xFFFF; + else + return 0; + } +} + +/* + * Returns T10-PI type 3 application/reference tag. Valid only with type 3 + * protection type. Alternatively, scst_dev_get_dif_static_app_tag_combined() + * can be used. + * + * If SCST_DIF_NO_CHECK_ALL_REF_TAG returned, the target driver should not + * check the tag for this lba (SCSI requirement). + */ +static inline __be32 scst_cmd_get_dif_app_ref_tag(struct scst_cmd *cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + BUG_ON(cmd->dev->dev_dif_type != 3); + WARN_ON(!(scst_get_dif_checks(cmd->cmd_dif_actions) & SCST_DIF_CHECK_REF_TAG)); +#endif + return cmd->dev->dev_dif_static_app_ref_tag; +} + +/* + * Returns format of T10-PI GUARD TAGs as one of SCST_DIF_GUARD_FORMAT_* + * constants + */ +static inline int scst_cmd_get_dif_guard_format(struct scst_cmd *cmd) +{ + return cmd->tgt_dev->tgt_dev_dif_guard_format; +} + +/* + * Returns block size of this cmd's device. Supposed to be used + * by target drivers during T10-PI processing. + */ +static inline int scst_cmd_get_block_size(struct scst_cmd *cmd) +{ + return cmd->dev->block_size; +} + static inline unsigned int scst_get_active_cmd_count(struct scst_cmd *cmd) { if (likely(cmd->tgt_dev != NULL)) @@ -4210,6 +4998,45 @@ static inline void scst_put_buf(struct scst_cmd *cmd, void *buf) /* Nothing to do */ } +/* + * Functions for access to the commands DIF tags (SG) buffer. It doesn't + * return any error code, because this buffer is strictly connected to the + * data buffer, so for each block in that buffer, there must be tag here. + * + * Parameter psg on entrance specifies previous DIF tags SG in the access. + * Must point to NULL for the first access. On exit returns current DIF + * tags SG. + * + * The "put" function unmaps the buffer. + * + * !!! NOTE: this function does not check DIF SG cnt, hence must be used !!! + * !!! only inside code bound to the corresponding data SG cnt! !!! + */ +static inline uint8_t *scst_get_dif_buf(struct scst_cmd *cmd, + struct scatterlist **psg, int *length) +{ + uint8_t *buf; + struct scatterlist *sg; + + if (*psg == NULL) + sg = cmd->dif_sg; + else + sg = __sg_next_inline(*psg); + + buf = page_address(sg_page(sg)); + buf += sg->offset; + *length = sg->length; + + *psg = sg; + + return buf; +} + +static inline void scst_put_dif_buf(struct scst_cmd *cmd, void *buf) +{ + /* Nothing to do */ +} + static inline int scst_get_out_buf_first(struct scst_cmd *cmd, uint8_t **buf) { if (unlikely(cmd->out_sg == NULL)) { diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index e0aae987e..b028e826c 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -334,6 +334,9 @@ static inline int scst_sense_response_code(const uint8_t *sense) /* ABORTED_COMMAND is 0xb */ #define scst_sense_aborted_command ABORTED_COMMAND, 0x00, 0 +#define scst_logical_block_guard_check_failed ABORTED_COMMAND, 0x10, 1 +#define scst_logical_block_app_tag_check_failed ABORTED_COMMAND, 0x10, 2 +#define scst_logical_block_ref_tag_check_failed ABORTED_COMMAND, 0x10, 3 #define scst_sense_internal_failure ABORTED_COMMAND, 0x44, 0 /* retriable */ /* MISCOMPARE is 0xe */ @@ -427,6 +430,13 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define UNMAP 0x42 #endif +/* Subcodes of VARIABLE_LENGTH_CMD (0x7F) */ +#define SUBCODE_READ_32 0x09 +#define SUBCODE_VERIFY_32 0x0a +#define SUBCODE_WRITE_32 0x0b +#define SUBCODE_WRITE_VERIFY_32 0x0c +#define SUBCODE_WRITE_SAME_32 0x0d + #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) /* * From . See also commit @@ -445,7 +455,6 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define BLKDISCARD _IO(0x12,119) #endif - /************************************************************* ** SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft ** T10/1561-D Revision 4 Draft dated 7th November 2002. @@ -520,6 +529,44 @@ enum { #define SCST_QERR_2_RESERVED 2 #define SCST_QERR_3_ABORT_THIS_NEXUS_ONLY 3 +/************************************************************* + ** Values for the control mode page ATO field + *************************************************************/ + +/* I.e., app tags owned by storage */ +#define SCST_ATO_0_MODIFIED_BY_STORAGE 0 + +/* I.e., app tags owned by application */ +#define SCST_ATO_1_NOT_MODIFIED_BY_STORAGE 1 + +/************************************************************* + ** Values for the control mode page DPICZ field + *************************************************************/ +#define SCST_DPICZ_CHECK_ON_xPROT_0 0 +#define SCST_DPICZ_NO_CHECK_ON_xPROT_0 1 + +/************************************************************* + ** APP/REF TAG DIF constants + *************************************************************/ + +/* Skip all guards checks for this block */ +#define SCST_DIF_NO_CHECK_ALL_APP_TAG cpu_to_be16(0xFFFF) +#define SCST_DIF_NO_CHECK_ALL_REF_TAG cpu_to_be32(0xFFFFFFFF) + +/* Don't check app tag for this block */ +#define SCST_DIF_NO_CHECK_APP_TAG 0 + +/************************************************************* + ** Shift of size of DIF tag (8 bytes) + *************************************************************/ +#define SCST_DIF_TAG_SHIFT 3 + +/************************************************************* + ** Formats of DIF guard tags + *************************************************************/ +#define SCST_DIF_GUARD_FORMAT_CRC 0 +#define SCST_DIF_GUARD_FORMAT_IP 1 + /************************************************************* ** TransportID protocol identifiers *************************************************************/ diff --git a/scst/src/Makefile b/scst/src/Makefile index 1535e6db6..28107ff99 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -121,6 +121,7 @@ install: all install -m 644 $(MODULE_SYMVERS) $(INSTALL_DIR_H) -/sbin/depmod -b $(INSTALL_MOD_PATH)/ -a $(KVER) mkdir -p $(DESTDIR)/var/lib/scst/pr + mkdir -p $(DESTDIR)/var/lib/scst/dif_tags mkdir -p $(DESTDIR)/var/lib/scst/vdev_mode_pages @echo "****************************************************************" @echo "*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*" diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index f5e8003dc..12ef87e71 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -814,7 +814,7 @@ static int dev_user_parse(struct scst_cmd *cmd) ucmd->user_cmd.parse_cmd.expected_data_direction = cmd->expected_data_direction; ucmd->user_cmd.parse_cmd.expected_transfer_len = - cmd->expected_transfer_len; + cmd->expected_transfer_len_full; ucmd->user_cmd.parse_cmd.expected_out_transfer_len = cmd->expected_out_transfer_len; ucmd->user_cmd.parse_cmd.sn = cmd->tgt_sn; diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index da49cbd64..a00e6be26 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -134,6 +134,10 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define DEF_TAS 0 #define DEF_DSENSE SCST_D_SENSE_0_FIXED_SENSE +#define DEF_DPICZ SCST_DPICZ_CHECK_ON_xPROT_0 + +#define DEF_DIF_FILENAME_TMPL (SCST_VAR_DIR "/dif_tags/%s.dif") + #ifdef CONFIG_SCST_PROC #define VDISK_PROC_HELP "help" #endif @@ -162,6 +166,7 @@ struct scst_vdisk_dev { unsigned int prevent_allow_medium_removal:1; unsigned int nullio:1; unsigned int blockio:1; + unsigned int blk_integrity:1; unsigned int cdrom_empty:1; unsigned int dummy:1; unsigned int read_zero:1; @@ -176,6 +181,7 @@ struct scst_vdisk_dev { unsigned int discard_zeroes_data:1; struct file *fd; + struct file *dif_fd; struct block_device *bdev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) struct bio_set *vdisk_bioset; @@ -227,8 +233,13 @@ struct scst_vdisk_dev { int tgt_dev_cnt; - /* Only to pass it to attach() callback. Don't use it anywhere else! */ + char *dif_filename; + + /* Only to pass it to attach() callback. Don't use them anywhere else! */ int blk_shift; + enum scst_dif_mode dif_mode; + int dif_type; + __be64 dif_static_app_tag_combined; }; struct vdisk_cmd_params { @@ -267,6 +278,12 @@ static int num_threads = DEF_NUM_THREADS; module_param_named(num_threads, num_threads, int, S_IRUGO); MODULE_PARM_DESC(num_threads, "vdisk threads count"); +/* + * Used to serialize sense setting between blockio data and DIF tags + * unsuccessful readings/writings + */ +static spinlock_t vdev_err_lock; + static int vdisk_attach(struct scst_device *dev); static void vdisk_detach(struct scst_device *dev); static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev); @@ -291,6 +308,9 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p); static enum compl_status_e nullio_exec_write(struct vdisk_cmd_params *p); static enum compl_status_e blockio_exec_write(struct vdisk_cmd_params *p); static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p); +static enum compl_status_e nullio_exec_var_len_cmd(struct vdisk_cmd_params *p); +static enum compl_status_e blockio_exec_var_len_cmd(struct vdisk_cmd_params *p); +static enum compl_status_e fileio_exec_var_len_cmd(struct vdisk_cmd_params *p); static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua); static int vdisk_blockio_flush(struct block_device *bdev, gfp_t gfp_mask, bool report_error, struct scst_cmd *cmd, bool async); @@ -421,6 +441,8 @@ static ssize_t vdev_sysfs_inq_vend_specific_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdev_zero_copy_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); +static ssize_t vdev_dif_filename_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); @@ -495,6 +517,8 @@ static struct kobj_attribute vdev_inq_vend_specific_attr = vdev_sysfs_inq_vend_specific_store); static struct kobj_attribute vdev_zero_copy_attr = __ATTR(zero_copy, S_IRUGO, vdev_zero_copy_show, NULL); +static struct kobj_attribute vdev_dif_filename_attr = + __ATTR(dif_filename, S_IRUGO, vdev_dif_filename_show, NULL); static struct kobj_attribute vcdrom_filename_attr = __ATTR(filename, S_IRUGO|S_IWUSR, vdev_sysfs_filename_show, @@ -657,7 +681,11 @@ static struct scst_dev_type vdisk_file_devtype = { "thin_provisioned, " "tst, " "write_through, " - "zero_copy", + "zero_copy, " + "dif_mode, " + "dif_type, " + "dif_static_app_tag, " + "dif_filename", #endif #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS, @@ -695,6 +723,10 @@ static struct scst_dev_type vdisk_blk_devtype = { .dev_attrs = vdisk_blockio_attrs, .add_device_parameters = "blocksize, " + "dif_mode, " + "dif_type, " + "dif_static_app_tag, " + "dif_filename, " "filename, " "nv_cache, " "read_only, " @@ -739,6 +771,9 @@ static struct scst_dev_type vdisk_null_devtype = { .add_device_parameters = "blocksize, " "dummy, " + "dif_mode, " + "dif_type, " + "dif_static_app_tag, ", "read_only, " "removable, " "rotational, " @@ -822,14 +857,14 @@ static const char *vdev_get_filename(const struct scst_vdisk_dev *virt_dev) /* Returns fd, use IS_ERR(fd) to get error status */ static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev, - bool read_only) + const char *name, bool read_only) { int open_flags = 0; struct file *fd; TRACE_ENTRY(); - sBUG_ON(!virt_dev->filename); + sBUG_ON(!name); if (read_only) open_flags |= O_RDONLY; @@ -839,9 +874,11 @@ static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev, open_flags |= O_DIRECT; if (virt_dev->wt_flag && !virt_dev->nv_cache) open_flags |= O_DSYNC; - TRACE_DBG("Opening file %s, flags 0x%x", - virt_dev->filename, open_flags); - fd = filp_open(virt_dev->filename, O_LARGEFILE | open_flags, 0600); + + 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)); TRACE_EXIT(); return fd; @@ -1309,6 +1346,112 @@ out: return res; } +#if defined(CONFIG_BLK_DEV_INTEGRITY) +static int vdisk_init_block_integrity(struct scst_vdisk_dev *virt_dev) +{ + int res; + struct scst_device *dev = virt_dev->dev; + struct inode *inode; + struct file *fd; + struct blk_integrity *bi; + + TRACE_ENTRY(); + + fd = vdev_open_fd(virt_dev, virt_dev->filename, virt_dev->rd_only); + if (IS_ERR(fd)) { + res = -EINVAL; + goto out; + } + + inode = file_inode(fd); + + if (!S_ISBLK(inode->i_mode)) { + PRINT_ERROR("%s is NOT a block device!", virt_dev->filename); + res = -EINVAL; + goto out_close; + } + + bi = bdev_get_integrity(inode->i_bdev); + if (bi == NULL) { + TRACE_DBG("Block integrity not supported"); + goto out_no_bi; + } + + TRACE_DBG("BI name %s", bi->name); + + if (!strcmp(bi->name, "T10-DIF-TYPE1-CRC")) { + dev->dev_dif_ip_not_supported = 1; + if (virt_dev->dif_type != 1) { + PRINT_ERROR("Integrity type mismatch, %d expected, " + "but block device has 1 (dev %s)", + virt_dev->dif_type, dev->virt_name); + res = -EINVAL; + goto out_close; + } + } else if (!strcmp(bi->name, "T10-DIF-TYPE1-IP")) { + if (virt_dev->dif_type != 1) { + PRINT_ERROR("Integrity type mismatch, %d expected, " + "but block device has 1 (dev %s)", + virt_dev->dif_type, dev->virt_name); + res = -EINVAL; + goto out_close; + } + } else if (!strcmp(bi->name, "T10-DIF-TYPE3-CRC")) { + dev->dev_dif_ip_not_supported = 1; + if (virt_dev->dif_type != 3) { + PRINT_ERROR("Integrity type mismatch, %d expected, " + "but block device has 1 (dev %s)", + virt_dev->dif_type, dev->virt_name); + res = -EINVAL; + goto out_close; + } + } else if (!strcmp(bi->name, "T10-DIF-TYPE3-IP")) { + if (virt_dev->dif_type != 3) { + PRINT_ERROR("Integrity type mismatch, %d expected, " + "but block device has 3 (dev %s)", + virt_dev->dif_type, dev->virt_name); + res = -EINVAL; + goto out_close; + } + } else { + PRINT_ERROR("Unable to understand integrity name %s" + "(dev %s)", bi->name, dev->virt_name); + res = -EINVAL; + goto out_close; + } + + virt_dev->blk_integrity = 1; + + if ((virt_dev->dif_mode & SCST_DIF_MODE_DEV_CHECK) && + !(virt_dev->dif_mode & SCST_DIF_MODE_DEV_STORE)) { + PRINT_ERROR("Blockio dev_check is not possible without " + "dev_store (dev %s)", dev->virt_name); + res = -EINVAL; + goto out_close; + } + + if (!(virt_dev->dif_mode & SCST_DIF_MODE_DEV_CHECK)) + PRINT_WARNING("Blk integrity implies dev_check (dev %s)", + dev->virt_name); + +out_no_bi: + res = 0; + +out_close: + filp_close(fd, NULL); + +out: + TRACE_EXIT_RES(res); + return res; +} +#else /* defined(CONFIG_BLK_DEV_INTEGRITY) */ +static int vdisk_init_block_integrity(struct scst_vdisk_dev *virt_dev) +{ + PRINT_ERROR("Kernel does not support block device integrity"); + return -EINVAL; +} +#endif /* defined(CONFIG_BLK_DEV_INTEGRITY) */ + /* * Reexamine size, flush support and thin provisioning support for * vdisk_fileio, vdisk_blockio and vdisk_cdrom devices. Do not modify the size @@ -1369,6 +1512,74 @@ static int vdisk_attach(struct scst_device *dev) dev->block_shift = virt_dev->blk_shift; dev->block_size = 1 << dev->block_shift; + if ((virt_dev->dif_type == 0) && + ((virt_dev->dif_mode != SCST_DIF_MODE_NONE) || + (virt_dev->dif_filename != NULL))) { + PRINT_ERROR("Device %s cannot have DIF TYPE 0 if DIF MODE is " + "not NONE or DIF FILENAME is not NULL", virt_dev->name); + res = -EINVAL; + goto out; + } + + if (virt_dev->blockio) { + if (!(virt_dev->dif_mode & SCST_DIF_MODE_DEV)) + goto next; + + res = vdisk_init_block_integrity(virt_dev); + if (res != 0) + goto out; + } else if (virt_dev->dif_mode & SCST_DIF_MODE_DEV_CHECK) { + PRINT_ERROR("dev_check supported only for BLOCKIO devices " + "(dev %s)!", dev->virt_name); + res = -EINVAL; + goto out; + } + +next: + if ((virt_dev->dif_mode & SCST_DIF_MODE_DEV_STORE) && + (virt_dev->dif_filename == NULL) && !virt_dev->blk_integrity) { + virt_dev->dif_filename = kasprintf(GFP_KERNEL, + DEF_DIF_FILENAME_TMPL, dev->virt_name); + if (virt_dev->dif_filename == NULL) { + PRINT_ERROR("Allocation of default dif_filename " + "failed (dev %s)", dev->virt_name); + res = -ENOMEM; + goto out; + } + } + + if (virt_dev->dif_filename != NULL) { + /* Check if it can be used */ + struct file *dfd = vdev_open_fd(virt_dev, virt_dev->dif_filename, + virt_dev->rd_only); + if (IS_ERR(dfd)) { + res = PTR_ERR(dfd); + goto out; + } + filp_close(dfd, NULL); + } + + res = scst_set_dif_params(dev, virt_dev->dif_mode, virt_dev->dif_type); + if (res != 0) + goto out; + + if (virt_dev->dif_type != 2) + scst_dev_set_dif_static_app_tag_combined(dev, + virt_dev->dif_static_app_tag_combined); + else if (virt_dev->dif_static_app_tag_combined != SCST_DIF_NO_CHECK_APP_TAG) + PRINT_WARNING("Device %s: static app tag is ignored for DIF " + "mode 2", dev->virt_name); + + if (virt_dev->dif_filename != NULL) { + res = scst_create_dev_attr(dev, &vdev_dif_filename_attr); + if (res != 0) { + PRINT_ERROR("Can't create attr %s for dev %s", + vdev_dif_filename_attr.attr.name, + dev->virt_name); + goto out; + } + } + if (virt_dev->zero_copy && virt_dev->o_direct_flag) { PRINT_ERROR("%s: combining zero_copy with o_direct is not" " supported", virt_dev->filename); @@ -1422,6 +1633,13 @@ static int vdisk_attach(struct scst_device *dev) dev->tas = DEF_TAS; dev->tas_saved = DEF_TAS; dev->tas_default = DEF_TAS; + dev->dpicz = DEF_DPICZ; + dev->dpicz_saved = DEF_DPICZ; + dev->dpicz_default = DEF_DPICZ; + if ((virt_dev->dif_filename == NULL) && !virt_dev->blk_integrity) + dev->ato = SCST_ATO_0_MODIFIED_BY_STORAGE; + else + dev->ato = SCST_ATO_1_NOT_MODIFIED_BY_STORAGE; if (vdev_saved_mode_pages_enabled) vdev_load_mode_pages(virt_dev); @@ -1459,7 +1677,7 @@ static int vdisk_open_fd(struct scst_vdisk_dev *virt_dev, bool read_only) lockdep_assert_held(&scst_mutex); sBUG_ON(!virt_dev->filename); - virt_dev->fd = vdev_open_fd(virt_dev, read_only); + 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; @@ -1471,8 +1689,23 @@ static int vdisk_open_fd(struct scst_vdisk_dev *virt_dev, bool read_only) NULL; res = 0; + if (virt_dev->dif_filename != NULL) { + virt_dev->dif_fd = vdev_open_fd(virt_dev, + virt_dev->dif_filename, read_only); + if (IS_ERR(virt_dev->dif_fd)) { + res = PTR_ERR(virt_dev->dif_fd); + virt_dev->dif_fd = NULL; + goto out_close_fd; + } + } + out: return res; + +out_close_fd: + filp_close(virt_dev->fd, NULL); + virt_dev->fd = NULL; + goto out; } static void vdisk_close_fd(struct scst_vdisk_dev *virt_dev) @@ -1484,6 +1717,10 @@ static void vdisk_close_fd(struct scst_vdisk_dev *virt_dev) virt_dev->fd = NULL; virt_dev->bdev = NULL; } + if (virt_dev->dif_fd) { + filp_close(virt_dev->dif_fd, NULL); + virt_dev->dif_fd = NULL; + } } /* Invoked with scst_mutex held, so no further locking is necessary here. */ @@ -1505,8 +1742,10 @@ static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev) virt_dev->tgt_dev_cnt--; goto out; } - } else + } else { virt_dev->fd = NULL; + virt_dev->dif_fd = NULL; + } out: TRACE_EXIT_RES(res); @@ -1620,6 +1859,119 @@ static enum compl_status_e vdisk_exec_send_diagnostic(struct vdisk_cmd_params *p return CMD_SUCCEEDED; } +static int vdisk_format_dif(struct scst_cmd *cmd, uint64_t start_lba, + uint64_t blocks) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + loff_t loff; + mm_segment_t old_fs; + loff_t err = 0; + ssize_t full_len; + struct file *fd = virt_dev->dif_fd; + struct iovec *iv; + int max_iv_count, iv_count, i; + struct page *iv_page, *data_page; + uint8_t *data_buf; + int64_t left, done; + + TRACE_ENTRY(); + + if (virt_dev->dif_fd == NULL) + goto out; + + EXTRACHECKS_BUG_ON(!(dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE)); + + iv_page = alloc_page(GFP_KERNEL); + if (iv_page == NULL) { + PRINT_ERROR("Unable to allocate iv page"); + scst_set_busy(cmd); + res = -ENOMEM; + goto out; + } + + data_page = alloc_page(GFP_KERNEL); + if (data_page == NULL) { + PRINT_ERROR("Unable to allocate tags data page"); + scst_set_busy(cmd); + res = -ENOMEM; + goto out_free_iv; + } + + data_buf = page_address(data_page); + memset(data_buf, 0xFF, PAGE_SIZE); + + iv = page_address(iv_page); + max_iv_count = min_t(int, UIO_MAXIOV, (int)PAGE_SIZE/sizeof(*iv)); + + for (i = 0; i < max_iv_count; i++) + iv[i].iov_base = (uint8_t __force __user *)data_buf; + + old_fs = get_fs(); + set_fs(get_ds()); + + loff = start_lba << SCST_DIF_TAG_SHIFT; + left = blocks << SCST_DIF_TAG_SHIFT; + done = 0; + while (left > 0) { + iv_count = 0; + full_len = 0; + i = -1; + while (1) { + int len = min_t(size_t, (size_t)left, PAGE_SIZE); + full_len += len; + i++; + iv_count++; + iv[i].iov_len = len; + left -= len; + done += len; + EXTRACHECKS_BUG_ON(left < 0); + if ((iv_count == max_iv_count) || (left == 0)) + break; + } + + TRACE_DBG("Formatting DIF: full_len %zd, off %lld", + full_len, (long long)loff); + + /* WRITE */ + err = vfs_writev(fd, (struct iovec __force __user *)iv, iv_count, + &loff); + if (err < 0) { + PRINT_ERROR("Formatting DIF write() returned %lld from " + "%zd", (long long unsigned int)err, full_len); + if (err == -EAGAIN) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_write_error)); + res = err; + goto out_set_fs; + } else if (err < full_len) { + /* + * Probably that's wrong, but sometimes write() returns + * value less, than requested. Let's restart. + */ + left += full_len - err; + done -= full_len - err; + } + + virt_dev->format_progress_done = done; + }; + +out_set_fs: + set_fs(old_fs); + + __free_page(data_page); + +out_free_iv: + __free_page(iv_page); + +out: + TRACE_EXIT_RES(res); + return res; +} + static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) { int res = CMD_SUCCEEDED; @@ -1627,7 +1979,7 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) struct scst_device *dev = cmd->dev; struct scst_vdisk_dev *virt_dev = dev->dh_priv; uint8_t *buf; - int prot_type = 0, pinfo; + int prot_type = dev->dev_dif_type, pinfo; bool immed = false; TRACE_ENTRY(); @@ -1753,7 +2105,7 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) TRACE_DBG("prot_type %d, pinfo %d, immed %d (cmd %p)", prot_type, pinfo, immed, cmd); - if (prot_type != 0) { + if (prot_type != dev->dev_dif_type) { PRINT_ERROR("FORMAT UNIT: DIF type %d not supported (dev %s)", prot_type, dev->virt_name); scst_set_invalid_field_in_cdb(cmd, 1, @@ -1773,7 +2125,7 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) spin_unlock(&virt_dev->flags_lock); virt_dev->format_progress_done = 0; - virt_dev->format_progress_to_do = 100; + virt_dev->format_progress_to_do = virt_dev->nblocks << SCST_DIF_TAG_SHIFT; if (virt_dev->thin_provisioned) { int rc = vdisk_unmap_range(cmd, virt_dev, 0, virt_dev->nblocks); @@ -1781,6 +2133,9 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) goto finished; } + if (pinfo != 0) + vdisk_format_dif(cmd, 0, virt_dev->nblocks); + finished: spin_lock(&virt_dev->flags_lock); virt_dev->format_active = 0; @@ -1804,9 +2159,9 @@ static enum compl_status_e vdisk_invalid_opcode(struct vdisk_cmd_params *p) return INVALID_OPCODE; } -#define VDEV_DEF_RDPROTECT 0 -#define VDEV_DEF_WRPROTECT 0 -#define VDEV_DEF_VRPROTECT 0 +#define VDEV_DEF_RDPROTECT 0xE0 +#define VDEV_DEF_WRPROTECT 0xE0 +#define VDEV_DEF_VRPROTECT 0xE0 #define VDEF_DEF_GROUP_NUM 0 @@ -1898,6 +2253,22 @@ static const struct scst_opcode_descriptor scst_op_descr_read16 = { SCST_OD_DEFAULT_CONTROL_BYTE }, }; +static const struct scst_opcode_descriptor scst_op_descr_read32 = { + .od_opcode = VARIABLE_LENGTH_CMD, + .od_serv_action = SUBCODE_READ_32, + .od_serv_action_valid = 1, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 32, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { VARIABLE_LENGTH_CMD, SCST_OD_DEFAULT_CONTROL_BYTE, + 0, 0, 0, 0, VDEF_DEF_GROUP_NUM, 0x18, 0, + SUBCODE_READ_32, VDEV_DEF_RDPROTECT | 0x18, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }, +}; + static const struct scst_opcode_descriptor scst_op_descr_read_capacity = { .od_opcode = READ_CAPACITY, .od_support = 3, /* supported as in the standard */ @@ -1998,6 +2369,22 @@ static const struct scst_opcode_descriptor scst_op_descr_verify16 = { SCST_OD_DEFAULT_CONTROL_BYTE }, }; +static const struct scst_opcode_descriptor scst_op_descr_verify32 = { + .od_opcode = VARIABLE_LENGTH_CMD, + .od_serv_action = SUBCODE_VERIFY_32, + .od_serv_action_valid = 1, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 32, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { VARIABLE_LENGTH_CMD, SCST_OD_DEFAULT_CONTROL_BYTE, + 0, 0, 0, 0, VDEF_DEF_GROUP_NUM, 0x18, 0, + SUBCODE_VERIFY_32, VDEV_DEF_VRPROTECT | 0x16, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }, +}; + static const struct scst_opcode_descriptor scst_op_descr_write6 = { .od_opcode = WRITE_6, .od_support = 3, /* supported as in the standard */ @@ -2042,6 +2429,22 @@ static const struct scst_opcode_descriptor scst_op_descr_write16 = { SCST_OD_DEFAULT_CONTROL_BYTE }, }; +static const struct scst_opcode_descriptor scst_op_descr_write32 = { + .od_opcode = VARIABLE_LENGTH_CMD, + .od_serv_action = SUBCODE_WRITE_32, + .od_serv_action_valid = 1, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 32, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { VARIABLE_LENGTH_CMD, SCST_OD_DEFAULT_CONTROL_BYTE, + 0, 0, 0, 0, VDEF_DEF_GROUP_NUM, 0x18, 0, + SUBCODE_WRITE_32, VDEV_DEF_WRPROTECT | 0x1A, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }, +}; + static const struct scst_opcode_descriptor scst_op_descr_write_verify10 = { .od_opcode = WRITE_VERIFY, .od_support = 3, /* supported as in the standard */ @@ -2076,6 +2479,22 @@ static const struct scst_opcode_descriptor scst_op_descr_write_verify16 = { SCST_OD_DEFAULT_CONTROL_BYTE }, }; +static const struct scst_opcode_descriptor scst_op_descr_write_verify32 = { + .od_opcode = VARIABLE_LENGTH_CMD, + .od_serv_action = SUBCODE_WRITE_VERIFY_32, + .od_serv_action_valid = 1, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 32, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { VARIABLE_LENGTH_CMD, SCST_OD_DEFAULT_CONTROL_BYTE, + 0, 0, 0, 0, VDEF_DEF_GROUP_NUM, 0x18, 0, + SUBCODE_WRITE_VERIFY_32, VDEV_DEF_WRPROTECT | 0x16, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }, +}; + static const struct scst_opcode_descriptor scst_op_descr_write_same10 = { .od_opcode = WRITE_SAME, .od_support = 3, /* supported as in the standard */ @@ -2099,6 +2518,22 @@ static const struct scst_opcode_descriptor scst_op_descr_write_same16 = { SCST_OD_DEFAULT_CONTROL_BYTE }, }; +static const struct scst_opcode_descriptor scst_op_descr_write_same32 = { + .od_opcode = VARIABLE_LENGTH_CMD, + .od_serv_action = SUBCODE_WRITE_SAME_32, + .od_serv_action_valid = 1, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 32, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { VARIABLE_LENGTH_CMD, SCST_OD_DEFAULT_CONTROL_BYTE, + 0, 0, 0, 0, VDEF_DEF_GROUP_NUM, 0x18, 0, + SUBCODE_WRITE_SAME_32, VDEV_DEF_WRPROTECT | 0x8, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }, +}; + static const struct scst_opcode_descriptor scst_op_descr_read_toc = { .od_opcode = READ_TOC, .od_support = 3, /* supported as in the standard */ @@ -2166,6 +2601,14 @@ static const struct scst_opcode_descriptor scst_op_descr_read_toc = { &scst_op_descr_verify12, \ &scst_op_descr_verify16, +static const vdisk_op_fn blockio_var_len_ops[] = { + [SUBCODE_READ_32] = blockio_exec_read, + [SUBCODE_WRITE_32] = blockio_exec_write, + [SUBCODE_WRITE_VERIFY_32] = blockio_exec_write_verify, + [SUBCODE_VERIFY_32] = vdev_exec_verify, + [SUBCODE_WRITE_SAME_32] = vdisk_exec_write_same, +}; + static vdisk_op_fn blockio_ops[256] = { [READ_6] = blockio_exec_read, [READ_10] = blockio_exec_read, @@ -2178,12 +2621,21 @@ static vdisk_op_fn blockio_ops[256] = { [WRITE_VERIFY] = blockio_exec_write_verify, [WRITE_VERIFY_12] = blockio_exec_write_verify, [WRITE_VERIFY_16] = blockio_exec_write_verify, + [VARIABLE_LENGTH_CMD] = blockio_exec_var_len_cmd, [VERIFY] = vdev_exec_verify, [VERIFY_12] = vdev_exec_verify, [VERIFY_16] = vdev_exec_verify, SHARED_OPS }; +static const vdisk_op_fn fileio_var_len_ops[] = { + [SUBCODE_READ_32] = fileio_exec_read, + [SUBCODE_WRITE_32] = fileio_exec_write, + [SUBCODE_WRITE_VERIFY_32] = fileio_exec_write_verify, + [SUBCODE_VERIFY_32] = vdev_exec_verify, + [SUBCODE_WRITE_SAME_32] = vdisk_exec_write_same, +}; + static vdisk_op_fn fileio_ops[256] = { [READ_6] = fileio_exec_read, [READ_10] = fileio_exec_read, @@ -2196,12 +2648,20 @@ static vdisk_op_fn fileio_ops[256] = { [WRITE_VERIFY] = fileio_exec_write_verify, [WRITE_VERIFY_12] = fileio_exec_write_verify, [WRITE_VERIFY_16] = fileio_exec_write_verify, + [VARIABLE_LENGTH_CMD] = fileio_exec_var_len_cmd, [VERIFY] = vdev_exec_verify, [VERIFY_12] = vdev_exec_verify, [VERIFY_16] = vdev_exec_verify, SHARED_OPS }; +static const vdisk_op_fn nullio_var_len_ops[] = { + [SUBCODE_READ_32] = nullio_exec_read, + [SUBCODE_WRITE_32] = nullio_exec_write, + [SUBCODE_WRITE_VERIFY_32] = nullio_exec_write_verify, + [SUBCODE_WRITE_SAME_32] = vdisk_exec_write_same, +}; + static vdisk_op_fn nullio_ops[256] = { [READ_6] = nullio_exec_read, [READ_10] = nullio_exec_read, @@ -2214,6 +2674,7 @@ static vdisk_op_fn nullio_ops[256] = { [WRITE_VERIFY] = nullio_exec_write_verify, [WRITE_VERIFY_12] = nullio_exec_write_verify, [WRITE_VERIFY_16] = nullio_exec_write_verify, + [VARIABLE_LENGTH_CMD] = nullio_exec_var_len_cmd, [VERIFY] = nullio_exec_verify, [VERIFY_12] = nullio_exec_verify, [VERIFY_16] = nullio_exec_verify, @@ -2235,6 +2696,17 @@ static const struct scst_opcode_descriptor *vdisk_opcode_descriptors[] = { SCST_OPCODE_DESCRIPTORS }; +static const struct scst_opcode_descriptor *vdisk_opcode_descriptors_type2[] = { + SHARED_OPCODE_DESCRIPTORS + VDISK_OPCODE_DESCRIPTORS + &scst_op_descr_read32, + &scst_op_descr_write32, + &scst_op_descr_verify32, + &scst_op_descr_write_verify32, + &scst_op_descr_write_same32, + SCST_OPCODE_DESCRIPTORS +}; + static const struct scst_opcode_descriptor *vcdrom_opcode_descriptors[] = { SHARED_OPCODE_DESCRIPTORS &scst_op_descr_allow_medium_removal, @@ -2246,8 +2718,13 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd, const struct scst_opcode_descriptor ***out_supp_opcodes, int *out_supp_opcodes_cnt) { - *out_supp_opcodes = vdisk_opcode_descriptors; - *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors); + if (cmd->dev->dev_dif_type != 2) { + *out_supp_opcodes = vdisk_opcode_descriptors; + *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors); + } else { + *out_supp_opcodes = vdisk_opcode_descriptors_type2; + *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors); + } return 0; } @@ -2268,6 +2745,10 @@ static bool vdisk_use_zero_copy(const struct scst_cmd *cmd) return false; switch (cmd->cdb[0]) { + case VARIABLE_LENGTH_CMD: + if (cmd->cdb[9] != SUBCODE_READ_32) + break; + /* else go througth */ case READ_6: case READ_10: case READ_12: @@ -2353,6 +2834,15 @@ static bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) } switch (opcode) { + case VARIABLE_LENGTH_CMD: + if (cmd->cdb[9] == SUBCODE_WRITE_32) { + fua = (cdb[10] & 0x8); + if (fua) + TRACE(TRACE_ORDER, "FUA: loff=%lld, data_len=%lld", + (long long unsigned int)loff, + (long long unsigned int)data_len); + } + break; case WRITE_10: case WRITE_12: case WRITE_16: @@ -3130,6 +3620,12 @@ static int vdisk_unmap_range(struct scst_cmd *cmd, goto out; } + if (virt_dev->dif_fd != NULL) { + res = vdisk_format_dif(cmd, start_lba, blocks); + if (unlikely(res != 0)) + goto out; + } + success: res = 0; @@ -3198,36 +3694,29 @@ static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; enum compl_status_e res = CMD_SUCCEEDED; + uint8_t ctrl_offs = (cmd->cdb_len < 32) ? 1 : 10; TRACE_ENTRY(); - if (unlikely(cmd->cdb[1] & 1)) { + if (unlikely(cmd->cdb[ctrl_offs] & 1)) { TRACE_DBG("%s", "ANCHOR not supported"); - scst_set_invalid_field_in_cdb(cmd, 1, + scst_set_invalid_field_in_cdb(cmd, ctrl_offs, SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); goto out; } - 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; - } - if (unlikely(cmd->data_len <= 0)) { scst_set_invalid_field_in_cdb(cmd, cmd->len_off, 0); goto out; } - if (cmd->cdb[1] & 0x8) { + if (cmd->cdb[ctrl_offs] & 0x8) vdisk_exec_write_same_unmap(p); - goto out; + else { + scst_write_same(cmd); + res = RUNNING_ASYNC; } - scst_write_same(cmd); - res = RUNNING_ASYNC; - out: TRACE_EXIT_RES(res); return res; @@ -3457,8 +3946,31 @@ static int vdisk_dev_id_vpd(uint8_t *buf, struct scst_cmd *cmd, static int vdisk_ext_inq(uint8_t *buf, struct scst_cmd *cmd, struct scst_vdisk_dev *virt_dev) { + struct scst_device *dev = cmd->dev; + buf[1] = 0x86; buf[3] = 0x3C; + if (dev->dev_dif_mode != SCST_DIF_MODE_NONE) { + switch (dev->dev_dif_type) { + case 1: + buf[4] = 0; /* SPT=0, type 1 only supported */ + break; + case 2: + buf[4] = 0x10; /* SPT=010, type 2 only supported */ + break; + case 3: + buf[4] = 0x20; /* SPT=100, type 3 only supported */ + break; + default: + sBUG_ON(1); + break; + } + buf[4] |= 4; /* GRD_CHK */ + if (dev->dif_app_chk) + buf[4] |= 2; /* APP_CHK */ + if (dev->dif_ref_chk) + buf[4] |= 1; /* REF_CHK */ + } buf[5] = 7; /* HEADSUP=1, ORDSUP=1, SIMPSUP=1 */ buf[6] = (virt_dev->wt_flag || virt_dev->nv_cache) ? 0 : 1; /* V_SUP */ buf[7] = 1; /* LUICLR=1 */ @@ -3556,8 +4068,10 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, if (cmd->tgtt->fake_aca) buf[3] |= 0x20; buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */ + if (cmd->dev->dev_dif_mode != SCST_DIF_MODE_NONE) + buf[5] |= 1; /* PROTECT */ if (scst_impl_alua_configured(cmd->dev)) - buf[5] = SCST_INQ_TPGS_MODE_IMPLICIT; + buf[5] |= SCST_INQ_TPGS_MODE_IMPLICIT; buf[6] = 0x10; /* MultiP 1 */ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ @@ -3900,12 +4414,14 @@ static int vdisk_ctrl_m_pg(unsigned char *p, int pcontrol, switch (pcontrol) { case 0: /* current */ p[2] |= virt_dev->dev->tst << 5; - p[2] |= virt_dev->dev->tmf_only << 4; p[2] |= virt_dev->dev->d_sense << 2; + p[2] |= virt_dev->dev->dpicz << 3; + p[2] |= virt_dev->dev->tmf_only << 4; p[3] |= virt_dev->dev->queue_alg << 4; p[3] |= virt_dev->dev->qerr << 1; p[4] |= virt_dev->dev->swp << 3; p[5] |= virt_dev->dev->tas << 6; + p[5] |= virt_dev->dev->ato << 7; break; case 1: /* changeable */ memset(p + 2, 0, sizeof(ctrl_m_pg) - 2); @@ -3917,29 +4433,35 @@ 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 */ break; case 2: /* default */ p[2] |= virt_dev->tst << 5; p[2] |= virt_dev->dev->d_sense_default << 2; + p[2] |= virt_dev->dev->dpicz_default << 3; p[2] |= virt_dev->dev->tmf_only_default << 4; p[3] |= virt_dev->dev->queue_alg_default << 4; p[3] |= virt_dev->dev->qerr_default << 1; p[4] |= virt_dev->dev->swp_default << 3; p[5] |= virt_dev->dev->tas_default << 6; + p[5] |= virt_dev->dev->ato << 7; break; case 3: /* saved */ p[2] |= virt_dev->dev->tst << 5; p[2] |= virt_dev->dev->d_sense_saved << 2; + p[2] |= virt_dev->dev->dpicz_saved << 3; p[2] |= virt_dev->dev->tmf_only_default << 4; p[3] |= virt_dev->dev->queue_alg_saved << 4; p[3] |= virt_dev->dev->qerr_saved << 1; p[4] |= virt_dev->dev->swp_saved << 3; p[5] |= virt_dev->dev->tas_saved << 6; + p[5] |= virt_dev->dev->ato << 7; break; default: sBUG(); @@ -4118,7 +4640,7 @@ out_not_sup: static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) { int res = 0; - struct file *fd; + struct file *fd, *dif_fd = NULL; bool old_wt = virt_dev->wt_flag; TRACE_ENTRY(); @@ -4135,25 +4657,40 @@ static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) * to reopen fd. */ - fd = vdev_open_fd(virt_dev, read_only); + fd = vdev_open_fd(virt_dev, virt_dev->filename, read_only); if (IS_ERR(fd)) { - PRINT_ERROR("filp_open(%s) returned an error %ld", - virt_dev->filename, PTR_ERR(fd)); - spin_lock(&virt_dev->flags_lock); - virt_dev->wt_flag = old_wt; - spin_unlock(&virt_dev->flags_lock); res = PTR_ERR(fd); - goto out; + goto out_err; + } + + if (virt_dev->dif_filename != NULL) { + dif_fd = vdev_open_fd(virt_dev, virt_dev->dif_filename, read_only); + if (IS_ERR(dif_fd)) { + res = PTR_ERR(dif_fd); + goto out_err_close_fd; + } } if (virt_dev->fd) filp_close(virt_dev->fd, NULL); + if (virt_dev->dif_fd) + filp_close(virt_dev->dif_fd, NULL); virt_dev->fd = fd; + virt_dev->dif_fd = dif_fd; out: TRACE_EXIT_RES(res); return res; + +out_err_close_fd: + filp_close(fd, NULL); + +out_err: + spin_lock(&virt_dev->flags_lock); + virt_dev->wt_flag = old_wt; + spin_unlock(&virt_dev->flags_lock); + goto out; } static void vdisk_ctrl_m_pg_select(unsigned char *p, @@ -4162,9 +4699,9 @@ static void vdisk_ctrl_m_pg_select(unsigned char *p, { struct scst_device *dev = virt_dev->dev; int old_swp = dev->swp, old_tas = dev->tas, old_dsense = dev->d_sense; - int old_queue_alg = dev->queue_alg; + int old_queue_alg = dev->queue_alg, old_dpicz = dev->dpicz; int rc, old_tmf_only = dev->tmf_only, old_qerr = dev->qerr; - int queue_alg, swp, tas, tmf_only, qerr, d_sense; + int queue_alg, swp, tas, tmf_only, qerr, d_sense, dpicz; TRACE_ENTRY(); @@ -4235,6 +4772,16 @@ static void vdisk_ctrl_m_pg_select(unsigned char *p, goto out; } + dpicz = (p[2] & 0x8) >> 3; + if (dpicz > 1) { + PRINT_WARNING("Attempt to set invalid Control mode page " + "dpicz value %d (initiator %s, dev %s)", dpicz, + cmd->sess->initiator_name, dev->virt_name); + scst_set_invalid_field_in_parm_list(cmd, param_offset + 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 3); + goto out; + } + qerr = (p[3] & 0x6) >> 1; if ((qerr == SCST_QERR_2_RESERVED) || (qerr > SCST_QERR_3_ABORT_THIS_NEXUS_ONLY)) { @@ -4260,12 +4807,14 @@ static void vdisk_ctrl_m_pg_select(unsigned char *p, dev->swp = swp; dev->tas = tas; dev->tmf_only = tmf_only; + dev->dpicz = dpicz; dev->qerr = qerr; dev->d_sense = d_sense; if ((dev->swp == old_swp) && (dev->tas == old_tas) && (dev->d_sense == old_dsense) && (dev->queue_alg == old_queue_alg) && - (dev->qerr == old_qerr) && (dev->tmf_only == old_tmf_only)) + (dev->qerr == old_qerr) && (dev->tmf_only == old_tmf_only) && + (dev->dpicz == old_dpicz)) goto out; if (!save) @@ -4279,6 +4828,7 @@ static void vdisk_ctrl_m_pg_select(unsigned char *p, dev->queue_alg = old_queue_alg; dev->tmf_only = old_tmf_only; dev->qerr = old_qerr; + dev->dpicz = old_dpicz; /* Hopefully, the error is temporary */ scst_set_busy(cmd); goto out; @@ -4290,14 +4840,16 @@ static void vdisk_ctrl_m_pg_select(unsigned char *p, dev->queue_alg_saved = dev->queue_alg; dev->tmf_only_saved = dev->tmf_only; dev->qerr_saved = dev->qerr; + dev->dpicz_saved = dev->dpicz; out_ok: PRINT_INFO("Device %s: new control mode page parameters: SWP %x " "(was %x), TAS %x (was %x), TMF_ONLY %d (was %x), QErr %x " - "(was %x), D_SENSE %d (was %d), QUEUE ALG %d (was %d)", - virt_dev->name, dev->swp, old_swp, dev->tas, old_tas, - dev->tmf_only, old_tmf_only, dev->qerr, old_qerr, - dev->d_sense, old_dsense, dev->queue_alg, old_queue_alg); + "(was %x), D_SENSE %d (was %d), QUEUE ALG %d (was %d), " + "DPICZ %d (was %d)", virt_dev->name, dev->swp, old_swp, + dev->tas, old_tas, dev->tmf_only, old_tmf_only, dev->qerr, + old_qerr, dev->d_sense, old_dsense, dev->queue_alg, + old_queue_alg, dev->dpicz, old_dpicz); out: TRACE_EXIT(); @@ -4542,6 +5094,23 @@ static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p put_unaligned_be64(nblocks, &buffer[0]); put_unaligned_be32(blocksize, &buffer[8]); + if (cmd->dev->dev_dif_mode != SCST_DIF_MODE_NONE) { + switch (cmd->dev->dev_dif_type) { + case 1: + buffer[12] = 1; + break; + case 2: + buffer[12] = 3; + break; + case 3: + buffer[12] = 5; + break; + default: + sBUG_ON(1); + break; + } + } + switch (blocksize) { case 512: buffer[13] = 3; @@ -4731,12 +5300,11 @@ static enum compl_status_e vdisk_exec_prevent_allow_medium_removal(struct vdisk_ return CMD_SUCCEEDED; } -static int vdisk_fsync_blockio(loff_t loff, - loff_t len, struct scst_device *dev, gfp_t gfp_flags, - struct scst_cmd *cmd, bool async) +static int __vdisk_fsync_fileio(loff_t loff, + loff_t len, struct scst_device *dev, struct scst_cmd *cmd, + struct file *file) { int res; - struct scst_vdisk_dev *virt_dev = dev->dh_priv; TRACE_ENTRY(); @@ -4745,32 +5313,7 @@ static int vdisk_fsync_blockio(loff_t loff, ** anything without checking for NULL at first !!! **/ - EXTRACHECKS_BUG_ON(!virt_dev->blockio); - - res = vdisk_blockio_flush(virt_dev->bdev, gfp_flags, true, - cmd, async); - - TRACE_EXIT_RES(res); - return res; -} - -static int vdisk_fsync_fileio(loff_t loff, - loff_t len, struct scst_device *dev, struct scst_cmd *cmd, bool async) -{ - int res; - struct scst_vdisk_dev *virt_dev = dev->dh_priv; - struct file *file; - - TRACE_ENTRY(); - - /** - ** !!! CAUTION !!!: cmd can be NULL here! Don't use it for - ** anything without checking for NULL at first !!! - **/ - - EXTRACHECKS_BUG_ON(virt_dev->blockio); - - file = virt_dev->fd; + /* BLOCKIO can be here for DIF tags fsync */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) res = sync_page_range(file_inode(file), file->f_mapping, loff, len); @@ -4792,6 +5335,69 @@ static int vdisk_fsync_fileio(loff_t loff, } } + TRACE_EXIT_RES(res); + return res; +} + +static int vdisk_fsync_blockio(loff_t loff, + loff_t len, struct scst_device *dev, gfp_t gfp_flags, + struct scst_cmd *cmd, bool async) +{ + int res; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /** + ** !!! CAUTION !!!: cmd can be NULL here! Don't use it for + ** anything without checking for NULL at first !!! + **/ + + EXTRACHECKS_BUG_ON(!virt_dev->blockio); + + /* Must be first, because vdisk_blockio_flush() can call scst_cmd_done()! */ + if (virt_dev->dif_fd != NULL) { + loff = (loff >> dev->block_shift) << SCST_DIF_TAG_SHIFT; + len = (len >> dev->block_shift) << SCST_DIF_TAG_SHIFT; + res = __vdisk_fsync_fileio(loff, len, dev, cmd, + virt_dev->dif_fd); + if (unlikely(res != 0)) + goto out; + } + + res = vdisk_blockio_flush(virt_dev->bdev, gfp_flags, true, + cmd, async); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int vdisk_fsync_fileio(loff_t loff, + loff_t len, struct scst_device *dev, struct scst_cmd *cmd, bool async) +{ + int res; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /** + ** !!! CAUTION !!!: cmd can be NULL here! Don't use it for + ** anything without checking for NULL at first !!! + **/ + + res = __vdisk_fsync_fileio(loff, len, dev, cmd, virt_dev->fd); + if (unlikely(res != 0)) + goto done; + + if (virt_dev->dif_fd != NULL) { + loff = (loff >> dev->block_shift) << SCST_DIF_TAG_SHIFT; + len = (len >> dev->block_shift) << SCST_DIF_TAG_SHIFT; + res = __vdisk_fsync_fileio(loff, len, dev, cmd, + virt_dev->dif_fd); + } + +done: if (async) { if (cmd != NULL) { cmd->completed = 1; @@ -4890,10 +5496,302 @@ static enum compl_status_e nullio_exec_read(struct vdisk_cmd_params *p) } } + scst_dif_process_read(p->cmd); + TRACE_EXIT(); return CMD_SUCCEEDED; } +static int vdev_read_dif_tags(struct vdisk_cmd_params *p) +{ + int res = 0; + struct scst_cmd *cmd = p->cmd; + loff_t loff; + mm_segment_t old_fs; + loff_t err = 0; + ssize_t length, full_len; + uint8_t *address; + struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + struct file *fd = virt_dev->dif_fd; + struct iovec *iv; + int iv_count, max_iv_count, i; + bool finished = false; + int tags_num, l; + struct scatterlist *tags_sg; + bool free_iv = false; + + TRACE_ENTRY(); + + /* + * !! Data for this cmd can be read simultaneously !! + */ + + EXTRACHECKS_BUG_ON(virt_dev->nullio); + +#if 0 /* no zero-copy (yet) */ + if (p->use_zero_copy) + goto out; +#endif + + EXTRACHECKS_BUG_ON(!(cmd->dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) || + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) == SCST_DIF_ACTION_NONE)); + + tags_num = (cmd->bufflen >> cmd->dev->block_shift); + if (unlikely(tags_num == 0)) + goto out; + + iv = p->iv; + if (iv == NULL) { + 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); + spin_unlock_irqrestore(&vdev_err_lock, flags); + res = -ENOMEM; + goto out; + } + free_iv = true; + } + max_iv_count = p->iv_count; + + old_fs = get_fs(); + set_fs(get_ds()); + + tags_sg = NULL; + loff = (p->loff >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; + while (1) { + iv_count = 0; + full_len = 0; + i = -1; + address = scst_get_dif_buf(cmd, &tags_sg, &l); + length = l; + EXTRACHECKS_BUG_ON(length <= 0); + while (1) { + full_len += length; + i++; + iv_count++; + iv[i].iov_base = (uint8_t __force __user *)address; + iv[i].iov_len = length; + tags_num -= length >> SCST_DIF_TAG_SHIFT; + EXTRACHECKS_BUG_ON(tags_num < 0); + if ((iv_count == max_iv_count) || (tags_num == 0)) + break; + address = scst_get_dif_buf(cmd, &tags_sg, &l); + length = l; + EXTRACHECKS_BUG_ON(length <= 0); + } + if (tags_num == 0) + finished = true; + + TRACE_DBG("Reading DIF iv_count %d, full_len %zd, loff %lld", + iv_count, full_len, (long long)loff); + + /* READ */ + err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count, + &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); + /* To protect sense setting with blockio */ + spin_lock_irqsave(&vdev_err_lock, flags); + if (err == -EAGAIN) + scst_set_busy(cmd); + else { + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_read_error)); + } + spin_unlock_irqrestore(&vdev_err_lock, flags); + res = err; + goto out_set_fs; + } + + for (i = 0; i < iv_count; i++) + scst_put_dif_buf(cmd, (void __force *)(iv[i].iov_base)); + + if (finished) + break; + }; + + set_fs(old_fs); + +out_free_iv: + if (free_iv && (iv != p->small_iv)) + kfree(p->iv); + +out: + TRACE_EXIT_RES(res); + return res; + +out_set_fs: + set_fs(old_fs); + for (i = 0; i < iv_count; i++) + scst_put_dif_buf(cmd, (void __force *)(iv[i].iov_base)); + goto out_free_iv; +} + +static int vdev_write_dif_tags(struct vdisk_cmd_params *p) +{ + int res = 0; + struct scst_cmd *cmd = p->cmd; + loff_t loff; + mm_segment_t old_fs; + loff_t err = 0; + ssize_t length, full_len; + uint8_t *address; + struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + struct file *fd = virt_dev->dif_fd; + struct iovec *iv, *eiv; + int iv_count, eiv_count, max_iv_count, i; + bool finished = false; + int tags_num, l; + struct scatterlist *tags_sg; + bool free_iv = false; + + TRACE_ENTRY(); + + /* + * !! Data for this cmd can be written simultaneously !! + */ + + EXTRACHECKS_BUG_ON(virt_dev->nullio); + +#if 0 /* no zero-copy (yet) */ + if (p->use_zero_copy) + goto out; +#endif + + EXTRACHECKS_BUG_ON(!(cmd->dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) || + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) == SCST_DIF_ACTION_NONE)); + + tags_num = (cmd->bufflen >> cmd->dev->block_shift); + if (unlikely(tags_num == 0)) + goto out; + + iv = p->iv; + if (iv == NULL) { + 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); + spin_unlock_irqrestore(&vdev_err_lock, flags); + res = -ENOMEM; + goto out; + } + free_iv = true; + } + max_iv_count = p->iv_count; + + old_fs = get_fs(); + set_fs(get_ds()); + + tags_sg = NULL; + loff = (p->loff >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; + while (1) { + iv_count = 0; + full_len = 0; + i = -1; + address = scst_get_dif_buf(cmd, &tags_sg, &l); + length = l; + EXTRACHECKS_BUG_ON(length <= 0); + while (1) { + full_len += length; + i++; + iv_count++; + iv[i].iov_base = (uint8_t __force __user *)address; + iv[i].iov_len = length; + tags_num -= length >> SCST_DIF_TAG_SHIFT; + EXTRACHECKS_BUG_ON(tags_num < 0); + if ((iv_count == max_iv_count) || (tags_num == 0)) + break; + address = scst_get_dif_buf(cmd, &tags_sg, &l); + length = l; + EXTRACHECKS_BUG_ON(length <= 0); + } + if (tags_num == 0) + finished = true; + + eiv = iv; + eiv_count = iv_count; +restart: + TRACE_DBG("Writing DIF: eiv_count %d, full_len %zd", eiv_count, full_len); + + /* WRITE */ + err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count, + &loff); + if (err < 0) { + unsigned long flags; + PRINT_ERROR("DIF write() returned %lld from %zd", + (long long unsigned int)err, full_len); + /* To protect sense setting with blockio */ + spin_lock_irqsave(&vdev_err_lock, flags); + if (err == -EAGAIN) + scst_set_busy(cmd); + else { + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_write_error)); + } + spin_unlock_irqrestore(&vdev_err_lock, flags); + res = err; + goto out_set_fs; + } else if (err < full_len) { + /* + * Probably that's wrong, but sometimes write() returns + * 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); + if (err == 0) { + PRINT_INFO("Suspicious: DIF write() returned 0 from " + "%zd (iv_count=%d)", full_len, eiv_count); + } + full_len -= err; + for (i = 0; i < e; i++) { + if ((long long)eiv->iov_len < err) { + err -= eiv->iov_len; + eiv++; + eiv_count--; + } else { + eiv->iov_base = + (uint8_t __force __user *)eiv->iov_base + err; + eiv->iov_len -= err; + break; + } + } + goto restart; + } + + for (i = 0; i < iv_count; i++) + scst_put_dif_buf(cmd, (void __force *)(iv[i].iov_base)); + + if (finished) + break; + }; + + set_fs(old_fs); + +out_free_iv: + if (free_iv && (iv != p->small_iv)) + kfree(iv); + +out: + TRACE_EXIT_RES(res); + return res; + +out_set_fs: + set_fs(old_fs); + for (i = 0; i < iv_count; i++) + scst_put_dif_buf(cmd, (void __force *)(iv[i].iov_base)); + goto out_free_iv; +} + static enum compl_status_e blockio_exec_read(struct vdisk_cmd_params *p) { blockio_exec_rw(p, false, false); @@ -4908,10 +5806,11 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p) loff_t err = 0; ssize_t length, full_len; uint8_t __user *address; - struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; struct file *fd = virt_dev->fd; struct iovec *iv; - int iv_count, i; + int iv_count, i, max_iv_count; bool finished = false; TRACE_ENTRY(); @@ -4919,12 +5818,14 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p) EXTRACHECKS_BUG_ON(virt_dev->nullio); if (p->use_zero_copy) - goto out; + goto out_dif; iv = vdisk_alloc_iv(cmd, p); if (iv == NULL) goto out_nomem; + max_iv_count = p->iv_count; + length = scst_get_buf_first(cmd, (uint8_t __force **)&address); if (unlikely(length < 0)) { PRINT_ERROR("scst_get_buf_first() failed: %zd", length); @@ -4946,7 +5847,7 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p) iv_count++; iv[i].iov_base = address; iv[i].iov_len = length; - if (iv_count == UIO_MAXIOV) + if (iv_count == max_iv_count) break; length = scst_get_buf_next(cmd, (uint8_t __force **)&address); @@ -4962,12 +5863,11 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p) goto out_set_fs; } - TRACE_DBG("(iv_count %d, full_len %zd)", iv_count, full_len); + TRACE_DBG("Reading iv_count %d, full_len %zd", iv_count, full_len); /* READ */ err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count, &loff); - if ((err < 0) || (err < full_len)) { PRINT_ERROR("readv() returned %lld from %zd", (unsigned long long int)err, @@ -4992,6 +5892,16 @@ static enum compl_status_e fileio_exec_read(struct vdisk_cmd_params *p) set_fs(old_fs); + if ((dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) && + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE)) { + err = vdev_read_dif_tags(p); + if (err != 0) + goto out; + } + +out_dif: + scst_dif_process_read(cmd); + out: TRACE_EXIT(); return CMD_SUCCEEDED; @@ -5010,6 +5920,7 @@ out_nomem: static enum compl_status_e nullio_exec_write(struct vdisk_cmd_params *p) { + scst_dif_process_write(p->cmd); return CMD_SUCCEEDED; } @@ -5018,14 +5929,67 @@ static enum compl_status_e blockio_exec_write(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, rc; + + TRACE_ENTRY(); + + rc = scst_dif_process_write(cmd); + if (unlikely(rc != 0)) { + res = CMD_SUCCEEDED; + goto out; + } blockio_exec_rw(p, true, p->fua || virt_dev->wt_flag); - return RUNNING_ASYNC; + res = RUNNING_ASYNC; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static enum compl_status_e blockio_exec_var_len_cmd(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + int res; + + TRACE_ENTRY(); + + res = blockio_var_len_ops[cmd->cdb[9]](p); + + TRACE_EXIT_RES(res); + return res; +} + +static enum compl_status_e nullio_exec_var_len_cmd(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + int res; + + TRACE_ENTRY(); + + res = nullio_var_len_ops[cmd->cdb[9]](p); + + TRACE_EXIT_RES(res); + return res; +} + +static enum compl_status_e fileio_exec_var_len_cmd(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + int res; + + TRACE_ENTRY(); + + res = fileio_var_len_ops[cmd->cdb[9]](p); + + TRACE_EXIT_RES(res); + return res; } static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; + struct scst_device *dev = cmd->dev; loff_t loff = p->loff; mm_segment_t old_fs; loff_t err = 0; @@ -5034,13 +5998,17 @@ static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p) struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; struct file *fd = virt_dev->fd; struct iovec *iv, *eiv; - int i, iv_count, eiv_count; + int rc, i, iv_count, eiv_count, max_iv_count; bool finished = false; TRACE_ENTRY(); EXTRACHECKS_BUG_ON(virt_dev->nullio); + rc = scst_dif_process_write(cmd); + if (unlikely(rc != 0)) + goto out; + if (p->use_zero_copy) goto out_sync; @@ -5048,6 +6016,8 @@ static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p) if (iv == NULL) goto out_nomem; + max_iv_count = p->iv_count; + length = scst_get_buf_first(cmd, &address); if (unlikely(length < 0)) { PRINT_ERROR("scst_get_buf_first() failed: %zd", length); @@ -5069,7 +6039,7 @@ static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p) iv_count++; iv[i].iov_base = (uint8_t __force __user *)address; iv[i].iov_len = length; - if (iv_count == UIO_MAXIOV) + if (iv_count == max_iv_count) break; length = scst_get_buf_next(cmd, &address); } @@ -5087,12 +6057,11 @@ static enum compl_status_e fileio_exec_write(struct vdisk_cmd_params *p) eiv = iv; eiv_count = iv_count; restart: - TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len); + TRACE_DBG("Writing: eiv_count %d, full_len %zd", eiv_count, full_len); /* WRITE */ err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count, &loff); - if (err < 0) { PRINT_ERROR("write() returned %lld from %zd", (unsigned long long int)err, @@ -5143,6 +6112,13 @@ restart: set_fs(old_fs); + if ((dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) && + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE)) { + err = vdev_write_dif_tags(p); + if (err != 0) + goto out; + } + out_sync: /* O_DSYNC flag is used for WT devices */ if (p->fua) @@ -5177,9 +6153,21 @@ static inline void blockio_check_finish(struct scst_blockio_work *blockio_work) { /* Decrement the bios in processing, and if zero signal completion */ if (atomic_dec_and_test(&blockio_work->bios_inflight)) { + struct scst_cmd *cmd = blockio_work->cmd; + + if ((cmd->data_direction & SCST_DATA_READ) && + likely(cmd->status == SAM_STAT_GOOD)) { + /* + * We, most likely, on interrupt, so defer DIF + * checking to later stage in thread context + */ + cmd->deferred_dif_read_check = 1; + } + blockio_work->cmd->completed = 1; - blockio_work->cmd->scst_cmd_done(blockio_work->cmd, + blockio_work->cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, scst_estimate_context()); + kmem_cache_free(blockio_work_cachep, blockio_work); } return; @@ -5216,14 +6204,16 @@ static void blockio_endio(struct bio *bio, int error) } if (unlikely(error != 0)) { - static DEFINE_SPINLOCK(blockio_endio_lock); unsigned long flags; PRINT_ERROR("BLOCKIO for cmd %p finished with error %d", blockio_work->cmd, error); - /* To protect from several bios finishing simultaneously */ - spin_lock_irqsave(&blockio_endio_lock, flags); + /* + * To protect from several bios finishing simultaneously + + * unsuccessful DIF tags reading/writing + */ + spin_lock_irqsave(&vdev_err_lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) if (bio->bi_rw & (1 << BIO_RW)) @@ -5236,7 +6226,7 @@ static void blockio_endio(struct bio *bio, int error) scst_set_cmd_error(blockio_work->cmd, SCST_LOAD_SENSE(scst_sense_read_error)); - spin_unlock_irqrestore(&blockio_endio_lock, flags); + spin_unlock_irqrestore(&vdev_err_lock, flags); } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)) || (LINUX_VERSION_CODE > KERNEL_VERSION(3, 6, 0)) @@ -5297,12 +6287,117 @@ static void vdisk_bio_set_hoq(struct bio *bio) #endif } +#if defined(CONFIG_BLK_DEV_INTEGRITY) +static void vdisk_blk_add_dif(struct bio *bio, gfp_t gfp_mask, + const struct scst_device *dev, struct scatterlist **pdsg, + int *pdsg_offs, int *pdsg_len, bool last) +{ + int block_shift = dev->block_shift; + struct scatterlist *orig_dsg = *pdsg; + struct scatterlist *sg; + int sg_offs = *pdsg_offs, sg_len = *pdsg_len; + int pages, left, len, tags_len, rc; + struct bio_integrity_payload *bip; + + TRACE_ENTRY(); + + tags_len = ((bio_sectors(bio) << 9) >> block_shift) << SCST_DIF_TAG_SHIFT; + + TRACE_DBG("bio %p, tags_len %d, pdsg %p, pdsg_offs %d, pdsg_len %d, " + "last %d", bio, tags_len, *pdsg, *pdsg_offs, *pdsg_len, last); + + pages = 0; + left = tags_len; + sg = orig_dsg; + len = sg->length; + while (1) { + pages++; + left -= len; + if (left <= 0) { + if (!last) { + left = -left; + sg = __sg_next_inline(sg); + *pdsg = sg; + *pdsg_offs = sg->offset + left; + *pdsg_len = sg->length - left; + TRACE_DBG("left %d, pdsg %p, pdsg_offs %d, pdsg_len %d", + left, *pdsg, *pdsg_offs, *pdsg_len); + } + break; + } + sg = __sg_next_inline(sg); + len = sg->length; + } + + bip = bio_integrity_alloc(bio, gfp_mask, pages); + if (unlikely(bip == NULL)) { + PRINT_WARNING("Allocation of %d pages for DIF tags " + "failed! (dev %s)", pages, dev->virt_name); + goto out; /* proceed without integrity */ + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + bip->bip_iter.bi_size = tags_len; + bip->bip_iter.bi_sector = bio->bi_iter.bi_sector; +#else + bip->bip_size = tags_len; + bip->bip_sector = bio->bi_sector; +#endif + + left = tags_len; + sg = orig_dsg; + while (1) { + TRACE_DBG("page %p (buf %p), sg_len %d, sg_offs %d", + sg_page(sg), page_address(sg_page(sg)), + sg_len, sg_offs); + + rc = bio_integrity_add_page(bio, sg_page(sg), sg_len, sg_offs); + if (rc != sg_len) { + PRINT_WARNING("Can not add DIF tags page! " + "(dev %s)", dev->virt_name); + /* bio_integrity_free() will be called as part of bio_free() */ + goto out; /* proceed without integrity */ + } + + if (left < sg_len) { + TRACE_DBG("left %d, sg_len %d, sg_offs %d", + left, sg_len, sg_offs); + break; + } + + left -= sg_len; + EXTRACHECKS_BUG_ON(left < 0); + + TRACE_DBG("left %d", left); + + if (left == 0) + break; + + sg = __sg_next_inline(sg); + sg_len = sg->length; + sg_offs = sg->offset; + } + +out: + TRACE_EXIT(); + return; +} +#else /* defined(CONFIG_BLK_DEV_INTEGRITY) */ +static void vdisk_blk_add_dif(struct bio *bio, gfp_t gfp_mask, + const struct scst_device *dev, struct scatterlist **pdsg, + int *pdsg_offs, int *pdsg_len, bool last) +{ + BUG(); +} +#endif /* defined(CONFIG_BLK_DEV_INTEGRITY) */ + static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) { struct scst_cmd *cmd = p->cmd; u64 lba_start = scst_cmd_get_lba(cmd); - struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; - int block_shift = cmd->dev->block_shift; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + int block_shift = dev->block_shift; struct block_device *bdev = virt_dev->bdev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) struct bio_set *bs = virt_dev->vdisk_bioset; @@ -5318,11 +6413,21 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) struct blk_plug plug; #endif + struct scatterlist *dsg; + int dsg_offs, dsg_len; + bool dif = virt_dev->blk_integrity && + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE); TRACE_ENTRY(); WARN_ON(virt_dev->nullio); + if (dif) { + dsg = cmd->dif_sg; + dsg_offs = dsg->offset; + dsg_len = dsg->length; + } + /* Allocate and initialize blockio_work struct */ blockio_work = kmem_cache_alloc(blockio_work_cachep, gfp_mask); if (blockio_work == NULL) { @@ -5434,6 +6539,9 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) rc = bio_add_page(bio, pg, bytes, off); if (rc < bytes) { WARN_ON(rc != 0); + if (dif) + vdisk_blk_add_dif(bio, gfp_mask, dev, &dsg, + &dsg_offs, &dsg_len, false); need_new_bio = 1; lba_start0 += thislen >> block_shift; thislen = 0; @@ -5452,6 +6560,10 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) length = scst_get_sg_page_next(cmd, &page, &offset); } + if (dif) + vdisk_blk_add_dif(bio, gfp_mask, dev, &dsg, &dsg_offs, + &dsg_len, true); + /* +1 to prevent erroneous too early command completion */ atomic_set(&blockio_work->bios_inflight, bios+1); @@ -5463,6 +6575,7 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) bio = hbio; hbio = hbio->bi_next; bio->bi_next = NULL; + submit_bio((write != 0), bio); } @@ -5473,6 +6586,15 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) q->unplug_fn(q); #endif + if ((dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) && + (virt_dev->dif_fd != NULL) && + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE)) { + if (write) + vdev_write_dif_tags(p); + else + vdev_read_dif_tags(p); + } + blockio_check_finish(blockio_work); out: @@ -5798,6 +6920,7 @@ static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; uint8_t *mem_verify = NULL; int64_t data_len = scst_cmd_get_data_len(cmd); + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); TRACE_ENTRY(); @@ -5849,6 +6972,7 @@ static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) scst_put_buf(cmd, address_sav); goto out_free; } + if (compare && memcmp(address, mem_verify, len_mem) != 0) { TRACE_DBG("Verify: error memcmp length %zd", length); scst_set_cmd_error(cmd, @@ -5856,6 +6980,11 @@ static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) scst_put_buf(cmd, address_sav); goto out_free; } + + if (checks != 0) { + /* ToDo: check DIF tags as well */ + } + length -= len_mem; if (compare) address += len_mem; @@ -6016,6 +7145,7 @@ static enum compl_status_e fileio_exec_write_verify(struct vdisk_cmd_params *p) static enum compl_status_e nullio_exec_write_verify(struct vdisk_cmd_params *p) { + scst_dif_process_write(p->cmd); return CMD_SUCCEEDED; } @@ -6037,6 +7167,7 @@ static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, dev->tmf_only = dev->tmf_only_saved; dev->d_sense = dev->d_sense_saved; + dev->dpicz = dev->dpicz_saved; dev->swp = dev->swp_saved; dev->tas = dev->tas_saved; dev->queue_alg = dev->queue_alg_saved; @@ -6120,6 +7251,18 @@ static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev) i += snprintf(&buf[i], buf_size - i, "%sTHIN_PROVISIONED", (j == i) ? "(" : ", "); + if (virt_dev->dif_mode != SCST_DIF_MODE_NONE) { + i += snprintf(&buf[i], buf_size - i, "%sDIF MODE %x, " + "DIF TYPE %d", (j == i) ? "(" : ", ", + virt_dev->dif_mode, virt_dev->dif_type); + if (virt_dev->dif_filename != NULL) + i += snprintf(&buf[i], buf_size - i, ", DIF FILENAME %s", + virt_dev->dif_filename); + else if (virt_dev->dif_static_app_tag_combined != SCST_DIF_NO_CHECK_APP_TAG) + i += snprintf(&buf[i], buf_size - i, ", DIF STATIC APP TAG %llx", + (long long)be64_to_cpu(virt_dev->dif_static_app_tag_combined)); + } + if (virt_dev->zero_copy) i += snprintf(&buf[i], buf_size - i, "%sZERO_COPY", (j == i) ? "(" : ", "); @@ -6192,10 +7335,25 @@ static int vdisk_create_bioset(struct scst_vdisk_dev *virt_dev) goto out; } + if (virt_dev->dif_mode & SCST_DIF_MODE_DEV) { + /* The same, pool size doesn't really matter */ + res = bioset_integrity_create(virt_dev->vdisk_bioset, 2); + if (res != 0) { + PRINT_ERROR("Failed to create integrity bioset " + "(dev %s)", virt_dev->name); + goto out_free; + } + } + res = 0; out: return res; + +out_free: + bioset_free(virt_dev->vdisk_bioset); + virt_dev->vdisk_bioset = NULL; + goto out; } static void vdisk_free_bioset(struct scst_vdisk_dev *virt_dev) @@ -6304,6 +7462,7 @@ static void vdev_destroy(struct scst_vdisk_dev *virt_dev) vdisk_free_bioset(virt_dev); #endif kfree(virt_dev->filename); + kfree(virt_dev->dif_filename); kfree(virt_dev); return; } @@ -6391,13 +7550,68 @@ static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev, continue; } + if (!strcasecmp("dif_filename", p)) { + if (*pp != '/') { + PRINT_ERROR("DIF filename %s must be global " + "(device %s)", pp, virt_dev->name); + res = -EINVAL; + goto out; + } + + virt_dev->dif_filename = kstrdup(pp, GFP_KERNEL); + if (virt_dev->dif_filename == NULL) { + PRINT_ERROR("Unable to duplicate DIF filename %s " + "(device %s)", pp, virt_dev->name); + res = -ENOMEM; + goto out; + } + continue; + } + + if (!strcasecmp("dif_mode", p)) { + char *d = pp; + + while (1) { + char *dd; + + for (; (*d != '\0') && isspace(*d); d++) + ; + if (*d == '\0') + break; + + dd = strchr(d, '|'); + if (dd != NULL) + *dd = '\0'; + if (!strcasecmp(SCST_DIF_MODE_TGT_STR, d)) + virt_dev->dif_mode |= SCST_DIF_MODE_TGT; + else if (!strcasecmp(SCST_DIF_MODE_SCST_STR, d)) + virt_dev->dif_mode |= SCST_DIF_MODE_SCST; + else if (!strcasecmp(SCST_DIF_MODE_DEV_CHECK_STR, d)) { + virt_dev->dif_mode |= SCST_DIF_MODE_DEV_CHECK; + } else if (!strcasecmp(SCST_DIF_MODE_DEV_STORE_STR, d)) + virt_dev->dif_mode |= SCST_DIF_MODE_DEV_STORE; + else { + PRINT_ERROR("Error parsing DIF mode %s", pp); + res = -EINVAL; + goto out; + } + if (dd == NULL) + break; + else + *dd = '|'; + d = dd+1; + } + TRACE_DBG("DIF DEV mode %x", virt_dev->dif_mode); + 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("strtoul() for %s failed: %d (device %s)", + PRINT_ERROR("strtoull() for %s failed: %d (device %s)", pp, res, virt_dev->name); goto out; } @@ -6461,6 +7675,13 @@ static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev, } TRACE_DBG("block size %lld, block shift %d", val, virt_dev->blk_shift); + } else if (!strcasecmp("dif_type", p)) { + virt_dev->dif_type = val; + TRACE_DBG("DIF type %d", virt_dev->dif_type); + } else if (!strcasecmp("dif_static_app_tag", p)) { + virt_dev->dif_static_app_tag_combined = cpu_to_be64(val); + TRACE_DBG("DIF static app tag %llx", + (long long)be64_to_cpu(virt_dev->dif_static_app_tag_combined)); } else { PRINT_ERROR("Unknown parameter %s (device %s)", p, virt_dev->name); @@ -6547,7 +7768,9 @@ 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", NULL }; + "rotational", "thin_provisioned", "tst", + "dif_mode", "dif_type", "dif_static_app_tag", + "dif_filename", NULL }; struct scst_vdisk_dev *virt_dev; TRACE_ENTRY(); @@ -6611,6 +7834,7 @@ static int vdev_nullio_add_device(const char *device_name, char *params) int res = 0; static const char *const allowed_params[] = { "read_only", "dummy", "removable", "blocksize", "rotational", + "dif_mode", "dif_type", "dif_static_app_tag", "size", "size_mb", "tst", NULL }; struct scst_vdisk_dev *virt_dev; @@ -8233,6 +9457,26 @@ static ssize_t vdev_zero_copy_show(struct kobject *kobj, return pos; } +static ssize_t vdev_dif_filename_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_device *dev; + struct scst_vdisk_dev *virt_dev; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + virt_dev = dev->dh_priv; + + pos = sprintf(buf, "%s\n%s", virt_dev->dif_filename, + (virt_dev->dif_filename != NULL) ? + SCST_SYSFS_KEY_MARK "\n" : ""); + + TRACE_EXIT_RES(pos); + return pos; +} + #else /* CONFIG_SCST_PROC */ /* @@ -8995,6 +10239,8 @@ static int __init init_scst_vdisk_driver(void) { int res; + spin_lock_init(&vdev_err_lock); + init_ops(fileio_ops, ARRAY_SIZE(fileio_ops)); init_ops(blockio_ops, ARRAY_SIZE(blockio_ops)); init_ops(nullio_ops, ARRAY_SIZE(nullio_ops)); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index ca608d657..3f38d6d60 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -400,7 +402,9 @@ static int get_cdb_info_verify16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_len_1(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); -static int get_cdb_info_lba_3_len_1_256(struct scst_cmd *cmd, +static int get_cdb_info_lba_3_len_1_256_read(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); +static int get_cdb_info_lba_3_len_1_256_write(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_bidi_lba_4_len_2(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); @@ -414,13 +418,19 @@ static int get_cdb_info_lba_2_none(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_lba_4_len_2(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_read_10(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); +static int get_cdb_info_lba_4_len_2_wrprotect(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_lba_4_none(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); -static int get_cdb_info_lba_4_len_2(struct scst_cmd *cmd, +static int get_cdb_info_lba_4_len_4_rdprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); -static int get_cdb_info_lba_4_len_4(struct scst_cmd *cmd, +static int get_cdb_info_lba_4_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); -static int get_cdb_info_lba_8_len_4(struct scst_cmd *cmd, +static int get_cdb_info_read_16(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); +static int get_cdb_info_lba_8_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_lba_8_none(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); @@ -434,6 +444,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_var_len(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); /* +=====================================-============-======- @@ -593,7 +605,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_EXCL_ALLOWED, .info_lba_off = 1, .info_lba_len = 3, .info_len_off = 4, .info_len_len = 1, - .get_cdb_info = get_cdb_info_lba_3_len_1_256}, + .get_cdb_info = get_cdb_info_lba_3_len_1_256_read}, {.ops = 0x08, .devkey = " MV O OV ", .info_op_name = "READ(6)", .info_data_direction = SCST_DATA_READ, @@ -623,7 +635,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_MEDIUM, .info_lba_off = 1, .info_lba_len = 3, .info_len_off = 4, .info_len_len = 1, - .get_cdb_info = get_cdb_info_lba_3_len_1_256}, + .get_cdb_info = get_cdb_info_lba_3_len_1_256_write}, {.ops = 0x0A, .devkey = " M O OV ", .info_op_name = "WRITE(6)", .info_data_direction = SCST_DATA_WRITE, @@ -820,7 +832,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_EXCL_ALLOWED, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 7, .info_len_len = 2, - .get_cdb_info = get_cdb_info_lba_4_len_2}, + .get_cdb_info = get_cdb_info_read_10}, {.ops = 0x28, .devkey = " O ", .info_op_name = "GET MESSAGE(10)", .info_data_direction = SCST_DATA_READ, @@ -843,7 +855,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 7, .info_len_len = 2, - .get_cdb_info = get_cdb_info_lba_4_len_2}, + .get_cdb_info = get_cdb_info_lba_4_len_2_wrprotect}, {.ops = 0x2A, .devkey = " O ", .info_op_name = "SEND MESSAGE(10)", .info_data_direction = SCST_DATA_WRITE, @@ -888,7 +900,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 7, .info_len_len = 2, - .get_cdb_info = get_cdb_info_lba_4_len_2}, + .get_cdb_info = get_cdb_info_lba_4_len_2_wrprotect}, {.ops = 0x2F, .devkey = "O OO O ", .info_op_name = "VERIFY(10)", .info_data_direction = SCST_DATA_UNKNOWN, @@ -1086,7 +1098,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 7, .info_len_len = 2, - .get_cdb_info = get_cdb_info_lba_4_len_2}, + .get_cdb_info = get_cdb_info_lba_4_len_2_wrprotect}, {.ops = 0x51, .devkey = " O ", .info_op_name = "READ DISC INFORMATION", .info_data_direction = SCST_DATA_READ, @@ -1187,6 +1199,13 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_len_off = 5, .info_len_len = 4, .get_cdb_info = get_cdb_info_len_4}, + /* Variable length CDBs */ + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "VAR LEN CDB", + .info_data_direction = SCST_DATA_UNKNOWN, + .info_op_flags = 0, + .get_cdb_info = get_cdb_info_var_len}, + /* 16-bytes length CDB */ {.ops = 0x80, .devkey = " O ", .info_op_name = "WRITE FILEMARKS(16)", @@ -1245,7 +1264,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_EXCL_ALLOWED, .info_lba_off = 2, .info_lba_len = 8, .info_len_off = 10, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_8_len_4}, + .get_cdb_info = get_cdb_info_read_16}, {.ops = 0x89, .devkey = "O ", .info_op_name = "COMPARE AND WRITE", .info_data_direction = SCST_DATA_WRITE, @@ -1264,7 +1283,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 8, .info_len_off = 10, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_8_len_4}, + .get_cdb_info = get_cdb_info_lba_8_len_4_wrprotect}, {.ops = 0x8C, .devkey = " OOOOOOOOO ", .info_op_name = "READ ATTRIBUTE", .info_data_direction = SCST_DATA_READ, @@ -1283,7 +1302,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 8, .info_len_off = 10, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_8_len_4}, + .get_cdb_info = get_cdb_info_lba_8_len_4_wrprotect}, {.ops = 0x8F, .devkey = "O OO O ", .info_op_name = "VERIFY(16)", .info_data_direction = SCST_DATA_UNKNOWN, @@ -1424,7 +1443,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_EXCL_ALLOWED, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 6, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_4_len_4}, + .get_cdb_info = get_cdb_info_lba_4_len_4_rdprotect}, {.ops = 0xA9, .devkey = " O ", .info_op_name = "PLAY TRACK RELATIVE(12)", .info_data_direction = SCST_DATA_NONE, @@ -1440,7 +1459,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 6, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_4_len_4}, + .get_cdb_info = get_cdb_info_lba_4_len_4_wrprotect}, {.ops = 0xAA, .devkey = " O ", .info_op_name = "SEND MESSAGE(12)", .info_data_direction = SCST_DATA_WRITE, @@ -1469,7 +1488,7 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, .info_lba_off = 2, .info_lba_len = 4, .info_len_off = 6, .info_len_len = 4, - .get_cdb_info = get_cdb_info_lba_4_len_4}, + .get_cdb_info = get_cdb_info_lba_4_len_4_wrprotect}, {.ops = 0xAF, .devkey = "O OO O ", .info_op_name = "VERIFY(12)", .info_data_direction = SCST_DATA_UNKNOWN, @@ -3206,24 +3225,39 @@ static void scst_adjust_sg(struct scst_cmd *cmd, bool reg_sg, { struct scatterlist *sg; int *sg_cnt; + bool adjust_dif; TRACE_ENTRY(); - EXTRACHECKS_BUG_ON(cmd->sg_buff_modified); + EXTRACHECKS_BUG_ON(cmd->sg_buff_modified || cmd->dif_sg_buff_modified); if (reg_sg) { sg = cmd->sg; sg_cnt = &cmd->sg_cnt; + adjust_dif = (cmd->dif_sg != NULL); } else { sg = *cmd->write_sg; sg_cnt = cmd->write_sg_cnt; + if (sg == cmd->sg) + adjust_dif = (cmd->dif_sg != NULL); + else + adjust_dif = false; } - TRACE_DBG("reg_sg %d, adjust_len %d", reg_sg, adjust_len); + TRACE_DBG("reg_sg %d, adjust_len %d, adjust_dif %d", reg_sg, + adjust_len, adjust_dif); - cmd->sg_buff_modified = __scst_adjust_sg(cmd, sg, sg_cnt, adjust_len, + cmd->sg_buff_modified = !!__scst_adjust_sg(cmd, sg, sg_cnt, adjust_len, &cmd->orig_sg); + if (adjust_dif) { + adjust_len >>= (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + TRACE_DBG("DIF adjust_len %d", adjust_len); + cmd->dif_sg_buff_modified = __scst_adjust_sg(cmd, cmd->dif_sg, + &cmd->dif_sg_cnt, adjust_len, + &cmd->orig_dif_sg); + } + TRACE_EXIT(); return; } @@ -3235,13 +3269,16 @@ static void scst_adjust_sg(struct scst_cmd *cmd, bool reg_sg, */ void scst_restore_sg_buff(struct scst_cmd *cmd) { - TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_MEMORY, "cmd %p, sg %p, " + TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_MEMORY, "cmd %p, sg %p, DATA: " "orig_sg_entry %p, orig_entry_offs %d, orig_entry_len %d, " - "orig_sg_cnt %d", cmd, cmd->sg, cmd->orig_sg.orig_sg_entry, - cmd->orig_sg.orig_entry_offs, cmd->orig_sg.orig_entry_len, - cmd->orig_sg.orig_sg_cnt); + "orig_sg_cnt %d, DIF: orig_sg_entry %p, " + "orig_entry_offs %d, orig_entry_len %d, orig_sg_cnt %d", cmd, + cmd->sg, cmd->orig_sg.orig_sg_entry, cmd->orig_sg.orig_entry_offs, + cmd->orig_sg.orig_entry_len, cmd->orig_sg.orig_sg_cnt, + cmd->orig_dif_sg.orig_sg_entry, cmd->orig_dif_sg.orig_entry_offs, + cmd->orig_dif_sg.orig_entry_len, cmd->orig_dif_sg.orig_sg_cnt); - EXTRACHECKS_BUG_ON(!cmd->sg_buff_modified); + EXTRACHECKS_BUG_ON(!(cmd->sg_buff_modified || cmd->dif_sg_buff_modified)); if (cmd->sg_buff_modified) { cmd->orig_sg.orig_sg_entry->offset = cmd->orig_sg.orig_entry_offs; @@ -3249,7 +3286,14 @@ void scst_restore_sg_buff(struct scst_cmd *cmd) *cmd->orig_sg.p_orig_sg_cnt = cmd->orig_sg.orig_sg_cnt; } + if (cmd->dif_sg_buff_modified) { + cmd->orig_dif_sg.orig_sg_entry->offset = cmd->orig_dif_sg.orig_entry_offs; + cmd->orig_dif_sg.orig_sg_entry->length = cmd->orig_dif_sg.orig_entry_len; + *cmd->orig_dif_sg.p_orig_sg_cnt = cmd->orig_dif_sg.orig_sg_cnt; + } + cmd->sg_buff_modified = 0; + cmd->dif_sg_buff_modified = 0; } EXPORT_SYMBOL(scst_restore_sg_buff); @@ -3310,6 +3354,71 @@ void scst_limit_sg_write_len(struct scst_cmd *cmd) return; } +static int scst_full_len_to_data_len(int full_len, int block_shift) +{ + int rem, res; + + res = full_len << block_shift; + rem = do_div(res, (1 << block_shift) + (1 << SCST_DIF_TAG_SHIFT)); + if (unlikely(rem != 0)) + TRACE(TRACE_MINOR, "Reminder %d for full len! (full len%d)", + rem, full_len); + + TRACE_DBG("data len %d (full %d)", res, full_len); + + return res; +} + +/* + * Returns data expected transfer length, i.e. expected transfer length, + * adjusted on DIF tags expected transfer length, if any. + * + * Very expensive, don't call on fast path! + */ +int scst_cmd_get_expected_transfer_len_data(struct scst_cmd *cmd) +{ + int rem, res; + + if (!cmd->tgt_dif_data_expected) + return cmd->expected_transfer_len_full; + + res = cmd->expected_transfer_len_full << cmd->dev->block_shift; + rem = do_div(res, cmd->dev->block_size + (1 << SCST_DIF_TAG_SHIFT)); + if (unlikely(rem != 0)) + TRACE(TRACE_MINOR, "Reminder %d for expected transfer len " + "data! (cmd %p, op %s, expected len full %d)", rem, + cmd, scst_get_opcode_name(cmd), + cmd->expected_transfer_len_full); + + TRACE_DBG("Expected transfer len data %d (cmd %p)", res, cmd); + + return res; +} + +/* + * Returns DIF tags expected transfer length. + * + * Very expensive, don't call on fast path! + */ +int scst_cmd_get_expected_transfer_len_dif(struct scst_cmd *cmd) +{ + int rem, res; + + if (!cmd->tgt_dif_data_expected) + return 0; + + res = cmd->expected_transfer_len_full << SCST_DIF_TAG_SHIFT; + rem = do_div(res, cmd->dev->block_size + (1 << SCST_DIF_TAG_SHIFT)); + if (unlikely(rem != 0)) + TRACE(TRACE_MINOR, "Reminder %d for expected transfer len dif! " + "(cmd %p, op %s, expected len full %d)", rem, cmd, + scst_get_opcode_name(cmd), cmd->expected_transfer_len_full); + + TRACE_DBG("Expected transfer len DIF %d (cmd %p)", res, cmd); + + return res; +} + void scst_adjust_resp_data_len(struct scst_cmd *cmd) { TRACE_ENTRY(); @@ -3320,7 +3429,7 @@ void scst_adjust_resp_data_len(struct scst_cmd *cmd) } cmd->adjusted_resp_data_len = min(cmd->resp_data_len, - cmd->expected_transfer_len); + scst_cmd_get_expected_transfer_len_data(cmd)); if (cmd->adjusted_resp_data_len != cmd->resp_data_len) { TRACE_MEM("Adjusting resp_data_len to %d (cmd %p, sg %p, " @@ -3365,7 +3474,10 @@ void scst_cmd_set_write_not_received_data_len(struct scst_cmd *cmd, if (cmd->write_len == cmd->out_bufflen) goto out; } else if (cmd->expected_data_direction & SCST_DATA_WRITE) { - cmd->write_len = cmd->expected_transfer_len - not_received; + cmd->write_len = cmd->expected_transfer_len_full - not_received; + if (cmd->tgt_dif_data_expected) + cmd->write_len = scst_full_len_to_data_len(cmd->write_len, + cmd->dev->block_shift); if (cmd->write_len == cmd->bufflen) goto out; } @@ -3400,7 +3512,7 @@ void scst_cmd_set_write_no_data_received(struct scst_cmd *cmd) (cmd->expected_data_direction & SCST_DATA_WRITE)) w = cmd->expected_out_transfer_len; else - w = cmd->expected_transfer_len; + w = cmd->expected_transfer_len_full; scst_cmd_set_write_not_received_data_len(cmd, w); @@ -3437,7 +3549,10 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) } if (cmd->expected_data_direction & SCST_DATA_READ) { - *resid = cmd->expected_transfer_len - cmd->resp_data_len; + 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; if ((cmd->expected_data_direction & SCST_DATA_WRITE) && bidi_out_resid) { if (cmd->write_len < cmd->expected_out_transfer_len) *bidi_out_resid = cmd->expected_out_transfer_len - @@ -3446,18 +3561,34 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) *bidi_out_resid = cmd->write_len - cmd->out_bufflen; } } else if (cmd->expected_data_direction & SCST_DATA_WRITE) { - if (cmd->write_len < cmd->expected_transfer_len) - *resid = cmd->expected_transfer_len - cmd->write_len; - else + 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) + *resid = cmd->expected_transfer_len_full - wl; + else { *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; + if (*resid > 0) + *resid = r; + else + *resid = -r; + } + } } res = true; TRACE_DBG("cmd %p, resid %d, bidi_out_resid %d (resp_data_len %d, " - "expected_data_direction %d, write_len %d, bufflen %d)", cmd, - *resid, bidi_out_resid ? *bidi_out_resid : 0, cmd->resp_data_len, - cmd->expected_data_direction, cmd->write_len, cmd->bufflen); + "expected_data_direction %d, write_len %d, bufflen %d, " + "tgt_dif_data_expected %d)", cmd, *resid, + bidi_out_resid ? *bidi_out_resid : 0, cmd->resp_data_len, + cmd->expected_data_direction, cmd->write_len, cmd->bufflen, + cmd->tgt_dif_data_expected); out: TRACE_EXIT_RES(res); @@ -3721,11 +3852,27 @@ int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt) init_waitqueue_head(&t->unreg_waitQ); t->tgtt = tgtt; t->sg_tablesize = tgtt->sg_tablesize; + t->tgt_dif_supported = tgtt->dif_supported; + t->tgt_hw_dif_type1_supported = tgtt->hw_dif_type1_supported; + t->tgt_hw_dif_type2_supported = tgtt->hw_dif_type2_supported; + t->tgt_hw_dif_type3_supported = tgtt->hw_dif_type3_supported; + t->tgt_hw_dif_ip_supported = tgtt->hw_dif_ip_supported; + t->tgt_hw_dif_same_sg_layout_required = tgtt->hw_dif_same_sg_layout_required; + t->tgt_supported_dif_block_sizes = tgtt->supported_dif_block_sizes; spin_lock_init(&t->tgt_lock); INIT_LIST_HEAD(&t->retry_cmd_list); init_timer(&t->retry_timer); t->retry_timer.data = (unsigned long)t; t->retry_timer.function = scst_tgt_retry_timer_fn; + atomic_set(&t->tgt_dif_app_failed_tgt, 0); + atomic_set(&t->tgt_dif_ref_failed_tgt, 0); + atomic_set(&t->tgt_dif_guard_failed_tgt, 0); + atomic_set(&t->tgt_dif_app_failed_scst, 0); + atomic_set(&t->tgt_dif_ref_failed_scst, 0); + atomic_set(&t->tgt_dif_guard_failed_scst, 0); + atomic_set(&t->tgt_dif_app_failed_dev, 0); + atomic_set(&t->tgt_dif_ref_failed_dev, 0); + atomic_set(&t->tgt_dif_guard_failed_dev, 0); #ifdef CONFIG_SCST_PROC res = gen_relative_target_port_id(&t->rel_tgt_id); @@ -3776,6 +3923,13 @@ static void scst_init_order_data(struct scst_order_data *order_data) return; } +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); +#else +#define scst_dif_none_type1 scst_dif_none +#endif + /* Called under scst_mutex and suspended activity */ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) { @@ -3811,6 +3965,11 @@ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) dev->pr_type = TYPE_UNSPECIFIED; INIT_LIST_HEAD(&dev->dev_registrants_list); + BUILD_BUG_ON(SCST_DIF_NO_CHECK_APP_TAG != 0); + dev->dev_dif_static_app_tag = SCST_DIF_NO_CHECK_APP_TAG; + dev->dev_dif_static_app_ref_tag = SCST_DIF_NO_CHECK_APP_TAG; + dev->dev_dif_fn = scst_dif_none; + scst_init_order_data(&dev->dev_order_data); scst_init_threads(&dev->dev_cmd_threads); @@ -3927,12 +4086,82 @@ static void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs) return; } +static int scst_check_dif_compatibility(const struct scst_acg *acg, + const struct scst_device *dev) +{ + int res = -EINVAL; + struct scst_tgt *tgt; + const int *p; + bool supported; + + TRACE_ENTRY(); + + if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) + goto out_ok; + + tgt = acg->tgt; + + if (!tgt->tgt_dif_supported) { + PRINT_ERROR("Target %s doesn't support T10-PI (device %s)", + tgt->tgt_name, dev->virt_name); + goto out; + } + + if (dev->dev_dif_mode & SCST_DIF_MODE_TGT) { + if ((dev->dev_dif_type == 1) && !tgt->tgt_hw_dif_type1_supported) { + PRINT_ERROR("Target %s doesn't support type 1 " + "protection TGT mode (device %s)", tgt->tgt_name, + dev->virt_name); + goto out; + } + + if ((dev->dev_dif_type == 2) && !tgt->tgt_hw_dif_type2_supported) { + PRINT_ERROR("Target %s doesn't support type 2 " + "protection TGT mode (device %s)", tgt->tgt_name, + dev->virt_name); + goto out; + } + + if ((dev->dev_dif_type == 3) && !tgt->tgt_hw_dif_type3_supported) { + PRINT_ERROR("Target %s doesn't support type 3 " + "protection TGT mode (device %s)", tgt->tgt_name, + dev->virt_name); + goto out; + } + } + + if (tgt->tgt_supported_dif_block_sizes == NULL) + goto out_ok; + + p = tgt->tgt_supported_dif_block_sizes; + supported = false; + while (*p != 0) { + if (*p == dev->block_size) { + supported = true; + break; + } + p++; + } + if (!supported) { + PRINT_ERROR("Target %s doesn't support block size %d of " + "device %s", tgt->tgt_name, dev->block_size, dev->virt_name); + goto out; + } + +out_ok: + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + /* The activity supposed to be suspended and scst_mutex held */ int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent, struct scst_device *dev, uint64_t lun, int read_only, bool gen_scst_report_luns_changed, struct scst_acg_dev **out_acg_dev) { - int res = 0; + int res; struct scst_acg_dev *acg_dev; struct scst_tgt_dev *tgt_dev; struct scst_session *sess; @@ -3942,12 +4171,24 @@ int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent, INIT_LIST_HEAD(&tmp_tgt_dev_list); + res = scst_check_dif_compatibility(acg, dev); + if (res != 0) + goto out; + acg_dev = scst_alloc_acg_dev(acg, dev, lun); if (acg_dev == NULL) { res = -ENOMEM; goto out; } acg_dev->acg_dev_rd_only = read_only; + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) { + /* Devices are allowed to store only CRCs */ + acg_dev->acg_dev_dif_guard_format = SCST_DIF_GUARD_FORMAT_CRC; + } else + acg_dev->acg_dev_dif_guard_format = + acg->tgt->tgt_hw_dif_ip_supported && !dev->dev_dif_ip_not_supported ? + SCST_DIF_GUARD_FORMAT_IP : + SCST_DIF_GUARD_FORMAT_CRC; TRACE_DBG("Adding acg_dev %p to acg_dev_list and dev_acg_dev_list", acg_dev); @@ -4584,6 +4825,9 @@ out_deinit: return; } +static __be16 scst_dif_crc_fn(const void *data, unsigned int len); +static __be16 scst_dif_ip_fn(const void *data, unsigned int len); + /* * scst_mutex supposed to be held, there must not be parallel activity in this * session. May be invoked from inside scst_check_reassign_sessions() which @@ -4614,6 +4858,24 @@ static int scst_alloc_add_tgt_dev(struct scst_session *sess, tgt_dev->lun = acg_dev->lun; tgt_dev->acg_dev = acg_dev; tgt_dev->tgt_dev_rd_only = acg_dev->acg_dev_rd_only || dev->dev_rd_only; + tgt_dev->hw_dif_same_sg_layout_required = sess->tgt->tgt_hw_dif_same_sg_layout_required; + tgt_dev->tgt_dev_dif_guard_format = acg_dev->acg_dev_dif_guard_format; + if (tgt_dev->tgt_dev_dif_guard_format == SCST_DIF_GUARD_FORMAT_IP) + tgt_dev->tgt_dev_dif_crc_fn = scst_dif_ip_fn; + else { + EXTRACHECKS_BUG_ON(tgt_dev->tgt_dev_dif_guard_format != SCST_DIF_GUARD_FORMAT_CRC); + tgt_dev->tgt_dev_dif_crc_fn = scst_dif_crc_fn; + } + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_dev, 0); + tgt_dev->sess = sess; atomic_set(&tgt_dev->tgt_dev_cmd_count, 0); if (acg_dev->acg->acg_black_hole_type != SCST_ACG_BLACK_HOLE_NONE) @@ -4636,7 +4898,9 @@ static int scst_alloc_add_tgt_dev(struct scst_session *sess, tgt_dev->max_sg_cnt = min(ini_sg, sess->tgt->sg_tablesize); if ((sess->tgt->tgtt->use_clustering || ini_use_clustering) && - !sess->tgt->tgtt->no_clustering) + !sess->tgt->tgtt->no_clustering && + !(sess->tgt->tgt_hw_dif_same_sg_layout_required && + (tgt_dev->dev->dev_dif_type != 0))) scst_sgv_pool_use_norm_clust(tgt_dev); if (sess->tgt->tgtt->unchecked_isa_dma || ini_unchecked_isa_dma) @@ -5052,6 +5316,24 @@ out: return res; } +static void scst_prelim_finish_internal_cmd(struct scst_cmd *cmd) +{ + unsigned long flags; + + TRACE_ENTRY(); + + sBUG_ON(!cmd->internal); + + spin_lock_irqsave(&cmd->sess->sess_list_lock, flags); + list_del(&cmd->sess_cmd_list_entry); + spin_unlock_irqrestore(&cmd->sess->sess_list_lock, flags); + + __scst_cmd_put(cmd); + + TRACE_EXIT(); + return; +} + int scst_prepare_request_sense(struct scst_cmd *orig_cmd) { int res = 0; @@ -5079,7 +5361,7 @@ int scst_prepare_request_sense(struct scst_cmd *orig_cmd) rs_cmd->cdb[1] |= scst_get_cmd_dev_d_sense(orig_cmd); rs_cmd->expected_data_direction = SCST_DATA_READ; - rs_cmd->expected_transfer_len = SCST_SENSE_BUFFERSIZE; + rs_cmd->expected_transfer_len_full = SCST_SENSE_BUFFERSIZE; rs_cmd->expected_values_set = 1; TRACE_MGMT_DBG("Adding REQUEST SENSE cmd %p to head of active " @@ -5158,6 +5440,12 @@ struct scst_write_same_priv { int64_t ws_cur_lba; /* in blocks */ int ws_left_to_send; /* in blocks */ + __be16 guard_tag; + __be16 app_tag; + uint32_t ref_tag; + unsigned int dif_valid:1; + unsigned int inc_ref_tag:1; + int ws_max_each;/* in blocks */ int ws_cur_in_flight; @@ -5184,11 +5472,14 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, int64_t lba, int blocks) { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; + struct scst_device *dev = ws_cmd->dev; struct scatterlist *ws_sg = wsp->ws_sg; int res; - uint8_t write16_cdb[16]; + uint8_t write_cdb[32]; + int write_cdb_len; int len = blocks << ws_cmd->dev->block_shift; struct scst_cmd *cmd; + bool needs_dif = wsp->dif_valid; TRACE_ENTRY(); @@ -5203,24 +5494,98 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, goto out; } - 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]); + if (!needs_dif || (dev->dev_dif_type != 2)) { + memset(write_cdb, 0, sizeof(write_cdb)); + write_cdb[0] = WRITE_16; + write_cdb_len = 16; + 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; + } + put_unaligned_be64(lba, &write_cdb[2]); + put_unaligned_be32(blocks, &write_cdb[10]); + } 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 + wrprotect = ws_cmd->cdb[10] & 0xE0; + if (wrprotect == 0) + wrprotect = 0x20; + write_cdb[0] = VARIABLE_LENGTH_CMD; + put_unaligned_be16(SUBCODE_WRITE_32, &write_cdb[8]); + write_cdb[7] = 0x18; + write_cdb_len = 32; + write_cdb[10] = ws_cmd->cdb[10]; + write_cdb[10] |= wrprotect; + put_unaligned_be64(lba, &write_cdb[12]); + put_unaligned_be32(blocks, &write_cdb[28]); + put_unaligned_be32(wsp->ref_tag, &write_cdb[20]); + put_unaligned(wsp->app_tag, &write_cdb[24]); + write_cdb[26] = ws_cmd->cdb[26]; + write_cdb[27] = ws_cmd->cdb[27]; + } - cmd = scst_create_prepare_internal_cmd(ws_cmd, write16_cdb, - sizeof(write16_cdb), SCST_CMD_QUEUE_SIMPLE); + cmd = scst_create_prepare_internal_cmd(ws_cmd, write_cdb, + write_cdb_len, SCST_CMD_QUEUE_SIMPLE); if (cmd == NULL) { res = -ENOMEM; goto out_busy; } cmd->expected_data_direction = SCST_DATA_WRITE; - cmd->expected_transfer_len = len; + cmd->expected_transfer_len_full = len; cmd->expected_values_set = 1; cmd->tgt_i_priv = wsp; + if (needs_dif) { + struct scatterlist *dif_sg, *s; + struct sgv_pool_obj *dif_sgv = NULL; + int dif_bufflen, i, dif_sg_cnt = 0; + + TRACE_DBG("Allocating and filling DIF buff for cmd %p " + "(ws_cmd %p)", cmd, ws_cmd); + + dif_bufflen = blocks << SCST_DIF_TAG_SHIFT; + cmd->expected_transfer_len_full += dif_bufflen; + + dif_sg = sgv_pool_alloc(ws_cmd->tgt_dev->pool, + dif_bufflen, GFP_KERNEL, 0, &dif_sg_cnt, &dif_sgv, + &cmd->dev->dev_mem_lim, NULL); + if (unlikely(dif_sg == NULL)) { + TRACE(TRACE_OUT_OF_MEM, "Unable to alloc DIF sg " + "for %d blocks", blocks); + res = -ENOMEM; + goto out_free_cmd; + } + cmd->out_sgv = dif_sgv; /* hacky, but it isn't used for WRITE(16/32) */ + cmd->tgt_i_dif_sg = dif_sg; + cmd->tgt_i_dif_sg_cnt = dif_sg_cnt; + + TRACE_DBG("dif_sg %p, cnt %d", dif_sg, dif_sg_cnt); + + for_each_sg(dif_sg, s, dif_sg_cnt, i) { + int left = (s->length - s->offset) >> SCST_DIF_TAG_SHIFT; + struct scst_dif_tuple *t = sg_virt(s); + TRACE_DBG("sg %p, offset %d, length %d, left %d", s, + s->offset, s->length, left); + while (left > 0) { + t->app_tag = wsp->app_tag; + t->ref_tag = cpu_to_be32(wsp->ref_tag); + if (wsp->inc_ref_tag) + wsp->ref_tag++; + t->guard_tag = wsp->guard_tag; + t++; + left--; + } + } + } + cmd->tgt_i_sg = ws_sg; cmd->tgt_i_sg_cnt = wsp->ws_sg_cnt; cmd->tgt_i_data_buf_alloced = 1; @@ -5240,6 +5605,9 @@ out: TRACE_EXIT_RES(res); return res; +out_free_cmd: + scst_prelim_finish_internal_cmd(cmd); + out_busy: scst_set_busy(ws_cmd); goto out; @@ -5324,6 +5692,9 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) TRACE_DBG("Write cmd %p finished (ws cmd %p, ws_cur_in_flight %d)", cmd, ws_cmd, wsp->ws_cur_in_flight); + if (cmd->out_sgv != NULL) + sgv_pool_free(cmd->out_sgv, &cmd->dev->dev_mem_lim); + cmd->sg = NULL; cmd->sg_cnt = 0; @@ -5436,10 +5807,11 @@ out_err: void scst_write_same(struct scst_cmd *cmd) { 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; - int i; + uint8_t ctrl_offs = (cmd->cdb_len < 32) ? 1 : 10; TRACE_ENTRY(); @@ -5455,10 +5827,10 @@ void scst_write_same(struct scst_cmd *cmd) goto out_done; } - if (unlikely((cmd->cdb[1] & 0x6) != 0)) { + if (unlikely((cmd->cdb[ctrl_offs] & 0x6) != 0)) { TRACE(TRACE_MINOR, "LBDATA and/or PBDATA (ctrl %x) are not " - "supported", cmd->cdb[1]); - scst_set_invalid_field_in_cdb(cmd, 1, + "supported", cmd->cdb[ctrl_offs]); + scst_set_invalid_field_in_cdb(cmd, ctrl_offs, SCST_INVAL_FIELD_BIT_OFFS_VALID | 1); goto out_done; } @@ -5471,6 +5843,10 @@ void scst_write_same(struct scst_cmd *cmd) goto out_done; } + rc = scst_dif_process_write(cmd); + if (unlikely(rc != 0)) + goto out_done; + wsp = kzalloc(sizeof(*wsp), GFP_KERNEL); if (wsp == NULL) { PRINT_ERROR("Unable to allocate ws_priv (size %zd, cmd %p)", @@ -5525,6 +5901,34 @@ void scst_write_same(struct scst_cmd *cmd) } sBUG_ON(left != 0); /* crash here to avoid data corruption */ + if (scst_cmd_needs_dif_buf(cmd)) { + struct scst_dif_tuple *t; + struct scatterlist *tags_sg = NULL; + int tags_len = 0; + + t = (struct scst_dif_tuple *)scst_get_dif_buf(cmd, &tags_sg, &tags_len); + + wsp->app_tag = t->app_tag; + wsp->ref_tag = be32_to_cpu(t->ref_tag); + wsp->guard_tag = t->guard_tag; + wsp->dif_valid = 1; + + switch (cmd->dev->dev_dif_type) { + case 1: + case 2: + wsp->inc_ref_tag = 1; + break; + case 3: + wsp->inc_ref_tag = 0; + break; + default: + sBUG_ON(1); + break; + } + + scst_put_dif_buf(cmd, t); + } + scst_ws_gen_writes(wsp); out: @@ -6222,6 +6626,29 @@ failed: goto out; } +static void scst_restore_dif_sg(struct scst_cmd *cmd) +{ + int left = cmd->bufflen; + struct scatterlist *sg = cmd->dif_sg; + + TRACE_ENTRY(); + + if (!cmd->dif_sg_normalized) + goto out; + + do { + sg->length = min_t(int, PAGE_SIZE, left); + left -= sg->length; + TRACE_DBG("DIF sg %p, restored len %d (left %d)", sg, + sg->length, left); + sg = __sg_next_inline(sg); + } while (left > 0); + +out: + TRACE_EXIT(); + return; +} + int scst_alloc_space(struct scst_cmd *cmd) { gfp_t gfp_mask; @@ -6247,6 +6674,42 @@ int scst_alloc_space(struct scst_cmd *cmd) if (!scst_on_sg_tablesize_low(cmd, false)) goto out_sg_free; + if ((scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE) || + (scst_get_dif_action(scst_get_dev_dif_actions(cmd->cmd_dif_actions)) != SCST_DIF_ACTION_NONE)) { + int dif_bufflen; + bool same_layout; + + same_layout = tgt_dev->hw_dif_same_sg_layout_required; + if (!same_layout) + dif_bufflen = __scst_cmd_get_bufflen_dif(cmd); + else + dif_bufflen = cmd->bufflen; + + cmd->dif_sg = sgv_pool_alloc(tgt_dev->pool, dif_bufflen, gfp_mask, flags, + &cmd->dif_sg_cnt, &cmd->dif_sgv, &cmd->dev->dev_mem_lim, NULL); + if (unlikely(cmd->dif_sg == NULL)) + goto out_sg_free; + + if (same_layout) { + int left = dif_bufflen; + struct scatterlist *sgd = cmd->sg; + struct scatterlist *sgt = cmd->dif_sg; + int block_shift = cmd->dev->block_shift; + + EXTRACHECKS_BUG_ON(left != cmd->bufflen); + + do { + left -= sgt->length; + sgt->length = (sgd->length >> block_shift) << SCST_DIF_TAG_SHIFT; + TRACE_SG("sgt %p, new len %d", sgt, sgt->length); + sgd = __sg_next_inline(sgd); + sgt = __sg_next_inline(sgt); + } while (left > 0); + + cmd->dif_sg_normalized = 1; + } + } + if (cmd->data_direction != SCST_DATA_BIDI) goto success; @@ -6254,7 +6717,7 @@ int scst_alloc_space(struct scst_cmd *cmd) flags, &cmd->out_sg_cnt, &cmd->out_sgv, &cmd->dev->dev_mem_lim, NULL); if (unlikely(cmd->out_sg == NULL)) - goto out_sg_free; + goto out_dif_sg_free; if (unlikely(cmd->out_sg_cnt > tgt_dev->max_sg_cnt)) if (!scst_on_sg_tablesize_low(cmd, true)) @@ -6273,6 +6736,15 @@ out_out_sg_free: cmd->out_sg = NULL; cmd->out_sg_cnt = 0; +out_dif_sg_free: + if (cmd->dif_sgv != NULL) { + scst_restore_dif_sg(cmd); + sgv_pool_free(cmd->dif_sgv, &cmd->dev->dev_mem_lim); + cmd->dif_sgv = NULL; + cmd->dif_sg = NULL; + cmd->dif_sg_cnt = 0; + } + out_sg_free: sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim); cmd->sgv = NULL; @@ -6309,12 +6781,20 @@ static void scst_release_space(struct scst_cmd *cmd) cmd->out_bufflen = 0; } + if (cmd->dif_sgv != NULL) { + scst_restore_dif_sg(cmd); + sgv_pool_free(cmd->dif_sgv, &cmd->dev->dev_mem_lim); + } + sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim); out_zero: cmd->sgv = NULL; cmd->sg_cnt = 0; cmd->sg = NULL; + cmd->dif_sgv = NULL; + cmd->dif_sg = NULL; + cmd->dif_sg_cnt = 0; cmd->bufflen = 0; cmd->data_len = 0; @@ -6974,13 +7454,15 @@ EXPORT_SYMBOL(scst_scsi_exec_async); /** * scst_copy_sg() - copy data between the command's SGs * - * Copies data between cmd->tgt_i_sg and cmd->sg in direction defined by + * Copies data between cmd->tgt_i_sg and cmd->sg as well as + * cmd->tgt_i_dif_sg and cmd->dif_sg in direction defined by * copy_dir parameter. */ void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir) { struct scatterlist *src_sg, *dst_sg; - unsigned int to_copy; + struct scatterlist *src_sg_dif, *dst_sg_dif; + unsigned int to_copy, to_copy_dif; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) int atomic = scst_cmd_atomic(cmd); #endif @@ -6992,20 +7474,31 @@ void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir) src_sg = cmd->tgt_i_sg; dst_sg = cmd->sg; to_copy = cmd->bufflen; + src_sg_dif = cmd->tgt_i_dif_sg; + dst_sg_dif = cmd->dif_sg; + to_copy_dif = scst_cmd_get_bufflen_dif(cmd); } else { TRACE_MEM("BIDI cmd %p", cmd); src_sg = cmd->tgt_out_sg; dst_sg = cmd->out_sg; to_copy = cmd->out_bufflen; + src_sg_dif = NULL; + dst_sg_dif = NULL; + to_copy_dif = 0; } } else { src_sg = cmd->sg; dst_sg = cmd->tgt_i_sg; to_copy = cmd->adjusted_resp_data_len; + src_sg_dif = cmd->dif_sg; + dst_sg_dif = cmd->tgt_i_dif_sg; + to_copy_dif = scst_cmd_get_bufflen_dif(cmd); } - TRACE_MEM("cmd %p, copy_dir %d, src_sg %p, dst_sg %p, to_copy %lld", - cmd, copy_dir, src_sg, dst_sg, (long long)to_copy); + TRACE_MEM("cmd %p, copy_dir %d, src_sg %p, dst_sg %p, to_copy %lld, " + "src_sg_dif %p, dst_sg_dif %p, to_copy_dif %lld", + cmd, copy_dir, src_sg, dst_sg, (long long)to_copy, + src_sg_dif, dst_sg_dif, (long long)to_copy_dif); if (unlikely(src_sg == NULL) || unlikely(dst_sg == NULL) || unlikely(to_copy == 0)) { @@ -7024,6 +7517,17 @@ void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir) sg_copy(dst_sg, src_sg, 0, to_copy); #endif + if ((src_sg_dif == NULL) || (dst_sg_dif == NULL) || (to_copy_dif == 0)) + goto out; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + sg_copy(dst_sg_dif, src_sg_dif, 0, to_copy_dif, + atomic ? KM_SOFTIRQ0 : KM_USER0, + atomic ? KM_SOFTIRQ1 : KM_USER1); +#else + sg_copy(dst_sg_dif, src_sg_dif, 0, to_copy_dif); +#endif + out: TRACE_EXIT(); return; @@ -7174,10 +7678,1835 @@ out: } EXPORT_SYMBOL(scst_put_buf_full); -static const int SCST_CDB_LENGTH[8] = { 6, 10, 10, 0, 16, 12, 0, 0 }; +static __be16 scst_dif_crc_fn(const void *data, unsigned int len) +{ + return cpu_to_be16(crc_t10dif(data, len)); +} + +static __be16 scst_dif_ip_fn(const void *data, unsigned int len) +{ + return (__force __be16)ip_compute_csum(data, len); +} + +static int scst_verify_dif_type1(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + const struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + uint64_t lba = cmd->lba; + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 1); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_STRIP: + case SCST_DIF_ACTION_PASS_CHECK: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } + EXTRACHECKS_BUG_ON(checks == SCST_DIF_ACTION_NONE); +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + for (i = 0; i < (len >> block_shift); i++) { + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + TRACE_DBG("tags_buf %p", tags_buf); + TRACE_BUFF_FLAG(TRACE_DEBUG, "Tags to verify", + tags_buf, tags_len); + t = (struct scst_dif_tuple *)tags_buf; + } + + if (t->app_tag == SCST_DIF_NO_CHECK_ALL_APP_TAG) { + TRACE_DBG("Skipping tag %lld (cmd %p)", + (long long)lba, cmd); + goto next; + } + + if (checks & SCST_DIF_CHECK_APP_TAG) { + if (t->app_tag != dev->dev_dif_static_app_tag) { + PRINT_WARNING("APP TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), lba %lld, " + "dev %s)", dev->dev_dif_static_app_tag, + t->app_tag, cmd, scst_get_opcode_name(cmd), + (long long)lba, dev->virt_name); + scst_dif_acc_app_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_app_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + if (checks & SCST_DIF_CHECK_REF_TAG) { + if (t->ref_tag != cpu_to_be32(lba & 0xFFFFFFFF)) { + PRINT_WARNING("REF TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), lba %lld, " + "dev %s)", cpu_to_be32(lba & 0xFFFFFFFF), + t->ref_tag, cmd, scst_get_opcode_name(cmd), + (long long)lba, dev->virt_name); + scst_dif_acc_ref_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_ref_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + /* Skip CRC check for internal commands */ + 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 " + "(cmd %p (op %s), lba %lld, " + "dev %s)", crc, t->guard_tag, cmd, + scst_get_opcode_name(cmd), (long long)lba, + dev->virt_name); + scst_dif_acc_guard_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_guard_check_failed)); + res = -EIO; + goto out_put; + } + } + +next: + cur_buf += dev->block_size; + lba++; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + +out: + TRACE_EXIT_RES(res); + return res; + +out_put: + scst_put_buf(cmd, buf); + scst_put_dif_buf(cmd, tags_buf); + goto out; +} + +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS +static void scst_check_fail_ref_tag(struct scst_cmd *cmd) +{ + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + + if (((cmd->dev->dev_dif_mode & SCST_DIF_MODE_TGT) == 0) && + (checks & SCST_DIF_CHECK_REF_TAG)) { + TRACE(TRACE_SCSI|TRACE_MINOR, "No tgt dif_mode, failing " + "cmd %p with ref tag check failed", cmd); + scst_dif_acc_ref_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_ref_tag_check_failed)); + } + return; +} + +static void scst_check_fail_guard_tag(struct scst_cmd *cmd) +{ + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + + if (((cmd->dev->dev_dif_mode & SCST_DIF_MODE_TGT) == 0) && + (checks & SCST_DIF_CHECK_GUARD_TAG)) { + TRACE(TRACE_SCSI|TRACE_MINOR, "No tgt dif_mode, failing " + "cmd %p with guard tag check failed", cmd); + scst_dif_acc_guard_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_guard_check_failed)); + } + return; +} +#endif + +static int scst_generate_dif_type1(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + uint64_t lba = cmd->lba; + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 1); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_INSERT: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + TRACE_DBG("len %d", len); + + for (i = 0; i < (len >> block_shift); i++) { + TRACE_DBG("lba %lld, tags_len %d", (long long)lba, tags_len); + + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + TRACE_DBG("tags_sg %p, tags_buf %p, tags_len %d", + tags_sg, tags_buf, tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + t = (struct scst_dif_tuple *)tags_buf; + } + + t->app_tag = dev->dev_dif_static_app_tag; + t->ref_tag = cpu_to_be32(lba & 0xFFFFFFFF); + t->guard_tag = crc_fn(cur_buf, block_size); + +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + switch (cmd->cmd_corrupt_dif_tag) { + case 0: + break; + case 1: + if (lba == cmd->lba) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 2: + if (lba == (cmd->lba + 1)) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 3: + if (lba == (cmd->lba + 2)) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 4: + if (lba == (cmd->lba + ((cmd->data_len >> dev->block_shift) >> 1))) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 5: + if (lba == (cmd->lba + ((cmd->data_len >> dev->block_shift) - 3))) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 6: + if (lba == (cmd->lba + ((cmd->data_len >> dev->block_shift) - 2))) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + case 7: + if (lba == (cmd->lba + ((cmd->data_len >> dev->block_shift) - 1))) { + if (cmd->cdb[1] & 0x80) { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting ref tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->ref_tag = cpu_to_be32(0xebfeedad); + scst_check_fail_ref_tag(cmd); + } else { + TRACE(TRACE_SCSI|TRACE_MINOR, + "Corrupting guard tag at lba " + "%lld (case %d, cmd %p)", + (long long)lba, + cmd->cmd_corrupt_dif_tag, cmd); + t->guard_tag = cpu_to_be16(0xebed); + scst_check_fail_guard_tag(cmd); + } + } + break; + } +#endif + cur_buf += dev->block_size; + lba++; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_do_dif(struct scst_cmd *cmd, + int (*generate_fn)(struct scst_cmd *cmd), + int (*verify_fn)(struct scst_cmd *cmd)) +{ + int res; + enum scst_dif_actions action = scst_get_dif_action( + scst_get_scst_dif_actions(cmd->cmd_dif_actions)); + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p, action %d", cmd, action); + + switch (action) { + case SCST_DIF_ACTION_PASS: + /* Nothing to do */ + TRACE_DBG("PASS DIF action, skipping (cmd %p)", cmd); + res = 0; + break; + + case SCST_DIF_ACTION_STRIP: + 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 + res = 0; + break; + } + + case SCST_DIF_ACTION_INSERT: + res = generate_fn(cmd); + break; + + default: + EXTRACHECKS_BUG_ON(1); + /* go through */ + case SCST_DIF_ACTION_NONE: + /* Nothing to do */ + TRACE_DBG("NONE DIF action, skipping (cmd %p)", cmd); + res = 0; + break; + } + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_dif_type1(struct scst_cmd *cmd) +{ + int res; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->dev->dev_dif_type != 1); + + res = scst_do_dif(cmd, scst_generate_dif_type1, scst_verify_dif_type1); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_verify_dif_type2(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + const struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + uint64_t lba = cmd->lba; + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + uint32_t ref_tag = scst_cmd_get_dif_exp_ref_tag(cmd); + /* Let's keep both in BE */ + __be16 app_tag_mask = cpu_to_be16(scst_cmd_get_dif_app_tag_mask(cmd)); + __be16 app_tag_masked = cpu_to_be16(scst_cmd_get_dif_exp_app_tag(cmd)) & app_tag_mask; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 2); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_STRIP: + case SCST_DIF_ACTION_PASS_CHECK: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } + EXTRACHECKS_BUG_ON(checks == SCST_DIF_ACTION_NONE); +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + for (i = 0; i < (len >> block_shift); i++) { + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + t = (struct scst_dif_tuple *)tags_buf; + } + + if (t->app_tag == SCST_DIF_NO_CHECK_ALL_APP_TAG) { + TRACE_DBG("Skipping tag (cmd %p)", cmd); + goto next; + } + + if (checks & SCST_DIF_CHECK_APP_TAG) { + if ((t->app_tag & app_tag_mask) != app_tag_masked) { + PRINT_WARNING("APP TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), dev %s)", + app_tag_masked, t->app_tag & app_tag_mask, + cmd, scst_get_opcode_name(cmd), dev->virt_name); + scst_dif_acc_app_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_app_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + if (checks & SCST_DIF_CHECK_REF_TAG) { + if (t->ref_tag != cpu_to_be32(ref_tag)) { + PRINT_WARNING("REF TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), dev %s)", + cpu_to_be32(ref_tag), + t->ref_tag, cmd, scst_get_opcode_name(cmd), + dev->virt_name); + scst_dif_acc_ref_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_ref_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + /* Skip CRC check for internal commands */ + 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 " + "(cmd %p (op %s), lba %lld, " + "dev %s)", crc, t->guard_tag, cmd, + scst_get_opcode_name(cmd), (long long)lba, + dev->virt_name); + scst_dif_acc_guard_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_guard_check_failed)); + res = -EIO; + goto out_put; + } + } + +next: + cur_buf += dev->block_size; + lba++; + ref_tag++; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + +out: + TRACE_EXIT_RES(res); + return res; + +out_put: + scst_put_buf(cmd, buf); + scst_put_dif_buf(cmd, tags_buf); + goto out; +} + +static int scst_generate_dif_type2(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + uint32_t ref_tag = scst_cmd_get_dif_exp_ref_tag(cmd); + /* Let's keep both in BE */ + __be16 app_tag_mask = cpu_to_be16(scst_cmd_get_dif_app_tag_mask(cmd)); + __be16 app_tag_masked = cpu_to_be16(scst_cmd_get_dif_exp_app_tag(cmd)) & app_tag_mask; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 2); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_INSERT: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + TRACE_DBG("len %d", len); + + for (i = 0; i < (len >> block_shift); i++) { + TRACE_DBG("tags_len %d", tags_len); + + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + TRACE_DBG("tags_sg %p, tags_buf %p, tags_len %d", + tags_sg, tags_buf, tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + t = (struct scst_dif_tuple *)tags_buf; + } + + t->app_tag = app_tag_masked; + t->ref_tag = cpu_to_be32(ref_tag); + t->guard_tag = crc_fn(cur_buf, block_size); + + cur_buf += dev->block_size; + ref_tag++; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_dif_type2(struct scst_cmd *cmd) +{ + int res; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->dev->dev_dif_type != 2); + + res = scst_do_dif(cmd, scst_generate_dif_type2, scst_verify_dif_type2); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_verify_dif_type3(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + const struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + uint64_t lba = cmd->lba; + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 3); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_STRIP: + case SCST_DIF_ACTION_PASS_CHECK: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } + EXTRACHECKS_BUG_ON(checks == SCST_DIF_ACTION_NONE); +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + for (i = 0; i < (len >> block_shift); i++) { + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + t = (struct scst_dif_tuple *)tags_buf; + } + + if ((t->app_tag == SCST_DIF_NO_CHECK_ALL_APP_TAG) && + (t->ref_tag == SCST_DIF_NO_CHECK_ALL_REF_TAG)) { + TRACE_DBG("Skipping tag (cmd %p)", cmd); + goto next; + } + + if (checks & SCST_DIF_CHECK_APP_TAG) { + if (t->app_tag != dev->dev_dif_static_app_tag) { + PRINT_WARNING("APP TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), dev %s)", + dev->dev_dif_static_app_tag, + t->app_tag, cmd, scst_get_opcode_name(cmd), + dev->virt_name); + scst_dif_acc_app_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_app_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + if (checks & SCST_DIF_CHECK_REF_TAG) { + if (t->ref_tag != dev->dev_dif_static_app_ref_tag) { + PRINT_WARNING("REF TAG check failed, " + "expected 0x%x, seeing " + "0x%x (cmd %p (op %s), dev %s)", + dev->dev_dif_static_app_ref_tag, + t->ref_tag, cmd, scst_get_opcode_name(cmd), + dev->virt_name); + scst_dif_acc_ref_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_ref_tag_check_failed)); + res = -EIO; + goto out_put; + } + } + + /* Skip CRC check for internal commands */ + 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 " + "(cmd %p (op %s), lba %lld, " + "dev %s)", crc, t->guard_tag, cmd, + scst_get_opcode_name(cmd), (long long)lba, + dev->virt_name); + scst_dif_acc_guard_check_failed_scst(cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_logical_block_guard_check_failed)); + res = -EIO; + goto out_put; + } + } + +next: + cur_buf += dev->block_size; + lba++; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + +out: + TRACE_EXIT_RES(res); + return res; + +out_put: + scst_put_buf(cmd, buf); + scst_put_dif_buf(cmd, tags_buf); + goto out; +} + +static int scst_generate_dif_type3(struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + int len, tags_len = 0, tag_size = 1 << SCST_DIF_TAG_SHIFT; + struct scatterlist *tags_sg = NULL; + uint8_t *buf, *tags_buf = NULL; + struct scst_dif_tuple *t = NULL; /* to silence compiler warning */ + int block_size = dev->block_size, block_shift = dev->block_shift; + __be16 (*crc_fn)(const void *buffer, unsigned int len); + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type != 3); + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (scst_get_dif_action(scst_get_scst_dif_actions(cmd->cmd_dif_actions))) { + case SCST_DIF_ACTION_INSERT: + break; + default: + EXTRACHECKS_BUG_ON(1); + break; + } +#endif + + crc_fn = cmd->tgt_dev->tgt_dev_dif_crc_fn; + + len = scst_get_buf_first(cmd, &buf); + while (len > 0) { + int i; + uint8_t *cur_buf = buf; + + TRACE_DBG("len %d", len); + + for (i = 0; i < (len >> block_shift); i++) { + TRACE_DBG("tags_len %d", tags_len); + + if (tags_buf == NULL) { + tags_buf = scst_get_dif_buf(cmd, &tags_sg, &tags_len); + TRACE_DBG("tags_sg %p, tags_buf %p, tags_len %d", + tags_sg, tags_buf, tags_len); + EXTRACHECKS_BUG_ON(tags_len <= 0); + t = (struct scst_dif_tuple *)tags_buf; + } + + t->app_tag = dev->dev_dif_static_app_tag; + t->ref_tag = dev->dev_dif_static_app_ref_tag; + t->guard_tag = crc_fn(cur_buf, block_size); + + cur_buf += dev->block_size; + + t++; + tags_len -= tag_size; + if (tags_len == 0) { + scst_put_dif_buf(cmd, tags_buf); + tags_buf = NULL; + } + } + + scst_put_buf(cmd, buf); + len = scst_get_buf_next(cmd, &buf); + } + + EXTRACHECKS_BUG_ON(tags_buf != NULL); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_dif_type3(struct scst_cmd *cmd) +{ + int res; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->dev->dev_dif_type != 3); + + res = scst_do_dif(cmd, scst_generate_dif_type3, scst_verify_dif_type3); + + TRACE_EXIT_RES(res); + return res; +} + +static void scst_init_dif_checks(struct scst_device *dev) +{ + switch (dev->dev_dif_type) { + case 0: + dev->dif_app_chk = 0; + dev->dif_ref_chk = 0; + break; + case 1: + if (scst_dev_get_dif_static_app_tag(dev) != SCST_DIF_NO_CHECK_APP_TAG) + dev->dif_app_chk = SCST_DIF_CHECK_APP_TAG; + else + dev->dif_app_chk = 0; + dev->dif_ref_chk = SCST_DIF_CHECK_REF_TAG; + break; + case 2: + dev->dif_app_chk = dev->ato ? SCST_DIF_CHECK_APP_TAG : 0; + dev->dif_ref_chk = SCST_DIF_CHECK_REF_TAG; + break; + case 3: + if (scst_dev_get_dif_static_app_tag_combined(dev) != SCST_DIF_NO_CHECK_APP_TAG) { + dev->dif_app_chk = SCST_DIF_CHECK_APP_TAG; + dev->dif_ref_chk = SCST_DIF_CHECK_REF_TAG; + } else { + dev->dif_app_chk = 0; + dev->dif_ref_chk = 0; + } + break; + default: + WARN_ON(1); + break; + } + return; +} + +/** + * scst_dev_set_dif_static_app_tag_combined() - sets static app tag + * + * Description: + * Sets static app tag for both APP TAG and REF TAG (DIF mode 3) + */ +void scst_dev_set_dif_static_app_tag_combined( + struct scst_device *dev, __be64 app_tag) +{ + uint64_t a = be64_to_cpu(app_tag); + + dev->dev_dif_static_app_tag = cpu_to_be16(a & 0xFFFF); + if (dev->dev_dif_type == 3) + dev->dev_dif_static_app_ref_tag = cpu_to_be32((a >> 16) & 0xFFFFFFFF); + scst_init_dif_checks(dev); + return; +} +EXPORT_SYMBOL_GPL(scst_dev_set_dif_static_app_tag_combined); + +static int scst_init_dif_actions(struct scst_device *dev) +{ + int res = 0; + + TRACE_ENTRY(); + + BUILD_BUG_ON(SCST_DIF_ACTION_NONE != 0); + + if ((dev->dev_dif_type == 0) || (dev->dev_dif_mode == 0)) { + dev->dev_dif_rd_actions = SCST_DIF_ACTION_NONE; + dev->dev_dif_wr_actions = SCST_DIF_ACTION_NONE; + dev->dev_dif_rd_prot0_actions = SCST_DIF_ACTION_NONE; + dev->dev_dif_wr_prot0_actions = SCST_DIF_ACTION_NONE; + goto out; + } + + if ((dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) && + !(dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE)) { + PRINT_ERROR("DEV CHECK is not allowed without DEV STORE! " + "(dev %s)", dev->virt_name); + res = -EINVAL; + goto out; + } + + /* + * COMMON RULES + * ============ + * + * 1. For SCST STRIP and PASS_CHECK as well as PASS and NONE are + * the same. + * + * 2. For dev handlers STRIP and INSERT are invalid, PASS and NONE are + * the same. Also, PASS and PASS_CHECK are equal regarding STORE, + * if STORE is configured. + */ + + BUILD_BUG_ON(SCST_DIF_MODE_DEV != (SCST_DIF_MODE_DEV_CHECK | SCST_DIF_MODE_DEV_STORE)); + + if (dev->dev_dif_mode & SCST_DIF_MODE_TGT) { + if (dev->dev_dif_mode & SCST_DIF_MODE_SCST) { + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV) { /* TGT, SCST, DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + + if (dev->dpicz) { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + } else { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_STRIP); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + } + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_INSERT); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS); + } else { /* TGT, SCST, no DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_INSERT); + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_NONE); + + if (dev->dpicz) { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + } else { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_STRIP); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_INSERT); + } + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_STRIP); + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_INSERT); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_STRIP); + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + } + } else { /* TGT, no SCST */ + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV) { /* TGT, no SCST, DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + } else { + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + } + + if (dev->dpicz) { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + } else { + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_STRIP); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + } + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_INSERT); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS); + } else { /* TGT, no SCST, no DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_INSERT); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + +#if 1 /* + * Workaround for Emulex ASIC errata to pass Oracle certification. + * + * Emulex ASIC in the STRIP mode can't distinguish between different types of + * PI tags mismatches (reference, guard, application), hence the Oracle + * certification fails. Workaround is to get the tags data in the memory, so + * then, if HW is signaling that there is some tag mismatch, manually recheck + * in the driver, then set correct tag mismatch sense. + */ + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); +#else + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_STRIP); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_NONE); +#endif + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + } + } + } else { /* No TGT */ + if (dev->dev_dif_mode & SCST_DIF_MODE_SCST) { /* No TGT, SCST */ + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV) { /* No TGT, SCST, DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + if (dev->dpicz) { + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + } else { + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_STRIP); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + } + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_INSERT); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS); + } else { /* No TGT, SCST, no DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_INSERT); + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_STRIP); + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_NONE); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + } + } else { /* No TGT, no SCST */ + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV) { /* No TGT, no SCST, DEV */ + scst_set_tgt_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + if (dev->dpicz) { + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_NONE); + } else { + scst_set_scst_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_rd_prot0_actions, SCST_DIF_ACTION_PASS); + } + + scst_set_tgt_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + scst_set_scst_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_actions, SCST_DIF_ACTION_PASS); + + scst_set_tgt_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_NONE); + scst_set_scst_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_INSERT); + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) { + /* STORE might be set as well */ + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS_CHECK); + } else + scst_set_dev_dif_action(&dev->dev_dif_wr_prot0_actions, SCST_DIF_ACTION_PASS); + } else { /* No TGT, no SCST, no DEV */ + sBUG_ON(1); + } + } + } + + TRACE_DBG("rd_actions %x, rd_prot0_actions %x, wr_actions %x, " + "wr_prot0_actions %x", dev->dev_dif_rd_actions, + dev->dev_dif_rd_prot0_actions, dev->dev_dif_wr_actions, + dev->dev_dif_wr_prot0_actions); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/** + * scst_set_dif_params() - sets DIF parameters + * + * Description: + * Sets DIF parameters for device + * + * Returns: 0 on success, negative error code otherwise. + */ +int scst_set_dif_params(struct scst_device *dev, enum scst_dif_mode dif_mode, + int dif_type) +{ + int res = -EINVAL; + + TRACE_ENTRY(); + + BUILD_BUG_ON(SCST_DIF_ACTION_NONE != 0); + + if (((dif_mode & ~(SCST_DIF_MODE_TGT|SCST_DIF_MODE_SCST|SCST_DIF_MODE_DEV)) != 0)) { + PRINT_ERROR("Invalid DIF mode %x", dif_mode); + goto out; + } + + if ((dif_type < 0) || (dif_type > 3)) { + PRINT_ERROR("DIF type %d not supported", dif_type); + goto out; + } + + if ((dif_mode == 0) && (dif_type != 0)) { + PRINT_ERROR("With DIF mode 0 DIF type must be 0 (dev %s)", + dev->virt_name); + goto out; + } + + dev->dev_dif_mode = dif_mode; + dev->dev_dif_type = dif_type; + + if (dif_type == 0) { + TRACE_DBG("DIF type 0, ignoring DIF mode"); + goto out_none; + } + + if (dif_mode == 0) { + TRACE_DBG("DIF mode 0"); + goto out_none; + } + + scst_init_dif_checks(dev); + + if (dif_mode & SCST_DIF_MODE_SCST) { + switch (dif_type) { + case 1: + dev->dev_dif_fn = scst_dif_type1; + break; + case 2: + dev->dev_dif_fn = scst_dif_type2; + break; + case 3: + dev->dev_dif_fn = scst_dif_type3; + break; + default: + sBUG_ON(1); + break; + } + } else { + if ((dif_type == 1) && !(dif_mode & SCST_DIF_MODE_TGT)) + dev->dev_dif_fn = scst_dif_type1; + else if ((dif_type == 1) && (dif_mode & SCST_DIF_MODE_TGT)) + dev->dev_dif_fn = scst_dif_none_type1; + else + dev->dev_dif_fn = scst_dif_none; + } + + res = scst_init_dif_actions(dev); + if (res != 0) + goto out; + + res = scst_dev_sysfs_dif_create(dev); + if (res != 0) + goto out; + + PRINT_INFO("device %s: DIF mode %x, DIF type %d", dev->virt_name, + dev->dev_dif_mode, dev->dev_dif_type); + TRACE_DBG("app_chk %x, ref_chk %x", dev->dif_app_chk, dev->dif_ref_chk); + +out: + TRACE_EXIT_RES(res); + return res; + +out_none: + dev->dif_app_chk = 0; + if (dev->dev_dif_type == 3) + dev->dif_ref_chk = 0; + dev->dev_dif_static_app_tag = SCST_DIF_NO_CHECK_APP_TAG; + dev->dev_dif_static_app_ref_tag = SCST_DIF_NO_CHECK_APP_TAG; + dev->dev_dif_fn = scst_dif_none; + res = scst_init_dif_actions(dev); + goto out; +} +EXPORT_SYMBOL_GPL(scst_set_dif_params); + +static int scst_dif_none(struct scst_cmd *cmd) +{ + int res = 0; + + TRACE_ENTRY(); + + /* Nothing to do */ + + TRACE_EXIT_RES(res); + return res; +} + +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS +static int scst_dif_none_type1(struct scst_cmd *cmd) +{ + int res = 0; + + TRACE_ENTRY(); + + if (unlikely(cmd->cmd_corrupt_dif_tag != 0)) { + EXTRACHECKS_BUG_ON(cmd->dev->dev_dif_type != 1); + res = scst_dif_type1(cmd); + } + + TRACE_EXIT_RES(res); + return res; +} +#endif + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int __scst_parse_rdprotect(struct scst_cmd *cmd, int rdprotect, + int rdprotect_offs) +{ + int res = 0; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + TRACE_DBG("rdprotect %x", rdprotect); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type == 0); + + switch (rdprotect) { + case 0: + cmd->cmd_dif_actions = dev->dev_dif_rd_prot0_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + break; + case 1: + case 5: + cmd->cmd_dif_actions = dev->dev_dif_rd_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + cmd->tgt_dif_data_expected = 1; + break; + case 2: + cmd->cmd_dif_actions = dev->dev_dif_rd_actions | + dev->dif_app_chk | dev->dif_ref_chk; + cmd->tgt_dif_data_expected = 1; + break; + case 3: + cmd->cmd_dif_actions = dev->dev_dif_rd_actions; + cmd->tgt_dif_data_expected = 1; + break; + case 4: + cmd->cmd_dif_actions = dev->dev_dif_rd_actions | + dev->dif_app_chk; + cmd->tgt_dif_data_expected = 1; + break; + default: + TRACE(TRACE_SCSI|TRACE_MINOR, "Invalid RDPROTECT value %x " + "(dev %s, cmd %p)", rdprotect, dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, rdprotect_offs, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + + TRACE_DBG("cmd_dif_actions %x, tgt_dif_data_expected %d (cmd %p)", + cmd->cmd_dif_actions, cmd->tgt_dif_data_expected, cmd); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_rdprotect(struct scst_cmd *cmd) +{ + int res = 0; + int rdprotect = (cmd->cdb[1] & 0xE0) >> 5; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) { + if (likely(rdprotect == 0)) + goto out; + + TRACE(TRACE_SCSI|TRACE_MINOR, "RDPROTECT %x and no DIF " + "device %s (cmd %p)", rdprotect, + dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, 1, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + + if (unlikely((dev->dev_dif_type == 2) && (rdprotect != 0))) { + TRACE(TRACE_SCSI|TRACE_MINOR, "Non-32-byte CDBs with non-zero " + "rdprotect (%d) are not allowed in DIF type 2 " + "(dev %s, cmd %p, op %s)", rdprotect, dev->virt_name, + cmd, scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + if (unlikely(cmd->cmd_corrupt_dif_tag != 0)) { + if ((cmd->dev->dev_dif_type == 1) && + ((dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) == 0)) { + TRACE_DBG("cmd_corrupt_dif_tag %x, rdprotect %x, cmd %p", + cmd->cmd_corrupt_dif_tag, rdprotect, cmd); + /* Reset the highest bit used to choose which tag to corrupt */ + rdprotect &= 3; + } else { + /* Restore it */ + cmd->cmd_corrupt_dif_tag = 0; + } + } +#endif + + res = __scst_parse_rdprotect(cmd, rdprotect, 1); + +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + if (unlikely(cmd->cmd_corrupt_dif_tag != 0)) { + if (res == 0) { + TRACE_DBG("Corrupt DIF tag, (re)set cmd_dif_actions " + "(cmd %p)", cmd); + /* + * Then (re)set it just in case if dif_mode is tgt-only. + * It's OK, because we have ensured that dif_mode + * doesn't contain dev_store. + */ + scst_set_scst_dif_action(&cmd->cmd_dif_actions, + SCST_DIF_ACTION_INSERT); + if (scst_get_dif_action(scst_get_read_dif_tgt_actions(cmd)) == SCST_DIF_ACTION_INSERT) + scst_set_tgt_dif_action(&cmd->cmd_dif_actions, + SCST_DIF_ACTION_PASS_CHECK); + } else + TRACE_DBG("parse rdprotect failed: %d", res); + } +#endif + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_rdprotect32(struct scst_cmd *cmd) +{ + int res = 0; + int rdprotect = (cmd->cdb[10] & 0xE0) >> 5; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (unlikely(dev->dev_dif_type != 2)) { + TRACE(TRACE_SCSI|TRACE_MINOR, "32-byte CDBs are not " + "allowed in DIF type %d (dev %s, cmd %p, op %s)", + dev->dev_dif_type, dev->virt_name, cmd, + scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + + res = __scst_parse_rdprotect(cmd, rdprotect, 10); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int __scst_parse_wrprotect(struct scst_cmd *cmd, int wrprotect, + int wrprotect_offs) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + TRACE_DBG("wrprotect %x", wrprotect); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type == 0); + + switch (wrprotect) { + case 0: + cmd->cmd_dif_actions = dev->dev_dif_wr_prot0_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + break; + case 1: + cmd->cmd_dif_actions = dev->dev_dif_wr_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + cmd->tgt_dif_data_expected = 1; + break; + case 2: + cmd->cmd_dif_actions = dev->dev_dif_wr_actions | + dev->dif_app_chk | dev->dif_ref_chk; + cmd->tgt_dif_data_expected = 1; + break; + case 3: + cmd->cmd_dif_actions = dev->dev_dif_wr_actions; + cmd->tgt_dif_data_expected = 1; + break; + case 4: + cmd->cmd_dif_actions = dev->dev_dif_wr_actions | + SCST_DIF_CHECK_GUARD_TAG; + cmd->tgt_dif_data_expected = 1; + break; + case 5: + cmd->cmd_dif_actions = dev->dev_dif_wr_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + cmd->tgt_dif_data_expected = 1; + break; + default: + TRACE(TRACE_SCSI|TRACE_MINOR, "Invalid WRPROTECT value %x " + "(dev %s, cmd %p)", wrprotect, dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, wrprotect_offs, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + + TRACE_DBG("cmd_dif_actions %x, tgt_dif_data_expected %d (cmd %p)", + cmd->cmd_dif_actions, cmd->tgt_dif_data_expected, cmd); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_wrprotect(struct scst_cmd *cmd) +{ + int res = 0; + int wrprotect = (cmd->cdb[1] & 0xE0) >> 5; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) { + if (likely(wrprotect == 0)) + goto out; + + TRACE(TRACE_SCSI|TRACE_MINOR, "WRPROTECT %x and no DIF " + "device %s (cmd %p)", wrprotect, + dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, 1, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + + if (unlikely((dev->dev_dif_type == 2) && (wrprotect != 0))) { + TRACE(TRACE_SCSI|TRACE_MINOR, "Non-32-byte CDBs with non-zero " + "wrprotect (%d) are not allowed in DIF type 2 " + "(dev %s, cmd %p, op %s)", wrprotect, dev->virt_name, + cmd, scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + + res = __scst_parse_wrprotect(cmd, wrprotect, 1); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_wrprotect32(struct scst_cmd *cmd) +{ + int res = 0; + int wrprotect = (cmd->cdb[10] & 0xE0) >> 5; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (unlikely(dev->dev_dif_type != 2)) { + TRACE(TRACE_SCSI|TRACE_MINOR, "32-byte CDBs are not " + "allowed in DIF type %d (dev %s, cmd %p, op %s)", + dev->dev_dif_type, dev->virt_name, cmd, + scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + + res = __scst_parse_wrprotect(cmd, wrprotect, 10); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int __scst_parse_vrprotect(struct scst_cmd *cmd, int vrprotect_offs) +{ + int res = 0; + struct scst_device *dev = cmd->dev; + int vrprotect = (cmd->cdb[vrprotect_offs] & 0xE0) >> 5; + int bytchk = (cmd->cdb[vrprotect_offs] & 6) >> 1; + + TRACE_ENTRY(); + + TRACE_DBG("vrprotect %x", vrprotect); + + EXTRACHECKS_BUG_ON(dev->dev_dif_type == 0); + + switch (bytchk) { + case 0: + switch (vrprotect) { + case 0: + if (dev->dpicz) { + cmd->cmd_dif_actions = 0; + break; + } + /* else go through */ + case 1: + case 5: + cmd->cmd_dif_actions = SCST_DIF_CHECK_GUARD_TAG | + dev->dif_app_chk | dev->dif_ref_chk; + break; + case 2: + cmd->cmd_dif_actions = dev->dif_app_chk | dev->dif_ref_chk; + break; + case 3: + cmd->cmd_dif_actions = 0; + break; + case 4: + cmd->cmd_dif_actions = SCST_DIF_CHECK_GUARD_TAG; + break; + default: + TRACE(TRACE_SCSI|TRACE_MINOR, "Invalid VRPROTECT value %x " + "(dev %s, cmd %p)", vrprotect, dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, vrprotect_offs, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + break; + case 1: + case 3: + switch (vrprotect) { + case 0: + if (dev->dpicz) + cmd->cmd_dif_actions = 0; + else + cmd->cmd_dif_actions = dev->dev_dif_wr_actions | + SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | + dev->dif_ref_chk; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + cmd->cmd_dif_actions = 0; + break; + default: + TRACE(TRACE_SCSI|TRACE_MINOR, "Invalid VRPROTECT value %x " + "(dev %s, cmd %p)", vrprotect, dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, vrprotect_offs, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + break; + default: + sBUG_ON(1); + break; + } + + TRACE_DBG("cmd_dif_actions %x, tgt_dif_data_expected %d (cmd %p, " + "bytchk %d)", cmd->cmd_dif_actions, cmd->tgt_dif_data_expected, + cmd, bytchk); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_vrprotect(struct scst_cmd *cmd) +{ + int res = 0; + int vrprotect = (cmd->cdb[1] & 0xE0) >> 5; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) { + if (likely(vrprotect == 0)) + goto out; + + TRACE(TRACE_SCSI|TRACE_MINOR, "VRPROTECT %x and no DIF " + "device %s (cmd %p)", vrprotect, + dev->virt_name, cmd); + scst_set_invalid_field_in_cdb(cmd, 1, + 5|SCST_INVAL_FIELD_BIT_OFFS_VALID); + res = EINVAL; + goto out; + } + + if (unlikely((dev->dev_dif_type == 2) && (vrprotect != 0))) { + TRACE(TRACE_SCSI|TRACE_MINOR, "Non-32-byte CDBs with non-zero " + "vrprotect (%d) are not allowed in DIF type 2 " + "(dev %s, cmd %p, op %s)", vrprotect, + dev->virt_name, cmd, scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + + res = __scst_parse_vrprotect(cmd, 1); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns 0 on success or POSITIVE error code otherwise (to match + * scst_get_cdb_info() semantic) + */ +static int scst_parse_vrprotect32(struct scst_cmd *cmd) +{ + int res = 0; + const struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (unlikely(dev->dev_dif_type != 2)) { + TRACE(TRACE_SCSI|TRACE_MINOR, "32-byte CDBs are not " + "allowed in DIF type %d (dev %s, cmd %p, op %s)", + dev->dev_dif_type, dev->virt_name, cmd, + scst_get_opcode_name(cmd)); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + res = EINVAL; + goto out; + } + + res = __scst_parse_vrprotect(cmd, 10); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * So far we support the only variable length CDBs, 32 bytes SBC PI type 2 + * commands, so 32 on the forth place is OK. Don't forget to change it + * adding in this group new commands with other lengths! + */ +static const int scst_cdb_length[8] = { 6, 10, 10, 32, 16, 12, 0, 0 }; #define SCST_CDB_GROUP(opcode) ((opcode >> 5) & 0x7) -#define SCST_GET_CDB_LEN(opcode) SCST_CDB_LENGTH[SCST_CDB_GROUP(opcode)] +#define SCST_GET_CDB_LEN(opcode) scst_cdb_length[SCST_CDB_GROUP(opcode)] static int get_cdb_info_len_10(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) @@ -7412,7 +9741,7 @@ static int get_cdb_info_verify10(struct scst_cmd *cmd, cmd->data_len = get_unaligned_be16(cmd->cdb + sdbops->info_len_off); cmd->data_direction = SCST_DATA_NONE; } - return 0; + return scst_parse_vrprotect(cmd); } static int get_cdb_info_verify6(struct scst_cmd *cmd, @@ -7468,7 +9797,7 @@ static int get_cdb_info_verify12(struct scst_cmd *cmd, cmd->data_len = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); cmd->data_direction = SCST_DATA_NONE; } - return 0; + return scst_parse_vrprotect(cmd); } static int get_cdb_info_verify16(struct scst_cmd *cmd, @@ -7498,7 +9827,37 @@ static int get_cdb_info_verify16(struct scst_cmd *cmd, cmd->data_len = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); cmd->data_direction = SCST_DATA_NONE; } - return 0; + return scst_parse_vrprotect(cmd); +} + +static int get_cdb_info_verify32(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + if (unlikely(cmd->cdb[10] & 4)) { + PRINT_ERROR("VERIFY(16): BYTCHK 1x not supported (dev %s)", + cmd->dev ? cmd->dev->virt_name : NULL); + scst_set_invalid_field_in_cdb(cmd, 1, + 2 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + return 1; + } + + cmd->lba = get_unaligned_be64(cmd->cdb + sdbops->info_lba_off); + if (cmd->cdb[10] & 2) { + cmd->bufflen = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); + if (unlikely(cmd->bufflen & SCST_MAX_VALID_BUFFLEN_MASK)) { + PRINT_ERROR("Too big bufflen %d (op %s)", + cmd->bufflen, scst_get_opcode_name(cmd)); + scst_set_invalid_field_in_cdb(cmd, sdbops->info_len_off, 0); + return 1; + } + cmd->data_len = cmd->bufflen; + cmd->data_direction = SCST_DATA_WRITE; + } else { + cmd->bufflen = 0; + cmd->data_len = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); + cmd->data_direction = SCST_DATA_NONE; + } + return scst_parse_vrprotect32(cmd); } static int get_cdb_info_len_1(struct scst_cmd *cmd, @@ -7512,7 +9871,7 @@ static int get_cdb_info_len_1(struct scst_cmd *cmd, return 0; } -static int get_cdb_info_lba_3_len_1_256(struct scst_cmd *cmd, +static inline int get_cdb_info_lba_3_len_1_256(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { cmd->lba = (cmd->cdb[sdbops->info_lba_off] & 0x1F) << 16; @@ -7533,6 +9892,40 @@ static int get_cdb_info_lba_3_len_1_256(struct scst_cmd *cmd, return 0; } +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 | + SCST_DIF_CHECK_REF_TAG; + } +out: + return res; +} + +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 | + SCST_DIF_CHECK_REF_TAG; + } +out: + return res; +} + static int get_cdb_info_len_2(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { @@ -7627,7 +10020,32 @@ static int get_cdb_info_lba_4_len_2(struct scst_cmd *cmd, return 0; } -static int get_cdb_info_lba_4_len_4(struct scst_cmd *cmd, +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 { +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + EXTRACHECKS_BUG_ON(cmd->cdb[0] != READ_10); + cmd->cmd_corrupt_dif_tag = (cmd->cdb[6] & 0xE0) >> 5; +#endif + return scst_parse_rdprotect(cmd); + } +} + +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 + return scst_parse_wrprotect(cmd); +} + +static inline int get_cdb_info_lba_4_len_4(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { cmd->lba = get_unaligned_be32(cmd->cdb + sdbops->info_lba_off); @@ -7642,7 +10060,27 @@ static int get_cdb_info_lba_4_len_4(struct scst_cmd *cmd, return 0; } -static int get_cdb_info_lba_8_len_4(struct scst_cmd *cmd, +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 + return scst_parse_rdprotect(cmd); +} + +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 + return scst_parse_wrprotect(cmd); +} + +static inline int get_cdb_info_lba_8_len_4(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { cmd->lba = get_unaligned_be64(cmd->cdb + sdbops->info_lba_off); @@ -7657,13 +10095,58 @@ static int get_cdb_info_lba_8_len_4(struct scst_cmd *cmd, return 0; } +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 { +#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS + EXTRACHECKS_BUG_ON(cmd->cdb[0] != READ_16); + cmd->cmd_corrupt_dif_tag = (cmd->cdb[14] & 0xE0) >> 5; +#endif + return scst_parse_rdprotect(cmd); + } +} + +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 + return scst_parse_wrprotect(cmd); +} + +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 + return scst_parse_wrprotect32(cmd); +} + +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 + return scst_parse_rdprotect32(cmd); +} + static int get_cdb_info_write_same10(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { cmd->lba = get_unaligned_be32(cmd->cdb + sdbops->info_lba_off); cmd->bufflen = 1; cmd->data_len = get_unaligned_be16(cmd->cdb + sdbops->info_len_off); - return 0; + return scst_parse_wrprotect(cmd); } static int get_cdb_info_write_same16(struct scst_cmd *cmd, @@ -7672,7 +10155,132 @@ static int get_cdb_info_write_same16(struct scst_cmd *cmd, cmd->lba = get_unaligned_be64(cmd->cdb + sdbops->info_lba_off); cmd->bufflen = 1; cmd->data_len = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); - return 0; + return scst_parse_wrprotect(cmd); +} + +static int get_cdb_info_write_same32(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + cmd->lba = get_unaligned_be64(cmd->cdb + sdbops->info_lba_off); + cmd->bufflen = 1; + cmd->data_len = get_unaligned_be32(cmd->cdb + sdbops->info_len_off); + return scst_parse_wrprotect32(cmd); +} + +static int scst_set_cmd_from_cdb_info(struct scst_cmd *cmd, + const struct scst_sdbops *ptr) +{ + cmd->cdb_len = SCST_GET_CDB_LEN(cmd->cdb[0]); + cmd->cmd_naca = (cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_NACA_BIT); + cmd->cmd_linked = (cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_LINK_BIT); + cmd->op_name = ptr->info_op_name; + cmd->data_direction = ptr->info_data_direction; + cmd->op_flags = ptr->info_op_flags | SCST_INFO_VALID; + cmd->lba_off = ptr->info_lba_off; + cmd->lba_len = ptr->info_lba_len; + cmd->len_off = ptr->info_len_off; + cmd->len_len = ptr->info_len_len; + return (*ptr->get_cdb_info)(cmd, ptr); +} + +static int get_cdb_info_var_len(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + int res; + /* + * !! Indexed by (cdb[8-9] - SUBCODE_READ_32), the smallest subcode, + * !! hence all items in the array MUST be sorted and with NO HOLES + * !! in ops field! + */ + static const struct scst_sdbops scst_scsi_op32_table[] = { + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "READ(32)", + .info_data_direction = SCST_DATA_READ, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED| +#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ + SCST_TEST_IO_IN_SIRQ_ALLOWED| +#endif + SCST_WRITE_EXCL_ALLOWED, + .info_lba_off = 12, .info_lba_len = 8, + .info_len_off = 28, .info_len_len = 4, + .get_cdb_info = get_cdb_info_lba_8_len_4_rdprotect32}, + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "VERIFY(32)", + .info_data_direction = SCST_DATA_UNKNOWN, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_EXCL_ALLOWED, + .info_lba_off = 12, .info_lba_len = 8, + .info_len_off = 28, .info_len_len = 4, + .get_cdb_info = get_cdb_info_verify32}, + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "WRITE(32)", + .info_data_direction = SCST_DATA_WRITE, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED| +#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ + SCST_TEST_IO_IN_SIRQ_ALLOWED| +#endif + SCST_WRITE_MEDIUM, + .info_lba_off = 12, .info_lba_len = 8, + .info_len_off = 28, .info_len_len = 4, + .get_cdb_info = get_cdb_info_lba_8_len_4_wrprotect32}, + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "WRITE AND VERIFY(32)", + .info_data_direction = SCST_DATA_WRITE, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + .info_lba_off = 12, .info_lba_len = 8, + .info_len_off = 28, .info_len_len = 4, + .get_cdb_info = get_cdb_info_lba_8_len_4_wrprotect32}, + {.ops = 0x7F, .devkey = "O ", + .info_op_name = "WRITE SAME(32)", + .info_data_direction = SCST_DATA_WRITE, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + .info_lba_off = 12, .info_lba_len = 8, + .info_len_off = 28, .info_len_len = 4, + .get_cdb_info = get_cdb_info_write_same32}, + }; + const struct scst_sdbops *ptr; + int subcode = be16_to_cpu(*(__be16 *)&cmd->cdb[8]); + unsigned int i = subcode - SUBCODE_READ_32; + + EXTRACHECKS_BUG_ON(cmd->cdb[0] != 0x7F); + EXTRACHECKS_BUG_ON(sdbops->ops != 0x7F); + + /* i is unsigned, so this will handle subcodes < READ_32 as well */ + if (unlikely(i >= ARRAY_SIZE(scst_scsi_op32_table))) { + TRACE(TRACE_MINOR, "Too big cmd index %d for 0x7F CDB (cmd %p)", + i, cmd); + goto out_unknown; + } + + ptr = &scst_scsi_op32_table[i]; +#if 0 /* not possible */ + if (unlikely(ptr == NULL)) + goto out_unknown; +#endif + + if (unlikely(cmd->cdb[7] != 0x18)) { + TRACE(TRACE_MINOR, "Incorrect ADDITIONAL CDB LENGTH %d for " + "0x7F CDB (cmd %p)", cmd->cdb[7], cmd); + cmd->op_flags |= SCST_LBA_NOT_VALID; + scst_set_invalid_field_in_cdb(cmd, 7, 0); + res = 1; /* command invalid */ + goto out; + } + + res = scst_set_cmd_from_cdb_info(cmd, ptr); + + cmd->cmd_naca = (cmd->cdb[1] & CONTROL_BYTE_NACA_BIT); + cmd->cmd_linked = (cmd->cdb[1] & CONTROL_BYTE_LINK_BIT); + +out: + TRACE_EXIT_RES(res); + return res; + +out_unknown: + TRACE(TRACE_MINOR, "Unknown opcode 0x%x, subcode %d for type %d", + cmd->cdb[0], subcode, cmd->dev->type); + cmd->op_flags &= ~SCST_INFO_VALID; + res = -1; /* command unknown */ + goto out; } static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, @@ -7681,7 +10289,7 @@ static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, cmd->lba = get_unaligned_be64(cmd->cdb + sdbops->info_lba_off); cmd->data_len = cmd->cdb[sdbops->info_len_off]; cmd->bufflen = 2 * cmd->data_len; - return 0; + return scst_parse_wrprotect(cmd); } /** @@ -7870,17 +10478,7 @@ int scst_get_cdb_info(struct scst_cmd *cmd) goto out; } - cmd->cdb_len = SCST_GET_CDB_LEN(op); - cmd->cmd_naca = (cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_NACA_BIT); - cmd->cmd_linked = (cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_LINK_BIT); - cmd->op_name = ptr->info_op_name; - cmd->data_direction = ptr->info_data_direction; - cmd->op_flags = ptr->info_op_flags | SCST_INFO_VALID; - cmd->lba_off = ptr->info_lba_off; - cmd->lba_len = ptr->info_lba_len; - cmd->len_off = ptr->info_len_off; - cmd->len_len = ptr->info_len_len; - res = (*ptr->get_cdb_info)(cmd, ptr); + res = scst_set_cmd_from_cdb_info(cmd, ptr); out: TRACE_EXIT_RES(res); @@ -9402,6 +12000,7 @@ int scst_obtain_device_parameters(struct scst_device *dev, dev->tst = buffer[4+2] >> 5; dev->tmf_only = (buffer[4+2] & 0x10) >> 4; + dev->dpicz = (buffer[4+2] & 0x8) >> 3; q = buffer[4+3] >> 4; if (q > SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER) { PRINT_ERROR("Too big QUEUE ALG %x, dev %s, " @@ -9424,11 +12023,11 @@ int scst_obtain_device_parameters(struct scst_device *dev, dev->has_own_order_mgmt = !dev->queue_alg; PRINT_INFO("Device %s: TST %x, TMF_ONLY %x, QUEUE ALG %x, " - "QErr %x, SWP %x, TAS %x, D_SENSE %d, " + "QErr %x, SWP %x, TAS %x, D_SENSE %d, DPICZ %d, " "has_own_order_mgmt %d", dev->virt_name, dev->tst, dev->tmf_only, dev->queue_alg, dev->qerr, dev->swp, dev->tas, dev->d_sense, - dev->has_own_order_mgmt); + dev->dpicz, dev->has_own_order_mgmt); goto out; } else { @@ -9487,10 +12086,10 @@ int scst_obtain_device_parameters(struct scst_device *dev, brk: PRINT_WARNING("Unable to get device's %s control mode page, using " "existing values/defaults: TST %x, TMF_ONLY %x, QUEUE ALG %x, " - "QErr %x, SWP %x, TAS %x, D_SENSE %d, has_own_order_mgmt %d", - dev->virt_name, dev->tst, dev->tmf_only, dev->queue_alg, - dev->qerr, dev->swp, dev->tas, dev->d_sense, - dev->has_own_order_mgmt); + "QErr %x, SWP %x, TAS %x, D_SENSE %d, DPICZ %d, " + "has_own_order_mgmt %d", dev->virt_name, dev->tst, + dev->tmf_only, dev->queue_alg, dev->qerr, dev->swp, dev->tas, + dev->d_sense, dev->dpicz, dev->has_own_order_mgmt); out: TRACE_EXIT(); @@ -10084,6 +12683,7 @@ static void scst_free_descriptors(struct scst_cmd *cmd) #define SCST_SWP_LABEL "SWP" #define SCST_DSENSE_LABEL "D_SENSE" #define SCST_QUEUE_ALG_LABEL "QUEUE_ALG" +#define SCST_DPICZ_LABEL "DPICZ" int scst_save_global_mode_pages(const struct scst_device *dev, uint8_t *buf, int size) @@ -10127,6 +12727,13 @@ int scst_save_global_mode_pages(const struct scst_device *dev, goto out_overflow; } + if (dev->dpicz != dev->dpicz_default) { + res += scnprintf(&buf[res], size - res, "%s=%d\n", + SCST_DPICZ_LABEL, dev->dpicz); + if (res >= size-1) + goto out_overflow; + } + if (dev->queue_alg != dev->queue_alg_default) { res += scnprintf(&buf[res], size - res, "%s=%d\n", SCST_QUEUE_ALG_LABEL, dev->queue_alg); @@ -10276,6 +12883,32 @@ out: return res; } +static int scst_restore_dpicz(struct scst_device *dev, unsigned int val) +{ + int res; + + TRACE_ENTRY(); + + if (val > 1) { + PRINT_ERROR("Invalid value %d for parameter %s (device %s)", + val, SCST_DPICZ_LABEL, dev->virt_name); + res = -EINVAL; + goto out; + } + + dev->dpicz = val; + dev->dpicz_saved = val; + + PRINT_INFO("%s restored to %d for device %s", SCST_DPICZ_LABEL, + dev->dpicz, dev->virt_name); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + static int scst_restore_queue_alg(struct scst_device *dev, unsigned int val) { int res; @@ -10347,6 +12980,8 @@ int scst_restore_global_mode_pages(struct scst_device *dev, char *params, res = scst_restore_swp(dev, val); else if (strcasecmp(SCST_DSENSE_LABEL, p) == 0) res = scst_restore_dsense(dev, val); + else if (strcasecmp(SCST_DPICZ_LABEL, p) == 0) + res = scst_restore_dpicz(dev, val); else if (strcasecmp(SCST_QUEUE_ALG_LABEL, p) == 0) res = scst_restore_queue_alg(dev, val); else { diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 5f016fb08..a711ddb93 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -520,6 +520,11 @@ static inline void scst_devt_dev_sysfs_del(struct scst_device *dev) { } static inline void scst_dev_sysfs_del(struct scst_device *dev) { } +static inline int scst_dev_sysfs_dif_create(struct scst_device *dev) +{ + return 0; +} + static inline int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev) { return 0; @@ -568,6 +573,7 @@ int scst_devt_sysfs_create(struct scst_dev_type *devt); void scst_devt_sysfs_del(struct scst_dev_type *devt); int scst_dev_sysfs_create(struct scst_device *dev); void scst_dev_sysfs_del(struct scst_device *dev); +int scst_dev_sysfs_dif_create(struct scst_device *dev); int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev); void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev); int scst_devt_dev_sysfs_create(struct scst_device *dev); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 7ac882963..2619f711f 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -1033,6 +1033,63 @@ static struct kobj_attribute scst_tgtt_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tgtt_mgmt_show, scst_tgtt_mgmt_store); +static ssize_t scst_tgtt_dif_capable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_tgt_template *tgtt; + + TRACE_ENTRY(); + + tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); + + EXTRACHECKS_BUG_ON(!tgtt->dif_supported); + + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "dif_supported"); + + if (tgtt->hw_dif_type1_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type1_supported"); + + if (tgtt->hw_dif_type2_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type2_supported"); + + if (tgtt->hw_dif_type3_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type3_supported"); + + if (tgtt->hw_dif_ip_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_ip_supported"); + + if (tgtt->hw_dif_same_sg_layout_required) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_same_sg_layout_required"); + + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "\n"); + + 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; + while (*p != 0) { + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%d", (j == pos) ? "" : ", ", *p); + p++; + } + } + + TRACE_EXIT_RES(pos); + return pos; +} + +static struct kobj_attribute scst_tgtt_dif_capable_attr = + __ATTR(dif_capabilities, S_IRUGO, scst_tgtt_dif_capable_show, NULL); + /* * Creates an attribute entry for target driver. */ @@ -1085,6 +1142,16 @@ int scst_tgtt_sysfs_create(struct scst_tgt_template *tgtt) } } + if (tgtt->dif_supported) { + res = sysfs_create_file(&tgtt->tgtt_kobj, + &scst_tgtt_dif_capable_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't add attribute %s for target driver %s", + scst_tgtt_dif_capable_attr.attr.name, tgtt->name); + goto out; + } + } + #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) if (tgtt->trace_flags != NULL) { res = sysfs_create_file(&tgtt->tgtt_kobj, @@ -2524,6 +2591,114 @@ out: } EXPORT_SYMBOL(scst_create_tgt_attr); +static ssize_t scst_tgt_dif_capable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_tgt *tgt; + + TRACE_ENTRY(); + + tgt = container_of(kobj, struct scst_tgt, tgt_kobj); + + EXTRACHECKS_BUG_ON(!tgt->tgt_dif_supported); + + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "dif_supported"); + + if (tgt->tgt_hw_dif_type1_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type1_supported"); + + if (tgt->tgt_hw_dif_type2_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type2_supported"); + + if (tgt->tgt_hw_dif_type3_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_type3_supported"); + + if (tgt->tgt_hw_dif_ip_supported) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_ip_supported"); + + if (tgt->tgt_hw_dif_same_sg_layout_required) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + ", hw_dif_same_sg_layout_required"); + + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "\n"); + + 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; + while (*p != 0) { + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%d", (j == pos) ? "" : ", ", *p); + p++; + } + } + + TRACE_EXIT_RES(pos); + return pos; +} + +static struct kobj_attribute scst_tgt_dif_capable_attr = + __ATTR(dif_capabilities, S_IRUGO, scst_tgt_dif_capable_show, NULL); + +static ssize_t scst_tgt_dif_checks_failed_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_tgt *tgt; + + tgt = container_of(kobj, struct scst_tgt, tgt_kobj); + + pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "\tapp\tref\tguard\n" + "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", + atomic_read(&tgt->tgt_dif_app_failed_tgt), + atomic_read(&tgt->tgt_dif_ref_failed_tgt), + atomic_read(&tgt->tgt_dif_guard_failed_tgt), + atomic_read(&tgt->tgt_dif_app_failed_scst), + atomic_read(&tgt->tgt_dif_ref_failed_scst), + atomic_read(&tgt->tgt_dif_guard_failed_scst), + atomic_read(&tgt->tgt_dif_app_failed_dev), + atomic_read(&tgt->tgt_dif_ref_failed_dev), + atomic_read(&tgt->tgt_dif_guard_failed_dev)); + + return pos; +} + +static ssize_t scst_tgt_dif_checks_failed_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct scst_tgt *tgt; + + tgt = container_of(kobj, struct scst_tgt, tgt_kobj); + + PRINT_INFO("Zeroing DIF failures statistics for target %s", + tgt->tgt_name); + + atomic_set(&tgt->tgt_dif_app_failed_tgt, 0); + atomic_set(&tgt->tgt_dif_ref_failed_tgt, 0); + atomic_set(&tgt->tgt_dif_guard_failed_tgt, 0); + atomic_set(&tgt->tgt_dif_app_failed_scst, 0); + atomic_set(&tgt->tgt_dif_ref_failed_scst, 0); + atomic_set(&tgt->tgt_dif_guard_failed_scst, 0); + atomic_set(&tgt->tgt_dif_app_failed_dev, 0); + atomic_set(&tgt->tgt_dif_ref_failed_dev, 0); + atomic_set(&tgt->tgt_dif_guard_failed_dev, 0); + + return count; +} + +static struct kobj_attribute scst_tgt_dif_checks_failed_attr = + __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, + scst_tgt_dif_checks_failed_show, + scst_tgt_dif_checks_failed_store); + #define SCST_TGT_SYSFS_STAT_ATTR(member_name, attr, dir, result_op) \ static int scst_tgt_sysfs_##attr##_show_work_fn( \ struct scst_sysfs_work_item *work) \ @@ -2679,6 +2854,26 @@ int scst_tgt_sysfs_create(struct scst_tgt *tgt) goto out_err; } + if (tgt->tgt_dif_supported) { + res = sysfs_create_file(&tgt->tgt_kobj, + &scst_tgt_dif_capable_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't add attribute %s for tgt %s", + scst_tgt_dif_capable_attr.attr.name, + tgt->tgt_name); + goto out_err; + } + + res = sysfs_create_file(&tgt->tgt_kobj, + &scst_tgt_dif_checks_failed_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't add attribute %s for tgt %s", + scst_tgt_dif_checks_failed_attr.attr.name, + tgt->tgt_name); + goto out_err; + } + } + if (tgt->tgtt->tgt_attrs) { res = sysfs_create_files(&tgt->tgt_kobj, tgt->tgtt->tgt_attrs); if (res != 0) { @@ -3397,6 +3592,164 @@ void scst_dev_sysfs_del(struct scst_device *dev) return; } +static ssize_t scst_dev_dif_mode_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); + + if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) + pos = sprintf(buf, "None\n"); + else { + int j = pos; + + if (dev->dev_dif_mode & SCST_DIF_MODE_TGT) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_TGT_STR); + + if (dev->dev_dif_mode & SCST_DIF_MODE_SCST) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_SCST_STR); + + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_DEV_CHECK_STR); + + if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_DEV_STORE_STR); + + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, + "\n%s", SCST_SYSFS_KEY_MARK "\n"); + } + + TRACE_EXIT_RES(pos); + return pos; +} + +static struct kobj_attribute scst_dev_dif_mode_attr = + __ATTR(dif_mode, S_IRUGO, scst_dev_dif_mode_show, NULL); + +static ssize_t scst_dev_dif_type_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\n%s", dev->dev_dif_type, + (dev->dev_dif_type != 0) ? SCST_SYSFS_KEY_MARK "\n" : ""); + + TRACE_EXIT_RES(pos); + return pos; +} + +static struct kobj_attribute scst_dev_dif_type_attr = + __ATTR(dif_type, S_IRUGO, scst_dev_dif_type_show, NULL); + +static ssize_t scst_dev_sysfs_dif_static_app_tag_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res; + struct scst_device *dev; + unsigned long long val; + + TRACE_ENTRY(); + + 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); + goto out; + } + + scst_dev_set_dif_static_app_tag_combined(dev, cpu_to_be64(val)); + + res = count; + + PRINT_INFO("APP TAG for device %s changed to %llx", dev->virt_name, + (long long)be64_to_cpu(scst_dev_get_dif_static_app_tag_combined(dev))); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_dev_sysfs_dif_static_app_tag_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_device *dev; + __be64 a; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + + a = scst_dev_get_dif_static_app_tag_combined(dev); + + pos = sprintf(buf, "0x%llx\n%s", (unsigned long long)be64_to_cpu(a), + (a != SCST_DIF_NO_CHECK_APP_TAG) ? SCST_SYSFS_KEY_MARK "\n" : ""); + + TRACE_EXIT_RES(pos); + return pos; +} + +static struct kobj_attribute scst_dev_dif_static_app_tag_attr = + __ATTR(dif_static_app_tag, S_IWUSR|S_IRUGO, + scst_dev_sysfs_dif_static_app_tag_show, + scst_dev_sysfs_dif_static_app_tag_store); + +int scst_dev_sysfs_dif_create(struct scst_device *dev) +{ + int res; + + TRACE_ENTRY(); + + /* + * On errors the caller supposed to unregister this device, hence, + * perform the cleanup. + */ + + res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_mode_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't create attr %s for dev %s", + scst_dev_dif_mode_attr.attr.name, dev->virt_name); + goto out; + } + + res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_type_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't create attr %s for dev %s", + scst_dev_dif_type_attr.attr.name, dev->virt_name); + goto out; + } + + res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_static_app_tag_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't create attr %s for dev %s", + scst_dev_dif_static_app_tag_attr.attr.name, dev->virt_name); + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + /** ** Tgt_dev implementation **/ @@ -3554,6 +3907,58 @@ static struct kobj_attribute tgt_dev_active_commands_attr = __ATTR(active_commands, S_IRUGO, scst_tgt_dev_active_commands_show, NULL); +static ssize_t scst_tgt_dev_dif_checks_failed_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_tgt_dev *tgt_dev; + + tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); + + pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "\tapp\tref\tguard\n" + "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", + atomic_read(&tgt_dev->tgt_dev_dif_app_failed_tgt), + atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_tgt), + atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_tgt), + atomic_read(&tgt_dev->tgt_dev_dif_app_failed_scst), + atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_scst), + atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_scst), + atomic_read(&tgt_dev->tgt_dev_dif_app_failed_dev), + atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_dev), + atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_dev)); + + return pos; +} + +static ssize_t scst_tgt_dev_dif_checks_failed_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct scst_tgt_dev *tgt_dev; + + tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); + + PRINT_INFO("Zeroing DIF failures statistics for initiator " + "%s, target %s, LUN %lld", tgt_dev->sess->initiator_name, + tgt_dev->sess->tgt->tgt_name, (unsigned long long)tgt_dev->lun); + + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_dev, 0); + + return count; +} + +static struct kobj_attribute tgt_dev_dif_checks_failed_attr = + __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, + scst_tgt_dev_dif_checks_failed_show, + scst_tgt_dev_dif_checks_failed_store); + static struct attribute *scst_tgt_dev_attrs[] = { &tgt_dev_thread_pid_attr.attr, &tgt_dev_active_commands_attr.attr, @@ -3598,9 +4003,25 @@ int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev) goto out; } + if (tgt_dev->sess->tgt->tgt_dif_supported && (tgt_dev->dev->dev_dif_type != 0)) { + res = sysfs_create_file(&tgt_dev->tgt_dev_kobj, + &tgt_dev_dif_checks_failed_attr.attr); + if (res != 0) { + PRINT_ERROR("Adding %s sysfs attribute to tgt_dev %lld " + "failed (%d)", tgt_dev_dif_checks_failed_attr.attr.name, + (unsigned long long)tgt_dev->lun, res); + goto out_del; + } + } + out: TRACE_EXIT_RES(res); return res; + +out_del: + kobject_del(&tgt_dev->tgt_dev_kobj); + kobject_put(&tgt_dev->tgt_dev_kobj); + goto out; } /* @@ -3944,6 +4365,161 @@ static struct kobj_attribute session_active_commands_attr = __ATTR(active_commands, S_IRUGO, scst_sess_sysfs_active_commands_show, NULL); +static int scst_sysfs_sess_get_dif_checks_failed_work_fn(struct scst_sysfs_work_item *work) +{ + int res, t; + struct scst_session *sess = work->sess; + int app_failed_tgt = 0, ref_failed_tgt = 0, guard_failed_tgt = 0; + int app_failed_scst = 0, ref_failed_scst = 0, guard_failed_scst = 0; + int app_failed_dev = 0, ref_failed_dev = 0, guard_failed_dev = 0; + + TRACE_ENTRY(); + + res = mutex_lock_interruptible(&scst_mutex); + if (res != 0) + goto out_put; + + 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); + guard_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_tgt); + app_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_scst); + ref_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_scst); + guard_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_scst); + app_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_dev); + ref_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_dev); + guard_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_dev); + } + } + + mutex_unlock(&scst_mutex); + + work->res_buf = kasprintf(GFP_KERNEL, "\tapp\tref\tguard\n" + "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", + app_failed_tgt, ref_failed_tgt, guard_failed_tgt, + app_failed_scst, ref_failed_scst, guard_failed_scst, + app_failed_dev, ref_failed_dev, guard_failed_dev); + res = work->res_buf ? 0 : -ENOMEM; + +out_put: + kobject_put(&sess->sess_kobj); + + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_sess_sysfs_dif_checks_failed_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int res; + struct scst_session *sess; + struct scst_sysfs_work_item *work; + + sess = container_of(kobj, struct scst_session, sess_kobj); + + res = scst_alloc_sysfs_work(scst_sysfs_sess_get_dif_checks_failed_work_fn, + true, &work); + if (res != 0) + goto out; + + work->sess = sess; + + SCST_SET_DEP_MAP(work, &scst_sess_dep_map); + kobject_get(&sess->sess_kobj); + + scst_sysfs_work_get(work); + + res = scst_sysfs_queue_wait_work(work); + if (res != 0) + goto out_put; + + res = snprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s", work->res_buf); + +out_put: + scst_sysfs_work_put(work); + +out: + return res; +} + +static int scst_sess_zero_dif_checks_failed(struct scst_sysfs_work_item *work) +{ + int res, t; + struct scst_session *sess = work->sess; + + TRACE_ENTRY(); + + PRINT_INFO("Zeroing DIF failures statistics for initiator " + "%s, target %s", sess->initiator_name, sess->tgt->tgt_name); + + res = mutex_lock_interruptible(&scst_mutex); + if (res != 0) + goto out_put; + + 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); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_tgt, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_scst, 0); + atomic_set(&tgt_dev->tgt_dev_dif_app_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_dev, 0); + atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_dev, 0); + } + } + + mutex_unlock(&scst_mutex); + + res = 0; + +out_put: + kobject_put(&sess->sess_kobj); + + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_sess_sysfs_dif_checks_failed_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res; + struct scst_session *sess; + struct scst_sysfs_work_item *work; + + TRACE_ENTRY(); + + sess = container_of(kobj, struct scst_session, sess_kobj); + + res = scst_alloc_sysfs_work(scst_sess_zero_dif_checks_failed, false, &work); + if (res != 0) + goto out; + + work->sess = sess; + + SCST_SET_DEP_MAP(work, &scst_sess_dep_map); + kobject_get(&sess->sess_kobj); + + res = scst_sysfs_queue_wait_work(work); + if (res == 0) + res = count; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute session_dif_checks_failed_attr = + __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, + scst_sess_sysfs_dif_checks_failed_show, + scst_sess_sysfs_dif_checks_failed_store); + static ssize_t scst_sess_sysfs_initiator_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -4129,6 +4705,17 @@ int scst_sess_sysfs_create(struct scst_session *sess) } } + if (sess->tgt->tgt_dif_supported) { + res = sysfs_create_file(&sess->sess_kobj, + &session_dif_checks_failed_attr.attr); + if (res != 0) { + PRINT_ERROR("Adding %s sysfs attribute to session %s " + "failed (%d)", session_dif_checks_failed_attr.attr.name, + name, res); + goto out_del; + } + } + if (sess->tgt->tgtt->sess_attrs) { res = sysfs_create_files(&sess->sess_kobj, sess->tgt->tgtt->sess_attrs); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 2bb613b5c..90a85a6d3 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -578,12 +578,14 @@ int scst_pre_parse(struct scst_cmd *cmd) TRACE_DBG("op_name <%s> (cmd %p), direction=%d " "(expected %d, set %s), lba %lld, bufflen=%d, data_len %lld, " - "out_bufflen=%d (expected len %d, out expected len %d), " - "flags=0x%x, naca %d", cmd->op_name, cmd, cmd->data_direction, + "out_bufflen=%d (expected len data %d, expected len DIF %d, " + "out expected len %d), flags=0x%x, , naca %d", + cmd->op_name, cmd, cmd->data_direction, cmd->expected_data_direction, scst_cmd_is_expected_set(cmd) ? "yes" : "no", (long long)cmd->lba, cmd->bufflen, (long long)cmd->data_len, - cmd->out_bufflen, cmd->expected_transfer_len, + cmd->out_bufflen, scst_cmd_get_expected_transfer_len_data(cmd), + scst_cmd_get_expected_transfer_len_dif(cmd), cmd->expected_out_transfer_len, cmd->op_flags, cmd->cmd_naca); res = 0; @@ -617,6 +619,16 @@ static bool scst_is_allowed_to_mismatch_cmd(struct scst_cmd *cmd) } #endif +static bool scst_bufflen_eq_expecten_len(struct scst_cmd *cmd) +{ + int b = cmd->bufflen; + + if (cmd->tgt_dif_data_expected) + b += (b >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; + + return b == cmd->expected_transfer_len_full; +} + static int scst_parse_cmd(struct scst_cmd *cmd) { int res = SCST_CMD_STATE_RES_CONT_SAME; @@ -679,12 +691,12 @@ static int scst_parse_cmd(struct scst_cmd *cmd) #ifdef CONFIG_SCST_USE_EXPECTED_VALUES if (scst_cmd_is_expected_set(cmd)) { TRACE(TRACE_MINOR, "Using initiator supplied values: " - "direction %d, transfer_len %d/%d", - cmd->expected_data_direction, - cmd->expected_transfer_len, + "direction %d, transfer_len %d/%d/%d", + scst_cmd_get_expected_transfer_len_data(cmd), + scst_cmd_get_expected_transfer_len_dif(cmd), cmd->expected_out_transfer_len); cmd->data_direction = cmd->expected_data_direction; - cmd->bufflen = cmd->expected_transfer_len; + cmd->bufflen = scst_cmd_get_expected_transfer_len_data(cmd); cmd->data_len = cmd->bufflen; cmd->out_bufflen = cmd->expected_out_transfer_len; } else { @@ -722,7 +734,7 @@ static int scst_parse_cmd(struct scst_cmd *cmd) * expected value, but limit it to some * reasonable value (15MB). */ - cmd->bufflen = min(cmd->expected_transfer_len, + cmd->bufflen = min(scst_cmd_get_expected_transfer_len_data(cmd), 15*1024*1024); cmd->data_len = cmd->bufflen; if (cmd->data_direction == SCST_DATA_BIDI) @@ -785,21 +797,22 @@ static int scst_parse_cmd(struct scst_cmd *cmd) if (scst_cmd_is_expected_set(cmd)) { #ifdef CONFIG_SCST_USE_EXPECTED_VALUES if (unlikely((cmd->data_direction != cmd->expected_data_direction) || - (cmd->bufflen != cmd->expected_transfer_len) || + !scst_bufflen_eq_expecten_len(cmd) || (cmd->out_bufflen != cmd->expected_out_transfer_len))) { TRACE(TRACE_MINOR, "Expected values don't match " "decoded ones: data_direction %d, " "expected_data_direction %d, " - "bufflen %d, expected_transfer_len %d, " - "out_bufflen %d, expected_out_transfer_len %d", + "bufflen %d, expected len data %d, expected len " + "DIF %d, out_bufflen %d, expected_out_transfer_len %d", cmd->data_direction, cmd->expected_data_direction, - cmd->bufflen, cmd->expected_transfer_len, + cmd->bufflen, scst_cmd_get_expected_transfer_len_data(cmd), + scst_cmd_get_expected_transfer_len_dif(cmd), cmd->out_bufflen, cmd->expected_out_transfer_len); PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB", cmd->cdb, cmd->cdb_len); cmd->data_direction = cmd->expected_data_direction; - cmd->bufflen = cmd->expected_transfer_len; + cmd->bufflen = scst_cmd_get_expected_transfer_len_data(cmd); cmd->data_len = cmd->bufflen; cmd->out_bufflen = cmd->expected_out_transfer_len; cmd->resid_possible = 1; @@ -823,13 +836,15 @@ static int scst_parse_cmd(struct scst_cmd *cmd) goto out_done; } } - if (unlikely(cmd->bufflen != cmd->expected_transfer_len)) { + if (unlikely(!scst_bufflen_eq_expecten_len(cmd))) { TRACE(TRACE_MINOR, "Warning: expected " - "transfer length %d for opcode %s " + "transfer length %d (DIF %d) for opcode %s " "(handler %s, target %s) doesn't match " "decoded value %d", - cmd->expected_transfer_len, scst_get_opcode_name(cmd), - devt->name, cmd->tgtt->name, cmd->bufflen); + scst_cmd_get_expected_transfer_len_data(cmd), + scst_cmd_get_expected_transfer_len_dif(cmd), + scst_get_opcode_name(cmd), devt->name, + cmd->tgtt->name, cmd->bufflen); PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB", cmd->cdb, cmd->cdb_len); if ((cmd->expected_data_direction & SCST_DATA_READ) || @@ -878,14 +893,16 @@ set_res: TRACE(TRACE_SCSI, "op_name <%s> (cmd %p), direction=%d " "(expected %d, set %s), lba=%lld, bufflen=%d, data len %lld, " - "out_bufflen=%d, (expected len %d, out expected len %d), " - "flags=0x%x, internal %d, naca %d", cmd->op_name, cmd, - cmd->data_direction, cmd->expected_data_direction, + "out_bufflen=%d, (expected len data %d, expected len DIF %d, " + "out expected len %d), flags=0x%x, internal %d, naca %d", + cmd->op_name, cmd, cmd->data_direction, cmd->expected_data_direction, scst_cmd_is_expected_set(cmd) ? "yes" : "no", (unsigned long long)cmd->lba, cmd->bufflen, (long long)cmd->data_len, cmd->out_bufflen, - cmd->expected_transfer_len, cmd->expected_out_transfer_len, - cmd->op_flags, cmd->internal, cmd->cmd_naca); + scst_cmd_get_expected_transfer_len_data(cmd), + scst_cmd_get_expected_transfer_len_dif(cmd), + cmd->expected_out_transfer_len, cmd->op_flags, cmd->internal, + cmd->cmd_naca); #ifdef CONFIG_SCST_EXTRACHECKS switch (state) { @@ -1045,7 +1062,7 @@ static void scst_set_write_len(struct scst_cmd *cmd) goto out; } else { cmd->write_len = min(cmd->bufflen, - cmd->expected_transfer_len); + scst_cmd_get_expected_transfer_len_data(cmd)); if (cmd->write_len == cmd->bufflen) goto out; } @@ -1163,13 +1180,18 @@ alloc: TRACE_MEM("tgt_i_data_buf_alloced set (cmd %p)", cmd); cmd->sg = cmd->tgt_i_sg; cmd->sg_cnt = cmd->tgt_i_sg_cnt; + cmd->dif_sg = cmd->tgt_i_dif_sg; + cmd->dif_sg_cnt = cmd->tgt_i_dif_sg_cnt; cmd->out_sg = cmd->tgt_out_sg; cmd->out_sg_cnt = cmd->tgt_out_sg_cnt; r = 0; } else { TRACE_MEM("Both *_data_buf_alloced set (cmd %p, sg %p, " - "sg_cnt %d, tgt_i_sg %p, tgt_i_sg_cnt %d)", cmd, cmd->sg, - cmd->sg_cnt, cmd->tgt_i_sg, cmd->tgt_i_sg_cnt); + "sg_cnt %d, dif_sg %p, dif_sg_cnt %d, tgt_i_sg %p, " + "tgt_i_sg_cnt %d, tgt_i_dif_sg %p, tgt_i_dif_sg_cnt %d)", + cmd, cmd->sg, cmd->sg_cnt, cmd->dif_sg, cmd->dif_sg_cnt, + cmd->tgt_i_sg, cmd->tgt_i_sg_cnt, cmd->tgt_i_dif_sg, + cmd->tgt_i_dif_sg_cnt); r = 0; } @@ -3537,6 +3559,7 @@ static int scst_pre_dev_done(struct scst_cmd *cmd) TRACE_ENTRY(); +again: rc = scst_check_auto_sense(cmd); if (unlikely(rc)) { if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) @@ -3567,16 +3590,22 @@ next: goto out; } - rc = scsi_status_is_good(cmd->status); - if (likely(rc)) { - unsigned char type = cmd->dev->type; + if (likely(cmd->status == SAM_STAT_GOOD)) { + 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; + } + } + if (unlikely((cmd->cdb[0] == MODE_SENSE || cmd->cdb[0] == MODE_SENSE_10)) && (cmd->tgt_dev->tgt_dev_rd_only || cmd->dev->swp) && - (type == TYPE_DISK || - type == TYPE_WORM || - type == TYPE_MOD || - type == TYPE_TAPE)) { + (cmd->dev->type == TYPE_DISK || + cmd->dev->type == TYPE_WORM || + cmd->dev->type == TYPE_MOD || + cmd->dev->type == TYPE_TAPE)) { int32_t length; uint8_t *address; bool err = false; diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 27c7fdd83..30141669c 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -3433,7 +3433,7 @@ static int srpt_xfer_data(struct srpt_rdma_ch *ch, len = scst_get_buf_next(cmd, &buf); } WARN_ON_ONCE(offset != - scst_cmd_get_expected_transfer_len(cmd)); + scst_cmd_get_expected_transfer_len_full(cmd)); } scst_rx_data(cmd, SCST_RX_STATUS_SUCCESS, in_irq() ? SCST_CONTEXT_TASKLET :