/*
 *   BSD LICENSE
 *
 *   Copyright (c) 2018-2019 Broadcom.  All Rights Reserved.
 *   The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
 *   Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. 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.
 */

/*
 * NVMe_FC transport functions.
 */

#include "spdk/env.h"
#include "spdk/assert.h"
#include "spdk/nvmf_transport.h"
#include "spdk/string.h"
#include "spdk/trace.h"
#include "spdk/util.h"
#include "spdk/likely.h"
#include "spdk/endian.h"
#include "spdk/log.h"
#include "spdk/thread.h"

#include "nvmf_fc.h"
#include "fc_lld.h"

#include "spdk_internal/trace_defs.h"

#ifndef DEV_VERIFY
#define DEV_VERIFY assert
#endif

#ifndef ASSERT_SPDK_FC_MAIN_THREAD
#define ASSERT_SPDK_FC_MAIN_THREAD() \
        DEV_VERIFY(spdk_get_thread() == nvmf_fc_get_main_thread());
#endif

/*
 * PRLI service parameters
 */
enum spdk_nvmf_fc_service_parameters {
	SPDK_NVMF_FC_FIRST_BURST_SUPPORTED = 0x0001,
	SPDK_NVMF_FC_DISCOVERY_SERVICE = 0x0008,
	SPDK_NVMF_FC_TARGET_FUNCTION = 0x0010,
	SPDK_NVMF_FC_INITIATOR_FUNCTION = 0x0020,
	SPDK_NVMF_FC_CONFIRMED_COMPLETION_SUPPORTED = 0x0080,
};

static char *fc_req_state_strs[] = {
	"SPDK_NVMF_FC_REQ_INIT",
	"SPDK_NVMF_FC_REQ_READ_BDEV",
	"SPDK_NVMF_FC_REQ_READ_XFER",
	"SPDK_NVMF_FC_REQ_READ_RSP",
	"SPDK_NVMF_FC_REQ_WRITE_BUFFS",
	"SPDK_NVMF_FC_REQ_WRITE_XFER",
	"SPDK_NVMF_FC_REQ_WRITE_BDEV",
	"SPDK_NVMF_FC_REQ_WRITE_RSP",
	"SPDK_NVMF_FC_REQ_NONE_BDEV",
	"SPDK_NVMF_FC_REQ_NONE_RSP",
	"SPDK_NVMF_FC_REQ_SUCCESS",
	"SPDK_NVMF_FC_REQ_FAILED",
	"SPDK_NVMF_FC_REQ_ABORTED",
	"SPDK_NVMF_FC_REQ_BDEV_ABORTED",
	"SPDK_NVMF_FC_REQ_PENDING",
	"SPDK_NVMF_FC_REQ_FUSED_WAITING"
};

#define HWQP_CONN_TABLE_SIZE			8192
#define HWQP_RPI_TABLE_SIZE			4096

SPDK_TRACE_REGISTER_FN(nvmf_fc_trace, "nvmf_fc", TRACE_GROUP_NVMF_FC)
{
	spdk_trace_register_object(OBJECT_NVMF_FC_IO, 'r');
	spdk_trace_register_description("FC_NEW",
					TRACE_FC_REQ_INIT,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 1,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_READ_SBMT_TO_BDEV",
					TRACE_FC_REQ_READ_BDEV,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_READ_XFER_DATA",
					TRACE_FC_REQ_READ_XFER,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_READ_RSP",
					TRACE_FC_REQ_READ_RSP,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_WRITE_NEED_BUFFER",
					TRACE_FC_REQ_WRITE_BUFFS,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_WRITE_XFER_DATA",
					TRACE_FC_REQ_WRITE_XFER,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_WRITE_SBMT_TO_BDEV",
					TRACE_FC_REQ_WRITE_BDEV,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_WRITE_RSP",
					TRACE_FC_REQ_WRITE_RSP,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_NONE_SBMT_TO_BDEV",
					TRACE_FC_REQ_NONE_BDEV,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_NONE_RSP",
					TRACE_FC_REQ_NONE_RSP,
					OWNER_NONE, OBJECT_NVMF_FC_IO, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_SUCCESS",
					TRACE_FC_REQ_SUCCESS,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_FAILED",
					TRACE_FC_REQ_FAILED,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_ABRT",
					TRACE_FC_REQ_ABORTED,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_ABRT_SBMT_TO_BDEV",
					TRACE_FC_REQ_BDEV_ABORTED,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_PENDING",
					TRACE_FC_REQ_PENDING,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
	spdk_trace_register_description("FC_FUSED_WAITING",
					TRACE_FC_REQ_FUSED_WAITING,
					OWNER_NONE, OBJECT_NONE, 0,
					SPDK_TRACE_ARG_TYPE_INT, "");
}

/**
 * The structure used by all fc adm functions
 */
struct spdk_nvmf_fc_adm_api_data {
	void *api_args;
	spdk_nvmf_fc_callback cb_func;
};

/**
 * The callback structure for nport-delete
 */
struct spdk_nvmf_fc_adm_nport_del_cb_data {
	struct spdk_nvmf_fc_nport *nport;
	uint8_t port_handle;
	spdk_nvmf_fc_callback fc_cb_func;
	void *fc_cb_ctx;
};

/**
 * The callback structure for it-delete
 */
struct spdk_nvmf_fc_adm_i_t_del_cb_data {
	struct spdk_nvmf_fc_nport *nport;
	struct spdk_nvmf_fc_remote_port_info *rport;
	uint8_t port_handle;
	spdk_nvmf_fc_callback fc_cb_func;
	void *fc_cb_ctx;
};


typedef void (*spdk_nvmf_fc_adm_i_t_delete_assoc_cb_fn)(void *arg, uint32_t err);

/**
 * The callback structure for the it-delete-assoc callback
 */
struct spdk_nvmf_fc_adm_i_t_del_assoc_cb_data {
	struct spdk_nvmf_fc_nport *nport;
	struct spdk_nvmf_fc_remote_port_info *rport;
	uint8_t port_handle;
	spdk_nvmf_fc_adm_i_t_delete_assoc_cb_fn cb_func;
	void *cb_ctx;
};

/*
 * Call back function pointer for HW port quiesce.
 */
typedef void (*spdk_nvmf_fc_adm_hw_port_quiesce_cb_fn)(void *ctx, int err);

/**
 * Context structure for quiescing a hardware port
 */
struct spdk_nvmf_fc_adm_hw_port_quiesce_ctx {
	int quiesce_count;
	void *ctx;
	spdk_nvmf_fc_adm_hw_port_quiesce_cb_fn cb_func;
};

/**
 * Context structure used to reset a hardware port
 */
struct spdk_nvmf_fc_adm_hw_port_reset_ctx {
	void *reset_args;
	spdk_nvmf_fc_callback reset_cb_func;
};

struct spdk_nvmf_fc_transport {
	struct spdk_nvmf_transport transport;
	struct spdk_poller *accept_poller;
	pthread_mutex_t lock;
};

static struct spdk_nvmf_fc_transport *g_nvmf_ftransport;

static spdk_nvmf_transport_destroy_done_cb g_transport_destroy_done_cb = NULL;

static TAILQ_HEAD(, spdk_nvmf_fc_port) g_spdk_nvmf_fc_port_list =
	TAILQ_HEAD_INITIALIZER(g_spdk_nvmf_fc_port_list);

static struct spdk_thread *g_nvmf_fc_main_thread = NULL;

static uint32_t g_nvmf_fgroup_count = 0;
static TAILQ_HEAD(, spdk_nvmf_fc_poll_group) g_nvmf_fgroups =
	TAILQ_HEAD_INITIALIZER(g_nvmf_fgroups);

struct spdk_thread *
nvmf_fc_get_main_thread(void)
{
	return g_nvmf_fc_main_thread;
}

static inline void
nvmf_fc_record_req_trace_point(struct spdk_nvmf_fc_request *fc_req,
			       enum spdk_nvmf_fc_request_state state)
{
	uint16_t tpoint_id = SPDK_TRACE_MAX_TPOINT_ID;

	switch (state) {
	case SPDK_NVMF_FC_REQ_INIT:
		/* Start IO tracing */
		tpoint_id = TRACE_FC_REQ_INIT;
		break;
	case SPDK_NVMF_FC_REQ_READ_BDEV:
		tpoint_id = TRACE_FC_REQ_READ_BDEV;
		break;
	case SPDK_NVMF_FC_REQ_READ_XFER:
		tpoint_id = TRACE_FC_REQ_READ_XFER;
		break;
	case SPDK_NVMF_FC_REQ_READ_RSP:
		tpoint_id = TRACE_FC_REQ_READ_RSP;
		break;
	case SPDK_NVMF_FC_REQ_WRITE_BUFFS:
		tpoint_id = TRACE_FC_REQ_WRITE_BUFFS;
		break;
	case SPDK_NVMF_FC_REQ_WRITE_XFER:
		tpoint_id = TRACE_FC_REQ_WRITE_XFER;
		break;
	case SPDK_NVMF_FC_REQ_WRITE_BDEV:
		tpoint_id = TRACE_FC_REQ_WRITE_BDEV;
		break;
	case SPDK_NVMF_FC_REQ_WRITE_RSP:
		tpoint_id = TRACE_FC_REQ_WRITE_RSP;
		break;
	case SPDK_NVMF_FC_REQ_NONE_BDEV:
		tpoint_id = TRACE_FC_REQ_NONE_BDEV;
		break;
	case SPDK_NVMF_FC_REQ_NONE_RSP:
		tpoint_id = TRACE_FC_REQ_NONE_RSP;
		break;
	case SPDK_NVMF_FC_REQ_SUCCESS:
		tpoint_id = TRACE_FC_REQ_SUCCESS;
		break;
	case SPDK_NVMF_FC_REQ_FAILED:
		tpoint_id = TRACE_FC_REQ_FAILED;
		break;
	case SPDK_NVMF_FC_REQ_ABORTED:
		tpoint_id = TRACE_FC_REQ_ABORTED;
		break;
	case SPDK_NVMF_FC_REQ_BDEV_ABORTED:
		tpoint_id = TRACE_FC_REQ_ABORTED;
		break;
	case SPDK_NVMF_FC_REQ_PENDING:
		tpoint_id = TRACE_FC_REQ_PENDING;
		break;
	case SPDK_NVMF_FC_REQ_FUSED_WAITING:
		tpoint_id = TRACE_FC_REQ_FUSED_WAITING;
		break;
	default:
		assert(0);
		break;
	}
	if (tpoint_id != SPDK_TRACE_MAX_TPOINT_ID) {
		spdk_trace_record(tpoint_id, fc_req->poller_lcore, 0,
				  (uint64_t)(&fc_req->req));
	}
}

static struct rte_hash *
nvmf_fc_create_hash_table(const char *name, size_t num_entries, size_t key_len)
{
	struct rte_hash_parameters hash_params = { 0 };

	hash_params.entries = num_entries;
	hash_params.key_len = key_len;
	hash_params.name = name;

	return rte_hash_create(&hash_params);
}

void
nvmf_fc_free_conn_reqpool(struct spdk_nvmf_fc_conn *fc_conn)
{
	free(fc_conn->pool_memory);
	fc_conn->pool_memory = NULL;
}

int
nvmf_fc_create_conn_reqpool(struct spdk_nvmf_fc_conn *fc_conn)
{
	uint32_t i, qd;
	struct spdk_nvmf_fc_pooled_request *req;

	/*
	 * Create number of fc-requests to be more than the actual SQ size.
	 * This is to handle race conditions where the target driver may send
	 * back a RSP and before the target driver gets to process the CQE
	 * for the RSP, the initiator may have sent a new command.
	 * Depending on the load on the HWQP, there is a slim possibility
	 * that the target reaps the RQE corresponding to the new
	 * command before processing the CQE corresponding to the RSP.
	 */
	qd = fc_conn->max_queue_depth * 2;

	STAILQ_INIT(&fc_conn->pool_queue);
	fc_conn->pool_memory = calloc((fc_conn->max_queue_depth * 2),
				      sizeof(struct spdk_nvmf_fc_request));
	if (!fc_conn->pool_memory) {
		SPDK_ERRLOG("create fc req ring objects failed\n");
		goto error;
	}
	fc_conn->pool_size = qd;
	fc_conn->pool_free_elems = qd;

	/* Initialise value in ring objects and link the objects */
	for (i = 0; i < qd; i++) {
		req = (struct spdk_nvmf_fc_pooled_request *)((char *)fc_conn->pool_memory +
				i * sizeof(struct spdk_nvmf_fc_request));

		STAILQ_INSERT_TAIL(&fc_conn->pool_queue, req, pool_link);
	}
	return 0;
error:
	nvmf_fc_free_conn_reqpool(fc_conn);
	return -1;
}

static inline struct spdk_nvmf_fc_request *
nvmf_fc_conn_alloc_fc_request(struct spdk_nvmf_fc_conn *fc_conn)
{
	struct spdk_nvmf_fc_request *fc_req;
	struct spdk_nvmf_fc_pooled_request *pooled_req;
	struct spdk_nvmf_fc_hwqp *hwqp = fc_conn->hwqp;

	pooled_req = STAILQ_FIRST(&fc_conn->pool_queue);
	if (!pooled_req) {
		SPDK_ERRLOG("Alloc request buffer failed\n");
		return NULL;
	}
	STAILQ_REMOVE_HEAD(&fc_conn->pool_queue, pool_link);
	fc_conn->pool_free_elems -= 1;

	fc_req = (struct spdk_nvmf_fc_request *)pooled_req;
	memset(fc_req, 0, sizeof(struct spdk_nvmf_fc_request));
	nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_INIT);

	TAILQ_INSERT_TAIL(&hwqp->in_use_reqs, fc_req, link);
	TAILQ_INSERT_TAIL(&fc_conn->in_use_reqs, fc_req, conn_link);
	TAILQ_INIT(&fc_req->abort_cbs);
	return fc_req;
}

static inline void
nvmf_fc_conn_free_fc_request(struct spdk_nvmf_fc_conn *fc_conn, struct spdk_nvmf_fc_request *fc_req)
{
	if (fc_req->state != SPDK_NVMF_FC_REQ_SUCCESS) {
		/* Log an error for debug purpose. */
		nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_FAILED);
	}

	/* set the magic to mark req as no longer valid. */
	fc_req->magic = 0xDEADBEEF;

	TAILQ_REMOVE(&fc_conn->hwqp->in_use_reqs, fc_req, link);
	TAILQ_REMOVE(&fc_conn->in_use_reqs, fc_req, conn_link);

	STAILQ_INSERT_HEAD(&fc_conn->pool_queue, (struct spdk_nvmf_fc_pooled_request *)fc_req, pool_link);
	fc_conn->pool_free_elems += 1;
}

static inline void
nvmf_fc_request_remove_from_pending(struct spdk_nvmf_fc_request *fc_req)
{
	STAILQ_REMOVE(&fc_req->hwqp->fgroup->group.pending_buf_queue, &fc_req->req,
		      spdk_nvmf_request, buf_link);
}

int
nvmf_fc_init_hwqp(struct spdk_nvmf_fc_port *fc_port, struct spdk_nvmf_fc_hwqp *hwqp)
{
	char name[64];

	hwqp->fc_port = fc_port;

	/* clear counters */
	memset(&hwqp->counters, 0, sizeof(struct spdk_nvmf_fc_errors));

	TAILQ_INIT(&hwqp->in_use_reqs);
	TAILQ_INIT(&hwqp->sync_cbs);
	TAILQ_INIT(&hwqp->ls_pending_queue);

	snprintf(name, sizeof(name), "nvmf_fc_conn_hash:%d-%d", fc_port->port_hdl, hwqp->hwqp_id);
	hwqp->connection_list_hash = nvmf_fc_create_hash_table(name, HWQP_CONN_TABLE_SIZE,
				     sizeof(uint64_t));
	if (!hwqp->connection_list_hash) {
		SPDK_ERRLOG("Failed to create connection hash table.\n");
		return -ENOMEM;
	}

	snprintf(name, sizeof(name), "nvmf_fc_rpi_hash:%d-%d", fc_port->port_hdl, hwqp->hwqp_id);
	hwqp->rport_list_hash = nvmf_fc_create_hash_table(name, HWQP_RPI_TABLE_SIZE, sizeof(uint16_t));
	if (!hwqp->rport_list_hash) {
		SPDK_ERRLOG("Failed to create rpi hash table.\n");
		rte_hash_free(hwqp->connection_list_hash);
		return -ENOMEM;
	}

	/* Init low level driver queues */
	nvmf_fc_init_q(hwqp);
	return 0;
}

static struct spdk_nvmf_fc_poll_group *
nvmf_fc_assign_idlest_poll_group(struct spdk_nvmf_fc_hwqp *hwqp)
{
	uint32_t max_count = UINT32_MAX;
	struct spdk_nvmf_fc_poll_group *fgroup;
	struct spdk_nvmf_fc_poll_group *ret_fgroup = NULL;

	pthread_mutex_lock(&g_nvmf_ftransport->lock);
	/* find poll group with least number of hwqp's assigned to it */
	TAILQ_FOREACH(fgroup, &g_nvmf_fgroups, link) {
		if (fgroup->hwqp_count < max_count) {
			ret_fgroup = fgroup;
			max_count = fgroup->hwqp_count;
		}
	}

	if (ret_fgroup) {
		ret_fgroup->hwqp_count++;
		hwqp->thread = ret_fgroup->group.group->thread;
		hwqp->fgroup = ret_fgroup;
	}

	pthread_mutex_unlock(&g_nvmf_ftransport->lock);

	return ret_fgroup;
}

bool
nvmf_fc_poll_group_valid(struct spdk_nvmf_fc_poll_group *fgroup)
{
	struct spdk_nvmf_fc_poll_group *tmp;
	bool rc = false;

	pthread_mutex_lock(&g_nvmf_ftransport->lock);
	TAILQ_FOREACH(tmp, &g_nvmf_fgroups, link) {
		if (tmp == fgroup) {
			rc = true;
			break;
		}
	}
	pthread_mutex_unlock(&g_nvmf_ftransport->lock);
	return rc;
}

void
nvmf_fc_poll_group_add_hwqp(struct spdk_nvmf_fc_hwqp *hwqp)
{
	assert(hwqp);
	if (hwqp == NULL) {
		SPDK_ERRLOG("Error: hwqp is NULL\n");
		return;
	}

	assert(g_nvmf_fgroup_count);

	if (!nvmf_fc_assign_idlest_poll_group(hwqp)) {
		SPDK_ERRLOG("Could not assign poll group for hwqp (%d)\n", hwqp->hwqp_id);
		return;
	}

	nvmf_fc_poller_api_func(hwqp, SPDK_NVMF_FC_POLLER_API_ADD_HWQP, NULL);
}

static void
nvmf_fc_poll_group_remove_hwqp_cb(void *cb_data, enum spdk_nvmf_fc_poller_api_ret ret)
{
	struct spdk_nvmf_fc_poller_api_remove_hwqp_args *args = cb_data;

	if (ret == SPDK_NVMF_FC_POLLER_API_SUCCESS) {
		SPDK_DEBUGLOG(nvmf_fc_adm_api,
			      "Remove hwqp%d from fgroup success\n", args->hwqp->hwqp_id);
	} else {
		SPDK_ERRLOG("Remove hwqp%d from fgroup failed.\n", args->hwqp->hwqp_id);
	}

	if (args->cb_fn) {
		args->cb_fn(args->cb_ctx, 0);
	}

	free(args);
}

void
nvmf_fc_poll_group_remove_hwqp(struct spdk_nvmf_fc_hwqp *hwqp,
			       spdk_nvmf_fc_remove_hwqp_cb cb_fn, void *cb_ctx)
{
	struct spdk_nvmf_fc_poller_api_remove_hwqp_args *args;
	struct spdk_nvmf_fc_poll_group *tmp;
	int rc = 0;

	assert(hwqp);

	SPDK_DEBUGLOG(nvmf_fc,
		      "Remove hwqp from poller: for port: %d, hwqp: %d\n",
		      hwqp->fc_port->port_hdl, hwqp->hwqp_id);

	if (!hwqp->fgroup) {
		SPDK_ERRLOG("HWQP (%d) not assigned to poll group\n", hwqp->hwqp_id);
	} else {
		pthread_mutex_lock(&g_nvmf_ftransport->lock);
		TAILQ_FOREACH(tmp, &g_nvmf_fgroups, link) {
			if (tmp == hwqp->fgroup) {
				hwqp->fgroup->hwqp_count--;
				break;
			}
		}
		pthread_mutex_unlock(&g_nvmf_ftransport->lock);

		if (tmp != hwqp->fgroup) {
			/* Pollgroup was already removed. Dont bother. */
			goto done;
		}

		args = calloc(1, sizeof(struct spdk_nvmf_fc_poller_api_remove_hwqp_args));
		if (args == NULL) {
			rc = -ENOMEM;
			SPDK_ERRLOG("Failed to allocate memory for poller remove hwqp:%d\n", hwqp->hwqp_id);
			goto done;
		}

		args->hwqp   = hwqp;
		args->cb_fn  = cb_fn;
		args->cb_ctx = cb_ctx;
		args->cb_info.cb_func = nvmf_fc_poll_group_remove_hwqp_cb;
		args->cb_info.cb_data = args;
		args->cb_info.cb_thread = spdk_get_thread();

		rc = nvmf_fc_poller_api_func(hwqp, SPDK_NVMF_FC_POLLER_API_REMOVE_HWQP, args);
		if (rc) {
			rc = -EINVAL;
			SPDK_ERRLOG("Remove hwqp%d from fgroup failed.\n", hwqp->hwqp_id);
			free(args);
			goto done;
		}
		return;
	}
done:
	if (cb_fn) {
		cb_fn(cb_ctx, rc);
	}
}

/*
 * Note: This needs to be used only on main poller.
 */
static uint64_t
nvmf_fc_get_abts_unique_id(void)
{
	static uint32_t u_id = 0;

	return (uint64_t)(++u_id);
}

static void
nvmf_fc_queue_synced_cb(void *cb_data, enum spdk_nvmf_fc_poller_api_ret ret)
{
	struct spdk_nvmf_fc_abts_ctx *ctx = cb_data;
	struct spdk_nvmf_fc_poller_api_abts_recvd_args *args, *poller_arg;

	ctx->hwqps_responded++;

	if (ctx->hwqps_responded < ctx->num_hwqps) {
		/* Wait for all pollers to complete. */
		return;
	}

	/* Free the queue sync poller args. */
	free(ctx->sync_poller_args);

	/* Mark as queue synced */
	ctx->queue_synced = true;

	/* Reset the ctx values */
	ctx->hwqps_responded = 0;
	ctx->handled = false;

	SPDK_DEBUGLOG(nvmf_fc,
		      "QueueSync(0x%lx) completed for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
		      ctx->u_id, ctx->nport->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);

	/* Resend ABTS to pollers */
	args = ctx->abts_poller_args;
	for (int i = 0; i < ctx->num_hwqps; i++) {
		poller_arg = args + i;
		nvmf_fc_poller_api_func(poller_arg->hwqp,
					SPDK_NVMF_FC_POLLER_API_ABTS_RECEIVED,
					poller_arg);
	}
}

static int
nvmf_fc_handle_abts_notfound(struct spdk_nvmf_fc_abts_ctx *ctx)
{
	struct spdk_nvmf_fc_poller_api_queue_sync_args *args, *poller_arg;
	struct spdk_nvmf_fc_poller_api_abts_recvd_args *abts_args, *abts_poller_arg;

	/* check if FC driver supports queue sync */
	if (!nvmf_fc_q_sync_available()) {
		return -EPERM;
	}

	assert(ctx);
	if (!ctx) {
		SPDK_ERRLOG("NULL ctx pointer");
		return -EINVAL;
	}

	/* Reset the ctx values */
	ctx->hwqps_responded = 0;

	args = calloc(ctx->num_hwqps,
		      sizeof(struct spdk_nvmf_fc_poller_api_queue_sync_args));
	if (!args) {
		SPDK_ERRLOG("QueueSync(0x%lx) failed for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
			    ctx->u_id, ctx->nport->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);
		return -ENOMEM;
	}
	ctx->sync_poller_args = args;

	abts_args = ctx->abts_poller_args;
	for (int i = 0; i < ctx->num_hwqps; i++) {
		abts_poller_arg = abts_args + i;
		poller_arg = args + i;
		poller_arg->u_id = ctx->u_id;
		poller_arg->hwqp = abts_poller_arg->hwqp;
		poller_arg->cb_info.cb_func = nvmf_fc_queue_synced_cb;
		poller_arg->cb_info.cb_data = ctx;
		poller_arg->cb_info.cb_thread = spdk_get_thread();

		/* Send a Queue sync message to interested pollers */
		nvmf_fc_poller_api_func(poller_arg->hwqp,
					SPDK_NVMF_FC_POLLER_API_QUEUE_SYNC,
					poller_arg);
	}

	SPDK_DEBUGLOG(nvmf_fc,
		      "QueueSync(0x%lx) Sent for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
		      ctx->u_id, ctx->nport->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);

	/* Post Marker to queue to track aborted request */
	nvmf_fc_issue_q_sync(ctx->ls_hwqp, ctx->u_id, ctx->fcp_rq_id);

	return 0;
}

static void
nvmf_fc_abts_handled_cb(void *cb_data, enum spdk_nvmf_fc_poller_api_ret ret)
{
	struct spdk_nvmf_fc_abts_ctx *ctx = cb_data;
	struct spdk_nvmf_fc_nport *nport  = NULL;

	if (ret != SPDK_NVMF_FC_POLLER_API_OXID_NOT_FOUND) {
		ctx->handled = true;
	}

	ctx->hwqps_responded++;

	if (ctx->hwqps_responded < ctx->num_hwqps) {
		/* Wait for all pollers to complete. */
		return;
	}

	nport = nvmf_fc_nport_find(ctx->port_hdl, ctx->nport_hdl);

	if (ctx->nport != nport) {
		/* Nport can be deleted while this abort is being
		 * processed by the pollers.
		 */
		SPDK_NOTICELOG("nport_%d deleted while processing ABTS frame, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
			       ctx->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);
	} else {
		if (!ctx->handled) {
			/* Try syncing the queues and try one more time */
			if (!ctx->queue_synced && (nvmf_fc_handle_abts_notfound(ctx) == 0)) {
				SPDK_DEBUGLOG(nvmf_fc,
					      "QueueSync(0x%lx) for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
					      ctx->u_id, ctx->nport->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);
				return;
			} else {
				/* Send Reject */
				nvmf_fc_xmt_bls_rsp(&ctx->nport->fc_port->ls_queue,
						    ctx->oxid, ctx->rxid, ctx->rpi, true,
						    FCNVME_BLS_REJECT_EXP_INVALID_OXID, NULL, NULL);
			}
		} else {
			/* Send Accept */
			nvmf_fc_xmt_bls_rsp(&ctx->nport->fc_port->ls_queue,
					    ctx->oxid, ctx->rxid, ctx->rpi, false,
					    0, NULL, NULL);
		}
	}
	SPDK_NOTICELOG("BLS_%s sent for ABTS frame nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
		       (ctx->handled) ? "ACC" : "REJ", ctx->nport->nport_hdl, ctx->rpi, ctx->oxid, ctx->rxid);

	free(ctx->abts_poller_args);
	free(ctx);
}

void
nvmf_fc_handle_abts_frame(struct spdk_nvmf_fc_nport *nport, uint16_t rpi,
			  uint16_t oxid, uint16_t rxid)
{
	struct spdk_nvmf_fc_abts_ctx *ctx = NULL;
	struct spdk_nvmf_fc_poller_api_abts_recvd_args *args = NULL, *poller_arg;
	struct spdk_nvmf_fc_association *assoc = NULL;
	struct spdk_nvmf_fc_conn *conn = NULL;
	uint32_t hwqp_cnt = 0;
	bool skip_hwqp_cnt;
	struct spdk_nvmf_fc_hwqp **hwqps = NULL;
	uint32_t i;

	SPDK_NOTICELOG("Handle ABTS frame for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
		       nport->nport_hdl, rpi, oxid, rxid);

	/* Allocate memory to track hwqp's with at least 1 active connection. */
	hwqps = calloc(nport->fc_port->num_io_queues, sizeof(struct spdk_nvmf_fc_hwqp *));
	if (hwqps == NULL) {
		SPDK_ERRLOG("Unable to allocate temp. hwqp array for abts processing!\n");
		goto bls_rej;
	}

	TAILQ_FOREACH(assoc, &nport->fc_associations, link) {
		TAILQ_FOREACH(conn, &assoc->fc_conns, assoc_link) {
			if ((conn->rpi != rpi) || !conn->hwqp) {
				continue;
			}

			skip_hwqp_cnt = false;
			for (i = 0; i < hwqp_cnt; i++) {
				if (hwqps[i] == conn->hwqp) {
					/* Skip. This is already present */
					skip_hwqp_cnt = true;
					break;
				}
			}
			if (!skip_hwqp_cnt) {
				assert(hwqp_cnt < nport->fc_port->num_io_queues);
				hwqps[hwqp_cnt] = conn->hwqp;
				hwqp_cnt++;
			}
		}
	}

	if (!hwqp_cnt) {
		goto bls_rej;
	}

	args = calloc(hwqp_cnt,
		      sizeof(struct spdk_nvmf_fc_poller_api_abts_recvd_args));
	if (!args) {
		goto bls_rej;
	}

	ctx = calloc(1, sizeof(struct spdk_nvmf_fc_abts_ctx));
	if (!ctx) {
		goto bls_rej;
	}
	ctx->rpi = rpi;
	ctx->oxid = oxid;
	ctx->rxid = rxid;
	ctx->nport = nport;
	ctx->nport_hdl = nport->nport_hdl;
	ctx->port_hdl = nport->fc_port->port_hdl;
	ctx->num_hwqps = hwqp_cnt;
	ctx->ls_hwqp = &nport->fc_port->ls_queue;
	ctx->fcp_rq_id = nport->fc_port->fcp_rq_id;
	ctx->abts_poller_args = args;

	/* Get a unique context for this ABTS */
	ctx->u_id = nvmf_fc_get_abts_unique_id();

	for (i = 0; i < hwqp_cnt; i++) {
		poller_arg = args + i;
		poller_arg->hwqp = hwqps[i];
		poller_arg->cb_info.cb_func = nvmf_fc_abts_handled_cb;
		poller_arg->cb_info.cb_data = ctx;
		poller_arg->cb_info.cb_thread = spdk_get_thread();
		poller_arg->ctx = ctx;

		nvmf_fc_poller_api_func(poller_arg->hwqp,
					SPDK_NVMF_FC_POLLER_API_ABTS_RECEIVED,
					poller_arg);
	}

	free(hwqps);

	return;
bls_rej:
	free(args);
	free(hwqps);

	/* Send Reject */
	nvmf_fc_xmt_bls_rsp(&nport->fc_port->ls_queue, oxid, rxid, rpi,
			    true, FCNVME_BLS_REJECT_EXP_NOINFO, NULL, NULL);
	SPDK_NOTICELOG("BLS_RJT for ABTS frame for nport: %d, rpi: 0x%x, oxid: 0x%x, rxid: 0x%x\n",
		       nport->nport_hdl, rpi, oxid, rxid);
	return;
}

/*** Accessor functions for the FC structures - BEGIN */
/*
 * Returns true if the port is in offline state.
 */
bool
nvmf_fc_port_is_offline(struct spdk_nvmf_fc_port *fc_port)
{
	if (fc_port && (fc_port->hw_port_status == SPDK_FC_PORT_OFFLINE)) {
		return true;
	}

	return false;
}

/*
 * Returns true if the port is in online state.
 */
bool
nvmf_fc_port_is_online(struct spdk_nvmf_fc_port *fc_port)
{
	if (fc_port && (fc_port->hw_port_status == SPDK_FC_PORT_ONLINE)) {
		return true;
	}

	return false;
}

int
nvmf_fc_port_set_online(struct spdk_nvmf_fc_port *fc_port)
{
	if (fc_port && (fc_port->hw_port_status != SPDK_FC_PORT_ONLINE)) {
		fc_port->hw_port_status = SPDK_FC_PORT_ONLINE;
		return 0;
	}

	return -EPERM;
}

int
nvmf_fc_port_set_offline(struct spdk_nvmf_fc_port *fc_port)
{
	if (fc_port && (fc_port->hw_port_status != SPDK_FC_PORT_OFFLINE)) {
		fc_port->hw_port_status = SPDK_FC_PORT_OFFLINE;
		return 0;
	}

	return -EPERM;
}

int
nvmf_fc_hwqp_set_online(struct spdk_nvmf_fc_hwqp *hwqp)
{
	if (hwqp && (hwqp->state != SPDK_FC_HWQP_ONLINE)) {
		hwqp->state = SPDK_FC_HWQP_ONLINE;
		/* reset some queue counters */
		hwqp->num_conns = 0;
		return nvmf_fc_set_q_online_state(hwqp, true);
	}

	return -EPERM;
}

int
nvmf_fc_hwqp_set_offline(struct spdk_nvmf_fc_hwqp *hwqp)
{
	if (hwqp && (hwqp->state != SPDK_FC_HWQP_OFFLINE)) {
		hwqp->state = SPDK_FC_HWQP_OFFLINE;
		return nvmf_fc_set_q_online_state(hwqp, false);
	}

	return -EPERM;
}

void
nvmf_fc_port_add(struct spdk_nvmf_fc_port *fc_port)
{
	TAILQ_INSERT_TAIL(&g_spdk_nvmf_fc_port_list, fc_port, link);

	/*
	 * Let LLD add the port to its list.
	 */
	nvmf_fc_lld_port_add(fc_port);
}

static void
nvmf_fc_port_remove(struct spdk_nvmf_fc_port *fc_port)
{
	TAILQ_REMOVE(&g_spdk_nvmf_fc_port_list, fc_port, link);

	/*
	 * Let LLD remove the port from its list.
	 */
	nvmf_fc_lld_port_remove(fc_port);
}

struct spdk_nvmf_fc_port *
nvmf_fc_port_lookup(uint8_t port_hdl)
{
	struct spdk_nvmf_fc_port *fc_port = NULL;

	TAILQ_FOREACH(fc_port, &g_spdk_nvmf_fc_port_list, link) {
		if (fc_port->port_hdl == port_hdl) {
			return fc_port;
		}
	}
	return NULL;
}

uint32_t
nvmf_fc_get_prli_service_params(void)
{
	return (SPDK_NVMF_FC_DISCOVERY_SERVICE | SPDK_NVMF_FC_TARGET_FUNCTION);
}

int
nvmf_fc_port_add_nport(struct spdk_nvmf_fc_port *fc_port,
		       struct spdk_nvmf_fc_nport *nport)
{
	if (fc_port) {
		TAILQ_INSERT_TAIL(&fc_port->nport_list, nport, link);
		fc_port->num_nports++;
		return 0;
	}

	return -EINVAL;
}

int
nvmf_fc_port_remove_nport(struct spdk_nvmf_fc_port *fc_port,
			  struct spdk_nvmf_fc_nport *nport)
{
	if (fc_port && nport) {
		TAILQ_REMOVE(&fc_port->nport_list, nport, link);
		fc_port->num_nports--;
		return 0;
	}

	return -EINVAL;
}

static struct spdk_nvmf_fc_nport *
nvmf_fc_nport_hdl_lookup(struct spdk_nvmf_fc_port *fc_port, uint16_t nport_hdl)
{
	struct spdk_nvmf_fc_nport *fc_nport = NULL;

	TAILQ_FOREACH(fc_nport, &fc_port->nport_list, link) {
		if (fc_nport->nport_hdl == nport_hdl) {
			return fc_nport;
		}
	}

	return NULL;
}

struct spdk_nvmf_fc_nport *
nvmf_fc_nport_find(uint8_t port_hdl, uint16_t nport_hdl)
{
	struct spdk_nvmf_fc_port *fc_port = NULL;

	fc_port = nvmf_fc_port_lookup(port_hdl);
	if (fc_port) {
		return nvmf_fc_nport_hdl_lookup(fc_port, nport_hdl);
	}

	return NULL;
}

static inline int
nvmf_fc_hwqp_find_nport_and_rport(struct spdk_nvmf_fc_hwqp *hwqp,
				  uint32_t d_id, struct spdk_nvmf_fc_nport **nport,
				  uint32_t s_id, struct spdk_nvmf_fc_remote_port_info **rport)
{
	struct spdk_nvmf_fc_nport *n_port;
	struct spdk_nvmf_fc_remote_port_info *r_port;

	assert(hwqp);
	if (hwqp == NULL) {
		SPDK_ERRLOG("Error: hwqp is NULL\n");
		return -EINVAL;
	}
	assert(nport);
	if (nport == NULL) {
		SPDK_ERRLOG("Error: nport is NULL\n");
		return -EINVAL;
	}
	assert(rport);
	if (rport == NULL) {
		SPDK_ERRLOG("Error: rport is NULL\n");
		return -EINVAL;
	}

	TAILQ_FOREACH(n_port, &hwqp->fc_port->nport_list, link) {
		if (n_port->d_id == d_id) {
			TAILQ_FOREACH(r_port, &n_port->rem_port_list, link) {
				if (r_port->s_id == s_id) {
					*nport = n_port;
					*rport = r_port;
					return 0;
				}
			}
			break;
		}
	}

	return -ENOENT;
}

/* Returns true if the Nport is empty of all rem_ports */
bool
nvmf_fc_nport_has_no_rport(struct spdk_nvmf_fc_nport *nport)
{
	if (nport && TAILQ_EMPTY(&nport->rem_port_list)) {
		assert(nport->rport_count == 0);
		return true;
	} else {
		return false;
	}
}

int
nvmf_fc_nport_set_state(struct spdk_nvmf_fc_nport *nport,
			enum spdk_nvmf_fc_object_state state)
{
	if (nport) {
		nport->nport_state = state;
		return 0;
	} else {
		return -EINVAL;
	}
}

bool
nvmf_fc_nport_add_rem_port(struct spdk_nvmf_fc_nport *nport,
			   struct spdk_nvmf_fc_remote_port_info *rem_port)
{
	if (nport && rem_port) {
		TAILQ_INSERT_TAIL(&nport->rem_port_list, rem_port, link);
		nport->rport_count++;
		return 0;
	} else {
		return -EINVAL;
	}
}

bool
nvmf_fc_nport_remove_rem_port(struct spdk_nvmf_fc_nport *nport,
			      struct spdk_nvmf_fc_remote_port_info *rem_port)
{
	if (nport && rem_port) {
		TAILQ_REMOVE(&nport->rem_port_list, rem_port, link);
		nport->rport_count--;
		return 0;
	} else {
		return -EINVAL;
	}
}

int
nvmf_fc_rport_set_state(struct spdk_nvmf_fc_remote_port_info *rport,
			enum spdk_nvmf_fc_object_state state)
{
	if (rport) {
		rport->rport_state = state;
		return 0;
	} else {
		return -EINVAL;
	}
}
int
nvmf_fc_assoc_set_state(struct spdk_nvmf_fc_association *assoc,
			enum spdk_nvmf_fc_object_state state)
{
	if (assoc) {
		assoc->assoc_state = state;
		return 0;
	} else {
		return -EINVAL;
	}
}

static struct spdk_nvmf_fc_association *
nvmf_ctrlr_get_fc_assoc(struct spdk_nvmf_ctrlr *ctrlr)
{
	struct spdk_nvmf_qpair *qpair = ctrlr->admin_qpair;
	struct spdk_nvmf_fc_conn *fc_conn;

	if (!qpair) {
		SPDK_ERRLOG("Controller %d has no associations\n", ctrlr->cntlid);
		return NULL;
	}

	fc_conn = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);

	return fc_conn->fc_assoc;
}

bool
nvmf_ctrlr_is_on_nport(uint8_t port_hdl, uint16_t nport_hdl,
		       struct spdk_nvmf_ctrlr *ctrlr)
{
	struct spdk_nvmf_fc_nport *fc_nport = NULL;
	struct spdk_nvmf_fc_association *assoc = NULL;

	if (!ctrlr) {
		return false;
	}

	fc_nport = nvmf_fc_nport_find(port_hdl, nport_hdl);
	if (!fc_nport) {
		return false;
	}

	assoc = nvmf_ctrlr_get_fc_assoc(ctrlr);
	if (assoc && assoc->tgtport == fc_nport) {
		SPDK_DEBUGLOG(nvmf_fc,
			      "Controller: %d corresponding to association: %p(%lu:%d) is on port: %d nport: %d\n",
			      ctrlr->cntlid, assoc, assoc->assoc_id, assoc->assoc_state, port_hdl,
			      nport_hdl);
		return true;
	}
	return false;
}

static void
nvmf_fc_release_ls_rqst(struct spdk_nvmf_fc_hwqp *hwqp,
			struct spdk_nvmf_fc_ls_rqst *ls_rqst)
{
	assert(ls_rqst);

	TAILQ_REMOVE(&hwqp->ls_pending_queue, ls_rqst, ls_pending_link);

	/* Return buffer to chip */
	nvmf_fc_rqpair_buffer_release(hwqp, ls_rqst->rqstbuf.buf_index);
}

static int
nvmf_fc_delete_ls_pending(struct spdk_nvmf_fc_hwqp *hwqp,
			  struct spdk_nvmf_fc_nport *nport,
			  struct spdk_nvmf_fc_remote_port_info *rport)
{
	struct spdk_nvmf_fc_ls_rqst *ls_rqst = NULL, *tmp;
	int num_deleted = 0;

	assert(hwqp);
	assert(nport);
	assert(rport);

	TAILQ_FOREACH_SAFE(ls_rqst, &hwqp->ls_pending_queue, ls_pending_link, tmp) {
		if ((ls_rqst->d_id == nport->d_id) && (ls_rqst->s_id == rport->s_id)) {
			num_deleted++;
			nvmf_fc_release_ls_rqst(hwqp, ls_rqst);
		}
	}
	return num_deleted;
}

static void
nvmf_fc_req_bdev_abort(void *arg1)
{
	struct spdk_nvmf_fc_request *fc_req = arg1;
	struct spdk_nvmf_ctrlr *ctrlr = fc_req->req.qpair->ctrlr;
	int i;

	/* Initial release - we don't have to abort Admin Queue or
	 * Fabric commands. The AQ commands supported at this time are
	 * Get-Log-Page,
	 * Identify
	 * Set Features
	 * Get Features
	 * AER -> Special case and handled differently.
	 * Every one of the above Admin commands (except AER) run
	 * to completion and so an Abort of such commands doesn't
	 * make sense.
	 */
	/* The Fabric commands supported are
	 * Property Set
	 * Property Get
	 * Connect -> Special case (async. handling). Not sure how to
	 * handle at this point. Let it run to completion.
	 */
	for (i = 0; i < NVMF_MAX_ASYNC_EVENTS; i++) {
		if (ctrlr->aer_req[i] == &fc_req->req) {
			SPDK_NOTICELOG("Abort AER request\n");
			nvmf_qpair_free_aer(fc_req->req.qpair);
		}
	}
}

void
nvmf_fc_request_abort_complete(void *arg1)
{
	struct spdk_nvmf_fc_request *fc_req =
		(struct spdk_nvmf_fc_request *)arg1;
	struct spdk_nvmf_fc_hwqp *hwqp = fc_req->hwqp;
	struct spdk_nvmf_fc_caller_ctx *ctx = NULL, *tmp = NULL;
	TAILQ_HEAD(, spdk_nvmf_fc_caller_ctx) abort_cbs;

	/* Make a copy of the cb list from fc_req */
	TAILQ_INIT(&abort_cbs);
	TAILQ_SWAP(&abort_cbs, &fc_req->abort_cbs, spdk_nvmf_fc_caller_ctx, link);

	SPDK_NOTICELOG("FC Request(%p) in state :%s aborted\n", fc_req,
		       fc_req_state_strs[fc_req->state]);

	_nvmf_fc_request_free(fc_req);

	/* Request abort completed. Notify all the callbacks */
	TAILQ_FOREACH_SAFE(ctx, &abort_cbs, link, tmp) {
		/* Notify */
		ctx->cb(hwqp, 0, ctx->cb_args);
		/* Remove */
		TAILQ_REMOVE(&abort_cbs, ctx, link);
		/* free */
		free(ctx);
	}
}

void
nvmf_fc_request_abort(struct spdk_nvmf_fc_request *fc_req, bool send_abts,
		      spdk_nvmf_fc_caller_cb cb, void *cb_args)
{
	struct spdk_nvmf_fc_caller_ctx *ctx = NULL;
	bool kill_req = false;

	/* Add the cb to list */
	if (cb) {
		ctx = calloc(1, sizeof(struct spdk_nvmf_fc_caller_ctx));
		if (!ctx) {
			SPDK_ERRLOG("ctx alloc failed.\n");
			return;
		}
		ctx->cb = cb;
		ctx->cb_args = cb_args;

		TAILQ_INSERT_TAIL(&fc_req->abort_cbs, ctx, link);
	}

	if (!fc_req->is_aborted) {
		/* Increment aborted command counter */
		fc_req->hwqp->counters.num_aborted++;
	}

	/* If port is dead, skip abort wqe */
	kill_req = nvmf_fc_is_port_dead(fc_req->hwqp);
	if (kill_req && nvmf_fc_req_in_xfer(fc_req)) {
		fc_req->is_aborted = true;
		goto complete;
	}

	/* Check if the request is already marked for deletion */
	if (fc_req->is_aborted) {
		return;
	}

	/* Mark request as aborted */
	fc_req->is_aborted = true;

	/* If xchg is allocated, then save if we need to send abts or not. */
	if (fc_req->xchg) {
		fc_req->xchg->send_abts = send_abts;
		fc_req->xchg->aborted	= true;
	}

	switch (fc_req->state) {
	case SPDK_NVMF_FC_REQ_BDEV_ABORTED:
		/* Aborted by backend */
		goto complete;

	case SPDK_NVMF_FC_REQ_READ_BDEV:
	case SPDK_NVMF_FC_REQ_WRITE_BDEV:
	case SPDK_NVMF_FC_REQ_NONE_BDEV:
		/* Notify bdev */
		spdk_thread_send_msg(fc_req->hwqp->thread,
				     nvmf_fc_req_bdev_abort, (void *)fc_req);
		break;

	case SPDK_NVMF_FC_REQ_READ_XFER:
	case SPDK_NVMF_FC_REQ_READ_RSP:
	case SPDK_NVMF_FC_REQ_WRITE_XFER:
	case SPDK_NVMF_FC_REQ_WRITE_RSP:
	case SPDK_NVMF_FC_REQ_NONE_RSP:
		/* Notify HBA to abort this exchange  */
		nvmf_fc_issue_abort(fc_req->hwqp, fc_req->xchg, NULL, NULL);
		break;

	case SPDK_NVMF_FC_REQ_PENDING:
		/* Remove from pending */
		nvmf_fc_request_remove_from_pending(fc_req);
		goto complete;
	case SPDK_NVMF_FC_REQ_FUSED_WAITING:
		TAILQ_REMOVE(&fc_req->fc_conn->fused_waiting_queue, fc_req, fused_link);
		goto complete;
	default:
		SPDK_ERRLOG("Request in invalid state.\n");
		goto complete;
	}

	return;
complete:
	nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_ABORTED);
	nvmf_fc_poller_api_func(fc_req->hwqp, SPDK_NVMF_FC_POLLER_API_REQ_ABORT_COMPLETE,
				(void *)fc_req);
}

static int
nvmf_fc_request_alloc_buffers(struct spdk_nvmf_fc_request *fc_req)
{
	uint32_t length = fc_req->req.length;
	struct spdk_nvmf_fc_poll_group *fgroup = fc_req->hwqp->fgroup;
	struct spdk_nvmf_transport_poll_group *group = &fgroup->group;
	struct spdk_nvmf_transport *transport = group->transport;

	if (spdk_nvmf_request_get_buffers(&fc_req->req, group, transport, length)) {
		return -ENOMEM;
	}

	return 0;
}

static int
nvmf_fc_request_execute(struct spdk_nvmf_fc_request *fc_req)
{
	/* Allocate an XCHG if we dont use send frame for this command. */
	if (!nvmf_fc_use_send_frame(fc_req)) {
		fc_req->xchg = nvmf_fc_get_xri(fc_req->hwqp);
		if (!fc_req->xchg) {
			fc_req->hwqp->counters.no_xchg++;
			return -EAGAIN;
		}
	}

	if (fc_req->req.length) {
		if (nvmf_fc_request_alloc_buffers(fc_req) < 0) {
			fc_req->hwqp->counters.buf_alloc_err++;
			if (fc_req->xchg) {
				nvmf_fc_put_xchg(fc_req->hwqp, fc_req->xchg);
				fc_req->xchg = NULL;
			}
			return -EAGAIN;
		}
		fc_req->req.data = fc_req->req.iov[0].iov_base;
	}

	if (fc_req->req.xfer == SPDK_NVME_DATA_HOST_TO_CONTROLLER) {
		SPDK_DEBUGLOG(nvmf_fc, "WRITE CMD.\n");

		nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_WRITE_XFER);

		if (nvmf_fc_recv_data(fc_req)) {
			/* Dropped return success to caller */
			fc_req->hwqp->counters.unexpected_err++;
			_nvmf_fc_request_free(fc_req);
		}
	} else {
		SPDK_DEBUGLOG(nvmf_fc, "READ/NONE CMD\n");

		if (fc_req->req.xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST) {
			nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_READ_BDEV);
		} else {
			nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_NONE_BDEV);
		}
		spdk_nvmf_request_exec(&fc_req->req);
	}

	return 0;
}

static void
nvmf_fc_set_vmid_priority(struct spdk_nvmf_fc_request *fc_req,
			  struct spdk_nvmf_fc_frame_hdr *fchdr)
{
	uint8_t df_ctl = fchdr->df_ctl;
	uint32_t f_ctl = fchdr->f_ctl;

	/* VMID */
	if (df_ctl & FCNVME_D_FCTL_DEVICE_HDR_16_MASK) {
		struct spdk_nvmf_fc_vm_header *vhdr;
		uint32_t vmhdr_offset = 0;

		if (df_ctl & FCNVME_D_FCTL_ESP_HDR_MASK) {
			vmhdr_offset += FCNVME_D_FCTL_ESP_HDR_SIZE;
		}

		if (df_ctl & FCNVME_D_FCTL_NETWORK_HDR_MASK) {
			vmhdr_offset += FCNVME_D_FCTL_NETWORK_HDR_SIZE;
		}

		vhdr = (struct spdk_nvmf_fc_vm_header *)((char *)fchdr +
				sizeof(struct spdk_nvmf_fc_frame_hdr) + vmhdr_offset);
		fc_req->app_id = from_be32(&vhdr->src_vmid);
	}

	/* Priority */
	if ((from_be32(&f_ctl) >> 8) & FCNVME_F_CTL_PRIORITY_ENABLE) {
		fc_req->csctl = fchdr->cs_ctl;
	}
}

static int
nvmf_fc_hwqp_handle_request(struct spdk_nvmf_fc_hwqp *hwqp, struct spdk_nvmf_fc_frame_hdr *frame,
			    struct spdk_nvmf_fc_buffer_desc *buffer, uint32_t plen)
{
	uint16_t cmnd_len;
	uint64_t rqst_conn_id;
	struct spdk_nvmf_fc_request *fc_req = NULL;
	struct spdk_nvmf_fc_cmnd_iu *cmd_iu = NULL;
	struct spdk_nvmf_fc_conn *fc_conn = NULL;
	enum spdk_nvme_data_transfer xfer;
	uint32_t s_id, d_id;

	s_id = (uint32_t)frame->s_id;
	d_id = (uint32_t)frame->d_id;
	s_id = from_be32(&s_id) >> 8;
	d_id = from_be32(&d_id) >> 8;

	cmd_iu = buffer->virt;
	cmnd_len = cmd_iu->cmnd_iu_len;
	cmnd_len = from_be16(&cmnd_len);

	/* check for a valid cmnd_iu format */
	if ((cmd_iu->fc_id != FCNVME_CMND_IU_FC_ID) ||
	    (cmd_iu->scsi_id != FCNVME_CMND_IU_SCSI_ID) ||
	    (cmnd_len != sizeof(struct spdk_nvmf_fc_cmnd_iu) / 4)) {
		SPDK_ERRLOG("IU CMD error\n");
		hwqp->counters.nvme_cmd_iu_err++;
		return -ENXIO;
	}

	xfer = spdk_nvme_opc_get_data_transfer(cmd_iu->flags);
	if (xfer == SPDK_NVME_DATA_BIDIRECTIONAL) {
		SPDK_ERRLOG("IU CMD xfer error\n");
		hwqp->counters.nvme_cmd_xfer_err++;
		return -EPERM;
	}

	rqst_conn_id = from_be64(&cmd_iu->conn_id);

	if (rte_hash_lookup_data(hwqp->connection_list_hash,
				 (void *)&rqst_conn_id, (void **)&fc_conn) < 0) {
		SPDK_ERRLOG("IU CMD conn(%ld) invalid\n", rqst_conn_id);
		hwqp->counters.invalid_conn_err++;
		return -ENODEV;
	}

	/* Validate s_id and d_id */
	if (s_id != fc_conn->s_id) {
		hwqp->counters.rport_invalid++;
		SPDK_ERRLOG("Frame s_id invalid for connection %ld\n", rqst_conn_id);
		return -ENODEV;
	}

	if (d_id != fc_conn->d_id) {
		hwqp->counters.nport_invalid++;
		SPDK_ERRLOG("Frame d_id invalid for connection %ld\n", rqst_conn_id);
		return -ENODEV;
	}

	/* If association/connection is being deleted - return */
	if (fc_conn->fc_assoc->assoc_state != SPDK_NVMF_FC_OBJECT_CREATED) {
		SPDK_ERRLOG("Association %ld state = %d not valid\n",
			    fc_conn->fc_assoc->assoc_id, fc_conn->fc_assoc->assoc_state);
		return -EACCES;
	}

	if (fc_conn->conn_state != SPDK_NVMF_FC_OBJECT_CREATED) {
		SPDK_ERRLOG("Connection %ld state = %d not valid\n",
			    rqst_conn_id, fc_conn->conn_state);
		return -EACCES;
	}

	if (fc_conn->qpair.state != SPDK_NVMF_QPAIR_ACTIVE) {
		SPDK_ERRLOG("Connection %ld qpair state = %d not valid\n",
			    rqst_conn_id, fc_conn->qpair.state);
		return -EACCES;
	}

	/* Make sure xfer len is according to mdts */
	if (from_be32(&cmd_iu->data_len) >
	    hwqp->fgroup->group.transport->opts.max_io_size) {
		SPDK_ERRLOG("IO length requested is greater than MDTS\n");
		return -EINVAL;
	}

	/* allocate a request buffer */
	fc_req = nvmf_fc_conn_alloc_fc_request(fc_conn);
	if (fc_req == NULL) {
		return -ENOMEM;
	}

	fc_req->req.length = from_be32(&cmd_iu->data_len);
	fc_req->req.qpair = &fc_conn->qpair;
	memcpy(&fc_req->cmd, &cmd_iu->cmd, sizeof(union nvmf_h2c_msg));
	fc_req->req.cmd = (union nvmf_h2c_msg *)&fc_req->cmd;
	fc_req->req.rsp = (union nvmf_c2h_msg *)&fc_req->ersp.rsp;
	fc_req->oxid = frame->ox_id;
	fc_req->oxid = from_be16(&fc_req->oxid);
	fc_req->rpi = fc_conn->rpi;
	fc_req->poller_lcore = hwqp->lcore_id;
	fc_req->poller_thread = hwqp->thread;
	fc_req->hwqp = hwqp;
	fc_req->fc_conn = fc_conn;
	fc_req->req.xfer = xfer;
	fc_req->s_id = s_id;
	fc_req->d_id = d_id;
	fc_req->csn  = from_be32(&cmd_iu->cmnd_seq_num);
	nvmf_fc_set_vmid_priority(fc_req, frame);

	nvmf_fc_record_req_trace_point(fc_req, SPDK_NVMF_FC_REQ_INIT);

	if (!STAILQ_EMPTY(&hwqp->fgroup->group.pending_buf_queue) || nvmf_fc_request_execute(fc_req)) {
		STAILQ_INSERT_TAIL(&hwqp->fgroup->group.pending_buf_queue, &fc_req->req, buf_link);
		nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_PENDING);
	}

	return 0;
}

/*
 * These functions are called from the FC LLD
 */

void
_nvmf_fc_request_free(struct spdk_nvmf_fc_request *fc_req)
{
	struct spdk_nvmf_fc_hwqp *hwqp;
	struct spdk_nvmf_transport_poll_group *group;

	if (!fc_req) {
		return;
	}
	hwqp = fc_req->hwqp;

	if (fc_req->xchg) {
		nvmf_fc_put_xchg(hwqp, fc_req->xchg);
		fc_req->xchg = NULL;
	}

	/* Release IO buffers */
	if (fc_req->req.data_from_pool) {
		group = &hwqp->fgroup->group;
		spdk_nvmf_request_free_buffers(&fc_req->req, group,
					       group->transport);
	}
	fc_req->req.data = NULL;
	fc_req->req.iovcnt  = 0;

	/* Free Fc request */
	nvmf_fc_conn_free_fc_request(fc_req->fc_conn, fc_req);
}

void
nvmf_fc_request_set_state(struct spdk_nvmf_fc_request *fc_req,
			  enum spdk_nvmf_fc_request_state state)
{
	assert(fc_req->magic != 0xDEADBEEF);

	SPDK_DEBUGLOG(nvmf_fc,
		      "FC Request(%p):\n\tState Old:%s New:%s\n", fc_req,
		      nvmf_fc_request_get_state_str(fc_req->state),
		      nvmf_fc_request_get_state_str(state));
	nvmf_fc_record_req_trace_point(fc_req, state);
	fc_req->state = state;
}

char *
nvmf_fc_request_get_state_str(int state)
{
	static char *unk_str = "unknown";

	return (state >= 0 && state < (int)(sizeof(fc_req_state_strs) / sizeof(char *)) ?
		fc_req_state_strs[state] : unk_str);
}

int
nvmf_fc_hwqp_process_frame(struct spdk_nvmf_fc_hwqp *hwqp,
			   uint32_t buff_idx,
			   struct spdk_nvmf_fc_frame_hdr *frame,
			   struct spdk_nvmf_fc_buffer_desc *buffer,
			   uint32_t plen)
{
	int rc = 0;
	uint32_t s_id, d_id;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport = NULL;

	s_id = (uint32_t)frame->s_id;
	d_id = (uint32_t)frame->d_id;
	s_id = from_be32(&s_id) >> 8;
	d_id = from_be32(&d_id) >> 8;

	SPDK_DEBUGLOG(nvmf_fc,
		      "Process NVME frame s_id:0x%x d_id:0x%x oxid:0x%x rxid:0x%x.\n",
		      s_id, d_id,
		      ((frame->ox_id << 8) & 0xff00) | ((frame->ox_id >> 8) & 0xff),
		      ((frame->rx_id << 8) & 0xff00) | ((frame->rx_id >> 8) & 0xff));

	if ((frame->r_ctl == FCNVME_R_CTL_LS_REQUEST) &&
	    (frame->type == FCNVME_TYPE_NVMF_DATA)) {
		struct spdk_nvmf_fc_rq_buf_ls_request *req_buf = buffer->virt;
		struct spdk_nvmf_fc_ls_rqst *ls_rqst;

		SPDK_DEBUGLOG(nvmf_fc, "Process LS NVME frame\n");

		rc = nvmf_fc_hwqp_find_nport_and_rport(hwqp, d_id, &nport, s_id, &rport);
		if (rc) {
			if (nport == NULL) {
				SPDK_ERRLOG("Nport not found. Dropping\n");
				/* increment invalid nport counter */
				hwqp->counters.nport_invalid++;
			} else if (rport == NULL) {
				SPDK_ERRLOG("Rport not found. Dropping\n");
				/* increment invalid rport counter */
				hwqp->counters.rport_invalid++;
			}
			return rc;
		}

		if (nport->nport_state != SPDK_NVMF_FC_OBJECT_CREATED ||
		    rport->rport_state != SPDK_NVMF_FC_OBJECT_CREATED) {
			SPDK_ERRLOG("%s state not created. Dropping\n",
				    nport->nport_state != SPDK_NVMF_FC_OBJECT_CREATED ?
				    "Nport" : "Rport");
			return -EACCES;
		}

		/* Use the RQ buffer for holding LS request. */
		ls_rqst = (struct spdk_nvmf_fc_ls_rqst *)&req_buf->ls_rqst;

		/* Fill in the LS request structure */
		ls_rqst->rqstbuf.virt = (void *)&req_buf->rqst;
		ls_rqst->rqstbuf.phys = buffer->phys +
					offsetof(struct spdk_nvmf_fc_rq_buf_ls_request, rqst);
		ls_rqst->rqstbuf.buf_index = buff_idx;
		ls_rqst->rqst_len = plen;

		ls_rqst->rspbuf.virt = (void *)&req_buf->resp;
		ls_rqst->rspbuf.phys = buffer->phys +
				       offsetof(struct spdk_nvmf_fc_rq_buf_ls_request, resp);
		ls_rqst->rsp_len = FCNVME_MAX_LS_RSP_SIZE;

		ls_rqst->private_data = (void *)hwqp;
		ls_rqst->rpi = rport->rpi;
		ls_rqst->oxid = (uint16_t)frame->ox_id;
		ls_rqst->oxid = from_be16(&ls_rqst->oxid);
		ls_rqst->s_id = s_id;
		ls_rqst->d_id = d_id;
		ls_rqst->nport = nport;
		ls_rqst->rport = rport;
		ls_rqst->nvmf_tgt = g_nvmf_ftransport->transport.tgt;

		if (TAILQ_EMPTY(&hwqp->ls_pending_queue)) {
			ls_rqst->xchg = nvmf_fc_get_xri(hwqp);
		} else {
			ls_rqst->xchg = NULL;
		}

		if (ls_rqst->xchg) {
			/* Handover the request to LS module */
			nvmf_fc_handle_ls_rqst(ls_rqst);
		} else {
			/* No XCHG available. Add to pending list. */
			hwqp->counters.no_xchg++;
			TAILQ_INSERT_TAIL(&hwqp->ls_pending_queue, ls_rqst, ls_pending_link);
		}
	} else if ((frame->r_ctl == FCNVME_R_CTL_CMD_REQ) &&
		   (frame->type == FCNVME_TYPE_FC_EXCHANGE)) {

		SPDK_DEBUGLOG(nvmf_fc, "Process IO NVME frame\n");
		rc = nvmf_fc_hwqp_handle_request(hwqp, frame, buffer, plen);
		if (!rc) {
			nvmf_fc_rqpair_buffer_release(hwqp, buff_idx);
		}
	} else {

		SPDK_ERRLOG("Unknown frame received. Dropping\n");
		hwqp->counters.unknown_frame++;
		rc = -EINVAL;
	}

	return rc;
}

void
nvmf_fc_hwqp_process_pending_reqs(struct spdk_nvmf_fc_hwqp *hwqp)
{
	struct spdk_nvmf_request *req = NULL, *tmp;
	struct spdk_nvmf_fc_request *fc_req;
	int budget = 64;

	if (!hwqp->fgroup) {
		/* LS queue is tied to acceptor_poll group and LS pending requests
		 * are stagged and processed using hwqp->ls_pending_queue.
		 */
		return;
	}

	STAILQ_FOREACH_SAFE(req, &hwqp->fgroup->group.pending_buf_queue, buf_link, tmp) {
		fc_req = SPDK_CONTAINEROF(req, struct spdk_nvmf_fc_request, req);
		if (!nvmf_fc_request_execute(fc_req)) {
			/* Successfully posted, Delete from pending. */
			nvmf_fc_request_remove_from_pending(fc_req);
		}

		if (budget) {
			budget--;
		} else {
			return;
		}
	}
}

void
nvmf_fc_hwqp_process_pending_ls_rqsts(struct spdk_nvmf_fc_hwqp *hwqp)
{
	struct spdk_nvmf_fc_ls_rqst *ls_rqst = NULL, *tmp;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport = NULL;

	TAILQ_FOREACH_SAFE(ls_rqst, &hwqp->ls_pending_queue, ls_pending_link, tmp) {
		/* lookup nport and rport again - make sure they are still valid */
		int rc = nvmf_fc_hwqp_find_nport_and_rport(hwqp, ls_rqst->d_id, &nport, ls_rqst->s_id, &rport);
		if (rc) {
			if (nport == NULL) {
				SPDK_ERRLOG("Nport not found. Dropping\n");
				/* increment invalid nport counter */
				hwqp->counters.nport_invalid++;
			} else if (rport == NULL) {
				SPDK_ERRLOG("Rport not found. Dropping\n");
				/* increment invalid rport counter */
				hwqp->counters.rport_invalid++;
			}
			nvmf_fc_release_ls_rqst(hwqp, ls_rqst);
			continue;
		}
		if (nport->nport_state != SPDK_NVMF_FC_OBJECT_CREATED ||
		    rport->rport_state != SPDK_NVMF_FC_OBJECT_CREATED) {
			SPDK_ERRLOG("%s state not created. Dropping\n",
				    nport->nport_state != SPDK_NVMF_FC_OBJECT_CREATED ?
				    "Nport" : "Rport");
			nvmf_fc_release_ls_rqst(hwqp, ls_rqst);
			continue;
		}

		ls_rqst->xchg = nvmf_fc_get_xri(hwqp);
		if (ls_rqst->xchg) {
			/* Got an XCHG */
			TAILQ_REMOVE(&hwqp->ls_pending_queue, ls_rqst, ls_pending_link);
			/* Handover the request to LS module */
			nvmf_fc_handle_ls_rqst(ls_rqst);
		} else {
			/* No more XCHGs. Stop processing. */
			hwqp->counters.no_xchg++;
			return;
		}
	}
}

int
nvmf_fc_handle_rsp(struct spdk_nvmf_fc_request *fc_req)
{
	int rc = 0;
	struct spdk_nvmf_request *req = &fc_req->req;
	struct spdk_nvmf_qpair *qpair = req->qpair;
	struct spdk_nvmf_fc_conn *fc_conn = nvmf_fc_get_conn(qpair);
	struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
	uint16_t ersp_len = 0;

	/* set sq head value in resp */
	rsp->sqhd = nvmf_fc_advance_conn_sqhead(qpair);

	/* Increment connection responses */
	fc_conn->rsp_count++;

	if (nvmf_fc_send_ersp_required(fc_req, fc_conn->rsp_count,
				       fc_req->transferred_len)) {
		/* Fill ERSP Len */
		to_be16(&ersp_len, (sizeof(struct spdk_nvmf_fc_ersp_iu) /
				    sizeof(uint32_t)));
		fc_req->ersp.ersp_len = ersp_len;

		/* Fill RSN */
		to_be32(&fc_req->ersp.response_seq_no, fc_conn->rsn);
		fc_conn->rsn++;

		/* Fill transfer length */
		to_be32(&fc_req->ersp.transferred_data_len, fc_req->transferred_len);

		SPDK_DEBUGLOG(nvmf_fc, "Posting ERSP.\n");
		rc = nvmf_fc_xmt_rsp(fc_req, (uint8_t *)&fc_req->ersp,
				     sizeof(struct spdk_nvmf_fc_ersp_iu));
	} else {
		SPDK_DEBUGLOG(nvmf_fc, "Posting RSP.\n");
		rc = nvmf_fc_xmt_rsp(fc_req, NULL, 0);
	}

	return rc;
}

bool
nvmf_fc_send_ersp_required(struct spdk_nvmf_fc_request *fc_req,
			   uint32_t rsp_cnt, uint32_t xfer_len)
{
	struct spdk_nvmf_request *req = &fc_req->req;
	struct spdk_nvmf_qpair *qpair = req->qpair;
	struct spdk_nvmf_fc_conn *fc_conn = nvmf_fc_get_conn(qpair);
	struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
	struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
	uint16_t status = *((uint16_t *)&rsp->status);

	/*
	 * Check if we need to send ERSP
	 * 1) For every N responses where N == ersp_ratio
	 * 2) Fabric commands.
	 * 3) Completion status failed or Completion dw0 or dw1 valid.
	 * 4) SQ == 90% full.
	 * 5) Transfer length not equal to CMD IU length
	 */

	if (!(rsp_cnt % fc_conn->esrp_ratio) ||
	    (cmd->opc == SPDK_NVME_OPC_FABRIC) ||
	    (status & 0xFFFE) || rsp->cdw0 || rsp->cdw1 ||
	    (req->length != xfer_len)) {
		return true;
	}
	return false;
}

static int
nvmf_fc_request_complete(struct spdk_nvmf_request *req)
{
	int rc = 0;
	struct spdk_nvmf_fc_request *fc_req = nvmf_fc_get_fc_req(req);
	struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;

	if (fc_req->is_aborted) {
		/* Defer this to make sure we dont call io cleanup in same context. */
		nvmf_fc_poller_api_func(fc_req->hwqp, SPDK_NVMF_FC_POLLER_API_REQ_ABORT_COMPLETE,
					(void *)fc_req);
	} else if (rsp->status.sc == SPDK_NVME_SC_SUCCESS &&
		   req->xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST) {

		nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_READ_XFER);

		rc = nvmf_fc_send_data(fc_req);
	} else {
		if (req->xfer == SPDK_NVME_DATA_HOST_TO_CONTROLLER) {
			nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_WRITE_RSP);
		} else if (req->xfer == SPDK_NVME_DATA_CONTROLLER_TO_HOST) {
			nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_READ_RSP);
		} else {
			nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_NONE_RSP);
		}

		rc = nvmf_fc_handle_rsp(fc_req);
	}

	if (rc) {
		SPDK_ERRLOG("Error in request complete.\n");
		_nvmf_fc_request_free(fc_req);
	}
	return 0;
}

struct spdk_nvmf_tgt *
nvmf_fc_get_tgt(void)
{
	if (g_nvmf_ftransport) {
		return g_nvmf_ftransport->transport.tgt;
	}
	return NULL;
}

/*
 * FC Transport Public API begins here
 */

#define SPDK_NVMF_FC_DEFAULT_MAX_QUEUE_DEPTH 128
#define SPDK_NVMF_FC_DEFAULT_AQ_DEPTH 32
#define SPDK_NVMF_FC_DEFAULT_MAX_QPAIRS_PER_CTRLR 5
#define SPDK_NVMF_FC_DEFAULT_IN_CAPSULE_DATA_SIZE 0
#define SPDK_NVMF_FC_DEFAULT_MAX_IO_SIZE 65536
#define SPDK_NVMF_FC_DEFAULT_IO_UNIT_SIZE 4096
#define SPDK_NVMF_FC_DEFAULT_NUM_SHARED_BUFFERS 8192
#define SPDK_NVMF_FC_DEFAULT_MAX_SGE (SPDK_NVMF_FC_DEFAULT_MAX_IO_SIZE /	\
				      SPDK_NVMF_FC_DEFAULT_IO_UNIT_SIZE)

static void
nvmf_fc_opts_init(struct spdk_nvmf_transport_opts *opts)
{
	opts->max_queue_depth =      SPDK_NVMF_FC_DEFAULT_MAX_QUEUE_DEPTH;
	opts->max_qpairs_per_ctrlr = SPDK_NVMF_FC_DEFAULT_MAX_QPAIRS_PER_CTRLR;
	opts->in_capsule_data_size = SPDK_NVMF_FC_DEFAULT_IN_CAPSULE_DATA_SIZE;
	opts->max_io_size =          SPDK_NVMF_FC_DEFAULT_MAX_IO_SIZE;
	opts->io_unit_size =         SPDK_NVMF_FC_DEFAULT_IO_UNIT_SIZE;
	opts->max_aq_depth =         SPDK_NVMF_FC_DEFAULT_AQ_DEPTH;
	opts->num_shared_buffers =   SPDK_NVMF_FC_DEFAULT_NUM_SHARED_BUFFERS;
}

static int
nvmf_fc_accept(void *ctx);

static struct spdk_nvmf_transport *
nvmf_fc_create(struct spdk_nvmf_transport_opts *opts)
{
	uint32_t sge_count;

	SPDK_INFOLOG(nvmf_fc, "*** FC Transport Init ***\n"
		     "  Transport opts:  max_ioq_depth=%d, max_io_size=%d,\n"
		     "  max_io_qpairs_per_ctrlr=%d, io_unit_size=%d,\n"
		     "  max_aq_depth=%d\n",
		     opts->max_queue_depth,
		     opts->max_io_size,
		     opts->max_qpairs_per_ctrlr - 1,
		     opts->io_unit_size,
		     opts->max_aq_depth);

	if (g_nvmf_ftransport) {
		SPDK_ERRLOG("Duplicate NVMF-FC transport create request!\n");
		return NULL;
	}

	if (spdk_env_get_last_core() < 1) {
		SPDK_ERRLOG("Not enough cores/threads (%d) to run NVMF-FC transport!\n",
			    spdk_env_get_last_core() + 1);
		return NULL;
	}

	sge_count = opts->max_io_size / opts->io_unit_size;
	if (sge_count > SPDK_NVMF_FC_DEFAULT_MAX_SGE) {
		SPDK_ERRLOG("Unsupported IO Unit size specified, %d bytes\n", opts->io_unit_size);
		return NULL;
	}

	g_nvmf_fc_main_thread = spdk_get_thread();
	g_nvmf_fgroup_count = 0;
	g_nvmf_ftransport = calloc(1, sizeof(*g_nvmf_ftransport));

	if (!g_nvmf_ftransport) {
		SPDK_ERRLOG("Failed to allocate NVMF-FC transport\n");
		return NULL;
	}

	if (pthread_mutex_init(&g_nvmf_ftransport->lock, NULL)) {
		SPDK_ERRLOG("pthread_mutex_init() failed\n");
		free(g_nvmf_ftransport);
		g_nvmf_ftransport = NULL;
		return NULL;
	}

	g_nvmf_ftransport->accept_poller = SPDK_POLLER_REGISTER(nvmf_fc_accept,
					   &g_nvmf_ftransport->transport, opts->acceptor_poll_rate);
	if (!g_nvmf_ftransport->accept_poller) {
		free(g_nvmf_ftransport);
		g_nvmf_ftransport = NULL;
		return NULL;
	}

	/* initialize the low level FC driver */
	nvmf_fc_lld_init();

	return &g_nvmf_ftransport->transport;
}

static void
nvmf_fc_destroy_done_cb(void *cb_arg)
{
	free(g_nvmf_ftransport);
	if (g_transport_destroy_done_cb) {
		g_transport_destroy_done_cb(cb_arg);
		g_transport_destroy_done_cb = NULL;
	}
}

static int
nvmf_fc_destroy(struct spdk_nvmf_transport *transport,
		spdk_nvmf_transport_destroy_done_cb cb_fn, void *cb_arg)
{
	if (transport) {
		struct spdk_nvmf_fc_poll_group *fgroup, *pg_tmp;

		/* clean up any FC poll groups still around */
		TAILQ_FOREACH_SAFE(fgroup, &g_nvmf_fgroups, link, pg_tmp) {
			TAILQ_REMOVE(&g_nvmf_fgroups, fgroup, link);
			free(fgroup);
		}

		spdk_poller_unregister(&g_nvmf_ftransport->accept_poller);
		g_nvmf_fgroup_count = 0;
		g_transport_destroy_done_cb = cb_fn;

		/* low level FC driver clean up */
		nvmf_fc_lld_fini(nvmf_fc_destroy_done_cb, cb_arg);
	}

	return 0;
}

static int
nvmf_fc_listen(struct spdk_nvmf_transport *transport, const struct spdk_nvme_transport_id *trid,
	       struct spdk_nvmf_listen_opts *listen_opts)
{
	return 0;
}

static void
nvmf_fc_stop_listen(struct spdk_nvmf_transport *transport,
		    const struct spdk_nvme_transport_id *_trid)
{
}

static int
nvmf_fc_accept(void *ctx)
{
	struct spdk_nvmf_fc_port *fc_port = NULL;
	uint32_t count = 0;
	static bool start_lld = false;

	if (spdk_unlikely(!start_lld)) {
		start_lld  = true;
		nvmf_fc_lld_start();
	}

	/* poll the LS queue on each port */
	TAILQ_FOREACH(fc_port, &g_spdk_nvmf_fc_port_list, link) {
		if (fc_port->hw_port_status == SPDK_FC_PORT_ONLINE) {
			count += nvmf_fc_process_queue(&fc_port->ls_queue);
		}
	}

	return count > 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE;
}

static void
nvmf_fc_discover(struct spdk_nvmf_transport *transport,
		 struct spdk_nvme_transport_id *trid,
		 struct spdk_nvmf_discovery_log_page_entry *entry)
{
	entry->trtype = (enum spdk_nvme_transport_type) SPDK_NVMF_TRTYPE_FC;
	entry->adrfam = trid->adrfam;
	entry->treq.secure_channel = SPDK_NVMF_TREQ_SECURE_CHANNEL_NOT_SPECIFIED;

	spdk_strcpy_pad(entry->trsvcid, trid->trsvcid, sizeof(entry->trsvcid), ' ');
	spdk_strcpy_pad(entry->traddr, trid->traddr, sizeof(entry->traddr), ' ');
}

static struct spdk_nvmf_transport_poll_group *
nvmf_fc_poll_group_create(struct spdk_nvmf_transport *transport,
			  struct spdk_nvmf_poll_group *group)
{
	struct spdk_nvmf_fc_poll_group *fgroup;
	struct spdk_nvmf_fc_transport *ftransport =
		SPDK_CONTAINEROF(transport, struct spdk_nvmf_fc_transport, transport);

	fgroup = calloc(1, sizeof(struct spdk_nvmf_fc_poll_group));
	if (!fgroup) {
		SPDK_ERRLOG("Unable to alloc FC poll group\n");
		return NULL;
	}

	TAILQ_INIT(&fgroup->hwqp_list);

	pthread_mutex_lock(&ftransport->lock);
	TAILQ_INSERT_TAIL(&g_nvmf_fgroups, fgroup, link);
	g_nvmf_fgroup_count++;
	pthread_mutex_unlock(&ftransport->lock);

	return &fgroup->group;
}

static void
nvmf_fc_poll_group_destroy(struct spdk_nvmf_transport_poll_group *group)
{
	struct spdk_nvmf_fc_poll_group *fgroup;
	struct spdk_nvmf_fc_transport *ftransport =
		SPDK_CONTAINEROF(group->transport, struct spdk_nvmf_fc_transport, transport);

	fgroup = SPDK_CONTAINEROF(group, struct spdk_nvmf_fc_poll_group, group);
	pthread_mutex_lock(&ftransport->lock);
	TAILQ_REMOVE(&g_nvmf_fgroups, fgroup, link);
	g_nvmf_fgroup_count--;
	pthread_mutex_unlock(&ftransport->lock);

	free(fgroup);
}

static int
nvmf_fc_poll_group_add(struct spdk_nvmf_transport_poll_group *group,
		       struct spdk_nvmf_qpair *qpair)
{
	struct spdk_nvmf_fc_poll_group *fgroup;
	struct spdk_nvmf_fc_conn *fc_conn;
	struct spdk_nvmf_fc_hwqp *hwqp = NULL;
	struct spdk_nvmf_fc_ls_add_conn_api_data *api_data = NULL;
	bool hwqp_found = false;

	fgroup = SPDK_CONTAINEROF(group, struct spdk_nvmf_fc_poll_group, group);
	fc_conn  = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);

	TAILQ_FOREACH(hwqp, &fgroup->hwqp_list, link) {
		if (fc_conn->fc_assoc->tgtport->fc_port == hwqp->fc_port) {
			hwqp_found = true;
			break;
		}
	}

	if (!hwqp_found) {
		SPDK_ERRLOG("No valid hwqp found for new QP.\n");
		goto err;
	}

	if (!nvmf_fc_assign_conn_to_hwqp(hwqp,
					 &fc_conn->conn_id,
					 fc_conn->max_queue_depth)) {
		SPDK_ERRLOG("Failed to get a connection id for new QP.\n");
		goto err;
	}

	fc_conn->hwqp = hwqp;

	/* If this is for ADMIN connection, then update assoc ID. */
	if (fc_conn->qpair.qid == 0) {
		fc_conn->fc_assoc->assoc_id = fc_conn->conn_id;
	}

	api_data = &fc_conn->create_opd->u.add_conn;
	nvmf_fc_poller_api_func(hwqp, SPDK_NVMF_FC_POLLER_API_ADD_CONNECTION, &api_data->args);
	return 0;
err:
	return -1;
}

static int
nvmf_fc_poll_group_poll(struct spdk_nvmf_transport_poll_group *group)
{
	uint32_t count = 0;
	struct spdk_nvmf_fc_poll_group *fgroup;
	struct spdk_nvmf_fc_hwqp *hwqp;

	fgroup = SPDK_CONTAINEROF(group, struct spdk_nvmf_fc_poll_group, group);

	TAILQ_FOREACH(hwqp, &fgroup->hwqp_list, link) {
		if (hwqp->state == SPDK_FC_HWQP_ONLINE) {
			count += nvmf_fc_process_queue(hwqp);
		}
	}

	return (int) count;
}

static int
nvmf_fc_request_free(struct spdk_nvmf_request *req)
{
	struct spdk_nvmf_fc_request *fc_req = nvmf_fc_get_fc_req(req);

	if (!fc_req->is_aborted) {
		nvmf_fc_request_set_state(fc_req, SPDK_NVMF_FC_REQ_BDEV_ABORTED);
		nvmf_fc_request_abort(fc_req, true, NULL, NULL);
	} else {
		nvmf_fc_request_abort_complete(fc_req);
	}

	return 0;
}

static void
nvmf_fc_connection_delete_done_cb(void *arg)
{
	struct spdk_nvmf_fc_qpair_remove_ctx *fc_ctx = arg;

	if (fc_ctx->cb_fn) {
		spdk_thread_send_msg(fc_ctx->qpair_thread, fc_ctx->cb_fn, fc_ctx->cb_ctx);
	}
	free(fc_ctx);
}

static void
_nvmf_fc_close_qpair(void *arg)
{
	struct spdk_nvmf_fc_qpair_remove_ctx *fc_ctx = arg;
	struct spdk_nvmf_qpair *qpair = fc_ctx->qpair;
	struct spdk_nvmf_fc_conn *fc_conn;
	int rc;

	fc_conn = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);
	if (fc_conn->conn_id == NVMF_FC_INVALID_CONN_ID) {
		struct spdk_nvmf_fc_ls_add_conn_api_data *api_data = NULL;

		if (fc_conn->create_opd) {
			api_data = &fc_conn->create_opd->u.add_conn;

			nvmf_fc_ls_add_conn_failure(api_data->assoc, api_data->ls_rqst,
						    api_data->args.fc_conn, api_data->aq_conn);
		}
	} else if (fc_conn->conn_state == SPDK_NVMF_FC_OBJECT_CREATED) {
		rc = nvmf_fc_delete_connection(fc_conn, false, true,
					       nvmf_fc_connection_delete_done_cb, fc_ctx);
		if (!rc) {
			/* Wait for transport to complete its work. */
			return;
		}

		SPDK_ERRLOG("%s: Delete FC connection failed.\n", __func__);
	}

	nvmf_fc_connection_delete_done_cb(fc_ctx);
}

static void
nvmf_fc_close_qpair(struct spdk_nvmf_qpair *qpair,
		    spdk_nvmf_transport_qpair_fini_cb cb_fn, void *cb_arg)
{
	struct spdk_nvmf_fc_qpair_remove_ctx *fc_ctx;

	fc_ctx = calloc(1, sizeof(struct spdk_nvmf_fc_qpair_remove_ctx));
	if (!fc_ctx) {
		SPDK_ERRLOG("Unable to allocate close_qpair ctx.");
		if (cb_fn) {
			cb_fn(cb_arg);
		}
		return;
	}
	fc_ctx->qpair = qpair;
	fc_ctx->cb_fn = cb_fn;
	fc_ctx->cb_ctx = cb_arg;
	fc_ctx->qpair_thread = spdk_get_thread();

	spdk_thread_send_msg(nvmf_fc_get_main_thread(), _nvmf_fc_close_qpair, fc_ctx);
}

static int
nvmf_fc_qpair_get_peer_trid(struct spdk_nvmf_qpair *qpair,
			    struct spdk_nvme_transport_id *trid)
{
	struct spdk_nvmf_fc_conn *fc_conn;

	fc_conn = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);
	memcpy(trid, &fc_conn->trid, sizeof(struct spdk_nvme_transport_id));
	return 0;
}

static int
nvmf_fc_qpair_get_local_trid(struct spdk_nvmf_qpair *qpair,
			     struct spdk_nvme_transport_id *trid)
{
	struct spdk_nvmf_fc_conn *fc_conn;

	fc_conn = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);
	memcpy(trid, &fc_conn->trid, sizeof(struct spdk_nvme_transport_id));
	return 0;
}

static int
nvmf_fc_qpair_get_listen_trid(struct spdk_nvmf_qpair *qpair,
			      struct spdk_nvme_transport_id *trid)
{
	struct spdk_nvmf_fc_conn *fc_conn;

	fc_conn = SPDK_CONTAINEROF(qpair, struct spdk_nvmf_fc_conn, qpair);
	memcpy(trid, &fc_conn->trid, sizeof(struct spdk_nvme_transport_id));
	return 0;
}

static void
nvmf_fc_qpair_abort_request(struct spdk_nvmf_qpair *qpair,
			    struct spdk_nvmf_request *req)
{
	spdk_nvmf_request_complete(req);
}

const struct spdk_nvmf_transport_ops spdk_nvmf_transport_fc = {
	.name = "FC",
	.type = (enum spdk_nvme_transport_type) SPDK_NVMF_TRTYPE_FC,
	.opts_init = nvmf_fc_opts_init,
	.create = nvmf_fc_create,
	.destroy = nvmf_fc_destroy,

	.listen = nvmf_fc_listen,
	.stop_listen = nvmf_fc_stop_listen,

	.listener_discover = nvmf_fc_discover,

	.poll_group_create = nvmf_fc_poll_group_create,
	.poll_group_destroy = nvmf_fc_poll_group_destroy,
	.poll_group_add = nvmf_fc_poll_group_add,
	.poll_group_poll = nvmf_fc_poll_group_poll,

	.req_complete = nvmf_fc_request_complete,
	.req_free = nvmf_fc_request_free,
	.qpair_fini = nvmf_fc_close_qpair,
	.qpair_get_peer_trid = nvmf_fc_qpair_get_peer_trid,
	.qpair_get_local_trid = nvmf_fc_qpair_get_local_trid,
	.qpair_get_listen_trid = nvmf_fc_qpair_get_listen_trid,
	.qpair_abort_request = nvmf_fc_qpair_abort_request,
};

/* Initializes the data for the creation of a FC-Port object in the SPDK
 * library. The spdk_nvmf_fc_port is a well defined structure that is part of
 * the API to the library. The contents added to this well defined structure
 * is private to each vendors implementation.
 */
static int
nvmf_fc_adm_hw_port_data_init(struct spdk_nvmf_fc_port *fc_port,
			      struct spdk_nvmf_fc_hw_port_init_args *args)
{
	int rc = 0;
	/* Used a high number for the LS HWQP so that it does not clash with the
	 * IO HWQP's and immediately shows a LS queue during tracing.
	 */
	uint32_t i;

	fc_port->port_hdl       = args->port_handle;
	fc_port->lld_fc_port	= args->lld_fc_port;
	fc_port->hw_port_status = SPDK_FC_PORT_OFFLINE;
	fc_port->fcp_rq_id      = args->fcp_rq_id;
	fc_port->num_io_queues  = args->io_queue_cnt;

	/*
	 * Set port context from init args. Used for FCP port stats.
	 */
	fc_port->port_ctx = args->port_ctx;

	/*
	 * Initialize the LS queue wherever needed.
	 */
	fc_port->ls_queue.queues = args->ls_queue;
	fc_port->ls_queue.thread = nvmf_fc_get_main_thread();
	fc_port->ls_queue.hwqp_id = SPDK_MAX_NUM_OF_FC_PORTS * fc_port->num_io_queues;
	fc_port->ls_queue.is_ls_queue = true;

	/*
	 * Initialize the LS queue.
	 */
	rc = nvmf_fc_init_hwqp(fc_port, &fc_port->ls_queue);
	if (rc) {
		return rc;
	}

	/*
	 * Initialize the IO queues.
	 */
	for (i = 0; i < args->io_queue_cnt; i++) {
		struct spdk_nvmf_fc_hwqp *hwqp = &fc_port->io_queues[i];
		hwqp->hwqp_id = i;
		hwqp->queues = args->io_queues[i];
		hwqp->is_ls_queue = false;
		rc = nvmf_fc_init_hwqp(fc_port, hwqp);
		if (rc) {
			for (; i > 0; --i) {
				rte_hash_free(fc_port->io_queues[i - 1].connection_list_hash);
				rte_hash_free(fc_port->io_queues[i - 1].rport_list_hash);
			}
			rte_hash_free(fc_port->ls_queue.connection_list_hash);
			rte_hash_free(fc_port->ls_queue.rport_list_hash);
			return rc;
		}
	}

	/*
	 * Initialize the LS processing for port
	 */
	nvmf_fc_ls_init(fc_port);

	/*
	 * Initialize the list of nport on this HW port.
	 */
	TAILQ_INIT(&fc_port->nport_list);
	fc_port->num_nports = 0;

	return 0;
}

/*
 * FC port must have all its nports deleted before transitioning to offline state.
 */
static void
nvmf_fc_adm_hw_port_offline_nport_delete(struct spdk_nvmf_fc_port *fc_port)
{
	struct spdk_nvmf_fc_nport *nport = NULL;
	/* All nports must have been deleted at this point for this fc port */
	DEV_VERIFY(fc_port && TAILQ_EMPTY(&fc_port->nport_list));
	DEV_VERIFY(fc_port->num_nports == 0);
	/* Mark the nport states to be zombie, if they exist */
	if (fc_port && !TAILQ_EMPTY(&fc_port->nport_list)) {
		TAILQ_FOREACH(nport, &fc_port->nport_list, link) {
			(void)nvmf_fc_nport_set_state(nport, SPDK_NVMF_FC_OBJECT_ZOMBIE);
		}
	}
}

static void
nvmf_fc_adm_i_t_delete_cb(void *args, uint32_t err)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_i_t_del_cb_data *cb_data = args;
	struct spdk_nvmf_fc_nport *nport = cb_data->nport;
	struct spdk_nvmf_fc_remote_port_info *rport = cb_data->rport;
	spdk_nvmf_fc_callback cb_func = cb_data->fc_cb_func;
	int spdk_err = 0;
	uint8_t port_handle = cb_data->port_handle;
	uint32_t s_id = rport->s_id;
	uint32_t rpi = rport->rpi;
	uint32_t assoc_count = rport->assoc_count;
	uint32_t nport_hdl = nport->nport_hdl;
	uint32_t d_id = nport->d_id;
	char log_str[256];

	/*
	 * Assert on any delete failure.
	 */
	if (0 != err) {
		DEV_VERIFY(!"Error in IT Delete callback.");
		goto out;
	}

	if (cb_func != NULL) {
		(void)cb_func(port_handle, SPDK_FC_IT_DELETE, cb_data->fc_cb_ctx, spdk_err);
	}

out:
	free(cb_data);

	snprintf(log_str, sizeof(log_str),
		 "IT delete assoc_cb on nport %d done, port_handle:%d s_id:%d d_id:%d rpi:%d rport_assoc_count:%d rc = %d.\n",
		 nport_hdl, port_handle, s_id, d_id, rpi, assoc_count, err);

	if (err != 0) {
		SPDK_ERRLOG("%s", log_str);
	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "%s", log_str);
	}
}

static void
nvmf_fc_adm_i_t_delete_assoc_cb(void *args, uint32_t err)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_i_t_del_assoc_cb_data *cb_data = args;
	struct spdk_nvmf_fc_nport *nport = cb_data->nport;
	struct spdk_nvmf_fc_remote_port_info *rport = cb_data->rport;
	spdk_nvmf_fc_adm_i_t_delete_assoc_cb_fn cb_func = cb_data->cb_func;
	uint32_t s_id = rport->s_id;
	uint32_t rpi = rport->rpi;
	uint32_t assoc_count = rport->assoc_count;
	uint32_t nport_hdl = nport->nport_hdl;
	uint32_t d_id = nport->d_id;
	char log_str[256];

	/*
	 * Assert on any association delete failure. We continue to delete other
	 * associations in promoted builds.
	 */
	if (0 != err) {
		DEV_VERIFY(!"Nport's association delete callback returned error");
		if (nport->assoc_count > 0) {
			nport->assoc_count--;
		}
		if (rport->assoc_count > 0) {
			rport->assoc_count--;
		}
	}

	/*
	 * If this is the last association being deleted for the ITN,
	 * execute the callback(s).
	 */
	if (0 == rport->assoc_count) {
		/* Remove the rport from the remote port list. */
		if (nvmf_fc_nport_remove_rem_port(nport, rport) != 0) {
			SPDK_ERRLOG("Error while removing rport from list.\n");
			DEV_VERIFY(!"Error while removing rport from list.");
		}

		if (cb_func != NULL) {
			/*
			 * Callback function is provided by the caller
			 * of nvmf_fc_adm_i_t_delete_assoc().
			 */
			(void)cb_func(cb_data->cb_ctx, 0);
		}
		free(rport);
		free(args);
	}

	snprintf(log_str, sizeof(log_str),
		 "IT delete assoc_cb on nport %d done, s_id:%d d_id:%d rpi:%d rport_assoc_count:%d err = %d.\n",
		 nport_hdl, s_id, d_id, rpi, assoc_count, err);

	if (err != 0) {
		SPDK_ERRLOG("%s", log_str);
	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "%s", log_str);
	}
}

/**
 * Process a IT delete.
 */
static void
nvmf_fc_adm_i_t_delete_assoc(struct spdk_nvmf_fc_nport *nport,
			     struct spdk_nvmf_fc_remote_port_info *rport,
			     spdk_nvmf_fc_adm_i_t_delete_assoc_cb_fn cb_func,
			     void *cb_ctx)
{
	int err = 0;
	struct spdk_nvmf_fc_association *assoc = NULL;
	int assoc_err = 0;
	uint32_t num_assoc = 0;
	uint32_t num_assoc_del_scheduled = 0;
	struct spdk_nvmf_fc_adm_i_t_del_assoc_cb_data *cb_data = NULL;
	uint8_t port_hdl = nport->port_hdl;
	uint32_t s_id = rport->s_id;
	uint32_t rpi = rport->rpi;
	uint32_t assoc_count = rport->assoc_count;
	char log_str[256];

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "IT delete associations on nport:%d begin.\n",
		      nport->nport_hdl);

	/*
	 * Allocate memory for callback data.
	 * This memory will be freed by the callback function.
	 */
	cb_data = calloc(1, sizeof(struct spdk_nvmf_fc_adm_i_t_del_assoc_cb_data));
	if (NULL == cb_data) {
		SPDK_ERRLOG("Failed to allocate memory for cb_data on nport:%d.\n", nport->nport_hdl);
		err = -ENOMEM;
		goto out;
	}
	cb_data->nport       = nport;
	cb_data->rport       = rport;
	cb_data->port_handle = port_hdl;
	cb_data->cb_func     = cb_func;
	cb_data->cb_ctx      = cb_ctx;

	/*
	 * Delete all associations, if any, related with this ITN/remote_port.
	 */
	TAILQ_FOREACH(assoc, &nport->fc_associations, link) {
		num_assoc++;
		if (assoc->s_id == s_id) {
			assoc_err = nvmf_fc_delete_association(nport,
							       assoc->assoc_id,
							       false /* send abts */, false,
							       nvmf_fc_adm_i_t_delete_assoc_cb, cb_data);
			if (0 != assoc_err) {
				/*
				 * Mark this association as zombie.
				 */
				err = -EINVAL;
				DEV_VERIFY(!"Error while deleting association");
				(void)nvmf_fc_assoc_set_state(assoc, SPDK_NVMF_FC_OBJECT_ZOMBIE);
			} else {
				num_assoc_del_scheduled++;
			}
		}
	}

out:
	if ((cb_data) && (num_assoc_del_scheduled == 0)) {
		/*
		 * Since there are no association_delete calls
		 * successfully scheduled, the association_delete
		 * callback function will never be called.
		 * In this case, call the callback function now.
		 */
		nvmf_fc_adm_i_t_delete_assoc_cb(cb_data, 0);
	}

	snprintf(log_str, sizeof(log_str),
		 "IT delete associations on nport:%d end. "
		 "s_id:%d rpi:%d assoc_count:%d assoc:%d assoc_del_scheduled:%d rc:%d.\n",
		 nport->nport_hdl, s_id, rpi, assoc_count, num_assoc, num_assoc_del_scheduled, err);

	if (err == 0) {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "%s", log_str);
	} else {
		SPDK_ERRLOG("%s", log_str);
	}
}

static void
nvmf_fc_adm_queue_quiesce_cb(void *cb_data, enum spdk_nvmf_fc_poller_api_ret ret)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_poller_api_quiesce_queue_args *quiesce_api_data = NULL;
	struct spdk_nvmf_fc_adm_hw_port_quiesce_ctx *port_quiesce_ctx = NULL;
	struct spdk_nvmf_fc_hwqp *hwqp = NULL;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	int err = 0;

	quiesce_api_data = (struct spdk_nvmf_fc_poller_api_quiesce_queue_args *)cb_data;
	hwqp = quiesce_api_data->hwqp;
	fc_port = hwqp->fc_port;
	port_quiesce_ctx = (struct spdk_nvmf_fc_adm_hw_port_quiesce_ctx *)quiesce_api_data->ctx;
	spdk_nvmf_fc_adm_hw_port_quiesce_cb_fn cb_func = port_quiesce_ctx->cb_func;

	/*
	 * Decrement the callback/quiesced queue count.
	 */
	port_quiesce_ctx->quiesce_count--;
	SPDK_DEBUGLOG(nvmf_fc_adm_api, "Queue%d Quiesced\n", quiesce_api_data->hwqp->hwqp_id);

	free(quiesce_api_data);
	/*
	 * Wait for call backs i.e. max_ioq_queues + LS QUEUE.
	 */
	if (port_quiesce_ctx->quiesce_count > 0) {
		return;
	}

	if (fc_port->hw_port_status == SPDK_FC_PORT_QUIESCED) {
		SPDK_ERRLOG("Port %d already in quiesced state.\n", fc_port->port_hdl);
	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d quiesced.\n", fc_port->port_hdl);
		fc_port->hw_port_status = SPDK_FC_PORT_QUIESCED;
	}

	if (cb_func) {
		/*
		 * Callback function for the called of quiesce.
		 */
		cb_func(port_quiesce_ctx->ctx, err);
	}

	/*
	 * Free the context structure.
	 */
	free(port_quiesce_ctx);

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d quiesce done, rc = %d.\n", fc_port->port_hdl,
		      err);
}

static int
nvmf_fc_adm_hw_queue_quiesce(struct spdk_nvmf_fc_hwqp *fc_hwqp, void *ctx,
			     spdk_nvmf_fc_poller_api_cb cb_func)
{
	struct spdk_nvmf_fc_poller_api_quiesce_queue_args *args;
	enum spdk_nvmf_fc_poller_api_ret rc = SPDK_NVMF_FC_POLLER_API_SUCCESS;
	int err = 0;

	args = calloc(1, sizeof(struct spdk_nvmf_fc_poller_api_quiesce_queue_args));

	if (args == NULL) {
		err = -ENOMEM;
		SPDK_ERRLOG("Failed to allocate memory for poller quiesce args, hwqp:%d\n", fc_hwqp->hwqp_id);
		goto done;
	}
	args->hwqp = fc_hwqp;
	args->ctx = ctx;
	args->cb_info.cb_func = cb_func;
	args->cb_info.cb_data = args;
	args->cb_info.cb_thread = spdk_get_thread();

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "Quiesce queue %d\n", fc_hwqp->hwqp_id);
	rc = nvmf_fc_poller_api_func(fc_hwqp, SPDK_NVMF_FC_POLLER_API_QUIESCE_QUEUE, args);
	if (rc) {
		free(args);
		err = -EINVAL;
	}

done:
	return err;
}

/*
 * Hw port Quiesce
 */
static int
nvmf_fc_adm_hw_port_quiesce(struct spdk_nvmf_fc_port *fc_port, void *ctx,
			    spdk_nvmf_fc_adm_hw_port_quiesce_cb_fn cb_func)
{
	struct spdk_nvmf_fc_adm_hw_port_quiesce_ctx *port_quiesce_ctx = NULL;
	uint32_t i = 0;
	int err = 0;

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port:%d is being quiesced.\n", fc_port->port_hdl);

	/*
	 * If the port is in an OFFLINE state, set the state to QUIESCED
	 * and execute the callback.
	 */
	if (fc_port->hw_port_status == SPDK_FC_PORT_OFFLINE) {
		fc_port->hw_port_status = SPDK_FC_PORT_QUIESCED;
	}

	if (fc_port->hw_port_status == SPDK_FC_PORT_QUIESCED) {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "Port %d already in quiesced state.\n",
			      fc_port->port_hdl);
		/*
		 * Execute the callback function directly.
		 */
		cb_func(ctx, err);
		goto out;
	}

	port_quiesce_ctx = calloc(1, sizeof(struct spdk_nvmf_fc_adm_hw_port_quiesce_ctx));

	if (port_quiesce_ctx == NULL) {
		err = -ENOMEM;
		SPDK_ERRLOG("Failed to allocate memory for LS queue quiesce ctx, port:%d\n",
			    fc_port->port_hdl);
		goto out;
	}

	port_quiesce_ctx->quiesce_count = 0;
	port_quiesce_ctx->ctx = ctx;
	port_quiesce_ctx->cb_func = cb_func;

	/*
	 * Quiesce the LS queue.
	 */
	err = nvmf_fc_adm_hw_queue_quiesce(&fc_port->ls_queue, port_quiesce_ctx,
					   nvmf_fc_adm_queue_quiesce_cb);
	if (err != 0) {
		SPDK_ERRLOG("Failed to quiesce the LS queue.\n");
		goto out;
	}
	port_quiesce_ctx->quiesce_count++;

	/*
	 * Quiesce the IO queues.
	 */
	for (i = 0; i < fc_port->num_io_queues; i++) {
		err = nvmf_fc_adm_hw_queue_quiesce(&fc_port->io_queues[i],
						   port_quiesce_ctx,
						   nvmf_fc_adm_queue_quiesce_cb);
		if (err != 0) {
			DEV_VERIFY(0);
			SPDK_ERRLOG("Failed to quiesce the IO queue:%d.\n", fc_port->io_queues[i].hwqp_id);
		}
		port_quiesce_ctx->quiesce_count++;
	}

out:
	if (port_quiesce_ctx && err != 0) {
		free(port_quiesce_ctx);
	}
	return err;
}

/*
 * Initialize and add a HW port entry to the global
 * HW port list.
 */
static void
nvmf_fc_adm_evnt_hw_port_init(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_port_init_args *args = (struct spdk_nvmf_fc_hw_port_init_args *)
			api_data->api_args;
	int err = 0;

	if (args->io_queue_cnt > spdk_env_get_core_count()) {
		SPDK_ERRLOG("IO queues count greater than cores for %d.\n", args->port_handle);
		err = EINVAL;
		goto abort_port_init;
	}

	/*
	 * 1. Check for duplicate initialization.
	 */
	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port != NULL) {
		SPDK_ERRLOG("Duplicate port found %d.\n", args->port_handle);
		goto abort_port_init;
	}

	/*
	 * 2. Get the memory to instantiate a fc port.
	 */
	fc_port = calloc(1, sizeof(struct spdk_nvmf_fc_port) +
			 (args->io_queue_cnt * sizeof(struct spdk_nvmf_fc_hwqp)));
	if (fc_port == NULL) {
		SPDK_ERRLOG("Failed to allocate memory for fc_port %d.\n", args->port_handle);
		err = -ENOMEM;
		goto abort_port_init;
	}

	/* assign the io_queues array */
	fc_port->io_queues = (struct spdk_nvmf_fc_hwqp *)((uint8_t *)fc_port + sizeof(
				     struct spdk_nvmf_fc_port));

	/*
	 * 3. Initialize the contents for the FC-port
	 */
	err = nvmf_fc_adm_hw_port_data_init(fc_port, args);

	if (err != 0) {
		SPDK_ERRLOG("Data initialization failed for fc_port %d.\n", args->port_handle);
		DEV_VERIFY(!"Data initialization failed for fc_port");
		goto abort_port_init;
	}

	/*
	 * 4. Add this port to the global fc port list in the library.
	 */
	nvmf_fc_port_add(fc_port);

abort_port_init:
	if (err && fc_port) {
		free(fc_port);
	}
	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_HW_PORT_INIT, args->cb_ctx, err);
	}

	free(arg);

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d initialize done, rc = %d.\n",
		      args->port_handle, err);
}

static void
nvmf_fc_adm_hwqp_clean_sync_cb(struct spdk_nvmf_fc_hwqp *hwqp)
{
	struct spdk_nvmf_fc_abts_ctx *ctx;
	struct spdk_nvmf_fc_poller_api_queue_sync_args *args = NULL, *tmp = NULL;

	TAILQ_FOREACH_SAFE(args, &hwqp->sync_cbs, link, tmp) {
		TAILQ_REMOVE(&hwqp->sync_cbs, args, link);
		ctx = args->cb_info.cb_data;
		if (ctx) {
			if (++ctx->hwqps_responded == ctx->num_hwqps) {
				free(ctx->sync_poller_args);
				free(ctx->abts_poller_args);
				free(ctx);
			}
		}
	}
}

static void
nvmf_fc_adm_evnt_hw_port_free(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	int err = 0, i;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_hwqp *hwqp = NULL;
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_port_free_args *args = (struct spdk_nvmf_fc_hw_port_free_args *)
			api_data->api_args;

	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (!fc_port) {
		SPDK_ERRLOG("Unable to find the SPDK FC port %d\n", args->port_handle);
		err = -EINVAL;
		goto out;
	}

	if (!TAILQ_EMPTY(&fc_port->nport_list)) {
		SPDK_ERRLOG("Hw port %d: nports not cleared up yet.\n", args->port_handle);
		err = -EIO;
		goto out;
	}

	/* Clean up and free fc_port */
	hwqp = &fc_port->ls_queue;
	nvmf_fc_adm_hwqp_clean_sync_cb(hwqp);
	rte_hash_free(hwqp->connection_list_hash);
	rte_hash_free(hwqp->rport_list_hash);

	for (i = 0; i < (int)fc_port->num_io_queues; i++) {
		hwqp = &fc_port->io_queues[i];

		nvmf_fc_adm_hwqp_clean_sync_cb(&fc_port->io_queues[i]);
		rte_hash_free(hwqp->connection_list_hash);
		rte_hash_free(hwqp->rport_list_hash);
	}

	nvmf_fc_port_remove(fc_port);
	free(fc_port);
out:
	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d free done, rc = %d.\n",
		      args->port_handle, err);
	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_HW_PORT_FREE, args->cb_ctx, err);
	}

	free(arg);
}

/*
 * Online a HW port.
 */
static void
nvmf_fc_adm_evnt_hw_port_online(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_hwqp *hwqp = NULL;
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_port_online_args *args = (struct spdk_nvmf_fc_hw_port_online_args *)
			api_data->api_args;
	int i = 0;
	int err = 0;

	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port) {
		/* Set the port state to online */
		err = nvmf_fc_port_set_online(fc_port);
		if (err != 0) {
			SPDK_ERRLOG("Hw port %d online failed. err = %d\n", fc_port->port_hdl, err);
			DEV_VERIFY(!"Hw port online failed");
			goto out;
		}

		hwqp = &fc_port->ls_queue;
		hwqp->context = NULL;
		(void)nvmf_fc_hwqp_set_online(hwqp);

		/* Cycle through all the io queues and setup a hwqp poller for each. */
		for (i = 0; i < (int)fc_port->num_io_queues; i++) {
			hwqp = &fc_port->io_queues[i];
			hwqp->context = NULL;
			(void)nvmf_fc_hwqp_set_online(hwqp);
			nvmf_fc_poll_group_add_hwqp(hwqp);
		}
	} else {
		SPDK_ERRLOG("Unable to find the SPDK FC port %d\n", args->port_handle);
		err = -EINVAL;
	}

out:
	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_HW_PORT_ONLINE, args->cb_ctx, err);
	}

	free(arg);

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d online done, rc = %d.\n", args->port_handle,
		      err);
}

static void
nvmf_fc_adm_hw_port_offline_cb(void *ctx, int status)
{
	int err = 0;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_remove_hwqp_cb_args *remove_hwqp_args = ctx;
	struct spdk_nvmf_fc_hw_port_offline_args *args = remove_hwqp_args->cb_args;

	if (--remove_hwqp_args->pending_remove_hwqp) {
		return;
	}

	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (!fc_port) {
		err = -EINVAL;
		SPDK_ERRLOG("fc_port not found.\n");
		goto out;
	}

	/*
	 * Delete all the nports. Ideally, the nports should have been purged
	 * before the offline event, in which case, only a validation is required.
	 */
	nvmf_fc_adm_hw_port_offline_nport_delete(fc_port);
out:
	if (remove_hwqp_args->cb_fn) {
		remove_hwqp_args->cb_fn(args->port_handle, SPDK_FC_HW_PORT_OFFLINE, args->cb_ctx, err);
	}

	free(remove_hwqp_args);
}

/*
 * Offline a HW port.
 */
static void
nvmf_fc_adm_evnt_hw_port_offline(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_hwqp *hwqp = NULL;
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_port_offline_args *args = (struct spdk_nvmf_fc_hw_port_offline_args *)
			api_data->api_args;
	struct spdk_nvmf_fc_remove_hwqp_cb_args *remove_hwqp_args;
	int i = 0;
	int err = 0;

	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port) {
		/* Set the port state to offline, if it is not already. */
		err = nvmf_fc_port_set_offline(fc_port);
		if (err != 0) {
			SPDK_ERRLOG("Hw port %d already offline. err = %d\n", fc_port->port_hdl, err);
			err = 0;
			goto out;
		}

		remove_hwqp_args = calloc(1, sizeof(struct spdk_nvmf_fc_remove_hwqp_cb_args));
		if (!remove_hwqp_args) {
			SPDK_ERRLOG("Failed to alloc memory for remove_hwqp_args\n");
			err = -ENOMEM;
			goto out;
		}
		remove_hwqp_args->cb_fn = api_data->cb_func;
		remove_hwqp_args->cb_args = api_data->api_args;
		remove_hwqp_args->pending_remove_hwqp = fc_port->num_io_queues;

		hwqp = &fc_port->ls_queue;
		(void)nvmf_fc_hwqp_set_offline(hwqp);

		/* Remove poller for all the io queues. */
		for (i = 0; i < (int)fc_port->num_io_queues; i++) {
			hwqp = &fc_port->io_queues[i];
			(void)nvmf_fc_hwqp_set_offline(hwqp);
			nvmf_fc_poll_group_remove_hwqp(hwqp, nvmf_fc_adm_hw_port_offline_cb,
						       remove_hwqp_args);
		}

		free(arg);

		/* Wait untill all the hwqps are removed from poll groups. */
		return;
	} else {
		SPDK_ERRLOG("Unable to find the SPDK FC port %d\n", args->port_handle);
		err = -EINVAL;
	}
out:
	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_HW_PORT_OFFLINE, args->cb_ctx, err);
	}

	free(arg);

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d offline done, rc = %d.\n", args->port_handle,
		      err);
}

struct nvmf_fc_add_rem_listener_ctx {
	struct spdk_nvmf_subsystem *subsystem;
	bool add_listener;
	struct spdk_nvme_transport_id trid;
};

static void
nvmf_fc_adm_subsystem_resume_cb(struct spdk_nvmf_subsystem *subsystem, void *cb_arg, int status)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct nvmf_fc_add_rem_listener_ctx *ctx = (struct nvmf_fc_add_rem_listener_ctx *)cb_arg;
	free(ctx);
}

static void
nvmf_fc_adm_listen_done(void *cb_arg, int status)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct nvmf_fc_add_rem_listener_ctx *ctx = cb_arg;

	if (spdk_nvmf_subsystem_resume(ctx->subsystem, nvmf_fc_adm_subsystem_resume_cb, ctx)) {
		SPDK_ERRLOG("Failed to resume subsystem: %s\n", ctx->subsystem->subnqn);
		free(ctx);
	}
}

static void
nvmf_fc_adm_subsystem_paused_cb(struct spdk_nvmf_subsystem *subsystem, void *cb_arg, int status)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct nvmf_fc_add_rem_listener_ctx *ctx = (struct nvmf_fc_add_rem_listener_ctx *)cb_arg;

	if (ctx->add_listener) {
		spdk_nvmf_subsystem_add_listener(subsystem, &ctx->trid, nvmf_fc_adm_listen_done, ctx);
	} else {
		spdk_nvmf_subsystem_remove_listener(subsystem, &ctx->trid);
		nvmf_fc_adm_listen_done(ctx, 0);
	}
}

static int
nvmf_fc_adm_add_rem_nport_listener(struct spdk_nvmf_fc_nport *nport, bool add)
{
	struct spdk_nvmf_tgt *tgt = nvmf_fc_get_tgt();
	struct spdk_nvmf_subsystem *subsystem;
	struct spdk_nvmf_listen_opts opts;

	if (!tgt) {
		SPDK_ERRLOG("No nvmf target defined\n");
		return -EINVAL;
	}

	spdk_nvmf_listen_opts_init(&opts, sizeof(opts));

	subsystem = spdk_nvmf_subsystem_get_first(tgt);
	while (subsystem) {
		struct nvmf_fc_add_rem_listener_ctx *ctx;

		if (spdk_nvmf_subsytem_any_listener_allowed(subsystem) == true) {
			ctx = calloc(1, sizeof(struct nvmf_fc_add_rem_listener_ctx));
			if (ctx) {
				ctx->add_listener = add;
				ctx->subsystem = subsystem;
				nvmf_fc_create_trid(&ctx->trid,
						    nport->fc_nodename.u.wwn,
						    nport->fc_portname.u.wwn);

				if (spdk_nvmf_tgt_listen_ext(subsystem->tgt, &ctx->trid, &opts)) {
					SPDK_ERRLOG("Failed to add transport address %s to tgt listeners\n",
						    ctx->trid.traddr);
					free(ctx);
				} else if (spdk_nvmf_subsystem_pause(subsystem,
								     0,
								     nvmf_fc_adm_subsystem_paused_cb,
								     ctx)) {
					SPDK_ERRLOG("Failed to pause subsystem: %s\n",
						    subsystem->subnqn);
					free(ctx);
				}
			}
		}

		subsystem = spdk_nvmf_subsystem_get_next(subsystem);
	}

	return 0;
}

/*
 * Create a Nport.
 */
static void
nvmf_fc_adm_evnt_nport_create(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_nport_create_args *args = (struct spdk_nvmf_fc_nport_create_args *)
			api_data->api_args;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	int err = 0;

	/*
	 * Get the physical port.
	 */
	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port == NULL) {
		err = -EINVAL;
		goto out;
	}

	/*
	 * Check for duplicate initialization.
	 */
	nport = nvmf_fc_nport_find(args->port_handle, args->nport_handle);
	if (nport != NULL) {
		SPDK_ERRLOG("Duplicate SPDK FC nport %d exists for FC port:%d.\n", args->nport_handle,
			    args->port_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * Get the memory to instantiate a fc nport.
	 */
	nport = calloc(1, sizeof(struct spdk_nvmf_fc_nport));
	if (nport == NULL) {
		SPDK_ERRLOG("Failed to allocate memory for nport %d.\n",
			    args->nport_handle);
		err = -ENOMEM;
		goto out;
	}

	/*
	 * Initialize the contents for the nport
	 */
	nport->nport_hdl    = args->nport_handle;
	nport->port_hdl     = args->port_handle;
	nport->nport_state  = SPDK_NVMF_FC_OBJECT_CREATED;
	nport->fc_nodename  = args->fc_nodename;
	nport->fc_portname  = args->fc_portname;
	nport->d_id         = args->d_id;
	nport->fc_port      = nvmf_fc_port_lookup(args->port_handle);

	(void)nvmf_fc_nport_set_state(nport, SPDK_NVMF_FC_OBJECT_CREATED);
	TAILQ_INIT(&nport->rem_port_list);
	nport->rport_count = 0;
	TAILQ_INIT(&nport->fc_associations);
	nport->assoc_count = 0;

	/*
	 * Populate the nport address (as listening address) to the nvmf subsystems.
	 */
	err = nvmf_fc_adm_add_rem_nport_listener(nport, true);

	(void)nvmf_fc_port_add_nport(fc_port, nport);
out:
	if (err && nport) {
		free(nport);
	}

	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_NPORT_CREATE, args->cb_ctx, err);
	}

	free(arg);
}

static void
nvmf_fc_adm_delete_nport_cb(uint8_t port_handle, enum spdk_fc_event event_type,
			    void *cb_args, int spdk_err)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_nport_del_cb_data *cb_data = cb_args;
	struct spdk_nvmf_fc_nport *nport = cb_data->nport;
	spdk_nvmf_fc_callback cb_func = cb_data->fc_cb_func;
	int err = 0;
	uint16_t nport_hdl = 0;
	char log_str[256];

	/*
	 * Assert on any delete failure.
	 */
	if (nport == NULL) {
		SPDK_ERRLOG("Nport delete callback returned null nport");
		DEV_VERIFY(!"nport is null.");
		goto out;
	}

	nport_hdl = nport->nport_hdl;
	if (0 != spdk_err) {
		SPDK_ERRLOG("Nport delete callback returned error. FC Port: "
			    "%d, Nport: %d\n",
			    nport->port_hdl, nport->nport_hdl);
		DEV_VERIFY(!"nport delete callback error.");
	}

	/*
	 * Free the nport if this is the last rport being deleted and
	 * execute the callback(s).
	 */
	if (nvmf_fc_nport_has_no_rport(nport)) {
		if (0 != nport->assoc_count) {
			SPDK_ERRLOG("association count != 0\n");
			DEV_VERIFY(!"association count != 0");
		}

		err = nvmf_fc_port_remove_nport(nport->fc_port, nport);
		if (0 != err) {
			SPDK_ERRLOG("Nport delete callback: Failed to remove "
				    "nport from nport list. FC Port:%d Nport:%d\n",
				    nport->port_hdl, nport->nport_hdl);
		}
		/* Free the nport */
		free(nport);

		if (cb_func != NULL) {
			(void)cb_func(cb_data->port_handle, SPDK_FC_NPORT_DELETE, cb_data->fc_cb_ctx, spdk_err);
		}
		free(cb_data);
	}
out:
	snprintf(log_str, sizeof(log_str),
		 "port:%d nport:%d delete cb exit, evt_type:%d rc:%d.\n",
		 port_handle, nport_hdl, event_type, spdk_err);

	if (err != 0) {
		SPDK_ERRLOG("%s", log_str);
	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "%s", log_str);
	}
}

/*
 * Delete Nport.
 */
static void
nvmf_fc_adm_evnt_nport_delete(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_nport_delete_args *args = (struct spdk_nvmf_fc_nport_delete_args *)
			api_data->api_args;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_adm_nport_del_cb_data *cb_data = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport_iter = NULL;
	int err = 0;
	uint32_t rport_cnt = 0;
	int rc = 0;

	/*
	 * Make sure that the nport exists.
	 */
	nport = nvmf_fc_nport_find(args->port_handle, args->nport_handle);
	if (nport == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC nport %d for FC Port: %d.\n", args->nport_handle,
			    args->port_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * Allocate memory for callback data.
	 */
	cb_data = calloc(1, sizeof(struct spdk_nvmf_fc_adm_nport_del_cb_data));
	if (NULL == cb_data) {
		SPDK_ERRLOG("Failed to allocate memory for cb_data %d.\n", args->nport_handle);
		err = -ENOMEM;
		goto out;
	}

	cb_data->nport = nport;
	cb_data->port_handle = args->port_handle;
	cb_data->fc_cb_func = api_data->cb_func;
	cb_data->fc_cb_ctx = args->cb_ctx;

	/*
	 * Begin nport tear down
	 */
	if (nport->nport_state == SPDK_NVMF_FC_OBJECT_CREATED) {
		(void)nvmf_fc_nport_set_state(nport, SPDK_NVMF_FC_OBJECT_TO_BE_DELETED);
	} else if (nport->nport_state == SPDK_NVMF_FC_OBJECT_TO_BE_DELETED) {
		/*
		 * Deletion of this nport already in progress. Register callback
		 * and return.
		 */
		/* TODO: Register callback in callback vector. For now, set the error and return. */
		err = -ENODEV;
		goto out;
	} else {
		/* nport partially created/deleted */
		DEV_VERIFY(nport->nport_state == SPDK_NVMF_FC_OBJECT_ZOMBIE);
		DEV_VERIFY(0 != "Nport in zombie state");
		err = -ENODEV;
		goto out;
	}

	/*
	 * Remove this nport from listening addresses across subsystems
	 */
	rc = nvmf_fc_adm_add_rem_nport_listener(nport, false);

	if (0 != rc) {
		err = nvmf_fc_nport_set_state(nport, SPDK_NVMF_FC_OBJECT_ZOMBIE);
		SPDK_ERRLOG("Unable to remove the listen addr in the subsystems for nport %d.\n",
			    nport->nport_hdl);
		goto out;
	}

	/*
	 * Delete all the remote ports (if any) for the nport
	 */
	/* TODO - Need to do this with a "first" and a "next" accessor function
	 * for completeness. Look at app-subsystem as examples.
	 */
	if (nvmf_fc_nport_has_no_rport(nport)) {
		/* No rports to delete. Complete the nport deletion. */
		nvmf_fc_adm_delete_nport_cb(nport->port_hdl, SPDK_FC_NPORT_DELETE, cb_data, 0);
		goto out;
	}

	TAILQ_FOREACH(rport_iter, &nport->rem_port_list, link) {
		struct spdk_nvmf_fc_hw_i_t_delete_args *it_del_args = calloc(
					1, sizeof(struct spdk_nvmf_fc_hw_i_t_delete_args));

		if (it_del_args == NULL) {
			err = -ENOMEM;
			SPDK_ERRLOG("SPDK_FC_IT_DELETE no mem to delete rport with rpi:%d s_id:%d.\n",
				    rport_iter->rpi, rport_iter->s_id);
			DEV_VERIFY(!"SPDK_FC_IT_DELETE failed, cannot allocate memory");
			goto out;
		}

		rport_cnt++;
		it_del_args->port_handle = nport->port_hdl;
		it_del_args->nport_handle = nport->nport_hdl;
		it_del_args->cb_ctx = (void *)cb_data;
		it_del_args->rpi = rport_iter->rpi;
		it_del_args->s_id = rport_iter->s_id;

		nvmf_fc_main_enqueue_event(SPDK_FC_IT_DELETE, (void *)it_del_args,
					   nvmf_fc_adm_delete_nport_cb);
	}

out:
	/* On failure, execute the callback function now */
	if ((err != 0) || (rc != 0)) {
		SPDK_ERRLOG("NPort %d delete failed, error:%d, fc port:%d, "
			    "rport_cnt:%d rc:%d.\n",
			    args->nport_handle, err, args->port_handle,
			    rport_cnt, rc);
		if (cb_data) {
			free(cb_data);
		}
		if (api_data->cb_func != NULL) {
			(void)api_data->cb_func(args->port_handle, SPDK_FC_NPORT_DELETE, args->cb_ctx, err);
		}

	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api,
			      "NPort %d delete done successfully, fc port:%d. "
			      "rport_cnt:%d\n",
			      args->nport_handle, args->port_handle, rport_cnt);
	}

	free(arg);
}

/*
 * Process an PRLI/IT add.
 */
static void
nvmf_fc_adm_evnt_i_t_add(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_i_t_add_args *args = (struct spdk_nvmf_fc_hw_i_t_add_args *)
			api_data->api_args;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport_iter = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport = NULL;
	int err = 0;

	/*
	 * Make sure the nport port exists.
	 */
	nport = nvmf_fc_nport_find(args->port_handle, args->nport_handle);
	if (nport == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC nport %d\n", args->nport_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * Check for duplicate i_t_add.
	 */
	TAILQ_FOREACH(rport_iter, &nport->rem_port_list, link) {
		if ((rport_iter->s_id == args->s_id) && (rport_iter->rpi == args->rpi)) {
			SPDK_ERRLOG("Duplicate rport found for FC nport %d: sid:%d rpi:%d\n",
				    args->nport_handle, rport_iter->s_id, rport_iter->rpi);
			err = -EEXIST;
			goto out;
		}
	}

	/*
	 * Get the memory to instantiate the remote port
	 */
	rport = calloc(1, sizeof(struct spdk_nvmf_fc_remote_port_info));
	if (rport == NULL) {
		SPDK_ERRLOG("Memory allocation for rem port failed.\n");
		err = -ENOMEM;
		goto out;
	}

	/*
	 * Initialize the contents for the rport
	 */
	(void)nvmf_fc_rport_set_state(rport, SPDK_NVMF_FC_OBJECT_CREATED);
	rport->s_id = args->s_id;
	rport->rpi = args->rpi;
	rport->fc_nodename = args->fc_nodename;
	rport->fc_portname = args->fc_portname;

	/*
	 * Add remote port to nport
	 */
	if (nvmf_fc_nport_add_rem_port(nport, rport) != 0) {
		DEV_VERIFY(!"Error while adding rport to list");
	};

	/*
	 * TODO: Do we validate the initiators service parameters?
	 */

	/*
	 * Get the targets service parameters from the library
	 * to return back to the driver.
	 */
	args->target_prli_info = nvmf_fc_get_prli_service_params();

out:
	if (api_data->cb_func != NULL) {
		/*
		 * Passing pointer to the args struct as the first argument.
		 * The cb_func should handle this appropriately.
		 */
		(void)api_data->cb_func(args->port_handle, SPDK_FC_IT_ADD, args->cb_ctx, err);
	}

	free(arg);

	SPDK_DEBUGLOG(nvmf_fc_adm_api,
		      "IT add on nport %d done, rc = %d.\n",
		      args->nport_handle, err);
}

/**
 * Process a IT delete.
 */
static void
nvmf_fc_adm_evnt_i_t_delete(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_i_t_delete_args *args = (struct spdk_nvmf_fc_hw_i_t_delete_args *)
			api_data->api_args;
	int rc = 0;
	struct spdk_nvmf_fc_nport *nport = NULL;
	struct spdk_nvmf_fc_adm_i_t_del_cb_data *cb_data = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport_iter = NULL;
	struct spdk_nvmf_fc_remote_port_info *rport = NULL;
	uint32_t num_rport = 0;
	char log_str[256];

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "IT delete on nport:%d begin.\n", args->nport_handle);

	/*
	 * Make sure the nport port exists. If it does not, error out.
	 */
	nport = nvmf_fc_nport_find(args->port_handle, args->nport_handle);
	if (nport == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC nport:%d\n", args->nport_handle);
		rc = -EINVAL;
		goto out;
	}

	/*
	 * Find this ITN / rport (remote port).
	 */
	TAILQ_FOREACH(rport_iter, &nport->rem_port_list, link) {
		num_rport++;
		if ((rport_iter->s_id == args->s_id) &&
		    (rport_iter->rpi == args->rpi) &&
		    (rport_iter->rport_state == SPDK_NVMF_FC_OBJECT_CREATED)) {
			rport = rport_iter;
			break;
		}
	}

	/*
	 * We should find either zero or exactly one rport.
	 *
	 * If we find zero rports, that means that a previous request has
	 * removed the rport by the time we reached here. In this case,
	 * simply return out.
	 */
	if (rport == NULL) {
		rc = -ENODEV;
		goto out;
	}

	/*
	 * We have the rport slated for deletion. At this point clean up
	 * any LS requests that are sitting in the pending list. Do this
	 * first, then, set the states of the rport so that new LS requests
	 * are not accepted. Then start the cleanup.
	 */
	nvmf_fc_delete_ls_pending(&(nport->fc_port->ls_queue), nport, rport);

	/*
	 * We have found exactly one rport. Allocate memory for callback data.
	 */
	cb_data = calloc(1, sizeof(struct spdk_nvmf_fc_adm_i_t_del_cb_data));
	if (NULL == cb_data) {
		SPDK_ERRLOG("Failed to allocate memory for cb_data for nport:%d.\n", args->nport_handle);
		rc = -ENOMEM;
		goto out;
	}

	cb_data->nport = nport;
	cb_data->rport = rport;
	cb_data->port_handle = args->port_handle;
	cb_data->fc_cb_func = api_data->cb_func;
	cb_data->fc_cb_ctx = args->cb_ctx;

	/*
	 * Validate rport object state.
	 */
	if (rport->rport_state == SPDK_NVMF_FC_OBJECT_CREATED) {
		(void)nvmf_fc_rport_set_state(rport, SPDK_NVMF_FC_OBJECT_TO_BE_DELETED);
	} else if (rport->rport_state == SPDK_NVMF_FC_OBJECT_TO_BE_DELETED) {
		/*
		 * Deletion of this rport already in progress. Register callback
		 * and return.
		 */
		/* TODO: Register callback in callback vector. For now, set the error and return. */
		rc = -ENODEV;
		goto out;
	} else {
		/* rport partially created/deleted */
		DEV_VERIFY(rport->rport_state == SPDK_NVMF_FC_OBJECT_ZOMBIE);
		DEV_VERIFY(!"Invalid rport_state");
		rc = -ENODEV;
		goto out;
	}

	/*
	 * We have successfully found a rport to delete. Call
	 * nvmf_fc_i_t_delete_assoc(), which will perform further
	 * IT-delete processing as well as free the cb_data.
	 */
	nvmf_fc_adm_i_t_delete_assoc(nport, rport, nvmf_fc_adm_i_t_delete_cb,
				     (void *)cb_data);

out:
	if (rc != 0) {
		/*
		 * We have entered here because either we encountered an
		 * error, or we did not find a rport to delete.
		 * As a result, we will not call the function
		 * nvmf_fc_i_t_delete_assoc() for further IT-delete
		 * processing. Therefore, execute the callback function now.
		 */
		if (cb_data) {
			free(cb_data);
		}
		if (api_data->cb_func != NULL) {
			(void)api_data->cb_func(args->port_handle, SPDK_FC_IT_DELETE, args->cb_ctx, rc);
		}
	}

	snprintf(log_str, sizeof(log_str),
		 "IT delete on nport:%d end. num_rport:%d rc = %d.\n",
		 args->nport_handle, num_rport, rc);

	if (rc != 0) {
		SPDK_ERRLOG("%s", log_str);
	} else {
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "%s", log_str);
	}

	free(arg);
}

/*
 * Process ABTS received
 */
static void
nvmf_fc_adm_evnt_abts_recv(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_abts_args *args = (struct spdk_nvmf_fc_abts_args *)api_data->api_args;
	struct spdk_nvmf_fc_nport *nport = NULL;
	int err = 0;

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "FC ABTS received. RPI:%d, oxid:%d, rxid:%d\n", args->rpi,
		      args->oxid, args->rxid);

	/*
	 * 1. Make sure the nport port exists.
	 */
	nport = nvmf_fc_nport_find(args->port_handle, args->nport_handle);
	if (nport == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC nport %d\n", args->nport_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * 2. If the nport is in the process of being deleted, drop the ABTS.
	 */
	if (nport->nport_state == SPDK_NVMF_FC_OBJECT_TO_BE_DELETED) {
		SPDK_DEBUGLOG(nvmf_fc_adm_api,
			      "FC ABTS dropped because the nport is being deleted; RPI:%d, oxid:%d, rxid:%d\n",
			      args->rpi, args->oxid, args->rxid);
		err = 0;
		goto out;

	}

	/*
	 * 3. Pass the received ABTS-LS to the library for handling.
	 */
	nvmf_fc_handle_abts_frame(nport, args->rpi, args->oxid, args->rxid);

out:
	if (api_data->cb_func != NULL) {
		/*
		 * Passing pointer to the args struct as the first argument.
		 * The cb_func should handle this appropriately.
		 */
		(void)api_data->cb_func(args->port_handle, SPDK_FC_ABTS_RECV, args, err);
	} else {
		/* No callback set, free the args */
		free(args);
	}

	free(arg);
}

/*
 * Callback function for hw port quiesce.
 */
static void
nvmf_fc_adm_hw_port_quiesce_reset_cb(void *ctx, int err)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_hw_port_reset_ctx *reset_ctx =
		(struct spdk_nvmf_fc_adm_hw_port_reset_ctx *)ctx;
	struct spdk_nvmf_fc_hw_port_reset_args *args = reset_ctx->reset_args;
	spdk_nvmf_fc_callback cb_func = reset_ctx->reset_cb_func;
	struct spdk_nvmf_fc_queue_dump_info dump_info;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	char *dump_buf = NULL;
	uint32_t dump_buf_size = SPDK_FC_HW_DUMP_BUF_SIZE;

	/*
	 * Free the callback context struct.
	 */
	free(ctx);

	if (err != 0) {
		SPDK_ERRLOG("Port %d  quiesce operation failed.\n", args->port_handle);
		goto out;
	}

	if (args->dump_queues == false) {
		/*
		 * Queues need not be dumped.
		 */
		goto out;
	}

	SPDK_ERRLOG("Dumping queues for HW port %d\n", args->port_handle);

	/*
	 * Get the fc port.
	 */
	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC port %d\n", args->port_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * Allocate memory for the dump buffer.
	 * This memory will be freed by FCT.
	 */
	dump_buf = (char *)calloc(1, dump_buf_size);
	if (dump_buf == NULL) {
		err = -ENOMEM;
		SPDK_ERRLOG("Memory allocation for dump buffer failed, SPDK FC port %d\n", args->port_handle);
		goto out;
	}
	*args->dump_buf  = (uint32_t *)dump_buf;
	dump_info.buffer = dump_buf;
	dump_info.offset = 0;

	/*
	 * Add the dump reason to the top of the buffer.
	 */
	nvmf_fc_dump_buf_print(&dump_info, "%s\n", args->reason);

	/*
	 * Dump the hwqp.
	 */
	nvmf_fc_dump_all_queues(&fc_port->ls_queue, fc_port->io_queues,
				fc_port->num_io_queues, &dump_info);

out:
	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d reset done, queues_dumped = %d, rc = %d.\n",
		      args->port_handle, args->dump_queues, err);

	if (cb_func != NULL) {
		(void)cb_func(args->port_handle, SPDK_FC_HW_PORT_RESET, args->cb_ctx, err);
	}
}

/*
 * HW port reset

 */
static void
nvmf_fc_adm_evnt_hw_port_reset(void *arg)
{
	ASSERT_SPDK_FC_MAIN_THREAD();
	struct spdk_nvmf_fc_adm_api_data *api_data = (struct spdk_nvmf_fc_adm_api_data *)arg;
	struct spdk_nvmf_fc_hw_port_reset_args *args = (struct spdk_nvmf_fc_hw_port_reset_args *)
			api_data->api_args;
	struct spdk_nvmf_fc_port *fc_port = NULL;
	struct spdk_nvmf_fc_adm_hw_port_reset_ctx *ctx = NULL;
	int err = 0;

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d dump\n", args->port_handle);

	/*
	 * Make sure the physical port exists.
	 */
	fc_port = nvmf_fc_port_lookup(args->port_handle);
	if (fc_port == NULL) {
		SPDK_ERRLOG("Unable to find the SPDK FC port %d\n", args->port_handle);
		err = -EINVAL;
		goto out;
	}

	/*
	 * Save the reset event args and the callback in a context struct.
	 */
	ctx = calloc(1, sizeof(struct spdk_nvmf_fc_adm_hw_port_reset_ctx));

	if (ctx == NULL) {
		err = -ENOMEM;
		SPDK_ERRLOG("Memory allocation for reset ctx failed, SPDK FC port %d\n", args->port_handle);
		goto fail;
	}

	ctx->reset_args = args;
	ctx->reset_cb_func = api_data->cb_func;

	/*
	 * Quiesce the hw port.
	 */
	err = nvmf_fc_adm_hw_port_quiesce(fc_port, ctx, nvmf_fc_adm_hw_port_quiesce_reset_cb);
	if (err != 0) {
		goto fail;
	}

	/*
	 * Once the ports are successfully quiesced the reset processing
	 * will continue in the callback function: spdk_fc_port_quiesce_reset_cb
	 */
	return;
fail:
	free(ctx);

out:
	SPDK_DEBUGLOG(nvmf_fc_adm_api, "HW port %d dump done, rc = %d.\n", args->port_handle,
		      err);

	if (api_data->cb_func != NULL) {
		(void)api_data->cb_func(args->port_handle, SPDK_FC_HW_PORT_RESET, args->cb_ctx, err);
	}

	free(arg);
}

static inline void
nvmf_fc_adm_run_on_main_thread(spdk_msg_fn fn, void *args)
{
	if (nvmf_fc_get_main_thread()) {
		spdk_thread_send_msg(nvmf_fc_get_main_thread(), fn, args);
	}
}

/*
 * Queue up an event in the SPDK main threads event queue.
 * Used by the FC driver to notify the SPDK main thread of FC related events.
 */
int
nvmf_fc_main_enqueue_event(enum spdk_fc_event event_type, void *args,
			   spdk_nvmf_fc_callback cb_func)
{
	int err = 0;
	struct spdk_nvmf_fc_adm_api_data *api_data = NULL;
	spdk_msg_fn event_fn = NULL;

	SPDK_DEBUGLOG(nvmf_fc_adm_api, "Enqueue event %d.\n", event_type);

	if (event_type >= SPDK_FC_EVENT_MAX) {
		SPDK_ERRLOG("Invalid spdk_fc_event_t %d.\n", event_type);
		err = -EINVAL;
		goto done;
	}

	if (args == NULL) {
		SPDK_ERRLOG("Null args for event %d.\n", event_type);
		err = -EINVAL;
		goto done;
	}

	api_data = calloc(1, sizeof(*api_data));

	if (api_data == NULL) {
		SPDK_ERRLOG("Failed to alloc api data for event %d.\n", event_type);
		err = -ENOMEM;
		goto done;
	}

	api_data->api_args = args;
	api_data->cb_func = cb_func;

	switch (event_type) {
	case SPDK_FC_HW_PORT_INIT:
		event_fn = nvmf_fc_adm_evnt_hw_port_init;
		break;

	case SPDK_FC_HW_PORT_FREE:
		event_fn = nvmf_fc_adm_evnt_hw_port_free;
		break;

	case SPDK_FC_HW_PORT_ONLINE:
		event_fn = nvmf_fc_adm_evnt_hw_port_online;
		break;

	case SPDK_FC_HW_PORT_OFFLINE:
		event_fn = nvmf_fc_adm_evnt_hw_port_offline;
		break;

	case SPDK_FC_NPORT_CREATE:
		event_fn = nvmf_fc_adm_evnt_nport_create;
		break;

	case SPDK_FC_NPORT_DELETE:
		event_fn = nvmf_fc_adm_evnt_nport_delete;
		break;

	case SPDK_FC_IT_ADD:
		event_fn = nvmf_fc_adm_evnt_i_t_add;
		break;

	case SPDK_FC_IT_DELETE:
		event_fn = nvmf_fc_adm_evnt_i_t_delete;
		break;

	case SPDK_FC_ABTS_RECV:
		event_fn = nvmf_fc_adm_evnt_abts_recv;
		break;

	case SPDK_FC_HW_PORT_RESET:
		event_fn = nvmf_fc_adm_evnt_hw_port_reset;
		break;

	case SPDK_FC_UNRECOVERABLE_ERR:
	default:
		SPDK_ERRLOG("Invalid spdk_fc_event_t: %d\n", event_type);
		err = -EINVAL;
		break;
	}

done:

	if (err == 0) {
		assert(event_fn != NULL);
		nvmf_fc_adm_run_on_main_thread(event_fn, (void *)api_data);
		SPDK_DEBUGLOG(nvmf_fc_adm_api, "Enqueue event %d done successfully\n", event_type);
	} else {
		SPDK_ERRLOG("Enqueue event %d failed, err = %d\n", event_type, err);
		if (api_data) {
			free(api_data);
		}
	}

	return err;
}

SPDK_NVMF_TRANSPORT_REGISTER(fc, &spdk_nvmf_transport_fc);
SPDK_LOG_REGISTER_COMPONENT(nvmf_fc_adm_api)
SPDK_LOG_REGISTER_COMPONENT(nvmf_fc)
