scsi: add SPC2 RESERVE/RELEASE support

Some OS(Windows and VMWARE esxi) will send SPC2 RESERVE/RELEASE
commands when formatting the filesystems, but in our previous
implementation of reservation feature, we didn't add such support,
in specification SPC3/4, they all include one section "Exceptions to
SPC-2 RESERVE and RELEASE behavior" feature for compatible support of
SCP2 RESERVE/RELEASE, so we add this support now.

Fix issue #1414.

Change-Id: I65d85ffb3b8f824b0ae4e130f53f01d95735d700
Signed-off-by: Changpeng Liu <changpeng.liu@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/2802
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Community-CI: Broadcom CI
Community-CI: Mellanox Build Bot
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
This commit is contained in:
Changpeng Liu 2020-06-08 02:13:10 -04:00 committed by Tomasz Zawadzki
parent 9d7b1f7525
commit 6b6a3ff91f
7 changed files with 223 additions and 4 deletions

View File

@ -34,7 +34,7 @@
SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..)
include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
SO_VER := 3
SO_VER := 4
SO_MINOR := 0
C_SRCS = dev.c lun.c port.c scsi.c scsi_bdev.c scsi_pr.c scsi_rpc.c task.c

View File

@ -197,7 +197,11 @@ _scsi_lun_execute_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)
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;

View File

@ -1712,7 +1712,6 @@ bdev_scsi_process_primary(struct spdk_scsi_task *task)
int dbd, pc, page, subpage;
int cmd_parsed = 0;
switch (cdb[0]) {
case SPDK_SPC_INQUIRY:
alloc_len = from_be16(&cdb[3]);
@ -1931,6 +1930,22 @@ bdev_scsi_process_primary(struct spdk_scsi_task *task)
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;
}

View File

@ -73,8 +73,11 @@ struct spdk_scsi_pr_registrant {
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;
@ -144,6 +147,8 @@ struct spdk_scsi_lun {
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;
@ -196,6 +201,10 @@ int scsi_pr_out(struct spdk_scsi_task *task, uint8_t *cdb, uint8_t *data, uint16
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;
};

View File

@ -53,6 +53,23 @@ scsi_pr_get_registrant(struct spdk_scsi_lun *lun,
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)
@ -638,8 +655,9 @@ scsi_pr_in_report_capabilities(struct spdk_scsi_task *task,
param = (struct spdk_scsi_pr_in_report_capabilities_data *)data;
memset(param, 0, sizeof(*param));
/* TODO: can support more capabilities bits */
to_be16(&param->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;
@ -775,6 +793,12 @@ scsi_pr_check(struct spdk_scsi_task *task)
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:
@ -878,3 +902,166 @@ conflict:
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;
}

View File

@ -107,6 +107,7 @@ DEFINE_STUB_V(spdk_scsi_dev_delete_lun,
(struct spdk_scsi_dev *dev, struct spdk_scsi_lun *lun));
DEFINE_STUB(scsi_pr_check, int, (struct spdk_scsi_task *task), 0);
DEFINE_STUB(scsi2_reserve_check, int, (struct spdk_scsi_task *task), 0);
void
bdev_scsi_reset(struct spdk_scsi_task *task)

View File

@ -104,6 +104,9 @@ DEFINE_STUB(scsi_pr_out, int, (struct spdk_scsi_task *task,
DEFINE_STUB(scsi_pr_in, int, (struct spdk_scsi_task *task, uint8_t *cdb,
uint8_t *data, uint16_t data_len), 0);
DEFINE_STUB(scsi2_reserve, int, (struct spdk_scsi_task *task, uint8_t *cdb), 0);
DEFINE_STUB(scsi2_release, int, (struct spdk_scsi_task *task), 0);
void
scsi_lun_complete_task(struct spdk_scsi_lun *lun, struct spdk_scsi_task *task)
{