#include "qemu/bitops.h"
#include "qemu/bitmap.h"
#include "block/block_int.h"
+#include "block/qdict.h"
#include "scsi/constants.h"
#include "qemu/iov.h"
#include "qemu/option.h"
#include "qapi/qmp/qstring.h"
#include "crypto/secret.h"
#include "scsi/utils.h"
+#include "trace.h"
/* Conflict between scsi/utils.h and libiscsi! :( */
#define SCSI_XFER_NONE ISCSI_XFER_NONE
QemuMutex mutex;
struct scsi_inquiry_logical_block_provisioning lbp;
struct scsi_inquiry_block_limits bl;
+ struct scsi_inquiry_device_designator *dd;
unsigned char *zeroblock;
/* The allocmap tracks which clusters (pages) on the iSCSI target are
* allocated and which are not. In case a target returns zeros for
offset / iscsilun->cluster_size) == size);
}
+static void coroutine_fn iscsi_co_wait_for_task(IscsiTask *iTask,
+ IscsiLun *iscsilun)
+{
+ while (!iTask->complete) {
+ iscsi_set_events(iscsilun);
+ qemu_mutex_unlock(&iscsilun->mutex);
+ qemu_coroutine_yield();
+ qemu_mutex_lock(&iscsilun->mutex);
+ }
+}
+
static int coroutine_fn
-iscsi_co_writev_flags(BlockDriverState *bs, int64_t sector_num, int nb_sectors,
- QEMUIOVector *iov, int flags)
+iscsi_co_writev(BlockDriverState *bs, int64_t sector_num, int nb_sectors,
+ QEMUIOVector *iov, int flags)
{
IscsiLun *iscsilun = bs->opaque;
struct IscsiTask iTask;
scsi_task_set_iov_out(iTask.task, (struct scsi_iovec *) iov->iov,
iov->niov);
#endif
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.task != NULL) {
scsi_free_scsi_task(iTask.task);
ret = -ENOMEM;
goto out_unlock;
}
-
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.do_retry) {
if (iTask.task != NULL) {
goto out_unlock;
}
- *pnum = lbasd->num_blocks * iscsilun->block_size;
+ *pnum = (int64_t) lbasd->num_blocks * iscsilun->block_size;
if (lbasd->provisioning == SCSI_PROVISIONING_TYPE_DEALLOCATED ||
lbasd->provisioning == SCSI_PROVISIONING_TYPE_ANCHORED) {
#if LIBISCSI_API_VERSION < (20160603)
scsi_task_set_iov_in(iTask.task, (struct scsi_iovec *) iov->iov, iov->niov);
#endif
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.task != NULL) {
scsi_free_scsi_task(iTask.task);
iTask.task = NULL;
return -ENOMEM;
}
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.task != NULL) {
scsi_free_scsi_task(iTask.task);
goto out_unlock;
}
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.task != NULL) {
scsi_free_scsi_task(iTask.task);
return -ENOMEM;
}
- while (!iTask.complete) {
- iscsi_set_events(iscsilun);
- qemu_mutex_unlock(&iscsilun->mutex);
- qemu_coroutine_yield();
- qemu_mutex_lock(&iscsilun->mutex);
- }
+ iscsi_co_wait_for_task(&iTask, iscsilun);
if (iTask.status == SCSI_STATUS_CHECK_CONDITION &&
iTask.task->sense.key == SCSI_SENSE_ILLEGAL_REQUEST &&
.name = "timeout",
.type = QEMU_OPT_NUMBER,
},
- {
- .name = "filename",
- .type = QEMU_OPT_STRING,
- },
{ /* end of list */ }
},
};
+static void iscsi_save_designator(IscsiLun *lun,
+ struct scsi_inquiry_device_identification *inq_di)
+{
+ struct scsi_inquiry_device_designator *desig, *copy = NULL;
+
+ for (desig = inq_di->designators; desig; desig = desig->next) {
+ if (desig->association ||
+ desig->designator_type > SCSI_DESIGNATOR_TYPE_NAA) {
+ continue;
+ }
+ /* NAA works better than T10 vendor ID based designator. */
+ if (!copy || copy->designator_type < desig->designator_type) {
+ copy = desig;
+ }
+ }
+ if (copy) {
+ lun->dd = g_new(struct scsi_inquiry_device_designator, 1);
+ *lun->dd = *copy;
+ lun->dd->next = NULL;
+ lun->dd->designator = g_malloc(copy->designator_length);
+ memcpy(lun->dd->designator, copy->designator, copy->designator_length);
+ }
+}
+
static int iscsi_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp)
{
char *initiator_name = NULL;
QemuOpts *opts;
Error *local_err = NULL;
- const char *transport_name, *portal, *target, *filename;
+ const char *transport_name, *portal, *target;
#if LIBISCSI_API_VERSION >= (20160603)
enum iscsi_transport_type transport;
#endif
int i, ret = 0, timeout = 0, lun;
- /* If we are given a filename, parse the filename, with precedence given to
- * filename encoded options */
- filename = qdict_get_try_str(options, "filename");
- if (filename) {
- warn_report("'filename' option specified. "
- "This is an unsupported option, and may be deprecated "
- "in the future");
- iscsi_parse_filename(filename, options, &local_err);
- if (local_err) {
- ret = -EINVAL;
- error_propagate(errp, local_err);
- goto exit;
- }
- }
-
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &local_err);
if (local_err) {
struct scsi_task *inq_task;
struct scsi_inquiry_logical_block_provisioning *inq_lbp;
struct scsi_inquiry_block_limits *inq_bl;
+ struct scsi_inquiry_device_identification *inq_di;
switch (inq_vpd->pages[i]) {
case SCSI_INQUIRY_PAGECODE_LOGICAL_BLOCK_PROVISIONING:
inq_task = iscsi_do_inquiry(iscsilun->iscsi, iscsilun->lun, 1,
sizeof(struct scsi_inquiry_block_limits));
scsi_free_scsi_task(inq_task);
break;
+ case SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION:
+ inq_task = iscsi_do_inquiry(iscsilun->iscsi, iscsilun->lun, 1,
+ SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
+ (void **) &inq_di, errp);
+ if (inq_task == NULL) {
+ ret = -EINVAL;
+ goto out;
+ }
+ iscsi_save_designator(iscsilun, inq_di);
+ scsi_free_scsi_task(inq_task);
+ break;
default:
break;
}
}
memset(iscsilun, 0, sizeof(IscsiLun));
}
-exit:
+
return ret;
}
iscsi_logout_sync(iscsi);
}
iscsi_destroy_context(iscsi);
+ if (iscsilun->dd) {
+ g_free(iscsilun->dd->designator);
+ g_free(iscsilun->dd);
+ }
g_free(iscsilun->zeroblock);
iscsi_allocmap_free(iscsilun);
qemu_mutex_destroy(&iscsilun->mutex);
}
}
-static int iscsi_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
+ PreallocMode prealloc, Error **errp)
{
IscsiLun *iscsilun = bs->opaque;
Error *local_err = NULL;
iscsi_allocmap_invalidate(iscsilun);
}
+static int coroutine_fn iscsi_co_copy_range_from(BlockDriverState *bs,
+ BdrvChild *src,
+ uint64_t src_offset,
+ BdrvChild *dst,
+ uint64_t dst_offset,
+ uint64_t bytes,
+ BdrvRequestFlags read_flags,
+ BdrvRequestFlags write_flags)
+{
+ return bdrv_co_copy_range_to(src, src_offset, dst, dst_offset, bytes,
+ read_flags, write_flags);
+}
+
+static struct scsi_task *iscsi_xcopy_task(int param_len)
+{
+ struct scsi_task *task;
+
+ task = g_new0(struct scsi_task, 1);
+
+ task->cdb[0] = EXTENDED_COPY;
+ task->cdb[10] = (param_len >> 24) & 0xFF;
+ task->cdb[11] = (param_len >> 16) & 0xFF;
+ task->cdb[12] = (param_len >> 8) & 0xFF;
+ task->cdb[13] = param_len & 0xFF;
+ task->cdb_size = 16;
+ task->xfer_dir = SCSI_XFER_WRITE;
+ task->expxferlen = param_len;
+
+ return task;
+}
+
+static void iscsi_populate_target_desc(unsigned char *desc, IscsiLun *lun)
+{
+ struct scsi_inquiry_device_designator *dd = lun->dd;
+
+ memset(desc, 0, 32);
+ desc[0] = 0xE4; /* IDENT_DESCR_TGT_DESCR */
+ desc[4] = dd->code_set;
+ desc[5] = (dd->designator_type & 0xF)
+ | ((dd->association & 3) << 4);
+ desc[7] = dd->designator_length;
+ memcpy(desc + 8, dd->designator, MIN(dd->designator_length, 20));
+
+ desc[28] = 0;
+ desc[29] = (lun->block_size >> 16) & 0xFF;
+ desc[30] = (lun->block_size >> 8) & 0xFF;
+ desc[31] = lun->block_size & 0xFF;
+}
+
+static void iscsi_xcopy_desc_hdr(uint8_t *hdr, int dc, int cat, int src_index,
+ int dst_index)
+{
+ hdr[0] = 0x02; /* BLK_TO_BLK_SEG_DESCR */
+ hdr[1] = ((dc << 1) | cat) & 0xFF;
+ hdr[2] = (XCOPY_BLK2BLK_SEG_DESC_SIZE >> 8) & 0xFF;
+ /* don't account for the first 4 bytes in descriptor header*/
+ hdr[3] = (XCOPY_BLK2BLK_SEG_DESC_SIZE - 4 /* SEG_DESC_SRC_INDEX_OFFSET */) & 0xFF;
+ hdr[4] = (src_index >> 8) & 0xFF;
+ hdr[5] = src_index & 0xFF;
+ hdr[6] = (dst_index >> 8) & 0xFF;
+ hdr[7] = dst_index & 0xFF;
+}
+
+static void iscsi_xcopy_populate_desc(uint8_t *desc, int dc, int cat,
+ int src_index, int dst_index, int num_blks,
+ uint64_t src_lba, uint64_t dst_lba)
+{
+ iscsi_xcopy_desc_hdr(desc, dc, cat, src_index, dst_index);
+
+ /* The caller should verify the request size */
+ assert(num_blks < 65536);
+ desc[10] = (num_blks >> 8) & 0xFF;
+ desc[11] = num_blks & 0xFF;
+ desc[12] = (src_lba >> 56) & 0xFF;
+ desc[13] = (src_lba >> 48) & 0xFF;
+ desc[14] = (src_lba >> 40) & 0xFF;
+ desc[15] = (src_lba >> 32) & 0xFF;
+ desc[16] = (src_lba >> 24) & 0xFF;
+ desc[17] = (src_lba >> 16) & 0xFF;
+ desc[18] = (src_lba >> 8) & 0xFF;
+ desc[19] = src_lba & 0xFF;
+ desc[20] = (dst_lba >> 56) & 0xFF;
+ desc[21] = (dst_lba >> 48) & 0xFF;
+ desc[22] = (dst_lba >> 40) & 0xFF;
+ desc[23] = (dst_lba >> 32) & 0xFF;
+ desc[24] = (dst_lba >> 24) & 0xFF;
+ desc[25] = (dst_lba >> 16) & 0xFF;
+ desc[26] = (dst_lba >> 8) & 0xFF;
+ desc[27] = dst_lba & 0xFF;
+}
+
+static void iscsi_xcopy_populate_header(unsigned char *buf, int list_id, int str,
+ int list_id_usage, int prio,
+ int tgt_desc_len,
+ int seg_desc_len, int inline_data_len)
+{
+ buf[0] = list_id;
+ buf[1] = ((str & 1) << 5) | ((list_id_usage & 3) << 3) | (prio & 7);
+ buf[2] = (tgt_desc_len >> 8) & 0xFF;
+ buf[3] = tgt_desc_len & 0xFF;
+ buf[8] = (seg_desc_len >> 24) & 0xFF;
+ buf[9] = (seg_desc_len >> 16) & 0xFF;
+ buf[10] = (seg_desc_len >> 8) & 0xFF;
+ buf[11] = seg_desc_len & 0xFF;
+ buf[12] = (inline_data_len >> 24) & 0xFF;
+ buf[13] = (inline_data_len >> 16) & 0xFF;
+ buf[14] = (inline_data_len >> 8) & 0xFF;
+ buf[15] = inline_data_len & 0xFF;
+}
+
+static void iscsi_xcopy_data(struct iscsi_data *data,
+ IscsiLun *src, int64_t src_lba,
+ IscsiLun *dst, int64_t dst_lba,
+ uint16_t num_blocks)
+{
+ uint8_t *buf;
+ const int src_offset = XCOPY_DESC_OFFSET;
+ const int dst_offset = XCOPY_DESC_OFFSET + IDENT_DESCR_TGT_DESCR_SIZE;
+ const int seg_offset = dst_offset + IDENT_DESCR_TGT_DESCR_SIZE;
+
+ data->size = XCOPY_DESC_OFFSET +
+ IDENT_DESCR_TGT_DESCR_SIZE * 2 +
+ XCOPY_BLK2BLK_SEG_DESC_SIZE;
+ data->data = g_malloc0(data->size);
+ buf = data->data;
+
+ /* Initialise the parameter list header */
+ iscsi_xcopy_populate_header(buf, 1, 0, 2 /* LIST_ID_USAGE_DISCARD */,
+ 0, 2 * IDENT_DESCR_TGT_DESCR_SIZE,
+ XCOPY_BLK2BLK_SEG_DESC_SIZE,
+ 0);
+
+ /* Initialise CSCD list with one src + one dst descriptor */
+ iscsi_populate_target_desc(&buf[src_offset], src);
+ iscsi_populate_target_desc(&buf[dst_offset], dst);
+
+ /* Initialise one segment descriptor */
+ iscsi_xcopy_populate_desc(&buf[seg_offset], 0, 0, 0, 1, num_blocks,
+ src_lba, dst_lba);
+}
+
+static int coroutine_fn iscsi_co_copy_range_to(BlockDriverState *bs,
+ BdrvChild *src,
+ uint64_t src_offset,
+ BdrvChild *dst,
+ uint64_t dst_offset,
+ uint64_t bytes,
+ BdrvRequestFlags read_flags,
+ BdrvRequestFlags write_flags)
+{
+ IscsiLun *dst_lun = dst->bs->opaque;
+ IscsiLun *src_lun;
+ struct IscsiTask iscsi_task;
+ struct iscsi_data data;
+ int r = 0;
+ int block_size;
+
+ if (src->bs->drv->bdrv_co_copy_range_to != iscsi_co_copy_range_to) {
+ return -ENOTSUP;
+ }
+ src_lun = src->bs->opaque;
+
+ if (!src_lun->dd || !dst_lun->dd) {
+ return -ENOTSUP;
+ }
+ if (!is_byte_request_lun_aligned(dst_offset, bytes, dst_lun)) {
+ return -ENOTSUP;
+ }
+ if (!is_byte_request_lun_aligned(src_offset, bytes, src_lun)) {
+ return -ENOTSUP;
+ }
+ if (dst_lun->block_size != src_lun->block_size ||
+ !dst_lun->block_size) {
+ return -ENOTSUP;
+ }
+
+ block_size = dst_lun->block_size;
+ if (bytes / block_size > 65535) {
+ return -ENOTSUP;
+ }
+
+ iscsi_xcopy_data(&data,
+ src_lun, src_offset / block_size,
+ dst_lun, dst_offset / block_size,
+ bytes / block_size);
+
+ iscsi_co_init_iscsitask(dst_lun, &iscsi_task);
+
+ qemu_mutex_lock(&dst_lun->mutex);
+ iscsi_task.task = iscsi_xcopy_task(data.size);
+retry:
+ if (iscsi_scsi_command_async(dst_lun->iscsi, dst_lun->lun,
+ iscsi_task.task, iscsi_co_generic_cb,
+ &data,
+ &iscsi_task) != 0) {
+ r = -EIO;
+ goto out_unlock;
+ }
+
+ iscsi_co_wait_for_task(&iscsi_task, dst_lun);
+
+ if (iscsi_task.do_retry) {
+ iscsi_task.complete = 0;
+ goto retry;
+ }
+
+ if (iscsi_task.status != SCSI_STATUS_GOOD) {
+ r = iscsi_task.err_code;
+ goto out_unlock;
+ }
+
+out_unlock:
+
+ trace_iscsi_xcopy(src_lun, src_offset, dst_lun, dst_offset, bytes, r);
+ g_free(iscsi_task.task);
+ qemu_mutex_unlock(&dst_lun->mutex);
+ g_free(iscsi_task.err_str);
+ return r;
+}
+
static QemuOptsList iscsi_create_opts = {
.name = "iscsi-create-opts",
.head = QTAILQ_HEAD_INITIALIZER(iscsi_create_opts.head),
.bdrv_getlength = iscsi_getlength,
.bdrv_get_info = iscsi_get_info,
- .bdrv_truncate = iscsi_truncate,
+ .bdrv_co_truncate = iscsi_co_truncate,
.bdrv_refresh_limits = iscsi_refresh_limits,
.bdrv_co_block_status = iscsi_co_block_status,
.bdrv_co_pdiscard = iscsi_co_pdiscard,
+ .bdrv_co_copy_range_from = iscsi_co_copy_range_from,
+ .bdrv_co_copy_range_to = iscsi_co_copy_range_to,
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
.bdrv_co_readv = iscsi_co_readv,
- .bdrv_co_writev_flags = iscsi_co_writev_flags,
+ .bdrv_co_writev = iscsi_co_writev,
.bdrv_co_flush_to_disk = iscsi_co_flush,
#ifdef __linux__
.bdrv_getlength = iscsi_getlength,
.bdrv_get_info = iscsi_get_info,
- .bdrv_truncate = iscsi_truncate,
+ .bdrv_co_truncate = iscsi_co_truncate,
.bdrv_refresh_limits = iscsi_refresh_limits,
.bdrv_co_block_status = iscsi_co_block_status,
.bdrv_co_pdiscard = iscsi_co_pdiscard,
+ .bdrv_co_copy_range_from = iscsi_co_copy_range_from,
+ .bdrv_co_copy_range_to = iscsi_co_copy_range_to,
.bdrv_co_pwrite_zeroes = iscsi_co_pwrite_zeroes,
.bdrv_co_readv = iscsi_co_readv,
- .bdrv_co_writev_flags = iscsi_co_writev_flags,
+ .bdrv_co_writev = iscsi_co_writev,
.bdrv_co_flush_to_disk = iscsi_co_flush,
#ifdef __linux__