aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/target/target_core_sbc.c
diff options
context:
space:
mode:
authorNicholas Bellinger <nab@daterainc.com>2013-08-19 18:20:28 -0400
committerNicholas Bellinger <nab@linux-iscsi.org>2013-09-10 19:41:59 -0400
commit68ff9b9b27525cdaaea81890456f65aed5ce0b70 (patch)
tree80219e09e0c9d42b4ae61987075d9d03624695fc /drivers/target/target_core_sbc.c
parent0123a9ec6a4fea20d5afea90c9b47fb73fb1bc34 (diff)
target: Add support for COMPARE_AND_WRITE emulation
This patch adds support for COMPARE_AND_WRITE emulation on a per block basis. This logic is used as an atomic test and set primative currently used by VMWare ESX VAAI for performing array side locking of individual VMFS extent ownership. This includes the COMPARE_AND_WRITE CDB parsing within sbc_parse_cdb(), and does the majority of the work within the compare_and_write_callback() to perform the verify instance user data comparision, and subsequent write instance user data I/O submission upon a successfull comparision. The synchronization is enforced by se_device->caw_sem, that is obtained before the initial READ I/O submission in sbc_compare_and_write(). The mutex is then released upon MISCOMPARE in compare_and_write_callback(), or upon WRITE instance user-data completion in compare_and_write_post(). The implementation currently assumes a single logical block (NoLB=1). v4 changes: - Explicitly clear cmd->transport_complete_callback for two failure cases in sbc_compare_and_write() in order to avoid double unlock of ->caw_sem in compare_and_write_callback() (Dan Carpenter) v3 changes: - Convert se_device->caw_mutex to ->caw_sem v2 changes: - Set SCF_COMPARE_AND_WRITE and cmd->execute_cmd() to sbc_compare_and_write() during setup in sbc_parse_cdb() - Use sbc_compare_and_write() for initial READ submission with DMA_FROM_DEVICE - Reset cmd->execute_cmd() to sbc_execute_rw() for write instance user-data in compare_and_write_callback() - Drop SCF_BIDI command flag usage - Set TRANSPORT_PROCESSING + transport_state flags before write instance submission, and convert to __target_execute_cmd() - Prevent sbc_get_size() from being being called twice to generate incorrect size in sbc_parse_cdb() - Enforce se_device->caw_mutex synchronization between initial READ I/O submission, and final WRITE I/O completion. Cc: Christoph Hellwig <hch@lst.de> Cc: Hannes Reinecke <hare@suse.de> Cc: Martin Petersen <martin.petersen@oracle.com> Cc: Chris Mason <chris.mason@fusionio.com> Cc: James Bottomley <JBottomley@Parallels.com> Cc: Nicholas Bellinger <nab@linux-iscsi.org> Signed-off-by: Nicholas Bellinger <nab@daterainc.com>
Diffstat (limited to 'drivers/target/target_core_sbc.c')
-rw-r--r--drivers/target/target_core_sbc.c197
1 files changed, 196 insertions, 1 deletions
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);