diff options
author | Andy Grover <agrover@redhat.com> | 2015-05-07 11:40:20 -0700 |
---|---|---|
committer | Andy Grover <agrover@redhat.com> | 2015-05-07 11:40:20 -0700 |
commit | 01616b20311a9826ae1cb1806f93f1574a0fbfbd (patch) | |
tree | 05333db2600f0c6aa1c081155c7f5c30e29992ad | |
parent | 0d34082b2f11a94f3f372e1490d7a6f179b5a820 (diff) | |
download | linux-tcmu-iomode-option1.tar.gz |
target/user: Don't call sbc_parse_cdb for BIDI and COMPARE_AND_WRITEtcmu-iomode-option1
sbc_parse_cdb layers on special behavior for these that we don't want,
so don't call it but do what it would have done.
Signed-off-by: Andy Grover <agrover@redhat.com>
-rw-r--r-- | drivers/target/target_core_sbc.c | 4 | ||||
-rw-r--r-- | drivers/target/target_core_transport.c | 1 | ||||
-rw-r--r-- | drivers/target/target_core_user.c | 99 | ||||
-rw-r--r-- | include/target/target_core_backend.h | 1 |
4 files changed, 102 insertions, 3 deletions
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c index 8855781ac6530..8862bf90f16fd 100644 --- a/drivers/target/target_core_sbc.c +++ b/drivers/target/target_core_sbc.c @@ -734,8 +734,7 @@ sbc_check_prot(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb, return TCM_NO_SENSE; } -static int -sbc_check_dpofua(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb) +int sbc_check_dpofua(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb) { if (cdb[1] & 0x10) { if (!dev->dev_attrib.emulate_dpo) { @@ -755,6 +754,7 @@ sbc_check_dpofua(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb) } return 0; } +EXPORT_SYMBOL(sbc_check_dpofua); sense_reason_t sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops) diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 3fe5cb240b6f6..39c7ce8791b58 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -1151,6 +1151,7 @@ target_cmd_size_check(struct se_cmd *cmd, unsigned int size) return 0; } +EXPORT_SYMBOL(target_cmd_size_check); /* * Used by fabric modules containing a local struct se_cmd within their diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c index 0e0feeaec39c2..0aa84ee18d89d 100644 --- a/drivers/target/target_core_user.c +++ b/drivers/target/target_core_user.c @@ -21,12 +21,14 @@ #include <linux/idr.h> #include <linux/timer.h> #include <linux/parser.h> +#include <asm/unaligned.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> #include <linux/uio_driver.h> #include <net/genetlink.h> #include <target/target_core_base.h> #include <target/target_core_fabric.h> +#include "target_core_internal.h" #include <target/target_core_backend.h> #include <target/target_core_backend_configfs.h> @@ -1048,10 +1050,105 @@ static struct sbc_ops tcmu_sbc_ops = { .execute_unmap = tcmu_pass_op, }; +static inline unsigned long long tcmu_transport_lba_64_ext(unsigned char *cdb) +{ + unsigned int __v1, __v2; + + __v1 = (cdb[12] << 24) | (cdb[13] << 16) | (cdb[14] << 8) | cdb[15]; + __v2 = (cdb[16] << 24) | (cdb[17] << 16) | (cdb[18] << 8) | cdb[19]; + + return ((unsigned long long)__v2) | (unsigned long long)__v1 << 32; +} + static sense_reason_t tcmu_parse_cdb(struct se_cmd *cmd) { - return sbc_parse_cdb(cmd, &tcmu_sbc_ops); + struct se_device *dev = cmd->se_dev; + unsigned char *cdb = cmd->t_task_cdb; + unsigned int size; + u32 sectors = 0; + unsigned long long end_lba; + + /* + * sbc_parse_cdb does the wrong thing for us for BIDI ops and + * COMPARE_AND_WRITE. Avoid calling it for these, but call it + * for the rest. + */ + switch (cdb[0]) { + case XDWRITEREAD_10: + if (cmd->data_direction != DMA_TO_DEVICE || + !(cmd->se_cmd_flags & SCF_BIDI)) + return TCM_INVALID_CDB_FIELD; + sectors = (u32)(cdb[7] << 8) + cdb[8]; + + if (sbc_check_dpofua(dev, cmd, cdb)) + return TCM_INVALID_CDB_FIELD; + + cmd->t_task_lba = get_unaligned_be32(&cdb[2]); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + + cmd->execute_cmd = tcmu_pass_op; + break; + case VARIABLE_LENGTH_CMD: + { + u16 service_action = get_unaligned_be16(&cdb[8]); + if (service_action == XDWRITEREAD_32) { + sectors = get_unaligned_be32(&cdb[28]); + + if (sbc_check_dpofua(dev, cmd, cdb)) + return TCM_INVALID_CDB_FIELD; + /* + * Use WRITE_32 and READ_32 opcodes for the emulated + * XDWRITE_READ_32 logic. + */ + cmd->t_task_lba = tcmu_transport_lba_64_ext(cdb); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + + cmd->execute_cmd = tcmu_pass_op; + } + else + return sbc_parse_cdb(cmd, &tcmu_sbc_ops); + break; + } + case COMPARE_AND_WRITE: + sectors = cdb[13]; + /* + * Currently enforce COMPARE_AND_WRITE for a single sector + */ + if (sectors > 1) { + pr_err("COMPARE_AND_WRITE contains NoLB: %u greater" + " than 1\n", sectors); + return TCM_INVALID_CDB_FIELD; + } + + cmd->t_task_lba = get_unaligned_be64(&cdb[2]); + cmd->t_task_nolb = sectors; + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB | SCF_COMPARE_AND_WRITE; + cmd->execute_cmd = tcmu_pass_op; + break; + default: + return sbc_parse_cdb(cmd, &tcmu_sbc_ops); + } + + end_lba = dev->transport->get_blocks(dev) + 1; + if (((cmd->t_task_lba + sectors) < cmd->t_task_lba) || + ((cmd->t_task_lba + sectors) > end_lba)) { + pr_err("cmd exceeds last lba %llu " + "(lba %llu, sectors %u)\n", + end_lba, cmd->t_task_lba, sectors); + return TCM_ADDRESS_OUT_OF_RANGE; + } + + size = cmd->se_dev->dev_attrib.block_size * sectors; + if (cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) { + /* + * Double size because we have two buffers, note that + * zero is not an error.. + */ + size *= 2; + } + + return target_cmd_size_check(cmd, size); } DEF_TB_DEFAULT_ATTRIBS(tcmu); diff --git a/include/target/target_core_backend.h b/include/target/target_core_backend.h index d61be7297b2c8..9277b803ed19f 100644 --- a/include/target/target_core_backend.h +++ b/include/target/target_core_backend.h @@ -77,6 +77,7 @@ sense_reason_t spc_emulate_report_luns(struct se_cmd *cmd); sense_reason_t spc_emulate_inquiry_std(struct se_cmd *, unsigned char *); sense_reason_t spc_emulate_evpd_83(struct se_cmd *, unsigned char *); +int sbc_check_dpofua(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb); sense_reason_t sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops); u32 sbc_get_device_rev(struct se_device *dev); u32 sbc_get_device_type(struct se_device *dev); |