/*- * BSD LICENSE * * Copyright (c) Intel Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 COPYRIGHT * OWNER OR CONTRIBUTORS 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 "spdk/stdinc.h" #include "spdk/bdev.h" #include "spdk/conf.h" #include "spdk/env.h" #include "spdk/io_channel.h" #include "spdk/string.h" #include "spdk/endian.h" #include "spdk/stdinc.h" #include "spdk/util.h" #include "spdk_internal/bdev.h" #include "spdk_internal/log.h" #include #include #include #include #include #include "spdk/scsi_spec.h" #define BDEV_VIRTIO_MAX_TARGET 64 #define BDEV_VIRTIO_SCAN_PAYLOAD_SIZE 256 static int bdev_virtio_initialize(void); static void bdev_virtio_finish(void); struct virtio_scsi_io_ctx { struct virtio_req vreq; struct virtio_scsi_cmd_req req; struct virtio_scsi_cmd_resp resp; }; struct virtio_scsi_scan_base { struct virtio_dev *vdev; struct spdk_bdev_poller *scan_poller; /* Currently queried target */ unsigned target; /* Disks to be registered after the scan finishes */ TAILQ_HEAD(, virtio_scsi_disk) found_disks; struct virtio_scsi_io_ctx io_ctx; struct iovec iov; uint8_t payload[BDEV_VIRTIO_SCAN_PAYLOAD_SIZE]; }; struct virtio_scsi_disk { struct spdk_bdev bdev; struct virtio_dev *vdev; uint64_t num_blocks; uint32_t block_size; TAILQ_ENTRY(virtio_scsi_disk) link; }; struct bdev_virtio_io_channel { struct virtio_dev *vdev; struct spdk_bdev_poller *poller; }; static void scan_target(struct virtio_scsi_scan_base *base); static int bdev_virtio_get_ctx_size(void) { return sizeof(struct virtio_scsi_io_ctx); } SPDK_BDEV_MODULE_REGISTER(virtio_scsi, bdev_virtio_initialize, bdev_virtio_finish, NULL, bdev_virtio_get_ctx_size, NULL) SPDK_BDEV_MODULE_ASYNC_INIT(virtio_scsi) static void bdev_virtio_rw(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io) { struct virtio_req *vreq; struct virtio_scsi_cmd_req *req; struct virtio_scsi_cmd_resp *resp; struct virtio_scsi_disk *disk = (struct virtio_scsi_disk *)bdev_io->bdev; struct virtio_scsi_io_ctx *io_ctx = (struct virtio_scsi_io_ctx *)bdev_io->driver_ctx; bool is_read = (bdev_io->type == SPDK_BDEV_IO_TYPE_READ); vreq = &io_ctx->vreq; req = &io_ctx->req; resp = &io_ctx->resp; vreq->iov_req.iov_base = (void *)req; vreq->iov_req.iov_len = sizeof(*req); vreq->iov_resp.iov_base = (void *)resp; vreq->iov_resp.iov_len = sizeof(*resp); vreq->is_write = !is_read; memset(req, 0, sizeof(*req)); req->lun[0] = 1; req->lun[1] = 0; if (is_read) { vreq->iov = bdev_io->u.read.iovs; vreq->iovcnt = bdev_io->u.read.iovcnt; req->cdb[0] = SPDK_SBC_READ_10; to_be32(&req->cdb[2], bdev_io->u.read.offset_blocks); to_be16(&req->cdb[7], bdev_io->u.read.num_blocks); } else { vreq->iov = bdev_io->u.write.iovs; vreq->iovcnt = bdev_io->u.write.iovcnt; req->cdb[0] = SPDK_SBC_WRITE_10; to_be32(&req->cdb[2], bdev_io->u.write.offset_blocks); to_be16(&req->cdb[7], bdev_io->u.write.num_blocks); } virtio_xmit_pkts(disk->vdev->vqs[2], vreq); } static int _bdev_virtio_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io) { switch (bdev_io->type) { case SPDK_BDEV_IO_TYPE_READ: spdk_bdev_io_get_buf(bdev_io, bdev_virtio_rw); return 0; case SPDK_BDEV_IO_TYPE_WRITE: bdev_virtio_rw(ch, bdev_io); return 0; case SPDK_BDEV_IO_TYPE_RESET: spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS); return 0; case SPDK_BDEV_IO_TYPE_FLUSH: case SPDK_BDEV_IO_TYPE_UNMAP: default: return -1; } return 0; } static void bdev_virtio_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io) { if (_bdev_virtio_submit_request(ch, bdev_io) < 0) { spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED); } } static bool bdev_virtio_io_type_supported(void *ctx, enum spdk_bdev_io_type io_type) { switch (io_type) { case SPDK_BDEV_IO_TYPE_READ: case SPDK_BDEV_IO_TYPE_WRITE: case SPDK_BDEV_IO_TYPE_FLUSH: case SPDK_BDEV_IO_TYPE_RESET: case SPDK_BDEV_IO_TYPE_UNMAP: return true; default: return false; } } static struct spdk_io_channel * bdev_virtio_get_io_channel(void *ctx) { struct virtio_scsi_disk *disk = ctx; return spdk_get_io_channel(&disk->vdev); } static int bdev_virtio_destruct(void *ctx) { return 0; } static const struct spdk_bdev_fn_table virtio_fn_table = { .destruct = bdev_virtio_destruct, .submit_request = bdev_virtio_submit_request, .io_type_supported = bdev_virtio_io_type_supported, .get_io_channel = bdev_virtio_get_io_channel, }; static void get_scsi_status(struct virtio_scsi_cmd_resp *resp, int *sk, int *asc, int *ascq) { /* see spdk_scsi_task_build_sense_data() for sense data details */ *sk = 0; *asc = 0; *ascq = 0; if (resp->sense_len < 3) { return; } *sk = resp->sense[2] & 0xf; if (resp->sense_len < 13) { return; } *asc = resp->sense[12]; if (resp->sense_len < 14) { return; } *ascq = resp->sense[13]; } static void bdev_virtio_io_cpl(struct virtio_req *req) { struct virtio_scsi_io_ctx *io_ctx = SPDK_CONTAINEROF(req, struct virtio_scsi_io_ctx, vreq); struct spdk_bdev_io *bdev_io = spdk_bdev_io_from_ctx(io_ctx); int sk, asc, ascq; get_scsi_status(&io_ctx->resp, &sk, &asc, &ascq); spdk_bdev_io_complete_scsi_status(bdev_io, io_ctx->resp.status, sk, asc, ascq); } static void bdev_virtio_poll(void *arg) { struct bdev_virtio_io_channel *ch = arg; struct virtio_req *req[32]; uint16_t i, cnt; cnt = virtio_recv_pkts(ch->vdev->vqs[2], req, SPDK_COUNTOF(req)); for (i = 0; i < cnt; ++i) { bdev_virtio_io_cpl(req[i]); } } static int bdev_virtio_create_cb(void *io_device, void *ctx_buf) { struct virtio_dev **vdev = io_device; struct bdev_virtio_io_channel *ch = ctx_buf; ch->vdev = *vdev; spdk_bdev_poller_start(&ch->poller, bdev_virtio_poll, ch, spdk_env_get_current_core(), 0); return 0; } static void bdev_virtio_destroy_cb(void *io_device, void *ctx_buf) { struct bdev_virtio_io_channel *io_channel = ctx_buf; spdk_bdev_poller_stop(&io_channel->poller); } static void scan_target_finish(struct virtio_scsi_scan_base *base) { struct virtio_scsi_disk *disk; base->target++; if (base->target < BDEV_VIRTIO_MAX_TARGET) { scan_target(base); return; } spdk_bdev_poller_stop(&base->scan_poller); while ((disk = TAILQ_FIRST(&base->found_disks))) { TAILQ_REMOVE(&base->found_disks, disk, link); spdk_io_device_register(&disk->vdev, bdev_virtio_create_cb, bdev_virtio_destroy_cb, sizeof(struct bdev_virtio_io_channel)); spdk_bdev_register(&disk->bdev); } spdk_dma_free(base); spdk_bdev_module_init_done(SPDK_GET_BDEV_MODULE(virtio_scsi)); } static int process_scan_inquiry(struct virtio_scsi_scan_base *base, struct virtio_req *vreq) { struct iovec *iov = vreq->iov; struct virtio_scsi_cmd_req *req = vreq->iov_req.iov_base; struct virtio_scsi_cmd_resp *resp = vreq->iov_resp.iov_base; uint8_t lun_id; if (resp->response != VIRTIO_SCSI_S_OK || resp->status != SPDK_SCSI_STATUS_GOOD) { return -1; } lun_id = req->lun[1]; /* reuse vreq for next request */ memset(req, 0, sizeof(*req)); req->lun[0] = 1; req->lun[1] = lun_id; req->cdb[0] = SPDK_SPC_SERVICE_ACTION_IN_16; req->cdb[1] = SPDK_SBC_SAI_READ_CAPACITY_16; iov[0].iov_len = 32; to_be32(&req->cdb[10], iov[0].iov_len); virtio_xmit_pkts(base->vdev->vqs[2], vreq); return 0; } static int process_read_cap(struct virtio_scsi_scan_base *base, struct virtio_req *vreq) { struct virtio_scsi_disk *disk; struct spdk_bdev *bdev; struct virtio_scsi_cmd_req *req = vreq->iov_req.iov_base; struct virtio_scsi_cmd_resp *resp = vreq->iov_resp.iov_base; if (resp->response != VIRTIO_SCSI_S_OK || resp->status != SPDK_SCSI_STATUS_GOOD) { SPDK_ERRLOG("read capacity failed for target %"PRIu8".\n", req->lun[1]); return -1; } disk = calloc(1, sizeof(*disk)); if (disk == NULL) { SPDK_ERRLOG("could not allocate disk\n"); return -1; } disk->num_blocks = from_be64((uint64_t *)(vreq->iov[0].iov_base)) + 1; disk->block_size = from_be32((uint32_t *)(vreq->iov[0].iov_base + 8)); disk->vdev = base->vdev; bdev = &disk->bdev; bdev->name = spdk_sprintf_alloc("Virtio0"); bdev->product_name = "Virtio SCSI Disk"; bdev->write_cache = 0; bdev->blocklen = disk->block_size; bdev->blockcnt = disk->num_blocks; bdev->ctxt = disk; bdev->fn_table = &virtio_fn_table; bdev->module = SPDK_GET_BDEV_MODULE(virtio_scsi); TAILQ_INSERT_TAIL(&base->found_disks, disk, link); scan_target_finish(base); return 0; } static void process_scan_resp(struct virtio_scsi_scan_base *base, struct virtio_req *vreq) { struct virtio_scsi_cmd_req *req = vreq->iov_req.iov_base; int rc; if (vreq->iov_req.iov_len < sizeof(struct virtio_scsi_cmd_req) || vreq->iov_resp.iov_len < sizeof(struct virtio_scsi_cmd_resp)) { SPDK_ERRLOG("Received target scan message with invalid length.\n"); scan_target_finish(base); return; } switch (req->cdb[0]) { case SPDK_SPC_INQUIRY: rc = process_scan_inquiry(base, vreq); break; case SPDK_SPC_SERVICE_ACTION_IN_16: rc = process_read_cap(base, vreq); break; default: SPDK_ERRLOG("Received invalid target scan message: cdb[0] = %"PRIu8".\n", req->cdb[0]); rc = -1; break; } if (rc < 0) { scan_target_finish(base); } } static void bdev_scan_poll(void *arg) { struct virtio_scsi_scan_base *base = arg; struct virtio_req *req; uint16_t cnt; cnt = virtio_recv_pkts(base->vdev->vqs[2], &req, 1); if (cnt > 0) { process_scan_resp(base, req); } } static void scan_target(struct virtio_scsi_scan_base *base) { struct iovec *iov; struct virtio_req *vreq; struct virtio_scsi_cmd_req *req; struct virtio_scsi_cmd_resp *resp; struct spdk_scsi_cdb_inquiry *cdb; vreq = &base->io_ctx.vreq; req = &base->io_ctx.req; resp = &base->io_ctx.resp; iov = &base->iov; vreq->iov = iov; vreq->iovcnt = 1; vreq->is_write = 0; vreq->iov_req.iov_base = (void *)req; vreq->iov_req.iov_len = sizeof(*req); vreq->iov_resp.iov_base = (void *)resp; vreq->iov_resp.iov_len = sizeof(*resp); iov[0].iov_base = (void *)&base->payload; iov[0].iov_len = BDEV_VIRTIO_SCAN_PAYLOAD_SIZE; req->lun[0] = 1; req->lun[1] = base->target; cdb = (struct spdk_scsi_cdb_inquiry *)req->cdb; cdb->opcode = SPDK_SPC_INQUIRY; cdb->alloc_len[1] = 255; virtio_xmit_pkts(base->vdev->vqs[2], vreq); } static int bdev_virtio_initialize(void) { struct spdk_conf_section *sp = spdk_conf_find_section(NULL, "Virtio"); struct virtio_scsi_scan_base *base; struct virtio_dev *vdev = NULL; char *type, *path; uint32_t i; int rc = 0; if (sp == NULL) { goto out; } for (i = 0; spdk_conf_section_get_nval(sp, "Dev", i) != NULL; i++) { type = spdk_conf_section_get_nmval(sp, "Dev", i, 0); if (type == NULL) { SPDK_ERRLOG("No type specified for index %d\n", i); continue; } if (!strcmp("User", type)) { path = spdk_conf_section_get_nmval(sp, "Dev", i, 1); if (path == NULL) { SPDK_ERRLOG("No path specified for index %d\n", i); continue; } vdev = virtio_user_dev_init(path, 1, 512); } else if (!strcmp("Pci", type)) { vdev = get_pci_virtio_hw(); } else { SPDK_ERRLOG("Invalid type %s specified for index %d\n", type, i); continue; } } if (vdev == NULL) { goto out; } base = spdk_dma_zmalloc(sizeof(*base), 64, NULL); if (base == NULL) { SPDK_ERRLOG("couldn't allocate memory for scsi target scan.\n"); rc = -1; goto out; } /* TODO check rc, add virtio_dev_deinit() */ virtio_init_device(vdev, VIRTIO_PMD_DEFAULT_GUEST_FEATURES); virtio_dev_start(vdev); base->vdev = vdev; TAILQ_INIT(&base->found_disks); spdk_bdev_poller_start(&base->scan_poller, bdev_scan_poll, base, spdk_env_get_current_core(), 0); scan_target(base); return 0; out: spdk_bdev_module_init_done(SPDK_GET_BDEV_MODULE(virtio_scsi)); return rc; } static void bdev_virtio_finish(void) { } SPDK_LOG_REGISTER_TRACE_FLAG("virtio", SPDK_TRACE_VIRTIO)