]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/spdk/lib/nvmf/ctrlr.c
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / spdk / lib / nvmf / ctrlr.c
index 67feb0a1952b69a5fb87174a2beffd859e529a78..638cde9d23d36ea7968efbad9b56c604c4d9452c 100644 (file)
@@ -1,8 +1,8 @@
 /*-
  *   BSD LICENSE
  *
- *   Copyright (c) Intel Corporation.
- *   All rights reserved.
+ *   Copyright (c) Intel Corporation. All rights reserved.
+ *   Copyright (c) 2019, 2020 Mellanox Technologies LTD. All rights reserved.
  *
  *   Redistribution and use in source and binary forms, with or without
  *   modification, are permitted provided that the following conditions
@@ -41,6 +41,7 @@
 #include "spdk/thread.h"
 #include "spdk/trace.h"
 #include "spdk/nvme_spec.h"
+#include "spdk/nvmf_cmd.h"
 #include "spdk/string.h"
 #include "spdk/util.h"
 #include "spdk/version.h"
  */
 #define FW_VERSION SPDK_VERSION_MAJOR_STRING SPDK_VERSION_MINOR_STRING SPDK_VERSION_PATCH_STRING
 
+/*
+ * Support for custom admin command handlers
+ */
+struct spdk_nvmf_custom_admin_cmd {
+       spdk_nvmf_custom_cmd_hdlr hdlr;
+       uint32_t nsid; /* nsid to forward */
+};
+
+static struct spdk_nvmf_custom_admin_cmd g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_MAX_OPC + 1];
+
+static void _nvmf_request_complete(void *ctx);
+
 static inline void
-spdk_nvmf_invalid_connect_response(struct spdk_nvmf_fabric_connect_rsp *rsp,
-                                  uint8_t iattr, uint16_t ipo)
+nvmf_invalid_connect_response(struct spdk_nvmf_fabric_connect_rsp *rsp,
+                             uint8_t iattr, uint16_t ipo)
 {
        rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
        rsp->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
@@ -69,12 +82,12 @@ spdk_nvmf_invalid_connect_response(struct spdk_nvmf_fabric_connect_rsp *rsp,
 }
 
 #define SPDK_NVMF_INVALID_CONNECT_CMD(rsp, field)      \
-       spdk_nvmf_invalid_connect_response(rsp, 0, offsetof(struct spdk_nvmf_fabric_connect_cmd, field))
+       nvmf_invalid_connect_response(rsp, 0, offsetof(struct spdk_nvmf_fabric_connect_cmd, field))
 #define SPDK_NVMF_INVALID_CONNECT_DATA(rsp, field)     \
-       spdk_nvmf_invalid_connect_response(rsp, 1, offsetof(struct spdk_nvmf_fabric_connect_data, field))
+       nvmf_invalid_connect_response(rsp, 1, offsetof(struct spdk_nvmf_fabric_connect_data, field))
 
 static void
-spdk_nvmf_ctrlr_stop_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
+nvmf_ctrlr_stop_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
 {
        if (!ctrlr) {
                SPDK_ERRLOG("Controller is NULL\n");
@@ -90,7 +103,7 @@ spdk_nvmf_ctrlr_stop_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
 }
 
 static void
-spdk_nvmf_ctrlr_disconnect_qpairs_done(struct spdk_io_channel_iter *i, int status)
+nvmf_ctrlr_disconnect_qpairs_done(struct spdk_io_channel_iter *i, int status)
 {
        if (status == 0) {
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "ctrlr disconnect qpairs complete successfully\n");
@@ -99,8 +112,8 @@ spdk_nvmf_ctrlr_disconnect_qpairs_done(struct spdk_io_channel_iter *i, int statu
        }
 }
 
-static void
-spdk_nvmf_ctrlr_disconnect_qpairs_on_pg(struct spdk_io_channel_iter *i)
+static int
+_nvmf_ctrlr_disconnect_qpairs_on_pg(struct spdk_io_channel_iter *i, bool include_admin)
 {
        int rc = 0;
        struct spdk_nvmf_ctrlr *ctrlr;
@@ -113,21 +126,32 @@ spdk_nvmf_ctrlr_disconnect_qpairs_on_pg(struct spdk_io_channel_iter *i)
        group = spdk_io_channel_get_ctx(ch);
 
        TAILQ_FOREACH_SAFE(qpair, &group->qpairs, link, temp_qpair) {
-               if (qpair->ctrlr == ctrlr) {
+               if (qpair->ctrlr == ctrlr && (include_admin || !nvmf_qpair_is_admin_queue(qpair))) {
                        rc = spdk_nvmf_qpair_disconnect(qpair, NULL, NULL);
                        if (rc) {
                                SPDK_ERRLOG("Qpair disconnect failed\n");
-                               goto next_channel;
+                               return rc;
                        }
                }
        }
 
-next_channel:
-       spdk_for_each_channel_continue(i, rc);
+       return rc;
+}
+
+static void
+nvmf_ctrlr_disconnect_qpairs_on_pg(struct spdk_io_channel_iter *i)
+{
+       spdk_for_each_channel_continue(i, _nvmf_ctrlr_disconnect_qpairs_on_pg(i, true));
+}
+
+static void
+nvmf_ctrlr_disconnect_io_qpairs_on_pg(struct spdk_io_channel_iter *i)
+{
+       spdk_for_each_channel_continue(i, _nvmf_ctrlr_disconnect_qpairs_on_pg(i, false));
 }
 
 static int
-spdk_nvmf_ctrlr_keep_alive_poll(void *ctx)
+nvmf_ctrlr_keep_alive_poll(void *ctx)
 {
        uint64_t keep_alive_timeout_tick;
        uint64_t now = spdk_get_ticks();
@@ -151,17 +175,17 @@ spdk_nvmf_ctrlr_keep_alive_poll(void *ctx)
                         * disconnect qpairs with qpair->ctrlr == ctrlr
                         */
                        spdk_for_each_channel(ctrlr->subsys->tgt,
-                                             spdk_nvmf_ctrlr_disconnect_qpairs_on_pg,
+                                             nvmf_ctrlr_disconnect_qpairs_on_pg,
                                              ctrlr,
-                                             spdk_nvmf_ctrlr_disconnect_qpairs_done);
+                                             nvmf_ctrlr_disconnect_qpairs_done);
                }
        }
 
-       return 1;
+       return SPDK_POLLER_BUSY;
 }
 
 static void
-spdk_nvmf_ctrlr_start_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
+nvmf_ctrlr_start_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
 {
        if (!ctrlr) {
                SPDK_ERRLOG("Controller is NULL\n");
@@ -174,7 +198,7 @@ spdk_nvmf_ctrlr_start_keep_alive_timer(struct spdk_nvmf_ctrlr *ctrlr)
                ctrlr->last_keep_alive_tick = spdk_get_ticks();
 
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Ctrlr add keep alive poller\n");
-               ctrlr->keep_alive_poller = spdk_poller_register(spdk_nvmf_ctrlr_keep_alive_poll, ctrlr,
+               ctrlr->keep_alive_poller = SPDK_POLLER_REGISTER(nvmf_ctrlr_keep_alive_poll, ctrlr,
                                           ctrlr->feat.keep_alive_timer.bits.kato * 1000);
        }
 }
@@ -212,15 +236,7 @@ ctrlr_add_qpair_and_update_rsp(struct spdk_nvmf_qpair *qpair,
 }
 
 static void
-_spdk_nvmf_request_complete(void *ctx)
-{
-       struct spdk_nvmf_request *req = ctx;
-
-       spdk_nvmf_request_complete(req);
-}
-
-static void
-_spdk_nvmf_ctrlr_add_admin_qpair(void *ctx)
+_nvmf_ctrlr_add_admin_qpair(void *ctx)
 {
        struct spdk_nvmf_request *req = ctx;
        struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
@@ -228,37 +244,57 @@ _spdk_nvmf_ctrlr_add_admin_qpair(void *ctx)
        struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
 
        ctrlr->admin_qpair = qpair;
-       spdk_nvmf_ctrlr_start_keep_alive_timer(ctrlr);
+       nvmf_ctrlr_start_keep_alive_timer(ctrlr);
        ctrlr_add_qpair_and_update_rsp(qpair, ctrlr, rsp);
-       spdk_nvmf_request_complete(req);
+       _nvmf_request_complete(req);
 }
 
 static void
-_spdk_nvmf_subsystem_add_ctrlr(void *ctx)
+_nvmf_subsystem_add_ctrlr(void *ctx)
 {
        struct spdk_nvmf_request *req = ctx;
        struct spdk_nvmf_qpair *qpair = req->qpair;
        struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
        struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
 
-       if (spdk_nvmf_subsystem_add_ctrlr(ctrlr->subsys, ctrlr)) {
+       if (nvmf_subsystem_add_ctrlr(ctrlr->subsys, ctrlr)) {
                SPDK_ERRLOG("Unable to add controller to subsystem\n");
                spdk_bit_array_free(&ctrlr->qpair_mask);
                free(ctrlr);
                qpair->ctrlr = NULL;
                rsp->status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR;
-               spdk_thread_send_msg(qpair->group->thread, _spdk_nvmf_request_complete, req);
+               spdk_nvmf_request_complete(req);
                return;
        }
 
-       spdk_thread_send_msg(ctrlr->thread, _spdk_nvmf_ctrlr_add_admin_qpair, req);
+       spdk_thread_send_msg(ctrlr->thread, _nvmf_ctrlr_add_admin_qpair, req);
+}
+
+static void
+nvmf_ctrlr_cdata_init(struct spdk_nvmf_transport *transport, struct spdk_nvmf_subsystem *subsystem,
+                     struct spdk_nvmf_ctrlr_data *cdata)
+{
+       cdata->kas = KAS_DEFAULT_VALUE;
+       cdata->sgls.supported = 1;
+       cdata->sgls.keyed_sgl = 1;
+       cdata->sgls.sgl_offset = 1;
+       cdata->nvmf_specific.ioccsz = sizeof(struct spdk_nvme_cmd) / 16;
+       cdata->nvmf_specific.ioccsz += transport->opts.in_capsule_data_size / 16;
+       cdata->nvmf_specific.iorcsz = sizeof(struct spdk_nvme_cpl) / 16;
+       cdata->nvmf_specific.icdoff = 0; /* offset starts directly after SQE */
+       cdata->nvmf_specific.ctrattr.ctrlr_model = SPDK_NVMF_CTRLR_MODEL_DYNAMIC;
+       cdata->nvmf_specific.msdbd = 1;
+
+       if (transport->ops->cdata_init) {
+               transport->ops->cdata_init(transport, subsystem, cdata);
+       }
 }
 
 static struct spdk_nvmf_ctrlr *
-spdk_nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
-                      struct spdk_nvmf_request *req,
-                      struct spdk_nvmf_fabric_connect_cmd *connect_cmd,
-                      struct spdk_nvmf_fabric_connect_data *connect_data)
+nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
+                 struct spdk_nvmf_request *req,
+                 struct spdk_nvmf_fabric_connect_cmd *connect_cmd,
+                 struct spdk_nvmf_fabric_connect_data *connect_data)
 {
        struct spdk_nvmf_ctrlr  *ctrlr;
        struct spdk_nvmf_transport *transport;
@@ -281,28 +317,25 @@ spdk_nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
                return NULL;
        }
 
+       nvmf_ctrlr_cdata_init(transport, subsystem, &ctrlr->cdata);
+
        /*
-        * KAS: this field indicates the granularity of the Keep Alive Timer in 100ms units
-        * keep-alive timeout in milliseconds
+        * KAS: This field indicates the granularity of the Keep Alive Timer in 100ms units.
+        * If this field is cleared to 0h, then Keep Alive is not supported.
         */
-       ctrlr->feat.keep_alive_timer.bits.kato = spdk_divide_round_up(connect_cmd->kato,
-                       KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS) *
-                       KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS;
+       if (ctrlr->cdata.kas) {
+               ctrlr->feat.keep_alive_timer.bits.kato = spdk_divide_round_up(connect_cmd->kato,
+                               KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS) *
+                               KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS;
+       }
+
        ctrlr->feat.async_event_configuration.bits.ns_attr_notice = 1;
        ctrlr->feat.volatile_write_cache.bits.wce = 1;
 
        if (ctrlr->subsys->subtype == SPDK_NVMF_SUBTYPE_DISCOVERY) {
-               /* Don't accept keep-alive timeout for discovery controllers */
-               if (ctrlr->feat.keep_alive_timer.bits.kato != 0) {
-                       SPDK_ERRLOG("Discovery controller don't accept keep-alive timeout\n");
-                       spdk_bit_array_free(&ctrlr->qpair_mask);
-                       free(ctrlr);
-                       return NULL;
-               }
-
                /*
-                * Discovery controllers use some arbitrary high value in order
-                * to cleanup stale discovery sessions
+                * If keep-alive timeout is not set, discovery controllers use some
+                * arbitrary high value in order to cleanup stale discovery sessions
                 *
                 * From the 1.0a nvme-of spec:
                 * "The Keep Alive command is reserved for
@@ -314,7 +347,9 @@ spdk_nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
                 * actions for Keep Alive Timer expiration".
                 * kato is in millisecond.
                 */
-               ctrlr->feat.keep_alive_timer.bits.kato = NVMF_DISC_KATO_IN_MS;
+               if (ctrlr->feat.keep_alive_timer.bits.kato == 0) {
+                       ctrlr->feat.keep_alive_timer.bits.kato = NVMF_DISC_KATO_IN_MS;
+               }
        }
 
        /* Subtract 1 for admin queue, 1 for 0's based */
@@ -324,6 +359,7 @@ spdk_nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
                        1;
 
        spdk_uuid_copy(&ctrlr->hostid, (struct spdk_uuid *)connect_data->hostid);
+       memcpy(ctrlr->hostnqn, connect_data->hostnqn, sizeof(ctrlr->hostnqn));
 
        ctrlr->vcprop.cap.raw = 0;
        ctrlr->vcprop.cap.bits.cqr = 1; /* NVMe-oF specification required */
@@ -352,19 +388,21 @@ spdk_nvmf_ctrlr_create(struct spdk_nvmf_subsystem *subsystem,
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "cc 0x%x\n", ctrlr->vcprop.cc.raw);
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "csts 0x%x\n", ctrlr->vcprop.csts.raw);
 
+       ctrlr->dif_insert_or_strip = transport->opts.dif_insert_or_strip;
+
        req->qpair->ctrlr = ctrlr;
-       spdk_thread_send_msg(subsystem->thread, _spdk_nvmf_subsystem_add_ctrlr, req);
+       spdk_thread_send_msg(subsystem->thread, _nvmf_subsystem_add_ctrlr, req);
 
        return ctrlr;
 }
 
 static void
-_spdk_nvmf_ctrlr_destruct(void *ctx)
+_nvmf_ctrlr_destruct(void *ctx)
 {
        struct spdk_nvmf_ctrlr *ctrlr = ctx;
        struct spdk_nvmf_reservation_log *log, *log_tmp;
 
-       spdk_nvmf_ctrlr_stop_keep_alive_timer(ctrlr);
+       nvmf_ctrlr_stop_keep_alive_timer(ctrlr);
 
        TAILQ_FOREACH_SAFE(log, &ctrlr->log_head, link, log_tmp) {
                TAILQ_REMOVE(&ctrlr->log_head, log, link);
@@ -374,15 +412,15 @@ _spdk_nvmf_ctrlr_destruct(void *ctx)
 }
 
 void
-spdk_nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr)
+nvmf_ctrlr_destruct(struct spdk_nvmf_ctrlr *ctrlr)
 {
-       spdk_nvmf_subsystem_remove_ctrlr(ctrlr->subsys, ctrlr);
+       nvmf_subsystem_remove_ctrlr(ctrlr->subsys, ctrlr);
 
-       spdk_thread_send_msg(ctrlr->thread, _spdk_nvmf_ctrlr_destruct, ctrlr);
+       spdk_thread_send_msg(ctrlr->thread, _nvmf_ctrlr_destruct, ctrlr);
 }
 
 static void
-spdk_nvmf_ctrlr_add_io_qpair(void *ctx)
+nvmf_ctrlr_add_io_qpair(void *ctx)
 {
        struct spdk_nvmf_request *req = ctx;
        struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
@@ -422,11 +460,11 @@ spdk_nvmf_ctrlr_add_io_qpair(void *ctx)
 
        ctrlr_add_qpair_and_update_rsp(qpair, ctrlr, rsp);
 end:
-       spdk_thread_send_msg(qpair->group->thread, _spdk_nvmf_request_complete, req);
+       spdk_nvmf_request_complete(req);
 }
 
 static void
-_spdk_nvmf_ctrlr_add_io_qpair(void *ctx)
+_nvmf_ctrlr_add_io_qpair(void *ctx)
 {
        struct spdk_nvmf_request *req = ctx;
        struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
@@ -443,39 +481,55 @@ _spdk_nvmf_ctrlr_add_io_qpair(void *ctx)
        /* We already checked this in spdk_nvmf_ctrlr_connect */
        assert(subsystem != NULL);
 
-       ctrlr = spdk_nvmf_subsystem_get_ctrlr(subsystem, data->cntlid);
+       ctrlr = nvmf_subsystem_get_ctrlr(subsystem, data->cntlid);
        if (ctrlr == NULL) {
                SPDK_ERRLOG("Unknown controller ID 0x%x\n", data->cntlid);
                SPDK_NVMF_INVALID_CONNECT_DATA(rsp, cntlid);
-               spdk_thread_send_msg(qpair->group->thread, _spdk_nvmf_request_complete, req);
+               spdk_nvmf_request_complete(req);
                return;
        }
 
        admin_qpair = ctrlr->admin_qpair;
        qpair->ctrlr = ctrlr;
-       spdk_thread_send_msg(admin_qpair->group->thread, spdk_nvmf_ctrlr_add_io_qpair, req);
+       spdk_thread_send_msg(admin_qpair->group->thread, nvmf_ctrlr_add_io_qpair, req);
+}
+
+static bool
+nvmf_qpair_access_allowed(struct spdk_nvmf_qpair *qpair, struct spdk_nvmf_subsystem *subsystem,
+                         const char *hostnqn)
+{
+       struct spdk_nvme_transport_id listen_trid = {};
+
+       if (!spdk_nvmf_subsystem_host_allowed(subsystem, hostnqn)) {
+               SPDK_ERRLOG("Subsystem '%s' does not allow host '%s'\n", subsystem->subnqn, hostnqn);
+               return false;
+       }
+
+       if (spdk_nvmf_qpair_get_listen_trid(qpair, &listen_trid)) {
+               SPDK_ERRLOG("Subsystem '%s' is unable to enforce access control due to an internal error.\n",
+                           subsystem->subnqn);
+               return false;
+       }
+
+       if (!spdk_nvmf_subsystem_listener_allowed(subsystem, &listen_trid)) {
+               SPDK_ERRLOG("Subsystem '%s' does not allow host '%s' to connect at this address.\n",
+                           subsystem->subnqn, hostnqn);
+               return false;
+       }
+
+       return true;
 }
 
 static int
-spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
+_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_fabric_connect_data *data = req->data;
        struct spdk_nvmf_fabric_connect_cmd *cmd = &req->cmd->connect_cmd;
        struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
        struct spdk_nvmf_qpair *qpair = req->qpair;
        struct spdk_nvmf_transport *transport = qpair->transport;
-       struct spdk_nvmf_tgt *tgt = transport->tgt;
        struct spdk_nvmf_ctrlr *ctrlr;
        struct spdk_nvmf_subsystem *subsystem;
-       const char *subnqn, *hostnqn;
-       struct spdk_nvme_transport_id listen_trid = {};
-       void *end;
-
-       if (req->length < sizeof(struct spdk_nvmf_fabric_connect_data)) {
-               SPDK_ERRLOG("Connect command data length 0x%x too small\n", req->length);
-               rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "recfmt 0x%x qid %u sqsize %u\n",
                      cmd->recfmt, cmd->qid, cmd->sqsize);
@@ -490,71 +544,19 @@ spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
                      data->hostid[9],
                      ntohs(*(uint16_t *)&data->hostid[10]),
                      ntohl(*(uint32_t *)&data->hostid[12]));
+       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "  subnqn: \"%s\"\n", data->subnqn);
+       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "  hostnqn: \"%s\"\n", data->hostnqn);
 
-       if (cmd->recfmt != 0) {
-               SPDK_ERRLOG("Connect command unsupported RECFMT %u\n", cmd->recfmt);
-               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-
-       /* Ensure that subnqn is null terminated */
-       end = memchr(data->subnqn, '\0', SPDK_NVMF_NQN_MAX_LEN + 1);
-       if (!end) {
-               SPDK_ERRLOG("Connect SUBNQN is not null terminated\n");
-               SPDK_NVMF_INVALID_CONNECT_DATA(rsp, subnqn);
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-       subnqn = data->subnqn;
-       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "  subnqn: \"%s\"\n", subnqn);
-
-       subsystem = spdk_nvmf_tgt_find_subsystem(tgt, subnqn);
-       if (subsystem == NULL) {
-               SPDK_ERRLOG("Could not find subsystem '%s'\n", subnqn);
+       subsystem = spdk_nvmf_tgt_find_subsystem(transport->tgt, data->subnqn);
+       if (!subsystem) {
                SPDK_NVMF_INVALID_CONNECT_DATA(rsp, subnqn);
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       if ((subsystem->state == SPDK_NVMF_SUBSYSTEM_INACTIVE) ||
-           (subsystem->state == SPDK_NVMF_SUBSYSTEM_PAUSING) ||
-           (subsystem->state == SPDK_NVMF_SUBSYSTEM_PAUSED) ||
-           (subsystem->state == SPDK_NVMF_SUBSYSTEM_DEACTIVATING)) {
-               SPDK_ERRLOG("Subsystem '%s' is not ready\n", subnqn);
-               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-               rsp->status.sc = SPDK_NVMF_FABRIC_SC_CONTROLLER_BUSY;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-
-       /* Ensure that hostnqn is null terminated */
-       end = memchr(data->hostnqn, '\0', SPDK_NVMF_NQN_MAX_LEN + 1);
-       if (!end) {
-               SPDK_ERRLOG("Connect HOSTNQN is not null terminated\n");
-               SPDK_NVMF_INVALID_CONNECT_DATA(rsp, hostnqn);
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-       hostnqn = data->hostnqn;
-       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "  hostnqn: \"%s\"\n", hostnqn);
-
-       if (!spdk_nvmf_subsystem_host_allowed(subsystem, hostnqn)) {
-               SPDK_ERRLOG("Subsystem '%s' does not allow host '%s'\n", subnqn, hostnqn);
-               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_HOST;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-
-       if (spdk_nvmf_qpair_get_listen_trid(qpair, &listen_trid)) {
-               SPDK_ERRLOG("Subsystem '%s' is unable to enforce access control due to an internal error.\n",
-                           subnqn);
-               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_HOST;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-
-       if (!spdk_nvmf_subsystem_listener_allowed(subsystem, &listen_trid)) {
-               SPDK_ERRLOG("Subsystem '%s' does not allow host '%s' to connect at this address.\n", subnqn,
-                           hostnqn);
+       if (cmd->recfmt != 0) {
+               SPDK_ERRLOG("Connect command unsupported RECFMT %u\n", cmd->recfmt);
                rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_HOST;
+               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
@@ -568,7 +570,7 @@ spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       if (spdk_nvmf_qpair_is_admin_queue(qpair)) {
+       if (cmd->qid == 0) {
                if (cmd->sqsize >= transport->opts.max_aq_depth) {
                        SPDK_ERRLOG("Invalid SQSIZE for admin queue %u (min 1, max %u)\n",
                                    cmd->sqsize, transport->opts.max_aq_depth - 1);
@@ -585,10 +587,10 @@ spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
        qpair->sq_head_max = cmd->sqsize;
        qpair->qid = cmd->qid;
 
-       if (spdk_nvmf_transport_qpair_set_sqsize(qpair)) {
-               SPDK_ERRLOG("Can not create SQSIZE %u for qpair=%p\n", cmd->sqsize, qpair);
-               rsp->status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR;
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       if (0 == qpair->qid) {
+               qpair->group->stat.admin_qpairs++;
+       } else {
+               qpair->group->stat.io_qpairs++;
        }
 
        if (cmd->qid == 0) {
@@ -602,20 +604,144 @@ spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
                }
 
                /* Establish a new ctrlr */
-               ctrlr = spdk_nvmf_ctrlr_create(subsystem, req, cmd, data);
+               ctrlr = nvmf_ctrlr_create(subsystem, req, cmd, data);
                if (!ctrlr) {
-                       SPDK_ERRLOG("spdk_nvmf_ctrlr_create() failed\n");
+                       SPDK_ERRLOG("nvmf_ctrlr_create() failed\n");
                        rsp->status.sc = SPDK_NVME_SC_INTERNAL_DEVICE_ERROR;
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                } else {
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
                }
        } else {
-               spdk_thread_send_msg(subsystem->thread, _spdk_nvmf_ctrlr_add_io_qpair, req);
+               spdk_thread_send_msg(subsystem->thread, _nvmf_ctrlr_add_io_qpair, req);
                return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
        }
 }
 
+static inline bool
+nvmf_request_is_fabric_connect(struct spdk_nvmf_request *req)
+{
+       return req->cmd->nvmf_cmd.opcode == SPDK_NVME_OPC_FABRIC &&
+              req->cmd->nvmf_cmd.fctype == SPDK_NVMF_FABRIC_COMMAND_CONNECT;
+}
+
+static struct spdk_nvmf_subsystem_poll_group *
+nvmf_subsystem_pg_from_connect_cmd(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_fabric_connect_data *data;
+       struct spdk_nvmf_subsystem *subsystem;
+       struct spdk_nvmf_tgt *tgt;
+
+       assert(nvmf_request_is_fabric_connect(req));
+       assert(req->qpair->ctrlr == NULL);
+
+       data = req->data;
+       tgt = req->qpair->transport->tgt;
+
+       subsystem = spdk_nvmf_tgt_find_subsystem(tgt, data->subnqn);
+       if (subsystem == NULL) {
+               return NULL;
+       }
+
+       return &req->qpair->group->sgroups[subsystem->id];
+}
+
+int
+spdk_nvmf_ctrlr_connect(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
+       struct spdk_nvmf_qpair *qpair = req->qpair;
+       struct spdk_nvmf_subsystem_poll_group *sgroup;
+       enum spdk_nvmf_request_exec_status status;
+
+       sgroup = nvmf_subsystem_pg_from_connect_cmd(req);
+       if (!sgroup) {
+               SPDK_NVMF_INVALID_CONNECT_DATA(rsp, subnqn);
+               status = SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+               goto out;
+       }
+
+       sgroup->io_outstanding++;
+       TAILQ_INSERT_TAIL(&qpair->outstanding, req, link);
+
+       status = _nvmf_ctrlr_connect(req);
+
+out:
+       if (status == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE) {
+               _nvmf_request_complete(req);
+       }
+
+       return status;
+}
+
+static int
+nvmf_ctrlr_cmd_connect(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_fabric_connect_data *data = req->data;
+       struct spdk_nvmf_fabric_connect_rsp *rsp = &req->rsp->connect_rsp;
+       struct spdk_nvmf_transport *transport = req->qpair->transport;
+       struct spdk_nvmf_subsystem *subsystem;
+
+       if (req->length < sizeof(struct spdk_nvmf_fabric_connect_data)) {
+               SPDK_ERRLOG("Connect command data length 0x%x too small\n", req->length);
+               rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       subsystem = spdk_nvmf_tgt_find_subsystem(transport->tgt, data->subnqn);
+       if (!subsystem) {
+               SPDK_NVMF_INVALID_CONNECT_DATA(rsp, subnqn);
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       if ((subsystem->state == SPDK_NVMF_SUBSYSTEM_INACTIVE) ||
+           (subsystem->state == SPDK_NVMF_SUBSYSTEM_PAUSING) ||
+           (subsystem->state == SPDK_NVMF_SUBSYSTEM_PAUSED) ||
+           (subsystem->state == SPDK_NVMF_SUBSYSTEM_DEACTIVATING)) {
+               SPDK_ERRLOG("Subsystem '%s' is not ready\n", subsystem->subnqn);
+               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
+               rsp->status.sc = SPDK_NVMF_FABRIC_SC_CONTROLLER_BUSY;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       /* Ensure that hostnqn is null terminated */
+       if (!memchr(data->hostnqn, '\0', SPDK_NVMF_NQN_MAX_LEN + 1)) {
+               SPDK_ERRLOG("Connect HOSTNQN is not null terminated\n");
+               SPDK_NVMF_INVALID_CONNECT_DATA(rsp, hostnqn);
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       if (!nvmf_qpair_access_allowed(req->qpair, subsystem, data->hostnqn)) {
+               rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
+               rsp->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_HOST;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       return _nvmf_ctrlr_connect(req);
+}
+
+static void
+nvmf_ctrlr_cc_reset_done(struct spdk_io_channel_iter *i, int status)
+{
+       struct spdk_nvmf_ctrlr *ctrlr = spdk_io_channel_iter_get_ctx(i);
+
+       if (status < 0) {
+               SPDK_ERRLOG("Fail to disconnect io ctrlr qpairs\n");
+               assert(false);
+       }
+
+       /* Only a subset of the registers are cleared out on a reset */
+       ctrlr->vcprop.cc.raw = 0;
+       ctrlr->vcprop.csts.raw = 0;
+
+}
+
+const struct spdk_nvmf_registers *
+spdk_nvmf_ctrlr_get_regs(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return &ctrlr->vcprop;
+}
+
 static uint64_t
 nvmf_prop_get_cap(struct spdk_nvmf_ctrlr *ctrlr)
 {
@@ -635,11 +761,11 @@ nvmf_prop_get_cc(struct spdk_nvmf_ctrlr *ctrlr)
 }
 
 static bool
-nvmf_prop_set_cc(struct spdk_nvmf_ctrlr *ctrlr, uint64_t value)
+nvmf_prop_set_cc(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
 {
        union spdk_nvme_cc_register cc, diff;
 
-       cc.raw = (uint32_t)value;
+       cc.raw = value;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "cur CC: 0x%08x\n", ctrlr->vcprop.cc.raw);
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "new CC: 0x%08x\n", cc.raw);
@@ -656,8 +782,12 @@ nvmf_prop_set_cc(struct spdk_nvmf_ctrlr *ctrlr, uint64_t value)
                        ctrlr->vcprop.cc.bits.en = 1;
                        ctrlr->vcprop.csts.bits.rdy = 1;
                } else {
-                       SPDK_ERRLOG("CC.EN transition from 1 to 0 (reset) not implemented!\n");
-
+                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Property Set CC Disable!\n");
+                       ctrlr->vcprop.cc.bits.en = 0;
+                       spdk_for_each_channel(ctrlr->subsys->tgt,
+                                             nvmf_ctrlr_disconnect_io_qpairs_on_pg,
+                                             ctrlr,
+                                             nvmf_ctrlr_cc_reset_done);
                }
                diff.bits.en = 0;
        }
@@ -695,6 +825,21 @@ nvmf_prop_set_cc(struct spdk_nvmf_ctrlr *ctrlr, uint64_t value)
                diff.bits.iocqes = 0;
        }
 
+       if (diff.bits.ams) {
+               SPDK_ERRLOG("Arbitration Mechanism Selected (AMS) 0x%x not supported!\n", cc.bits.ams);
+               return false;
+       }
+
+       if (diff.bits.mps) {
+               SPDK_ERRLOG("Memory Page Size (MPS) %u KiB not supported!\n", (1 << (2 + cc.bits.mps)));
+               return false;
+       }
+
+       if (diff.bits.css) {
+               SPDK_ERRLOG("I/O Command Set Selected (CSS) 0x%x not supported!\n", cc.bits.css);
+               return false;
+       }
+
        if (diff.raw != 0) {
                SPDK_ERRLOG("Prop Set CC toggled reserved bits 0x%x!\n", diff.raw);
                return false;
@@ -709,38 +854,109 @@ nvmf_prop_get_csts(struct spdk_nvmf_ctrlr *ctrlr)
        return ctrlr->vcprop.csts.raw;
 }
 
+static uint64_t
+nvmf_prop_get_aqa(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return ctrlr->vcprop.aqa.raw;
+}
+
+static bool
+nvmf_prop_set_aqa(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
+{
+       union spdk_nvme_aqa_register aqa;
+
+       aqa.raw = value;
+
+       if (aqa.bits.asqs > ctrlr->vcprop.cap.bits.mqes ||
+           aqa.bits.acqs > ctrlr->vcprop.cap.bits.mqes) {
+               return false;
+       }
+
+       ctrlr->vcprop.aqa.raw = value;
+
+       return true;
+}
+
+static uint64_t
+nvmf_prop_get_asq(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return ctrlr->vcprop.asq;
+}
+
+static bool
+nvmf_prop_set_asq_lower(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
+{
+       ctrlr->vcprop.asq = (ctrlr->vcprop.asq & (0xFFFFFFFFULL << 32ULL)) | value;
+
+       return true;
+}
+
+static bool
+nvmf_prop_set_asq_upper(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
+{
+       ctrlr->vcprop.asq = (ctrlr->vcprop.asq & 0xFFFFFFFFULL) | ((uint64_t)value << 32ULL);
+
+       return true;
+}
+
+static uint64_t
+nvmf_prop_get_acq(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return ctrlr->vcprop.acq;
+}
+
+static bool
+nvmf_prop_set_acq_lower(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
+{
+       ctrlr->vcprop.acq = (ctrlr->vcprop.acq & (0xFFFFFFFFULL << 32ULL)) | value;
+
+       return true;
+}
+
+static bool
+nvmf_prop_set_acq_upper(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value)
+{
+       ctrlr->vcprop.acq = (ctrlr->vcprop.acq & 0xFFFFFFFFULL) | ((uint64_t)value << 32ULL);
+
+       return true;
+}
+
 struct nvmf_prop {
        uint32_t ofst;
        uint8_t size;
        char name[11];
        uint64_t (*get_cb)(struct spdk_nvmf_ctrlr *ctrlr);
-       bool (*set_cb)(struct spdk_nvmf_ctrlr *ctrlr, uint64_t value);
+       bool (*set_cb)(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value);
+       bool (*set_upper_cb)(struct spdk_nvmf_ctrlr *ctrlr, uint32_t value);
 };
 
-#define PROP(field, size, get_cb, set_cb) \
+#define PROP(field, size, get_cb, set_cb, set_upper_cb) \
        { \
                offsetof(struct spdk_nvme_registers, field), \
-               SPDK_NVMF_PROP_SIZE_##size, \
+               size, \
                #field, \
-               get_cb, set_cb \
+               get_cb, set_cb, set_upper_cb \
        }
 
 static const struct nvmf_prop nvmf_props[] = {
-       PROP(cap,  8, nvmf_prop_get_cap,  NULL),
-       PROP(vs,   4, nvmf_prop_get_vs,   NULL),
-       PROP(cc,   4, nvmf_prop_get_cc,   nvmf_prop_set_cc),
-       PROP(csts, 4, nvmf_prop_get_csts, NULL),
+       PROP(cap,  8, nvmf_prop_get_cap,  NULL,                    NULL),
+       PROP(vs,   4, nvmf_prop_get_vs,   NULL,                    NULL),
+       PROP(cc,   4, nvmf_prop_get_cc,   nvmf_prop_set_cc,        NULL),
+       PROP(csts, 4, nvmf_prop_get_csts, NULL,                    NULL),
+       PROP(aqa,  4, nvmf_prop_get_aqa,  nvmf_prop_set_aqa,       NULL),
+       PROP(asq,  8, nvmf_prop_get_asq,  nvmf_prop_set_asq_lower, nvmf_prop_set_asq_upper),
+       PROP(acq,  8, nvmf_prop_get_acq,  nvmf_prop_set_acq_lower, nvmf_prop_set_acq_upper),
 };
 
 static const struct nvmf_prop *
-find_prop(uint32_t ofst)
+find_prop(uint32_t ofst, uint8_t size)
 {
        size_t i;
 
        for (i = 0; i < SPDK_COUNTOF(nvmf_props); i++) {
                const struct nvmf_prop *prop = &nvmf_props[i];
 
-               if (prop->ofst == ofst) {
+               if ((ofst >= prop->ofst) && (ofst + size <= prop->ofst + prop->size)) {
                        return prop;
                }
        }
@@ -749,12 +965,13 @@ find_prop(uint32_t ofst)
 }
 
 static int
-spdk_nvmf_property_get(struct spdk_nvmf_request *req)
+nvmf_property_get(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvmf_fabric_prop_get_cmd *cmd = &req->cmd->prop_get_cmd;
        struct spdk_nvmf_fabric_prop_get_rsp *response = &req->rsp->prop_get_rsp;
        const struct nvmf_prop *prop;
+       uint8_t size;
 
        response->status.sc = 0;
        response->value.u64 = 0;
@@ -762,70 +979,110 @@ spdk_nvmf_property_get(struct spdk_nvmf_request *req)
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "size %d, offset 0x%x\n",
                      cmd->attrib.size, cmd->ofst);
 
-       if (cmd->attrib.size != SPDK_NVMF_PROP_SIZE_4 &&
-           cmd->attrib.size != SPDK_NVMF_PROP_SIZE_8) {
+       switch (cmd->attrib.size) {
+       case SPDK_NVMF_PROP_SIZE_4:
+               size = 4;
+               break;
+       case SPDK_NVMF_PROP_SIZE_8:
+               size = 8;
+               break;
+       default:
                SPDK_ERRLOG("Invalid size value %d\n", cmd->attrib.size);
                response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                response->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       prop = find_prop(cmd->ofst);
+       prop = find_prop(cmd->ofst, size);
        if (prop == NULL || prop->get_cb == NULL) {
-               /* Reserved properties return 0 when read */
-               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
-       }
-
-       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "name: %s\n", prop->name);
-       if (cmd->attrib.size != prop->size) {
-               SPDK_ERRLOG("offset 0x%x size mismatch: cmd %u, prop %u\n",
-                           cmd->ofst, cmd->attrib.size, prop->size);
                response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                response->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
+       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "name: %s\n", prop->name);
+
        response->value.u64 = prop->get_cb(ctrlr);
+
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "response value: 0x%" PRIx64 "\n", response->value.u64);
 
+       if (size != prop->size) {
+               /* The size must be 4 and the prop->size is 8. Figure out which part of the property to read. */
+               assert(size == 4);
+               assert(prop->size == 8);
+
+               if (cmd->ofst == prop->ofst) {
+                       /* Keep bottom 4 bytes only */
+                       response->value.u64 &= 0xFFFFFFFF;
+               } else {
+                       /* Keep top 4 bytes only */
+                       response->value.u64 >>= 32;
+               }
+       }
+
        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
 }
 
 static int
-spdk_nvmf_property_set(struct spdk_nvmf_request *req)
+nvmf_property_set(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvmf_fabric_prop_set_cmd *cmd = &req->cmd->prop_set_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
        const struct nvmf_prop *prop;
        uint64_t value;
+       uint8_t size;
+       bool ret;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "size %d, offset 0x%x, value 0x%" PRIx64 "\n",
                      cmd->attrib.size, cmd->ofst, cmd->value.u64);
 
-       prop = find_prop(cmd->ofst);
-       if (prop == NULL || prop->set_cb == NULL) {
-               SPDK_ERRLOG("Invalid offset 0x%x\n", cmd->ofst);
+       switch (cmd->attrib.size) {
+       case SPDK_NVMF_PROP_SIZE_4:
+               size = 4;
+               break;
+       case SPDK_NVMF_PROP_SIZE_8:
+               size = 8;
+               break;
+       default:
+               SPDK_ERRLOG("Invalid size value %d\n", cmd->attrib.size);
                response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                response->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "name: %s\n", prop->name);
-       if (cmd->attrib.size != prop->size) {
-               SPDK_ERRLOG("offset 0x%x size mismatch: cmd %u, prop %u\n",
-                           cmd->ofst, cmd->attrib.size, prop->size);
+       prop = find_prop(cmd->ofst, size);
+       if (prop == NULL || prop->set_cb == NULL) {
+               SPDK_ERRLOG("Invalid offset 0x%x\n", cmd->ofst);
                response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                response->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
+       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "name: %s\n", prop->name);
+
        value = cmd->value.u64;
-       if (prop->size == SPDK_NVMF_PROP_SIZE_4) {
-               value = (uint32_t)value;
+
+       if (prop->size == 4) {
+               ret = prop->set_cb(ctrlr, (uint32_t)value);
+       } else if (size != prop->size) {
+               /* The size must be 4 and the prop->size is 8. Figure out which part of the property to write. */
+               assert(size == 4);
+               assert(prop->size == 8);
+
+               if (cmd->ofst == prop->ofst) {
+                       ret = prop->set_cb(ctrlr, (uint32_t)value);
+               } else {
+                       ret = prop->set_upper_cb(ctrlr, (uint32_t)value);
+               }
+       } else {
+               ret = prop->set_cb(ctrlr, (uint32_t)value);
+               if (ret) {
+                       ret = prop->set_upper_cb(ctrlr, (uint32_t)(value >> 32));
+               }
        }
 
-       if (!prop->set_cb(ctrlr, value)) {
+       if (!ret) {
                SPDK_ERRLOG("prop set_cb failed\n");
                response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                response->status.sc = SPDK_NVMF_FABRIC_SC_INVALID_PARAM;
@@ -836,7 +1093,7 @@ spdk_nvmf_property_set(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_arbitration(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_arbitration(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -850,19 +1107,17 @@ spdk_nvmf_ctrlr_set_features_arbitration(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_power_management(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_power_management(struct spdk_nvmf_request *req)
 {
-       union spdk_nvme_feat_power_management opts;
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Set Features - Power Management (cdw11 = 0x%0x)\n", cmd->cdw11);
-       opts.raw = cmd->cdw11;
 
        /* Only PS = 0 is allowed, since we report NPSS = 0 */
-       if (opts.bits.ps != 0) {
-               SPDK_ERRLOG("Invalid power state %u\n", opts.bits.ps);
+       if (cmd->cdw11_bits.feat_power_management.bits.ps != 0) {
+               SPDK_ERRLOG("Invalid power state %u\n", cmd->cdw11_bits.feat_power_management.bits.ps);
                rsp->status.sct = SPDK_NVME_SCT_GENERIC;
                rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
@@ -903,16 +1158,14 @@ temp_threshold_opts_valid(const union spdk_nvme_feat_temperature_threshold *opts
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_temperature_threshold(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_temperature_threshold(struct spdk_nvmf_request *req)
 {
-       union spdk_nvme_feat_temperature_threshold opts;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Set Features - Temperature Threshold (cdw11 = 0x%0x)\n", cmd->cdw11);
-       opts.raw = cmd->cdw11;
 
-       if (!temp_threshold_opts_valid(&opts)) {
+       if (!temp_threshold_opts_valid(&cmd->cdw11_bits.feat_temp_threshold)) {
                rsp->status.sct = SPDK_NVME_SCT_GENERIC;
                rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
@@ -923,16 +1176,14 @@ spdk_nvmf_ctrlr_set_features_temperature_threshold(struct spdk_nvmf_request *req
 }
 
 static int
-spdk_nvmf_ctrlr_get_features_temperature_threshold(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_features_temperature_threshold(struct spdk_nvmf_request *req)
 {
-       union spdk_nvme_feat_temperature_threshold opts;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Get Features - Temperature Threshold (cdw11 = 0x%0x)\n", cmd->cdw11);
-       opts.raw = cmd->cdw11;
 
-       if (!temp_threshold_opts_valid(&opts)) {
+       if (!temp_threshold_opts_valid(&cmd->cdw11_bits.feat_temp_threshold)) {
                rsp->status.sct = SPDK_NVME_SCT_GENERIC;
                rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
@@ -945,17 +1196,15 @@ spdk_nvmf_ctrlr_get_features_temperature_threshold(struct spdk_nvmf_request *req
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_error_recovery(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_error_recovery(struct spdk_nvmf_request *req)
 {
-       union spdk_nvme_feat_error_recovery opts;
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Set Features - Error Recovery (cdw11 = 0x%0x)\n", cmd->cdw11);
-       opts.raw = cmd->cdw11;
 
-       if (opts.bits.dulbe) {
+       if (cmd->cdw11_bits.feat_error_recovery.bits.dulbe) {
                /*
                 * Host is not allowed to set this bit, since we don't advertise it in
                 * Identify Namespace.
@@ -973,7 +1222,7 @@ spdk_nvmf_ctrlr_set_features_error_recovery(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_volatile_write_cache(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_volatile_write_cache(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -989,7 +1238,7 @@ spdk_nvmf_ctrlr_set_features_volatile_write_cache(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_write_atomicity(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_write_atomicity(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -1003,7 +1252,7 @@ spdk_nvmf_ctrlr_set_features_write_atomicity(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_host_identifier(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_host_identifier(struct spdk_nvmf_request *req)
 {
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
 
@@ -1013,17 +1262,15 @@ spdk_nvmf_ctrlr_set_features_host_identifier(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_get_features_host_identifier(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_features_host_identifier(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
-       union spdk_nvme_feat_host_identifier opts;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Get Features - Host Identifier\n");
 
-       opts.raw = cmd->cdw11;
-       if (!opts.bits.exhid) {
+       if (!cmd->cdw11_bits.feat_host_identifier.bits.exhid) {
                /* NVMe over Fabrics requires EXHID=1 (128-bit/16-byte host ID) */
                SPDK_ERRLOG("Get Features - Host Identifier with EXHID=0 not allowed\n");
                response->status.sc = SPDK_NVME_SC_INVALID_FIELD;
@@ -1041,7 +1288,7 @@ spdk_nvmf_ctrlr_get_features_host_identifier(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_get_features_reservation_notification_mask(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_features_reservation_notification_mask(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -1056,7 +1303,7 @@ spdk_nvmf_ctrlr_get_features_reservation_notification_mask(struct spdk_nvmf_requ
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       ns = _spdk_nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
        if (ns == NULL) {
                SPDK_ERRLOG("Set Features - Invalid Namespace ID\n");
                rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
@@ -1068,7 +1315,7 @@ spdk_nvmf_ctrlr_get_features_reservation_notification_mask(struct spdk_nvmf_requ
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_reservation_notification_mask(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_reservation_notification_mask(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvmf_subsystem *subsystem = ctrlr->subsys;
@@ -1086,7 +1333,7 @@ spdk_nvmf_ctrlr_set_features_reservation_notification_mask(struct spdk_nvmf_requ
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       ns = _spdk_nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
        if (ns == NULL) {
                SPDK_ERRLOG("Set Features - Invalid Namespace ID\n");
                rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
@@ -1098,7 +1345,7 @@ spdk_nvmf_ctrlr_set_features_reservation_notification_mask(struct spdk_nvmf_requ
 }
 
 static int
-spdk_nvmf_ctrlr_get_features_reservation_persistence(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_features_reservation_persistence(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -1107,7 +1354,7 @@ spdk_nvmf_ctrlr_get_features_reservation_persistence(struct spdk_nvmf_request *r
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Get Features - Reservation Persistence\n");
 
-       ns = _spdk_nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
        /* NSID with 0xffffffffu also included */
        if (ns == NULL) {
                SPDK_ERRLOG("Get Features - Invalid Namespace ID\n");
@@ -1116,8 +1363,7 @@ spdk_nvmf_ctrlr_get_features_reservation_persistence(struct spdk_nvmf_request *r
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       /* TODO: Persistence feature can't support for now */
-       response->cdw0 = 0;
+       response->cdw0 = ns->ptpl_activated;
 
        response->status.sct = SPDK_NVME_SCT_GENERIC;
        response->status.sc = SPDK_NVME_SC_SUCCESS;
@@ -1125,18 +1371,28 @@ spdk_nvmf_ctrlr_get_features_reservation_persistence(struct spdk_nvmf_request *r
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_reservation_persistence(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_reservation_persistence(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
        struct spdk_nvmf_ns *ns;
+       bool ptpl;
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Set Features - Reservation Persistence\n");
 
-       ns = _spdk_nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
-       if (cmd->nsid != 0xffffffffu && ns == NULL) {
-               SPDK_ERRLOG("Set Features - Invalid Namespace ID\n");
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
+       ptpl = cmd->cdw11_bits.feat_rsv_persistence.bits.ptpl;
+
+       if (cmd->nsid != 0xffffffffu && ns && ns->ptpl_file) {
+               ns->ptpl_activated = ptpl;
+       } else if (cmd->nsid == 0xffffffffu) {
+               for (ns = spdk_nvmf_subsystem_get_first_ns(ctrlr->subsys); ns && ns->ptpl_file;
+                    ns = spdk_nvmf_subsystem_get_next_ns(ctrlr->subsys, ns)) {
+                       ns->ptpl_activated = ptpl;
+               }
+       } else {
+               SPDK_ERRLOG("Set Features - Invalid Namespace ID or Reservation Configuration\n");
                response->status.sct = SPDK_NVME_SCT_GENERIC;
                response->status.sc = SPDK_NVME_SC_INVALID_FIELD;
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
@@ -1144,12 +1400,12 @@ spdk_nvmf_ctrlr_set_features_reservation_persistence(struct spdk_nvmf_request *r
 
        /* TODO: Feature not changeable for now */
        response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-       response->status.sc = SPDK_NVME_SC_FEATURE_NOT_CHANGEABLE;
+       response->status.sc = SPDK_NVME_SC_FEATURE_ID_NOT_SAVEABLE;
        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_keep_alive_timer(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_keep_alive_timer(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -1161,14 +1417,15 @@ spdk_nvmf_ctrlr_set_features_keep_alive_timer(struct spdk_nvmf_request *req)
         * if attempts to disable keep alive by setting kato to 0h
         * a status value of keep alive invalid shall be returned
         */
-       if (cmd->cdw11 == 0) {
+       if (cmd->cdw11_bits.feat_keep_alive_timer.bits.kato == 0) {
                rsp->status.sc = SPDK_NVME_SC_KEEP_ALIVE_INVALID;
-       } else if (cmd->cdw11 < MIN_KEEP_ALIVE_TIMEOUT_IN_MS) {
+       } else if (cmd->cdw11_bits.feat_keep_alive_timer.bits.kato < MIN_KEEP_ALIVE_TIMEOUT_IN_MS) {
                ctrlr->feat.keep_alive_timer.bits.kato = MIN_KEEP_ALIVE_TIMEOUT_IN_MS;
        } else {
                /* round up to milliseconds */
-               ctrlr->feat.keep_alive_timer.bits.kato = spdk_divide_round_up(cmd->cdw11,
-                               KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS) *
+               ctrlr->feat.keep_alive_timer.bits.kato = spdk_divide_round_up(
+                                       cmd->cdw11_bits.feat_keep_alive_timer.bits.kato,
+                                       KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS) *
                                KAS_DEFAULT_VALUE * KAS_TIME_UNIT_IN_MS;
        }
 
@@ -1176,11 +1433,11 @@ spdk_nvmf_ctrlr_set_features_keep_alive_timer(struct spdk_nvmf_request *req)
         * if change the keep alive timeout value successfully
         * update the keep alive poller.
         */
-       if (cmd->cdw11 != 0) {
+       if (cmd->cdw11_bits.feat_keep_alive_timer.bits.kato != 0) {
                if (ctrlr->keep_alive_poller != NULL) {
                        spdk_poller_unregister(&ctrlr->keep_alive_poller);
                }
-               ctrlr->keep_alive_poller = spdk_poller_register(spdk_nvmf_ctrlr_keep_alive_poll, ctrlr,
+               ctrlr->keep_alive_poller = SPDK_POLLER_REGISTER(nvmf_ctrlr_keep_alive_poll, ctrlr,
                                           ctrlr->feat.keep_alive_timer.bits.kato * 1000);
        }
 
@@ -1191,7 +1448,7 @@ spdk_nvmf_ctrlr_set_features_keep_alive_timer(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_number_of_queues(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_number_of_queues(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
@@ -1217,7 +1474,7 @@ spdk_nvmf_ctrlr_set_features_number_of_queues(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features_async_event_configuration(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features_async_event_configuration(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
@@ -1230,7 +1487,7 @@ spdk_nvmf_ctrlr_set_features_async_event_configuration(struct spdk_nvmf_request
 }
 
 static int
-spdk_nvmf_ctrlr_async_event_request(struct spdk_nvmf_request *req)
+nvmf_ctrlr_async_event_request(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
@@ -1238,8 +1495,8 @@ spdk_nvmf_ctrlr_async_event_request(struct spdk_nvmf_request *req)
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Async Event Request\n");
 
-       /* Only one asynchronous event is supported for now */
-       if (ctrlr->aer_req != NULL) {
+       /* Four asynchronous events are supported for now */
+       if (ctrlr->nr_aer_reqs >= NVMF_MAX_ASYNC_EVENTS) {
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "AERL exceeded\n");
                rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
                rsp->status.sc = SPDK_NVME_SC_ASYNC_EVENT_REQUEST_LIMIT_EXCEEDED;
@@ -1262,14 +1519,15 @@ spdk_nvmf_ctrlr_async_event_request(struct spdk_nvmf_request *req)
 
        /* AER cmd is an exception */
        sgroup = &req->qpair->group->sgroups[ctrlr->subsys->id];
+       assert(sgroup != NULL);
        sgroup->io_outstanding--;
 
-       ctrlr->aer_req = req;
+       ctrlr->aer_req[ctrlr->nr_aer_reqs++] = req;
        return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
 }
 
 static void
-spdk_nvmf_get_firmware_slot_log_page(void *buffer, uint64_t offset, uint32_t length)
+nvmf_get_firmware_slot_log_page(void *buffer, uint64_t offset, uint32_t length)
 {
        struct spdk_nvme_firmware_page fw_page;
        size_t copy_len;
@@ -1288,7 +1546,7 @@ spdk_nvmf_get_firmware_slot_log_page(void *buffer, uint64_t offset, uint32_t len
 }
 
 void
-spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid)
+nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid)
 {
        uint16_t max_changes = SPDK_COUNTOF(ctrlr->changed_ns_list.ns_list);
        uint16_t i;
@@ -1313,13 +1571,11 @@ spdk_nvmf_ctrlr_ns_changed(struct spdk_nvmf_ctrlr *ctrlr, uint32_t nsid)
                        ctrlr->changed_ns_list.ns_list[ctrlr->changed_ns_list_count++] = nsid;
                }
        }
-
-       spdk_nvmf_ctrlr_async_event_ns_notice(ctrlr);
 }
 
 static void
-spdk_nvmf_get_changed_ns_list_log_page(struct spdk_nvmf_ctrlr *ctrlr,
-                                      void *buffer, uint64_t offset, uint32_t length)
+nvmf_get_changed_ns_list_log_page(struct spdk_nvmf_ctrlr *ctrlr,
+                                 void *buffer, uint64_t offset, uint32_t length)
 {
        size_t copy_length;
 
@@ -1365,12 +1621,14 @@ static const struct spdk_nvme_cmds_and_effect_log_page g_cmds_and_effect_log_pag
                [SPDK_NVME_OPC_WRITE_ZEROES]            = {1, 1, 0, 0, 0, 0, 0, 0},
                /* DATASET MANAGEMENT */
                [SPDK_NVME_OPC_DATASET_MANAGEMENT]      = {1, 1, 0, 0, 0, 0, 0, 0},
+               /* COMPARE */
+               [SPDK_NVME_OPC_COMPARE]                 = {1, 0, 0, 0, 0, 0, 0, 0},
        },
 };
 
 static void
-spdk_nvmf_get_cmds_and_effects_log_page(void *buffer,
-                                       uint64_t offset, uint32_t length)
+nvmf_get_cmds_and_effects_log_page(void *buffer,
+                                  uint64_t offset, uint32_t length)
 {
        uint32_t page_size = sizeof(struct spdk_nvme_cmds_and_effect_log_page);
        size_t copy_len = 0;
@@ -1388,7 +1646,7 @@ spdk_nvmf_get_cmds_and_effects_log_page(void *buffer,
 }
 
 static void
-spdk_nvmf_get_reservation_notification_log_page(struct spdk_nvmf_ctrlr *ctrlr,
+nvmf_get_reservation_notification_log_page(struct spdk_nvmf_ctrlr *ctrlr,
                void *data, uint64_t offset, uint32_t length)
 {
        uint32_t unit_log_len, avail_log_len, next_pos, copy_len;
@@ -1430,7 +1688,7 @@ spdk_nvmf_get_reservation_notification_log_page(struct spdk_nvmf_ctrlr *ctrlr,
 }
 
 static int
-spdk_nvmf_ctrlr_get_log_page(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_log_page(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvmf_subsystem *subsystem = ctrlr->subsys;
@@ -1455,8 +1713,8 @@ spdk_nvmf_ctrlr_get_log_page(struct spdk_nvmf_request *req)
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       numdl = (cmd->cdw10 >> 16) & 0xFFFFu;
-       numdu = (cmd->cdw11) & 0xFFFFu;
+       numdl = cmd->cdw10_bits.get_log_page.numdl;
+       numdu = cmd->cdw11_bits.get_log_page.numdu;
        len = ((numdu << 16) + numdl + (uint64_t)1) * 4;
        if (len > req->length) {
                SPDK_ERRLOG("Get log page: len (%" PRIu64 ") > buf size (%u)\n",
@@ -1466,14 +1724,15 @@ spdk_nvmf_ctrlr_get_log_page(struct spdk_nvmf_request *req)
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       lid = cmd->cdw10 & 0xFF;
+       lid = cmd->cdw10_bits.get_log_page.lid;
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Get log page: LID=0x%02X offset=0x%" PRIx64 " len=0x%" PRIx64 "\n",
                      lid, offset, len);
 
        if (subsystem->subtype == SPDK_NVMF_SUBTYPE_DISCOVERY) {
                switch (lid) {
                case SPDK_NVME_LOG_DISCOVERY:
-                       spdk_nvmf_get_discovery_log_page(subsystem->tgt, req->iov, req->iovcnt, offset, len);
+                       nvmf_get_discovery_log_page(subsystem->tgt, ctrlr->hostnqn, req->iov, req->iovcnt, offset,
+                                                   len);
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                default:
                        goto invalid_log_page;
@@ -1485,16 +1744,16 @@ spdk_nvmf_ctrlr_get_log_page(struct spdk_nvmf_request *req)
                        /* TODO: actually fill out log page data */
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                case SPDK_NVME_LOG_FIRMWARE_SLOT:
-                       spdk_nvmf_get_firmware_slot_log_page(req->data, offset, len);
+                       nvmf_get_firmware_slot_log_page(req->data, offset, len);
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                case SPDK_NVME_LOG_COMMAND_EFFECTS_LOG:
-                       spdk_nvmf_get_cmds_and_effects_log_page(req->data, offset, len);
+                       nvmf_get_cmds_and_effects_log_page(req->data, offset, len);
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                case SPDK_NVME_LOG_CHANGED_NS_LIST:
-                       spdk_nvmf_get_changed_ns_list_log_page(ctrlr, req->data, offset, len);
+                       nvmf_get_changed_ns_list_log_page(ctrlr, req->data, offset, len);
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                case SPDK_NVME_LOG_RESERVATION_NOTIFICATION:
-                       spdk_nvmf_get_reservation_notification_log_page(ctrlr, req->data, offset, len);
+                       nvmf_get_reservation_notification_log_page(ctrlr, req->data, offset, len);
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                default:
                        goto invalid_log_page;
@@ -1508,7 +1767,7 @@ invalid_log_page:
        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
 }
 
-static int
+int
 spdk_nvmf_ctrlr_identify_ns(struct spdk_nvmf_ctrlr *ctrlr,
                            struct spdk_nvme_cmd *cmd,
                            struct spdk_nvme_cpl *rsp,
@@ -1525,11 +1784,11 @@ spdk_nvmf_ctrlr_identify_ns(struct spdk_nvmf_ctrlr *ctrlr,
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       ns = _spdk_nvmf_subsystem_get_ns(subsystem, cmd->nsid);
+       ns = _nvmf_subsystem_get_ns(subsystem, cmd->nsid);
        if (ns == NULL || ns->bdev == NULL) {
                /*
                 * Inactive namespaces should return a zero filled data structure.
-                * The data buffer is already zeroed by spdk_nvmf_ctrlr_process_admin_cmd(),
+                * The data buffer is already zeroed by nvmf_ctrlr_process_admin_cmd(),
                 * so we can just return early here.
                 */
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Identify Namespace for inactive NSID %u\n", cmd->nsid);
@@ -1538,7 +1797,7 @@ spdk_nvmf_ctrlr_identify_ns(struct spdk_nvmf_ctrlr *ctrlr,
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       spdk_nvmf_bdev_ctrlr_identify_ns(ns, nsdata);
+       nvmf_bdev_ctrlr_identify_ns(ns, nsdata, ctrlr->dif_insert_or_strip);
 
        /* Due to bug in the Linux kernel NVMe driver we have to set noiob no larger than mdts */
        max_num_blocks = ctrlr->admin_qpair->transport->opts.max_io_size /
@@ -1550,7 +1809,32 @@ spdk_nvmf_ctrlr_identify_ns(struct spdk_nvmf_ctrlr *ctrlr,
        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
 }
 
-static int
+static void
+nvmf_ctrlr_populate_oacs(struct spdk_nvmf_ctrlr *ctrlr,
+                        struct spdk_nvme_ctrlr_data *cdata)
+{
+       cdata->oacs.virtualization_management =
+               g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_VIRTUALIZATION_MANAGEMENT].hdlr != NULL;
+       cdata->oacs.nvme_mi = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_NVME_MI_SEND].hdlr != NULL
+                             && g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_NVME_MI_RECEIVE].hdlr != NULL;
+       cdata->oacs.directives = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_DIRECTIVE_SEND].hdlr != NULL
+                                && g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_DIRECTIVE_RECEIVE].hdlr != NULL;
+       cdata->oacs.device_self_test =
+               g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_DEVICE_SELF_TEST].hdlr != NULL;
+       cdata->oacs.ns_manage = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_NS_MANAGEMENT].hdlr != NULL
+                               && g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_NS_ATTACHMENT].hdlr != NULL;
+       cdata->oacs.firmware = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD].hdlr !=
+                              NULL
+                              && g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_FIRMWARE_COMMIT].hdlr != NULL;
+       cdata->oacs.format =
+               g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_FORMAT_NVM].hdlr != NULL;
+       cdata->oacs.security = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_SECURITY_SEND].hdlr != NULL
+                              && g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_SECURITY_RECEIVE].hdlr != NULL;
+       cdata->oacs.get_lba_status = g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_GET_LBA_STATUS].hdlr !=
+                                    NULL;
+}
+
+int
 spdk_nvmf_ctrlr_identify_ctrlr(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_ctrlr_data *cdata)
 {
        struct spdk_nvmf_subsystem *subsystem = ctrlr->subsys;
@@ -1564,12 +1848,13 @@ spdk_nvmf_ctrlr_identify_ctrlr(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_c
        cdata->mdts = spdk_u32log2(transport->opts.max_io_size / 4096);
        cdata->cntlid = ctrlr->cntlid;
        cdata->ver = ctrlr->vcprop.vs;
+       cdata->aerl = NVMF_MAX_ASYNC_EVENTS - 1;
        cdata->lpa.edlp = 1;
        cdata->elpe = 127;
        cdata->maxcmd = transport->opts.max_queue_depth;
-       cdata->sgls.supported = 1;
-       cdata->sgls.keyed_sgl = 1;
-       cdata->sgls.sgl_offset = 1;
+       cdata->sgls = ctrlr->cdata.sgls;
+       cdata->fuses.compare_and_write = 1;
+       cdata->acwu = 1;
        spdk_strcpy_pad(cdata->subnqn, subsystem->subnqn, sizeof(cdata->subnqn), '\0');
 
        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "ctrlr data: maxcmd 0x%x\n", cdata->maxcmd);
@@ -1581,13 +1866,15 @@ spdk_nvmf_ctrlr_identify_ctrlr(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_c
        if (subsystem->subtype == SPDK_NVMF_SUBTYPE_NVME) {
                spdk_strcpy_pad(cdata->mn, spdk_nvmf_subsystem_get_mn(subsystem), sizeof(cdata->mn), ' ');
                spdk_strcpy_pad(cdata->sn, spdk_nvmf_subsystem_get_sn(subsystem), sizeof(cdata->sn), ' ');
-               cdata->kas = KAS_DEFAULT_VALUE;
+               cdata->kas = ctrlr->cdata.kas;
 
                cdata->rab = 6;
                cdata->cmic.multi_port = 1;
                cdata->cmic.multi_host = 1;
                cdata->oaes.ns_attribute_notices = 1;
                cdata->ctratt.host_id_exhid_supported = 1;
+               /* TODO: Concurrent execution of multiple abort commands. */
+               cdata->acl = 0;
                cdata->aerl = 0;
                cdata->frmw.slot1_ro = 1;
                cdata->frmw.num_slots = 1;
@@ -1602,24 +1889,14 @@ spdk_nvmf_ctrlr_identify_ctrlr(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_c
                cdata->vwc.present = 1;
                cdata->vwc.flush_broadcast = SPDK_NVME_FLUSH_BROADCAST_NOT_SUPPORTED;
 
-               cdata->nvmf_specific.ioccsz = sizeof(struct spdk_nvme_cmd) / 16;
-               cdata->nvmf_specific.iorcsz = sizeof(struct spdk_nvme_cpl) / 16;
-               cdata->nvmf_specific.icdoff = 0; /* offset starts directly after SQE */
-               cdata->nvmf_specific.ctrattr.ctrlr_model = SPDK_NVMF_CTRLR_MODEL_DYNAMIC;
-               /* The RDMA transport supports up to SPDK_NVMF_MAX_SGL_ENTRIES descriptors. */
-               if (transport->ops->type == SPDK_NVME_TRANSPORT_RDMA) {
-                       cdata->nvmf_specific.msdbd = SPDK_NVMF_MAX_SGL_ENTRIES;
-               } else {
-                       cdata->nvmf_specific.msdbd = 1;
-               }
-
-               /* TODO: this should be set by the transport */
-               cdata->nvmf_specific.ioccsz += transport->opts.in_capsule_data_size / 16;
+               cdata->nvmf_specific = ctrlr->cdata.nvmf_specific;
 
-               cdata->oncs.dsm = spdk_nvmf_ctrlr_dsm_supported(ctrlr);
-               cdata->oncs.write_zeroes = spdk_nvmf_ctrlr_write_zeroes_supported(ctrlr);
+               cdata->oncs.dsm = nvmf_ctrlr_dsm_supported(ctrlr);
+               cdata->oncs.write_zeroes = nvmf_ctrlr_write_zeroes_supported(ctrlr);
                cdata->oncs.reservations = 1;
 
+               nvmf_ctrlr_populate_oacs(ctrlr, cdata);
+
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "ext ctrlr data: ioccsz 0x%x\n",
                              cdata->nvmf_specific.ioccsz);
                SPDK_DEBUGLOG(SPDK_LOG_NVMF, "ext ctrlr data: iorcsz 0x%x\n",
@@ -1636,10 +1913,10 @@ spdk_nvmf_ctrlr_identify_ctrlr(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_c
 }
 
 static int
-spdk_nvmf_ctrlr_identify_active_ns_list(struct spdk_nvmf_subsystem *subsystem,
-                                       struct spdk_nvme_cmd *cmd,
-                                       struct spdk_nvme_cpl *rsp,
-                                       struct spdk_nvme_ns_list *ns_list)
+nvmf_ctrlr_identify_active_ns_list(struct spdk_nvmf_subsystem *subsystem,
+                                  struct spdk_nvme_cmd *cmd,
+                                  struct spdk_nvme_cpl *rsp,
+                                  struct spdk_nvme_ns_list *ns_list)
 {
        struct spdk_nvmf_ns *ns;
        uint32_t count = 0;
@@ -1694,7 +1971,7 @@ _add_ns_id_desc(void **buf_ptr, size_t *buf_remain,
 }
 
 static int
-spdk_nvmf_ctrlr_identify_ns_id_descriptor_list(
+nvmf_ctrlr_identify_ns_id_descriptor_list(
        struct spdk_nvmf_subsystem *subsystem,
        struct spdk_nvme_cmd *cmd,
        struct spdk_nvme_cpl *rsp,
@@ -1704,7 +1981,7 @@ spdk_nvmf_ctrlr_identify_ns_id_descriptor_list(
        size_t buf_remain = id_desc_list_size;
        void *buf_ptr = id_desc_list;
 
-       ns = _spdk_nvmf_subsystem_get_ns(subsystem, cmd->nsid);
+       ns = _nvmf_subsystem_get_ns(subsystem, cmd->nsid);
        if (ns == NULL || ns->bdev == NULL) {
                rsp->status.sct = SPDK_NVME_SCT_GENERIC;
                rsp->status.sc = SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT;
@@ -1724,7 +2001,7 @@ spdk_nvmf_ctrlr_identify_ns_id_descriptor_list(
 
        /*
         * The list is automatically 0-terminated because controller to host buffers in
-        * admin commands always get zeroed in spdk_nvmf_ctrlr_process_admin_cmd().
+        * admin commands always get zeroed in nvmf_ctrlr_process_admin_cmd().
         */
 
 #undef ADD_ID_DESC
@@ -1733,7 +2010,7 @@ spdk_nvmf_ctrlr_identify_ns_id_descriptor_list(
 }
 
 static int
-spdk_nvmf_ctrlr_identify(struct spdk_nvmf_request *req)
+nvmf_ctrlr_identify(struct spdk_nvmf_request *req)
 {
        uint8_t cns;
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
@@ -1748,7 +2025,7 @@ spdk_nvmf_ctrlr_identify(struct spdk_nvmf_request *req)
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       cns = cmd->cdw10 & 0xFF;
+       cns = cmd->cdw10_bits.identify.cns;
 
        if (subsystem->subtype == SPDK_NVMF_SUBTYPE_DISCOVERY &&
            cns != SPDK_NVME_IDENTIFY_CTRLR) {
@@ -1762,9 +2039,9 @@ spdk_nvmf_ctrlr_identify(struct spdk_nvmf_request *req)
        case SPDK_NVME_IDENTIFY_CTRLR:
                return spdk_nvmf_ctrlr_identify_ctrlr(ctrlr, req->data);
        case SPDK_NVME_IDENTIFY_ACTIVE_NS_LIST:
-               return spdk_nvmf_ctrlr_identify_active_ns_list(subsystem, cmd, rsp, req->data);
+               return nvmf_ctrlr_identify_active_ns_list(subsystem, cmd, rsp, req->data);
        case SPDK_NVME_IDENTIFY_NS_ID_DESCRIPTOR_LIST:
-               return spdk_nvmf_ctrlr_identify_ns_id_descriptor_list(subsystem, cmd, rsp, req->data, req->length);
+               return nvmf_ctrlr_identify_ns_id_descriptor_list(subsystem, cmd, rsp, req->data, req->length);
        default:
                goto invalid_cns;
        }
@@ -1776,74 +2053,90 @@ invalid_cns:
        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
 }
 
-
-static struct spdk_nvmf_request *
-spdk_nvmf_qpair_abort(struct spdk_nvmf_qpair *qpair, uint16_t cid)
+static bool
+nvmf_qpair_abort_aer(struct spdk_nvmf_qpair *qpair, uint16_t cid)
 {
        struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
        struct spdk_nvmf_request *req;
+       int i;
+
+       if (!nvmf_qpair_is_admin_queue(qpair)) {
+               return false;
+       }
 
-       if (spdk_nvmf_qpair_is_admin_queue(qpair)) {
-               if (ctrlr->aer_req && ctrlr->aer_req->cmd->nvme_cmd.cid == cid) {
+       for (i = 0; i < ctrlr->nr_aer_reqs; i++) {
+               if (ctrlr->aer_req[i]->cmd->nvme_cmd.cid == cid) {
                        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Aborting AER request\n");
-                       req = ctrlr->aer_req;
-                       ctrlr->aer_req = NULL;
-                       return req;
+                       req = ctrlr->aer_req[i];
+                       ctrlr->aer_req[i] = NULL;
+                       ctrlr->nr_aer_reqs--;
+
+                       /* Move the last req to the aborting position for making aer_reqs
+                        * in continuous
+                        */
+                       if (i < ctrlr->nr_aer_reqs) {
+                               ctrlr->aer_req[i] = ctrlr->aer_req[ctrlr->nr_aer_reqs];
+                               ctrlr->aer_req[ctrlr->nr_aer_reqs] = NULL;
+                       }
+
+                       req->rsp->nvme_cpl.status.sct = SPDK_NVME_SCT_GENERIC;
+                       req->rsp->nvme_cpl.status.sc = SPDK_NVME_SC_ABORTED_BY_REQUEST;
+                       _nvmf_request_complete(req);
+                       return true;
                }
        }
 
-       /* TODO: track list of outstanding requests in qpair? */
-       return NULL;
+       return false;
+}
+
+static void
+nvmf_qpair_abort_request(struct spdk_nvmf_qpair *qpair, struct spdk_nvmf_request *req)
+{
+       uint16_t cid = req->cmd->nvme_cmd.cdw10_bits.abort.cid;
+
+       if (nvmf_qpair_abort_aer(qpair, cid)) {
+               SPDK_DEBUGLOG(SPDK_LOG_NVMF, "abort ctrlr=%p sqid=%u cid=%u successful\n",
+                             qpair->ctrlr, qpair->qid, cid);
+               req->rsp->nvme_cpl.cdw0 &= ~1U; /* Command successfully aborted */
+
+               spdk_nvmf_request_complete(req);
+               return;
+       }
+
+       nvmf_transport_qpair_abort_request(qpair, req);
 }
 
 static void
-spdk_nvmf_ctrlr_abort_done(struct spdk_io_channel_iter *i, int status)
+nvmf_ctrlr_abort_done(struct spdk_io_channel_iter *i, int status)
 {
        struct spdk_nvmf_request *req = spdk_io_channel_iter_get_ctx(i);
 
-       spdk_nvmf_request_complete(req);
+       if (status == 0) {
+               /* There was no qpair whose ID matches SQID of the abort command.
+                * Hence call _nvmf_request_complete() here.
+                */
+               _nvmf_request_complete(req);
+       }
 }
 
 static void
-spdk_nvmf_ctrlr_abort_on_pg(struct spdk_io_channel_iter *i)
+nvmf_ctrlr_abort_on_pg(struct spdk_io_channel_iter *i)
 {
        struct spdk_nvmf_request *req = spdk_io_channel_iter_get_ctx(i);
        struct spdk_io_channel *ch = spdk_io_channel_iter_get_channel(i);
        struct spdk_nvmf_poll_group *group = spdk_io_channel_get_ctx(ch);
-       struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
-       struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
-       uint16_t sqid = cmd->cdw10 & 0xFFFFu;
+       uint16_t sqid = req->cmd->nvme_cmd.cdw10_bits.abort.sqid;
        struct spdk_nvmf_qpair *qpair;
 
        TAILQ_FOREACH(qpair, &group->qpairs, link) {
                if (qpair->ctrlr == req->qpair->ctrlr && qpair->qid == sqid) {
-                       struct spdk_nvmf_request *req_to_abort;
-                       uint16_t cid = cmd->cdw10 >> 16;
-
                        /* Found the qpair */
 
-                       req_to_abort = spdk_nvmf_qpair_abort(qpair, cid);
-                       if (req_to_abort == NULL) {
-                               SPDK_DEBUGLOG(SPDK_LOG_NVMF, "cid %u not found\n", cid);
-                               rsp->status.sct = SPDK_NVME_SCT_GENERIC;
-                               rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
-                               spdk_for_each_channel_continue(i, -EINVAL);
-                               return;
-                       }
-
-                       /* Complete the request with aborted status */
-                       req_to_abort->rsp->nvme_cpl.status.sct = SPDK_NVME_SCT_GENERIC;
-                       req_to_abort->rsp->nvme_cpl.status.sc = SPDK_NVME_SC_ABORTED_BY_REQUEST;
-                       spdk_nvmf_request_complete(req_to_abort);
+                       nvmf_qpair_abort_request(qpair, req);
 
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "abort ctrlr=%p req=%p sqid=%u cid=%u successful\n",
-                                     qpair->ctrlr, req_to_abort, sqid, cid);
-                       rsp->cdw0 = 0; /* Command successfully aborted */
-                       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
-                       rsp->status.sc = SPDK_NVME_SC_SUCCESS;
                        /* Return -1 for the status so the iteration across threads stops. */
                        spdk_for_each_channel_continue(i, -1);
-
+                       return;
                }
        }
 
@@ -1851,24 +2144,49 @@ spdk_nvmf_ctrlr_abort_on_pg(struct spdk_io_channel_iter *i)
 }
 
 static int
-spdk_nvmf_ctrlr_abort(struct spdk_nvmf_request *req)
+nvmf_ctrlr_abort(struct spdk_nvmf_request *req)
 {
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
 
-       rsp->cdw0 = 1; /* Command not aborted */
-       rsp->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
-       rsp->status.sc = SPDK_NVME_SC_INVALID_QUEUE_IDENTIFIER;
+       rsp->cdw0 = 1U; /* Command not aborted */
+       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+       rsp->status.sc = SPDK_NVME_SC_SUCCESS;
 
        /* Send a message to each poll group, searching for this ctrlr, sqid, and command. */
        spdk_for_each_channel(req->qpair->ctrlr->subsys->tgt,
-                             spdk_nvmf_ctrlr_abort_on_pg,
+                             nvmf_ctrlr_abort_on_pg,
                              req,
-                             spdk_nvmf_ctrlr_abort_done
+                             nvmf_ctrlr_abort_done
                             );
 
        return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
 }
 
+int
+nvmf_ctrlr_abort_request(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_request *req_to_abort = req->req_to_abort;
+       struct spdk_bdev *bdev;
+       struct spdk_bdev_desc *desc;
+       struct spdk_io_channel *ch;
+       int rc;
+
+       assert(req_to_abort != NULL);
+
+       if (g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_ABORT].hdlr &&
+           nvmf_qpair_is_admin_queue(req_to_abort->qpair)) {
+               return g_nvmf_custom_admin_cmd_hdlrs[SPDK_NVME_OPC_ABORT].hdlr(req);
+       }
+
+       rc = spdk_nvmf_request_get_bdev(req_to_abort->cmd->nvme_cmd.nsid, req_to_abort,
+                                       &bdev, &desc, &ch);
+       if (rc != 0) {
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       return spdk_nvmf_bdev_ctrlr_abort_cmd(bdev, desc, ch, req, req_to_abort);
+}
+
 static int
 get_features_generic(struct spdk_nvmf_request *req, uint32_t cdw0)
 {
@@ -1879,21 +2197,21 @@ get_features_generic(struct spdk_nvmf_request *req, uint32_t cdw0)
 }
 
 static int
-spdk_nvmf_ctrlr_get_features(struct spdk_nvmf_request *req)
+nvmf_ctrlr_get_features(struct spdk_nvmf_request *req)
 {
        uint8_t feature;
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
 
-       feature = cmd->cdw10 & 0xff; /* mask out the FID value */
+       feature = cmd->cdw10_bits.get_features.fid;
        switch (feature) {
        case SPDK_NVME_FEAT_ARBITRATION:
                return get_features_generic(req, ctrlr->feat.arbitration.raw);
        case SPDK_NVME_FEAT_POWER_MANAGEMENT:
                return get_features_generic(req, ctrlr->feat.power_management.raw);
        case SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD:
-               return spdk_nvmf_ctrlr_get_features_temperature_threshold(req);
+               return nvmf_ctrlr_get_features_temperature_threshold(req);
        case SPDK_NVME_FEAT_ERROR_RECOVERY:
                return get_features_generic(req, ctrlr->feat.error_recovery.raw);
        case SPDK_NVME_FEAT_VOLATILE_WRITE_CACHE:
@@ -1907,11 +2225,11 @@ spdk_nvmf_ctrlr_get_features(struct spdk_nvmf_request *req)
        case SPDK_NVME_FEAT_KEEP_ALIVE_TIMER:
                return get_features_generic(req, ctrlr->feat.keep_alive_timer.raw);
        case SPDK_NVME_FEAT_HOST_IDENTIFIER:
-               return spdk_nvmf_ctrlr_get_features_host_identifier(req);
+               return nvmf_ctrlr_get_features_host_identifier(req);
        case SPDK_NVME_FEAT_HOST_RESERVE_MASK:
-               return spdk_nvmf_ctrlr_get_features_reservation_notification_mask(req);
+               return nvmf_ctrlr_get_features_reservation_notification_mask(req);
        case SPDK_NVME_FEAT_HOST_RESERVE_PERSIST:
-               return spdk_nvmf_ctrlr_get_features_reservation_persistence(req);
+               return nvmf_ctrlr_get_features_reservation_persistence(req);
        default:
                SPDK_ERRLOG("Get Features command with unsupported feature ID 0x%02x\n", feature);
                response->status.sc = SPDK_NVME_SC_INVALID_FIELD;
@@ -1920,38 +2238,49 @@ spdk_nvmf_ctrlr_get_features(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_set_features(struct spdk_nvmf_request *req)
+nvmf_ctrlr_set_features(struct spdk_nvmf_request *req)
 {
-       uint8_t feature;
+       uint8_t feature, save;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
 
-       feature = cmd->cdw10 & 0xff; /* mask out the FID value */
+       /*
+        * Features are not saveable by the controller as indicated by
+        * ONCS field of the Identify Controller data.
+        * */
+       save = cmd->cdw10_bits.set_features.sv;
+       if (save) {
+               response->status.sc = SPDK_NVME_SC_FEATURE_ID_NOT_SAVEABLE;
+               response->status.sct = SPDK_NVME_SCT_COMMAND_SPECIFIC;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       feature = cmd->cdw10_bits.set_features.fid;
        switch (feature) {
        case SPDK_NVME_FEAT_ARBITRATION:
-               return spdk_nvmf_ctrlr_set_features_arbitration(req);
+               return nvmf_ctrlr_set_features_arbitration(req);
        case SPDK_NVME_FEAT_POWER_MANAGEMENT:
-               return spdk_nvmf_ctrlr_set_features_power_management(req);
+               return nvmf_ctrlr_set_features_power_management(req);
        case SPDK_NVME_FEAT_TEMPERATURE_THRESHOLD:
-               return spdk_nvmf_ctrlr_set_features_temperature_threshold(req);
+               return nvmf_ctrlr_set_features_temperature_threshold(req);
        case SPDK_NVME_FEAT_ERROR_RECOVERY:
-               return spdk_nvmf_ctrlr_set_features_error_recovery(req);
+               return nvmf_ctrlr_set_features_error_recovery(req);
        case SPDK_NVME_FEAT_VOLATILE_WRITE_CACHE:
-               return spdk_nvmf_ctrlr_set_features_volatile_write_cache(req);
+               return nvmf_ctrlr_set_features_volatile_write_cache(req);
        case SPDK_NVME_FEAT_NUMBER_OF_QUEUES:
-               return spdk_nvmf_ctrlr_set_features_number_of_queues(req);
+               return nvmf_ctrlr_set_features_number_of_queues(req);
        case SPDK_NVME_FEAT_WRITE_ATOMICITY:
-               return spdk_nvmf_ctrlr_set_features_write_atomicity(req);
+               return nvmf_ctrlr_set_features_write_atomicity(req);
        case SPDK_NVME_FEAT_ASYNC_EVENT_CONFIGURATION:
-               return spdk_nvmf_ctrlr_set_features_async_event_configuration(req);
+               return nvmf_ctrlr_set_features_async_event_configuration(req);
        case SPDK_NVME_FEAT_KEEP_ALIVE_TIMER:
-               return spdk_nvmf_ctrlr_set_features_keep_alive_timer(req);
+               return nvmf_ctrlr_set_features_keep_alive_timer(req);
        case SPDK_NVME_FEAT_HOST_IDENTIFIER:
-               return spdk_nvmf_ctrlr_set_features_host_identifier(req);
+               return nvmf_ctrlr_set_features_host_identifier(req);
        case SPDK_NVME_FEAT_HOST_RESERVE_MASK:
-               return spdk_nvmf_ctrlr_set_features_reservation_notification_mask(req);
+               return nvmf_ctrlr_set_features_reservation_notification_mask(req);
        case SPDK_NVME_FEAT_HOST_RESERVE_PERSIST:
-               return spdk_nvmf_ctrlr_set_features_reservation_persistence(req);
+               return nvmf_ctrlr_set_features_reservation_persistence(req);
        default:
                SPDK_ERRLOG("Set Features command with unsupported feature ID 0x%02x\n", feature);
                response->status.sc = SPDK_NVME_SC_INVALID_FIELD;
@@ -1960,7 +2289,7 @@ spdk_nvmf_ctrlr_set_features(struct spdk_nvmf_request *req)
 }
 
 static int
-spdk_nvmf_ctrlr_keep_alive(struct spdk_nvmf_request *req)
+nvmf_ctrlr_keep_alive(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
 
@@ -1979,11 +2308,12 @@ spdk_nvmf_ctrlr_keep_alive(struct spdk_nvmf_request *req)
 }
 
 int
-spdk_nvmf_ctrlr_process_admin_cmd(struct spdk_nvmf_request *req)
+nvmf_ctrlr_process_admin_cmd(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
        struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
        struct spdk_nvme_cpl *response = &req->rsp->nvme_cpl;
+       int rc;
 
        if (ctrlr == NULL) {
                SPDK_ERRLOG("Admin command sent before CONNECT\n");
@@ -2004,31 +2334,41 @@ spdk_nvmf_ctrlr_process_admin_cmd(struct spdk_nvmf_request *req)
        }
 
        if (ctrlr->subsys->subtype == SPDK_NVMF_SUBTYPE_DISCOVERY) {
-               /* Discovery controllers only support Get Log Page and Identify */
+               /* Discovery controllers only support Get Log Page, Identify and Keep Alive. */
                switch (cmd->opc) {
                case SPDK_NVME_OPC_IDENTIFY:
                case SPDK_NVME_OPC_GET_LOG_PAGE:
+               case SPDK_NVME_OPC_KEEP_ALIVE:
                        break;
                default:
                        goto invalid_opcode;
                }
        }
 
+       /* Call a custom adm cmd handler if set. Aborts are handled in a different path (see nvmf_passthru_admin_cmd) */
+       if (g_nvmf_custom_admin_cmd_hdlrs[cmd->opc].hdlr && cmd->opc != SPDK_NVME_OPC_ABORT) {
+               rc = g_nvmf_custom_admin_cmd_hdlrs[cmd->opc].hdlr(req);
+               if (rc >= SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE) {
+                       /* The handler took care of this commmand */
+                       return rc;
+               }
+       }
+
        switch (cmd->opc) {
        case SPDK_NVME_OPC_GET_LOG_PAGE:
-               return spdk_nvmf_ctrlr_get_log_page(req);
+               return nvmf_ctrlr_get_log_page(req);
        case SPDK_NVME_OPC_IDENTIFY:
-               return spdk_nvmf_ctrlr_identify(req);
+               return nvmf_ctrlr_identify(req);
        case SPDK_NVME_OPC_ABORT:
-               return spdk_nvmf_ctrlr_abort(req);
+               return nvmf_ctrlr_abort(req);
        case SPDK_NVME_OPC_GET_FEATURES:
-               return spdk_nvmf_ctrlr_get_features(req);
+               return nvmf_ctrlr_get_features(req);
        case SPDK_NVME_OPC_SET_FEATURES:
-               return spdk_nvmf_ctrlr_set_features(req);
+               return nvmf_ctrlr_set_features(req);
        case SPDK_NVME_OPC_ASYNC_EVENT_REQUEST:
-               return spdk_nvmf_ctrlr_async_event_request(req);
+               return nvmf_ctrlr_async_event_request(req);
        case SPDK_NVME_OPC_KEEP_ALIVE:
-               return spdk_nvmf_ctrlr_keep_alive(req);
+               return nvmf_ctrlr_keep_alive(req);
 
        case SPDK_NVME_OPC_CREATE_IO_SQ:
        case SPDK_NVME_OPC_CREATE_IO_CQ:
@@ -2049,7 +2389,7 @@ invalid_opcode:
 }
 
 int
-spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
+nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_qpair *qpair = req->qpair;
        struct spdk_nvmf_capsule_cmd *cap_hdr;
@@ -2059,7 +2399,7 @@ spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
        if (qpair->ctrlr == NULL) {
                /* No ctrlr established yet; the only valid command is Connect */
                if (cap_hdr->fctype == SPDK_NVMF_FABRIC_COMMAND_CONNECT) {
-                       return spdk_nvmf_ctrlr_connect(req);
+                       return nvmf_ctrlr_cmd_connect(req);
                } else {
                        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "Got fctype 0x%x, expected Connect\n",
                                      cap_hdr->fctype);
@@ -2067,16 +2407,16 @@ spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
                        req->rsp->nvme_cpl.status.sc = SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR;
                        return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
                }
-       } else if (spdk_nvmf_qpair_is_admin_queue(qpair)) {
+       } else if (nvmf_qpair_is_admin_queue(qpair)) {
                /*
                 * Controller session is established, and this is an admin queue.
                 * Disallow Connect and allow other fabrics commands.
                 */
                switch (cap_hdr->fctype) {
                case SPDK_NVMF_FABRIC_COMMAND_PROPERTY_SET:
-                       return spdk_nvmf_property_set(req);
+                       return nvmf_property_set(req);
                case SPDK_NVMF_FABRIC_COMMAND_PROPERTY_GET:
-                       return spdk_nvmf_property_get(req);
+                       return nvmf_property_get(req);
                default:
                        SPDK_DEBUGLOG(SPDK_LOG_NVMF, "unknown fctype 0x%02x\n",
                                      cap_hdr->fctype);
@@ -2094,11 +2434,29 @@ spdk_nvmf_ctrlr_process_fabrics_cmd(struct spdk_nvmf_request *req)
        }
 }
 
-int
-spdk_nvmf_ctrlr_async_event_ns_notice(struct spdk_nvmf_ctrlr *ctrlr)
+static inline int
+nvmf_ctrlr_async_event_notification(struct spdk_nvmf_ctrlr *ctrlr,
+                                   union spdk_nvme_async_event_completion *event)
 {
        struct spdk_nvmf_request *req;
        struct spdk_nvme_cpl *rsp;
+
+       assert(ctrlr->nr_aer_reqs > 0);
+
+       req = ctrlr->aer_req[--ctrlr->nr_aer_reqs];
+       rsp = &req->rsp->nvme_cpl;
+
+       rsp->cdw0 = event->raw;
+
+       _nvmf_request_complete(req);
+       ctrlr->aer_req[ctrlr->nr_aer_reqs] = NULL;
+
+       return 0;
+}
+
+int
+nvmf_ctrlr_async_event_ns_notice(struct spdk_nvmf_ctrlr *ctrlr)
+{
        union spdk_nvme_async_event_completion event = {0};
 
        /* Users may disable the event notification */
@@ -2114,7 +2472,7 @@ spdk_nvmf_ctrlr_async_event_ns_notice(struct spdk_nvmf_ctrlr *ctrlr)
         * if an AER is later submitted, this event can be sent as a
         * response.
         */
-       if (!ctrlr->aer_req) {
+       if (ctrlr->nr_aer_reqs == 0) {
                if (ctrlr->notice_event.bits.async_event_type ==
                    SPDK_NVME_ASYNC_EVENT_TYPE_NOTICE) {
                        return 0;
@@ -2124,22 +2482,12 @@ spdk_nvmf_ctrlr_async_event_ns_notice(struct spdk_nvmf_ctrlr *ctrlr)
                return 0;
        }
 
-       req = ctrlr->aer_req;
-       rsp = &req->rsp->nvme_cpl;
-
-       rsp->cdw0 = event.raw;
-
-       spdk_nvmf_request_complete(req);
-       ctrlr->aer_req = NULL;
-
-       return 0;
+       return nvmf_ctrlr_async_event_notification(ctrlr, &event);
 }
 
 void
-spdk_nvmf_ctrlr_async_event_reservation_notification(struct spdk_nvmf_ctrlr *ctrlr)
+nvmf_ctrlr_async_event_reservation_notification(struct spdk_nvmf_ctrlr *ctrlr)
 {
-       struct spdk_nvmf_request *req;
-       struct spdk_nvme_cpl *rsp;
        union spdk_nvme_async_event_completion event = {0};
 
        if (!ctrlr->num_avail_log_pages) {
@@ -2153,7 +2501,7 @@ spdk_nvmf_ctrlr_async_event_reservation_notification(struct spdk_nvmf_ctrlr *ctr
         * if an AER is later submitted, this event can be sent as a
         * response.
         */
-       if (!ctrlr->aer_req) {
+       if (ctrlr->nr_aer_reqs == 0) {
                if (ctrlr->reservation_event.bits.async_event_type ==
                    SPDK_NVME_ASYNC_EVENT_TYPE_IO) {
                        return;
@@ -2163,39 +2511,44 @@ spdk_nvmf_ctrlr_async_event_reservation_notification(struct spdk_nvmf_ctrlr *ctr
                return;
        }
 
-       req = ctrlr->aer_req;
-       rsp = &req->rsp->nvme_cpl;
-
-       rsp->cdw0 = event.raw;
-
-       spdk_nvmf_request_complete(req);
-       ctrlr->aer_req = NULL;
+       nvmf_ctrlr_async_event_notification(ctrlr, &event);
 }
 
 void
-spdk_nvmf_qpair_free_aer(struct spdk_nvmf_qpair *qpair)
+nvmf_qpair_free_aer(struct spdk_nvmf_qpair *qpair)
 {
        struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
+       int i;
 
-       if (!spdk_nvmf_qpair_is_admin_queue(qpair)) {
+       if (!nvmf_qpair_is_admin_queue(qpair)) {
                return;
        }
 
-       if (ctrlr->aer_req != NULL) {
-               spdk_nvmf_request_free(ctrlr->aer_req);
-               ctrlr->aer_req = NULL;
+       for (i = 0; i < ctrlr->nr_aer_reqs; i++) {
+               spdk_nvmf_request_free(ctrlr->aer_req[i]);
+               ctrlr->aer_req[i] = NULL;
        }
+
+       ctrlr->nr_aer_reqs = 0;
 }
 
 void
-spdk_nvmf_ctrlr_abort_aer(struct spdk_nvmf_ctrlr *ctrlr)
+nvmf_ctrlr_abort_aer(struct spdk_nvmf_ctrlr *ctrlr)
 {
-       if (!ctrlr->aer_req) {
-               return;
+       struct spdk_nvmf_request *req;
+       int i;
+
+       for (i = 0; i < ctrlr->nr_aer_reqs; i++) {
+               req = ctrlr->aer_req[i];
+
+               req->rsp->nvme_cpl.status.sct = SPDK_NVME_SCT_GENERIC;
+               req->rsp->nvme_cpl.status.sc = SPDK_NVME_SC_ABORTED_BY_REQUEST;
+               _nvmf_request_complete(req);
+
+               ctrlr->aer_req[i] = NULL;
        }
 
-       spdk_nvmf_request_complete(ctrlr->aer_req);
-       ctrlr->aer_req = NULL;
+       ctrlr->nr_aer_reqs = 0;
 }
 
 static void
@@ -2219,13 +2572,13 @@ _nvmf_ctrlr_add_reservation_log(void *ctx)
        log->log.num_avail_log_pages = ctrlr->num_avail_log_pages++;
        TAILQ_INSERT_TAIL(&ctrlr->log_head, log, link);
 
-       spdk_nvmf_ctrlr_async_event_reservation_notification(ctrlr);
+       nvmf_ctrlr_async_event_reservation_notification(ctrlr);
 }
 
 void
-spdk_nvmf_ctrlr_reservation_notice_log(struct spdk_nvmf_ctrlr *ctrlr,
-                                      struct spdk_nvmf_ns *ns,
-                                      enum spdk_nvme_reservation_notification_log_page_type type)
+nvmf_ctrlr_reservation_notice_log(struct spdk_nvmf_ctrlr *ctrlr,
+                                 struct spdk_nvmf_ns *ns,
+                                 enum spdk_nvme_reservation_notification_log_page_type type)
 {
        struct spdk_nvmf_reservation_log *log;
 
@@ -2318,7 +2671,6 @@ nvmf_ns_reservation_request_check(struct spdk_nvmf_subsystem_pg_ns_info *ns_info
                if ((rtype == SPDK_NVME_RESERVE_EXCLUSIVE_ACCESS_REG_ONLY ||
                     rtype == SPDK_NVME_RESERVE_EXCLUSIVE_ACCESS_ALL_REGS) && !is_registrant) {
                        status = SPDK_NVME_SC_RESERVATION_CONFLICT;
-                       goto exit;
                }
                break;
        case SPDK_NVME_OPC_FLUSH:
@@ -2333,20 +2685,21 @@ nvmf_ns_reservation_request_check(struct spdk_nvmf_subsystem_pg_ns_info *ns_info
                }
                if (!is_registrant) {
                        status = SPDK_NVME_SC_RESERVATION_CONFLICT;
-                       goto exit;
                }
                break;
        case SPDK_NVME_OPC_RESERVATION_ACQUIRE:
-       case SPDK_NVME_OPC_RESERVATION_RELEASE:
-               racqa = cmd->cdw10 & 0x7u;
-               if (cmd->opc == SPDK_NVME_OPC_RESERVATION_ACQUIRE &&
-                   racqa == SPDK_NVME_RESERVE_ACQUIRE) {
+               racqa = cmd->cdw10_bits.resv_acquire.racqa;
+               if (racqa == SPDK_NVME_RESERVE_ACQUIRE) {
                        status = SPDK_NVME_SC_RESERVATION_CONFLICT;
                        goto exit;
                }
                if (!is_registrant) {
                        status = SPDK_NVME_SC_RESERVATION_CONFLICT;
-                       goto exit;
+               }
+               break;
+       case SPDK_NVME_OPC_RESERVATION_RELEASE:
+               if (!is_registrant) {
+                       status = SPDK_NVME_SC_RESERVATION_CONFLICT;
                }
                break;
        default:
@@ -2363,8 +2716,88 @@ exit:
        return 0;
 }
 
+static int
+nvmf_ctrlr_process_io_fused_cmd(struct spdk_nvmf_request *req, struct spdk_bdev *bdev,
+                               struct spdk_bdev_desc *desc, struct spdk_io_channel *ch)
+{
+       struct spdk_nvme_cmd *cmd = &req->cmd->nvme_cmd;
+       struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
+       struct spdk_nvmf_request *first_fused_req = req->qpair->first_fused_req;
+       int rc;
+
+       if (cmd->fuse == SPDK_NVME_CMD_FUSE_FIRST) {
+               /* first fused operation (should be compare) */
+               if (first_fused_req != NULL) {
+                       struct spdk_nvme_cpl *fused_response = &first_fused_req->rsp->nvme_cpl;
+
+                       SPDK_ERRLOG("Wrong sequence of fused operations\n");
+
+                       /* abort req->qpair->first_fused_request and continue with new fused command */
+                       fused_response->status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED;
+                       fused_response->status.sct = SPDK_NVME_SCT_GENERIC;
+                       _nvmf_request_complete(first_fused_req);
+               } else if (cmd->opc != SPDK_NVME_OPC_COMPARE) {
+                       SPDK_ERRLOG("Wrong op code of fused operations\n");
+                       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+                       rsp->status.sc = SPDK_NVME_SC_INVALID_OPCODE;
+                       return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+               }
+
+               req->qpair->first_fused_req = req;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
+       } else if (cmd->fuse == SPDK_NVME_CMD_FUSE_SECOND) {
+               /* second fused operation (should be write) */
+               if (first_fused_req == NULL) {
+                       SPDK_ERRLOG("Wrong sequence of fused operations\n");
+                       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+                       rsp->status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED;
+                       return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+               } else if (cmd->opc != SPDK_NVME_OPC_WRITE) {
+                       struct spdk_nvme_cpl *fused_response = &first_fused_req->rsp->nvme_cpl;
+
+                       SPDK_ERRLOG("Wrong op code of fused operations\n");
+
+                       /* abort req->qpair->first_fused_request and fail current command */
+                       fused_response->status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED;
+                       fused_response->status.sct = SPDK_NVME_SCT_GENERIC;
+                       _nvmf_request_complete(first_fused_req);
+
+                       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+                       rsp->status.sc = SPDK_NVME_SC_INVALID_OPCODE;
+                       req->qpair->first_fused_req = NULL;
+                       return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+               }
+
+               /* save request of first command to generate response later */
+               req->first_fused_req = first_fused_req;
+               req->qpair->first_fused_req = NULL;
+       } else {
+               SPDK_ERRLOG("Invalid fused command fuse field.\n");
+               rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+               rsp->status.sc = SPDK_NVME_SC_INVALID_FIELD;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+
+       rc = nvmf_bdev_ctrlr_compare_and_write_cmd(bdev, desc, ch, req->first_fused_req, req);
+
+       if (rc == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE) {
+               if (spdk_nvme_cpl_is_error(rsp)) {
+                       struct spdk_nvme_cpl *fused_response = &first_fused_req->rsp->nvme_cpl;
+
+                       fused_response->status = rsp->status;
+                       rsp->status.sct = SPDK_NVME_SCT_GENERIC;
+                       rsp->status.sc = SPDK_NVME_SC_ABORTED_FAILED_FUSED;
+                       /* Complete first of fused commands. Second will be completed by upper layer */
+                       _nvmf_request_complete(first_fused_req);
+                       req->first_fused_req = NULL;
+               }
+       }
+
+       return rc;
+}
+
 int
-spdk_nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req)
+nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req)
 {
        uint32_t nsid;
        struct spdk_nvmf_ns *ns;
@@ -2395,7 +2828,7 @@ spdk_nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req)
                return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
        }
 
-       ns = _spdk_nvmf_subsystem_get_ns(ctrlr->subsys, nsid);
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, nsid);
        if (ns == NULL || ns->bdev == NULL) {
                SPDK_ERRLOG("Unsuccessful query for nsid %u\n", cmd->nsid);
                response->status.sc = SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT;
@@ -2415,30 +2848,47 @@ spdk_nvmf_ctrlr_process_io_cmd(struct spdk_nvmf_request *req)
        bdev = ns->bdev;
        desc = ns->desc;
        ch = ns_info->channel;
+
+       if (spdk_unlikely(cmd->fuse & SPDK_NVME_CMD_FUSE_MASK)) {
+               return nvmf_ctrlr_process_io_fused_cmd(req, bdev, desc, ch);
+       } else if (spdk_unlikely(req->qpair->first_fused_req != NULL)) {
+               struct spdk_nvme_cpl *fused_response = &req->qpair->first_fused_req->rsp->nvme_cpl;
+
+               SPDK_ERRLOG("Expected second of fused commands - failing first of fused commands\n");
+
+               /* abort req->qpair->first_fused_request and continue with new command */
+               fused_response->status.sc = SPDK_NVME_SC_ABORTED_MISSING_FUSED;
+               fused_response->status.sct = SPDK_NVME_SCT_GENERIC;
+               _nvmf_request_complete(req->qpair->first_fused_req);
+               req->qpair->first_fused_req = NULL;
+       }
+
        switch (cmd->opc) {
        case SPDK_NVME_OPC_READ:
-               return spdk_nvmf_bdev_ctrlr_read_cmd(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_read_cmd(bdev, desc, ch, req);
        case SPDK_NVME_OPC_WRITE:
-               return spdk_nvmf_bdev_ctrlr_write_cmd(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_write_cmd(bdev, desc, ch, req);
+       case SPDK_NVME_OPC_COMPARE:
+               return nvmf_bdev_ctrlr_compare_cmd(bdev, desc, ch, req);
        case SPDK_NVME_OPC_WRITE_ZEROES:
-               return spdk_nvmf_bdev_ctrlr_write_zeroes_cmd(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_write_zeroes_cmd(bdev, desc, ch, req);
        case SPDK_NVME_OPC_FLUSH:
-               return spdk_nvmf_bdev_ctrlr_flush_cmd(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_flush_cmd(bdev, desc, ch, req);
        case SPDK_NVME_OPC_DATASET_MANAGEMENT:
-               return spdk_nvmf_bdev_ctrlr_dsm_cmd(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_dsm_cmd(bdev, desc, ch, req);
        case SPDK_NVME_OPC_RESERVATION_REGISTER:
        case SPDK_NVME_OPC_RESERVATION_ACQUIRE:
        case SPDK_NVME_OPC_RESERVATION_RELEASE:
        case SPDK_NVME_OPC_RESERVATION_REPORT:
-               spdk_thread_send_msg(ctrlr->subsys->thread, spdk_nvmf_ns_reservation_request, req);
+               spdk_thread_send_msg(ctrlr->subsys->thread, nvmf_ns_reservation_request, req);
                return SPDK_NVMF_REQUEST_EXEC_STATUS_ASYNCHRONOUS;
        default:
-               return spdk_nvmf_bdev_ctrlr_nvme_passthru_io(bdev, desc, ch, req);
+               return nvmf_bdev_ctrlr_nvme_passthru_io(bdev, desc, ch, req);
        }
 }
 
 static void
-spdk_nvmf_qpair_request_cleanup(struct spdk_nvmf_qpair *qpair)
+nvmf_qpair_request_cleanup(struct spdk_nvmf_qpair *qpair)
 {
        if (qpair->state == SPDK_NVMF_QPAIR_DEACTIVATING) {
                assert(qpair->state_cb != NULL);
@@ -2446,8 +2896,6 @@ spdk_nvmf_qpair_request_cleanup(struct spdk_nvmf_qpair *qpair)
                if (TAILQ_EMPTY(&qpair->outstanding)) {
                        qpair->state_cb(qpair->state_cb_arg, 0);
                }
-       } else {
-               assert(qpair->state == SPDK_NVMF_QPAIR_ACTIVE);
        }
 }
 
@@ -2457,21 +2905,23 @@ spdk_nvmf_request_free(struct spdk_nvmf_request *req)
        struct spdk_nvmf_qpair *qpair = req->qpair;
 
        TAILQ_REMOVE(&qpair->outstanding, req, link);
-       if (spdk_nvmf_transport_req_free(req)) {
+       if (nvmf_transport_req_free(req)) {
                SPDK_ERRLOG("Unable to free transport level request resources.\n");
        }
 
-       spdk_nvmf_qpair_request_cleanup(qpair);
+       nvmf_qpair_request_cleanup(qpair);
 
        return 0;
 }
 
-int
-spdk_nvmf_request_complete(struct spdk_nvmf_request *req)
+static void
+_nvmf_request_complete(void *ctx)
 {
+       struct spdk_nvmf_request *req = ctx;
        struct spdk_nvme_cpl *rsp = &req->rsp->nvme_cpl;
        struct spdk_nvmf_qpair *qpair;
        struct spdk_nvmf_subsystem_poll_group *sgroup = NULL;
+       bool is_aer = false;
 
        rsp->sqid = 0;
        rsp->status.p = 0;
@@ -2480,22 +2930,23 @@ spdk_nvmf_request_complete(struct spdk_nvmf_request *req)
        qpair = req->qpair;
        if (qpair->ctrlr) {
                sgroup = &qpair->group->sgroups[qpair->ctrlr->subsys->id];
+               assert(sgroup != NULL);
+               is_aer = req->cmd->nvme_cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST;
+       } else if (spdk_unlikely(nvmf_request_is_fabric_connect(req))) {
+               sgroup = nvmf_subsystem_pg_from_connect_cmd(req);
        }
 
-       SPDK_DEBUGLOG(SPDK_LOG_NVMF,
-                     "cpl: cid=%u cdw0=0x%08x rsvd1=%u status=0x%04x\n",
-                     rsp->cid, rsp->cdw0, rsp->rsvd1,
-                     *(uint16_t *)&rsp->status);
+       if (SPDK_DEBUGLOG_FLAG_ENABLED("nvmf")) {
+               spdk_nvme_print_completion(qpair->qid, rsp);
+       }
 
        TAILQ_REMOVE(&qpair->outstanding, req, link);
-       if (spdk_nvmf_transport_req_complete(req)) {
+       if (nvmf_transport_req_complete(req)) {
                SPDK_ERRLOG("Transport request completion error!\n");
        }
 
-       /* AER cmd and fabric connect are exceptions */
-       if (sgroup != NULL && qpair->ctrlr->aer_req != req &&
-           !(req->cmd->nvmf_cmd.opcode == SPDK_NVME_OPC_FABRIC &&
-             req->cmd->nvmf_cmd.fctype == SPDK_NVMF_FABRIC_COMMAND_CONNECT)) {
+       /* AER cmd is an exception */
+       if (sgroup && !is_aer) {
                assert(sgroup->io_outstanding > 0);
                sgroup->io_outstanding--;
                if (sgroup->state == SPDK_NVMF_SUBSYSTEM_PAUSING &&
@@ -2505,66 +2956,84 @@ spdk_nvmf_request_complete(struct spdk_nvmf_request *req)
                }
        }
 
-       spdk_nvmf_qpair_request_cleanup(qpair);
+       nvmf_qpair_request_cleanup(qpair);
+}
+
+int
+spdk_nvmf_request_complete(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_qpair *qpair = req->qpair;
+
+       if (spdk_likely(qpair->group->thread == spdk_get_thread())) {
+               _nvmf_request_complete(req);
+       } else {
+               spdk_thread_send_msg(qpair->group->thread,
+                                    _nvmf_request_complete, req);
+       }
 
        return 0;
 }
 
 static void
-nvmf_trace_command(union nvmf_h2c_msg *h2c_msg, bool is_admin_queue)
-{
-       struct spdk_nvmf_capsule_cmd *cap_hdr = &h2c_msg->nvmf_cmd;
-       struct spdk_nvme_cmd *cmd = &h2c_msg->nvme_cmd;
-       struct spdk_nvme_sgl_descriptor *sgl = &cmd->dptr.sgl1;
-       uint8_t opc;
-
-       if (cmd->opc == SPDK_NVME_OPC_FABRIC) {
-               opc = cap_hdr->fctype;
-               SPDK_DEBUGLOG(SPDK_LOG_NVMF, "%s Fabrics cmd: fctype 0x%02x cid %u\n",
-                             is_admin_queue ? "Admin" : "I/O",
-                             cap_hdr->fctype, cap_hdr->cid);
+_nvmf_request_exec(struct spdk_nvmf_request *req,
+                  struct spdk_nvmf_subsystem_poll_group *sgroup)
+{
+       struct spdk_nvmf_qpair *qpair = req->qpair;
+       enum spdk_nvmf_request_exec_status status;
+
+       if (SPDK_DEBUGLOG_FLAG_ENABLED("nvmf")) {
+               spdk_nvme_print_command(qpair->qid, &req->cmd->nvme_cmd);
+       }
+
+       if (sgroup) {
+               sgroup->io_outstanding++;
+       }
+
+       /* Place the request on the outstanding list so we can keep track of it */
+       TAILQ_INSERT_TAIL(&qpair->outstanding, req, link);
+
+       if (spdk_unlikely(req->cmd->nvmf_cmd.opcode == SPDK_NVME_OPC_FABRIC)) {
+               status = nvmf_ctrlr_process_fabrics_cmd(req);
+       } else if (spdk_unlikely(nvmf_qpair_is_admin_queue(qpair))) {
+               status = nvmf_ctrlr_process_admin_cmd(req);
        } else {
-               opc = cmd->opc;
-               SPDK_DEBUGLOG(SPDK_LOG_NVMF, "%s cmd: opc 0x%02x fuse %u cid %u nsid %u cdw10 0x%08x\n",
-                             is_admin_queue ? "Admin" : "I/O",
-                             cmd->opc, cmd->fuse, cmd->cid, cmd->nsid, cmd->cdw10);
-               if (cmd->mptr) {
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "mptr 0x%" PRIx64 "\n", cmd->mptr);
-               }
-               if (cmd->psdt != SPDK_NVME_PSDT_SGL_MPTR_CONTIG &&
-                   cmd->psdt != SPDK_NVME_PSDT_SGL_MPTR_SGL) {
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "psdt %u\n", cmd->psdt);
-               }
+               status = nvmf_ctrlr_process_io_cmd(req);
        }
 
-       if (spdk_nvme_opc_get_data_transfer(opc) != SPDK_NVME_DATA_NONE) {
-               if (sgl->generic.type == SPDK_NVME_SGL_TYPE_KEYED_DATA_BLOCK) {
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF,
-                                     "SGL: Keyed%s: addr 0x%" PRIx64 " key 0x%x len 0x%x\n",
-                                     sgl->generic.subtype == SPDK_NVME_SGL_SUBTYPE_INVALIDATE_KEY ? " (Inv)" : "",
-                                     sgl->address, sgl->keyed.key, sgl->keyed.length);
-               } else if (sgl->generic.type == SPDK_NVME_SGL_TYPE_DATA_BLOCK) {
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "SGL: Data block: %s 0x%" PRIx64 " len 0x%x\n",
-                                     sgl->unkeyed.subtype == SPDK_NVME_SGL_SUBTYPE_OFFSET ? "offs" : "addr",
-                                     sgl->address, sgl->unkeyed.length);
-               } else {
-                       SPDK_DEBUGLOG(SPDK_LOG_NVMF, "SGL type 0x%x subtype 0x%x\n",
-                                     sgl->generic.type, sgl->generic.subtype);
-               }
+       if (status == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE) {
+               _nvmf_request_complete(req);
        }
 }
 
 void
-spdk_nvmf_request_exec(struct spdk_nvmf_request *req)
+spdk_nvmf_request_exec_fabrics(struct spdk_nvmf_request *req)
 {
        struct spdk_nvmf_qpair *qpair = req->qpair;
-       spdk_nvmf_request_exec_status status;
        struct spdk_nvmf_subsystem_poll_group *sgroup = NULL;
 
-       nvmf_trace_command(req->cmd, spdk_nvmf_qpair_is_admin_queue(qpair));
+       assert(req->cmd->nvmf_cmd.opcode == SPDK_NVME_OPC_FABRIC);
+
+       if (qpair->ctrlr) {
+               sgroup = &qpair->group->sgroups[qpair->ctrlr->subsys->id];
+               assert(sgroup != NULL);
+       } else {
+               sgroup = nvmf_subsystem_pg_from_connect_cmd(req);
+       }
+
+       _nvmf_request_exec(req, sgroup);
+}
+
+void
+spdk_nvmf_request_exec(struct spdk_nvmf_request *req)
+{
+       struct spdk_nvmf_qpair *qpair = req->qpair;
+       struct spdk_nvmf_subsystem_poll_group *sgroup = NULL;
 
        if (qpair->ctrlr) {
                sgroup = &qpair->group->sgroups[qpair->ctrlr->subsys->id];
+               assert(sgroup != NULL);
+       } else if (spdk_unlikely(nvmf_request_is_fabric_connect(req))) {
+               sgroup = nvmf_subsystem_pg_from_connect_cmd(req);
        }
 
        if (qpair->state != SPDK_NVMF_QPAIR_ACTIVE) {
@@ -2576,7 +3045,7 @@ spdk_nvmf_request_exec(struct spdk_nvmf_request *req)
                if (sgroup != NULL) {
                        sgroup->io_outstanding++;
                }
-               spdk_nvmf_request_complete(req);
+               _nvmf_request_complete(req);
                return;
        }
 
@@ -2587,22 +3056,169 @@ spdk_nvmf_request_exec(struct spdk_nvmf_request *req)
                        TAILQ_INSERT_TAIL(&sgroup->queued, req, link);
                        return;
                }
+       }
 
-               sgroup->io_outstanding++;
+       _nvmf_request_exec(req, sgroup);
+}
+
+static bool
+nvmf_ctrlr_get_dif_ctx(struct spdk_nvmf_ctrlr *ctrlr, struct spdk_nvme_cmd *cmd,
+                      struct spdk_dif_ctx *dif_ctx)
+{
+       struct spdk_nvmf_ns *ns;
+       struct spdk_bdev *bdev;
+
+       if (ctrlr == NULL || cmd == NULL) {
+               return false;
        }
 
-       /* Place the request on the outstanding list so we can keep track of it */
-       TAILQ_INSERT_TAIL(&qpair->outstanding, req, link);
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, cmd->nsid);
+       if (ns == NULL || ns->bdev == NULL) {
+               return false;
+       }
+
+       bdev = ns->bdev;
+
+       switch (cmd->opc) {
+       case SPDK_NVME_OPC_READ:
+       case SPDK_NVME_OPC_WRITE:
+       case SPDK_NVME_OPC_COMPARE:
+               return nvmf_bdev_ctrlr_get_dif_ctx(bdev, cmd, dif_ctx);
+       default:
+               break;
+       }
+
+       return false;
+}
+
+bool
+spdk_nvmf_request_get_dif_ctx(struct spdk_nvmf_request *req, struct spdk_dif_ctx *dif_ctx)
+{
+       struct spdk_nvmf_qpair *qpair = req->qpair;
+       struct spdk_nvmf_ctrlr *ctrlr = qpair->ctrlr;
+
+       if (spdk_likely(ctrlr == NULL || !ctrlr->dif_insert_or_strip)) {
+               return false;
+       }
+
+       if (spdk_unlikely(qpair->state != SPDK_NVMF_QPAIR_ACTIVE)) {
+               return false;
+       }
 
        if (spdk_unlikely(req->cmd->nvmf_cmd.opcode == SPDK_NVME_OPC_FABRIC)) {
-               status = spdk_nvmf_ctrlr_process_fabrics_cmd(req);
-       } else if (spdk_unlikely(spdk_nvmf_qpair_is_admin_queue(qpair))) {
-               status = spdk_nvmf_ctrlr_process_admin_cmd(req);
+               return false;
+       }
+
+       if (spdk_unlikely(nvmf_qpair_is_admin_queue(qpair))) {
+               return false;
+       }
+
+       return nvmf_ctrlr_get_dif_ctx(ctrlr, &req->cmd->nvme_cmd, dif_ctx);
+}
+
+void
+spdk_nvmf_set_custom_admin_cmd_hdlr(uint8_t opc, spdk_nvmf_custom_cmd_hdlr hdlr)
+{
+       g_nvmf_custom_admin_cmd_hdlrs[opc].hdlr = hdlr;
+}
+
+static int
+nvmf_passthru_admin_cmd(struct spdk_nvmf_request *req)
+{
+       struct spdk_bdev *bdev;
+       struct spdk_bdev_desc *desc;
+       struct spdk_io_channel *ch;
+       struct spdk_nvme_cmd *cmd = spdk_nvmf_request_get_cmd(req);
+       struct spdk_nvme_cpl *response = spdk_nvmf_request_get_response(req);
+       uint32_t bdev_nsid;
+       int rc;
+
+       if (g_nvmf_custom_admin_cmd_hdlrs[cmd->opc].nsid == 0) {
+               bdev_nsid = cmd->nsid;
        } else {
-               status = spdk_nvmf_ctrlr_process_io_cmd(req);
+               bdev_nsid = g_nvmf_custom_admin_cmd_hdlrs[cmd->opc].nsid;
        }
 
-       if (status == SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE) {
-               spdk_nvmf_request_complete(req);
+       rc = spdk_nvmf_request_get_bdev(bdev_nsid, req, &bdev, &desc, &ch);
+       if (rc) {
+               response->status.sct = SPDK_NVME_SCT_GENERIC;
+               response->status.sc = SPDK_NVME_SC_INVALID_NAMESPACE_OR_FORMAT;
+               return SPDK_NVMF_REQUEST_EXEC_STATUS_COMPLETE;
+       }
+       return spdk_nvmf_bdev_ctrlr_nvme_passthru_admin(bdev, desc, ch, req, NULL);
+}
+
+void
+spdk_nvmf_set_passthru_admin_cmd(uint8_t opc, uint32_t forward_nsid)
+{
+       g_nvmf_custom_admin_cmd_hdlrs[opc].hdlr = nvmf_passthru_admin_cmd;
+       g_nvmf_custom_admin_cmd_hdlrs[opc].nsid = forward_nsid;
+}
+
+int
+spdk_nvmf_request_get_bdev(uint32_t nsid, struct spdk_nvmf_request *req,
+                          struct spdk_bdev **bdev, struct spdk_bdev_desc **desc, struct spdk_io_channel **ch)
+{
+       struct spdk_nvmf_ctrlr *ctrlr = req->qpair->ctrlr;
+       struct spdk_nvmf_ns *ns;
+       struct spdk_nvmf_poll_group *group = req->qpair->group;
+       struct spdk_nvmf_subsystem_pg_ns_info *ns_info;
+
+       *bdev = NULL;
+       *desc = NULL;
+       *ch = NULL;
+
+       ns = _nvmf_subsystem_get_ns(ctrlr->subsys, nsid);
+       if (ns == NULL || ns->bdev == NULL) {
+               return -EINVAL;
        }
+
+       assert(group != NULL && group->sgroups != NULL);
+       ns_info = &group->sgroups[ctrlr->subsys->id].ns_info[nsid - 1];
+       *bdev = ns->bdev;
+       *desc = ns->desc;
+       *ch = ns_info->channel;
+
+       return 0;
+}
+
+struct spdk_nvmf_ctrlr *spdk_nvmf_request_get_ctrlr(struct spdk_nvmf_request *req)
+{
+       return req->qpair->ctrlr;
+}
+
+struct spdk_nvme_cmd *spdk_nvmf_request_get_cmd(struct spdk_nvmf_request *req)
+{
+       return &req->cmd->nvme_cmd;
+}
+
+struct spdk_nvme_cpl *spdk_nvmf_request_get_response(struct spdk_nvmf_request *req)
+{
+       return &req->rsp->nvme_cpl;
+}
+
+struct spdk_nvmf_subsystem *spdk_nvmf_request_get_subsystem(struct spdk_nvmf_request *req)
+{
+       return req->qpair->ctrlr->subsys;
+}
+
+void spdk_nvmf_request_get_data(struct spdk_nvmf_request *req, void **data, uint32_t *length)
+{
+       *data = req->data;
+       *length = req->length;
+}
+
+struct spdk_nvmf_subsystem *spdk_nvmf_ctrlr_get_subsystem(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return ctrlr->subsys;
+}
+
+uint16_t spdk_nvmf_ctrlr_get_id(struct spdk_nvmf_ctrlr *ctrlr)
+{
+       return ctrlr->cntlid;
+}
+
+struct spdk_nvmf_request *spdk_nvmf_request_get_req_to_abort(struct spdk_nvmf_request *req)
+{
+       return req->req_to_abort;
 }