Commit ab628b9f authored by David Disseldorp's avatar David Disseldorp Committed by Martin K. Petersen
Browse files

scsi: target: Split out COMPARE AND WRITE memcmp into helper

In preparation for finding and returning the miscompare offset.

Link: https://lore.kernel.org/r/20201031233211.5207-4-ddiss@suse.de


Reviewed-by: default avatarMike Christie <michael.christie@oracle.com>
Signed-off-by: default avatarDavid Disseldorp <ddiss@suse.de>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 8dd992fb
Loading
Loading
Loading
Loading
+68 −53
Original line number Diff line number Diff line
@@ -434,20 +434,75 @@ static sense_reason_t compare_and_write_post(struct se_cmd *cmd, bool success,
	return ret;
}

/*
 * compare @cmp_len bytes of @read_sgl with @cmp_sgl. On miscompare return
 * TCM_MISCOMPARE_VERIFY.
 */
static sense_reason_t
compare_and_write_do_cmp(struct scatterlist *read_sgl, unsigned int read_nents,
			 struct scatterlist *cmp_sgl, unsigned int cmp_nents,
			 unsigned int cmp_len)
{
	unsigned char *buf = NULL;
	struct scatterlist *sg;
	sense_reason_t ret;
	unsigned int offset;
	size_t rc;
	int i;

	buf = kzalloc(cmp_len, GFP_KERNEL);
	if (!buf) {
		ret = TCM_OUT_OF_RESOURCES;
		goto out;
	}

	rc = sg_copy_to_buffer(cmp_sgl, cmp_nents, buf, cmp_len);
	if (!rc) {
		pr_err("sg_copy_to_buffer() failed for compare_and_write\n");
		ret = TCM_OUT_OF_RESOURCES;
		goto out;
	}
	/*
	 * Compare SCSI READ payload against verify payload
	 */
	offset = 0;
	for_each_sg(read_sgl, sg, read_nents, i) {
		unsigned int len = min(sg->length, cmp_len);
		unsigned char *addr = kmap_atomic(sg_page(sg));

		if (memcmp(addr, buf + offset, len)) {
			pr_warn("Detected MISCOMPARE for addr: %p buf: %p\n",
				addr, buf + offset);
			kunmap_atomic(addr);
			ret = TCM_MISCOMPARE_VERIFY;
			goto out;
		}
		kunmap_atomic(addr);

		offset += len;
		cmp_len -= len;
		if (!cmp_len)
			break;
	}
	pr_debug("COMPARE AND WRITE read data matches compare data\n");
	ret = TCM_NO_SENSE;
out:
	kfree(buf);
	return ret;
}

static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool success,
						 int *post_ret)
{
	struct se_device *dev = cmd->se_dev;
	struct sg_table write_tbl = { };
	struct scatterlist *write_sg, *sg;
	unsigned char *buf = NULL, *addr;
	struct scatterlist *write_sg;
	struct sg_mapping_iter m;
	unsigned int offset = 0, len;
	unsigned int nlbas = cmd->t_task_nolb;
	unsigned int len;
	unsigned int block_size = dev->dev_attrib.block_size;
	unsigned int compare_len = (nlbas * block_size);
	unsigned int compare_len = (cmd->t_task_nolb * block_size);
	sense_reason_t ret = TCM_NO_SENSE;
	int rc, i;
	int i;

	/*
	 * Handle early failure in transport_generic_request_failure(),
@@ -473,12 +528,13 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes
		goto out;
	}

	buf = kzalloc(cmd->data_length, GFP_KERNEL);
	if (!buf) {
		pr_err("Unable to allocate compare_and_write buf\n");
		ret = TCM_OUT_OF_RESOURCES;
	ret = compare_and_write_do_cmp(cmd->t_bidi_data_sg,
				       cmd->t_bidi_data_nents,
				       cmd->t_data_sg,
				       cmd->t_data_nents,
				       compare_len);
	if (ret)
		goto out;
	}

	if (sg_alloc_table(&write_tbl, cmd->t_data_nents, GFP_KERNEL) < 0) {
		pr_err("Unable to allocate compare_and_write sg\n");
@@ -486,44 +542,9 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes
		goto out;
	}
	write_sg = write_tbl.sgl;
	/*
	 * Setup verify and write data payloads from total NumberLBAs.
	 */
	rc = sg_copy_to_buffer(cmd->t_data_sg, cmd->t_data_nents, buf,
			       cmd->data_length);
	if (!rc) {
		pr_err("sg_copy_to_buffer() failed for compare_and_write\n");
		ret = TCM_OUT_OF_RESOURCES;
		goto out;
	}
	/*
	 * Compare against SCSI READ payload against verify payload
	 */
	for_each_sg(cmd->t_bidi_data_sg, sg, cmd->t_bidi_data_nents, i) {
		addr = (unsigned char *)kmap_atomic(sg_page(sg));
		if (!addr) {
			ret = TCM_OUT_OF_RESOURCES;
			goto out;
		}

		len = min(sg->length, compare_len);

		if (memcmp(addr, buf + offset, len)) {
			pr_warn("Detected MISCOMPARE for addr: %p buf: %p\n",
				addr, buf + offset);
			kunmap_atomic(addr);
			goto miscompare;
		}
		kunmap_atomic(addr);

		offset += len;
		compare_len -= len;
		if (!compare_len)
			break;
	}

	i = 0;
	len = cmd->t_task_nolb * block_size;
	len = compare_len;
	sg_miter_start(&m, cmd->t_data_sg, cmd->t_data_nents, SG_MITER_TO_SG);
	/*
	 * Currently assumes NoLB=1 and SGLs are PAGE_SIZE..
@@ -568,13 +589,8 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes

	__target_execute_cmd(cmd, false);

	kfree(buf);
	return ret;

miscompare:
	pr_warn("Target/%s: Send MISCOMPARE check condition and sense\n",
		dev->transport->name);
	ret = TCM_MISCOMPARE_VERIFY;
out:
	/*
	 * In the MISCOMPARE or failure case, unlock ->caw_sem obtained in
@@ -582,7 +598,6 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes
	 */
	up(&dev->caw_sem);
	sg_free_table(&write_tbl);
	kfree(buf);
	return ret;
}