summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/target/target_core_device.c1
-rw-r--r--drivers/target/target_core_sbc.c197
-rw-r--r--include/target/target_core_base.h1
3 files changed, 198 insertions, 1 deletions
diff --git a/drivers/target/target_core_device.c b/drivers/target/target_core_device.c
index 0b5f86806f1d..f3dc1f5fc41c 100644
--- a/drivers/target/target_core_device.c
+++ b/drivers/target/target_core_device.c
@@ -1413,6 +1413,7 @@ struct se_device *target_alloc_device(struct se_hba *hba, const char *name)
1413 spin_lock_init(&dev->se_port_lock); 1413 spin_lock_init(&dev->se_port_lock);
1414 spin_lock_init(&dev->se_tmr_lock); 1414 spin_lock_init(&dev->se_tmr_lock);
1415 spin_lock_init(&dev->qf_cmd_lock); 1415 spin_lock_init(&dev->qf_cmd_lock);
1416 sema_init(&dev->caw_sem, 1);
1416 atomic_set(&dev->dev_ordered_id, 0); 1417 atomic_set(&dev->dev_ordered_id, 0);
1417 INIT_LIST_HEAD(&dev->t10_wwn.t10_vpd_list); 1418 INIT_LIST_HEAD(&dev->t10_wwn.t10_vpd_list);
1418 spin_lock_init(&dev->t10_wwn.t10_vpd_lock); 1419 spin_lock_init(&dev->t10_wwn.t10_vpd_lock);
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index 5569b3697103..dfb6603eab4d 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -25,6 +25,7 @@
25#include <linux/ratelimit.h> 25#include <linux/ratelimit.h>
26#include <asm/unaligned.h> 26#include <asm/unaligned.h>
27#include <scsi/scsi.h> 27#include <scsi/scsi.h>
28#include <scsi/scsi_tcq.h>
28 29
29#include <target/target_core_base.h> 30#include <target/target_core_base.h>
30#include <target/target_core_backend.h> 31#include <target/target_core_backend.h>
@@ -344,6 +345,177 @@ sbc_execute_rw(struct se_cmd *cmd)
344 cmd->data_direction); 345 cmd->data_direction);
345} 346}
346 347
348static sense_reason_t compare_and_write_post(struct se_cmd *cmd)
349{
350 struct se_device *dev = cmd->se_dev;
351
352 cmd->se_cmd_flags |= SCF_COMPARE_AND_WRITE_POST;
353 /*
354 * Unlock ->caw_sem originally obtained during sbc_compare_and_write()
355 * before the original READ I/O submission.
356 */
357 up(&dev->caw_sem);
358
359 return TCM_NO_SENSE;
360}
361
362static sense_reason_t compare_and_write_callback(struct se_cmd *cmd)
363{
364 struct se_device *dev = cmd->se_dev;
365 struct scatterlist *write_sg = NULL, *sg;
366 unsigned char *buf, *addr;
367 struct sg_mapping_iter m;
368 unsigned int offset = 0, len;
369 unsigned int nlbas = cmd->t_task_nolb;
370 unsigned int block_size = dev->dev_attrib.block_size;
371 unsigned int compare_len = (nlbas * block_size);
372 sense_reason_t ret = TCM_NO_SENSE;
373 int rc, i;
374
375 buf = kzalloc(cmd->data_length, GFP_KERNEL);
376 if (!buf) {
377 pr_err("Unable to allocate compare_and_write buf\n");
378 return TCM_OUT_OF_RESOURCES;
379 }
380
381 write_sg = kzalloc(sizeof(struct scatterlist) * cmd->t_data_nents,
382 GFP_KERNEL);
383 if (!write_sg) {
384 pr_err("Unable to allocate compare_and_write sg\n");
385 ret = TCM_OUT_OF_RESOURCES;
386 goto out;
387 }
388 /*
389 * Setup verify and write data payloads from total NumberLBAs.
390 */
391 rc = sg_copy_to_buffer(cmd->t_data_sg, cmd->t_data_nents, buf,
392 cmd->data_length);
393 if (!rc) {
394 pr_err("sg_copy_to_buffer() failed for compare_and_write\n");
395 ret = TCM_OUT_OF_RESOURCES;
396 goto out;
397 }
398 /*
399 * Compare against SCSI READ payload against verify payload
400 */
401 for_each_sg(cmd->t_bidi_data_sg, sg, cmd->t_bidi_data_nents, i) {
402 addr = (unsigned char *)kmap_atomic(sg_page(sg));
403 if (!addr) {
404 ret = TCM_OUT_OF_RESOURCES;
405 goto out;
406 }
407
408 len = min(sg->length, compare_len);
409
410 if (memcmp(addr, buf + offset, len)) {
411 pr_warn("Detected MISCOMPARE for addr: %p buf: %p\n",
412 addr, buf + offset);
413 kunmap_atomic(addr);
414 goto miscompare;
415 }
416 kunmap_atomic(addr);
417
418 offset += len;
419 compare_len -= len;
420 if (!compare_len)
421 break;
422 }
423
424 i = 0;
425 len = cmd->t_task_nolb * block_size;
426 sg_miter_start(&m, cmd->t_data_sg, cmd->t_data_nents, SG_MITER_TO_SG);
427 /*
428 * Currently assumes NoLB=1 and SGLs are PAGE_SIZE..
429 */
430 while (len) {
431 sg_miter_next(&m);
432
433 if (block_size < PAGE_SIZE) {
434 sg_set_page(&write_sg[i], m.page, block_size,
435 block_size);
436 } else {
437 sg_miter_next(&m);
438 sg_set_page(&write_sg[i], m.page, block_size,
439 0);
440 }
441 len -= block_size;
442 i++;
443 }
444 sg_miter_stop(&m);
445 /*
446 * Save the original SGL + nents values before updating to new
447 * assignments, to be released in transport_free_pages() ->
448 * transport_reset_sgl_orig()
449 */
450 cmd->t_data_sg_orig = cmd->t_data_sg;
451 cmd->t_data_sg = write_sg;
452 cmd->t_data_nents_orig = cmd->t_data_nents;
453 cmd->t_data_nents = 1;
454
455 cmd->sam_task_attr = MSG_HEAD_TAG;
456 cmd->transport_complete_callback = compare_and_write_post;
457 /*
458 * Now reset ->execute_cmd() to the normal sbc_execute_rw() handler
459 * for submitting the adjusted SGL to write instance user-data.
460 */
461 cmd->execute_cmd = sbc_execute_rw;
462
463 spin_lock_irq(&cmd->t_state_lock);
464 cmd->t_state = TRANSPORT_PROCESSING;
465 cmd->transport_state |= CMD_T_ACTIVE|CMD_T_BUSY|CMD_T_SENT;
466 spin_unlock_irq(&cmd->t_state_lock);
467
468 __target_execute_cmd(cmd);
469
470 kfree(buf);
471 return ret;
472
473miscompare:
474 pr_warn("Target/%s: Send MISCOMPARE check condition and sense\n",
475 dev->transport->name);
476 ret = TCM_MISCOMPARE_VERIFY;
477out:
478 /*
479 * In the MISCOMPARE or failure case, unlock ->caw_sem obtained in
480 * sbc_compare_and_write() before the original READ I/O submission.
481 */
482 up(&dev->caw_sem);
483 kfree(write_sg);
484 kfree(buf);
485 return ret;
486}
487
488static sense_reason_t
489sbc_compare_and_write(struct se_cmd *cmd)
490{
491 struct se_device *dev = cmd->se_dev;
492 sense_reason_t ret;
493 int rc;
494 /*
495 * Submit the READ first for COMPARE_AND_WRITE to perform the
496 * comparision using SGLs at cmd->t_bidi_data_sg..
497 */
498 rc = down_interruptible(&dev->caw_sem);
499 if ((rc != 0) || signal_pending(current)) {
500 cmd->transport_complete_callback = NULL;
501 return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
502 }
503
504 ret = cmd->execute_rw(cmd, cmd->t_bidi_data_sg, cmd->t_bidi_data_nents,
505 DMA_FROM_DEVICE);
506 if (ret) {
507 cmd->transport_complete_callback = NULL;
508 up(&dev->caw_sem);
509 return ret;
510 }
511 /*
512 * Unlock of dev->caw_sem to occur in compare_and_write_callback()
513 * upon MISCOMPARE, or in compare_and_write_done() upon completion
514 * of WRITE instance user-data.
515 */
516 return TCM_NO_SENSE;
517}
518
347sense_reason_t 519sense_reason_t
348sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops) 520sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops)
349{ 521{
@@ -481,6 +653,28 @@ sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops)
481 } 653 }
482 break; 654 break;
483 } 655 }
656 case COMPARE_AND_WRITE:
657 sectors = cdb[13];
658 /*
659 * Currently enforce COMPARE_AND_WRITE for a single sector
660 */
661 if (sectors > 1) {
662 pr_err("COMPARE_AND_WRITE contains NoLB: %u greater"
663 " than 1\n", sectors);
664 return TCM_INVALID_CDB_FIELD;
665 }
666 /*
667 * Double size because we have two buffers, note that
668 * zero is not an error..
669 */
670 size = 2 * sbc_get_size(cmd, sectors);
671 cmd->t_task_lba = get_unaligned_be64(&cdb[2]);
672 cmd->t_task_nolb = sectors;
673 cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB | SCF_COMPARE_AND_WRITE;
674 cmd->execute_rw = ops->execute_rw;
675 cmd->execute_cmd = sbc_compare_and_write;
676 cmd->transport_complete_callback = compare_and_write_callback;
677 break;
484 case READ_CAPACITY: 678 case READ_CAPACITY:
485 size = READ_CAP_LEN; 679 size = READ_CAP_LEN;
486 cmd->execute_cmd = sbc_emulate_readcapacity; 680 cmd->execute_cmd = sbc_emulate_readcapacity;
@@ -620,7 +814,8 @@ sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops)
620 return TCM_ADDRESS_OUT_OF_RANGE; 814 return TCM_ADDRESS_OUT_OF_RANGE;
621 } 815 }
622 816
623 size = sbc_get_size(cmd, sectors); 817 if (!(cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE))
818 size = sbc_get_size(cmd, sectors);
624 } 819 }
625 820
626 return target_cmd_size_check(cmd, size); 821 return target_cmd_size_check(cmd, size);
diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h
index 61a4dc890701..b5d0c2496c7e 100644
--- a/include/target/target_core_base.h
+++ b/include/target/target_core_base.h
@@ -672,6 +672,7 @@ struct se_device {
672 spinlock_t se_port_lock; 672 spinlock_t se_port_lock;
673 spinlock_t se_tmr_lock; 673 spinlock_t se_tmr_lock;
674 spinlock_t qf_cmd_lock; 674 spinlock_t qf_cmd_lock;
675 struct semaphore caw_sem;
675 /* Used for legacy SPC-2 reservationsa */ 676 /* Used for legacy SPC-2 reservationsa */
676 struct se_node_acl *dev_reserved_node_acl; 677 struct se_node_acl *dev_reserved_node_acl;
677 /* Used for ALUA Logical Unit Group membership */ 678 /* Used for ALUA Logical Unit Group membership */