diff options
Diffstat (limited to 'src/spdk/lib/scsi')
-rw-r--r-- | src/spdk/lib/scsi/Makefile | 45 | ||||
-rw-r--r-- | src/spdk/lib/scsi/dev.c | 436 | ||||
-rw-r--r-- | src/spdk/lib/scsi/lun.c | 623 | ||||
-rw-r--r-- | src/spdk/lib/scsi/port.c | 134 | ||||
-rw-r--r-- | src/spdk/lib/scsi/scsi.c | 110 | ||||
-rw-r--r-- | src/spdk/lib/scsi/scsi_bdev.c | 2067 | ||||
-rw-r--r-- | src/spdk/lib/scsi/scsi_internal.h | 214 | ||||
-rw-r--r-- | src/spdk/lib/scsi/scsi_pr.c | 1067 | ||||
-rw-r--r-- | src/spdk/lib/scsi/scsi_rpc.c | 77 | ||||
-rw-r--r-- | src/spdk/lib/scsi/spdk_scsi.map | 49 | ||||
-rw-r--r-- | src/spdk/lib/scsi/task.c | 300 |
11 files changed, 5122 insertions, 0 deletions
diff --git a/src/spdk/lib/scsi/Makefile b/src/spdk/lib/scsi/Makefile new file mode 100644 index 000000000..8f8a8c326 --- /dev/null +++ b/src/spdk/lib/scsi/Makefile @@ -0,0 +1,45 @@ +# +# 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +SO_VER := 3 +SO_MINOR := 0 + +C_SRCS = dev.c lun.c port.c scsi.c scsi_bdev.c scsi_pr.c scsi_rpc.c task.c +LIBNAME = scsi + +SPDK_MAP_FILE = $(abspath $(CURDIR)/spdk_scsi.map) + +include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk diff --git a/src/spdk/lib/scsi/dev.c b/src/spdk/lib/scsi/dev.c new file mode 100644 index 000000000..6d3cfdf31 --- /dev/null +++ b/src/spdk/lib/scsi/dev.c @@ -0,0 +1,436 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" + +static struct spdk_scsi_dev g_devs[SPDK_SCSI_MAX_DEVS]; + +struct spdk_scsi_dev * +scsi_dev_get_list(void) +{ + return g_devs; +} + +static struct spdk_scsi_dev * +allocate_dev(void) +{ + struct spdk_scsi_dev *dev; + int i; + + for (i = 0; i < SPDK_SCSI_MAX_DEVS; i++) { + dev = &g_devs[i]; + if (!dev->is_allocated) { + memset(dev, 0, sizeof(*dev)); + dev->id = i; + dev->is_allocated = 1; + return dev; + } + } + + return NULL; +} + +static void +free_dev(struct spdk_scsi_dev *dev) +{ + assert(dev->is_allocated == 1); + assert(dev->removed == true); + + dev->is_allocated = 0; + + if (dev->remove_cb) { + dev->remove_cb(dev->remove_ctx, 0); + dev->remove_cb = NULL; + } +} + +void +spdk_scsi_dev_destruct(struct spdk_scsi_dev *dev, + spdk_scsi_dev_destruct_cb_t cb_fn, void *cb_arg) +{ + int lun_cnt; + int i; + + if (dev == NULL) { + if (cb_fn) { + cb_fn(cb_arg, -EINVAL); + } + return; + } + + if (dev->removed) { + if (cb_fn) { + cb_fn(cb_arg, -EINVAL); + } + return; + } + + dev->removed = true; + dev->remove_cb = cb_fn; + dev->remove_ctx = cb_arg; + lun_cnt = 0; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == NULL) { + continue; + } + + /* + * LUN will remove itself from this dev when all outstanding IO + * is done. When no more LUNs, dev will be deleted. + */ + scsi_lun_destruct(dev->lun[i]); + lun_cnt++; + } + + if (lun_cnt == 0) { + free_dev(dev); + return; + } +} + +static int +scsi_dev_find_lowest_free_lun_id(struct spdk_scsi_dev *dev) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == NULL) { + return i; + } + } + + return -1; +} + +int +spdk_scsi_dev_add_lun(struct spdk_scsi_dev *dev, const char *bdev_name, int lun_id, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + struct spdk_bdev *bdev; + struct spdk_scsi_lun *lun; + + bdev = spdk_bdev_get_by_name(bdev_name); + if (bdev == NULL) { + SPDK_ERRLOG("device %s: cannot find bdev '%s' (target %d)\n", + dev->name, bdev_name, lun_id); + return -1; + } + + /* Search the lowest free LUN ID if LUN ID is default */ + if (lun_id == -1) { + lun_id = scsi_dev_find_lowest_free_lun_id(dev); + if (lun_id == -1) { + SPDK_ERRLOG("Free LUN ID is not found\n"); + return -1; + } + } + + lun = scsi_lun_construct(bdev, hotremove_cb, hotremove_ctx); + if (lun == NULL) { + return -1; + } + + lun->id = lun_id; + lun->dev = dev; + dev->lun[lun_id] = lun; + return 0; +} + +void +spdk_scsi_dev_delete_lun(struct spdk_scsi_dev *dev, + struct spdk_scsi_lun *lun) +{ + int lun_cnt = 0; + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == lun) { + dev->lun[i] = NULL; + } + + if (dev->lun[i]) { + lun_cnt++; + } + } + + if (dev->removed == true && lun_cnt == 0) { + free_dev(dev); + } +} + +struct spdk_scsi_dev *spdk_scsi_dev_construct(const char *name, const char *bdev_name_list[], + int *lun_id_list, int num_luns, uint8_t protocol_id, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + struct spdk_scsi_dev *dev; + size_t name_len; + bool found_lun_0; + int i, rc; + + name_len = strlen(name); + if (name_len > sizeof(dev->name) - 1) { + SPDK_ERRLOG("device %s: name longer than maximum allowed length %zu\n", + name, sizeof(dev->name) - 1); + return NULL; + } + + if (num_luns == 0) { + SPDK_ERRLOG("device %s: no LUNs specified\n", name); + return NULL; + } + + found_lun_0 = false; + for (i = 0; i < num_luns; i++) { + if (lun_id_list[i] == 0) { + found_lun_0 = true; + break; + } + } + + if (!found_lun_0) { + SPDK_ERRLOG("device %s: no LUN 0 specified\n", name); + return NULL; + } + + for (i = 0; i < num_luns; i++) { + if (bdev_name_list[i] == NULL) { + SPDK_ERRLOG("NULL spdk_scsi_lun for LUN %d\n", + lun_id_list[i]); + return NULL; + } + } + + dev = allocate_dev(); + if (dev == NULL) { + return NULL; + } + + memcpy(dev->name, name, name_len + 1); + + dev->num_ports = 0; + dev->protocol_id = protocol_id; + + for (i = 0; i < num_luns; i++) { + rc = spdk_scsi_dev_add_lun(dev, bdev_name_list[i], lun_id_list[i], + hotremove_cb, hotremove_ctx); + if (rc < 0) { + spdk_scsi_dev_destruct(dev, NULL, NULL); + return NULL; + } + } + + return dev; +} + +void +spdk_scsi_dev_queue_mgmt_task(struct spdk_scsi_dev *dev, + struct spdk_scsi_task *task) +{ + assert(task != NULL); + + scsi_lun_execute_mgmt_task(task->lun, task); +} + +void +spdk_scsi_dev_queue_task(struct spdk_scsi_dev *dev, + struct spdk_scsi_task *task) +{ + assert(task != NULL); + + scsi_lun_execute_task(task->lun, task); +} + +static struct spdk_scsi_port * +scsi_dev_find_free_port(struct spdk_scsi_dev *dev) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_PORTS; i++) { + if (!dev->port[i].is_used) { + return &dev->port[i]; + } + } + + return NULL; +} + +int +spdk_scsi_dev_add_port(struct spdk_scsi_dev *dev, uint64_t id, const char *name) +{ + struct spdk_scsi_port *port; + int rc; + + if (dev->num_ports == SPDK_SCSI_DEV_MAX_PORTS) { + SPDK_ERRLOG("device already has %d ports\n", SPDK_SCSI_DEV_MAX_PORTS); + return -1; + } + + port = spdk_scsi_dev_find_port_by_id(dev, id); + if (port != NULL) { + SPDK_ERRLOG("device already has port(%" PRIu64 ")\n", id); + return -1; + } + + port = scsi_dev_find_free_port(dev); + if (port == NULL) { + assert(false); + return -1; + } + + rc = scsi_port_construct(port, id, dev->num_ports, name); + if (rc != 0) { + return rc; + } + + dev->num_ports++; + return 0; +} + +int +spdk_scsi_dev_delete_port(struct spdk_scsi_dev *dev, uint64_t id) +{ + struct spdk_scsi_port *port; + + port = spdk_scsi_dev_find_port_by_id(dev, id); + if (port == NULL) { + SPDK_ERRLOG("device does not have specified port(%" PRIu64 ")\n", id); + return -1; + } + + scsi_port_destruct(port); + + dev->num_ports--; + + return 0; +} + +struct spdk_scsi_port * +spdk_scsi_dev_find_port_by_id(struct spdk_scsi_dev *dev, uint64_t id) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_PORTS; i++) { + if (!dev->port[i].is_used) { + continue; + } + if (dev->port[i].id == id) { + return &dev->port[i]; + } + } + + /* No matching port found. */ + return NULL; +} + +void +spdk_scsi_dev_free_io_channels(struct spdk_scsi_dev *dev) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == NULL) { + continue; + } + scsi_lun_free_io_channel(dev->lun[i]); + } +} + +int +spdk_scsi_dev_allocate_io_channels(struct spdk_scsi_dev *dev) +{ + int i, rc; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == NULL) { + continue; + } + rc = scsi_lun_allocate_io_channel(dev->lun[i]); + if (rc < 0) { + spdk_scsi_dev_free_io_channels(dev); + return -1; + } + } + + return 0; +} + +const char * +spdk_scsi_dev_get_name(const struct spdk_scsi_dev *dev) +{ + return dev->name; +} + +int +spdk_scsi_dev_get_id(const struct spdk_scsi_dev *dev) +{ + return dev->id; +} + +struct spdk_scsi_lun * +spdk_scsi_dev_get_lun(struct spdk_scsi_dev *dev, int lun_id) +{ + struct spdk_scsi_lun *lun; + + if (lun_id < 0 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) { + return NULL; + } + + lun = dev->lun[lun_id]; + + if (lun != NULL && !spdk_scsi_lun_is_removing(lun)) { + return lun; + } else { + return NULL; + } +} + +bool +spdk_scsi_dev_has_pending_tasks(const struct spdk_scsi_dev *dev, + const struct spdk_scsi_port *initiator_port) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; ++i) { + if (dev->lun[i] && + (scsi_lun_has_pending_tasks(dev->lun[i], initiator_port) || + scsi_lun_has_pending_mgmt_tasks(dev->lun[i], initiator_port))) { + return true; + } + } + + return false; +} diff --git a/src/spdk/lib/scsi/lun.c b/src/spdk/lib/scsi/lun.c new file mode 100644 index 000000000..262137d80 --- /dev/null +++ b/src/spdk/lib/scsi/lun.c @@ -0,0 +1,623 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/thread.h" +#include "spdk/util.h" +#include "spdk/likely.h" + +static void scsi_lun_execute_tasks(struct spdk_scsi_lun *lun); +static void _scsi_lun_execute_mgmt_task(struct spdk_scsi_lun *lun); + +void +scsi_lun_complete_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + if (lun) { + TAILQ_REMOVE(&lun->tasks, task, scsi_link); + spdk_trace_record(TRACE_SCSI_TASK_DONE, lun->dev->id, 0, (uintptr_t)task, 0); + } + task->cpl_fn(task); +} + +static void +scsi_lun_complete_mgmt_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + TAILQ_REMOVE(&lun->mgmt_tasks, task, scsi_link); + + task->cpl_fn(task); + + /* Try to execute the first pending mgmt task if it exists. */ + _scsi_lun_execute_mgmt_task(lun); +} + +static bool +_scsi_lun_has_pending_mgmt_tasks(const struct spdk_scsi_lun *lun) +{ + return !TAILQ_EMPTY(&lun->pending_mgmt_tasks); +} + +static bool +scsi_lun_has_outstanding_mgmt_tasks(const struct spdk_scsi_lun *lun) +{ + return !TAILQ_EMPTY(&lun->mgmt_tasks); +} + +static bool +_scsi_lun_has_pending_tasks(const struct spdk_scsi_lun *lun) +{ + return !TAILQ_EMPTY(&lun->pending_tasks); +} + +static bool +scsi_lun_has_outstanding_tasks(const struct spdk_scsi_lun *lun) +{ + return !TAILQ_EMPTY(&lun->tasks); +} + +/* Reset task have to wait until all prior outstanding tasks complete. */ +static int +scsi_lun_reset_check_outstanding_tasks(void *arg) +{ + struct spdk_scsi_task *task = (struct spdk_scsi_task *)arg; + struct spdk_scsi_lun *lun = task->lun; + + if (scsi_lun_has_outstanding_tasks(lun)) { + return SPDK_POLLER_BUSY; + } + spdk_poller_unregister(&lun->reset_poller); + + scsi_lun_complete_mgmt_task(lun, task); + return SPDK_POLLER_BUSY; +} + +void +scsi_lun_complete_reset_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + if (task->status == SPDK_SCSI_STATUS_GOOD) { + if (scsi_lun_has_outstanding_tasks(lun)) { + lun->reset_poller = + SPDK_POLLER_REGISTER(scsi_lun_reset_check_outstanding_tasks, + task, 10); + return; + } + } + + scsi_lun_complete_mgmt_task(lun, task); +} + +static void +scsi_lun_append_mgmt_task(struct spdk_scsi_lun *lun, + struct spdk_scsi_task *task) +{ + TAILQ_INSERT_TAIL(&lun->pending_mgmt_tasks, task, scsi_link); +} + +static void +_scsi_lun_execute_mgmt_task(struct spdk_scsi_lun *lun) +{ + struct spdk_scsi_task *task; + + if (!TAILQ_EMPTY(&lun->mgmt_tasks)) { + return; + } + + task = TAILQ_FIRST(&lun->pending_mgmt_tasks); + if (spdk_likely(task == NULL)) { + /* Try to execute all pending tasks */ + scsi_lun_execute_tasks(lun); + return; + } + TAILQ_REMOVE(&lun->pending_mgmt_tasks, task, scsi_link); + + TAILQ_INSERT_TAIL(&lun->mgmt_tasks, task, scsi_link); + + if (lun->removed) { + task->response = SPDK_SCSI_TASK_MGMT_RESP_INVALID_LUN; + scsi_lun_complete_mgmt_task(lun, task); + return; + } + + switch (task->function) { + case SPDK_SCSI_TASK_FUNC_ABORT_TASK: + task->response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + SPDK_ERRLOG("ABORT_TASK failed\n"); + break; + + case SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET: + task->response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + SPDK_ERRLOG("ABORT_TASK_SET failed\n"); + break; + + case SPDK_SCSI_TASK_FUNC_LUN_RESET: + bdev_scsi_reset(task); + return; + + default: + SPDK_ERRLOG("Unknown Task Management Function!\n"); + /* + * Task management functions other than those above should never + * reach this point having been filtered by the frontend. Reject + * the task as being unsupported. + */ + task->response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + break; + } + + scsi_lun_complete_mgmt_task(lun, task); +} + +void +scsi_lun_execute_mgmt_task(struct spdk_scsi_lun *lun, + struct spdk_scsi_task *task) +{ + scsi_lun_append_mgmt_task(lun, task); + _scsi_lun_execute_mgmt_task(lun); +} + +static void +_scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + int rc; + + task->status = SPDK_SCSI_STATUS_GOOD; + spdk_trace_record(TRACE_SCSI_TASK_START, lun->dev->id, task->length, (uintptr_t)task, 0); + TAILQ_INSERT_TAIL(&lun->tasks, task, scsi_link); + if (!lun->removed) { + /* Check the command is allowed or not when reservation is exist */ + if (spdk_unlikely(lun->reservation.flags & SCSI_SPC2_RESERVE)) { + rc = scsi2_reserve_check(task); + } else { + rc = scsi_pr_check(task); + } + if (spdk_unlikely(rc < 0)) { + /* Reservation Conflict */ + rc = SPDK_SCSI_TASK_COMPLETE; + } else { + rc = bdev_scsi_execute(task); + } + } else { + spdk_scsi_task_process_abort(task); + rc = SPDK_SCSI_TASK_COMPLETE; + } + + switch (rc) { + case SPDK_SCSI_TASK_PENDING: + break; + + case SPDK_SCSI_TASK_COMPLETE: + scsi_lun_complete_task(lun, task); + break; + + default: + abort(); + } +} + +static void +scsi_lun_append_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + TAILQ_INSERT_TAIL(&lun->pending_tasks, task, scsi_link); +} + +static void +scsi_lun_execute_tasks(struct spdk_scsi_lun *lun) +{ + struct spdk_scsi_task *task, *task_tmp; + + TAILQ_FOREACH_SAFE(task, &lun->pending_tasks, scsi_link, task_tmp) { + TAILQ_REMOVE(&lun->pending_tasks, task, scsi_link); + _scsi_lun_execute_task(lun, task); + } +} + +void +scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task) +{ + if (spdk_unlikely(_scsi_lun_has_pending_mgmt_tasks(lun))) { + /* Add the IO task to pending list and wait for completion of + * existing mgmt tasks. + */ + scsi_lun_append_task(lun, task); + } else if (spdk_unlikely(_scsi_lun_has_pending_tasks(lun))) { + /* If there is any pending IO task, append the IO task to the + * tail of the pending list, and then execute all pending IO tasks + * from the head to submit IO tasks in order. + */ + scsi_lun_append_task(lun, task); + scsi_lun_execute_tasks(lun); + } else { + /* Execute the IO task directly. */ + _scsi_lun_execute_task(lun, task); + } +} + +static void +_scsi_lun_remove(void *arg) +{ + struct spdk_scsi_lun *lun = (struct spdk_scsi_lun *)arg; + + spdk_bdev_close(lun->bdev_desc); + spdk_scsi_dev_delete_lun(lun->dev, lun); + free(lun); +} + +static void +scsi_lun_remove(struct spdk_scsi_lun *lun) +{ + struct spdk_scsi_pr_registrant *reg, *tmp; + struct spdk_thread *thread; + + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + TAILQ_REMOVE(&lun->reg_head, reg, link); + free(reg); + } + + thread = spdk_get_thread(); + if (thread != lun->thread) { + spdk_thread_send_msg(lun->thread, _scsi_lun_remove, lun); + } else { + _scsi_lun_remove(lun); + } +} + +static int +scsi_lun_check_io_channel(void *arg) +{ + struct spdk_scsi_lun *lun = (struct spdk_scsi_lun *)arg; + + if (lun->io_channel) { + return SPDK_POLLER_BUSY; + } + spdk_poller_unregister(&lun->hotremove_poller); + + scsi_lun_remove(lun); + return SPDK_POLLER_BUSY; +} + +static void +scsi_lun_notify_hot_remove(struct spdk_scsi_lun *lun) +{ + struct spdk_scsi_lun_desc *desc, *tmp; + + if (lun->hotremove_cb) { + lun->hotremove_cb(lun, lun->hotremove_ctx); + } + + TAILQ_FOREACH_SAFE(desc, &lun->open_descs, link, tmp) { + if (desc->hotremove_cb) { + desc->hotremove_cb(lun, desc->hotremove_ctx); + } else { + spdk_scsi_lun_close(desc); + } + } + + if (lun->io_channel) { + lun->hotremove_poller = SPDK_POLLER_REGISTER(scsi_lun_check_io_channel, + lun, 10); + } else { + scsi_lun_remove(lun); + } +} + +static int +scsi_lun_check_outstanding_tasks(void *arg) +{ + struct spdk_scsi_lun *lun = (struct spdk_scsi_lun *)arg; + + if (scsi_lun_has_outstanding_tasks(lun) || + scsi_lun_has_outstanding_mgmt_tasks(lun)) { + return SPDK_POLLER_BUSY; + } + spdk_poller_unregister(&lun->hotremove_poller); + + scsi_lun_notify_hot_remove(lun); + return SPDK_POLLER_BUSY; +} + +static void +_scsi_lun_hot_remove(void *arg1) +{ + struct spdk_scsi_lun *lun = arg1; + + /* If lun->removed is set, no new task can be submitted to the LUN. + * Execute previously queued tasks, which will be immediately aborted. + */ + scsi_lun_execute_tasks(lun); + + /* Then we only need to wait for all outstanding tasks to be completed + * before notifying the upper layer about the removal. + */ + if (scsi_lun_has_outstanding_tasks(lun) || + scsi_lun_has_outstanding_mgmt_tasks(lun)) { + lun->hotremove_poller = SPDK_POLLER_REGISTER(scsi_lun_check_outstanding_tasks, + lun, 10); + } else { + scsi_lun_notify_hot_remove(lun); + } +} + +static void +scsi_lun_hot_remove(void *remove_ctx) +{ + struct spdk_scsi_lun *lun = (struct spdk_scsi_lun *)remove_ctx; + struct spdk_thread *thread; + + if (lun->removed) { + return; + } + + lun->removed = true; + if (lun->io_channel == NULL) { + _scsi_lun_hot_remove(lun); + return; + } + + thread = spdk_io_channel_get_thread(lun->io_channel); + if (thread != spdk_get_thread()) { + spdk_thread_send_msg(thread, _scsi_lun_hot_remove, lun); + } else { + _scsi_lun_hot_remove(lun); + } +} + +/** + * \brief Constructs a new spdk_scsi_lun object based on the provided parameters. + * + * \param bdev bdev associated with this LUN + * + * \return NULL if bdev == NULL + * \return pointer to the new spdk_scsi_lun object otherwise + */ +struct spdk_scsi_lun *scsi_lun_construct(struct spdk_bdev *bdev, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx) +{ + struct spdk_scsi_lun *lun; + int rc; + + if (bdev == NULL) { + SPDK_ERRLOG("bdev must be non-NULL\n"); + return NULL; + } + + lun = calloc(1, sizeof(*lun)); + if (lun == NULL) { + SPDK_ERRLOG("could not allocate lun\n"); + return NULL; + } + + rc = spdk_bdev_open(bdev, true, scsi_lun_hot_remove, lun, &lun->bdev_desc); + + if (rc != 0) { + SPDK_ERRLOG("bdev %s cannot be opened, error=%d\n", spdk_bdev_get_name(bdev), rc); + free(lun); + return NULL; + } + + lun->thread = spdk_get_thread(); + + TAILQ_INIT(&lun->tasks); + TAILQ_INIT(&lun->pending_tasks); + TAILQ_INIT(&lun->mgmt_tasks); + TAILQ_INIT(&lun->pending_mgmt_tasks); + + lun->bdev = bdev; + lun->io_channel = NULL; + lun->hotremove_cb = hotremove_cb; + lun->hotremove_ctx = hotremove_ctx; + TAILQ_INIT(&lun->open_descs); + TAILQ_INIT(&lun->reg_head); + + return lun; +} + +void +scsi_lun_destruct(struct spdk_scsi_lun *lun) +{ + scsi_lun_hot_remove(lun); +} + +int +spdk_scsi_lun_open(struct spdk_scsi_lun *lun, spdk_scsi_lun_remove_cb_t hotremove_cb, + void *hotremove_ctx, struct spdk_scsi_lun_desc **_desc) +{ + struct spdk_scsi_lun_desc *desc; + + desc = calloc(1, sizeof(*desc)); + if (desc == NULL) { + SPDK_ERRLOG("calloc() failed for LUN descriptor.\n"); + return -ENOMEM; + } + + TAILQ_INSERT_TAIL(&lun->open_descs, desc, link); + + desc->lun = lun; + desc->hotremove_cb = hotremove_cb; + desc->hotremove_ctx = hotremove_ctx; + *_desc = desc; + + return 0; +} + +void +spdk_scsi_lun_close(struct spdk_scsi_lun_desc *desc) +{ + struct spdk_scsi_lun *lun = desc->lun; + + TAILQ_REMOVE(&lun->open_descs, desc, link); + free(desc); + + assert(!TAILQ_EMPTY(&lun->open_descs) || lun->io_channel == NULL); +} + +int +scsi_lun_allocate_io_channel(struct spdk_scsi_lun *lun) +{ + if (lun->io_channel != NULL) { + if (spdk_get_thread() == spdk_io_channel_get_thread(lun->io_channel)) { + lun->ref++; + return 0; + } + SPDK_ERRLOG("io_channel already allocated for lun %s\n", + spdk_bdev_get_name(lun->bdev)); + return -1; + } + + lun->io_channel = spdk_bdev_get_io_channel(lun->bdev_desc); + if (lun->io_channel == NULL) { + return -1; + } + lun->ref = 1; + return 0; +} + +void +scsi_lun_free_io_channel(struct spdk_scsi_lun *lun) +{ + if (lun->io_channel == NULL) { + return; + } + + if (spdk_get_thread() != spdk_io_channel_get_thread(lun->io_channel)) { + SPDK_ERRLOG("io_channel was freed by different thread\n"); + return; + } + + lun->ref--; + if (lun->ref == 0) { + spdk_put_io_channel(lun->io_channel); + lun->io_channel = NULL; + } +} + +int +spdk_scsi_lun_allocate_io_channel(struct spdk_scsi_lun_desc *desc) +{ + struct spdk_scsi_lun *lun = desc->lun; + + return scsi_lun_allocate_io_channel(lun); +} + +void +spdk_scsi_lun_free_io_channel(struct spdk_scsi_lun_desc *desc) +{ + struct spdk_scsi_lun *lun = desc->lun; + + scsi_lun_free_io_channel(lun); +} + +int +spdk_scsi_lun_get_id(const struct spdk_scsi_lun *lun) +{ + return lun->id; +} + +const char * +spdk_scsi_lun_get_bdev_name(const struct spdk_scsi_lun *lun) +{ + return spdk_bdev_get_name(lun->bdev); +} + +const struct spdk_scsi_dev * +spdk_scsi_lun_get_dev(const struct spdk_scsi_lun *lun) +{ + return lun->dev; +} + +bool +scsi_lun_has_pending_mgmt_tasks(const struct spdk_scsi_lun *lun, + const struct spdk_scsi_port *initiator_port) +{ + struct spdk_scsi_task *task; + + if (initiator_port == NULL) { + return _scsi_lun_has_pending_mgmt_tasks(lun) || + scsi_lun_has_outstanding_mgmt_tasks(lun); + } + + TAILQ_FOREACH(task, &lun->pending_mgmt_tasks, scsi_link) { + if (task->initiator_port == initiator_port) { + return true; + } + } + + TAILQ_FOREACH(task, &lun->mgmt_tasks, scsi_link) { + if (task->initiator_port == initiator_port) { + return true; + } + } + + return false; +} +/* This check includes both pending and submitted (outstanding) tasks. */ +bool +scsi_lun_has_pending_tasks(const struct spdk_scsi_lun *lun, + const struct spdk_scsi_port *initiator_port) +{ + struct spdk_scsi_task *task; + + if (initiator_port == NULL) { + return _scsi_lun_has_pending_tasks(lun) || + scsi_lun_has_outstanding_tasks(lun); + } + + TAILQ_FOREACH(task, &lun->pending_tasks, scsi_link) { + if (task->initiator_port == initiator_port) { + return true; + } + } + + TAILQ_FOREACH(task, &lun->tasks, scsi_link) { + if (task->initiator_port == initiator_port) { + return true; + } + } + + return false; +} + +bool +spdk_scsi_lun_is_removing(const struct spdk_scsi_lun *lun) +{ + return lun->removed; +} + +bool +spdk_scsi_lun_get_dif_ctx(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task, + struct spdk_dif_ctx *dif_ctx) +{ + return bdev_scsi_get_dif_ctx(lun->bdev, task, dif_ctx); +} diff --git a/src/spdk/lib/scsi/port.c b/src/spdk/lib/scsi/port.c new file mode 100644 index 000000000..09311bac2 --- /dev/null +++ b/src/spdk/lib/scsi/port.c @@ -0,0 +1,134 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" + +#include "spdk/endian.h" + +struct spdk_scsi_port * +spdk_scsi_port_create(uint64_t id, uint16_t index, const char *name) +{ + struct spdk_scsi_port *port; + + port = calloc(1, sizeof(struct spdk_scsi_port)); + + if (!port) { + return NULL; + } + + if (scsi_port_construct(port, id, index, name) != 0) { + spdk_scsi_port_free(&port); + return NULL; + } + + return port; +} + +void +spdk_scsi_port_free(struct spdk_scsi_port **pport) +{ + struct spdk_scsi_port *port; + + if (!pport) { + return; + } + + port = *pport; + *pport = NULL; + free(port); +} + +int +scsi_port_construct(struct spdk_scsi_port *port, uint64_t id, uint16_t index, + const char *name) +{ + if (strlen(name) >= sizeof(port->name)) { + SPDK_ERRLOG("port name too long\n"); + return -1; + } + + port->is_used = 1; + port->id = id; + port->index = index; + snprintf(port->name, sizeof(port->name), "%s", name); + return 0; +} + +void +scsi_port_destruct(struct spdk_scsi_port *port) +{ + memset(port, 0, sizeof(struct spdk_scsi_port)); +} + +const char * +spdk_scsi_port_get_name(const struct spdk_scsi_port *port) +{ + return port->name; +} + +/* + * spc3r23 7.5.4.6 iSCSI initiator port TransportID, + * using code format 0x01. + */ +void +spdk_scsi_port_set_iscsi_transport_id(struct spdk_scsi_port *port, char *iscsi_name, + uint64_t isid) +{ + struct spdk_scsi_iscsi_transport_id *data; + uint32_t len; + char *name; + + memset(port->transport_id, 0, sizeof(port->transport_id)); + port->transport_id_len = 0; + + data = (struct spdk_scsi_iscsi_transport_id *)port->transport_id; + + data->protocol_id = (uint8_t)SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI; + data->format = 0x1; + + name = data->name; + len = snprintf(name, SPDK_SCSI_MAX_TRANSPORT_ID_LENGTH - sizeof(*data), + "%s,i,0x%12.12" PRIx64, iscsi_name, isid); + do { + name[len++] = '\0'; + } while (len & 3); + + if (len < 20) { + SPDK_ERRLOG("The length of Transport ID should >= 20 bytes\n"); + return; + } + + to_be16(&data->additional_len, len); + port->transport_id_len = len + sizeof(*data); +} diff --git a/src/spdk/lib/scsi/scsi.c b/src/spdk/lib/scsi/scsi.c new file mode 100644 index 000000000..c18192e37 --- /dev/null +++ b/src/spdk/lib/scsi/scsi.c @@ -0,0 +1,110 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" + +struct spdk_scsi_globals g_scsi; + +int +spdk_scsi_init(void) +{ + int rc; + + rc = pthread_mutex_init(&g_scsi.mutex, NULL); + if (rc != 0) { + SPDK_ERRLOG("mutex_init() failed\n"); + return -1; + } + + return 0; +} + +void +spdk_scsi_fini(void) +{ + pthread_mutex_destroy(&g_scsi.mutex); +} + +SPDK_TRACE_REGISTER_FN(scsi_trace, "scsi", TRACE_GROUP_SCSI) +{ + spdk_trace_register_owner(OWNER_SCSI_DEV, 'd'); + spdk_trace_register_object(OBJECT_SCSI_TASK, 't'); + spdk_trace_register_description("SCSI_TASK_DONE", TRACE_SCSI_TASK_DONE, + OWNER_SCSI_DEV, OBJECT_SCSI_TASK, 0, 0, ""); + spdk_trace_register_description("SCSI_TASK_START", TRACE_SCSI_TASK_START, + OWNER_SCSI_DEV, OBJECT_SCSI_TASK, 0, 0, ""); +} + +uint64_t +spdk_scsi_lun_id_int_to_fmt(int lun_id) +{ + uint64_t fmt_lun, method; + + if (SPDK_SCSI_DEV_MAX_LUN <= 0x0100) { + /* below 256 */ + method = 0x00U; + fmt_lun = (method & 0x03U) << 62; + fmt_lun |= ((uint64_t)lun_id & 0x00ffU) << 48; + } else if (SPDK_SCSI_DEV_MAX_LUN <= 0x4000) { + /* below 16384 */ + method = 0x01U; + fmt_lun = (method & 0x03U) << 62; + fmt_lun |= ((uint64_t)lun_id & 0x3fffU) << 48; + } else { + /* XXX */ + fmt_lun = 0; + } + + return fmt_lun; +} + +int +spdk_scsi_lun_id_fmt_to_int(uint64_t fmt_lun) +{ + uint64_t method; + int lun_i; + + method = (fmt_lun >> 62) & 0x03U; + fmt_lun = fmt_lun >> 48; + if (method == 0x00U) { + lun_i = (int)(fmt_lun & 0x00ffU); + } else if (method == 0x01U) { + lun_i = (int)(fmt_lun & 0x3fffU); + } else { + lun_i = 0xffffU; + } + return lun_i; +} + +SPDK_LOG_REGISTER_COMPONENT("scsi", SPDK_LOG_SCSI) diff --git a/src/spdk/lib/scsi/scsi_bdev.c b/src/spdk/lib/scsi/scsi_bdev.c new file mode 100644 index 000000000..bf0fb5af7 --- /dev/null +++ b/src/spdk/lib/scsi/scsi_bdev.c @@ -0,0 +1,2067 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" + +/* + * TODO: move bdev SCSI error code translation tests to bdev unit test + * and remove this include. + */ +#include "spdk/bdev_module.h" + +#include "spdk/env.h" +#include "spdk/bdev.h" +#include "spdk/endian.h" +#include "spdk/likely.h" +#include "spdk/string.h" +#include "spdk/util.h" + +#define SPDK_WORK_BLOCK_SIZE (4ULL * 1024ULL * 1024ULL) +#define SPDK_WORK_ATS_BLOCK_SIZE (1ULL * 1024ULL * 1024ULL) +#define MAX_SERIAL_STRING 32 + +#define DEFAULT_DISK_VENDOR "INTEL" +#define DEFAULT_DISK_REVISION "0001" +#define DEFAULT_DISK_ROTATION_RATE 1 /* Non-rotating medium */ +#define DEFAULT_DISK_FORM_FACTOR 0x02 /* 3.5 inch */ +#define DEFAULT_MAX_UNMAP_BLOCK_DESCRIPTOR_COUNT 256 + +#define INQUIRY_OFFSET(field) offsetof(struct spdk_scsi_cdb_inquiry_data, field) + \ + sizeof(((struct spdk_scsi_cdb_inquiry_data *)0x0)->field) + +static void bdev_scsi_process_block_resubmit(void *arg); + +static int +hex2bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) { + return ch - '0'; + } + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) { + return ch - 'a' + 10; + } + return (int)ch; +} + +static void +bdev_scsi_set_naa_ieee_extended(const char *name, uint8_t *buf) +{ + int i, value, count = 0; + uint64_t local_value; + + for (i = 0; (i < 16) && (name[i] != '\0'); i++) { + value = hex2bin(name[i]); + if (i % 2) { + buf[count++] |= value << 4; + } else { + buf[count] = value; + } + } + + local_value = *(uint64_t *)buf; + /* + * see spc3r23 7.6.3.6.2, + * NAA IEEE Extended identifer format + */ + local_value &= 0x0fff000000ffffffull; + /* NAA 02, and 00 03 47 for IEEE Intel */ + local_value |= 0x2000000347000000ull; + + to_be64((void *)buf, local_value); +} + +static int +bdev_scsi_report_luns(struct spdk_scsi_lun *lun, + int sel, uint8_t *data, int alloc_len) +{ + struct spdk_scsi_dev *dev; + uint64_t fmt_lun; + int hlen, len = 0; + int i; + + if (alloc_len < 8) { + return -1; + } + + if (sel == 0x00) { + /* logical unit with addressing method */ + } else if (sel == 0x01) { + /* well known logical unit */ + } else if (sel == 0x02) { + /* logical unit */ + } else { + return -1; + } + + /* LUN LIST LENGTH */ + memset(data, 0, 4); + + /* Reserved */ + memset(&data[4], 0, 4); + hlen = 8; + + dev = lun->dev; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + if (dev->lun[i] == NULL) { + continue; + } + + if (alloc_len - (hlen + len) < 8) { + return -1; + } + + fmt_lun = spdk_scsi_lun_id_int_to_fmt(i); + + /* LUN */ + to_be64(&data[hlen + len], fmt_lun); + len += 8; + } + + /* LUN LIST LENGTH */ + to_be32(data, len); + + return hlen + len; +} + +static int +bdev_scsi_pad_scsi_name(char *dst, const char *name) +{ + size_t len; + + len = strlen(name); + memcpy(dst, name, len); + do { + dst[len++] = '\0'; + } while (len & 3); + + return len; +} + +static int +bdev_scsi_inquiry(struct spdk_bdev *bdev, struct spdk_scsi_task *task, + uint8_t *cdb, uint8_t *data, uint16_t alloc_len) +{ + struct spdk_scsi_lun *lun; + struct spdk_scsi_dev *dev; + struct spdk_scsi_port *port; + uint32_t blocks, optimal_blocks; + int hlen = 0, plen, plen2; + uint16_t len = 0; + int pc; + int pd; + int evpd; + int i; + struct spdk_scsi_cdb_inquiry *inq = (struct spdk_scsi_cdb_inquiry *)cdb; + + /* standard inquiry command at lease with 36 Bytes */ + if (alloc_len < 0x24) { + goto inq_error; + } + + lun = task->lun; + dev = lun->dev; + port = task->target_port; + + pd = SPDK_SPC_PERIPHERAL_DEVICE_TYPE_DISK; + pc = inq->page_code; + evpd = inq->evpd & 0x1; + + if (!evpd && pc) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + if (evpd) { + struct spdk_scsi_vpd_page *vpage = (struct spdk_scsi_vpd_page *)data; + + /* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */ + vpage->peripheral_device_type = pd; + vpage->peripheral_qualifier = SPDK_SPC_PERIPHERAL_QUALIFIER_CONNECTED; + /* PAGE CODE */ + vpage->page_code = pc; + + /* Vital product data */ + switch (pc) { + case SPDK_SPC_VPD_SUPPORTED_VPD_PAGES: + hlen = 4; + + vpage->params[0] = SPDK_SPC_VPD_SUPPORTED_VPD_PAGES; + vpage->params[1] = SPDK_SPC_VPD_UNIT_SERIAL_NUMBER; + vpage->params[2] = SPDK_SPC_VPD_DEVICE_IDENTIFICATION; + vpage->params[3] = SPDK_SPC_VPD_MANAGEMENT_NETWORK_ADDRESSES; + vpage->params[4] = SPDK_SPC_VPD_EXTENDED_INQUIRY_DATA; + vpage->params[5] = SPDK_SPC_VPD_MODE_PAGE_POLICY; + vpage->params[6] = SPDK_SPC_VPD_SCSI_PORTS; + vpage->params[7] = SPDK_SPC_VPD_BLOCK_LIMITS; + vpage->params[8] = SPDK_SPC_VPD_BLOCK_DEV_CHARS; + len = 9; + if (spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + vpage->params[9] = SPDK_SPC_VPD_BLOCK_THIN_PROVISION; + len++; + } + + /* PAGE LENGTH */ + to_be16(vpage->alloc_len, len); + break; + + case SPDK_SPC_VPD_UNIT_SERIAL_NUMBER: { + const char *name = spdk_bdev_get_name(bdev); + + hlen = 4; + + /* PRODUCT SERIAL NUMBER */ + len = strlen(name) + 1; + if (len > MAX_SERIAL_STRING) { + len = MAX_SERIAL_STRING; + } + + memcpy(vpage->params, name, len - 1); + vpage->params[len - 1] = 0; + + /* PAGE LENGTH */ + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_DEVICE_IDENTIFICATION: { + const char *name = spdk_bdev_get_name(bdev); + const char *product_name = spdk_bdev_get_product_name(bdev); + uint8_t protocol_id = dev->protocol_id; + uint8_t *buf = vpage->params; + struct spdk_scsi_desig_desc *desig; + + hlen = 4; + + /* Check total length by calculated how much space all entries take */ + len = sizeof(struct spdk_scsi_desig_desc) + 8; + len += sizeof(struct spdk_scsi_desig_desc) + 8 + 16 + MAX_SERIAL_STRING; + len += sizeof(struct spdk_scsi_desig_desc) + SPDK_SCSI_DEV_MAX_NAME + 1; + len += sizeof(struct spdk_scsi_desig_desc) + SPDK_SCSI_PORT_MAX_NAME_LENGTH; + len += sizeof(struct spdk_scsi_desig_desc) + 4; + len += sizeof(struct spdk_scsi_desig_desc) + 4; + len += sizeof(struct spdk_scsi_desig_desc) + 4; + if (sizeof(struct spdk_scsi_vpd_page) + len > alloc_len) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + /* Now fill out the designator array */ + + /* NAA designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_BINARY; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_NAA; + desig->association = SPDK_SPC_VPD_ASSOCIATION_LOGICAL_UNIT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = 8; + bdev_scsi_set_naa_ieee_extended(name, desig->desig); + len = sizeof(struct spdk_scsi_desig_desc) + 8; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* T10 Vendor ID designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_ASCII; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_T10_VENDOR_ID; + desig->association = SPDK_SPC_VPD_ASSOCIATION_LOGICAL_UNIT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = 8 + 16 + MAX_SERIAL_STRING; + spdk_strcpy_pad(desig->desig, DEFAULT_DISK_VENDOR, 8, ' '); + spdk_strcpy_pad(&desig->desig[8], product_name, 16, ' '); + spdk_strcpy_pad(&desig->desig[24], name, MAX_SERIAL_STRING, ' '); + len += sizeof(struct spdk_scsi_desig_desc) + 8 + 16 + MAX_SERIAL_STRING; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* SCSI Device Name designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_UTF8; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME; + desig->association = SPDK_SPC_VPD_ASSOCIATION_TARGET_DEVICE; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = bdev_scsi_pad_scsi_name(desig->desig, dev->name); + len += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* SCSI Port Name designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_UTF8; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME; + desig->association = SPDK_SPC_VPD_ASSOCIATION_TARGET_PORT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = snprintf(desig->desig, SPDK_SCSI_PORT_MAX_NAME_LENGTH, "%s", port->name); + len += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* Relative Target Port designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_BINARY; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_RELATIVE_TARGET_PORT; + desig->association = SPDK_SPC_VPD_ASSOCIATION_TARGET_PORT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = 4; + memset(desig->desig, 0, 2); /* Reserved */ + to_be16(&desig->desig[2], port->index); + len += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* Target port group designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_BINARY; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_TARGET_PORT_GROUP; + desig->association = SPDK_SPC_VPD_ASSOCIATION_TARGET_PORT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = 4; + memset(desig->desig, 0, 4); + len += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + buf += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + /* Logical unit group designator */ + desig = (struct spdk_scsi_desig_desc *)buf; + desig->code_set = SPDK_SPC_VPD_CODE_SET_BINARY; + desig->protocol_id = protocol_id; + desig->type = SPDK_SPC_VPD_IDENTIFIER_TYPE_LOGICAL_UNIT_GROUP; + desig->association = SPDK_SPC_VPD_ASSOCIATION_LOGICAL_UNIT; + desig->reserved0 = 0; + desig->piv = 1; + desig->reserved1 = 0; + desig->len = 4; + memset(desig->desig, 0, 2); /* Reserved */ + to_be16(&desig->desig[2], dev->id); + len += sizeof(struct spdk_scsi_desig_desc) + desig->len; + + to_be16(vpage->alloc_len, len); + + break; + } + + case SPDK_SPC_VPD_EXTENDED_INQUIRY_DATA: { + struct spdk_scsi_vpd_ext_inquiry *vext = (struct spdk_scsi_vpd_ext_inquiry *)vpage; + + hlen = 4; + memset((uint8_t *)vext + hlen, 0, sizeof(*vext) - hlen); + + /* RTO(3) GRD_CHK(2) APP_CHK(1) REF_CHK(0) */ + + /* GROUP_SUP(4) PRIOR_SUP(3) HEADSUP(2) ORDSUP(1) SIMPSUP(0) */ + vext->sup = SPDK_SCSI_VEXT_HEADSUP | SPDK_SCSI_VEXT_SIMPSUP; + + /* NV_SUP(1) V_SUP(0) */ + + /* Reserved[7-63] */ + + len = 64 - hlen; + + /* PAGE LENGTH */ + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_MANAGEMENT_NETWORK_ADDRESSES: + /* PAGE LENGTH */ + hlen = 4; + + to_be16(vpage->alloc_len, len); + break; + + case SPDK_SPC_VPD_MODE_PAGE_POLICY: { + struct spdk_scsi_mpage_policy_desc *pdesc = + (struct spdk_scsi_mpage_policy_desc *)vpage->params; + + hlen = 4; + + /* Mode page policy descriptor 1 */ + + /* POLICY PAGE CODE(5-0) */ + /* all page code */ + pdesc->page_code = 0x3f; + + /* POLICY SUBPAGE CODE */ + /* all sub page */ + pdesc->sub_page_code = 0xff; + + /* MLUS(7) MODE PAGE POLICY(1-0) */ + /* MLUS own copy */ + /* Shared MODE PAGE policy */ + pdesc->policy = 0; + /* Reserved */ + pdesc->reserved = 0; + + len += 4; + + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_SCSI_PORTS: { + /* PAGE LENGTH */ + hlen = 4; + + /* Identification descriptor list */ + for (i = 0; i < SPDK_SCSI_DEV_MAX_PORTS; i++) { + struct spdk_scsi_port_desc *sdesc; + struct spdk_scsi_tgt_port_desc *pdesc; + + if (!dev->port[i].is_used) { + continue; + } + + /* Identification descriptor N */ + sdesc = (struct spdk_scsi_port_desc *)&vpage->params[len]; + + /* Reserved */ + sdesc->reserved = 0; + + /* RELATIVE PORT IDENTIFIER */ + to_be16(&sdesc->rel_port_id, dev->port[i].index); + + /* Reserved */ + sdesc->reserved2 = 0; + + /* INITIATOR PORT TRANSPORTID LENGTH */ + sdesc->init_port_len = 0; + + /* Reserved */ + sdesc->init_port_id = 0; + + /* TARGET PORT DESCRIPTORS LENGTH */ + sdesc->tgt_desc_len = 0; + + len += 12; + + plen2 = 0; + /* Target port descriptor 1 */ + pdesc = (struct spdk_scsi_tgt_port_desc *)sdesc->tgt_desc; + + /* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */ + pdesc->code_set = + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI << 4 | + SPDK_SPC_VPD_CODE_SET_UTF8; + + /* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */ + pdesc->desig_type = SPDK_SPC_VPD_DESIG_PIV | + SPDK_SPC_VPD_ASSOCIATION_TARGET_PORT << 4 | + SPDK_SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME; + + /* Reserved */ + pdesc->reserved = 0; + + /* IDENTIFIER */ + plen = snprintf((char *)pdesc->designator, + SPDK_SCSI_PORT_MAX_NAME_LENGTH, "%s", + dev->port[i].name); + pdesc->len = plen; + + plen2 += 4 + plen; + + /* TARGET PORT DESCRIPTORS LENGTH */ + to_be16(&sdesc->tgt_desc_len, plen2); + + len += plen2; + } + + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_BLOCK_LIMITS: { + uint32_t block_size = spdk_bdev_get_data_block_size(bdev); + + /* PAGE LENGTH */ + memset(&data[4], 0, 60); + + hlen = 4; + + /* WSNZ(0) */ + /* support zero length in WRITE SAME */ + + /* MAXIMUM COMPARE AND WRITE LENGTH */ + blocks = SPDK_WORK_ATS_BLOCK_SIZE / block_size; + + if (blocks > 0xff) { + blocks = 0xff; + } + + data[5] = (uint8_t)blocks; + + /* force align to 4KB */ + if (block_size < 4096) { + optimal_blocks = 4096 / block_size; + } else { + optimal_blocks = 1; + } + + /* OPTIMAL TRANSFER LENGTH GRANULARITY */ + to_be16(&data[6], optimal_blocks); + + blocks = SPDK_WORK_BLOCK_SIZE / block_size; + + /* MAXIMUM TRANSFER LENGTH */ + to_be32(&data[8], blocks); + /* OPTIMAL TRANSFER LENGTH */ + to_be32(&data[12], blocks); + + /* MAXIMUM PREFETCH XDREAD XDWRITE TRANSFER LENGTH */ + + len = 20 - hlen; + + if (spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + /* + * MAXIMUM UNMAP LBA COUNT: indicates the + * maximum number of LBAs that may be + * unmapped by an UNMAP command. + */ + /* For now, choose 4MB as the maximum. */ + to_be32(&data[20], 4194304); + + /* + * MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT: + * indicates the maximum number of UNMAP + * block descriptors that shall be contained + * in the parameter data transferred to the + * device server for an UNMAP command. + * The bdev layer automatically splits unmap + * requests, so pick an arbitrary high number here. + */ + to_be32(&data[24], DEFAULT_MAX_UNMAP_BLOCK_DESCRIPTOR_COUNT); + + /* + * The UGAVALID bit is left as 0 which means neither the + * OPTIMAL UNMAP GRANULARITY nor the UNMAP GRANULARITY + * ALIGNMENT fields are valid. + */ + + /* + * MAXIMUM WRITE SAME LENGTH: indicates the + * maximum number of contiguous logical blocks + * that the device server allows to be unmapped + * or written in a single WRITE SAME command. + */ + to_be64(&data[36], 512); + + /* Reserved */ + /* not specified */ + len = 64 - hlen; + } + + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_BLOCK_DEV_CHARS: { + /* PAGE LENGTH */ + hlen = 4; + len = 64 - hlen; + + to_be16(&data[4], DEFAULT_DISK_ROTATION_RATE); + + /* Reserved */ + data[6] = 0; + /* NOMINAL FORM FACTOR(3-0) */ + data[7] = DEFAULT_DISK_FORM_FACTOR << 4; + /* Reserved */ + memset(&data[8], 0, 64 - 8); + + to_be16(vpage->alloc_len, len); + break; + } + + case SPDK_SPC_VPD_BLOCK_THIN_PROVISION: { + if (!spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + goto inq_error; + } + + hlen = 4; + len = 7; + + /* + * PAGE LENGTH : if the DP bit is set to one, then the + * page length shall be set 0004h. + */ + to_be16(&data[2], 0x0004); + + /* + * THRESHOLD EXPONENT : it indicates the threshold set + * size in LBAs as a power of 2( i.e., the threshold + * set size = 2 ^ (threshold exponent). + */ + data[4] = 0; + + /* + * Set the LBPU bit to indicate the support for UNMAP + * command. + */ + data[5] |= SPDK_SCSI_UNMAP_LBPU; + + /* + * Set the provisioning type to thin provision. + */ + data[6] = SPDK_SCSI_UNMAP_THIN_PROVISIONING; + + to_be16(vpage->alloc_len, len); + break; + } + + default: + if (pc >= 0xc0 && pc <= 0xff) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "Vendor specific INQUIRY VPD page 0x%x\n", pc); + } else { + SPDK_ERRLOG("unsupported INQUIRY VPD page 0x%x\n", pc); + } + goto inq_error; + } + } else { + struct spdk_scsi_cdb_inquiry_data *inqdata = + (struct spdk_scsi_cdb_inquiry_data *)data; + + /* Standard INQUIRY data */ + /* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */ + inqdata->peripheral_device_type = pd; + inqdata->peripheral_qualifier = SPDK_SPC_PERIPHERAL_QUALIFIER_CONNECTED; + + /* RMB(7) */ + inqdata->rmb = 0; + + /* VERSION */ + /* See SPC3/SBC2/MMC4/SAM2 for more details */ + inqdata->version = SPDK_SPC_VERSION_SPC3; + + /* NORMACA(5) HISUP(4) RESPONSE DATA FORMAT(3-0) */ + /* format 2 */ /* hierarchical support */ + inqdata->response = 2 | 1 << 4; + + hlen = 5; + + /* SCCS(7) ACC(6) TPGS(5-4) 3PC(3) PROTECT(0) */ + /* Not support TPGS */ + inqdata->flags = 0; + + /* MULTIP */ + inqdata->flags2 = 0x10; + + /* WBUS16(5) SYNC(4) LINKED(3) CMDQUE(1) VS(0) */ + /* CMDQUE */ + inqdata->flags3 = 0x2; + + /* T10 VENDOR IDENTIFICATION */ + spdk_strcpy_pad(inqdata->t10_vendor_id, DEFAULT_DISK_VENDOR, 8, ' '); + + /* PRODUCT IDENTIFICATION */ + spdk_strcpy_pad(inqdata->product_id, spdk_bdev_get_product_name(bdev), 16, ' '); + + /* PRODUCT REVISION LEVEL */ + spdk_strcpy_pad(inqdata->product_rev, DEFAULT_DISK_REVISION, 4, ' '); + + /* + * Standard inquiry data ends here. Only populate remaining fields if alloc_len + * indicates enough space to hold it. + */ + len = INQUIRY_OFFSET(product_rev) - 5; + + if (alloc_len >= INQUIRY_OFFSET(vendor)) { + /* Vendor specific */ + memset(inqdata->vendor, 0x20, 20); + len += sizeof(inqdata->vendor); + } + + if (alloc_len >= INQUIRY_OFFSET(ius)) { + /* CLOCKING(3-2) QAS(1) IUS(0) */ + inqdata->ius = 0; + len += sizeof(inqdata->ius); + } + + if (alloc_len >= INQUIRY_OFFSET(reserved)) { + /* Reserved */ + inqdata->reserved = 0; + len += sizeof(inqdata->reserved); + } + + /* VERSION DESCRIPTOR 1-8 */ + if (alloc_len >= INQUIRY_OFFSET(reserved) + 2) { + to_be16(&inqdata->desc[0], 0x0960); + len += 2; + } + + if (alloc_len >= INQUIRY_OFFSET(reserved) + 4) { + to_be16(&inqdata->desc[2], 0x0300); /* SPC-3 (no version claimed) */ + len += 2; + } + + if (alloc_len >= INQUIRY_OFFSET(reserved) + 6) { + to_be16(&inqdata->desc[4], 0x320); /* SBC-2 (no version claimed) */ + len += 2; + } + + if (alloc_len >= INQUIRY_OFFSET(reserved) + 8) { + to_be16(&inqdata->desc[6], 0x0040); /* SAM-2 (no version claimed) */ + len += 2; + } + + /* + * We only fill out 4 descriptors, but if the allocation length goes past + * that, zero the remaining bytes. This fixes some SCSI compliance tests + * which expect a full 96 bytes to be returned, including the unpopulated + * version descriptors 5-8 (4 * 2 = 8 bytes) plus the 22 bytes of reserved + * space (bytes 74-95) - for a total of 30 bytes. + */ + if (alloc_len > INQUIRY_OFFSET(reserved) + 8) { + i = alloc_len - (INQUIRY_OFFSET(reserved) + 8); + if (i > 30) { + i = 30; + } + memset(&inqdata->desc[8], 0, i); + len += i; + } + + /* ADDITIONAL LENGTH */ + inqdata->add_len = len; + } + + return hlen + len; + +inq_error: + task->data_transferred = 0; + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; +} + +static void +mode_sense_page_init(uint8_t *buf, int len, int page, int subpage) +{ + if (!buf) { + return; + } + + memset(buf, 0, len); + if (subpage != 0) { + buf[0] = page | 0x40; /* PAGE + SPF=1 */ + buf[1] = subpage; + to_be16(&buf[2], len - 4); + } else { + buf[0] = page; + buf[1] = len - 2; + } +} + +static int +bdev_scsi_mode_sense_page(struct spdk_bdev *bdev, + uint8_t *cdb, int pc, int page, int subpage, + uint8_t *data, struct spdk_scsi_task *task) +{ + uint8_t *cp = data; + int len = 0; + int plen; + int i; + + if (pc == 0x00) { + /* Current values */ + } else if (pc == 0x01) { + /* Changeable values */ + /* As we currently do not support changeable values, + all parameters are reported as zero. */ + } else if (pc == 0x02) { + /* Default values */ + } else { + /* Saved values not supported */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + switch (page) { + case 0x00: + /* Vendor specific */ + break; + case 0x01: + /* Read-Write Error Recovery */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Read-Write Error Recovery\n"); + if (subpage != 0x00) { + break; + } + plen = 0x0a + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x02: + /* Disconnect-Reconnect */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Disconnect-Reconnect\n"); + if (subpage != 0x00) { + break; + } + plen = 0x0e + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x03: + /* Obsolete (Format Device) */ + break; + case 0x04: + /* Obsolete (Rigid Disk Geometry) */ + break; + case 0x05: + /* Obsolete (Rigid Disk Geometry) */ + break; + case 0x06: + /* Reserved */ + break; + case 0x07: + /* Verify Error Recovery */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Verify Error Recovery\n"); + + if (subpage != 0x00) { + break; + } + + plen = 0x0a + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x08: { + /* Caching */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "MODE_SENSE Caching\n"); + if (subpage != 0x00) { + break; + } + + plen = 0x12 + 2; + mode_sense_page_init(cp, plen, page, subpage); + + if (cp && spdk_bdev_has_write_cache(bdev) && pc != 0x01) { + cp[2] |= 0x4; /* WCE */ + } + + /* Read Cache Disable (RCD) = 1 */ + if (cp && pc != 0x01) { + cp[2] |= 0x1; + } + + len += plen; + break; + } + case 0x09: + /* Obsolete */ + break; + case 0x0a: + switch (subpage) { + case 0x00: + /* Control */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Control\n"); + plen = 0x0a + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x01: + /* Control Extension */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Control Extension\n"); + plen = 0x1c + 4; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0xff: + /* All subpages */ + len += bdev_scsi_mode_sense_page(bdev, + cdb, pc, page, + 0x00, + cp ? &cp[len] : NULL, task); + len += bdev_scsi_mode_sense_page(bdev, + cdb, pc, page, + 0x01, + cp ? &cp[len] : NULL, task); + break; + default: + /* 0x02-0x3e: Reserved */ + break; + } + break; + case 0x0b: + /* Obsolete (Medium Types Supported) */ + break; + case 0x0c: + /* Obsolete (Notch And Partitio) */ + break; + case 0x0d: + /* Obsolete */ + break; + case 0x0e: + case 0x0f: + /* Reserved */ + break; + case 0x10: + /* XOR Control */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "MODE_SENSE XOR Control\n"); + if (subpage != 0x00) { + break; + } + plen = 0x16 + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x11: + case 0x12: + case 0x13: + /* Reserved */ + break; + case 0x14: + /* Enclosure Services Management */ + break; + case 0x15: + case 0x16: + case 0x17: + /* Reserved */ + break; + case 0x18: + /* Protocol-Specific LUN */ + break; + case 0x19: + /* Protocol-Specific Port */ + break; + case 0x1a: + /* Power Condition */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Power Condition\n"); + if (subpage != 0x00) { + break; + } + plen = 0x0a + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x1b: + /* Reserved */ + break; + case 0x1c: + /* Informational Exceptions Control */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "MODE_SENSE Informational Exceptions Control\n"); + if (subpage != 0x00) { + break; + } + + plen = 0x0a + 2; + mode_sense_page_init(cp, plen, page, subpage); + len += plen; + break; + case 0x1d: + case 0x1e: + case 0x1f: + /* Reserved */ + break; + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + /* Vendor-specific */ + break; + case 0x3f: + switch (subpage) { + case 0x00: + /* All mode pages */ + for (i = 0x00; i < 0x3e; i ++) { + len += bdev_scsi_mode_sense_page( + bdev, cdb, pc, i, 0x00, + cp ? &cp[len] : NULL, task); + } + break; + case 0xff: + /* All mode pages and subpages */ + for (i = 0x00; i < 0x3e; i ++) { + len += bdev_scsi_mode_sense_page( + bdev, cdb, pc, i, 0x00, + cp ? &cp[len] : NULL, task); + } + for (i = 0x00; i < 0x3e; i ++) { + len += bdev_scsi_mode_sense_page( + bdev, cdb, pc, i, 0xff, + cp ? &cp[len] : NULL, task); + } + break; + default: + /* 0x01-0x3e: Reserved */ + break; + } + } + + return len; +} + +static int +bdev_scsi_mode_sense(struct spdk_bdev *bdev, int md, + uint8_t *cdb, int dbd, int llbaa, int pc, + int page, int subpage, uint8_t *data, struct spdk_scsi_task *task) +{ + uint64_t num_blocks = spdk_bdev_get_num_blocks(bdev); + uint32_t block_size = spdk_bdev_get_data_block_size(bdev); + uint8_t *hdr, *bdesc, *pages; + int hlen; + int blen; + int plen, total; + + assert(md == 6 || md == 10); + + if (md == 6) { + hlen = 4; + blen = 8; /* For MODE SENSE 6 only short LBA */ + } else { + hlen = 8; + blen = llbaa ? 16 : 8; + } + + if (dbd) { + blen = 0; + } + + pages = data ? &data[hlen + blen] : NULL; + plen = bdev_scsi_mode_sense_page(bdev, cdb, pc, page, + subpage, + pages, task); + if (plen < 0) { + return -1; + } + + total = hlen + blen + plen; + if (data == NULL) { + return total; + } + + hdr = &data[0]; + if (hlen == 4) { + hdr[0] = total - 1; /* Mode Data Length */ + hdr[1] = 0; /* Medium Type */ + hdr[2] = 0; /* Device-Specific Parameter */ + hdr[3] = blen; /* Block Descripter Length */ + } else { + to_be16(&hdr[0], total - 2); /* Mode Data Length */ + hdr[2] = 0; /* Medium Type */ + hdr[3] = 0; /* Device-Specific Parameter */ + hdr[4] = llbaa ? 0x1 : 0; /* Long/short LBA */ + hdr[5] = 0; /* Reserved */ + to_be16(&hdr[6], blen); /* Block Descripter Length */ + } + + bdesc = &data[hlen]; + if (blen == 16) { + /* Number of Blocks */ + to_be64(&bdesc[0], num_blocks); + /* Reserved */ + memset(&bdesc[8], 0, 4); + /* Block Length */ + to_be32(&bdesc[12], block_size); + } else if (blen == 8) { + /* Number of Blocks */ + if (num_blocks > 0xffffffffULL) { + memset(&bdesc[0], 0xff, 4); + } else { + to_be32(&bdesc[0], num_blocks); + } + + /* Block Length */ + to_be32(&bdesc[4], block_size); + } + + return total; +} + +static void +bdev_scsi_task_complete_cmd(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + struct spdk_scsi_task *task = cb_arg; + int sc, sk, asc, ascq; + + spdk_bdev_io_get_scsi_status(bdev_io, &sc, &sk, &asc, &ascq); + + spdk_bdev_free_io(bdev_io); + + spdk_scsi_task_set_status(task, sc, sk, asc, ascq); + scsi_lun_complete_task(task->lun, task); +} + +static void +bdev_scsi_read_task_complete_cmd(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + struct spdk_scsi_task *task = cb_arg; + int sc, sk, asc, ascq; + + task->bdev_io = bdev_io; + + spdk_bdev_io_get_scsi_status(bdev_io, &sc, &sk, &asc, &ascq); + + spdk_scsi_task_set_status(task, sc, sk, asc, ascq); + scsi_lun_complete_task(task->lun, task); +} + +static void +bdev_scsi_task_complete_reset(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + struct spdk_scsi_task *task = cb_arg; + + spdk_bdev_free_io(bdev_io); + + if (success) { + task->response = SPDK_SCSI_TASK_MGMT_RESP_SUCCESS; + } + + scsi_lun_complete_reset_task(task->lun, task); +} + +static void +bdev_scsi_queue_io(struct spdk_scsi_task *task, spdk_bdev_io_wait_cb cb_fn, void *cb_arg) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_bdev *bdev = lun->bdev; + struct spdk_io_channel *ch = lun->io_channel; + int rc; + + task->bdev_io_wait.bdev = bdev; + task->bdev_io_wait.cb_fn = cb_fn; + task->bdev_io_wait.cb_arg = cb_arg; + + rc = spdk_bdev_queue_io_wait(bdev, ch, &task->bdev_io_wait); + if (rc != 0) { + assert(false); + } +} + +static int +bdev_scsi_sync(struct spdk_bdev *bdev, struct spdk_bdev_desc *bdev_desc, + struct spdk_io_channel *bdev_ch, struct spdk_scsi_task *task, + uint64_t lba, uint32_t num_blocks) +{ + uint64_t bdev_num_blocks; + int rc; + + if (num_blocks == 0) { + return SPDK_SCSI_TASK_COMPLETE; + } + + bdev_num_blocks = spdk_bdev_get_num_blocks(bdev); + + if (lba >= bdev_num_blocks || num_blocks > bdev_num_blocks || + lba > (bdev_num_blocks - num_blocks)) { + SPDK_ERRLOG("end of media\n"); + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return SPDK_SCSI_TASK_COMPLETE; + } + + rc = spdk_bdev_flush_blocks(bdev_desc, bdev_ch, lba, num_blocks, + bdev_scsi_task_complete_cmd, task); + + if (rc) { + if (rc == -ENOMEM) { + bdev_scsi_queue_io(task, bdev_scsi_process_block_resubmit, task); + return SPDK_SCSI_TASK_PENDING; + } + SPDK_ERRLOG("spdk_bdev_flush_blocks() failed\n"); + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return SPDK_SCSI_TASK_COMPLETE; + } + task->data_transferred = 0; + return SPDK_SCSI_TASK_PENDING; +} + +static uint64_t +_bytes_to_blocks(uint32_t block_size, uint64_t offset_bytes, uint64_t *offset_blocks, + uint64_t num_bytes, uint64_t *num_blocks) +{ + uint8_t shift_cnt; + + /* Avoid expensive div operations if possible. These spdk_u32 functions are very cheap. */ + if (spdk_likely(spdk_u32_is_pow2(block_size))) { + shift_cnt = spdk_u32log2(block_size); + *offset_blocks = offset_bytes >> shift_cnt; + *num_blocks = num_bytes >> shift_cnt; + return (offset_bytes - (*offset_blocks << shift_cnt)) | + (num_bytes - (*num_blocks << shift_cnt)); + } else { + *offset_blocks = offset_bytes / block_size; + *num_blocks = num_bytes / block_size; + return (offset_bytes % block_size) | (num_bytes % block_size); + } +} + +static int +bdev_scsi_readwrite(struct spdk_bdev *bdev, struct spdk_bdev_desc *bdev_desc, + struct spdk_io_channel *bdev_ch, struct spdk_scsi_task *task, + uint64_t lba, uint32_t xfer_len, bool is_read) +{ + uint64_t bdev_num_blocks, offset_blocks, num_blocks; + uint32_t max_xfer_len, block_size; + int sk = SPDK_SCSI_SENSE_NO_SENSE, asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + int rc; + + task->data_transferred = 0; + + if (spdk_unlikely(task->dxfer_dir != SPDK_SCSI_DIR_NONE && + task->dxfer_dir != (is_read ? SPDK_SCSI_DIR_FROM_DEV : SPDK_SCSI_DIR_TO_DEV))) { + SPDK_ERRLOG("Incorrect data direction\n"); + goto check_condition; + } + + bdev_num_blocks = spdk_bdev_get_num_blocks(bdev); + if (spdk_unlikely(bdev_num_blocks <= lba || bdev_num_blocks - lba < xfer_len)) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "end of media\n"); + sk = SPDK_SCSI_SENSE_ILLEGAL_REQUEST; + asc = SPDK_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + goto check_condition; + } + + if (spdk_unlikely(xfer_len == 0)) { + task->status = SPDK_SCSI_STATUS_GOOD; + return SPDK_SCSI_TASK_COMPLETE; + } + + block_size = spdk_bdev_get_data_block_size(bdev); + + /* Transfer Length is limited to the Block Limits VPD page Maximum Transfer Length */ + max_xfer_len = SPDK_WORK_BLOCK_SIZE / block_size; + if (spdk_unlikely(xfer_len > max_xfer_len)) { + SPDK_ERRLOG("xfer_len %" PRIu32 " > maximum transfer length %" PRIu32 "\n", + xfer_len, max_xfer_len); + sk = SPDK_SCSI_SENSE_ILLEGAL_REQUEST; + asc = SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB; + goto check_condition; + } + + if (!is_read) { + /* Additional check for Transfer Length */ + if (xfer_len * block_size > task->transfer_len) { + SPDK_ERRLOG("xfer_len %" PRIu32 " * block_size %" PRIu32 " > transfer_len %u\n", + xfer_len, block_size, task->transfer_len); + goto check_condition; + } + } + + if (_bytes_to_blocks(block_size, task->offset, &offset_blocks, task->length, &num_blocks) != 0) { + SPDK_ERRLOG("task's offset %" PRIu64 " or length %" PRIu32 " is not block multiple\n", + task->offset, task->length); + goto check_condition; + } + + offset_blocks += lba; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, + "%s: lba=%"PRIu64", len=%"PRIu64"\n", + is_read ? "Read" : "Write", offset_blocks, num_blocks); + + if (is_read) { + rc = spdk_bdev_readv_blocks(bdev_desc, bdev_ch, task->iovs, task->iovcnt, + offset_blocks, num_blocks, + bdev_scsi_read_task_complete_cmd, task); + } else { + rc = spdk_bdev_writev_blocks(bdev_desc, bdev_ch, task->iovs, task->iovcnt, + offset_blocks, num_blocks, + bdev_scsi_task_complete_cmd, task); + } + + if (rc) { + if (rc == -ENOMEM) { + bdev_scsi_queue_io(task, bdev_scsi_process_block_resubmit, task); + return SPDK_SCSI_TASK_PENDING; + } + SPDK_ERRLOG("spdk_bdev_%s_blocks() failed\n", is_read ? "readv" : "writev"); + goto check_condition; + } + + task->data_transferred = task->length; + return SPDK_SCSI_TASK_PENDING; + +check_condition: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, sk, asc, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return SPDK_SCSI_TASK_COMPLETE; +} + +struct spdk_bdev_scsi_unmap_ctx { + struct spdk_scsi_task *task; + struct spdk_scsi_unmap_bdesc desc[DEFAULT_MAX_UNMAP_BLOCK_DESCRIPTOR_COUNT]; + uint32_t count; +}; + +static int bdev_scsi_unmap(struct spdk_bdev *bdev, struct spdk_bdev_desc *bdev_desc, + struct spdk_io_channel *bdev_ch, struct spdk_scsi_task *task, + struct spdk_bdev_scsi_unmap_ctx *ctx); + +static void +bdev_scsi_task_complete_unmap_cmd(struct spdk_bdev_io *bdev_io, bool success, + void *cb_arg) +{ + struct spdk_bdev_scsi_unmap_ctx *ctx = cb_arg; + struct spdk_scsi_task *task = ctx->task; + int sc, sk, asc, ascq; + + ctx->count--; + + task->bdev_io = bdev_io; + + if (task->status == SPDK_SCSI_STATUS_GOOD) { + spdk_bdev_io_get_scsi_status(bdev_io, &sc, &sk, &asc, &ascq); + spdk_scsi_task_set_status(task, sc, sk, asc, ascq); + } + + if (ctx->count == 0) { + scsi_lun_complete_task(task->lun, task); + free(ctx); + } +} + +static int +__copy_desc(struct spdk_bdev_scsi_unmap_ctx *ctx, uint8_t *data, size_t data_len) +{ + uint16_t desc_data_len; + uint16_t desc_count; + + if (!data) { + return -EINVAL; + } + + if (data_len < 8) { + /* We can't even get the reported length, so fail. */ + return -EINVAL; + } + + desc_data_len = from_be16(&data[2]); + desc_count = desc_data_len / 16; + + if (desc_data_len > (data_len - 8)) { + SPDK_ERRLOG("Error - desc_data_len (%u) > data_len (%lu) - 8\n", + desc_data_len, data_len); + return -EINVAL; + } + + if (desc_count > DEFAULT_MAX_UNMAP_BLOCK_DESCRIPTOR_COUNT) { + SPDK_ERRLOG("desc_count (%u) greater than max allowed (%u)\n", + desc_count, DEFAULT_MAX_UNMAP_BLOCK_DESCRIPTOR_COUNT); + return -EINVAL; + } + + memcpy(ctx->desc, &data[8], desc_data_len); + return desc_count; +} + +static void +bdev_scsi_unmap_resubmit(void *arg) +{ + struct spdk_bdev_scsi_unmap_ctx *ctx = arg; + struct spdk_scsi_task *task = ctx->task; + struct spdk_scsi_lun *lun = task->lun; + + bdev_scsi_unmap(lun->bdev, lun->bdev_desc, lun->io_channel, task, ctx); +} + +static int +bdev_scsi_unmap(struct spdk_bdev *bdev, struct spdk_bdev_desc *bdev_desc, + struct spdk_io_channel *bdev_ch, struct spdk_scsi_task *task, + struct spdk_bdev_scsi_unmap_ctx *ctx) +{ + uint8_t *data; + int i, desc_count = -1; + int data_len; + int rc; + + assert(task->status == SPDK_SCSI_STATUS_GOOD); + + if (ctx == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return SPDK_SCSI_TASK_COMPLETE; + } + + ctx->task = task; + ctx->count = 0; + } + + + if (task->iovcnt == 1) { + data = (uint8_t *)task->iovs[0].iov_base; + data_len = task->iovs[0].iov_len; + desc_count = __copy_desc(ctx, data, data_len); + } else { + data = spdk_scsi_task_gather_data(task, &data_len); + if (data) { + desc_count = __copy_desc(ctx, data, data_len); + free(data); + } + } + + if (desc_count < 0) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + free(ctx); + return SPDK_SCSI_TASK_COMPLETE; + } + + for (i = ctx->count; i < desc_count; i++) { + struct spdk_scsi_unmap_bdesc *desc; + uint64_t offset_blocks; + uint64_t num_blocks; + + desc = &ctx->desc[i]; + + offset_blocks = from_be64(&desc->lba); + num_blocks = from_be32(&desc->block_count); + + if (num_blocks == 0) { + continue; + } + + ctx->count++; + rc = spdk_bdev_unmap_blocks(bdev_desc, bdev_ch, offset_blocks, num_blocks, + bdev_scsi_task_complete_unmap_cmd, ctx); + + if (rc) { + if (rc == -ENOMEM) { + bdev_scsi_queue_io(task, bdev_scsi_unmap_resubmit, ctx); + /* Unmap was not yet submitted to bdev */ + ctx->count--; + return SPDK_SCSI_TASK_PENDING; + } + SPDK_ERRLOG("SCSI Unmapping failed\n"); + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + ctx->count--; + /* We can't complete here - we may have to wait for previously + * submitted unmaps to complete */ + break; + } + } + + if (ctx->count == 0) { + free(ctx); + return SPDK_SCSI_TASK_COMPLETE; + } + + return SPDK_SCSI_TASK_PENDING; +} + +static int +bdev_scsi_process_block(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_bdev *bdev = lun->bdev; + uint64_t lba; + uint32_t xfer_len; + uint32_t len = 0; + uint8_t *cdb = task->cdb; + + /* XXX: We need to support FUA bit for writes! */ + switch (cdb[0]) { + case SPDK_SBC_READ_6: + case SPDK_SBC_WRITE_6: + lba = (uint64_t)cdb[1] << 16; + lba |= (uint64_t)cdb[2] << 8; + lba |= (uint64_t)cdb[3]; + xfer_len = cdb[4]; + if (xfer_len == 0) { + xfer_len = 256; + } + return bdev_scsi_readwrite(bdev, lun->bdev_desc, lun->io_channel, + task, lba, xfer_len, + cdb[0] == SPDK_SBC_READ_6); + + case SPDK_SBC_READ_10: + case SPDK_SBC_WRITE_10: + lba = from_be32(&cdb[2]); + xfer_len = from_be16(&cdb[7]); + return bdev_scsi_readwrite(bdev, lun->bdev_desc, lun->io_channel, + task, lba, xfer_len, + cdb[0] == SPDK_SBC_READ_10); + + case SPDK_SBC_READ_12: + case SPDK_SBC_WRITE_12: + lba = from_be32(&cdb[2]); + xfer_len = from_be32(&cdb[6]); + return bdev_scsi_readwrite(bdev, lun->bdev_desc, lun->io_channel, + task, lba, xfer_len, + cdb[0] == SPDK_SBC_READ_12); + case SPDK_SBC_READ_16: + case SPDK_SBC_WRITE_16: + lba = from_be64(&cdb[2]); + xfer_len = from_be32(&cdb[10]); + return bdev_scsi_readwrite(bdev, lun->bdev_desc, lun->io_channel, + task, lba, xfer_len, + cdb[0] == SPDK_SBC_READ_16); + + case SPDK_SBC_READ_CAPACITY_10: { + uint64_t num_blocks = spdk_bdev_get_num_blocks(bdev); + uint8_t buffer[8]; + + if (num_blocks - 1 > 0xffffffffULL) { + memset(buffer, 0xff, 4); + } else { + to_be32(buffer, num_blocks - 1); + } + to_be32(&buffer[4], spdk_bdev_get_data_block_size(bdev)); + + len = spdk_min(task->length, sizeof(buffer)); + if (spdk_scsi_task_scatter_data(task, buffer, len) < 0) { + break; + } + + task->data_transferred = len; + task->status = SPDK_SCSI_STATUS_GOOD; + break; + } + + case SPDK_SPC_SERVICE_ACTION_IN_16: + switch (cdb[1] & 0x1f) { /* SERVICE ACTION */ + case SPDK_SBC_SAI_READ_CAPACITY_16: { + uint8_t buffer[32] = {0}; + + to_be64(&buffer[0], spdk_bdev_get_num_blocks(bdev) - 1); + to_be32(&buffer[8], spdk_bdev_get_data_block_size(bdev)); + /* + * Set the TPE bit to 1 to indicate thin provisioning. + * The position of TPE bit is the 7th bit in 14th byte + * in READ CAPACITY (16) parameter data. + */ + if (spdk_bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_UNMAP)) { + buffer[14] |= 1 << 7; + } + + len = spdk_min(from_be32(&cdb[10]), sizeof(buffer)); + if (spdk_scsi_task_scatter_data(task, buffer, len) < 0) { + break; + } + + task->data_transferred = len; + task->status = SPDK_SCSI_STATUS_GOOD; + break; + } + + default: + return SPDK_SCSI_TASK_UNKNOWN; + } + break; + + case SPDK_SBC_SYNCHRONIZE_CACHE_10: + case SPDK_SBC_SYNCHRONIZE_CACHE_16: + if (cdb[0] == SPDK_SBC_SYNCHRONIZE_CACHE_10) { + lba = from_be32(&cdb[2]); + len = from_be16(&cdb[7]); + } else { + lba = from_be64(&cdb[2]); + len = from_be32(&cdb[10]); + } + + if (len == 0) { + len = spdk_bdev_get_num_blocks(bdev) - lba; + } + + return bdev_scsi_sync(bdev, lun->bdev_desc, lun->io_channel, task, lba, len); + break; + + case SPDK_SBC_UNMAP: + return bdev_scsi_unmap(bdev, lun->bdev_desc, lun->io_channel, task, NULL); + + default: + return SPDK_SCSI_TASK_UNKNOWN; + } + + return SPDK_SCSI_TASK_COMPLETE; +} + +static void +bdev_scsi_process_block_resubmit(void *arg) +{ + struct spdk_scsi_task *task = arg; + + bdev_scsi_process_block(task); +} + +static int +bdev_scsi_check_len(struct spdk_scsi_task *task, int len, int min_len) +{ + if (len >= min_len) { + return 0; + } + + /* INVALID FIELD IN CDB */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; +} + +static int +bdev_scsi_process_primary(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_bdev *bdev = lun->bdev; + int alloc_len = -1; + int data_len = -1; + uint8_t *cdb = task->cdb; + uint8_t *data = NULL; + int rc = 0; + int pllen, md = 0; + int llba; + int dbd, pc, page, subpage; + int cmd_parsed = 0; + + switch (cdb[0]) { + case SPDK_SPC_INQUIRY: + alloc_len = from_be16(&cdb[3]); + data_len = spdk_max(4096, alloc_len); + data = calloc(1, data_len); + assert(data != NULL); + rc = bdev_scsi_inquiry(bdev, task, cdb, data, data_len); + data_len = spdk_min(rc, data_len); + if (rc < 0) { + break; + } + + SPDK_LOGDUMP(SPDK_LOG_SCSI, "INQUIRY", data, data_len); + break; + + case SPDK_SPC_REPORT_LUNS: { + int sel; + + sel = cdb[2]; + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "sel=%x\n", sel); + + alloc_len = from_be32(&cdb[6]); + rc = bdev_scsi_check_len(task, alloc_len, 16); + if (rc < 0) { + break; + } + + data_len = spdk_max(4096, alloc_len); + data = calloc(1, data_len); + assert(data != NULL); + rc = bdev_scsi_report_luns(task->lun, sel, data, data_len); + data_len = rc; + if (rc < 0) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + break; + } + + SPDK_LOGDUMP(SPDK_LOG_SCSI, "REPORT LUNS", data, data_len); + break; + } + + case SPDK_SPC_MODE_SELECT_6: + case SPDK_SPC_MODE_SELECT_10: + if (cdb[0] == SPDK_SPC_MODE_SELECT_6) { + /* MODE_SELECT(6) must have at least a 4 byte header. */ + md = 4; + pllen = cdb[4]; + } else { + /* MODE_SELECT(10) must have at least an 8 byte header. */ + md = 8; + pllen = from_be16(&cdb[7]); + } + + if (pllen == 0) { + break; + } + + rc = bdev_scsi_check_len(task, pllen, md); + if (rc < 0) { + break; + } + + data = spdk_scsi_task_gather_data(task, &rc); + if (rc < 0) { + break; + } + data_len = rc; + + rc = bdev_scsi_check_len(task, data_len, spdk_max(pllen, md)); + if (rc < 0) { + break; + } + + rc = pllen; + data_len = 0; + break; + + case SPDK_SPC_MODE_SENSE_6: + alloc_len = cdb[4]; + md = 6; + /* FALLTHROUGH */ + case SPDK_SPC_MODE_SENSE_10: + llba = 0; + + if (md == 0) { + alloc_len = from_be16(&cdb[7]); + llba = !!(cdb[1] & 0x10); + md = 10; + } + + dbd = !!(cdb[1] & 0x8); + pc = (cdb[2] & 0xc0) >> 6; + page = cdb[2] & 0x3f; + subpage = cdb[3]; + + /* First call with no buffer to discover needed buffer size */ + rc = bdev_scsi_mode_sense(bdev, md, + cdb, dbd, llba, pc, + page, subpage, + NULL, task); + if (rc < 0) { + break; + } + + data_len = rc; + data = calloc(1, data_len); + assert(data != NULL); + + /* First call with no buffer to discover needed buffer size */ + rc = bdev_scsi_mode_sense(bdev, md, + cdb, dbd, llba, pc, + page, subpage, + data, task); + if (rc < 0) { + /* INVALID FIELD IN CDB */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + break; + } + break; + + case SPDK_SPC_REQUEST_SENSE: { + int desc; + int sk, asc, ascq; + + desc = cdb[1] & 0x1; + if (desc != 0) { + /* INVALID FIELD IN CDB */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + break; + } + + alloc_len = cdb[4]; + + /* NO ADDITIONAL SENSE INFORMATION */ + sk = SPDK_SCSI_SENSE_NO_SENSE; + asc = 0x00; + ascq = 0x00; + + spdk_scsi_task_build_sense_data(task, sk, asc, ascq); + + data_len = task->sense_data_len; + data = calloc(1, data_len); + assert(data != NULL); + memcpy(data, task->sense_data, data_len); + break; + } + + case SPDK_SPC_LOG_SELECT: + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "LOG_SELECT\n"); + cmd_parsed = 1; + /* FALLTHROUGH */ + case SPDK_SPC_LOG_SENSE: + if (!cmd_parsed) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "LOG_SENSE\n"); + } + + /* INVALID COMMAND OPERATION CODE */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_COMMAND_OPERATION_CODE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + rc = -1; + break; + + case SPDK_SPC_TEST_UNIT_READY: + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "TEST_UNIT_READY\n"); + cmd_parsed = 1; + /* FALLTHROUGH */ + case SPDK_SBC_START_STOP_UNIT: + if (!cmd_parsed) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "START_STOP_UNIT\n"); + } + + rc = 0; + break; + + case SPDK_SPC_PERSISTENT_RESERVE_OUT: + pllen = from_be32(&cdb[5]); + rc = bdev_scsi_check_len(task, pllen, 24); + if (rc < 0) { + break; + } + + data = spdk_scsi_task_gather_data(task, &rc); + if (rc < 0) { + break; + } + data_len = rc; + if (data_len < 24) { + rc = -1; + break; + } + + rc = scsi_pr_out(task, cdb, data, data_len); + if (rc < 0) { + break; + } + rc = pllen; + data_len = 0; + break; + + case SPDK_SPC_PERSISTENT_RESERVE_IN: + alloc_len = from_be16(&cdb[7]); + data_len = alloc_len; + data = calloc(1, data_len); + assert(data != NULL); + rc = scsi_pr_in(task, cdb, data, data_len); + break; + + case SPDK_SPC2_RESERVE_6: + case SPDK_SPC2_RESERVE_10: + rc = scsi2_reserve(task, cdb); + if (rc == 0) { + if (cdb[0] == SPDK_SPC2_RESERVE_10) { + rc = from_be16(&cdb[7]); + } + data_len = 0; + } + break; + + case SPDK_SPC2_RELEASE_6: + case SPDK_SPC2_RELEASE_10: + rc = scsi2_release(task); + break; + + default: + return SPDK_SCSI_TASK_UNKNOWN; + } + + if (rc >= 0 && data_len > 0) { + assert(alloc_len >= 0); + spdk_scsi_task_scatter_data(task, data, spdk_min(alloc_len, data_len)); + rc = spdk_min(data_len, alloc_len); + } + + if (rc >= 0) { + task->data_transferred = rc; + task->status = SPDK_SCSI_STATUS_GOOD; + } + + if (data) { + free(data); + } + + return SPDK_SCSI_TASK_COMPLETE; +} + +int +bdev_scsi_execute(struct spdk_scsi_task *task) +{ + int rc; + + if ((rc = bdev_scsi_process_block(task)) == SPDK_SCSI_TASK_UNKNOWN) { + if ((rc = bdev_scsi_process_primary(task)) == SPDK_SCSI_TASK_UNKNOWN) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "unsupported SCSI OP=0x%x\n", task->cdb[0]); + /* INVALID COMMAND OPERATION CODE */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_COMMAND_OPERATION_CODE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return SPDK_SCSI_TASK_COMPLETE; + } + } + + return rc; +} + +static void +bdev_scsi_reset_resubmit(void *arg) +{ + struct spdk_scsi_task *task = arg; + + bdev_scsi_reset(task); +} + +void +bdev_scsi_reset(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + int rc; + + rc = spdk_bdev_reset(lun->bdev_desc, lun->io_channel, bdev_scsi_task_complete_reset, + task); + if (rc == -ENOMEM) { + bdev_scsi_queue_io(task, bdev_scsi_reset_resubmit, task); + } +} + +bool +bdev_scsi_get_dif_ctx(struct spdk_bdev *bdev, struct spdk_scsi_task *task, + struct spdk_dif_ctx *dif_ctx) +{ + uint32_t ref_tag = 0, dif_check_flags = 0, data_offset; + uint8_t *cdb; + int rc; + + if (spdk_likely(spdk_bdev_get_md_size(bdev) == 0)) { + return false; + } + + cdb = task->cdb; + data_offset = task->offset; + + /* We use lower 32 bits of LBA as Reference. Tag */ + switch (cdb[0]) { + case SPDK_SBC_READ_6: + case SPDK_SBC_WRITE_6: + ref_tag = (uint32_t)cdb[1] << 16; + ref_tag |= (uint32_t)cdb[2] << 8; + ref_tag |= (uint32_t)cdb[3]; + break; + case SPDK_SBC_READ_10: + case SPDK_SBC_WRITE_10: + case SPDK_SBC_READ_12: + case SPDK_SBC_WRITE_12: + ref_tag = from_be32(&cdb[2]); + break; + case SPDK_SBC_READ_16: + case SPDK_SBC_WRITE_16: + ref_tag = (uint32_t)from_be64(&cdb[2]); + break; + default: + return false; + } + + if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_REFTAG)) { + dif_check_flags |= SPDK_DIF_FLAGS_REFTAG_CHECK; + } + + if (spdk_bdev_is_dif_check_enabled(bdev, SPDK_DIF_CHECK_TYPE_GUARD)) { + dif_check_flags |= SPDK_DIF_FLAGS_GUARD_CHECK; + } + + rc = spdk_dif_ctx_init(dif_ctx, + spdk_bdev_get_block_size(bdev), + spdk_bdev_get_md_size(bdev), + spdk_bdev_is_md_interleaved(bdev), + spdk_bdev_is_dif_head_of_md(bdev), + spdk_bdev_get_dif_type(bdev), + dif_check_flags, + ref_tag, 0, 0, data_offset, 0); + + return (rc == 0) ? true : false; +} diff --git a/src/spdk/lib/scsi/scsi_internal.h b/src/spdk/lib/scsi/scsi_internal.h new file mode 100644 index 000000000..2da3a99a8 --- /dev/null +++ b/src/spdk/lib/scsi/scsi_internal.h @@ -0,0 +1,214 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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. + */ + +#ifndef SPDK_SCSI_INTERNAL_H +#define SPDK_SCSI_INTERNAL_H + +#include "spdk/stdinc.h" + +#include "spdk/bdev.h" +#include "spdk/scsi.h" +#include "spdk/scsi_spec.h" +#include "spdk/trace.h" +#include "spdk/dif.h" + +#include "spdk_internal/log.h" + +enum { + SPDK_SCSI_TASK_UNKNOWN = -1, + SPDK_SCSI_TASK_COMPLETE, + SPDK_SCSI_TASK_PENDING, +}; + +struct spdk_scsi_port { + uint8_t is_used; + uint64_t id; + uint16_t index; + uint16_t transport_id_len; + char transport_id[SPDK_SCSI_MAX_TRANSPORT_ID_LENGTH]; + char name[SPDK_SCSI_PORT_MAX_NAME_LENGTH]; +}; + +/* Registrant with I_T nextus */ +struct spdk_scsi_pr_registrant { + uint64_t rkey; + uint16_t relative_target_port_id; + uint16_t transport_id_len; + char transport_id[SPDK_SCSI_MAX_TRANSPORT_ID_LENGTH]; + char initiator_port_name[SPDK_SCSI_PORT_MAX_NAME_LENGTH]; + char target_port_name[SPDK_SCSI_PORT_MAX_NAME_LENGTH]; + struct spdk_scsi_port *initiator_port; + struct spdk_scsi_port *target_port; + TAILQ_ENTRY(spdk_scsi_pr_registrant) link; +}; + +#define SCSI_SPC2_RESERVE 0x00000001U + +/* Reservation with LU_SCOPE */ +struct spdk_scsi_pr_reservation { + uint32_t flags; + struct spdk_scsi_pr_registrant *holder; + enum spdk_scsi_pr_type_code rtype; + uint64_t crkey; +}; + +struct spdk_scsi_dev { + int id; + int is_allocated; + bool removed; + spdk_scsi_dev_destruct_cb_t remove_cb; + void *remove_ctx; + + char name[SPDK_SCSI_DEV_MAX_NAME + 1]; + + struct spdk_scsi_lun *lun[SPDK_SCSI_DEV_MAX_LUN]; + + int num_ports; + struct spdk_scsi_port port[SPDK_SCSI_DEV_MAX_PORTS]; + + uint8_t protocol_id; +}; + +struct spdk_scsi_lun_desc { + struct spdk_scsi_lun *lun; + spdk_scsi_lun_remove_cb_t hotremove_cb; + void *hotremove_ctx; + TAILQ_ENTRY(spdk_scsi_lun_desc) link; +}; + +struct spdk_scsi_lun { + /** LUN id for this logical unit. */ + int id; + + /** Pointer to the SCSI device containing this LUN. */ + struct spdk_scsi_dev *dev; + + /** The bdev associated with this LUN. */ + struct spdk_bdev *bdev; + + /** Descriptor for opened block device. */ + struct spdk_bdev_desc *bdev_desc; + + /** The thread which opens this LUN. */ + struct spdk_thread *thread; + + /** I/O channel for the bdev associated with this LUN. */ + struct spdk_io_channel *io_channel; + + /** The reference number for this LUN, thus we can correctly free the io_channel */ + uint32_t ref; + + /** Poller to release the resource of the lun when it is hot removed */ + struct spdk_poller *hotremove_poller; + + /** The LUN is removed */ + bool removed; + + /** Callback to be fired when LUN removal is first triggered. */ + void (*hotremove_cb)(const struct spdk_scsi_lun *lun, void *arg); + + /** Argument for hotremove_cb */ + void *hotremove_ctx; + + /** Registrant head for I_T nexus */ + TAILQ_HEAD(, spdk_scsi_pr_registrant) reg_head; + /** Persistent Reservation Generation */ + uint32_t pr_generation; + /** Reservation for the LUN */ + struct spdk_scsi_pr_reservation reservation; + /** Reservation holder for SPC2 RESERVE(6) and RESERVE(10) */ + struct spdk_scsi_pr_registrant scsi2_holder; + + /** List of open descriptors for this LUN. */ + TAILQ_HEAD(, spdk_scsi_lun_desc) open_descs; + + /** submitted tasks */ + TAILQ_HEAD(tasks, spdk_scsi_task) tasks; + + /** pending tasks */ + TAILQ_HEAD(pending_tasks, spdk_scsi_task) pending_tasks; + + /** submitted management tasks */ + TAILQ_HEAD(mgmt_tasks, spdk_scsi_task) mgmt_tasks; + + /** pending management tasks */ + TAILQ_HEAD(pending_mgmt_tasks, spdk_scsi_task) pending_mgmt_tasks; + + /** poller to check completion of tasks prior to reset */ + struct spdk_poller *reset_poller; +}; + +struct spdk_scsi_lun *scsi_lun_construct(struct spdk_bdev *bdev, + void (*hotremove_cb)(const struct spdk_scsi_lun *, void *), + void *hotremove_ctx); +void scsi_lun_destruct(struct spdk_scsi_lun *lun); + +void scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task); +void scsi_lun_execute_mgmt_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task); +bool scsi_lun_has_pending_mgmt_tasks(const struct spdk_scsi_lun *lun, + const struct spdk_scsi_port *initiator_port); +void scsi_lun_complete_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task); +void scsi_lun_complete_reset_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task); +bool scsi_lun_has_pending_tasks(const struct spdk_scsi_lun *lun, + const struct spdk_scsi_port *initiator_port); +int scsi_lun_allocate_io_channel(struct spdk_scsi_lun *lun); +void scsi_lun_free_io_channel(struct spdk_scsi_lun *lun); + +struct spdk_scsi_dev *scsi_dev_get_list(void); + +int scsi_port_construct(struct spdk_scsi_port *port, uint64_t id, + uint16_t index, const char *name); +void scsi_port_destruct(struct spdk_scsi_port *port); + +int bdev_scsi_execute(struct spdk_scsi_task *task); +void bdev_scsi_reset(struct spdk_scsi_task *task); + +bool bdev_scsi_get_dif_ctx(struct spdk_bdev *bdev, struct spdk_scsi_task *task, + struct spdk_dif_ctx *dif_ctx); + +int scsi_pr_out(struct spdk_scsi_task *task, uint8_t *cdb, uint8_t *data, uint16_t data_len); +int scsi_pr_in(struct spdk_scsi_task *task, uint8_t *cdb, uint8_t *data, uint16_t data_len); +int scsi_pr_check(struct spdk_scsi_task *task); + +int scsi2_reserve(struct spdk_scsi_task *task, uint8_t *cdb); +int scsi2_release(struct spdk_scsi_task *task); +int scsi2_reserve_check(struct spdk_scsi_task *task); + +struct spdk_scsi_globals { + pthread_mutex_t mutex; +}; + +extern struct spdk_scsi_globals g_scsi; + +#endif /* SPDK_SCSI_INTERNAL_H */ diff --git a/src/spdk/lib/scsi/scsi_pr.c b/src/spdk/lib/scsi/scsi_pr.c new file mode 100644 index 000000000..4e17cc2c6 --- /dev/null +++ b/src/spdk/lib/scsi/scsi_pr.c @@ -0,0 +1,1067 @@ +/*- + * 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 "scsi_internal.h" + +#include "spdk/endian.h" + +/* Get registrant by I_T nexus */ +static struct spdk_scsi_pr_registrant * +scsi_pr_get_registrant(struct spdk_scsi_lun *lun, + struct spdk_scsi_port *initiator_port, + struct spdk_scsi_port *target_port) +{ + struct spdk_scsi_pr_registrant *reg, *tmp; + + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + if (initiator_port == reg->initiator_port && + target_port == reg->target_port) { + return reg; + } + } + + return NULL; +} + +static bool +scsi2_it_nexus_is_holder(struct spdk_scsi_lun *lun, + struct spdk_scsi_port *initiator_port, + struct spdk_scsi_port *target_port) +{ + struct spdk_scsi_pr_registrant *reg = lun->reservation.holder; + + assert(reg != NULL); + + if ((reg->initiator_port == initiator_port) && + (reg->target_port == target_port)) { + return true; + } + + return false; +} + +/* Reservation type is all registrants or not */ +static inline bool +scsi_pr_is_all_registrants_type(struct spdk_scsi_lun *lun) +{ + return (lun->reservation.rtype == SPDK_SCSI_PR_WRITE_EXCLUSIVE_ALL_REGS || + lun->reservation.rtype == SPDK_SCSI_PR_EXCLUSIVE_ACCESS_ALL_REGS); +} + +/* Registrant is reservation holder or not */ +static inline bool +scsi_pr_registrant_is_holder(struct spdk_scsi_lun *lun, + struct spdk_scsi_pr_registrant *reg) +{ + if (scsi_pr_is_all_registrants_type(lun)) { + return true; + } + + return (lun->reservation.holder == reg); +} + +/* LUN holds a reservation or not */ +static inline bool +scsi_pr_has_reservation(struct spdk_scsi_lun *lun) +{ + return !(lun->reservation.holder == NULL); +} + +static int +scsi_pr_register_registrant(struct spdk_scsi_lun *lun, + struct spdk_scsi_port *initiator_port, + struct spdk_scsi_port *target_port, + uint64_t sa_rkey) +{ + struct spdk_scsi_pr_registrant *reg; + + /* Register sa_rkey with the I_T nexus */ + reg = calloc(1, sizeof(*reg)); + if (!reg) { + return -ENOMEM; + } + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "REGISTER: new registrant registered " + "with key 0x%"PRIx64"\n", sa_rkey); + + /* New I_T nexus */ + reg->initiator_port = initiator_port; + if (initiator_port) { + snprintf(reg->initiator_port_name, sizeof(reg->initiator_port_name), "%s", + initiator_port->name); + reg->transport_id_len = initiator_port->transport_id_len; + memcpy(reg->transport_id, initiator_port->transport_id, reg->transport_id_len); + } + reg->target_port = target_port; + if (target_port) { + snprintf(reg->target_port_name, sizeof(reg->target_port_name), "%s", + target_port->name); + reg->relative_target_port_id = target_port->index; + } + reg->rkey = sa_rkey; + TAILQ_INSERT_TAIL(&lun->reg_head, reg, link); + lun->pr_generation++; + + return 0; +} + +static void +scsi_pr_release_reservation(struct spdk_scsi_lun *lun, struct spdk_scsi_pr_registrant *reg) +{ + bool all_regs = false; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "REGISTER: release reservation " + "with type %u\n", lun->reservation.rtype); + + /* TODO: Unit Attention */ + all_regs = scsi_pr_is_all_registrants_type(lun); + if (all_regs && !TAILQ_EMPTY(&lun->reg_head)) { + lun->reservation.holder = TAILQ_FIRST(&lun->reg_head); + return; + } + + memset(&lun->reservation, 0, sizeof(struct spdk_scsi_pr_reservation)); +} + +static void +scsi_pr_reserve_reservation(struct spdk_scsi_lun *lun, + enum spdk_scsi_pr_type_code type, + uint64_t rkey, + struct spdk_scsi_pr_registrant *holder) +{ + lun->reservation.rtype = type; + lun->reservation.crkey = rkey; + lun->reservation.holder = holder; +} + +static void +scsi_pr_unregister_registrant(struct spdk_scsi_lun *lun, + struct spdk_scsi_pr_registrant *reg) +{ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "REGISTER: unregister registrant\n"); + + TAILQ_REMOVE(&lun->reg_head, reg, link); + if (scsi_pr_registrant_is_holder(lun, reg)) { + scsi_pr_release_reservation(lun, reg); + } + + free(reg); + lun->pr_generation++; +} + +static void +scsi_pr_replace_registrant_key(struct spdk_scsi_lun *lun, + struct spdk_scsi_pr_registrant *reg, + uint64_t sa_rkey) +{ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "REGISTER: replace with new " + "reservation key 0x%"PRIx64"\n", sa_rkey); + reg->rkey = sa_rkey; + lun->pr_generation++; +} + +static int +scsi_pr_out_reserve(struct spdk_scsi_task *task, + enum spdk_scsi_pr_type_code rtype, uint64_t rkey, + uint8_t spec_i_pt, uint8_t all_tg_pt, uint8_t aptpl) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR OUT RESERVE: rkey 0x%"PRIx64", requested " + "reservation type %u, type %u\n", rkey, rtype, lun->reservation.rtype); + + /* TODO: don't support now */ + if (spec_i_pt || all_tg_pt || aptpl) { + SPDK_ERRLOG("Unspported spec_i_pt/all_tg_pt fields " + "or invalid aptpl field\n"); + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; + } + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + /* No registration for the I_T nexus */ + if (!reg) { + SPDK_ERRLOG("No registration\n"); + goto conflict; + } + + /* invalid reservation key */ + if (reg->rkey != rkey) { + SPDK_ERRLOG("Reservation key 0x%"PRIx64" don't match 0x%"PRIx64"\n", + rkey, reg->rkey); + goto conflict; + } + + /* reservation holder already exists */ + if (scsi_pr_has_reservation(lun)) { + if (rtype != lun->reservation.rtype) { + SPDK_ERRLOG("Reservation type doesn't match\n"); + goto conflict; + } + + if (!scsi_pr_registrant_is_holder(lun, reg)) { + SPDK_ERRLOG("Only 1 holder is allowed for type %u\n", rtype); + goto conflict; + } + } else { + /* current I_T nexus is the first reservation holder */ + scsi_pr_reserve_reservation(lun, rtype, rkey, reg); + } + + return 0; + +conflict: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_RESERVATION_CONFLICT, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +static int +scsi_pr_out_register(struct spdk_scsi_task *task, + enum spdk_scsi_pr_out_service_action_code action, + uint64_t rkey, uint64_t sa_rkey, + uint8_t spec_i_pt, uint8_t all_tg_pt, uint8_t aptpl) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg; + int sc, sk, asc; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR OUT REGISTER: rkey 0x%"PRIx64", " + "sa_key 0x%"PRIx64", reservation type %u\n", rkey, sa_rkey, lun->reservation.rtype); + + /* TODO: don't support now */ + if (spec_i_pt || all_tg_pt || aptpl) { + SPDK_ERRLOG("Unsupported spec_i_pt/all_tg_pt/aptpl field\n"); + sc = SPDK_SCSI_STATUS_CHECK_CONDITION; + sk = SPDK_SCSI_SENSE_ILLEGAL_REQUEST; + asc = SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB; + goto error_exit; + } + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + /* an unregistered I_T nexus session */ + if (!reg) { + if (rkey && (action == SPDK_SCSI_PR_OUT_REGISTER)) { + SPDK_ERRLOG("Reservation key field is not empty\n"); + sc = SPDK_SCSI_STATUS_RESERVATION_CONFLICT; + sk = SPDK_SCSI_SENSE_NO_SENSE; + asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + goto error_exit; + } + + if (!sa_rkey) { + /* Do nothing except return GOOD status */ + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "REGISTER: service action " + "reservation key is zero, do noting\n"); + return 0; + } + /* Add a new registrant for the I_T nexus */ + return scsi_pr_register_registrant(lun, task->initiator_port, + task->target_port, sa_rkey); + } else { + /* a registered I_T nexus */ + if (rkey != reg->rkey && action == SPDK_SCSI_PR_OUT_REGISTER) { + SPDK_ERRLOG("Reservation key 0x%"PRIx64" don't match " + "registrant's key 0x%"PRIx64"\n", rkey, reg->rkey); + sc = SPDK_SCSI_STATUS_RESERVATION_CONFLICT; + sk = SPDK_SCSI_SENSE_NO_SENSE; + asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + goto error_exit; + } + + if (!sa_rkey) { + /* unregister */ + scsi_pr_unregister_registrant(lun, reg); + } else { + /* replace */ + scsi_pr_replace_registrant_key(lun, reg, sa_rkey); + } + } + + return 0; + +error_exit: + spdk_scsi_task_set_status(task, sc, sk, asc, SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE); + return -EINVAL; +} + +static int +scsi_pr_out_release(struct spdk_scsi_task *task, + enum spdk_scsi_pr_type_code rtype, uint64_t rkey) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg; + int sk, asc; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR OUT RELEASE: rkey 0x%"PRIx64", " + "reservation type %u\n", rkey, rtype); + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + if (!reg) { + SPDK_ERRLOG("No registration\n"); + sk = SPDK_SCSI_SENSE_NOT_READY; + asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + goto check_condition; + } + + /* no reservation holder */ + if (!scsi_pr_has_reservation(lun)) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "RELEASE: no reservation holder\n"); + return 0; + } + + if (lun->reservation.rtype != rtype || rkey != lun->reservation.crkey) { + sk = SPDK_SCSI_SENSE_ILLEGAL_REQUEST; + asc = SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB; + goto check_condition; + } + + /* I_T nexus is not a persistent reservation holder */ + if (!scsi_pr_registrant_is_holder(lun, reg)) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "RELEASE: current I_T nexus is not holder\n"); + return 0; + } + + scsi_pr_release_reservation(lun, reg); + + return 0; + +check_condition: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, sk, asc, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +static int +scsi_pr_out_clear(struct spdk_scsi_task *task, uint64_t rkey) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg, *tmp; + int sc, sk, asc; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR OUT CLEAR: rkey 0x%"PRIx64"\n", rkey); + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + if (!reg) { + SPDK_ERRLOG("No registration\n"); + sc = SPDK_SCSI_STATUS_CHECK_CONDITION; + sk = SPDK_SCSI_SENSE_NOT_READY; + asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + goto error_exit; + } + + if (rkey != reg->rkey) { + SPDK_ERRLOG("Reservation key 0x%"PRIx64" doesn't match " + "registrant's key 0x%"PRIx64"\n", rkey, reg->rkey); + sc = SPDK_SCSI_STATUS_RESERVATION_CONFLICT; + sk = SPDK_SCSI_SENSE_NO_SENSE; + asc = SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE; + goto error_exit; + } + + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + scsi_pr_unregister_registrant(lun, reg); + } + + return 0; + +error_exit: + spdk_scsi_task_set_status(task, sc, sk, asc, SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +static void +scsi_pr_remove_all_regs_by_key(struct spdk_scsi_lun *lun, uint64_t sa_rkey) +{ + struct spdk_scsi_pr_registrant *reg, *tmp; + + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + if (reg->rkey == sa_rkey) { + scsi_pr_unregister_registrant(lun, reg); + } + } +} + +static void +scsi_pr_remove_all_other_regs(struct spdk_scsi_lun *lun, struct spdk_scsi_pr_registrant *reg) +{ + struct spdk_scsi_pr_registrant *reg_tmp, *reg_tmp2; + + TAILQ_FOREACH_SAFE(reg_tmp, &lun->reg_head, link, reg_tmp2) { + if (reg_tmp != reg) { + scsi_pr_unregister_registrant(lun, reg_tmp); + } + } +} + +static int +scsi_pr_out_preempt(struct spdk_scsi_task *task, + enum spdk_scsi_pr_out_service_action_code action, + enum spdk_scsi_pr_type_code rtype, + uint64_t rkey, uint64_t sa_rkey) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg; + bool all_regs = false; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR OUT PREEMPT: rkey 0x%"PRIx64", sa_rkey 0x%"PRIx64" " + "action %u, type %u, reservation type %u\n", + rkey, sa_rkey, action, rtype, lun->reservation.rtype); + + /* I_T nexus is not registered */ + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + if (!reg) { + SPDK_ERRLOG("No registration\n"); + goto conflict; + } + if (rkey != reg->rkey) { + SPDK_ERRLOG("Reservation key 0x%"PRIx64" doesn't match " + "registrant's key 0x%"PRIx64"\n", rkey, reg->rkey); + goto conflict; + } + + /* no persistent reservation */ + if (!scsi_pr_has_reservation(lun)) { + scsi_pr_remove_all_regs_by_key(lun, sa_rkey); + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PREEMPT: no persistent reservation\n"); + goto exit; + } + + all_regs = scsi_pr_is_all_registrants_type(lun); + + if (all_regs) { + if (sa_rkey != 0) { + scsi_pr_remove_all_regs_by_key(lun, sa_rkey); + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PREEMPT: All registrants type with sa_rkey\n"); + } else { + /* remove all other registrants and release persistent reservation if any */ + scsi_pr_remove_all_other_regs(lun, reg); + /* create persistent reservation using new type and scope */ + scsi_pr_reserve_reservation(lun, rtype, 0, reg); + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PREEMPT: All registrants type with sa_rkey zeroed\n"); + } + goto exit; + } + + assert(lun->reservation.crkey != 0); + + if (sa_rkey != lun->reservation.crkey) { + if (!sa_rkey) { + SPDK_ERRLOG("Zeroed sa_rkey\n"); + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; + } + scsi_pr_remove_all_regs_by_key(lun, sa_rkey); + goto exit; + } + + if (scsi_pr_registrant_is_holder(lun, reg)) { + scsi_pr_reserve_reservation(lun, rtype, rkey, reg); + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PREEMPT: preempt itself with type %u\n", rtype); + goto exit; + } + + /* unregister registrants if any */ + scsi_pr_remove_all_regs_by_key(lun, sa_rkey); + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + if (!reg) { + SPDK_ERRLOG("Current I_T nexus registrant was removed\n"); + goto conflict; + } + + /* preempt the holder */ + scsi_pr_reserve_reservation(lun, rtype, rkey, reg); + +exit: + lun->pr_generation++; + return 0; + +conflict: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_RESERVATION_CONFLICT, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +int +scsi_pr_out(struct spdk_scsi_task *task, uint8_t *cdb, + uint8_t *data, uint16_t data_len) +{ + int rc = -1; + uint64_t rkey, sa_rkey; + uint8_t spec_i_pt, all_tg_pt, aptpl; + enum spdk_scsi_pr_out_service_action_code action; + enum spdk_scsi_pr_scope_code scope; + enum spdk_scsi_pr_type_code rtype; + struct spdk_scsi_pr_out_param_list *param = (struct spdk_scsi_pr_out_param_list *)data; + + action = cdb[1] & 0x0f; + scope = (cdb[2] >> 4) & 0x0f; + rtype = cdb[2] & 0x0f; + + rkey = from_be64(¶m->rkey); + sa_rkey = from_be64(¶m->sa_rkey); + aptpl = param->aptpl; + spec_i_pt = param->spec_i_pt; + all_tg_pt = param->all_tg_pt; + + switch (action) { + case SPDK_SCSI_PR_OUT_REGISTER: + case SPDK_SCSI_PR_OUT_REG_AND_IGNORE_KEY: + rc = scsi_pr_out_register(task, action, rkey, sa_rkey, + spec_i_pt, all_tg_pt, aptpl); + break; + case SPDK_SCSI_PR_OUT_RESERVE: + if (scope != SPDK_SCSI_PR_LU_SCOPE) { + goto invalid; + } + rc = scsi_pr_out_reserve(task, rtype, rkey, + spec_i_pt, all_tg_pt, aptpl); + break; + case SPDK_SCSI_PR_OUT_RELEASE: + if (scope != SPDK_SCSI_PR_LU_SCOPE) { + goto invalid; + } + rc = scsi_pr_out_release(task, rtype, rkey); + break; + case SPDK_SCSI_PR_OUT_CLEAR: + rc = scsi_pr_out_clear(task, rkey); + break; + case SPDK_SCSI_PR_OUT_PREEMPT: + if (scope != SPDK_SCSI_PR_LU_SCOPE) { + goto invalid; + } + rc = scsi_pr_out_preempt(task, action, rtype, rkey, sa_rkey); + break; + default: + SPDK_ERRLOG("Invalid service action code %u\n", action); + goto invalid; + } + + return rc; + +invalid: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +static int +scsi_pr_in_read_keys(struct spdk_scsi_task *task, uint8_t *data, + uint16_t data_len) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_in_read_keys_data *keys; + struct spdk_scsi_pr_registrant *reg, *tmp; + uint16_t count = 0; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR IN READ KEYS\n"); + keys = (struct spdk_scsi_pr_in_read_keys_data *)data; + + to_be32(&keys->header.pr_generation, lun->pr_generation); + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + if (((count + 1) * 8 + sizeof(keys->header)) > data_len) { + break; + } + to_be64(&keys->rkeys[count], reg->rkey); + count++; + } + to_be32(&keys->header.additional_len, count * 8); + + return (sizeof(keys->header) + count * 8); +} + +static int +scsi_pr_in_read_reservations(struct spdk_scsi_task *task, + uint8_t *data, uint16_t data_len) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_in_read_reservations_data *param; + bool all_regs = false; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR IN READ RESERVATIONS\n"); + param = (struct spdk_scsi_pr_in_read_reservations_data *)(data); + + to_be32(¶m->header.pr_generation, lun->pr_generation); + if (scsi_pr_has_reservation(lun)) { + all_regs = scsi_pr_is_all_registrants_type(lun); + if (all_regs) { + to_be64(¶m->rkey, 0); + } else { + to_be64(¶m->rkey, lun->reservation.crkey); + } + to_be32(¶m->header.additional_len, 16); + param->scope = SPDK_SCSI_PR_LU_SCOPE; + param->type = lun->reservation.rtype; + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "READ RESERVATIONS with valid reservation\n"); + return sizeof(*param); + } + + /* no reservation */ + to_be32(¶m->header.additional_len, 0); + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "READ RESERVATIONS no reservation\n"); + return sizeof(param->header); +} + +static int +scsi_pr_in_report_capabilities(struct spdk_scsi_task *task, + uint8_t *data, uint16_t data_len) +{ + struct spdk_scsi_pr_in_report_capabilities_data *param; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR IN REPORT CAPABILITIES\n"); + param = (struct spdk_scsi_pr_in_report_capabilities_data *)data; + + memset(param, 0, sizeof(*param)); + to_be16(¶m->length, sizeof(*param)); + /* Compatible reservation handling to support RESERVE/RELEASE defined in SPC-2 */ + param->crh = 1; + param->tmv = 1; + param->wr_ex = 1; + param->ex_ac = 1; + param->wr_ex_ro = 1; + param->ex_ac_ro = 1; + param->wr_ex_ar = 1; + param->ex_ac_ar = 1; + + return sizeof(*param); +} + +static int +scsi_pr_in_read_full_status(struct spdk_scsi_task *task, + uint8_t *data, uint16_t data_len) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_in_full_status_data *param; + struct spdk_scsi_pr_in_full_status_desc *desc; + struct spdk_scsi_pr_registrant *reg, *tmp; + bool all_regs = false; + uint32_t add_len = 0; + + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "PR IN READ FULL STATUS\n"); + + all_regs = scsi_pr_is_all_registrants_type(lun); + param = (struct spdk_scsi_pr_in_full_status_data *)data; + to_be32(¶m->header.pr_generation, lun->pr_generation); + + TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) { + desc = (struct spdk_scsi_pr_in_full_status_desc *) + ((uint8_t *)param->desc_list + add_len); + if (add_len + sizeof(*desc) + sizeof(param->header) > data_len) { + break; + } + add_len += sizeof(*desc); + desc->rkey = reg->rkey; + if (all_regs || lun->reservation.holder == reg) { + desc->r_holder = true; + desc->type = lun->reservation.rtype; + } else { + desc->r_holder = false; + desc->type = 0; + } + desc->all_tg_pt = 0; + desc->scope = SPDK_SCSI_PR_LU_SCOPE; + desc->relative_target_port_id = reg->relative_target_port_id; + if (add_len + reg->transport_id_len + sizeof(param->header) > data_len) { + break; + } + add_len += reg->transport_id_len; + memcpy(&desc->transport_id, reg->transport_id, reg->transport_id_len); + to_be32(&desc->desc_len, reg->transport_id_len); + } + to_be32(¶m->header.additional_len, add_len); + + return (sizeof(param->header) + add_len); +} + +int +scsi_pr_in(struct spdk_scsi_task *task, uint8_t *cdb, + uint8_t *data, uint16_t data_len) +{ + enum spdk_scsi_pr_in_action_code action; + int rc = 0; + + action = cdb[1] & 0x1f; + if (data_len < sizeof(struct spdk_scsi_pr_in_read_header)) { + goto invalid; + } + + switch (action) { + case SPDK_SCSI_PR_IN_READ_KEYS: + rc = scsi_pr_in_read_keys(task, data, data_len); + break; + case SPDK_SCSI_PR_IN_READ_RESERVATION: + if (data_len < sizeof(struct spdk_scsi_pr_in_read_reservations_data)) { + goto invalid; + } + rc = scsi_pr_in_read_reservations(task, data, data_len); + break; + case SPDK_SCSI_PR_IN_REPORT_CAPABILITIES: + rc = scsi_pr_in_report_capabilities(task, data, data_len); + break; + case SPDK_SCSI_PR_IN_READ_FULL_STATUS: + rc = scsi_pr_in_read_full_status(task, data, data_len); + break; + default: + goto invalid; + } + + return rc; + +invalid: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -EINVAL; +} + +int +scsi_pr_check(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + uint8_t *cdb = task->cdb; + enum spdk_scsi_pr_type_code rtype; + enum spdk_scsi_pr_out_service_action_code action; + struct spdk_scsi_pr_registrant *reg; + bool dma_to_device = false; + + /* no reservation holders */ + if (!scsi_pr_has_reservation(lun)) { + return 0; + } + + rtype = lun->reservation.rtype; + assert(rtype != 0); + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + /* current I_T nexus hold the reservation */ + if (scsi_pr_registrant_is_holder(lun, reg)) { + return 0; + } + + /* reservation is held by other I_T nexus */ + switch (cdb[0]) { + case SPDK_SPC_INQUIRY: + case SPDK_SPC_REPORT_LUNS: + case SPDK_SPC_REQUEST_SENSE: + case SPDK_SPC_LOG_SENSE: + case SPDK_SPC_TEST_UNIT_READY: + case SPDK_SBC_START_STOP_UNIT: + case SPDK_SBC_READ_CAPACITY_10: + case SPDK_SPC_PERSISTENT_RESERVE_IN: + case SPDK_SPC_SERVICE_ACTION_IN_16: + /* CRH enabled, processed by scsi2_reserve() */ + case SPDK_SPC2_RESERVE_6: + case SPDK_SPC2_RESERVE_10: + /* CRH enabled, processed by scsi2_release() */ + case SPDK_SPC2_RELEASE_6: + case SPDK_SPC2_RELEASE_10: + return 0; + case SPDK_SPC_MODE_SELECT_6: + case SPDK_SPC_MODE_SELECT_10: + case SPDK_SPC_MODE_SENSE_6: + case SPDK_SPC_MODE_SENSE_10: + case SPDK_SPC_LOG_SELECT: + /* I_T nexus is registrant but not holder */ + if (!reg) { + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "CHECK: current I_T nexus " + "is not registered, cdb 0x%x\n", cdb[0]); + goto conflict; + } + return 0; + case SPDK_SPC_PERSISTENT_RESERVE_OUT: + action = cdb[1] & 0x1f; + SPDK_DEBUGLOG(SPDK_LOG_SCSI, "CHECK: PR OUT action %u\n", action); + switch (action) { + case SPDK_SCSI_PR_OUT_RELEASE: + case SPDK_SCSI_PR_OUT_CLEAR: + case SPDK_SCSI_PR_OUT_PREEMPT: + case SPDK_SCSI_PR_OUT_PREEMPT_AND_ABORT: + if (!reg) { + SPDK_ERRLOG("CHECK: PR OUT action %u\n", action); + goto conflict; + } + return 0; + case SPDK_SCSI_PR_OUT_REGISTER: + case SPDK_SCSI_PR_OUT_REG_AND_IGNORE_KEY: + return 0; + case SPDK_SCSI_PR_OUT_REG_AND_MOVE: + SPDK_ERRLOG("CHECK: PR OUT action %u\n", action); + goto conflict; + default: + SPDK_ERRLOG("CHECK: PR OUT invalid action %u\n", action); + goto conflict; + } + + /* For most SBC R/W commands */ + default: + break; + } + + switch (cdb[0]) { + case SPDK_SBC_READ_6: + case SPDK_SBC_READ_10: + case SPDK_SBC_READ_12: + case SPDK_SBC_READ_16: + break; + case SPDK_SBC_WRITE_6: + case SPDK_SBC_WRITE_10: + case SPDK_SBC_WRITE_12: + case SPDK_SBC_WRITE_16: + case SPDK_SBC_UNMAP: + case SPDK_SBC_SYNCHRONIZE_CACHE_10: + case SPDK_SBC_SYNCHRONIZE_CACHE_16: + dma_to_device = true; + break; + default: + SPDK_ERRLOG("CHECK: unsupported SCSI command cdb 0x%x\n", cdb[0]); + goto conflict; + } + + switch (rtype) { + case SPDK_SCSI_PR_WRITE_EXCLUSIVE: + if (dma_to_device) { + SPDK_ERRLOG("CHECK: Write Exclusive reservation type " + "rejects command 0x%x\n", cdb[0]); + goto conflict; + } + break; + case SPDK_SCSI_PR_EXCLUSIVE_ACCESS: + SPDK_ERRLOG("CHECK: Exclusive Access reservation type " + "rejects command 0x%x\n", cdb[0]); + goto conflict; + case SPDK_SCSI_PR_WRITE_EXCLUSIVE_REGS_ONLY: + case SPDK_SCSI_PR_WRITE_EXCLUSIVE_ALL_REGS: + if (!reg && dma_to_device) { + SPDK_ERRLOG("CHECK: Registrants only reservation " + "type reject command 0x%x\n", cdb[0]); + goto conflict; + } + break; + case SPDK_SCSI_PR_EXCLUSIVE_ACCESS_REGS_ONLY: + case SPDK_SCSI_PR_EXCLUSIVE_ACCESS_ALL_REGS: + if (!reg) { + SPDK_ERRLOG("CHECK: All Registrants reservation " + "type reject command 0x%x\n", cdb[0]); + goto conflict; + } + break; + default: + break; + } + + return 0; + +conflict: + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_RESERVATION_CONFLICT, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; +} + +static int +scsi2_check_reservation_conflict(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg; + bool conflict = false; + + reg = scsi_pr_get_registrant(lun, task->initiator_port, task->target_port); + if (reg) { + /* + * From spc4r31 5.9.3 Exceptions to SPC-2 RESERVE and RELEASE + * behavior + * + * A RESERVE(6) or RESERVE(10) command shall complete with GOOD + * status, but no reservation shall be established and the + * persistent reservation shall not be changed, if the command + * is received from a) and b) below. + * + * A RELEASE(6) or RELEASE(10) command shall complete with GOOD + * status, but the persistent reservation shall not be released, + * if the command is received from a) and b) + * + * a) An I_T nexus that is a persistent reservation holder; or + * b) An I_T nexus that is registered if a registrants only or + * all registrants type persistent reservation is present. + * + * In all other cases, a RESERVE(6) command, RESERVE(10) command, + * RELEASE(6) command, or RELEASE(10) command shall be processed + * as defined in SPC-2. + */ + if (scsi_pr_registrant_is_holder(lun, reg)) { + return 1; + } + + if (lun->reservation.rtype == SPDK_SCSI_PR_WRITE_EXCLUSIVE_REGS_ONLY || + lun->reservation.rtype == SPDK_SCSI_PR_EXCLUSIVE_ACCESS_REGS_ONLY) { + return 1; + } + + conflict = true; + } else { + /* + * From spc2r20 5.5.1 Reservations overview: + * + * If a logical unit has executed a PERSISTENT RESERVE OUT + * command with the REGISTER or the REGISTER AND IGNORE + * EXISTING KEY service action and is still registered by any + * initiator, all RESERVE commands and all RELEASE commands + * regardless of initiator shall conflict and shall terminate + * with a RESERVATION CONFLICT status. + */ + conflict = TAILQ_EMPTY(&lun->reg_head) ? false : true; + } + + if (conflict) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_RESERVATION_CONFLICT, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + return 0; +} + +int +scsi2_reserve(struct spdk_scsi_task *task, uint8_t *cdb) +{ + struct spdk_scsi_lun *lun = task->lun; + struct spdk_scsi_pr_registrant *reg = &lun->scsi2_holder; + int ret; + + /* Obsolete Bits and LongID set, returning ILLEGAL_REQUEST */ + if (cdb[1] & 0x3) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + ret = scsi2_check_reservation_conflict(task); + /* PERSISTENT RESERVE is enabled */ + if (ret == 1) { + return 0; + } else if (ret < 0) { + return ret; + } + + /* SPC2 RESERVE */ + reg->initiator_port = task->initiator_port; + if (task->initiator_port) { + snprintf(reg->initiator_port_name, sizeof(reg->initiator_port_name), "%s", + task->initiator_port->name); + reg->transport_id_len = task->initiator_port->transport_id_len; + memcpy(reg->transport_id, task->initiator_port->transport_id, + reg->transport_id_len); + } + reg->target_port = task->target_port; + if (task->target_port) { + snprintf(reg->target_port_name, sizeof(reg->target_port_name), "%s", + task->target_port->name); + } + + lun->reservation.flags = SCSI_SPC2_RESERVE; + lun->reservation.holder = &lun->scsi2_holder; + + return 0; +} + +int +scsi2_release(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + int ret; + + ret = scsi2_check_reservation_conflict(task); + /* PERSISTENT RESERVE is enabled */ + if (ret == 1) { + return 0; + } else if (ret < 0) { + return ret; + } + + assert(lun->reservation.flags & SCSI_SPC2_RESERVE); + + memset(&lun->reservation, 0, sizeof(struct spdk_scsi_pr_reservation)); + memset(&lun->scsi2_holder, 0, sizeof(struct spdk_scsi_pr_registrant)); + + return 0; +} + +int scsi2_reserve_check(struct spdk_scsi_task *task) +{ + struct spdk_scsi_lun *lun = task->lun; + uint8_t *cdb = task->cdb; + + switch (cdb[0]) { + case SPDK_SPC_INQUIRY: + case SPDK_SPC2_RELEASE_6: + case SPDK_SPC2_RELEASE_10: + return 0; + + default: + break; + } + + /* no reservation holders */ + if (!scsi_pr_has_reservation(lun)) { + return 0; + } + + if (scsi2_it_nexus_is_holder(lun, task->initiator_port, task->target_port)) { + return 0; + } + + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_RESERVATION_CONFLICT, + SPDK_SCSI_SENSE_NO_SENSE, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; +} diff --git a/src/spdk/lib/scsi/scsi_rpc.c b/src/spdk/lib/scsi/scsi_rpc.c new file mode 100644 index 000000000..1938ddac7 --- /dev/null +++ b/src/spdk/lib/scsi/scsi_rpc.c @@ -0,0 +1,77 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" + +#include "spdk/rpc.h" +#include "spdk/util.h" + +static void +rpc_scsi_get_devices(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + struct spdk_scsi_dev *devs = scsi_dev_get_list(); + int i; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "scsi_get_devices requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + + for (i = 0; i < SPDK_SCSI_MAX_DEVS; i++) { + struct spdk_scsi_dev *dev = &devs[i]; + + if (!dev->is_allocated) { + continue; + } + + spdk_json_write_object_begin(w); + + spdk_json_write_named_int32(w, "id", dev->id); + + spdk_json_write_named_string(w, "device_name", dev->name); + + spdk_json_write_object_end(w); + } + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("scsi_get_devices", rpc_scsi_get_devices, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(scsi_get_devices, get_scsi_devices) diff --git a/src/spdk/lib/scsi/spdk_scsi.map b/src/spdk/lib/scsi/spdk_scsi.map new file mode 100644 index 000000000..643372699 --- /dev/null +++ b/src/spdk/lib/scsi/spdk_scsi.map @@ -0,0 +1,49 @@ +{ + global: + + # Public functions + spdk_scsi_init; + spdk_scsi_fini; + spdk_scsi_lun_get_id; + spdk_scsi_lun_get_bdev_name; + spdk_scsi_lun_get_dev; + spdk_scsi_lun_is_removing; + spdk_scsi_dev_get_name; + spdk_scsi_dev_get_id; + spdk_scsi_dev_get_lun; + spdk_scsi_dev_has_pending_tasks; + spdk_scsi_dev_destruct; + spdk_scsi_dev_queue_mgmt_task; + spdk_scsi_dev_queue_task; + spdk_scsi_dev_add_port; + spdk_scsi_dev_delete_port; + spdk_scsi_dev_find_port_by_id; + spdk_scsi_dev_allocate_io_channels; + spdk_scsi_dev_free_io_channels; + spdk_scsi_dev_construct; + spdk_scsi_dev_delete_lun; + spdk_scsi_dev_add_lun; + spdk_scsi_port_create; + spdk_scsi_port_free; + spdk_scsi_port_get_name; + spdk_scsi_task_construct; + spdk_scsi_task_put; + spdk_scsi_task_set_data; + spdk_scsi_task_scatter_data; + spdk_scsi_task_gather_data; + spdk_scsi_task_build_sense_data; + spdk_scsi_task_set_status; + spdk_scsi_task_copy_status; + spdk_scsi_task_process_null_lun; + spdk_scsi_task_process_abort; + spdk_scsi_lun_open; + spdk_scsi_lun_close; + spdk_scsi_lun_allocate_io_channel; + spdk_scsi_lun_free_io_channel; + spdk_scsi_lun_get_dif_ctx; + spdk_scsi_port_set_iscsi_transport_id; + spdk_scsi_lun_id_int_to_fmt; + spdk_scsi_lun_id_fmt_to_int; + + local: *; +}; diff --git a/src/spdk/lib/scsi/task.c b/src/spdk/lib/scsi/task.c new file mode 100644 index 000000000..7fd8305ec --- /dev/null +++ b/src/spdk/lib/scsi/task.c @@ -0,0 +1,300 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * 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 "scsi_internal.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/util.h" + +static void +scsi_task_free_data(struct spdk_scsi_task *task) +{ + if (task->alloc_len != 0) { + spdk_dma_free(task->iov.iov_base); + task->alloc_len = 0; + } + + task->iov.iov_base = NULL; + task->iov.iov_len = 0; +} + +void +spdk_scsi_task_put(struct spdk_scsi_task *task) +{ + if (!task) { + return; + } + + assert(task->ref > 0); + task->ref--; + + if (task->ref == 0) { + struct spdk_bdev_io *bdev_io = task->bdev_io; + + if (bdev_io) { + spdk_bdev_free_io(bdev_io); + } + + scsi_task_free_data(task); + + task->free_fn(task); + } +} + +void +spdk_scsi_task_construct(struct spdk_scsi_task *task, + spdk_scsi_task_cpl cpl_fn, + spdk_scsi_task_free free_fn) +{ + assert(task != NULL); + assert(cpl_fn != NULL); + assert(free_fn != NULL); + + task->cpl_fn = cpl_fn; + task->free_fn = free_fn; + + task->ref++; + + /* + * Pre-fill the iov_buffers to point to the embedded iov + */ + assert(task->iov.iov_base == NULL); + task->iovs = &task->iov; + task->iovcnt = 1; +} + +static void * +scsi_task_alloc_data(struct spdk_scsi_task *task, uint32_t alloc_len) +{ + assert(task->alloc_len == 0); + + task->iov.iov_base = spdk_dma_zmalloc(alloc_len, 0, NULL); + task->iov.iov_len = alloc_len; + task->alloc_len = alloc_len; + + return task->iov.iov_base; +} + +int +spdk_scsi_task_scatter_data(struct spdk_scsi_task *task, const void *src, size_t buf_len) +{ + size_t len = 0; + size_t buf_left = buf_len; + int i; + struct iovec *iovs = task->iovs; + const uint8_t *pos; + + if (buf_len == 0) { + return 0; + } + + if (task->iovcnt == 1 && iovs[0].iov_base == NULL) { + scsi_task_alloc_data(task, buf_len); + iovs[0] = task->iov; + } + + for (i = 0; i < task->iovcnt; i++) { + assert(iovs[i].iov_base != NULL); + len += iovs[i].iov_len; + } + + if (len < buf_len) { + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_INVALID_FIELD_IN_CDB, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + return -1; + } + + pos = src; + + for (i = 0; i < task->iovcnt; i++) { + len = spdk_min(iovs[i].iov_len, buf_left); + buf_left -= len; + memcpy(iovs[i].iov_base, pos, len); + pos += len; + } + + return buf_len; +} + +void * +spdk_scsi_task_gather_data(struct spdk_scsi_task *task, int *len) +{ + int i; + struct iovec *iovs = task->iovs; + size_t buf_len = 0; + uint8_t *buf, *pos; + + for (i = 0; i < task->iovcnt; i++) { + assert(iovs[i].iov_base != NULL); + buf_len += iovs[i].iov_len; + } + + if (buf_len == 0) { + *len = 0; + return NULL; + } + + buf = calloc(1, buf_len); + if (buf == NULL) { + *len = -1; + return NULL; + } + + pos = buf; + for (i = 0; i < task->iovcnt; i++) { + memcpy(pos, iovs[i].iov_base, iovs[i].iov_len); + pos += iovs[i].iov_len; + } + + *len = buf_len; + return buf; +} + +void +spdk_scsi_task_set_data(struct spdk_scsi_task *task, void *data, uint32_t len) +{ + assert(task->iovcnt == 1); + assert(task->alloc_len == 0); + + task->iovs[0].iov_base = data; + task->iovs[0].iov_len = len; +} + +void +spdk_scsi_task_build_sense_data(struct spdk_scsi_task *task, int sk, int asc, int ascq) +{ + uint8_t *cp; + int resp_code; + + resp_code = 0x70; /* Current + Fixed format */ + + /* Sense Data */ + cp = task->sense_data; + + /* VALID(7) RESPONSE CODE(6-0) */ + cp[0] = 0x80 | resp_code; + /* Obsolete */ + cp[1] = 0; + /* FILEMARK(7) EOM(6) ILI(5) SENSE KEY(3-0) */ + cp[2] = sk & 0xf; + /* INFORMATION */ + memset(&cp[3], 0, 4); + + /* ADDITIONAL SENSE LENGTH */ + cp[7] = 10; + + /* COMMAND-SPECIFIC INFORMATION */ + memset(&cp[8], 0, 4); + /* ADDITIONAL SENSE CODE */ + cp[12] = asc; + /* ADDITIONAL SENSE CODE QUALIFIER */ + cp[13] = ascq; + /* FIELD REPLACEABLE UNIT CODE */ + cp[14] = 0; + + /* SKSV(7) SENSE KEY SPECIFIC(6-0,7-0,7-0) */ + cp[15] = 0; + cp[16] = 0; + cp[17] = 0; + + /* SenseLength */ + task->sense_data_len = 18; +} + +void +spdk_scsi_task_set_status(struct spdk_scsi_task *task, int sc, int sk, + int asc, int ascq) +{ + if (sc == SPDK_SCSI_STATUS_CHECK_CONDITION) { + spdk_scsi_task_build_sense_data(task, sk, asc, ascq); + } + task->status = sc; +} + +void +spdk_scsi_task_copy_status(struct spdk_scsi_task *dst, + struct spdk_scsi_task *src) +{ + memcpy(dst->sense_data, src->sense_data, src->sense_data_len); + dst->sense_data_len = src->sense_data_len; + dst->status = src->status; +} + +void +spdk_scsi_task_process_null_lun(struct spdk_scsi_task *task) +{ + uint8_t buffer[36]; + uint32_t allocation_len; + uint32_t data_len; + + task->length = task->transfer_len; + if (task->cdb[0] == SPDK_SPC_INQUIRY) { + /* + * SPC-4 states that INQUIRY commands to an unsupported LUN + * must be served with PERIPHERAL QUALIFIER = 0x3 and + * PERIPHERAL DEVICE TYPE = 0x1F. + */ + data_len = sizeof(buffer); + + memset(buffer, 0, data_len); + /* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */ + buffer[0] = 0x03 << 5 | 0x1f; + /* ADDITIONAL LENGTH */ + buffer[4] = data_len - 5; + + allocation_len = from_be16(&task->cdb[3]); + if (spdk_scsi_task_scatter_data(task, buffer, spdk_min(allocation_len, data_len)) >= 0) { + task->data_transferred = data_len; + task->status = SPDK_SCSI_STATUS_GOOD; + } + } else { + /* LOGICAL UNIT NOT SUPPORTED */ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ILLEGAL_REQUEST, + SPDK_SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); + task->data_transferred = 0; + } +} + +void +spdk_scsi_task_process_abort(struct spdk_scsi_task *task) +{ + spdk_scsi_task_set_status(task, SPDK_SCSI_STATUS_CHECK_CONDITION, + SPDK_SCSI_SENSE_ABORTED_COMMAND, + SPDK_SCSI_ASC_NO_ADDITIONAL_SENSE, + SPDK_SCSI_ASCQ_CAUSE_NOT_REPORTABLE); +} |