mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-19 19:51:27 +00:00
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 <bvanassche@acm.org> git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@5334 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 <scsi/scsi.h>. 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 <linux/fs.h>. See also commit
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user