aboutsummaryrefslogtreecommitdiff
path: root/lib/libnvmf/nvmf_host.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libnvmf/nvmf_host.c')
-rw-r--r--lib/libnvmf/nvmf_host.c1021
1 files changed, 1021 insertions, 0 deletions
diff --git a/lib/libnvmf/nvmf_host.c b/lib/libnvmf/nvmf_host.c
new file mode 100644
index 000000000000..3266f8898296
--- /dev/null
+++ b/lib/libnvmf/nvmf_host.c
@@ -0,0 +1,1021 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Chelsio Communications, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <sys/nv.h>
+#include <sys/sysctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <uuid.h>
+
+#include "libnvmf.h"
+#include "internal.h"
+
+static void
+nvmf_init_sqe(void *sqe, uint8_t opcode)
+{
+ struct nvme_command *cmd = sqe;
+
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->opc = opcode;
+}
+
+static void
+nvmf_init_fabrics_sqe(void *sqe, uint8_t fctype)
+{
+ struct nvmf_capsule_cmd *cmd = sqe;
+
+ nvmf_init_sqe(sqe, NVME_OPC_FABRICS_COMMANDS);
+ cmd->fctype = fctype;
+}
+
+struct nvmf_qpair *
+nvmf_connect(struct nvmf_association *na,
+ const struct nvmf_qpair_params *params, uint16_t qid, u_int queue_size,
+ const uint8_t hostid[16], uint16_t cntlid, const char *subnqn,
+ const char *hostnqn, uint32_t kato)
+{
+ struct nvmf_fabric_connect_cmd cmd;
+ struct nvmf_fabric_connect_data data;
+ const struct nvmf_fabric_connect_rsp *rsp;
+ struct nvmf_qpair *qp;
+ struct nvmf_capsule *cc, *rc;
+ int error;
+ uint16_t sqhd, status;
+
+ qp = NULL;
+ cc = NULL;
+ rc = NULL;
+ na_clear_error(na);
+ if (na->na_controller) {
+ na_error(na, "Cannot connect on a controller");
+ goto error;
+ }
+
+ if (params->admin != (qid == 0)) {
+ na_error(na, "Admin queue must use Queue ID 0");
+ goto error;
+ }
+
+ if (qid == 0) {
+ if (queue_size < NVME_MIN_ADMIN_ENTRIES ||
+ queue_size > NVME_MAX_ADMIN_ENTRIES) {
+ na_error(na, "Invalid queue size %u", queue_size);
+ goto error;
+ }
+ } else {
+ if (queue_size < NVME_MIN_IO_ENTRIES ||
+ queue_size > NVME_MAX_IO_ENTRIES) {
+ na_error(na, "Invalid queue size %u", queue_size);
+ goto error;
+ }
+
+ /* KATO is only for Admin queues. */
+ if (kato != 0) {
+ na_error(na, "Cannot set KATO on I/O queues");
+ goto error;
+ }
+ }
+
+ qp = nvmf_allocate_qpair(na, params);
+ if (qp == NULL)
+ goto error;
+
+ nvmf_init_fabrics_sqe(&cmd, NVMF_FABRIC_COMMAND_CONNECT);
+ cmd.recfmt = 0;
+ cmd.qid = htole16(qid);
+
+ /* N.B. sqsize is 0's based. */
+ cmd.sqsize = htole16(queue_size - 1);
+ if (!na->na_params.sq_flow_control)
+ cmd.cattr |= NVMF_CONNECT_ATTR_DISABLE_SQ_FC;
+ cmd.kato = htole32(kato);
+
+ cc = nvmf_allocate_command(qp, &cmd);
+ if (cc == NULL) {
+ na_error(na, "Failed to allocate command capsule: %s",
+ strerror(errno));
+ goto error;
+ }
+
+ memset(&data, 0, sizeof(data));
+ memcpy(data.hostid, hostid, sizeof(data.hostid));
+ data.cntlid = htole16(cntlid);
+ strlcpy(data.subnqn, subnqn, sizeof(data.subnqn));
+ strlcpy(data.hostnqn, hostnqn, sizeof(data.hostnqn));
+
+ error = nvmf_capsule_append_data(cc, &data, sizeof(data), true);
+ if (error != 0) {
+ na_error(na, "Failed to append data to CONNECT capsule: %s",
+ strerror(error));
+ goto error;
+ }
+
+ error = nvmf_transmit_capsule(cc);
+ if (error != 0) {
+ na_error(na, "Failed to transmit CONNECT capsule: %s",
+ strerror(errno));
+ goto error;
+ }
+
+ error = nvmf_receive_capsule(qp, &rc);
+ if (error != 0) {
+ na_error(na, "Failed to receive CONNECT response: %s",
+ strerror(error));
+ goto error;
+ }
+
+ rsp = (const struct nvmf_fabric_connect_rsp *)&rc->nc_cqe;
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ if (NVME_STATUS_GET_SC(status) == NVMF_FABRIC_SC_INVALID_PARAM)
+ na_error(na,
+ "CONNECT invalid parameter IATTR: %#x IPO: %#x",
+ rsp->status_code_specific.invalid.iattr,
+ rsp->status_code_specific.invalid.ipo);
+ else
+ na_error(na, "CONNECT failed, status %#x", status);
+ goto error;
+ }
+
+ if (rc->nc_cqe.cid != cmd.cid) {
+ na_error(na, "Mismatched CID in CONNECT response");
+ goto error;
+ }
+
+ if (!rc->nc_sqhd_valid) {
+ na_error(na, "CONNECT response without valid SQHD");
+ goto error;
+ }
+
+ sqhd = le16toh(rsp->sqhd);
+ if (sqhd == 0xffff) {
+ if (na->na_params.sq_flow_control) {
+ na_error(na, "Controller disabled SQ flow control");
+ goto error;
+ }
+ qp->nq_flow_control = false;
+ } else {
+ qp->nq_flow_control = true;
+ qp->nq_sqhd = sqhd;
+ qp->nq_sqtail = sqhd;
+ }
+
+ if (rsp->status_code_specific.success.authreq) {
+ na_error(na, "CONNECT response requests authentication\n");
+ goto error;
+ }
+
+ qp->nq_qsize = queue_size;
+ qp->nq_cntlid = le16toh(rsp->status_code_specific.success.cntlid);
+ qp->nq_kato = kato;
+ /* XXX: Save qid in qp? */
+ return (qp);
+
+error:
+ if (rc != NULL)
+ nvmf_free_capsule(rc);
+ if (cc != NULL)
+ nvmf_free_capsule(cc);
+ if (qp != NULL)
+ nvmf_free_qpair(qp);
+ return (NULL);
+}
+
+uint16_t
+nvmf_cntlid(struct nvmf_qpair *qp)
+{
+ return (qp->nq_cntlid);
+}
+
+int
+nvmf_host_transmit_command(struct nvmf_capsule *nc)
+{
+ struct nvmf_qpair *qp = nc->nc_qpair;
+ uint16_t new_sqtail;
+ int error;
+
+ /* Fail if the queue is full. */
+ new_sqtail = (qp->nq_sqtail + 1) % qp->nq_qsize;
+ if (new_sqtail == qp->nq_sqhd)
+ return (EBUSY);
+
+ nc->nc_sqe.cid = htole16(qp->nq_cid);
+
+ /* 4.2 Skip CID of 0xFFFF. */
+ qp->nq_cid++;
+ if (qp->nq_cid == 0xFFFF)
+ qp->nq_cid = 0;
+
+ error = nvmf_transmit_capsule(nc);
+ if (error != 0)
+ return (error);
+
+ qp->nq_sqtail = new_sqtail;
+ return (0);
+}
+
+/* Receive a single capsule and update SQ FC accounting. */
+static int
+nvmf_host_receive_capsule(struct nvmf_qpair *qp, struct nvmf_capsule **ncp)
+{
+ struct nvmf_capsule *nc;
+ int error;
+
+ /* If the SQ is empty, there is no response to wait for. */
+ if (qp->nq_sqhd == qp->nq_sqtail)
+ return (EWOULDBLOCK);
+
+ error = nvmf_receive_capsule(qp, &nc);
+ if (error != 0)
+ return (error);
+
+ if (qp->nq_flow_control) {
+ if (nc->nc_sqhd_valid)
+ qp->nq_sqhd = le16toh(nc->nc_cqe.sqhd);
+ } else {
+ /*
+ * If SQ FC is disabled, just advance the head for
+ * each response capsule received so that we track the
+ * number of outstanding commands.
+ */
+ qp->nq_sqhd = (qp->nq_sqhd + 1) % qp->nq_qsize;
+ }
+ *ncp = nc;
+ return (0);
+}
+
+int
+nvmf_host_receive_response(struct nvmf_qpair *qp, struct nvmf_capsule **ncp)
+{
+ struct nvmf_capsule *nc;
+
+ /* Return the oldest previously received response. */
+ if (!TAILQ_EMPTY(&qp->nq_rx_capsules)) {
+ nc = TAILQ_FIRST(&qp->nq_rx_capsules);
+ TAILQ_REMOVE(&qp->nq_rx_capsules, nc, nc_link);
+ *ncp = nc;
+ return (0);
+ }
+
+ return (nvmf_host_receive_capsule(qp, ncp));
+}
+
+int
+nvmf_host_wait_for_response(struct nvmf_capsule *cc,
+ struct nvmf_capsule **rcp)
+{
+ struct nvmf_qpair *qp = cc->nc_qpair;
+ struct nvmf_capsule *rc;
+ int error;
+
+ /* Check if a response was already received. */
+ TAILQ_FOREACH(rc, &qp->nq_rx_capsules, nc_link) {
+ if (rc->nc_cqe.cid == cc->nc_sqe.cid) {
+ TAILQ_REMOVE(&qp->nq_rx_capsules, rc, nc_link);
+ *rcp = rc;
+ return (0);
+ }
+ }
+
+ /* Wait for a response. */
+ for (;;) {
+ error = nvmf_host_receive_capsule(qp, &rc);
+ if (error != 0)
+ return (error);
+
+ if (rc->nc_cqe.cid != cc->nc_sqe.cid) {
+ TAILQ_INSERT_TAIL(&qp->nq_rx_capsules, rc, nc_link);
+ continue;
+ }
+
+ *rcp = rc;
+ return (0);
+ }
+}
+
+struct nvmf_capsule *
+nvmf_keepalive(struct nvmf_qpair *qp)
+{
+ struct nvme_command cmd;
+
+ if (!qp->nq_admin) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ nvmf_init_sqe(&cmd, NVME_OPC_KEEP_ALIVE);
+
+ return (nvmf_allocate_command(qp, &cmd));
+}
+
+static struct nvmf_capsule *
+nvmf_get_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size)
+{
+ struct nvmf_fabric_prop_get_cmd cmd;
+
+ nvmf_init_fabrics_sqe(&cmd, NVMF_FABRIC_COMMAND_PROPERTY_GET);
+ switch (size) {
+ case 4:
+ cmd.attrib.size = NVMF_PROP_SIZE_4;
+ break;
+ case 8:
+ cmd.attrib.size = NVMF_PROP_SIZE_8;
+ break;
+ default:
+ errno = EINVAL;
+ return (NULL);
+ }
+ cmd.ofst = htole32(offset);
+
+ return (nvmf_allocate_command(qp, &cmd));
+}
+
+int
+nvmf_read_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size,
+ uint64_t *value)
+{
+ struct nvmf_capsule *cc, *rc;
+ const struct nvmf_fabric_prop_get_rsp *rsp;
+ uint16_t status;
+ int error;
+
+ if (!qp->nq_admin)
+ return (EINVAL);
+
+ cc = nvmf_get_property(qp, offset, size);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ rsp = (const struct nvmf_fabric_prop_get_rsp *)&rc->nc_cqe;
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ printf("NVMF: PROPERTY_GET failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ if (size == 8)
+ *value = le64toh(rsp->value.u64);
+ else
+ *value = le32toh(rsp->value.u32.low);
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+static struct nvmf_capsule *
+nvmf_set_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size,
+ uint64_t value)
+{
+ struct nvmf_fabric_prop_set_cmd cmd;
+
+ nvmf_init_fabrics_sqe(&cmd, NVMF_FABRIC_COMMAND_PROPERTY_SET);
+ switch (size) {
+ case 4:
+ cmd.attrib.size = NVMF_PROP_SIZE_4;
+ cmd.value.u32.low = htole32(value);
+ break;
+ case 8:
+ cmd.attrib.size = NVMF_PROP_SIZE_8;
+ cmd.value.u64 = htole64(value);
+ break;
+ default:
+ errno = EINVAL;
+ return (NULL);
+ }
+ cmd.ofst = htole32(offset);
+
+ return (nvmf_allocate_command(qp, &cmd));
+}
+
+int
+nvmf_write_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size,
+ uint64_t value)
+{
+ struct nvmf_capsule *cc, *rc;
+ uint16_t status;
+ int error;
+
+ if (!qp->nq_admin)
+ return (EINVAL);
+
+ cc = nvmf_set_property(qp, offset, size, value);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ printf("NVMF: PROPERTY_SET failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+int
+nvmf_hostid_from_hostuuid(uint8_t hostid[16])
+{
+ char hostuuid_str[64];
+ uuid_t hostuuid;
+ size_t len;
+ uint32_t status;
+
+ len = sizeof(hostuuid_str);
+ if (sysctlbyname("kern.hostuuid", hostuuid_str, &len, NULL, 0) != 0)
+ return (errno);
+
+ uuid_from_string(hostuuid_str, &hostuuid, &status);
+ switch (status) {
+ case uuid_s_ok:
+ break;
+ case uuid_s_no_memory:
+ return (ENOMEM);
+ default:
+ return (EINVAL);
+ }
+
+ uuid_enc_le(hostid, &hostuuid);
+ return (0);
+}
+
+int
+nvmf_nqn_from_hostuuid(char nqn[NVMF_NQN_MAX_LEN])
+{
+ char hostuuid_str[64];
+ size_t len;
+
+ len = sizeof(hostuuid_str);
+ if (sysctlbyname("kern.hostuuid", hostuuid_str, &len, NULL, 0) != 0)
+ return (errno);
+
+ strlcpy(nqn, NVMF_NQN_UUID_PRE, NVMF_NQN_MAX_LEN);
+ strlcat(nqn, hostuuid_str, NVMF_NQN_MAX_LEN);
+ return (0);
+}
+
+int
+nvmf_host_identify_controller(struct nvmf_qpair *qp,
+ struct nvme_controller_data *cdata)
+{
+ struct nvme_command cmd;
+ struct nvmf_capsule *cc, *rc;
+ int error;
+ uint16_t status;
+
+ if (!qp->nq_admin)
+ return (EINVAL);
+
+ nvmf_init_sqe(&cmd, NVME_OPC_IDENTIFY);
+
+ /* 5.15.1 Use CNS of 0x01 for controller data. */
+ cmd.cdw10 = htole32(1);
+
+ cc = nvmf_allocate_command(qp, &cmd);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_capsule_append_data(cc, cdata, sizeof(*cdata), false);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ printf("NVMF: IDENTIFY failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+int
+nvmf_host_identify_namespace(struct nvmf_qpair *qp, uint32_t nsid,
+ struct nvme_namespace_data *nsdata)
+{
+ struct nvme_command cmd;
+ struct nvmf_capsule *cc, *rc;
+ int error;
+ uint16_t status;
+
+ if (!qp->nq_admin)
+ return (EINVAL);
+
+ nvmf_init_sqe(&cmd, NVME_OPC_IDENTIFY);
+
+ /* 5.15.1 Use CNS of 0x00 for namespace data. */
+ cmd.cdw10 = htole32(0);
+ cmd.nsid = htole32(nsid);
+
+ cc = nvmf_allocate_command(qp, &cmd);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_capsule_append_data(cc, nsdata, sizeof(*nsdata), false);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ printf("NVMF: IDENTIFY failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+static int
+nvmf_get_discovery_log_page(struct nvmf_qpair *qp, uint64_t offset, void *buf,
+ size_t len)
+{
+ struct nvme_command cmd;
+ struct nvmf_capsule *cc, *rc;
+ size_t numd;
+ int error;
+ uint16_t status;
+
+ if (len % 4 != 0 || len == 0 || offset % 4 != 0)
+ return (EINVAL);
+
+ numd = (len / 4) - 1;
+ nvmf_init_sqe(&cmd, NVME_OPC_GET_LOG_PAGE);
+ cmd.cdw10 = htole32(numd << 16 | NVME_LOG_DISCOVERY);
+ cmd.cdw11 = htole32(numd >> 16);
+ cmd.cdw12 = htole32(offset);
+ cmd.cdw13 = htole32(offset >> 32);
+
+ cc = nvmf_allocate_command(qp, &cmd);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_capsule_append_data(cc, buf, len, false);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ status = le16toh(rc->nc_cqe.status);
+ if (NVMEV(NVME_STATUS_SC, status) ==
+ NVMF_FABRIC_SC_LOG_RESTART_DISCOVERY) {
+ nvmf_free_capsule(rc);
+ return (EAGAIN);
+ }
+ if (status != 0) {
+ printf("NVMF: GET_LOG_PAGE failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+int
+nvmf_host_fetch_discovery_log_page(struct nvmf_qpair *qp,
+ struct nvme_discovery_log **logp)
+{
+ struct nvme_discovery_log hdr, *log;
+ size_t payload_len;
+ int error;
+
+ if (!qp->nq_admin)
+ return (EINVAL);
+
+ log = NULL;
+ for (;;) {
+ error = nvmf_get_discovery_log_page(qp, 0, &hdr, sizeof(hdr));
+ if (error != 0) {
+ free(log);
+ return (error);
+ }
+ nvme_discovery_log_swapbytes(&hdr);
+
+ if (hdr.recfmt != 0) {
+ printf("NVMF: Unsupported discovery log format: %d\n",
+ hdr.recfmt);
+ free(log);
+ return (EINVAL);
+ }
+
+ if (hdr.numrec > 1024) {
+ printf("NVMF: Too many discovery log entries: %ju\n",
+ (uintmax_t)hdr.numrec);
+ free(log);
+ return (EFBIG);
+ }
+
+ payload_len = sizeof(log->entries[0]) * hdr.numrec;
+ log = reallocf(log, sizeof(*log) + payload_len);
+ if (log == NULL)
+ return (ENOMEM);
+ *log = hdr;
+ if (hdr.numrec == 0)
+ break;
+
+ error = nvmf_get_discovery_log_page(qp, sizeof(hdr),
+ log->entries, payload_len);
+ if (error == EAGAIN)
+ continue;
+ if (error != 0) {
+ free(log);
+ return (error);
+ }
+
+ /* Re-read the header and check the generation count. */
+ error = nvmf_get_discovery_log_page(qp, 0, &hdr, sizeof(hdr));
+ if (error != 0) {
+ free(log);
+ return (error);
+ }
+ nvme_discovery_log_swapbytes(&hdr);
+
+ if (log->genctr != hdr.genctr)
+ continue;
+
+ for (u_int i = 0; i < log->numrec; i++)
+ nvme_discovery_log_entry_swapbytes(&log->entries[i]);
+ break;
+ }
+ *logp = log;
+ return (0);
+}
+
+int
+nvmf_init_dle_from_admin_qp(struct nvmf_qpair *qp,
+ const struct nvme_controller_data *cdata,
+ struct nvme_discovery_log_entry *dle)
+{
+ int error;
+ uint16_t cntlid;
+
+ memset(dle, 0, sizeof(*dle));
+ error = nvmf_populate_dle(qp, dle);
+ if (error != 0)
+ return (error);
+ if ((cdata->fcatt & 1) == 0)
+ cntlid = NVMF_CNTLID_DYNAMIC;
+ else
+ cntlid = cdata->ctrlr_id;
+ dle->cntlid = htole16(cntlid);
+ memcpy(dle->subnqn, cdata->subnqn, sizeof(dle->subnqn));
+ return (0);
+}
+
+int
+nvmf_host_request_queues(struct nvmf_qpair *qp, u_int requested, u_int *actual)
+{
+ struct nvme_command cmd;
+ struct nvmf_capsule *cc, *rc;
+ int error;
+ uint16_t status;
+
+ if (!qp->nq_admin || requested < 1 || requested > 65535)
+ return (EINVAL);
+
+ /* The number of queues is 0's based. */
+ requested--;
+
+ nvmf_init_sqe(&cmd, NVME_OPC_SET_FEATURES);
+ cmd.cdw10 = htole32(NVME_FEAT_NUMBER_OF_QUEUES);
+
+ /* Same number of completion and submission queues. */
+ cmd.cdw11 = htole32((requested << 16) | requested);
+
+ cc = nvmf_allocate_command(qp, &cmd);
+ if (cc == NULL)
+ return (errno);
+
+ error = nvmf_host_transmit_command(cc);
+ if (error != 0) {
+ nvmf_free_capsule(cc);
+ return (error);
+ }
+
+ error = nvmf_host_wait_for_response(cc, &rc);
+ nvmf_free_capsule(cc);
+ if (error != 0)
+ return (error);
+
+ status = le16toh(rc->nc_cqe.status);
+ if (status != 0) {
+ printf("NVMF: SET_FEATURES failed, status %#x\n", status);
+ nvmf_free_capsule(rc);
+ return (EIO);
+ }
+
+ *actual = (le32toh(rc->nc_cqe.cdw0) & 0xffff) + 1;
+ nvmf_free_capsule(rc);
+ return (0);
+}
+
+static bool
+is_queue_pair_idle(struct nvmf_qpair *qp)
+{
+ if (qp->nq_sqhd != qp->nq_sqtail)
+ return (false);
+ if (!TAILQ_EMPTY(&qp->nq_rx_capsules))
+ return (false);
+ return (true);
+}
+
+static int
+prepare_queues_for_handoff(struct nvmf_ioc_nv *nv,
+ const struct nvme_discovery_log_entry *dle, const char *hostnqn,
+ struct nvmf_qpair *admin_qp, u_int num_queues,
+ struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata,
+ uint32_t reconnect_delay, uint32_t controller_loss_timeout)
+{
+ const struct nvmf_association *na = admin_qp->nq_association;
+ nvlist_t *nvl, *nvl_qp, *nvl_rparams;
+ u_int i;
+ int error;
+
+ if (num_queues == 0)
+ return (EINVAL);
+
+ /* Ensure trtype matches. */
+ if (dle->trtype != na->na_trtype)
+ return (EINVAL);
+
+ /* All queue pairs must be idle. */
+ if (!is_queue_pair_idle(admin_qp))
+ return (EBUSY);
+ for (i = 0; i < num_queues; i++) {
+ if (!is_queue_pair_idle(io_queues[i]))
+ return (EBUSY);
+ }
+
+ /* Fill out reconnect parameters. */
+ nvl_rparams = nvlist_create(0);
+ nvlist_add_binary(nvl_rparams, "dle", dle, sizeof(*dle));
+ nvlist_add_string(nvl_rparams, "hostnqn", hostnqn);
+ nvlist_add_number(nvl_rparams, "num_io_queues", num_queues);
+ nvlist_add_number(nvl_rparams, "kato", admin_qp->nq_kato);
+ nvlist_add_number(nvl_rparams, "reconnect_delay", reconnect_delay);
+ nvlist_add_number(nvl_rparams, "controller_loss_timeout",
+ controller_loss_timeout);
+ nvlist_add_number(nvl_rparams, "io_qsize", io_queues[0]->nq_qsize);
+ nvlist_add_bool(nvl_rparams, "sq_flow_control",
+ na->na_params.sq_flow_control);
+ switch (na->na_trtype) {
+ case NVMF_TRTYPE_TCP:
+ nvlist_add_bool(nvl_rparams, "header_digests",
+ na->na_params.tcp.header_digests);
+ nvlist_add_bool(nvl_rparams, "data_digests",
+ na->na_params.tcp.data_digests);
+ break;
+ default:
+ __unreachable();
+ }
+ error = nvlist_error(nvl_rparams);
+ if (error != 0) {
+ nvlist_destroy(nvl_rparams);
+ return (error);
+ }
+
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "trtype", na->na_trtype);
+ nvlist_add_number(nvl, "kato", admin_qp->nq_kato);
+ nvlist_add_number(nvl, "reconnect_delay", reconnect_delay);
+ nvlist_add_number(nvl, "controller_loss_timeout",
+ controller_loss_timeout);
+ nvlist_move_nvlist(nvl, "rparams", nvl_rparams);
+
+ /* First, the admin queue. */
+ error = nvmf_kernel_handoff_params(admin_qp, &nvl_qp);
+ if (error) {
+ nvlist_destroy(nvl);
+ return (error);
+ }
+ nvlist_move_nvlist(nvl, "admin", nvl_qp);
+
+ /* Next, the I/O queues. */
+ for (i = 0; i < num_queues; i++) {
+ error = nvmf_kernel_handoff_params(io_queues[i], &nvl_qp);
+ if (error) {
+ nvlist_destroy(nvl);
+ return (error);
+ }
+ nvlist_append_nvlist_array(nvl, "io", nvl_qp);
+ }
+
+ nvlist_add_binary(nvl, "cdata", cdata, sizeof(*cdata));
+
+ error = nvmf_pack_ioc_nvlist(nv, nvl);
+ nvlist_destroy(nvl);
+ return (error);
+}
+
+int
+nvmf_handoff_host(const struct nvme_discovery_log_entry *dle,
+ const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues,
+ struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata,
+ uint32_t reconnect_delay, uint32_t controller_loss_timeout)
+{
+ struct nvmf_ioc_nv nv;
+ u_int i;
+ int error, fd;
+
+ fd = open("/dev/nvmf", O_RDWR);
+ if (fd == -1) {
+ error = errno;
+ goto out;
+ }
+
+ error = prepare_queues_for_handoff(&nv, dle, hostnqn, admin_qp,
+ num_queues, io_queues, cdata, reconnect_delay,
+ controller_loss_timeout);
+ if (error != 0)
+ goto out;
+
+ if (ioctl(fd, NVMF_HANDOFF_HOST, &nv) == -1)
+ error = errno;
+ free(nv.data);
+
+out:
+ if (fd >= 0)
+ close(fd);
+ for (i = 0; i < num_queues; i++)
+ (void)nvmf_free_qpair(io_queues[i]);
+ (void)nvmf_free_qpair(admin_qp);
+ return (error);
+}
+
+int
+nvmf_disconnect_host(const char *host)
+{
+ int error, fd;
+
+ error = 0;
+ fd = open("/dev/nvmf", O_RDWR);
+ if (fd == -1) {
+ error = errno;
+ goto out;
+ }
+
+ if (ioctl(fd, NVMF_DISCONNECT_HOST, &host) == -1)
+ error = errno;
+
+out:
+ if (fd >= 0)
+ close(fd);
+ return (error);
+}
+
+int
+nvmf_disconnect_all(void)
+{
+ int error, fd;
+
+ error = 0;
+ fd = open("/dev/nvmf", O_RDWR);
+ if (fd == -1) {
+ error = errno;
+ goto out;
+ }
+
+ if (ioctl(fd, NVMF_DISCONNECT_ALL) == -1)
+ error = errno;
+
+out:
+ if (fd >= 0)
+ close(fd);
+ return (error);
+}
+
+static int
+nvmf_read_ioc_nv(int fd, u_long com, nvlist_t **nvlp)
+{
+ struct nvmf_ioc_nv nv;
+ nvlist_t *nvl;
+ int error;
+
+ memset(&nv, 0, sizeof(nv));
+ if (ioctl(fd, com, &nv) == -1)
+ return (errno);
+
+ nv.data = malloc(nv.len);
+ nv.size = nv.len;
+ if (ioctl(fd, com, &nv) == -1) {
+ error = errno;
+ free(nv.data);
+ return (error);
+ }
+
+ nvl = nvlist_unpack(nv.data, nv.len, 0);
+ free(nv.data);
+ if (nvl == NULL)
+ return (EINVAL);
+
+ *nvlp = nvl;
+ return (0);
+}
+
+int
+nvmf_reconnect_params(int fd, nvlist_t **nvlp)
+{
+ return (nvmf_read_ioc_nv(fd, NVMF_RECONNECT_PARAMS, nvlp));
+}
+
+int
+nvmf_reconnect_host(int fd, const struct nvme_discovery_log_entry *dle,
+ const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues,
+ struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata,
+ uint32_t reconnect_delay, uint32_t controller_loss_timeout)
+{
+ struct nvmf_ioc_nv nv;
+ u_int i;
+ int error;
+
+ error = prepare_queues_for_handoff(&nv, dle, hostnqn, admin_qp,
+ num_queues, io_queues, cdata, reconnect_delay,
+ controller_loss_timeout);
+ if (error != 0)
+ goto out;
+
+ if (ioctl(fd, NVMF_RECONNECT_HOST, &nv) == -1)
+ error = errno;
+ free(nv.data);
+
+out:
+ for (i = 0; i < num_queues; i++)
+ (void)nvmf_free_qpair(io_queues[i]);
+ (void)nvmf_free_qpair(admin_qp);
+ return (error);
+}
+
+int
+nvmf_connection_status(int fd, nvlist_t **nvlp)
+{
+ return (nvmf_read_ioc_nv(fd, NVMF_CONNECTION_STATUS, nvlp));
+}