diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/lib/scsi/scsi_pr.c | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/lib/scsi/scsi_pr.c')
-rw-r--r-- | src/spdk/lib/scsi/scsi_pr.c | 1067 |
1 files changed, 1067 insertions, 0 deletions
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; +} |