aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndy Grover <agrover@redhat.com>2015-05-07 11:40:20 -0700
committerAndy Grover <agrover@redhat.com>2015-05-07 11:40:20 -0700
commit01616b20311a9826ae1cb1806f93f1574a0fbfbd (patch)
tree05333db2600f0c6aa1c081155c7f5c30e29992ad
parent0d34082b2f11a94f3f372e1490d7a6f179b5a820 (diff)
downloadlinux-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.c4
-rw-r--r--drivers/target/target_core_transport.c1
-rw-r--r--drivers/target/target_core_user.c99
-rw-r--r--include/target/target_core_backend.h1
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);