Merge tag 'perf-tools-2020-12-24' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6-microblaze.git] / drivers / target / target_core_sbc.c
index 6e8b8d3..f7c527a 100644 (file)
@@ -434,20 +434,81 @@ 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, fill
+ * @miscmp_off and 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 int *miscmp_off)
+{
+       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;
+       ret = TCM_NO_SENSE;
+       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)) {
+                       unsigned int i;
+
+                       for (i = 0; i < len && addr[i] == buf[offset + i]; i++)
+                               ;
+                       *miscmp_off = offset + i;
+                       pr_warn("Detected MISCOMPARE at offset %u\n",
+                               *miscmp_off);
+                       ret = TCM_MISCOMPARE_VERIFY;
+               }
+               kunmap_atomic(addr);
+               if (ret != TCM_NO_SENSE)
+                       goto out;
+
+               offset += len;
+               cmp_len -= len;
+               if (!cmp_len)
+                       break;
+       }
+       pr_debug("COMPARE AND WRITE read data matches compare data\n");
+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);
+       unsigned int miscmp_off = 0;
        sense_reason_t ret = TCM_NO_SENSE;
-       int rc, i;
+       int i;
 
        /*
         * Handle early failure in transport_generic_request_failure(),
@@ -473,12 +534,23 @@ 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,
+                                      &miscmp_off);
+       if (ret == TCM_MISCOMPARE_VERIFY) {
+               /*
+                * SBC-4 r15: 5.3 COMPARE AND WRITE command
+                * In the sense data (see 4.18 and SPC-5) 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.
+                */
+               cmd->sense_info = miscmp_off;
+               goto out;
+       } else 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 +558,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 +605,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 +614,6 @@ out:
         */
        up(&dev->caw_sem);
        sg_free_table(&write_tbl);
-       kfree(buf);
        return ret;
 }
 
@@ -1439,7 +1470,7 @@ sbc_dif_verify(struct se_cmd *cmd, sector_t start, unsigned int sectors,
                        if (rc) {
                                kunmap_atomic(daddr - dsg->offset);
                                kunmap_atomic(paddr - psg->offset);
-                               cmd->bad_sector = sector;
+                               cmd->sense_info = sector;
                                return rc;
                        }
 next: