diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index f4f6f816f..d30270955 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -8089,6 +8089,124 @@ Example response: } ~~~ +### vfu_virtio_scsi_add_target {#rpc_vfu_virtio_scsi_add_target} + +Add block device to specified SCSI target of vfio-user virtio-scsi PCI endpoint. + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +name | Required | string | Endpoint name +scsi_target_num | Required | number | SCSI target number +bdev_name | Required | string | Block device name + +#### Example + +Example request: + +~~~json +{ + "params": { + "name": "vfu.0", + "scsi_target_num": 0, + "bdev_name": "Malloc0" + }, + "jsonrpc": "2.0", + "method": "vfu_virtio_scsi_add_target", + "id": 1 +} +~~~ + +Example response: + +~~~json +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + +### vfu_virtio_scsi_remove_target {#rpc_vfu_virtio_scsi_remove_target} + +Remove a SCSI target of vfio-user virtio-scsi PCI endpoint. + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +name | Required | string | Endpoint name +scsi_target_num | Required | number | SCSI target number + +#### Example + +Example request: + +~~~json +{ + "params": { + "name": "vfu.0", + "scsi_target_num": 0 + }, + "jsonrpc": "2.0", + "method": "vfu_virtio_scsi_remove_target", + "id": 1 +} +~~~ + +Example response: + +~~~json +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + +### vfu_virtio_create_scsi_endpoint {#rpc_vfu_virtio_create_scsi_endpoint} + +Create vfio-user virtio-scsi PCI endpoint. + +#### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +name | Required | string | Endpoint name +cpumask | Optional | string | CPU masks +num_io_queues | Optional | number | Number of IO queues +qsize | Optional | number | Queue size +packed_ring | Optional | boolean | Enable packed ring + +#### Example + +Example request: + +~~~json +{ + "params": { + "name": "vfu.0", + "cpumask": "0x2", + "num_io_queues": 4, + "qsize": 256 + }, + "jsonrpc": "2.0", + "method": "vfu_virtio_create_scsi_endpoint", + "id": 1 +} +~~~ + +Example response: + +~~~json +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + ## Vhost Target {#jsonrpc_components_vhost_tgt} The following common preconditions need to be met in all target types. diff --git a/mk/spdk.lib_deps.mk b/mk/spdk.lib_deps.mk index 237f0ac1c..936c2b6e0 100644 --- a/mk/spdk.lib_deps.mk +++ b/mk/spdk.lib_deps.mk @@ -164,5 +164,5 @@ DEPDIRS-event_vfu_tgt := init vfu_tgt # module/vfu_device ifeq ($(CONFIG_VFIO_USER),y) -DEPDIRS-vfu_device := $(BDEV_DEPS_THREAD) vfu_tgt +DEPDIRS-vfu_device := $(BDEV_DEPS_THREAD) scsi vfu_tgt endif diff --git a/module/vfu_device/Makefile b/module/vfu_device/Makefile index 48fab3c99..ff16188a8 100644 --- a/module/vfu_device/Makefile +++ b/module/vfu_device/Makefile @@ -9,7 +9,7 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk SO_VER := 1 SO_MINOR := 0 -C_SRCS = vfu_virtio.c vfu_virtio_blk.c vfu_virtio_rpc.c +C_SRCS = vfu_virtio.c vfu_virtio_blk.c vfu_virtio_scsi.c vfu_virtio_rpc.c LIBNAME = vfu_device SPDK_MAP_FILE = $(SPDK_ROOT_DIR)/mk/spdk_blank.map diff --git a/module/vfu_device/vfu_virtio_internal.h b/module/vfu_device/vfu_virtio_internal.h index dfc9810f5..e6c963018 100644 --- a/module/vfu_device/vfu_virtio_internal.h +++ b/module/vfu_device/vfu_virtio_internal.h @@ -399,5 +399,10 @@ int vfu_virtio_pre_memory_remove(struct spdk_vfu_endpoint *endpoint, void *map_s int vfu_virtio_pci_reset_cb(struct spdk_vfu_endpoint *endpoint); int vfu_virtio_blk_add_bdev(const char *name, const char *bdev_name, uint16_t num_queues, uint16_t qsize, bool packed_ring); - +/* virtio_scsi */ +int vfu_virtio_scsi_add_target(const char *name, uint8_t scsi_target_num, + const char *bdev_name); +int vfu_virtio_scsi_remove_target(const char *name, uint8_t scsi_target_num); +int vfu_virtio_scsi_set_options(const char *name, uint16_t num_io_queues, uint16_t qsize, + bool packed_ring); #endif diff --git a/module/vfu_device/vfu_virtio_rpc.c b/module/vfu_device/vfu_virtio_rpc.c index 79702e2aa..c3803de2e 100644 --- a/module/vfu_device/vfu_virtio_rpc.c +++ b/module/vfu_device/vfu_virtio_rpc.c @@ -124,3 +124,164 @@ invalid: } SPDK_RPC_REGISTER("vfu_virtio_create_blk_endpoint", rpc_vfu_virtio_create_blk_endpoint, SPDK_RPC_RUNTIME) + +struct rpc_vfu_virtio_scsi { + char *name; + uint8_t scsi_target_num; + char *bdev_name; +}; + +static const struct spdk_json_object_decoder rpc_construct_vfu_virtio_scsi[] = { + {"name", offsetof(struct rpc_vfu_virtio_scsi, name), spdk_json_decode_string }, + {"scsi_target_num", offsetof(struct rpc_vfu_virtio_scsi, scsi_target_num), spdk_json_decode_uint8 }, + {"bdev_name", offsetof(struct rpc_vfu_virtio_scsi, bdev_name), spdk_json_decode_string }, +}; + +static void +free_rpc_vfu_virtio_scsi(struct rpc_vfu_virtio_scsi *req) +{ + free(req->name); + free(req->bdev_name); +} + +static void +rpc_vfu_virtio_scsi_add_target(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_vfu_virtio_scsi req = {0}; + int rc; + + if (spdk_json_decode_object(params, rpc_construct_vfu_virtio_scsi, + SPDK_COUNTOF(rpc_construct_vfu_virtio_scsi), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + rc = -EINVAL; + goto invalid; + } + + rc = vfu_virtio_scsi_add_target(req.name, req.scsi_target_num, req.bdev_name);; + if (rc < 0) { + goto invalid; + } + + free_rpc_vfu_virtio_scsi(&req); + spdk_jsonrpc_send_bool_response(request, true); + return; + +invalid: + free_rpc_vfu_virtio_scsi(&req); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + spdk_strerror(-rc)); +} +SPDK_RPC_REGISTER("vfu_virtio_scsi_add_target", rpc_vfu_virtio_scsi_add_target, + SPDK_RPC_RUNTIME) + +struct rpc_vfu_virtio_scsi_remove { + char *name; + uint8_t scsi_target_num; +}; + +static const struct spdk_json_object_decoder rpc_remove_vfu_virtio_scsi_target[] = { + {"name", offsetof(struct rpc_vfu_virtio_scsi_remove, name), spdk_json_decode_string }, + {"scsi_target_num", offsetof(struct rpc_vfu_virtio_scsi_remove, scsi_target_num), spdk_json_decode_uint8 }, +}; + +static void +free_rpc_vfu_virtio_scsi_remove(struct rpc_vfu_virtio_scsi_remove *req) +{ + free(req->name); +} + +static void +rpc_vfu_virtio_scsi_remove_target(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_vfu_virtio_scsi_remove req = {0}; + int rc; + + if (spdk_json_decode_object(params, rpc_remove_vfu_virtio_scsi_target, + SPDK_COUNTOF(rpc_remove_vfu_virtio_scsi_target), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + rc = -EINVAL; + goto invalid; + } + + rc = vfu_virtio_scsi_remove_target(req.name, req.scsi_target_num); + if (rc < 0) { + goto invalid; + } + + free_rpc_vfu_virtio_scsi_remove(&req); + spdk_jsonrpc_send_bool_response(request, true); + return; + +invalid: + free_rpc_vfu_virtio_scsi_remove(&req); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + spdk_strerror(-rc)); +} +SPDK_RPC_REGISTER("vfu_virtio_scsi_remove_target", rpc_vfu_virtio_scsi_remove_target, + SPDK_RPC_RUNTIME) + +struct rpc_vfu_virtio_create_scsi { + char *name; + char *cpumask; + uint16_t num_io_queues; + uint16_t qsize; + bool packed_ring; +}; + +static const struct spdk_json_object_decoder rpc_construct_vfu_virtio_create_scsi[] = { + {"name", offsetof(struct rpc_vfu_virtio_create_scsi, name), spdk_json_decode_string }, + {"cpumask", offsetof(struct rpc_vfu_virtio_create_scsi, cpumask), spdk_json_decode_string, true}, + {"num_io_queues", offsetof(struct rpc_vfu_virtio_create_scsi, num_io_queues), spdk_json_decode_uint16, true }, + {"qsize", offsetof(struct rpc_vfu_virtio_create_scsi, qsize), spdk_json_decode_uint16, true }, + {"packed_ring", offsetof(struct rpc_vfu_virtio_create_scsi, packed_ring), spdk_json_decode_bool, true}, +}; + +static void +free_rpc_vfu_virtio_create_scsi(struct rpc_vfu_virtio_create_scsi *req) +{ + free(req->name); + free(req->cpumask); +} + +static void +rpc_vfu_virtio_create_scsi_endpoint(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_vfu_virtio_create_scsi req = {0}; + int rc; + + if (spdk_json_decode_object(params, rpc_construct_vfu_virtio_create_scsi, + SPDK_COUNTOF(rpc_construct_vfu_virtio_create_scsi), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + rc = -EINVAL; + goto invalid; + } + + rc = spdk_vfu_create_endpoint(req.name, req.cpumask, "virtio_scsi"); + if (rc) { + SPDK_ERRLOG("Failed to create virtio_blk endpoint\n"); + goto invalid; + } + + rc = vfu_virtio_scsi_set_options(req.name, req.num_io_queues, req.qsize, req.packed_ring); + if (rc < 0) { + spdk_vfu_delete_endpoint(req.name); + goto invalid; + } + free_rpc_vfu_virtio_create_scsi(&req); + + spdk_jsonrpc_send_bool_response(request, true); + return; + +invalid: + free_rpc_vfu_virtio_create_scsi(&req); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + spdk_strerror(-rc)); +} +SPDK_RPC_REGISTER("vfu_virtio_create_scsi_endpoint", rpc_vfu_virtio_create_scsi_endpoint, + SPDK_RPC_RUNTIME) diff --git a/module/vfu_device/vfu_virtio_scsi.c b/module/vfu_device/vfu_virtio_scsi.c new file mode 100644 index 000000000..6093fc7ed --- /dev/null +++ b/module/vfu_device/vfu_virtio_scsi.c @@ -0,0 +1,1037 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) Intel Corporation. + * All rights reserved. + */ + +/* + * virtio-scsi over vfio-user transport + */ +#include + +#include "spdk/stdinc.h" +#include "spdk/env.h" +#include "spdk/bdev.h" +#include "spdk/bdev_module.h" +#include "spdk/assert.h" +#include "spdk/barrier.h" +#include "spdk/thread.h" +#include "spdk/memory.h" +#include "spdk/util.h" +#include "spdk/log.h" +#include "spdk/string.h" +#include "spdk/likely.h" +#include "spdk/scsi.h" +#include "spdk/scsi_spec.h" +#include "spdk/pci_ids.h" + +#include "vfu_virtio_internal.h" + +#define VIRTIO_SCSI_SUPPORTED_FEATURES ((1ULL << VIRTIO_SCSI_F_INOUT) | \ + (1ULL << VIRTIO_SCSI_F_HOTPLUG) | \ + (1ULL << VIRTIO_SCSI_F_CHANGE)) + +#define VIRTIO_SCSI_CTRLR_MAX_TARGETS (8) + +struct virtio_scsi_target { + struct spdk_scsi_dev *dev; +}; + +struct virtio_scsi_endpoint { + struct vfu_virtio_endpoint virtio; + + struct virtio_scsi_config scsi_cfg; + /* virtio_scsi specific configurations */ + struct virtio_scsi_target targets[VIRTIO_SCSI_CTRLR_MAX_TARGETS]; + /* virtio_scsi SCSI task and IO ring process poller */ + struct spdk_poller *ring_poller; +}; + +struct virtio_scsi_req { + struct spdk_scsi_task scsi; + union { + struct virtio_scsi_cmd_req *cmd_req; + struct virtio_scsi_ctrl_tmf_req *tmf_req; + }; + union { + struct virtio_scsi_cmd_resp *cmd_resp; + struct virtio_scsi_ctrl_tmf_resp *tmf_resp; + }; + struct virtio_scsi_endpoint *endpoint; + /* KEEP req at last */ + struct vfu_virtio_req req; +}; + +static inline struct virtio_scsi_endpoint * +to_scsi_endpoint(struct vfu_virtio_endpoint *virtio_endpoint) +{ + return SPDK_CONTAINEROF(virtio_endpoint, struct virtio_scsi_endpoint, virtio); +} + +static inline struct virtio_scsi_req * +to_scsi_request(struct vfu_virtio_req *request) +{ + return SPDK_CONTAINEROF(request, struct virtio_scsi_req, req); +} + +static void +virtio_scsi_req_finish(struct virtio_scsi_req *scsi_req) +{ + struct vfu_virtio_req *req = &scsi_req->req; + + vfu_virtio_finish_req(req); +} + +static int +vfu_virtio_scsi_vring_poll(void *ctx) +{ + struct virtio_scsi_endpoint *scsi_endpoint = ctx; + struct vfu_virtio_dev *dev = scsi_endpoint->virtio.dev; + struct vfu_virtio_vq *vq; + uint32_t i, count = 0; + + if (spdk_unlikely(!virtio_dev_is_started(dev))) { + return SPDK_POLLER_IDLE; + } + + if (spdk_unlikely(scsi_endpoint->virtio.quiesce_in_progress)) { + return SPDK_POLLER_IDLE; + } + + /* We don't process event queue here */ + for (i = 0; i < dev->num_queues; i++) { + if (i == 1) { + continue; + } + + vq = &dev->vqs[i]; + if (!vq->enabled || vq->q_state != VFU_VQ_ACTIVE) { + continue; + } + + vfu_virtio_vq_flush_irq(dev, vq); + + if (vq->packed.packed_ring) { + /* packed vring */ + count += vfu_virito_dev_process_packed_ring(dev, vq); + } else { + /* split vring */ + count += vfu_virito_dev_process_split_ring(dev, vq); + } + } + + return count ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE; +} + +static void +vfu_virtio_scsi_eventq_enqueue(struct virtio_scsi_endpoint *scsi_endpoint, uint8_t scsi_target_num, + uint32_t event, uint32_t reason) +{ + struct vfu_virtio_dev *dev = scsi_endpoint->virtio.dev; + struct vfu_virtio_req *req = NULL; + struct virtio_scsi_req *scsi_req; + struct virtio_scsi_event *desc_ev; + struct vfu_virtio_vq *vq; + + assert(dev != NULL); + + if (scsi_target_num >= VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + return; + } + + if (spdk_unlikely(scsi_endpoint->virtio.quiesce_in_progress)) { + return; + } + + /* event queue */ + vq = &dev->vqs[1]; + if (!vq->enabled || vq->q_state != VFU_VQ_ACTIVE) { + return; + } + + if (vq->packed.packed_ring) { + /* packed vring */ + req = virito_dev_packed_ring_get_next_avail_req(dev, vq); + } else { + /* split vring */ + req = virito_dev_split_ring_get_next_avail_req(dev, vq); + } + + if (!req) { + return; + } + scsi_req = to_scsi_request(req); + scsi_req->endpoint = scsi_endpoint; + /* add 1 for scsi event */ + scsi_endpoint->virtio.io_outstanding++; + + assert(req->iovcnt == 1); + assert(req->iovs[0].iov_len == sizeof(struct virtio_scsi_event)); + desc_ev = req->iovs[0].iov_base; + + desc_ev->event = event; + desc_ev->lun[0] = 1; + desc_ev->lun[1] = scsi_target_num; + /* virtio LUN id 0 can refer either to the entire device + * or actual LUN 0 (the only supported by vhost for now) + */ + desc_ev->lun[2] = 0 >> 8; + desc_ev->lun[3] = 0 & 0xFF; + /* virtio doesn't specify any strict format for LUN id (bytes 2 and 3) + * current implementation relies on linux kernel sources + */ + memset(&desc_ev->lun[4], 0, 4); + desc_ev->reason = reason; + + req->used_len = sizeof(*desc_ev); + + SPDK_DEBUGLOG(vfu_virtio_scsi, "%s: SCSI Target Num %u, Desc %p, Event %u, Reason %u\n", + spdk_vfu_get_endpoint_name(scsi_endpoint->virtio.endpoint), scsi_target_num, desc_ev, event, + reason); + + virtio_scsi_req_finish(scsi_req); + vfu_virtio_vq_flush_irq(dev, vq); +} + +static int +virtio_scsi_start(struct vfu_virtio_endpoint *virtio_endpoint) +{ + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + struct virtio_scsi_target *scsi_target; + uint8_t i; + int ret; + + if (scsi_endpoint->ring_poller) { + return 0; + } + + SPDK_DEBUGLOG(vfu_virtio_scsi, "starting %s\n", + spdk_vfu_get_endpoint_name(scsi_endpoint->virtio.endpoint)); + + for (i = 0; i < VIRTIO_SCSI_CTRLR_MAX_TARGETS; i++) { + scsi_target = &scsi_endpoint->targets[i]; + if (scsi_target->dev) { + ret = spdk_scsi_dev_allocate_io_channels(scsi_target->dev); + if (ret) { + SPDK_ERRLOG("%s: Couldn't allocate io channel for SCSI target %u.\n", + spdk_vfu_get_endpoint_name(scsi_endpoint->virtio.endpoint), i); + continue; + } + } + } + + scsi_endpoint->ring_poller = SPDK_POLLER_REGISTER(vfu_virtio_scsi_vring_poll, scsi_endpoint, + 0); + + return 0; +} + +static int +virtio_scsi_stop(struct vfu_virtio_endpoint *virtio_endpoint) +{ + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + struct virtio_scsi_target *scsi_target; + uint8_t i; + + SPDK_DEBUGLOG(vfu_virtio_scsi, "stopping %s\n", + spdk_vfu_get_endpoint_name(scsi_endpoint->virtio.endpoint)); + + spdk_poller_unregister(&scsi_endpoint->ring_poller); + + for (i = 0; i < VIRTIO_SCSI_CTRLR_MAX_TARGETS; i++) { + scsi_target = &scsi_endpoint->targets[i]; + if (scsi_target->dev) { + spdk_scsi_dev_free_io_channels(scsi_target->dev); + } + } + + return 0; +} + +static void +virtio_scsi_task_cpl(struct spdk_scsi_task *scsi_task) +{ + struct virtio_scsi_req *scsi_req = SPDK_CONTAINEROF(scsi_task, struct virtio_scsi_req, scsi); + + scsi_req->cmd_resp->status = scsi_task->status; + if (scsi_task->status != SPDK_SCSI_STATUS_GOOD) { + scsi_req->cmd_resp->sense_len = scsi_task->sense_data_len; + memcpy(scsi_req->cmd_resp->sense, scsi_task->sense_data, scsi_task->sense_data_len); + } + assert(scsi_task->transfer_len == scsi_task->length); + scsi_req->cmd_resp->resid = scsi_task->length - scsi_task->data_transferred; + + virtio_scsi_req_finish(scsi_req); + spdk_scsi_task_put(scsi_task); +} + +static void +virtio_scsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task) +{ + struct virtio_scsi_req *scsi_req = SPDK_CONTAINEROF(scsi_task, struct virtio_scsi_req, scsi); + + virtio_scsi_req_finish(scsi_req); + spdk_scsi_task_put(scsi_task); +} + +static void +virtio_scsi_task_free_cb(struct spdk_scsi_task *scsi_task) +{ + +} + +static struct virtio_scsi_target * +virtio_scsi_cmd_lun_setup(struct virtio_scsi_endpoint *scsi_endpoint, + struct virtio_scsi_req *scsi_req, __u8 *lun) +{ + struct virtio_scsi_target *scsi_target; + uint16_t lun_id = (((uint16_t)lun[2] << 8) | lun[3]) & 0x3FFF; + + SPDK_LOGDUMP(vfu_virtio_scsi_data, "LUN", lun, 8); + + /* First byte must be 1 and second is target */ + if (lun[0] != 1 || lun[1] >= VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + SPDK_DEBUGLOG(vfu_virtio_scsi, "Invalid LUN %u:%u\n", lun[0], lun[1]); + return NULL; + } + + scsi_target = &scsi_endpoint->targets[lun[1]]; + if (!scsi_target->dev) { + SPDK_DEBUGLOG(vfu_virtio_scsi, "SCSI Target num %u doesn't exist\n", lun[1]); + return NULL; + } + + scsi_req->scsi.target_port = spdk_scsi_dev_find_port_by_id(scsi_target->dev, 0); + scsi_req->scsi.lun = spdk_scsi_dev_get_lun(scsi_target->dev, lun_id); + if (scsi_req->scsi.lun == NULL) { + SPDK_DEBUGLOG(vfu_virtio_scsi, "LUN %u:%u doesn't exist\n", lun[0], lun[1]); + return NULL; + } + SPDK_DEBUGLOG(vfu_virtio_scsi, "Got valid SCSI Target num %u, bdev %s\n", lun[1], + spdk_scsi_lun_get_bdev_name(scsi_req->scsi.lun)); + + return scsi_target; +} + +static int +virtio_scsi_cmd_data_setup(struct virtio_scsi_req *scsi_req) +{ + struct iovec *iov; + uint32_t iovcnt; + uint32_t payload_len; + + iov = &scsi_req->req.iovs[0]; + iovcnt = scsi_req->req.iovcnt; + payload_len = scsi_req->req.payload_size; + + if (spdk_unlikely(iov->iov_len < sizeof(struct virtio_scsi_cmd_req))) { + SPDK_ERRLOG("Invalid virtio_scsi command header length"); + return -EINVAL; + } + if (spdk_unlikely(iovcnt < 2)) { + SPDK_ERRLOG("Invalid iovcnt %u\n", iovcnt); + return -EINVAL; + } + + scsi_req->cmd_req = scsi_req->req.iovs[0].iov_base; + payload_len -= scsi_req->req.iovs[0].iov_len; + + /* + * FROM_DEV (READ): [RO_req][WR_resp][WR_buf0]...[WR_bufN] + * TO_DEV (WRITE): [RO_req][RO_buf0]...[RO_bufN][WR_resp] + */ + if (virtio_req_iov_is_wr(&scsi_req->req, 1)) { + scsi_req->scsi.dxfer_dir = SPDK_SCSI_DIR_FROM_DEV; + } else { + scsi_req->scsi.dxfer_dir = SPDK_SCSI_DIR_TO_DEV; + } + + if (scsi_req->scsi.dxfer_dir == SPDK_SCSI_DIR_FROM_DEV) { + if (scsi_req->req.iovs[1].iov_len < sizeof(struct virtio_scsi_cmd_resp)) { + SPDK_ERRLOG("DIR_FROM_DEV: Invalid virtio_scsi command resp length"); + return -EINVAL; + } + scsi_req->cmd_resp = scsi_req->req.iovs[1].iov_base; + scsi_req->req.used_len = payload_len; + scsi_req->scsi.iovs = &scsi_req->req.iovs[2]; + } else { + if (scsi_req->req.iovs[iovcnt - 1].iov_len < sizeof(struct virtio_scsi_cmd_resp)) { + SPDK_ERRLOG("DIR_TO_DEV: Invalid virtio_scsi command resp length"); + return -EINVAL; + } + scsi_req->req.used_len = sizeof(struct virtio_scsi_cmd_resp); + scsi_req->cmd_resp = scsi_req->req.iovs[iovcnt - 1].iov_base; + scsi_req->scsi.iovs = &scsi_req->req.iovs[1]; + } + + /* -2 for REQ and RESP */ + iovcnt -= 2; + if (!iovcnt) { + scsi_req->scsi.length = 0; + scsi_req->scsi.transfer_len = 0; + scsi_req->scsi.iovs[0].iov_len = 0; + } else { + assert(payload_len > sizeof(struct virtio_scsi_cmd_resp)); + payload_len -= sizeof(struct virtio_scsi_cmd_resp); + scsi_req->scsi.length = payload_len; + scsi_req->scsi.transfer_len = payload_len; + } + scsi_req->scsi.iovcnt = iovcnt; + scsi_req->scsi.cdb = scsi_req->cmd_req->cdb; + scsi_req->cmd_resp->response = VIRTIO_SCSI_S_OK; + + SPDK_LOGDUMP(vfu_virtio_scsi_data, "CDB=", scsi_req->cmd_req->cdb, VIRTIO_SCSI_CDB_SIZE); + SPDK_DEBUGLOG(vfu_virtio_scsi, "%s, iovcnt %u, transfer_len %u, used len %u\n", + scsi_req->scsi.dxfer_dir == SPDK_SCSI_DIR_FROM_DEV ? "XFER_FROM_DEV" : "XFER_TO_DEV", + scsi_req->scsi.iovcnt, payload_len, scsi_req->req.used_len); + + return 0; +} + +static int +virtio_scsi_tmf_cmd_req(struct virtio_scsi_endpoint *scsi_endpoint, + struct virtio_scsi_req *scsi_req) +{ + uint32_t iovcnt; + struct iovec *iov; + struct virtio_scsi_ctrl_tmf_req *tmf_req; + struct virtio_scsi_target *scsi_target; + + iov = &scsi_req->req.iovs[0]; + iovcnt = scsi_req->req.iovcnt; + tmf_req = iov->iov_base; + if (spdk_unlikely(iovcnt < 2)) { + SPDK_ERRLOG("Invalid iovcnt %u\n", iovcnt); + goto invalid; + } + + memset(&scsi_req->scsi, 0, sizeof(struct spdk_scsi_task)); + spdk_scsi_task_construct(&scsi_req->scsi, virtio_scsi_task_mgmt_cpl, virtio_scsi_task_free_cb); + + switch (tmf_req->type) { + case VIRTIO_SCSI_T_TMF: + if (scsi_req->req.iovs[0].iov_len < sizeof(struct virtio_scsi_ctrl_tmf_req) || + scsi_req->req.iovs[1].iov_len < sizeof(struct virtio_scsi_ctrl_tmf_resp)) { + SPDK_ERRLOG("Invalid size of tmf_req or tmf_resp\n"); + goto invalid; + } + scsi_req->tmf_req = tmf_req; + scsi_req->tmf_resp = scsi_req->req.iovs[1].iov_base; + switch (tmf_req->subtype) { + case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET: + scsi_target = virtio_scsi_cmd_lun_setup(scsi_endpoint, scsi_req, scsi_req->tmf_req->lun); + if (!scsi_target) { + scsi_req->tmf_resp->response = VIRTIO_SCSI_S_BAD_TARGET; + break; + } + /* Management task submission */ + scsi_req->tmf_resp->response = VIRTIO_SCSI_S_OK; + scsi_req->scsi.function = SPDK_SCSI_TASK_FUNC_LUN_RESET; + spdk_scsi_dev_queue_mgmt_task(scsi_target->dev, &scsi_req->scsi); + return 0; + break; + default: + scsi_req->tmf_resp->response = VIRTIO_SCSI_S_FUNCTION_REJECTED; + break; + } + break; + + case VIRTIO_SCSI_T_AN_QUERY: + case VIRTIO_SCSI_T_AN_SUBSCRIBE: + if (scsi_req->req.iovs[0].iov_len < sizeof(struct virtio_scsi_ctrl_an_req) || + scsi_req->req.iovs[1].iov_len < sizeof(struct virtio_scsi_ctrl_an_resp)) { + SPDK_ERRLOG("Invalid size of tmf_req or tmf_resp\n"); + goto invalid; + } + scsi_req->req.used_len = sizeof(struct virtio_scsi_ctrl_an_resp); + /* Do nothing to response byte of virtio_scsi_ctrl_an_resp */ + goto invalid; + break; + default: + break; + } + +invalid: + /* invalid request */ + virtio_scsi_req_finish(scsi_req); + return -1; +} + +static int +virtio_scsi_cmd_req(struct virtio_scsi_endpoint *scsi_endpoint, struct virtio_scsi_req *scsi_req) +{ + int ret; + struct virtio_scsi_target *scsi_target; + + memset(&scsi_req->scsi, 0, sizeof(struct spdk_scsi_task)); + spdk_scsi_task_construct(&scsi_req->scsi, virtio_scsi_task_cpl, virtio_scsi_task_free_cb); + + ret = virtio_scsi_cmd_data_setup(scsi_req); + if (ret) { + SPDK_ERRLOG("Error to setup SCSI command, ret %d\n", ret); + goto invalid; + } + + scsi_target = virtio_scsi_cmd_lun_setup(scsi_endpoint, scsi_req, scsi_req->cmd_req->lun); + if (!scsi_target) { + scsi_req->cmd_resp->response = VIRTIO_SCSI_S_BAD_TARGET; + goto invalid; + } + + spdk_scsi_dev_queue_task(scsi_target->dev, &scsi_req->scsi); + return 0; + +invalid: + /* invalid request */ + virtio_scsi_req_finish(scsi_req); + return ret; +} + +static int +virtio_scsi_process_req(struct vfu_virtio_endpoint *virtio_endpoint, struct vfu_virtio_vq *vq, + struct vfu_virtio_req *req) +{ + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + struct virtio_scsi_req *scsi_req = to_scsi_request(req); + + scsi_req->endpoint = scsi_endpoint; + + /* SCSI task management command */ + if (spdk_unlikely(vq->id == 0)) { + return virtio_scsi_tmf_cmd_req(scsi_endpoint, scsi_req); + } + + /* SCSI command */ + return virtio_scsi_cmd_req(scsi_endpoint, scsi_req);; +} + +static void +virtio_scsi_update_config(struct virtio_scsi_endpoint *scsi_endpoint) +{ + struct virtio_scsi_config *scsi_cfg; + + if (!scsi_endpoint) { + return; + } + + scsi_cfg = &scsi_endpoint->scsi_cfg; + + scsi_cfg->num_queues = scsi_endpoint->virtio.num_queues; + /* -2 for REQ and RESP and -1 for region boundary splitting */ + scsi_cfg->seg_max = spdk_min(VIRTIO_DEV_MAX_IOVS - 2 - 1, BDEV_IO_NUM_CHILD_IOV - 2 - 1); + /* we can set `max_sectors` and `cmd_per_lun` based on bdevs */ + scsi_cfg->max_sectors = 131072; + scsi_cfg->cmd_per_lun = scsi_endpoint->virtio.qsize; + scsi_cfg->event_info_size = sizeof(struct virtio_scsi_event); + scsi_cfg->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE; + scsi_cfg->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE; + scsi_cfg->max_channel = 0; + scsi_cfg->max_target = VIRTIO_SCSI_CTRLR_MAX_TARGETS; + scsi_cfg->max_lun = 16383; +} + +static uint64_t +virtio_scsi_get_supported_features(struct vfu_virtio_endpoint *virtio_endpoint) +{ + uint64_t features; + + features = VIRTIO_SCSI_SUPPORTED_FEATURES | VIRTIO_HOST_SUPPORTED_FEATURES; + + if (!virtio_endpoint->packed_ring) { + features &= ~(1ULL << VIRTIO_F_RING_PACKED); + } + + return features; +} + +static int +virtio_scsi_get_device_specific_config(struct vfu_virtio_endpoint *virtio_endpoint, char *buf, + uint64_t offset, uint64_t count) +{ + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + uint8_t *scsi_cfg; + + if ((offset + count) > sizeof(struct virtio_scsi_config)) { + SPDK_ERRLOG("Invalid device specific configuration offset 0x%"PRIx64"\n", offset); + return -EINVAL; + } + + scsi_cfg = (uint8_t *)&scsi_endpoint->scsi_cfg; + memcpy(buf, scsi_cfg + offset, count); + + return 0; +} + +static int +virtio_scsi_set_device_specific_config(struct vfu_virtio_endpoint *virtio_endpoint, char *buf, + uint64_t offset, uint64_t count) +{ + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + uint32_t value; + int ret = 0; + + if ((offset + count) > sizeof(struct virtio_scsi_config)) { + SPDK_ERRLOG("Invalid device specific configuration offset 0x%"PRIx64"\n", offset); + return -EINVAL; + } + + switch (offset) { + case offsetof(struct virtio_scsi_config, sense_size): + value = *(uint32_t *)buf; + if (scsi_endpoint->scsi_cfg.sense_size != value) { + SPDK_ERRLOG("Sense data size set to %u\n", value); + ret = -ENOTSUP; + } + break; + case offsetof(struct virtio_scsi_config, cdb_size): + value = *(uint32_t *)buf; + if (scsi_endpoint->scsi_cfg.cdb_size != value) { + SPDK_ERRLOG("CDB size set to %u\n", value); + ret = -ENOTSUP; + } + break; + default: + SPDK_ERRLOG("Error offset %"PRIu64"\n", offset); + ret = -EINVAL; + break; + } + + + return ret; +} + +static struct vfu_virtio_req * +virtio_scsi_alloc_req(struct vfu_virtio_endpoint *virtio_endpoint, struct vfu_virtio_vq *vq) +{ + struct virtio_scsi_req *scsi_req; + + scsi_req = calloc(1, sizeof(*scsi_req) + dma_sg_size() * (VIRTIO_DEV_MAX_IOVS + 1)); + if (!scsi_req) { + return NULL; + } + + return &scsi_req->req; +} + +static void +virtio_scsi_free_req(struct vfu_virtio_endpoint *virtio_endpoint, struct vfu_virtio_vq *vq, + struct vfu_virtio_req *req) +{ + struct virtio_scsi_req *scsi_req = to_scsi_request(req); + + free(scsi_req); +} + +struct vfu_virtio_ops virtio_scsi_ops = { + .get_device_features = virtio_scsi_get_supported_features, + .alloc_req = virtio_scsi_alloc_req, + .free_req = virtio_scsi_free_req, + .exec_request = virtio_scsi_process_req, + .get_config = virtio_scsi_get_device_specific_config, + .set_config = virtio_scsi_set_device_specific_config, + .start_device = virtio_scsi_start, + .stop_device = virtio_scsi_stop, +}; + +int +vfu_virtio_scsi_set_options(const char *name, uint16_t num_io_queues, uint16_t qsize, + bool packed_ring) +{ + struct spdk_vfu_endpoint *endpoint; + uint32_t num_queues; + struct vfu_virtio_endpoint *virtio_endpoint; + struct virtio_scsi_endpoint *scsi_endpoint; + + num_queues = num_io_queues + 2; + + endpoint = spdk_vfu_get_endpoint_by_name(name); + if (!endpoint) { + SPDK_ERRLOG("Endpoint %s doesn't exist\n", name); + return -ENOENT; + } + + virtio_endpoint = spdk_vfu_get_endpoint_private(endpoint); + scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + if (virtio_endpoint->dev) { + SPDK_ERRLOG("Options are not allowed to change in runtime\n"); + return -EFAULT; + } + + if ((num_queues > 2) && (num_queues <= VIRTIO_DEV_MAX_VQS)) { + scsi_endpoint->virtio.num_queues = num_queues; + } else { + SPDK_NOTICELOG("Number of IO queue %u\n", VIRTIO_DEV_MAX_VQS - 2); + scsi_endpoint->virtio.num_queues = VIRTIO_DEV_MAX_VQS; + } + + if (qsize && qsize <= VIRTIO_VQ_MAX_SIZE) { + scsi_endpoint->virtio.qsize = qsize; + } else { + SPDK_NOTICELOG("Use queue size %u\n", VIRTIO_VQ_DEFAULT_SIZE); + scsi_endpoint->virtio.qsize = VIRTIO_VQ_DEFAULT_SIZE; + } + scsi_endpoint->virtio.packed_ring = packed_ring; + + SPDK_DEBUGLOG(vfu_virtio_scsi, "%s: num_queues %u, qsize %u, packed ring %s\n", + spdk_vfu_get_endpoint_id(endpoint), + scsi_endpoint->virtio.num_queues, scsi_endpoint->virtio.qsize, + packed_ring ? "enabled" : "disabled"); + + virtio_scsi_update_config(scsi_endpoint); + + return 0; +} + +struct virtio_scsi_event_ctx { + struct virtio_scsi_endpoint *scsi_endpoint; + struct virtio_scsi_target *scsi_target; + uint8_t scsi_target_num; +}; + +static uint8_t +get_scsi_target_num_by_lun(struct virtio_scsi_endpoint *scsi_endpoint, + const struct spdk_scsi_lun *lun) +{ + const struct spdk_scsi_dev *scsi_dev; + struct virtio_scsi_target *scsi_target; + uint8_t i; + + scsi_dev = spdk_scsi_lun_get_dev(lun); + for (i = 0; i < VIRTIO_SCSI_CTRLR_MAX_TARGETS; i++) { + scsi_target = &scsi_endpoint->targets[i]; + if (scsi_target->dev == scsi_dev) { + return i; + } + } + + return VIRTIO_SCSI_CTRLR_MAX_TARGETS; +} + +static void +vfu_virtio_scsi_lun_resize_msg(void *ctx) +{ + struct virtio_scsi_event_ctx *resize_ctx = ctx; + struct virtio_scsi_endpoint *scsi_endpoint = resize_ctx->scsi_endpoint; + uint8_t scsi_target_num = resize_ctx->scsi_target_num; + + free(resize_ctx); + + if (virtio_guest_has_feature(scsi_endpoint->virtio.dev, VIRTIO_SCSI_F_CHANGE)) { + vfu_virtio_scsi_eventq_enqueue(scsi_endpoint, scsi_target_num, + VIRTIO_SCSI_T_PARAM_CHANGE, 0x2a | (0x09 << 8)); + } +} + +static void +vfu_virtio_scsi_lun_resize(const struct spdk_scsi_lun *lun, void *arg) +{ + struct virtio_scsi_endpoint *scsi_endpoint = arg; + uint8_t scsi_target_num; + struct virtio_scsi_event_ctx *ctx; + + scsi_target_num = get_scsi_target_num_by_lun(scsi_endpoint, lun); + if (scsi_target_num == VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + return; + } + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + SPDK_ERRLOG("Error to allocate hotplug ctx\n"); + return; + } + ctx->scsi_endpoint = scsi_endpoint; + ctx->scsi_target_num = scsi_target_num; + + spdk_thread_send_msg(scsi_endpoint->virtio.thread, vfu_virtio_scsi_lun_resize_msg, ctx); +} + +static void +vfu_virtio_scsi_lun_hotremove_msg(void *ctx) +{ + struct virtio_scsi_event_ctx *hotplug = ctx; + struct virtio_scsi_endpoint *scsi_endpoint = hotplug->scsi_endpoint; + struct virtio_scsi_target *scsi_target = hotplug->scsi_target; + struct spdk_scsi_dev *scsi_dev = scsi_target->dev; + uint8_t scsi_target_num = hotplug->scsi_target_num; + + free(hotplug); + + if (!scsi_dev) { + return; + } + scsi_target->dev = NULL; + spdk_scsi_dev_free_io_channels(scsi_dev); + spdk_scsi_dev_destruct(scsi_dev, NULL, NULL); + + assert(scsi_endpoint->virtio.dev); + if (!virtio_dev_is_started(scsi_endpoint->virtio.dev)) { + return; + } + + if (virtio_guest_has_feature(scsi_endpoint->virtio.dev, VIRTIO_SCSI_F_HOTPLUG)) { + SPDK_DEBUGLOG(vfu_virtio_scsi, "Target num %u, sending event\n", scsi_target_num); + vfu_virtio_scsi_eventq_enqueue(scsi_endpoint, scsi_target_num, + VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_REMOVED); + } +} + +static void +vfu_virtio_scsi_lun_hotremove(const struct spdk_scsi_lun *lun, void *arg) +{ + struct virtio_scsi_endpoint *scsi_endpoint = arg; + struct virtio_scsi_target *scsi_target; + struct virtio_scsi_event_ctx *ctx; + uint8_t scsi_target_num; + + if (!scsi_endpoint->virtio.dev) { + return; + } + + scsi_target_num = get_scsi_target_num_by_lun(scsi_endpoint, lun); + if (scsi_target_num == VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + return; + } + scsi_target = &scsi_endpoint->targets[scsi_target_num]; + if (!scsi_target->dev) { + return; + } + + SPDK_DEBUGLOG(vfu_virtio_scsi, "Removing bdev %s, Target num %u\n", + spdk_scsi_lun_get_bdev_name(lun), scsi_target_num); + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + SPDK_ERRLOG("Error to allocate hotplug ctx\n"); + return; + } + ctx->scsi_endpoint = scsi_endpoint; + ctx->scsi_target = scsi_target; + ctx->scsi_target_num = scsi_target_num; + + spdk_thread_send_msg(scsi_endpoint->virtio.thread, vfu_virtio_scsi_lun_hotremove_msg, ctx); +} + +static void +vfu_virtio_scsi_lun_hotplug_msg(void *ctx) +{ + struct virtio_scsi_event_ctx *hotplug = ctx; + struct virtio_scsi_endpoint *scsi_endpoint = hotplug->scsi_endpoint; + struct virtio_scsi_target *scsi_target = hotplug->scsi_target; + uint8_t scsi_target_num = hotplug->scsi_target_num; + int ret; + + free(hotplug); + + assert(scsi_endpoint->virtio.dev); + if (!virtio_dev_is_started(scsi_endpoint->virtio.dev)) { + return; + } + + ret = spdk_scsi_dev_allocate_io_channels(scsi_target->dev); + if (ret) { + SPDK_ERRLOG("%s: Couldn't allocate io channel for SCSI target %u.\n", + spdk_vfu_get_endpoint_name(scsi_endpoint->virtio.endpoint), scsi_target_num); + return; + } + + if (virtio_guest_has_feature(scsi_endpoint->virtio.dev, VIRTIO_SCSI_F_HOTPLUG)) { + vfu_virtio_scsi_eventq_enqueue(scsi_endpoint, scsi_target_num, + VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_RESCAN); + } +} + +int +vfu_virtio_scsi_add_target(const char *name, uint8_t scsi_target_num, const char *bdev_name) +{ + struct spdk_vfu_endpoint *endpoint; + struct vfu_virtio_endpoint *virtio_endpoint; + struct virtio_scsi_endpoint *scsi_endpoint; + struct virtio_scsi_target *scsi_target; + char target_name[SPDK_SCSI_DEV_MAX_NAME]; + int lun_id_list[1]; + const char *bdev_names_list[1]; + + endpoint = spdk_vfu_get_endpoint_by_name(name); + if (!endpoint) { + SPDK_ERRLOG("Endpoint %s doesn't exist\n", name); + return -ENOENT; + } + virtio_endpoint = spdk_vfu_get_endpoint_private(endpoint); + scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + + if (scsi_target_num >= VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + SPDK_ERRLOG("Invalid SCSI target number, maximum SCSI target number is %u\n", + VIRTIO_SCSI_CTRLR_MAX_TARGETS - 1); + return -EINVAL; + } + scsi_target = &scsi_endpoint->targets[scsi_target_num]; + if (scsi_target->dev) { + SPDK_ERRLOG("SCSI Target %u is already occupied\n", scsi_target_num); + return -EEXIST; + } + + snprintf(target_name, sizeof(target_name), "Target %u", scsi_target_num); + lun_id_list[0] = 0; + bdev_names_list[0] = (char *)bdev_name; + + scsi_target->dev = spdk_scsi_dev_construct_ext(target_name, bdev_names_list, lun_id_list, 1, + SPDK_SPC_PROTOCOL_IDENTIFIER_SAS, + vfu_virtio_scsi_lun_resize, scsi_endpoint, + vfu_virtio_scsi_lun_hotremove, scsi_endpoint); + if (!scsi_target->dev) { + SPDK_ERRLOG("%s: couldn't create SCSI target %u via bdev %s\n", name, scsi_target_num, bdev_name); + return -EFAULT; + } + spdk_scsi_dev_add_port(scsi_target->dev, 0, "vfu-virtio-scsi"); + + SPDK_NOTICELOG("%s: added SCSI target %u using bdev '%s'\n", name, scsi_target_num, bdev_name); + virtio_scsi_update_config(scsi_endpoint); + + if (virtio_endpoint->dev) { + struct virtio_scsi_event_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + SPDK_ERRLOG("Error to allocate hotplug ctx\n"); + /* This isn't fatal, just skip hotplug notification */ + } else { + ctx->scsi_endpoint = scsi_endpoint; + ctx->scsi_target = scsi_target; + ctx->scsi_target_num = scsi_target_num; + spdk_thread_send_msg(virtio_endpoint->thread, vfu_virtio_scsi_lun_hotplug_msg, ctx); + } + } + + return 0; +} + +int +vfu_virtio_scsi_remove_target(const char *name, uint8_t scsi_target_num) +{ + struct spdk_vfu_endpoint *endpoint; + struct vfu_virtio_endpoint *virtio_endpoint; + struct virtio_scsi_endpoint *scsi_endpoint; + struct virtio_scsi_target *scsi_target; + + endpoint = spdk_vfu_get_endpoint_by_name(name); + if (!endpoint) { + SPDK_ERRLOG("Endpoint %s doesn't exist\n", name); + return -ENOENT; + } + virtio_endpoint = spdk_vfu_get_endpoint_private(endpoint); + scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + + if (scsi_target_num >= VIRTIO_SCSI_CTRLR_MAX_TARGETS) { + SPDK_ERRLOG("Invalid SCSI target number, maximum SCSI target number is %u\n", + VIRTIO_SCSI_CTRLR_MAX_TARGETS - 1); + return -EINVAL; + } + scsi_target = &scsi_endpoint->targets[scsi_target_num]; + if (!scsi_target->dev) { + SPDK_ERRLOG("SCSI Target %u doesn't exist\n", scsi_target_num); + return -ENOENT; + } + + SPDK_NOTICELOG("%s: Remove SCSI target num %u\n", name, scsi_target_num); + + if (virtio_endpoint->dev) { + struct virtio_scsi_event_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + SPDK_ERRLOG("Error to allocate hotplug ctx\n"); + /* This isn't fatal, just skip hotplug notification */ + } else { + ctx->scsi_endpoint = scsi_endpoint; + ctx->scsi_target = scsi_target; + ctx->scsi_target_num = scsi_target_num; + spdk_thread_send_msg(scsi_endpoint->virtio.thread, vfu_virtio_scsi_lun_hotremove_msg, ctx); + } + } else { + spdk_scsi_dev_destruct(scsi_target->dev, NULL, NULL); + scsi_target->dev = NULL; + } + + return 0; +} + +static int +vfu_virtio_scsi_endpoint_destruct(struct spdk_vfu_endpoint *endpoint) +{ + struct vfu_virtio_endpoint *virtio_endpoint = spdk_vfu_get_endpoint_private(endpoint); + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + struct virtio_scsi_target *scsi_target; + uint8_t i; + + for (i = 0; i < VIRTIO_SCSI_CTRLR_MAX_TARGETS; i++) { + scsi_target = &scsi_endpoint->targets[i]; + if (scsi_target->dev) { + spdk_scsi_dev_destruct(scsi_target->dev, NULL, NULL); + } + } + + vfu_virtio_endpoint_destruct(&scsi_endpoint->virtio); + free(scsi_endpoint); + + return 0; +} + +static void * +vfu_virtio_scsi_endpoint_init(struct spdk_vfu_endpoint *endpoint, + char *basename, const char *endpoint_name) +{ + struct virtio_scsi_endpoint *scsi_endpoint; + int ret; + + scsi_endpoint = calloc(1, sizeof(*scsi_endpoint)); + if (!scsi_endpoint) { + return NULL; + } + + ret = vfu_virtio_endpoint_setup(&scsi_endpoint->virtio, endpoint, basename, endpoint_name, + &virtio_scsi_ops); + if (ret) { + SPDK_ERRLOG("Error to setup endpoint %s\n", endpoint_name); + free(scsi_endpoint); + return NULL; + } + + virtio_scsi_update_config(scsi_endpoint); + return (void *)&scsi_endpoint->virtio; +} + +static int +vfu_virtio_scsi_get_device_info(struct spdk_vfu_endpoint *endpoint, + struct spdk_vfu_pci_device *device_info) +{ + struct vfu_virtio_endpoint *virtio_endpoint = spdk_vfu_get_endpoint_private(endpoint); + struct virtio_scsi_endpoint *scsi_endpoint = to_scsi_endpoint(virtio_endpoint); + + vfu_virtio_get_device_info(&scsi_endpoint->virtio, device_info); + /* Fill Device ID */ + device_info->id.did = PCI_DEVICE_ID_VIRTIO_SCSI_MODERN; + + return 0; +} + +struct spdk_vfu_endpoint_ops vfu_virtio_scsi_ops = { + .name = "virtio_scsi", + .init = vfu_virtio_scsi_endpoint_init, + .get_device_info = vfu_virtio_scsi_get_device_info, + .get_vendor_capability = vfu_virtio_get_vendor_capability, + .post_memory_add = vfu_virtio_post_memory_add, + .pre_memory_remove = vfu_virtio_pre_memory_remove, + .reset_device = vfu_virtio_pci_reset_cb, + .quiesce_device = vfu_virtio_quiesce_cb, + .destruct = vfu_virtio_scsi_endpoint_destruct, + .attach_device = vfu_virtio_attach_device, + .detach_device = vfu_virtio_detach_device, +}; + +static void +__attribute__((constructor)) _vfu_virtio_scsi_pci_model_register(void) +{ + spdk_vfu_register_endpoint_ops(&vfu_virtio_scsi_ops); +} + +SPDK_LOG_REGISTER_COMPONENT(vfu_virtio_scsi) +SPDK_LOG_REGISTER_COMPONENT(vfu_virtio_scsi_data) diff --git a/python/spdk/rpc/vfio_user.py b/python/spdk/rpc/vfio_user.py index 6c2740979..dd610838e 100644 --- a/python/spdk/rpc/vfio_user.py +++ b/python/spdk/rpc/vfio_user.py @@ -49,3 +49,60 @@ def vfu_virtio_create_blk_endpoint(client, name, bdev_name, cpumask, num_queues, params['packed_ring'] = packed_ring return client.call('vfu_virtio_create_blk_endpoint', params) + + +def vfu_virtio_scsi_add_target(client, name, scsi_target_num, bdev_name): + """Attach a block device to the specified SCSI target. + + Args: + name: endpoint name + scsi_target_num: SCSI target number + bdev_name: name of block device + """ + params = { + 'name': name, + 'scsi_target_num': scsi_target_num, + 'bdev_name': bdev_name + } + + return client.call('vfu_virtio_scsi_add_target', params) + + +def vfu_virtio_scsi_remove_target(client, name, scsi_target_num): + """Remove specified SCSI target of socket endpoint. + + Args: + name: endpoint name + scsi_target_num: SCSI target number + """ + params = { + 'name': name, + 'scsi_target_num': scsi_target_num + } + + return client.call('vfu_virtio_scsi_remove_target', params) + + +def vfu_virtio_create_scsi_endpoint(client, name, cpumask, num_io_queues, qsize, packed_ring): + """Create virtio-scsi endpoint. + + Args: + name: endpoint name + cpumask: CPU core mask + num_io_queues: number of IO vrings + qsize: number of element of each vring + packed_ring: enable packed ring + """ + params = { + 'name': name, + } + if cpumask: + params['cpumask'] = cpumask + if num_io_queues: + params['num_io_queues'] = num_io_queues + if qsize: + params['qsize'] = qsize + if packed_ring: + params['packed_ring'] = packed_ring + + return client.call('vfu_virtio_create_scsi_endpoint', params) diff --git a/scripts/rpc.py b/scripts/rpc.py index 37a19dfe5..75d0e9e8c 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -2649,6 +2649,44 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse p.add_argument("--packed-ring", action='store_true', help='Enable packed ring') p.set_defaults(func=vfu_virtio_create_blk_endpoint) + def vfu_virtio_scsi_add_target(args): + rpc.vfio_user.vfu_virtio_scsi_add_target(args.client, + name=args.name, + scsi_target_num=args.scsi_target_num, + bdev_name=args.bdev_name) + + p = subparsers.add_parser('vfu_virtio_scsi_add_target', help='Attach a block device to SCSI target of PCI endpoint.') + p.add_argument('name', help='Name of the endpoint') + p.add_argument('--scsi-target-num', help='number of SCSI Target', type=int, required=True) + p.add_argument('--bdev-name', help='block device name', type=str, required=True) + p.set_defaults(func=vfu_virtio_scsi_add_target) + + def vfu_virtio_scsi_remove_target(args): + rpc.vfio_user.vfu_virtio_scsi_remove_target(args.client, + name=args.name, + scsi_target_num=args.scsi_target_num) + + p = subparsers.add_parser('vfu_virtio_scsi_remove_target', help='Remove the specified SCSI target of PCI endpoint.') + p.add_argument('name', help='Name of the endpoint') + p.add_argument('--scsi-target-num', help='number of SCSI Target', type=int, required=True) + p.set_defaults(func=vfu_virtio_scsi_remove_target) + + def vfu_virtio_create_scsi_endpoint(args): + rpc.vfio_user.vfu_virtio_create_scsi_endpoint(args.client, + name=args.name, + cpumask=args.cpumask, + num_io_queues=args.num_io_queues, + qsize=args.qsize, + packed_ring=args.packed_ring) + + p = subparsers.add_parser('vfu_virtio_create_scsi_endpoint', help='Create virtio-scsi endpoint.') + p.add_argument('name', help='Name of the endpoint') + p.add_argument('--cpumask', help='CPU masks') + p.add_argument('--num-io-queues', help='number of IO vrings', type=int, default=0) + p.add_argument('--qsize', help='number of element for each vring', type=int, default=0) + p.add_argument("--packed-ring", action='store_true', help='Enable packed ring') + p.set_defaults(func=vfu_virtio_create_scsi_endpoint) + # accel_fw def accel_get_opc_assignments(args): print_dict(rpc.accel.accel_get_opc_assignments(args.client))