Commit d0d19eb8 authored by Changpeng Liu's avatar Changpeng Liu Committed by Jim Harris
Browse files

scsi: add persistent reservation out with register feature support



To establish a persistent reservation the application client shall first
register an I_T nexus with the device server. An application client registers
with a logical unit by issuing a PERSISTENT RESERVE OUT command with REGISTER
service action or REGISTER AND IGNORE EXISTING KEY service action.

Specify Initiator Ports (SPEC_I_PT) bit and All Target Ports (ALL_TG_PT) bit
are not supported for now, the registrants belong to the I_T nexus who sends
the command.

Also Activate Persist Through Power Loss (APTPL) bit will be supported in
following patches.

Change-Id: If057a764a4bfa73017f98048c94b697dbfa4b4a1
Signed-off-by: default avatarChangpeng Liu <changpeng.liu@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/436088


Reviewed-by: default avatarShuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
parent 6a8a1b6b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@
SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..)
include $(SPDK_ROOT_DIR)/mk/spdk.common.mk

C_SRCS = dev.c lun.c port.c scsi.c scsi_bdev.c scsi_rpc.c task.c
C_SRCS = dev.c lun.c port.c scsi.c scsi_bdev.c scsi_pr.c scsi_rpc.c task.c
LIBNAME = scsi

include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk
+7 −0
Original line number Diff line number Diff line
@@ -214,6 +214,12 @@ spdk_scsi_lun_execute_tasks(struct spdk_scsi_lun *lun)
static void
scsi_lun_remove(struct spdk_scsi_lun *lun)
{
	struct spdk_scsi_pr_registrant *reg, *tmp;

	TAILQ_FOREACH_SAFE(reg, &lun->reg_head, link, tmp) {
		TAILQ_REMOVE(&lun->reg_head, reg, link);
		free(reg);
	}
	spdk_bdev_close(lun->bdev_desc);

	free(lun);
@@ -358,6 +364,7 @@ spdk_scsi_lun_construct(struct spdk_bdev *bdev,
	lun->hotremove_cb = hotremove_cb;
	lun->hotremove_ctx = hotremove_ctx;
	TAILQ_INIT(&lun->open_descs);
	TAILQ_INIT(&lun->reg_head);

	return lun;
}
+25 −0
Original line number Diff line number Diff line
@@ -2018,6 +2018,31 @@ bdev_scsi_process_primary(struct spdk_scsi_task *task)
		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 = spdk_scsi_pr_out(task, cdb, data, data_len);
		if (rc < 0) {
			break;
		}
		rc = pllen;
		data_len = 0;
		break;

	default:
		return SPDK_SCSI_TASK_UNKNOWN;
	}
+39 −10
Original line number Diff line number Diff line
@@ -60,6 +60,26 @@ struct spdk_scsi_port {
	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;
};

/* Reservation with LU_SCOPE */
struct spdk_scsi_pr_reservation {
	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;
@@ -115,6 +135,13 @@ struct spdk_scsi_lun {
	/** 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;

	/** List of open descriptors for this LUN. */
	TAILQ_HEAD(, spdk_scsi_lun_desc) open_descs;

@@ -174,6 +201,8 @@ void spdk_bdev_scsi_reset(struct spdk_scsi_task *task);
bool spdk_scsi_bdev_get_dif_ctx(struct spdk_bdev *bdev, uint8_t *cdb, uint32_t offset,
				struct spdk_dif_ctx *dif_ctx);

int spdk_scsi_pr_out(struct spdk_scsi_task *task, uint8_t *cdb, uint8_t *data, uint16_t data_len);

struct spdk_scsi_globals {
	pthread_mutex_t mutex;
};

lib/scsi/scsi_pr.c

0 → 100644
+261 −0
Original line number Diff line number Diff line
/*-
 *   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 *
spdk_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;
}

/* Reservation type is all registrants or not */
static inline bool
spdk_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
spdk_scsi_pr_registrant_is_holder(struct spdk_scsi_lun *lun,
				  struct spdk_scsi_pr_registrant *reg)
{
	if (spdk_scsi_pr_is_all_registrants_type(lun)) {
		return true;
	}

	return (lun->reservation.holder == reg);
}

static int
spdk_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;
	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;
	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
spdk_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 = spdk_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
spdk_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 (spdk_scsi_pr_registrant_is_holder(lun, reg)) {
		spdk_scsi_pr_release_reservation(lun, reg);
	}

	free(reg);
	lun->pr_generation++;
}

static void
spdk_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
spdk_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 = spdk_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 spdk_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 */
			spdk_scsi_pr_unregister_registrant(lun, reg);
		} else {
			/* replace */
			spdk_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;
}

int
spdk_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;
	struct spdk_scsi_pr_out_param_list *param = (struct spdk_scsi_pr_out_param_list *)data;

	action = cdb[1] & 0x0f;

	rkey = from_be64(&param->rkey);
	sa_rkey = from_be64(&param->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 = spdk_scsi_pr_out_register(task, action, rkey, sa_rkey,
					       spec_i_pt, all_tg_pt, aptpl);
		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;
}
Loading