aboutsummaryrefslogtreecommitdiff
path: root/sys/cam/ctl/ctl_tpc.c
diff options
context:
space:
mode:
authorAlexander Motin <mav@FreeBSD.org>2014-07-16 15:57:17 +0000
committerAlexander Motin <mav@FreeBSD.org>2014-07-16 15:57:17 +0000
commit984a2ea91fbc21063e931d117aed354cfe0c02f5 (patch)
tree7d63c809c6f94d9453beaad7bd5a624884e26519 /sys/cam/ctl/ctl_tpc.c
parent22bdc15a57f919df52cb7b26ddffc55f5d03eed6 (diff)
downloadsrc-984a2ea91fbc21063e931d117aed354cfe0c02f5.tar.gz
src-984a2ea91fbc21063e931d117aed354cfe0c02f5.zip
Add support for VMWare dialect of EXTENDED COPY command, aka VAAI Clone.
This allows to clone VMs and move them between LUNs inside one storage host without generating extra network traffic to the initiator and back, and without being limited by network bandwidth. LUNs participating in copy operation should have UNIQUE NAA or EUI IDs set. For LUNs without these IDs VMWare will use traditional copy operations. Beware: the above LUN IDs explicitly set to values non-unique from the VM cluster point of view may cause data corruption if wrong LUN is addressed! MFC after: 2 weeks Sponsored by: iXsystems, Inc.
Notes
Notes: svn path=/head/; revision=268767
Diffstat (limited to 'sys/cam/ctl/ctl_tpc.c')
-rw-r--r--sys/cam/ctl/ctl_tpc.c1370
1 files changed, 1370 insertions, 0 deletions
diff --git a/sys/cam/ctl/ctl_tpc.c b/sys/cam/ctl/ctl_tpc.c
new file mode 100644
index 000000000000..d5baa01a008c
--- /dev/null
+++ b/sys/cam/ctl/ctl_tpc.c
@@ -0,0 +1,1370 @@
+/*-
+ * Copyright (c) 2014 Alexander Motin <mav@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * without modification, immediately at the beginning of the file.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/types.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <machine/atomic.h>
+
+#include <cam/cam.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_frontend.h>
+#include <cam/ctl/ctl_frontend_internal.h>
+#include <cam/ctl/ctl_util.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_ha.h>
+#include <cam/ctl/ctl_private.h>
+#include <cam/ctl/ctl_debug.h>
+#include <cam/ctl/ctl_scsi_all.h>
+#include <cam/ctl/ctl_tpc.h>
+#include <cam/ctl/ctl_error.h>
+
+#define TPC_MAX_CSCDS 64
+#define TPC_MAX_SEGS 64
+#define TPC_MAX_SEG 0
+#define TPC_MAX_LIST 8192
+#define TPC_MAX_INLINE 0
+#define TPC_MAX_LISTS 255
+#define TPC_MAX_IO_SIZE (1024 * 1024)
+
+MALLOC_DEFINE(M_CTL_TPC, "ctltpc", "CTL TPC");
+
+typedef enum {
+ TPC_ERR_RETRY = 0x000,
+ TPC_ERR_FAIL = 0x001,
+ TPC_ERR_MASK = 0x0ff,
+ TPC_ERR_NO_DECREMENT = 0x100
+} tpc_error_action;
+
+struct tpc_list;
+TAILQ_HEAD(runl, tpc_io);
+struct tpc_io {
+ union ctl_io *io;
+ uint64_t lun;
+ struct tpc_list *list;
+ struct runl run;
+ TAILQ_ENTRY(tpc_io) rlinks;
+ TAILQ_ENTRY(tpc_io) links;
+};
+
+struct tpc_list {
+ uint8_t service_action;
+ int init_port;
+ uint16_t init_idx;
+ uint32_t list_id;
+ uint8_t flags;
+ uint8_t *params;
+ struct scsi_ec_cscd *cscd;
+ struct scsi_ec_segment *seg[TPC_MAX_SEGS];
+ uint8_t *inl;
+ int ncscd;
+ int nseg;
+ int leninl;
+ int curseg;
+ off_t curbytes;
+ int curops;
+ int stage;
+ uint8_t *buf;
+ int segbytes;
+ int tbdio;
+ int error;
+ int abort;
+ int completed;
+ TAILQ_HEAD(, tpc_io) allio;
+ struct scsi_sense_data sense_data;
+ uint8_t sense_len;
+ uint8_t scsi_status;
+ struct ctl_scsiio *ctsio;
+ struct ctl_lun *lun;
+ TAILQ_ENTRY(tpc_list) links;
+};
+
+void
+ctl_tpc_init(struct ctl_lun *lun)
+{
+
+ TAILQ_INIT(&lun->tpc_lists);
+}
+
+void
+ctl_tpc_shutdown(struct ctl_lun *lun)
+{
+ struct tpc_list *list;
+
+ while ((list = TAILQ_FIRST(&lun->tpc_lists)) != NULL) {
+ TAILQ_REMOVE(&lun->tpc_lists, list, links);
+ KASSERT(list->completed,
+ ("Not completed TPC (%p) on shutdown", list));
+ free(list, M_CTL);
+ }
+}
+
+int
+ctl_inquiry_evpd_tpc(struct ctl_scsiio *ctsio, int alloc_len)
+{
+ struct scsi_vpd_tpc *tpc_ptr;
+ struct scsi_vpd_tpc_descriptor *d_ptr;
+ struct scsi_vpd_tpc_descriptor_sc *sc_ptr;
+ struct scsi_vpd_tpc_descriptor_sc_descr *scd_ptr;
+ struct scsi_vpd_tpc_descriptor_pd *pd_ptr;
+ struct scsi_vpd_tpc_descriptor_sd *sd_ptr;
+ struct scsi_vpd_tpc_descriptor_sdid *sdid_ptr;
+ struct scsi_vpd_tpc_descriptor_gco *gco_ptr;
+ struct ctl_lun *lun;
+ int data_len;
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ data_len = sizeof(struct scsi_vpd_tpc) +
+ roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sc) +
+ 2 * sizeof(struct scsi_vpd_tpc_descriptor_sc_descr) + 7, 4) +
+ sizeof(struct scsi_vpd_tpc_descriptor_pd) +
+ roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sd) + 4, 4) +
+ roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sdid) + 2, 4) +
+ sizeof(struct scsi_vpd_tpc_descriptor_gco);
+
+ ctsio->kern_data_ptr = malloc(data_len, M_CTL, M_WAITOK | M_ZERO);
+ tpc_ptr = (struct scsi_vpd_tpc *)ctsio->kern_data_ptr;
+ ctsio->kern_sg_entries = 0;
+
+ if (data_len < alloc_len) {
+ ctsio->residual = alloc_len - data_len;
+ ctsio->kern_data_len = data_len;
+ ctsio->kern_total_len = data_len;
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+
+ /*
+ * The control device is always connected. The disk device, on the
+ * other hand, may not be online all the time.
+ */
+ if (lun != NULL)
+ tpc_ptr->device = (SID_QUAL_LU_CONNECTED << 5) |
+ lun->be_lun->lun_type;
+ else
+ tpc_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT;
+ tpc_ptr->page_code = SVPD_SCSI_TPC;
+ scsi_ulto2b(data_len - 4, tpc_ptr->page_length);
+
+ /* Supported commands */
+ d_ptr = (struct scsi_vpd_tpc_descriptor *)&tpc_ptr->descr[0];
+ sc_ptr = (struct scsi_vpd_tpc_descriptor_sc *)d_ptr;
+ scsi_ulto2b(SVPD_TPC_SC, sc_ptr->desc_type);
+ sc_ptr->list_length = 2 * sizeof(*scd_ptr) + 7;
+ scsi_ulto2b(roundup2(1 + sc_ptr->list_length, 4), sc_ptr->desc_length);
+ scd_ptr = &sc_ptr->descr[0];
+ scd_ptr->opcode = EXTENDED_COPY;
+ scd_ptr->sa_length = 3;
+ scd_ptr->supported_service_actions[0] = EC_EC_LID1;
+ scd_ptr->supported_service_actions[1] = EC_EC_LID4;
+ scd_ptr->supported_service_actions[2] = EC_COA;
+ scd_ptr = (struct scsi_vpd_tpc_descriptor_sc_descr *)
+ &scd_ptr->supported_service_actions[scd_ptr->sa_length];
+ scd_ptr->opcode = RECEIVE_COPY_STATUS;
+ scd_ptr->sa_length = 4;
+ scd_ptr->supported_service_actions[0] = RCS_RCS_LID1;
+ scd_ptr->supported_service_actions[1] = RCS_RCFD;
+ scd_ptr->supported_service_actions[2] = RCS_RCS_LID4;
+ scd_ptr->supported_service_actions[3] = RCS_RCOP;
+
+ /* Parameter data. */
+ d_ptr = (struct scsi_vpd_tpc_descriptor *)
+ (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length));
+ pd_ptr = (struct scsi_vpd_tpc_descriptor_pd *)d_ptr;
+ scsi_ulto2b(SVPD_TPC_PD, pd_ptr->desc_type);
+ scsi_ulto2b(sizeof(*pd_ptr) - 4, pd_ptr->desc_length);
+ scsi_ulto2b(TPC_MAX_CSCDS, pd_ptr->maximum_cscd_descriptor_count);
+ scsi_ulto2b(TPC_MAX_SEGS, pd_ptr->maximum_segment_descriptor_count);
+ scsi_ulto4b(TPC_MAX_LIST, pd_ptr->maximum_descriptor_list_length);
+ scsi_ulto4b(TPC_MAX_INLINE, pd_ptr->maximum_inline_data_length);
+
+ /* Supported Descriptors */
+ d_ptr = (struct scsi_vpd_tpc_descriptor *)
+ (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length));
+ sd_ptr = (struct scsi_vpd_tpc_descriptor_sd *)d_ptr;
+ scsi_ulto2b(SVPD_TPC_SD, sd_ptr->desc_type);
+ scsi_ulto2b(roundup2(sizeof(*sd_ptr) - 4 + 4, 4), sd_ptr->desc_length);
+ sd_ptr->list_length = 4;
+ sd_ptr->supported_descriptor_codes[0] = EC_SEG_B2B;
+ sd_ptr->supported_descriptor_codes[1] = EC_SEG_VERIFY;
+ sd_ptr->supported_descriptor_codes[2] = EC_SEG_REGISTER_KEY;
+ sd_ptr->supported_descriptor_codes[3] = EC_CSCD_ID;
+
+ /* Supported CSCD Descriptor IDs */
+ d_ptr = (struct scsi_vpd_tpc_descriptor *)
+ (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length));
+ sdid_ptr = (struct scsi_vpd_tpc_descriptor_sdid *)d_ptr;
+ scsi_ulto2b(SVPD_TPC_SDID, sdid_ptr->desc_type);
+ scsi_ulto2b(roundup2(sizeof(*sdid_ptr) - 4 + 2, 4), sdid_ptr->desc_length);
+ scsi_ulto2b(2, sdid_ptr->list_length);
+ scsi_ulto2b(0xffff, &sdid_ptr->supported_descriptor_ids[0]);
+
+ /* General Copy Operations */
+ d_ptr = (struct scsi_vpd_tpc_descriptor *)
+ (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length));
+ gco_ptr = (struct scsi_vpd_tpc_descriptor_gco *)d_ptr;
+ scsi_ulto2b(SVPD_TPC_GCO, gco_ptr->desc_type);
+ scsi_ulto2b(sizeof(*gco_ptr) - 4, gco_ptr->desc_length);
+ scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->total_concurrent_copies);
+ scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->maximum_identified_concurrent_copies);
+ scsi_ulto4b(TPC_MAX_SEG, gco_ptr->maximum_segment_length);
+ gco_ptr->data_segment_granularity = 0;
+ gco_ptr->inline_data_granularity = 0;
+
+ ctsio->scsi_status = SCSI_STATUS_OK;
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+}
+
+int
+ctl_receive_copy_operating_parameters(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_receive_copy_operating_parameters *cdb;
+ struct scsi_receive_copy_operating_parameters_data *data;
+ int retval;
+ int alloc_len, total_len;
+
+ CTL_DEBUG_PRINT(("ctl_report_supported_tmf\n"));
+
+ cdb = (struct scsi_receive_copy_operating_parameters *)ctsio->cdb;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ total_len = sizeof(*data) + 4;
+ alloc_len = scsi_4btoul(cdb->length);
+
+ ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO);
+
+ ctsio->kern_sg_entries = 0;
+
+ if (total_len < alloc_len) {
+ ctsio->residual = alloc_len - total_len;
+ ctsio->kern_data_len = total_len;
+ ctsio->kern_total_len = total_len;
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+
+ data = (struct scsi_receive_copy_operating_parameters_data *)ctsio->kern_data_ptr;
+ scsi_ulto4b(sizeof(*data) - 4 + 4, data->length);
+ data->snlid = RCOP_SNLID;
+ scsi_ulto2b(TPC_MAX_CSCDS, data->maximum_cscd_descriptor_count);
+ scsi_ulto2b(TPC_MAX_SEGS, data->maximum_segment_descriptor_count);
+ scsi_ulto4b(TPC_MAX_LIST, data->maximum_descriptor_list_length);
+ scsi_ulto4b(TPC_MAX_SEG, data->maximum_segment_length);
+ scsi_ulto4b(TPC_MAX_INLINE, data->maximum_inline_data_length);
+ scsi_ulto4b(0, data->held_data_limit);
+ scsi_ulto4b(0, data->maximum_stream_device_transfer_size);
+ scsi_ulto2b(TPC_MAX_LISTS, data->total_concurrent_copies);
+ data->maximum_concurrent_copies = TPC_MAX_LISTS;
+ data->data_segment_granularity = 0;
+ data->inline_data_granularity = 0;
+ data->held_data_granularity = 0;
+ data->implemented_descriptor_list_length = 4;
+ data->list_of_implemented_descriptor_type_codes[0] = EC_SEG_B2B;
+ data->list_of_implemented_descriptor_type_codes[1] = EC_SEG_VERIFY;
+ data->list_of_implemented_descriptor_type_codes[2] = EC_SEG_REGISTER_KEY;
+ data->list_of_implemented_descriptor_type_codes[3] = EC_CSCD_ID;
+
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+
+ ctl_datamove((union ctl_io *)ctsio);
+ return (retval);
+}
+
+int
+ctl_receive_copy_status_lid1(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_receive_copy_status_lid1 *cdb;
+ struct scsi_receive_copy_status_lid1_data *data;
+ struct tpc_list *list;
+ struct tpc_list list_copy;
+ int retval;
+ int alloc_len, total_len;
+ uint32_t list_id;
+
+ CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid1\n"));
+
+ cdb = (struct scsi_receive_copy_status_lid1 *)ctsio->cdb;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ list_id = cdb->list_identifier;
+ mtx_lock(&lun->lun_lock);
+ TAILQ_FOREACH(list, &lun->tpc_lists, links) {
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE && list->list_id == list_id)
+ break;
+ }
+ if (list == NULL) {
+ mtx_unlock(&lun->lun_lock);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (retval);
+ }
+ list_copy = *list;
+ if (list->completed) {
+ TAILQ_REMOVE(&lun->tpc_lists, list, links);
+ free(list, M_CTL);
+ }
+ mtx_unlock(&lun->lun_lock);
+
+ total_len = sizeof(*data);
+ alloc_len = scsi_4btoul(cdb->length);
+
+ ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO);
+
+ ctsio->kern_sg_entries = 0;
+
+ if (total_len < alloc_len) {
+ ctsio->residual = alloc_len - total_len;
+ ctsio->kern_data_len = total_len;
+ ctsio->kern_total_len = total_len;
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+
+ data = (struct scsi_receive_copy_status_lid1_data *)ctsio->kern_data_ptr;
+ scsi_ulto4b(sizeof(*data) - 4, data->available_data);
+ if (list_copy.completed) {
+ if (list_copy.error || list_copy.abort)
+ data->copy_command_status = RCS_CCS_ERROR;
+ else
+ data->copy_command_status = RCS_CCS_COMPLETED;
+ } else
+ data->copy_command_status = RCS_CCS_INPROG;
+ scsi_ulto2b(list_copy.curseg, data->segments_processed);
+ if (list_copy.curbytes <= UINT32_MAX) {
+ data->transfer_count_units = RCS_TC_BYTES;
+ scsi_ulto4b(list_copy.curbytes, data->transfer_count);
+ } else {
+ data->transfer_count_units = RCS_TC_MBYTES;
+ scsi_ulto4b(list_copy.curbytes >> 20, data->transfer_count);
+ }
+
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+
+ ctl_datamove((union ctl_io *)ctsio);
+ return (retval);
+}
+
+int
+ctl_receive_copy_failure_details(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_receive_copy_failure_details *cdb;
+ struct scsi_receive_copy_failure_details_data *data;
+ struct tpc_list *list;
+ struct tpc_list list_copy;
+ int retval;
+ int alloc_len, total_len;
+ uint32_t list_id;
+
+ CTL_DEBUG_PRINT(("ctl_receive_copy_failure_details\n"));
+
+ cdb = (struct scsi_receive_copy_failure_details *)ctsio->cdb;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ list_id = cdb->list_identifier;
+ mtx_lock(&lun->lun_lock);
+ TAILQ_FOREACH(list, &lun->tpc_lists, links) {
+ if (list->completed && (list->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE && list->list_id == list_id)
+ break;
+ }
+ if (list == NULL) {
+ mtx_unlock(&lun->lun_lock);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (retval);
+ }
+ list_copy = *list;
+ TAILQ_REMOVE(&lun->tpc_lists, list, links);
+ free(list, M_CTL);
+ mtx_unlock(&lun->lun_lock);
+
+ total_len = sizeof(*data) + list_copy.sense_len;
+ alloc_len = scsi_4btoul(cdb->length);
+
+ ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO);
+
+ ctsio->kern_sg_entries = 0;
+
+ if (total_len < alloc_len) {
+ ctsio->residual = alloc_len - total_len;
+ ctsio->kern_data_len = total_len;
+ ctsio->kern_total_len = total_len;
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+
+ data = (struct scsi_receive_copy_failure_details_data *)ctsio->kern_data_ptr;
+ if (list_copy.completed && (list_copy.error || list_copy.abort)) {
+ scsi_ulto4b(sizeof(*data) - 4, data->available_data);
+ data->copy_command_status = RCS_CCS_ERROR;
+ } else
+ scsi_ulto4b(0, data->available_data);
+ scsi_ulto2b(list_copy.sense_len, data->sense_data_length);
+ memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len);
+
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+
+ ctl_datamove((union ctl_io *)ctsio);
+ return (retval);
+}
+
+int
+ctl_receive_copy_status_lid4(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_receive_copy_status_lid4 *cdb;
+ struct scsi_receive_copy_status_lid4_data *data;
+ struct tpc_list *list;
+ struct tpc_list list_copy;
+ int retval;
+ int alloc_len, total_len;
+ uint32_t list_id;
+
+ CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid4\n"));
+
+ cdb = (struct scsi_receive_copy_status_lid4 *)ctsio->cdb;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ list_id = scsi_4btoul(cdb->list_identifier);
+ mtx_lock(&lun->lun_lock);
+ TAILQ_FOREACH(list, &lun->tpc_lists, links) {
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE && list->list_id == list_id)
+ break;
+ }
+ if (list == NULL) {
+ mtx_unlock(&lun->lun_lock);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (retval);
+ }
+ list_copy = *list;
+ if (list->completed) {
+ TAILQ_REMOVE(&lun->tpc_lists, list, links);
+ free(list, M_CTL);
+ }
+ mtx_unlock(&lun->lun_lock);
+
+ total_len = sizeof(*data) + list_copy.sense_len;
+ alloc_len = scsi_4btoul(cdb->length);
+
+ ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO);
+
+ ctsio->kern_sg_entries = 0;
+
+ if (total_len < alloc_len) {
+ ctsio->residual = alloc_len - total_len;
+ ctsio->kern_data_len = total_len;
+ ctsio->kern_total_len = total_len;
+ } else {
+ ctsio->residual = 0;
+ ctsio->kern_data_len = alloc_len;
+ ctsio->kern_total_len = alloc_len;
+ }
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+
+ data = (struct scsi_receive_copy_status_lid4_data *)ctsio->kern_data_ptr;
+ scsi_ulto4b(sizeof(*data) - 4, data->available_data);
+ data->response_to_service_action = list_copy.service_action;
+ if (list_copy.completed) {
+ if (list_copy.error)
+ data->copy_command_status = RCS_CCS_ERROR;
+ else if (list_copy.abort)
+ data->copy_command_status = RCS_CCS_ABORTED;
+ else
+ data->copy_command_status = RCS_CCS_COMPLETED;
+ } else
+ data->copy_command_status = RCS_CCS_INPROG_FG;
+ scsi_ulto2b(list_copy.curops, data->operation_counter);
+ scsi_ulto4b(UINT32_MAX, data->estimated_status_update_delay);
+ if (list_copy.curbytes <= UINT32_MAX) {
+ data->transfer_count_units = RCS_TC_BYTES;
+ scsi_ulto4b(list_copy.curbytes, data->transfer_count);
+ } else {
+ data->transfer_count_units = RCS_TC_MBYTES;
+ scsi_ulto4b(list_copy.curbytes >> 20, data->transfer_count);
+ }
+ scsi_ulto2b(list_copy.curseg, data->segments_processed);
+ data->sense_data_length = list_copy.sense_len;
+ memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len);
+
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+
+ ctl_datamove((union ctl_io *)ctsio);
+ return (retval);
+}
+
+int
+ctl_copy_operation_abort(struct ctl_scsiio *ctsio)
+{
+ struct ctl_lun *lun;
+ struct scsi_copy_operation_abort *cdb;
+ struct tpc_list *list;
+ int retval;
+ uint32_t list_id;
+
+ CTL_DEBUG_PRINT(("ctl_copy_operation_abort\n"));
+
+ cdb = (struct scsi_copy_operation_abort *)ctsio->cdb;
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+
+ retval = CTL_RETVAL_COMPLETE;
+
+ list_id = scsi_4btoul(cdb->list_identifier);
+ mtx_lock(&lun->lun_lock);
+ TAILQ_FOREACH(list, &lun->tpc_lists, links) {
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE && list->list_id == list_id)
+ break;
+ }
+ if (list == NULL) {
+ mtx_unlock(&lun->lun_lock);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ ctl_done((union ctl_io *)ctsio);
+ return (retval);
+ }
+ list->abort = 1;
+ mtx_unlock(&lun->lun_lock);
+
+ ctl_set_success(ctsio);
+ ctl_done((union ctl_io *)ctsio);
+ return (retval);
+}
+
+static uint64_t
+tpc_resolve(struct tpc_list *list, uint16_t idx, uint32_t *ss)
+{
+
+ if (idx == 0xffff) {
+ if (ss && list->lun->be_lun)
+ *ss = list->lun->be_lun->blocksize;
+ return (list->lun->lun);
+ }
+ if (idx >= list->ncscd)
+ return (UINT64_MAX);
+ return (tpcl_resolve(list->init_port, &list->cscd[idx], ss));
+}
+
+static int
+tpc_process_b2b(struct tpc_list *list)
+{
+ struct scsi_ec_segment_b2b *seg;
+ struct scsi_ec_cscd_dtsp *sdstp, *ddstp;
+ struct tpc_io *tior, *tiow;
+ struct runl run, *prun;
+ uint64_t sl, dl;
+ off_t srclba, dstlba, numbytes, donebytes, roundbytes;
+ int numlba;
+ uint32_t srcblock, dstblock;
+
+ if (list->stage == 1) {
+complete:
+ while ((tior = TAILQ_FIRST(&list->allio)) != NULL) {
+ TAILQ_REMOVE(&list->allio, tior, links);
+ ctl_free_io(tior->io);
+ free(tior, M_CTL);
+ }
+ free(list->buf, M_CTL);
+ if (list->abort) {
+ ctl_set_task_aborted(list->ctsio);
+ return (CTL_RETVAL_ERROR);
+ } else if (list->error) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ } else {
+ list->curbytes += list->segbytes;
+ return (CTL_RETVAL_COMPLETE);
+ }
+ }
+
+ TAILQ_INIT(&list->allio);
+ seg = (struct scsi_ec_segment_b2b *)list->seg[list->curseg];
+ sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), &srcblock);
+ dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), &dstblock);
+ if (sl >= CTL_MAX_LUNS || dl >= CTL_MAX_LUNS) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ }
+ sdstp = &list->cscd[scsi_2btoul(seg->src_cscd)].dtsp;
+ if (scsi_3btoul(sdstp->block_length) != 0)
+ srcblock = scsi_3btoul(sdstp->block_length);
+ ddstp = &list->cscd[scsi_2btoul(seg->dst_cscd)].dtsp;
+ if (scsi_3btoul(ddstp->block_length) != 0)
+ dstblock = scsi_3btoul(ddstp->block_length);
+ numlba = scsi_2btoul(seg->number_of_blocks);
+ if (seg->flags & EC_SEG_DC)
+ numbytes = (off_t)numlba * dstblock;
+ else
+ numbytes = (off_t)numlba * srcblock;
+ srclba = scsi_8btou64(seg->src_lba);
+ dstlba = scsi_8btou64(seg->dst_lba);
+
+// printf("Copy %ju bytes from %ju @ %ju to %ju @ %ju\n",
+// (uintmax_t)numbytes, sl, scsi_8btou64(seg->src_lba),
+// dl, scsi_8btou64(seg->dst_lba));
+
+ if (numbytes == 0)
+ return (CTL_RETVAL_COMPLETE);
+
+ if (numbytes % srcblock != 0 || numbytes % dstblock != 0) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x26, /*ascq*/ 0x0A, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ }
+
+ list->buf = malloc(numbytes, M_CTL, M_WAITOK);
+ list->segbytes = numbytes;
+ donebytes = 0;
+ TAILQ_INIT(&run);
+ prun = &run;
+ list->tbdio = 1;
+ while (donebytes < numbytes) {
+ roundbytes = MIN(numbytes - donebytes, TPC_MAX_IO_SIZE);
+
+ tior = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO);
+ TAILQ_INIT(&tior->run);
+ tior->list = list;
+ TAILQ_INSERT_TAIL(&list->allio, tior, links);
+ tior->io = tpcl_alloc_io();
+ if (tior->io == NULL) {
+ list->error = 1;
+ goto complete;
+ }
+ ctl_scsi_read_write(tior->io,
+ /*data_ptr*/ &list->buf[donebytes],
+ /*data_len*/ roundbytes,
+ /*read_op*/ 1,
+ /*byte2*/ 0,
+ /*minimum_cdb_size*/ 0,
+ /*lba*/ srclba + donebytes / srcblock,
+ /*num_blocks*/ roundbytes / srcblock,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+ tior->io->io_hdr.retries = 3;
+ tior->lun = sl;
+ tior->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior;
+
+ tiow = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO);
+ TAILQ_INIT(&tiow->run);
+ tiow->list = list;
+ TAILQ_INSERT_TAIL(&list->allio, tiow, links);
+ tiow->io = tpcl_alloc_io();
+ if (tiow->io == NULL) {
+ list->error = 1;
+ goto complete;
+ }
+ ctl_scsi_read_write(tiow->io,
+ /*data_ptr*/ &list->buf[donebytes],
+ /*data_len*/ roundbytes,
+ /*read_op*/ 0,
+ /*byte2*/ 0,
+ /*minimum_cdb_size*/ 0,
+ /*lba*/ dstlba + donebytes / dstblock,
+ /*num_blocks*/ roundbytes / dstblock,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+ tiow->io->io_hdr.retries = 3;
+ tiow->lun = dl;
+ tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior;
+
+ TAILQ_INSERT_TAIL(&tior->run, tiow, rlinks);
+ TAILQ_INSERT_TAIL(prun, tior, rlinks);
+ prun = &tior->run;
+ donebytes += roundbytes;
+ }
+
+ while ((tior = TAILQ_FIRST(&run)) != NULL) {
+ TAILQ_REMOVE(&run, tior, rlinks);
+ if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE)
+ panic("tpcl_queue() error");
+ }
+
+ list->stage++;
+ return (CTL_RETVAL_QUEUED);
+}
+
+static int
+tpc_process_verify(struct tpc_list *list)
+{
+ struct scsi_ec_segment_verify *seg;
+ struct tpc_io *tio;
+ uint64_t sl;
+
+ if (list->stage == 1) {
+complete:
+ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) {
+ TAILQ_REMOVE(&list->allio, tio, links);
+ ctl_free_io(tio->io);
+ free(tio, M_CTL);
+ }
+ if (list->abort) {
+ ctl_set_task_aborted(list->ctsio);
+ return (CTL_RETVAL_ERROR);
+ } else if (list->error) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ } else
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ TAILQ_INIT(&list->allio);
+ seg = (struct scsi_ec_segment_verify *)list->seg[list->curseg];
+ sl = tpc_resolve(list, scsi_2btoul(seg->src_cscd), NULL);
+ if (sl >= CTL_MAX_LUNS) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ }
+
+// printf("Verify %ju\n", sl);
+
+ if ((seg->tur & 0x01) == 0)
+ return (CTL_RETVAL_COMPLETE);
+
+ list->tbdio = 1;
+ tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO);
+ TAILQ_INIT(&tio->run);
+ tio->list = list;
+ TAILQ_INSERT_TAIL(&list->allio, tio, links);
+ tio->io = tpcl_alloc_io();
+ if (tio->io == NULL) {
+ list->error = 1;
+ goto complete;
+ }
+ ctl_scsi_tur(tio->io, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0);
+ tio->io->io_hdr.retries = 3;
+ tio->lun = sl;
+ tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio;
+ list->stage++;
+ if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE)
+ panic("tpcl_queue() error");
+ return (CTL_RETVAL_QUEUED);
+}
+
+static int
+tpc_process_register_key(struct tpc_list *list)
+{
+ struct scsi_ec_segment_register_key *seg;
+ struct tpc_io *tio;
+ uint64_t dl;
+ int datalen;
+
+ if (list->stage == 1) {
+complete:
+ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) {
+ TAILQ_REMOVE(&list->allio, tio, links);
+ ctl_free_io(tio->io);
+ free(tio, M_CTL);
+ }
+ free(list->buf, M_CTL);
+ if (list->abort) {
+ ctl_set_task_aborted(list->ctsio);
+ return (CTL_RETVAL_ERROR);
+ } else if (list->error) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ } else
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ TAILQ_INIT(&list->allio);
+ seg = (struct scsi_ec_segment_register_key *)list->seg[list->curseg];
+ dl = tpc_resolve(list, scsi_2btoul(seg->dst_cscd), NULL);
+ if (dl >= CTL_MAX_LUNS) {
+ ctl_set_sense(list->ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_NONE);
+ return (CTL_RETVAL_ERROR);
+ }
+
+// printf("Register Key %ju\n", dl);
+
+ list->tbdio = 1;
+ tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO);
+ TAILQ_INIT(&tio->run);
+ tio->list = list;
+ TAILQ_INSERT_TAIL(&list->allio, tio, links);
+ tio->io = tpcl_alloc_io();
+ if (tio->io == NULL) {
+ list->error = 1;
+ goto complete;
+ }
+ datalen = sizeof(struct scsi_per_res_out_parms);
+ list->buf = malloc(datalen, M_CTL, M_WAITOK);
+ ctl_scsi_persistent_res_out(tio->io,
+ list->buf, datalen, SPRO_REGISTER, -1,
+ scsi_8btou64(seg->res_key), scsi_8btou64(seg->sa_res_key),
+ /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0);
+ tio->io->io_hdr.retries = 3;
+ tio->lun = dl;
+ tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio;
+ list->stage++;
+ if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE)
+ panic("tpcl_queue() error");
+ return (CTL_RETVAL_QUEUED);
+}
+
+static void
+tpc_process(struct tpc_list *list)
+{
+ struct ctl_lun *lun = list->lun;
+ struct scsi_ec_segment *seg;
+ struct ctl_scsiio *ctsio = list->ctsio;
+ int retval = CTL_RETVAL_COMPLETE;
+
+//printf("ZZZ %d cscd, %d segs\n", list->ncscd, list->nseg);
+ while (list->curseg < list->nseg) {
+ seg = list->seg[list->curseg];
+ switch (seg->type_code) {
+ case EC_SEG_B2B:
+ retval = tpc_process_b2b(list);
+ break;
+ case EC_SEG_VERIFY:
+ retval = tpc_process_verify(list);
+ break;
+ case EC_SEG_REGISTER_KEY:
+ retval = tpc_process_register_key(list);
+ break;
+ default:
+ ctl_set_sense(ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_COPY_ABORTED,
+ /*asc*/ 0x26, /*ascq*/ 0x09, SSD_ELEM_NONE);
+ goto done;
+ }
+ if (retval == CTL_RETVAL_QUEUED)
+ return;
+ if (retval == CTL_RETVAL_ERROR) {
+ list->error = 1;
+ goto done;
+ }
+ list->curseg++;
+ list->stage = 0;
+ }
+
+ ctl_set_success(ctsio);
+
+done:
+//printf("ZZZ done\n");
+ mtx_lock(&lun->lun_lock);
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) == EC_LIST_ID_USAGE_NONE) {
+ TAILQ_REMOVE(&lun->tpc_lists, list, links);
+ free(list, M_CTL);
+ } else {
+ list->completed = 1;
+ list->sense_data = ctsio->sense_data;
+ list->sense_len = ctsio->sense_len;
+ list->scsi_status = ctsio->scsi_status;
+ }
+ mtx_unlock(&lun->lun_lock);
+
+ ctl_done((union ctl_io *)ctsio);
+}
+
+/*
+ * For any sort of check condition, busy, etc., we just retry. We do not
+ * decrement the retry count for unit attention type errors. These are
+ * normal, and we want to save the retry count for "real" errors. Otherwise,
+ * we could end up with situations where a command will succeed in some
+ * situations and fail in others, depending on whether a unit attention is
+ * pending. Also, some of our error recovery actions, most notably the
+ * LUN reset action, will cause a unit attention.
+ *
+ * We can add more detail here later if necessary.
+ */
+static tpc_error_action
+tpc_checkcond_parse(union ctl_io *io)
+{
+ tpc_error_action error_action;
+ int error_code, sense_key, asc, ascq;
+
+ /*
+ * Default to retrying the command.
+ */
+ error_action = TPC_ERR_RETRY;
+
+ scsi_extract_sense_len(&io->scsiio.sense_data,
+ io->scsiio.sense_len,
+ &error_code,
+ &sense_key,
+ &asc,
+ &ascq,
+ /*show_errors*/ 1);
+
+ switch (error_code) {
+ case SSD_DEFERRED_ERROR:
+ case SSD_DESC_DEFERRED_ERROR:
+ error_action |= TPC_ERR_NO_DECREMENT;
+ break;
+ case SSD_CURRENT_ERROR:
+ case SSD_DESC_CURRENT_ERROR:
+ default:
+ switch (sense_key) {
+ case SSD_KEY_UNIT_ATTENTION:
+ error_action |= TPC_ERR_NO_DECREMENT;
+ break;
+ case SSD_KEY_HARDWARE_ERROR:
+ /*
+ * This is our generic "something bad happened"
+ * error code. It often isn't recoverable.
+ */
+ if ((asc == 0x44) && (ascq == 0x00))
+ error_action = TPC_ERR_FAIL;
+ break;
+ case SSD_KEY_NOT_READY:
+ /*
+ * If the LUN is powered down, there likely isn't
+ * much point in retrying right now.
+ */
+ if ((asc == 0x04) && (ascq == 0x02))
+ error_action = TPC_ERR_FAIL;
+ /*
+ * If the LUN is offline, there probably isn't much
+ * point in retrying, either.
+ */
+ if ((asc == 0x04) && (ascq == 0x03))
+ error_action = TPC_ERR_FAIL;
+ break;
+ }
+ }
+ return (error_action);
+}
+
+static tpc_error_action
+tpc_error_parse(union ctl_io *io)
+{
+ tpc_error_action error_action = TPC_ERR_RETRY;
+
+ switch (io->io_hdr.io_type) {
+ case CTL_IO_SCSI:
+ switch (io->io_hdr.status & CTL_STATUS_MASK) {
+ case CTL_SCSI_ERROR:
+ switch (io->scsiio.scsi_status) {
+ case SCSI_STATUS_CHECK_COND:
+ error_action = tpc_checkcond_parse(io);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case CTL_IO_TASK:
+ break;
+ default:
+ panic("%s: invalid ctl_io type %d\n", __func__,
+ io->io_hdr.io_type);
+ break;
+ }
+ return (error_action);
+}
+
+void
+tpc_done(union ctl_io *io)
+{
+ struct tpc_io *tio, *tior;
+
+ /*
+ * Very minimal retry logic. We basically retry if we got an error
+ * back, and the retry count is greater than 0. If we ever want
+ * more sophisticated initiator type behavior, the CAM error
+ * recovery code in ../common might be helpful.
+ */
+// if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)
+// ctl_io_error_print(io, NULL);
+ tio = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr;
+ if (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)
+ && (io->io_hdr.retries > 0)) {
+ ctl_io_status old_status;
+ tpc_error_action error_action;
+
+ error_action = tpc_error_parse(io);
+ switch (error_action & TPC_ERR_MASK) {
+ case TPC_ERR_FAIL:
+ break;
+ case TPC_ERR_RETRY:
+ default:
+ if ((error_action & TPC_ERR_NO_DECREMENT) == 0)
+ io->io_hdr.retries--;
+ old_status = io->io_hdr.status;
+ io->io_hdr.status = CTL_STATUS_NONE;
+ io->io_hdr.flags &= ~CTL_FLAG_ABORT;
+ io->io_hdr.flags &= ~CTL_FLAG_SENT_2OTHER_SC;
+ if (tpcl_queue(io, tio->lun) != CTL_RETVAL_COMPLETE) {
+ printf("%s: error returned from ctl_queue()!\n",
+ __func__);
+ io->io_hdr.status = old_status;
+ } else
+ return;
+ }
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)
+ tio->list->error = 1;
+ else
+ atomic_add_int(&tio->list->curops, 1);
+ if (!tio->list->error && !tio->list->abort) {
+ while ((tior = TAILQ_FIRST(&tio->run)) != NULL) {
+ TAILQ_REMOVE(&tio->run, tior, rlinks);
+ atomic_add_int(&tio->list->tbdio, 1);
+ if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE)
+ panic("tpcl_queue() error");
+ }
+ }
+ if (atomic_fetchadd_int(&tio->list->tbdio, -1) == 1)
+ tpc_process(tio->list);
+}
+
+int
+ctl_extended_copy_lid1(struct ctl_scsiio *ctsio)
+{
+ struct scsi_extended_copy *cdb;
+ struct scsi_extended_copy_lid1_data *data;
+ struct ctl_lun *lun;
+ struct tpc_list *list, *tlist;
+ uint8_t *ptr;
+ char *value;
+ int len, off, lencscd, lenseg, leninl, nseg;
+
+ CTL_DEBUG_PRINT(("ctl_extended_copy_lid1\n"));
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+ cdb = (struct scsi_extended_copy *)ctsio->cdb;
+ len = scsi_4btoul(cdb->length);
+
+ if (len < sizeof(struct scsi_extended_copy_lid1_data) ||
+ len > sizeof(struct scsi_extended_copy_lid1_data) +
+ TPC_MAX_LIST + TPC_MAX_INLINE) {
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1,
+ /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0);
+ goto done;
+ }
+
+ /*
+ * If we've got a kernel request that hasn't been malloced yet,
+ * malloc it and tell the caller the data buffer is here.
+ */
+ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) {
+ ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK);
+ ctsio->kern_data_len = len;
+ ctsio->kern_total_len = len;
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ data = (struct scsi_extended_copy_lid1_data *)ctsio->kern_data_ptr;
+ lencscd = scsi_2btoul(data->cscd_list_length);
+ lenseg = scsi_4btoul(data->segment_list_length);
+ leninl = scsi_4btoul(data->inline_data_length);
+ if (len < sizeof(struct scsi_extended_copy_lid1_data) +
+ lencscd + lenseg + leninl ||
+ leninl > TPC_MAX_INLINE) {
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0,
+ /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0);
+ goto done;
+ }
+ if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) {
+ ctl_set_sense(ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST,
+ /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE);
+ goto done;
+ }
+ if (lencscd + lenseg > TPC_MAX_LIST) {
+ ctl_set_param_len_error(ctsio);
+ goto done;
+ }
+
+ list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO);
+ list->service_action = cdb->service_action;
+ value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc");
+ if (value != NULL && strcmp(value, "on") == 0)
+ list->init_port = -1;
+ else
+ list->init_port = ctsio->io_hdr.nexus.targ_port;
+ list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus);
+ list->list_id = data->list_identifier;
+ list->flags = data->flags;
+ list->params = ctsio->kern_data_ptr;
+ list->cscd = (struct scsi_ec_cscd *)&data->data[0];
+ ptr = &data->data[lencscd];
+ for (nseg = 0, off = 0; off < lenseg; nseg++) {
+ if (nseg >= TPC_MAX_SEGS) {
+ free(list, M_CTL);
+ ctl_set_sense(ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST,
+ /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE);
+ goto done;
+ }
+ list->seg[nseg] = (struct scsi_ec_segment *)(ptr + off);
+ off += sizeof(struct scsi_ec_segment) +
+ scsi_2btoul(list->seg[nseg]->descr_length);
+ }
+ list->inl = &data->data[lencscd + lenseg];
+ list->ncscd = lencscd / sizeof(struct scsi_ec_cscd);
+ list->nseg = nseg;
+ list->leninl = leninl;
+ list->ctsio = ctsio;
+ list->lun = lun;
+ mtx_lock(&lun->lun_lock);
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) {
+ TAILQ_FOREACH(tlist, &lun->tpc_lists, links) {
+ if ((tlist->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE &&
+ tlist->list_id == list->list_id)
+ break;
+ }
+ if (tlist != NULL && !tlist->completed) {
+ mtx_unlock(&lun->lun_lock);
+ free(list, M_CTL);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ goto done;
+ }
+ if (tlist != NULL) {
+ TAILQ_REMOVE(&lun->tpc_lists, tlist, links);
+ free(tlist, M_CTL);
+ }
+ }
+ TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links);
+ mtx_unlock(&lun->lun_lock);
+
+ tpc_process(list);
+ return (CTL_RETVAL_COMPLETE);
+
+done:
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+}
+
+int
+ctl_extended_copy_lid4(struct ctl_scsiio *ctsio)
+{
+ struct scsi_extended_copy *cdb;
+ struct scsi_extended_copy_lid4_data *data;
+ struct ctl_lun *lun;
+ struct tpc_list *list, *tlist;
+ uint8_t *ptr;
+ char *value;
+ int len, off, lencscd, lenseg, leninl, nseg;
+
+ CTL_DEBUG_PRINT(("ctl_extended_copy_lid4\n"));
+
+ lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr;
+ cdb = (struct scsi_extended_copy *)ctsio->cdb;
+ len = scsi_4btoul(cdb->length);
+
+ if (len < sizeof(struct scsi_extended_copy_lid4_data) ||
+ len > sizeof(struct scsi_extended_copy_lid4_data) +
+ TPC_MAX_LIST + TPC_MAX_INLINE) {
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1,
+ /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0);
+ goto done;
+ }
+
+ /*
+ * If we've got a kernel request that hasn't been malloced yet,
+ * malloc it and tell the caller the data buffer is here.
+ */
+ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) {
+ ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK);
+ ctsio->kern_data_len = len;
+ ctsio->kern_total_len = len;
+ ctsio->kern_data_resid = 0;
+ ctsio->kern_rel_offset = 0;
+ ctsio->kern_sg_entries = 0;
+ ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED;
+ ctsio->be_move_done = ctl_config_move_done;
+ ctl_datamove((union ctl_io *)ctsio);
+
+ return (CTL_RETVAL_COMPLETE);
+ }
+
+ data = (struct scsi_extended_copy_lid4_data *)ctsio->kern_data_ptr;
+ lencscd = scsi_2btoul(data->cscd_list_length);
+ lenseg = scsi_2btoul(data->segment_list_length);
+ leninl = scsi_2btoul(data->inline_data_length);
+ if (len < sizeof(struct scsi_extended_copy_lid4_data) +
+ lencscd + lenseg + leninl ||
+ leninl > TPC_MAX_INLINE) {
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0,
+ /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0);
+ goto done;
+ }
+ if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) {
+ ctl_set_sense(ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST,
+ /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE);
+ goto done;
+ }
+ if (lencscd + lenseg > TPC_MAX_LIST) {
+ ctl_set_param_len_error(ctsio);
+ goto done;
+ }
+
+ list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO);
+ list->service_action = cdb->service_action;
+ value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc");
+ if (value != NULL && strcmp(value, "on") == 0)
+ list->init_port = -1;
+ else
+ list->init_port = ctsio->io_hdr.nexus.targ_port;
+ list->init_idx = ctl_get_resindex(&ctsio->io_hdr.nexus);
+ list->list_id = scsi_4btoul(data->list_identifier);
+ list->flags = data->flags;
+ list->params = ctsio->kern_data_ptr;
+ list->cscd = (struct scsi_ec_cscd *)&data->data[0];
+ ptr = &data->data[lencscd];
+ for (nseg = 0, off = 0; off < lenseg; nseg++) {
+ if (nseg >= TPC_MAX_SEGS) {
+ free(list, M_CTL);
+ ctl_set_sense(ctsio, /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST,
+ /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE);
+ goto done;
+ }
+ list->seg[nseg] = (struct scsi_ec_segment *)(ptr + off);
+ off += sizeof(struct scsi_ec_segment) +
+ scsi_2btoul(list->seg[nseg]->descr_length);
+ }
+ list->inl = &data->data[lencscd + lenseg];
+ list->ncscd = lencscd / sizeof(struct scsi_ec_cscd);
+ list->nseg = nseg;
+ list->leninl = leninl;
+ list->ctsio = ctsio;
+ list->lun = lun;
+ mtx_lock(&lun->lun_lock);
+ if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) {
+ TAILQ_FOREACH(tlist, &lun->tpc_lists, links) {
+ if ((tlist->flags & EC_LIST_ID_USAGE_MASK) !=
+ EC_LIST_ID_USAGE_NONE &&
+ tlist->list_id == list->list_id)
+ break;
+ }
+ if (tlist != NULL && !tlist->completed) {
+ mtx_unlock(&lun->lun_lock);
+ free(list, M_CTL);
+ ctl_set_invalid_field(ctsio, /*sks_valid*/ 1,
+ /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0,
+ /*bit*/ 0);
+ goto done;
+ }
+ if (tlist != NULL) {
+ TAILQ_REMOVE(&lun->tpc_lists, tlist, links);
+ free(tlist, M_CTL);
+ }
+ }
+ TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links);
+ mtx_unlock(&lun->lun_lock);
+
+ tpc_process(list);
+ return (CTL_RETVAL_COMPLETE);
+
+done:
+ ctl_done((union ctl_io *)ctsio);
+ return (CTL_RETVAL_COMPLETE);
+}
+