diff options
-rw-r--r-- | drivers/target/target_core_device.c | 1 | ||||
-rw-r--r-- | drivers/target/target_core_sbc.c | 197 | ||||
-rw-r--r-- | include/target/target_core_base.h | 1 |
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 | ||
348 | static 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 | |||
362 | static 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 | |||
473 | miscompare: | ||
474 | pr_warn("Target/%s: Send MISCOMPARE check condition and sense\n", | ||
475 | dev->transport->name); | ||
476 | ret = TCM_MISCOMPARE_VERIFY; | ||
477 | out: | ||
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 | |||
488 | static sense_reason_t | ||
489 | sbc_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 | |||
347 | sense_reason_t | 519 | sense_reason_t |
348 | sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops) | 520 | sbc_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 */ |