From f32f05c48357ef1ee483e2d109fb457bf8743036 Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Sat, 15 Mar 2014 02:01:17 +0000 Subject: [PATCH] scst_vdisk: Implement COMPARE AND WRITE Ensure that COMPARE AND WRITE is executed atomically by serializing all COMPARE AND WRITE commands per device (SCST_SERIALIZED). Signed-off-by: Bart Van Assche git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@5334 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- scst/include/scst.h | 2 + scst/include/scst_const.h | 10 ++ scst/src/dev_handlers/scst_vdisk.c | 151 ++++++++++++++++++++++++++++- scst/src/scst_lib.c | 51 ++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) diff --git a/scst/include/scst.h b/scst/include/scst.h index 229720009..734673935 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -3035,6 +3035,8 @@ int scst_get_cdb_info(struct scst_cmd *cmd); int scst_set_cmd_error_status(struct scst_cmd *cmd, int status); int scst_set_cmd_error(struct scst_cmd *cmd, int key, int asc, int ascq); +int scst_set_cmd_error_and_inf(struct scst_cmd *cmd, int key, int asc, + int ascq, uint64_t information); void scst_set_busy(struct scst_cmd *cmd); void scst_check_convert_sense(struct scst_cmd *cmd); diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index d534f2739..a84825b7e 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -421,6 +421,16 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define UNMAP 0x42 #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 12) +/* + * From . See also commit + * 1c68cc1626341665a8bd1d2c7dfffd7fc852a79c. + */ +#ifndef COMPARE_AND_WRITE +#define COMPARE_AND_WRITE 0x89 +#endif +#endif + #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28) /* * From . See also commit diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index b567c41ce..42a0074f3 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -276,6 +276,7 @@ static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_prevent_allow_medium_removal(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_unmap(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p); +static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p); static int vdisk_fsync(loff_t loff, loff_t len, struct scst_device *dev, gfp_t gfp_flags, struct scst_cmd *cmd, bool async); @@ -1406,6 +1407,7 @@ static enum compl_status_e vdisk_invalid_opcode(struct vdisk_cmd_params *p) [UNMAP] = vdisk_exec_unmap, \ [WRITE_SAME] = vdisk_exec_write_same, \ [WRITE_SAME_16] = vdisk_exec_write_same, \ + [COMPARE_AND_WRITE] = vdisk_exec_caw, \ [MAINTENANCE_IN] = vdisk_exec_maintenance_in, \ [SEND_DIAGNOSTIC] = vdisk_exec_send_diagnostic, \ [FORMAT_UNIT] = vdisk_exec_format_unit, @@ -1543,6 +1545,7 @@ static bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) case WRITE_10: case WRITE_12: case WRITE_16: + case COMPARE_AND_WRITE: fua = (cdb[1] & 0x8); if (fua) { TRACE(TRACE_ORDER, "FUA: loff=%lld, " @@ -2629,6 +2632,7 @@ static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p) buf[1] = 0xB0; buf[3] = 0x3C; buf[4] = 1; /* WSNZ set */ + buf[5] = 0xff; /* Maximum compare and write length */ /* Optimal transfer granuality is PAGE_SIZE */ put_unaligned_be16(max_t(int, PAGE_SIZE/dev->block_size, 1), &buf[6]); @@ -3739,7 +3743,9 @@ static int vdisk_fsync(loff_t loff, goto out; } - if (virt_dev->blockio) + if (virt_dev->nullio) + ; + else if (virt_dev->blockio) res = vdisk_fsync_blockio(loff, len, dev, gfp_flags, cmd, async); else res = vdisk_fsync_fileio(loff, len, dev, cmd, async); @@ -4502,6 +4508,29 @@ out: return ret; } +static ssize_t fileio_write_sync(struct file *fd, void *buf, size_t len, + loff_t *loff) +{ + mm_segment_t old_fs; + ssize_t ret; + + old_fs = get_fs(); + set_fs(get_ds()); + + if (fd->f_op->llseek) + ret = fd->f_op->llseek(fd, *loff, 0/*SEEK_SET*/); + else + ret = default_llseek(fd, *loff, 0/*SEEK_SET*/); + if (ret < 0) + goto out; + + ret = vfs_write(fd, (char __force __user *)buf, len, loff); + +out: + set_fs(old_fs); + + return ret; +} static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, size_t len, loff_t *loff) { @@ -4513,6 +4542,26 @@ static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, return fileio_read_sync(virt_dev->fd, buf, len, loff); } +static ssize_t vdev_write_sync(struct scst_vdisk_dev *virt_dev, void *buf, + size_t len, loff_t *loff) +{ + int rw; + + if (virt_dev->nullio) { + return len; + } else if (virt_dev->blockio) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + rw = REQ_WRITE; +#else + rw = 1 << BIO_RW; +#endif + + return blockio_rw_sync(virt_dev, buf, len, loff, rw); + } else { + return fileio_write_sync(virt_dev->fd, buf, len, loff); + } +} + static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; @@ -4607,6 +4656,106 @@ out: return CMD_SUCCEEDED; } +/* COMPARE AND WRITE */ +static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + uint32_t data_len = scst_cmd_get_data_len(cmd); + int length, i; + uint8_t *caw_buf = NULL, *read_buf = NULL; + loff_t loff, read, written; + + if (unlikely(cmd->cdb[1] & 0xE0)) { + TRACE_DBG("%s", "WRPROTECT not supported"); + scst_set_invalid_field_in_cdb(cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 5); + goto out; + } + + /* + * A NUMBER OF LOGICAL BLOCKS field set to zero specifies that no read + * operations shall be performed, no logical block data shall be + * transferred from the Data-Out Buffer, no compare operations shall + * be performed, and no write operations shall be performed. This + * condition shall not be considered an error. + */ + if (data_len == 0) + goto out; + + length = scst_get_buf_full(cmd, &caw_buf); + read_buf = vmalloc(data_len); + if (length < 0 || !read_buf) { + PRINT_ERROR("scst_get_buf_full() failed: %d", length); + if (length == -ENOMEM || !read_buf) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + WARN_ON_ONCE(length != 2 * data_len); + + loff = p->loff; + read = vdev_read_sync(virt_dev, read_buf, data_len, &loff); + if (read < data_len) { + PRINT_ERROR("COMPARE AND WRITE / READ returned %lld from %d", + read, data_len); + if (read == -EAGAIN) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_read_error)); + goto out; + } + + if (memcmp(caw_buf, read_buf, data_len) != 0) { + for (i = 0; i < data_len && caw_buf[i] == read_buf[i]; i++) + ; + /* + * SBC-3 $5.2: if the compare operation does not indicate a + * match, then terminate the command with CHECK CONDITION + * status with the sense key set to MISCOMPARE and the + * additional sense code set to MISCOMPARE DURING VERIFY + * OPERATION. In the sense data (see 4.18 and SPC-4) the + * offset from the start of the Data-Out Buffer to the first + * byte of data that was not equal shall be reported in the + * INFORMATION field. + */ + scst_set_cmd_error_and_inf(cmd, + SCST_LOAD_SENSE(scst_sense_miscompare_error), + p->loff + i); + goto out; + } + + loff = p->loff; + written = vdev_write_sync(virt_dev, caw_buf + data_len, data_len, + &loff); + if (written < data_len) { + PRINT_ERROR("COMPARE AND WRITE / WRITE wrote %lld / %d", + written, data_len); + if (written == -EAGAIN) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_write_error)); + goto out; + } + if (p->fua) + vdisk_fsync(loff, scst_cmd_get_data_len(cmd), cmd->dev, + cmd->cmd_gfp_mask, cmd, false); + +out: + if (read_buf) + vfree(read_buf); + if (caw_buf) + scst_put_buf_full(cmd, caw_buf); + + return CMD_SUCCEEDED; +} + static enum compl_status_e blockio_exec_write_verify(struct vdisk_cmd_params *p) { /* Not yet implemented */ diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 26bea1669..8cdb20328 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -150,6 +150,8 @@ static int get_cdb_info_write_same10(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_write_same16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_apt(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_min(struct scst_cmd *cmd, @@ -966,6 +968,14 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .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}, + {.ops = 0x89, .devkey = "O ", + .info_op_name = "COMPARE AND WRITE", + .info_data_direction = SCST_DATA_WRITE, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM| + SCST_SERIALIZED, + .info_lba_off = 2, .info_lba_len = 8, + .info_len_off = 13, .info_len_len = 1, + .get_cdb_info = get_cdb_info_compare_and_write}, {.ops = 0x8A, .devkey = "O OO O ", .info_op_name = "WRITE(16)", .info_data_direction = SCST_DATA_WRITE, @@ -1656,6 +1666,38 @@ out: } EXPORT_SYMBOL(scst_set_cmd_error); +int scst_set_cmd_error_and_inf(struct scst_cmd *cmd, int key, int asc, + int ascq, uint64_t information) +{ + int res; + + res = scst_set_cmd_error(cmd, key, asc, ascq); + if (res) + goto out; + + switch (cmd->sense[0] & 0x7f) { + case 0x70: + /* Fixed format */ + cmd->sense[0] |= 0x80; /* Information field is valid */ + put_unaligned_be32(information, &cmd->sense[3]); + break; + case 0x72: + /* Descriptor format */ + cmd->sense[7] = 12; /* additional sense length */ + cmd->sense[8 + 0] = 0; /* descriptor type: Information */ + cmd->sense[8 + 1] = 10; /* Additional length */ + cmd->sense[8 + 2] = 0x80; /* VALID */ + put_unaligned_be64(information, &cmd->sense[8 + 4]); + break; + default: + sBUG(); + } + +out: + return res; +} +EXPORT_SYMBOL(scst_set_cmd_error_and_inf); + static void scst_fill_field_pointer_sense(uint8_t *fp_sense, int field_offs, int bit_offs, bool cdb) { @@ -6684,6 +6726,15 @@ static int get_cdb_info_write_same16(struct scst_cmd *cmd, return 0; } +static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + 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; +} + /** * get_cdb_info_apt() - Parse ATA PASS-THROUGH CDB. *