summaryrefslogtreecommitdiffstats
path: root/src/nvme/mi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvme/mi.c')
-rw-r--r--src/nvme/mi.c1651
1 files changed, 1651 insertions, 0 deletions
diff --git a/src/nvme/mi.c b/src/nvme/mi.c
new file mode 100644
index 0000000..84d51b0
--- /dev/null
+++ b/src/nvme/mi.c
@@ -0,0 +1,1651 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libnvme.
+ * Copyright (c) 2021 Code Construct Pty Ltd
+ *
+ * Authors: Jeremy Kerr <jk@codeconstruct.com.au>
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+#include "log.h"
+#include "mi.h"
+#include "private.h"
+
+static const int default_timeout = 1000; /* milliseconds; endpoints may
+ override */
+
+static bool nvme_mi_probe_enabled_default(void)
+{
+ char *val;
+
+ val = getenv("LIBNVME_MI_PROBE_ENABLED");
+ if (!val)
+ return true;
+
+ return strcmp(val, "0") &&
+ strcasecmp(val, "false") &&
+ strncasecmp(val, "disable", 7);
+
+}
+
+/* MI-equivalent of nvme_create_root, but avoids clashing symbol names
+ * when linking against both libnvme and libnvme-mi.
+ */
+nvme_root_t nvme_mi_create_root(FILE *fp, int log_level)
+{
+ struct nvme_root *r = calloc(1, sizeof(*r));
+
+ if (!r) {
+ return NULL;
+ }
+ r->log_level = log_level;
+ r->fp = stderr;
+ r->mi_probe_enabled = nvme_mi_probe_enabled_default();
+ if (fp)
+ r->fp = fp;
+ list_head_init(&r->hosts);
+ list_head_init(&r->endpoints);
+ return r;
+}
+
+void nvme_mi_free_root(nvme_root_t root)
+{
+ nvme_mi_ep_t ep, tmp;
+
+ nvme_mi_for_each_endpoint_safe(root, ep, tmp)
+ nvme_mi_close(ep);
+
+ free(root);
+}
+
+void nvme_mi_set_probe_enabled(nvme_root_t root, bool enabled)
+{
+ root->mi_probe_enabled = enabled;
+}
+
+static void nvme_mi_record_resp_time(struct nvme_mi_ep *ep)
+{
+ int rc;
+
+ rc = clock_gettime(CLOCK_MONOTONIC, &ep->last_resp_time);
+ ep->last_resp_time_valid = !rc;
+}
+
+static bool nvme_mi_compare_vid_mn(struct nvme_mi_ep *ep,
+ struct nvme_id_ctrl *id,
+ __u16 vid, const char *mn)
+
+{
+ int len;
+
+ len = strlen(mn);
+ if (len >= sizeof(id->mn)) {
+ nvme_msg(ep->root, LOG_ERR,
+ "Internal error: invalid model number for %s\n",
+ __func__);
+ return false;
+ }
+
+ return le16_to_cpu(id->vid) == vid && !strncmp(id->mn, mn, len);
+}
+
+static void __nvme_mi_format_mn(struct nvme_id_ctrl *id,
+ char *mn, size_t mn_len)
+{
+ const size_t id_mn_size = sizeof(id->mn);
+ int i;
+
+ /* A BUILD_ASSERT() would be nice here, but we're not const enough for
+ * that
+ */
+ if (mn_len <= id_mn_size)
+ abort();
+
+ memcpy(mn, id->mn, id_mn_size);
+ mn[id_mn_size] = '\0';
+
+ for (i = id_mn_size - 1; i >= 0; i--) {
+ if (mn[i] != '\0' && mn[i] != ' ')
+ break;
+ mn[i] = '\0';
+ }
+}
+
+#define nvme_mi_format_mn(id, m) __nvme_mi_format_mn(id, m, sizeof(m))
+
+void nvme_mi_ep_probe(struct nvme_mi_ep *ep)
+{
+ struct nvme_identify_args id_args = { 0 };
+ struct nvme_id_ctrl id = { 0 };
+ struct nvme_mi_ctrl *ctrl;
+ int rc;
+
+ if (!ep->root->mi_probe_enabled)
+ return;
+
+ /* start with no quirks, detect as we go */
+ ep->quirks = 0;
+
+ ctrl = nvme_mi_init_ctrl(ep, 0);
+ if (!ctrl)
+ return;
+
+ /* Do enough of an identify (assuming controller 0) to retrieve
+ * device and firmware identification information. This gives us the
+ * following fields in id:
+ *
+ * - vid (PCI vendor ID)
+ * - ssvid (PCI subsystem vendor ID)
+ * - sn (Serial number)
+ * - mn (Model number)
+ * - fr (Firmware revision)
+ *
+ * all other fields - rab and onwards - will be zero!
+ */
+ id_args.args_size = sizeof(id_args);
+ id_args.data = &id;
+ id_args.cns = NVME_IDENTIFY_CNS_CTRL;
+ id_args.nsid = NVME_NSID_NONE;
+ id_args.cntid = 0;
+ id_args.csi = NVME_CSI_NVM;
+
+ rc = nvme_mi_admin_identify_partial(ctrl, &id_args, 0,
+ offsetof(struct nvme_id_ctrl, rab));
+ if (rc) {
+ nvme_msg(ep->root, LOG_WARNING,
+ "Identify Controller failed, no quirks applied\n");
+ goto out_close;
+ }
+
+ /* Samsung MZUL2512: cannot receive commands sent within ~1ms of
+ * the previous response. Set an inter-command delay of 1.2ms for
+ * a little extra tolerance.
+ */
+ if (nvme_mi_compare_vid_mn(ep, &id, 0x144d, "MZUL2512HCJQ")) {
+ ep->quirks |= NVME_QUIRK_MIN_INTER_COMMAND_TIME;
+ ep->inter_command_us = 1200;
+ }
+
+ /* If we're quirking for the inter-command time, record the last
+ * command time now, so we don't conflict with the just-sent identify.
+ */
+ if (ep->quirks & NVME_QUIRK_MIN_INTER_COMMAND_TIME)
+ nvme_mi_record_resp_time(ep);
+
+ if (ep->quirks) {
+ char tmp[sizeof(id.mn) + 1];
+
+ nvme_mi_format_mn(&id, tmp);
+ nvme_msg(ep->root, LOG_DEBUG,
+ "device %02x:%s: applying quirks 0x%08lx\n",
+ id.vid, tmp, ep->quirks);
+ }
+
+out_close:
+ nvme_mi_close_ctrl(ctrl);
+}
+
+static const int nsec_per_sec = 1000 * 1000 * 1000;
+/* timercmp and timersub, but for struct timespec */
+#define timespec_cmp(a, b, CMP) \
+ (((a)->tv_sec == (b)->tv_sec) \
+ ? ((a)->tv_nsec CMP (b)->tv_nsec) \
+ : ((a)->tv_sec CMP (b)->tv_sec))
+
+#define timespec_sub(a, b, result) \
+ do { \
+ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
+ if ((result)->tv_nsec < 0) { \
+ --(result)->tv_sec; \
+ (result)->tv_nsec += nsec_per_sec; \
+ } \
+ } while (0)
+
+static void nvme_mi_insert_delay(struct nvme_mi_ep *ep)
+{
+ struct timespec now, next, delay;
+ int rc;
+
+ if (!ep->last_resp_time_valid)
+ return;
+
+ /* calculate earliest next command time */
+ next.tv_nsec = ep->last_resp_time.tv_nsec + ep->inter_command_us * 1000;
+ next.tv_sec = ep->last_resp_time.tv_sec;
+ if (next.tv_nsec > nsec_per_sec) {
+ next.tv_nsec -= nsec_per_sec;
+ next.tv_sec += 1;
+ }
+
+ rc = clock_gettime(CLOCK_MONOTONIC, &now);
+ if (rc) {
+ /* not much we can do; continue immediately */
+ return;
+ }
+
+ if (timespec_cmp(&now, &next, >=))
+ return;
+
+ timespec_sub(&next, &now, &delay);
+
+ nanosleep(&delay, NULL);
+}
+
+struct nvme_mi_ep *nvme_mi_init_ep(nvme_root_t root)
+{
+ struct nvme_mi_ep *ep;
+
+ ep = calloc(1, sizeof(*ep));
+ if (!ep)
+ return NULL;
+
+ list_node_init(&ep->root_entry);
+ ep->root = root;
+ ep->controllers_scanned = false;
+ ep->timeout = default_timeout;
+ ep->mprt_max = 0;
+ list_head_init(&ep->controllers);
+
+ list_add(&root->endpoints, &ep->root_entry);
+
+ return ep;
+}
+
+int nvme_mi_ep_set_timeout(nvme_mi_ep_t ep, unsigned int timeout_ms)
+{
+ if (ep->transport->check_timeout) {
+ int rc;
+ rc = ep->transport->check_timeout(ep, timeout_ms);
+ if (rc)
+ return rc;
+ }
+
+ ep->timeout = timeout_ms;
+ return 0;
+}
+
+void nvme_mi_ep_set_mprt_max(nvme_mi_ep_t ep, unsigned int mprt_max_ms)
+{
+ ep->mprt_max = mprt_max_ms;
+}
+
+unsigned int nvme_mi_ep_get_timeout(nvme_mi_ep_t ep)
+{
+ return ep->timeout;
+}
+
+static bool nvme_mi_ep_has_quirk(nvme_mi_ep_t ep, unsigned long quirk)
+{
+ return ep->quirks & quirk;
+}
+
+struct nvme_mi_ctrl *nvme_mi_init_ctrl(nvme_mi_ep_t ep, __u16 ctrl_id)
+{
+ struct nvme_mi_ctrl *ctrl;
+
+ ctrl = malloc(sizeof(*ctrl));
+ if (!ctrl)
+ return NULL;
+
+ ctrl->ep = ep;
+ ctrl->id = ctrl_id;
+
+ list_add_tail(&ep->controllers, &ctrl->ep_entry);
+
+ return ctrl;
+}
+
+__u16 nvme_mi_ctrl_id(nvme_mi_ctrl_t ctrl)
+{
+ return ctrl->id;
+}
+
+int nvme_mi_scan_ep(nvme_mi_ep_t ep, bool force_rescan)
+{
+ struct nvme_ctrl_list list;
+ unsigned int i, n_ctrl;
+ int rc;
+
+ if (ep->controllers_scanned) {
+ if (force_rescan) {
+ struct nvme_mi_ctrl *ctrl, *tmp;
+ nvme_mi_for_each_ctrl_safe(ep, ctrl, tmp)
+ nvme_mi_close_ctrl(ctrl);
+ } else {
+ return 0;
+ }
+ }
+
+ rc = nvme_mi_mi_read_mi_data_ctrl_list(ep, 0, &list);
+ if (rc)
+ return -1;
+
+ n_ctrl = le16_to_cpu(list.num);
+ if (n_ctrl > NVME_ID_CTRL_LIST_MAX) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ for (i = 0; i < n_ctrl; i++) {
+ struct nvme_mi_ctrl *ctrl;
+ __u16 id;
+
+ id = le16_to_cpu(list.identifier[i]);
+
+ ctrl = nvme_mi_init_ctrl(ep, id);
+ if (!ctrl)
+ break;
+ }
+
+ ep->controllers_scanned = true;
+ return 0;
+}
+
+__u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len)
+{
+ int i;
+
+ while (len--) {
+ crc ^= *(unsigned char *)(data++);
+ for (i = 0; i < 8; i++)
+ crc = (crc >> 1) ^ ((crc & 1) ? 0x82F63B78 : 0);
+ }
+ return crc;
+}
+
+static void nvme_mi_calc_req_mic(struct nvme_mi_req *req)
+{
+ __u32 crc = 0xffffffff;
+
+ crc = nvme_mi_crc32_update(crc, req->hdr, req->hdr_len);
+ crc = nvme_mi_crc32_update(crc, req->data, req->data_len);
+
+ req->mic = ~crc;
+}
+
+/* returns zero on correct MIC */
+static int nvme_mi_verify_resp_mic(struct nvme_mi_resp *resp)
+{
+ __u32 crc = 0xffffffff;
+
+ crc = nvme_mi_crc32_update(crc, resp->hdr, resp->hdr_len);
+ crc = nvme_mi_crc32_update(crc, resp->data, resp->data_len);
+
+ return resp->mic != ~crc;
+}
+
+int nvme_mi_submit(nvme_mi_ep_t ep, struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp)
+{
+ int rc;
+
+ if (req->hdr_len < sizeof(struct nvme_mi_msg_hdr)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (req->hdr_len & 0x3) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (req->data_len & 0x3) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (resp->hdr_len < sizeof(struct nvme_mi_msg_hdr)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (resp->hdr_len & 0x3) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ep->transport->mic_enabled)
+ nvme_mi_calc_req_mic(req);
+
+ if (nvme_mi_ep_has_quirk(ep, NVME_QUIRK_MIN_INTER_COMMAND_TIME))
+ nvme_mi_insert_delay(ep);
+
+ rc = ep->transport->submit(ep, req, resp);
+
+ if (nvme_mi_ep_has_quirk(ep, NVME_QUIRK_MIN_INTER_COMMAND_TIME))
+ nvme_mi_record_resp_time(ep);
+
+ if (rc) {
+ nvme_msg(ep->root, LOG_INFO, "transport failure\n");
+ return rc;
+ }
+
+ if (ep->transport->mic_enabled) {
+ rc = nvme_mi_verify_resp_mic(resp);
+ if (rc) {
+ nvme_msg(ep->root, LOG_WARNING, "crc mismatch\n");
+ errno = EBADMSG;
+ return -1;
+ }
+ }
+
+ /* basic response checks */
+ if (resp->hdr_len < sizeof(struct nvme_mi_msg_hdr)) {
+ nvme_msg(ep->root, LOG_DEBUG,
+ "Bad response header len: %zd\n", resp->hdr_len);
+ errno = EPROTO;
+ return -1;
+ }
+
+ if (resp->hdr->type != NVME_MI_MSGTYPE_NVME) {
+ nvme_msg(ep->root, LOG_DEBUG,
+ "Invalid message type 0x%02x\n", resp->hdr->type);
+ errno = EPROTO;
+ return -1;
+ }
+
+ if (!(resp->hdr->nmp & (NVME_MI_ROR_RSP << 7))) {
+ nvme_msg(ep->root, LOG_DEBUG,
+ "ROR value in response indicates a request\n");
+ errno = EIO;
+ return -1;
+ }
+
+ if ((resp->hdr->nmp & 0x1) != (req->hdr->nmp & 0x1)) {
+ nvme_msg(ep->root, LOG_WARNING,
+ "Command slot mismatch: req %d, resp %d\n",
+ req->hdr->nmp & 0x1,
+ resp->hdr->nmp & 0x1);
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void nvme_mi_admin_init_req(struct nvme_mi_req *req,
+ struct nvme_mi_admin_req_hdr *hdr,
+ __u16 ctrl_id, __u8 opcode)
+{
+ memset(req, 0, sizeof(*req));
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->hdr.type = NVME_MI_MSGTYPE_NVME;
+ hdr->hdr.nmp = (NVME_MI_ROR_REQ << 7) |
+ (NVME_MI_MT_ADMIN << 3); /* we always use command slot 0 */
+ hdr->opcode = opcode;
+ hdr->ctrl_id = cpu_to_le16(ctrl_id);
+
+ req->hdr = &hdr->hdr;
+ req->hdr_len = sizeof(*hdr);
+}
+
+static void nvme_mi_admin_init_resp(struct nvme_mi_resp *resp,
+ struct nvme_mi_admin_resp_hdr *hdr)
+{
+ memset(resp, 0, sizeof(*resp));
+ resp->hdr = &hdr->hdr;
+ resp->hdr_len = sizeof(*hdr);
+}
+
+static int nvme_mi_admin_parse_status(struct nvme_mi_resp *resp, __u32 *result)
+{
+ struct nvme_mi_admin_resp_hdr *admin_hdr;
+ struct nvme_mi_msg_resp *resp_hdr;
+ __u32 nvme_status;
+ __u32 nvme_result;
+
+ /* we have a few different sources of "result" here: the status header
+ * in the MI response, the cdw3 status field, and (command specific)
+ * return values in cdw0. The latter is returned in the result pointer,
+ * the former two generate return values here
+ */
+
+ if (resp->hdr_len < sizeof(*resp_hdr)) {
+ errno = -EPROTO;
+ return -1;
+ }
+ resp_hdr = (struct nvme_mi_msg_resp *)resp->hdr;
+
+ /* If we have a MI error, we can't be sure there's an admin header
+ * following; return just the MI status, with the status type
+ * indicator of MI.
+ */
+ if (resp_hdr->status)
+ return resp_hdr->status |
+ (NVME_STATUS_TYPE_MI << NVME_STATUS_TYPE_SHIFT);
+
+ /* We shouldn't hit this, as we'd have an error reported earlier.
+ * However, for pointer safety, ensure we have a full admin header
+ */
+ if (resp->hdr_len < sizeof(*admin_hdr)) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ admin_hdr = (struct nvme_mi_admin_resp_hdr *)resp->hdr;
+ nvme_result = le32_to_cpu(admin_hdr->cdw0);
+
+ /* Shift down 17 here: the SC starts at bit 17, and the NVME_SC_*
+ * definitions align to this bit (and up). The CRD, MORE and DNR
+ * bits are defined accordingly (eg., DNR is 0x4000).
+ */
+ nvme_status = le32_to_cpu(admin_hdr->cdw3) >> 17;
+
+ /* the result pointer, optionally stored if the caller needs it */
+ if (result)
+ *result = nvme_result;
+
+ return nvme_status;
+}
+
+int nvme_mi_admin_xfer(nvme_mi_ctrl_t ctrl,
+ struct nvme_mi_admin_req_hdr *admin_req,
+ size_t req_data_size,
+ struct nvme_mi_admin_resp_hdr *admin_resp,
+ off_t resp_data_offset,
+ size_t *resp_data_size)
+{
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ /* length/offset checks. The common _submit() API will do further
+ * checking on the message lengths too, so these are kept specific
+ * to the requirements of the Admin command set
+ */
+
+ /* NVMe-MI v1.2 imposes a limit of 4096 bytes on the dlen field */
+ if (*resp_data_size > 4096) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* we only have 32 bits of offset */
+ if (resp_data_offset > 0xffffffff) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* request and response lengths & offset must be aligned */
+ if ((req_data_size & 0x3) ||
+ (*resp_data_size & 0x3) ||
+ (resp_data_offset & 0x3)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* bidirectional not permitted (see DLEN definition) */
+ if (req_data_size && *resp_data_size) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!*resp_data_size && resp_data_offset) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ admin_req->hdr.type = NVME_MI_MSGTYPE_NVME;
+ admin_req->hdr.nmp = (NVME_MI_ROR_REQ << 7) |
+ (NVME_MI_MT_ADMIN << 3);
+ admin_req->ctrl_id = cpu_to_le16(ctrl->id);
+ memset(&req, 0, sizeof(req));
+ req.hdr = &admin_req->hdr;
+ req.hdr_len = sizeof(*admin_req);
+ req.data = admin_req + 1;
+ req.data_len = req_data_size;
+
+ nvme_mi_calc_req_mic(&req);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &admin_resp->hdr;
+ resp.hdr_len = sizeof(*admin_resp);
+ resp.data = admin_resp + 1;
+ resp.data_len = *resp_data_size;
+
+ /* limit the response size, specify offset */
+ admin_req->flags = 0x3;
+ admin_req->dlen = cpu_to_le32(resp.data_len & 0xffffffff);
+ admin_req->doff = cpu_to_le32(resp_data_offset & 0xffffffff);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ *resp_data_size = resp.data_len;
+
+ return 0;
+}
+
+int nvme_mi_admin_admin_passthru(nvme_mi_ctrl_t ctrl, __u8 opcode, __u8 flags,
+ __u16 rsvd, __u32 nsid, __u32 cdw2, __u32 cdw3,
+ __u32 cdw10, __u32 cdw11, __u32 cdw12,
+ __u32 cdw13, __u32 cdw14, __u32 cdw15,
+ __u32 data_len, void *data, __u32 metadata_len,
+ void *metadata, __u32 timeout_ms, __u32 *result)
+{
+ /* Input parameters flags, rsvd, metadata, metadata_len are not used */
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+ int direction = opcode & 0x3;
+ bool has_write_data = false;
+ bool has_read_data = false;
+
+ if (direction == NVME_DATA_TFR_BIDIRECTIONAL) {
+ nvme_msg(ctrl->ep->root, LOG_ERR,
+ "nvme_mi_admin_admin_passthru doesn't support bidirectional commands\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (data_len > 4096) {
+ nvme_msg(ctrl->ep->root, LOG_ERR,
+ "nvme_mi_admin_admin_passthru doesn't support data_len over 4096 bytes.\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (data != NULL && data_len != 0) {
+ if (direction == NVME_DATA_TFR_HOST_TO_CTRL)
+ has_write_data = true;
+ if (direction == NVME_DATA_TFR_CTRL_TO_HOST)
+ has_read_data = true;
+ }
+
+ if (timeout_ms > nvme_mi_ep_get_timeout(ctrl->ep)) {
+ /* Set timeout if user needs a bigger timeout */
+ nvme_mi_ep_set_timeout(ctrl->ep, timeout_ms);
+ }
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id, opcode);
+ req_hdr.cdw1 = cpu_to_le32(nsid);
+ req_hdr.cdw2 = cpu_to_le32(cdw2);
+ req_hdr.cdw3 = cpu_to_le32(cdw3);
+ req_hdr.cdw10 = cpu_to_le32(cdw10);
+ req_hdr.cdw11 = cpu_to_le32(cdw11);
+ req_hdr.cdw12 = cpu_to_le32(cdw12);
+ req_hdr.cdw13 = cpu_to_le32(cdw13);
+ req_hdr.cdw14 = cpu_to_le32(cdw14);
+ req_hdr.cdw15 = cpu_to_le32(cdw15);
+ req_hdr.doff = 0;
+ if (data_len != 0) {
+ req_hdr.dlen = cpu_to_le32(data_len);
+ /* Bit 0 set to 1 means DLEN contains a value */
+ req_hdr.flags = 0x1;
+ }
+
+ if (has_write_data) {
+ req.data = data;
+ req.data_len = data_len;
+ }
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ if (has_read_data) {
+ resp.data = data;
+ resp.data_len = data_len;
+ }
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, result);
+ if (rc)
+ return rc;
+
+ if (has_read_data && (resp.data_len != data_len)) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+int nvme_mi_admin_identify_partial(nvme_mi_ctrl_t ctrl,
+ struct nvme_identify_args *args,
+ off_t offset, size_t size)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!size || size > 0xffffffff) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id, nvme_admin_identify);
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32(args->cntid << 16 | args->cns);
+ req_hdr.cdw11 = cpu_to_le32((args->csi & 0xff) << 24 |
+ args->cns_specific_id);
+ req_hdr.cdw14 = cpu_to_le32(args->uuidx);
+ req_hdr.dlen = cpu_to_le32(size & 0xffffffff);
+ req_hdr.flags = 0x1;
+ if (offset) {
+ req_hdr.flags |= 0x2;
+ req_hdr.doff = cpu_to_le32(offset);
+ }
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+ resp.data = args->data;
+ resp.data_len = size;
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (rc)
+ return rc;
+
+ /* callers will expect a full response; if the data buffer isn't
+ * fully valid, return an error */
+ if (resp.data_len != size) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* retrieves a MCTP-messsage-sized chunk of log page data. offset and len are
+ * specified within the args->data area. The `offset` parameter is a relative
+ * offset to the args->lpo !
+ *
+ * What's more, we change the LPO of original command to chunk the request
+ * message into proper size which is allowed by MI interface. One reason is that
+ * this option seems to be supported better by devices. For more information
+ * about this option, please check https://github.com/linux-nvme/libnvme/pull/539
+ * */
+static int __nvme_mi_admin_get_log(nvme_mi_ctrl_t ctrl,
+ const struct nvme_get_log_args *args,
+ off_t offset, size_t *lenp, bool final)
+{
+ __u64 log_page_offset = args->lpo + offset;
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ size_t len;
+ __u32 ndw;
+ int rc;
+
+ /* MI spec requires that the data length field is less than or equal
+ * to 4096 */
+ len = *lenp;
+ if (!len || len > 4096 || len < 4) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (offset < 0 || offset >= args->len || offset + len > args->len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ndw = (len >> 2) - 1;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id, nvme_admin_get_log_page);
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32((ndw & 0xffff) << 16 |
+ ((!final || args->rae) ? 1 : 0) << 15 |
+ args->lsp << 8 |
+ (args->lid & 0xff));
+ req_hdr.cdw11 = cpu_to_le32(args->lsi << 16 |
+ ndw >> 16);
+ req_hdr.cdw12 = cpu_to_le32(log_page_offset & 0xffffffff);
+ req_hdr.cdw13 = cpu_to_le32(log_page_offset >> 32);
+ req_hdr.cdw14 = cpu_to_le32(args->csi << 24 |
+ (args->ot ? 1 : 0) << 23 |
+ args->uuidx);
+ req_hdr.flags = 0x1;
+ req_hdr.dlen = cpu_to_le32(len & 0xffffffff);
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+ resp.data = args->log + offset;
+ resp.data_len = len;
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (!rc)
+ *lenp = resp.data_len;
+
+ return rc;
+}
+
+int nvme_mi_admin_get_log_page(nvme_mi_ctrl_t ctrl, __u32 xfer_size,
+ struct nvme_get_log_args *args)
+{
+ const size_t max_xfer_size = xfer_size;
+ off_t xfer_offset;
+ int rc = 0;
+
+ if (args->args_size < sizeof(*args)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (args->ot && (args->len > max_xfer_size)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (xfer_offset = 0; xfer_offset < args->len;) {
+ size_t xfered_size, cur_xfer_size = max_xfer_size;
+ bool final;
+
+ if (xfer_offset + cur_xfer_size > args->len)
+ cur_xfer_size = args->len - xfer_offset;
+
+ xfered_size = cur_xfer_size;
+
+ final = xfer_offset + cur_xfer_size >= args->len;
+
+ /* xfered_size is used as both input and output parameter */
+ rc = __nvme_mi_admin_get_log(ctrl, args, xfer_offset,
+ &xfered_size, final);
+ if (rc)
+ break;
+
+ xfer_offset += xfered_size;
+ /* if we returned less data than expected, consider that
+ * the end of the log page */
+ if (xfered_size != cur_xfer_size)
+ break;
+ }
+
+ if (!rc)
+ args->len = xfer_offset;
+
+ return rc;
+}
+
+int nvme_mi_admin_get_log(nvme_mi_ctrl_t ctrl, struct nvme_get_log_args *args)
+{
+ return nvme_mi_admin_get_log_page(ctrl, 4096, args);
+}
+
+int nvme_mi_admin_security_send(nvme_mi_ctrl_t ctrl,
+ struct nvme_security_send_args *args)
+{
+
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (args->data_len > 4096) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_security_send);
+
+ req_hdr.cdw10 = cpu_to_le32(args->secp << 24 |
+ args->spsp1 << 16 |
+ args->spsp0 << 8 |
+ args->nssf);
+
+ req_hdr.cdw11 = cpu_to_le32(args->data_len & 0xffffffff);
+
+ req_hdr.flags = 0x1;
+ req_hdr.dlen = cpu_to_le32(args->data_len & 0xffffffff);
+ req.data = args->data;
+ req.data_len = args->data_len;
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, args->result);
+}
+
+int nvme_mi_admin_security_recv(nvme_mi_ctrl_t ctrl,
+ struct nvme_security_receive_args *args)
+{
+
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (args->data_len > 4096) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_security_recv);
+
+ req_hdr.cdw10 = cpu_to_le32(args->secp << 24 |
+ args->spsp1 << 16 |
+ args->spsp0 << 8 |
+ args->nssf);
+
+ req_hdr.cdw11 = cpu_to_le32(args->data_len & 0xffffffff);
+
+ req_hdr.flags = 0x1;
+ req_hdr.dlen = cpu_to_le32(args->data_len & 0xffffffff);
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+ resp.data = args->data;
+ resp.data_len = args->data_len;
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (rc)
+ return rc;
+
+ args->data_len = resp.data_len;
+
+ return 0;
+}
+
+int nvme_mi_admin_get_features(nvme_mi_ctrl_t ctrl,
+ struct nvme_get_features_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_get_features);
+
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32((args->sel & 0x7) << 8 | args->fid);
+ req_hdr.cdw14 = cpu_to_le32(args->uuidx & 0x7f);
+ req_hdr.cdw11 = cpu_to_le32(args->cdw11);
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+ resp.data = args->data;
+ resp.data_len = args->data_len;
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (rc)
+ return rc;
+
+ args->data_len = resp.data_len;
+
+ return 0;
+}
+
+int nvme_mi_admin_set_features(nvme_mi_ctrl_t ctrl,
+ struct nvme_set_features_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_set_features);
+
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32((__u32)!!args->save << 31 |
+ (args->fid & 0xff));
+ req_hdr.cdw14 = cpu_to_le32(args->uuidx & 0x7f);
+ req_hdr.cdw11 = cpu_to_le32(args->cdw11);
+ req_hdr.cdw12 = cpu_to_le32(args->cdw12);
+ req_hdr.cdw13 = cpu_to_le32(args->cdw13);
+ req_hdr.cdw15 = cpu_to_le32(args->cdw15);
+
+ req.data_len = args->data_len;
+ req.data = args->data;
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (rc)
+ return rc;
+
+ args->data_len = resp.data_len;
+
+ return 0;
+}
+
+int nvme_mi_admin_ns_mgmt(nvme_mi_ctrl_t ctrl,
+ struct nvme_ns_mgmt_args *args)
+{
+ const size_t size_v1 = sizeof_args(struct nvme_ns_mgmt_args, csi, __u64);
+ const size_t size_v2 = sizeof_args(struct nvme_ns_mgmt_args, data, __u64);
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+ size_t data_len;
+
+ if (args->args_size < size_v1 || args->args_size > size_v2) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_ns_mgmt);
+
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32(args->sel & 0xf);
+ req_hdr.cdw11 = cpu_to_le32(args->csi << 24);
+
+ if (args->args_size == size_v2) {
+ if (args->data) {
+ req.data = args->data;
+ data_len = sizeof(*args->data);
+ }
+ }
+ else {
+ if (args->ns) {
+ req.data = args->ns;
+ data_len = sizeof(*args->ns);
+ }
+ }
+
+ if (req.data) {
+ req.data_len = data_len;
+ req_hdr.dlen = cpu_to_le32(data_len);
+ req_hdr.flags = 0x1;
+ }
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, args->result);
+}
+
+int nvme_mi_admin_ns_attach(nvme_mi_ctrl_t ctrl,
+ struct nvme_ns_attach_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_ns_attach);
+
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32(args->sel & 0xf);
+ req.data = args->ctrlist;
+ req.data_len = sizeof(*args->ctrlist);
+ req_hdr.dlen = cpu_to_le32(sizeof(*args->ctrlist));
+ req_hdr.flags = 0x1;
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, args->result);
+}
+
+int nvme_mi_admin_fw_download(nvme_mi_ctrl_t ctrl,
+ struct nvme_fw_download_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ if (args->data_len & 0x3)
+ return -EINVAL;
+
+ if (args->offset & 0x3)
+ return -EINVAL;
+
+ if (!args->data_len)
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_fw_download);
+
+ req_hdr.cdw10 = cpu_to_le32((args->data_len >> 2) - 1);
+ req_hdr.cdw11 = cpu_to_le32(args->offset >> 2);
+ req.data = args->data;
+ req.data_len = args->data_len;
+ req_hdr.dlen = cpu_to_le32(args->data_len);
+ req_hdr.flags = 0x1;
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, NULL);
+}
+
+int nvme_mi_admin_fw_commit(nvme_mi_ctrl_t ctrl,
+ struct nvme_fw_commit_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_fw_commit);
+
+ req_hdr.cdw10 = cpu_to_le32(((__u32)(args->bpid & 0x1) << 31) |
+ ((args->action & 0x7) << 3) |
+ ((args->slot & 0x7) << 0));
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, NULL);
+}
+
+int nvme_mi_admin_format_nvm(nvme_mi_ctrl_t ctrl,
+ struct nvme_format_nvm_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_format_nvm);
+
+ req_hdr.cdw1 = cpu_to_le32(args->nsid);
+ req_hdr.cdw10 = cpu_to_le32(((args->lbafu & 0x3) << 12)
+ | ((args->ses & 0x7) << 9)
+ | ((args->pil & 0x1) << 8)
+ | ((args->pi & 0x7) << 5)
+ | ((args->mset & 0x1) << 4)
+ | ((args->lbaf & 0xf) << 0));
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, args->result);
+}
+
+int nvme_mi_admin_sanitize_nvm(nvme_mi_ctrl_t ctrl,
+ struct nvme_sanitize_nvm_args *args)
+{
+ struct nvme_mi_admin_resp_hdr resp_hdr;
+ struct nvme_mi_admin_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ if (args->args_size < sizeof(*args))
+ return -EINVAL;
+
+ nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+ nvme_admin_sanitize_nvm);
+
+ req_hdr.cdw10 = cpu_to_le32(((args->nodas ? 1 : 0) << 9)
+ | ((args->oipbp ? 1 : 0) << 8)
+ | ((args->owpass & 0xf) << 4)
+ | ((args->ause ? 1 : 0) << 3)
+ | ((args->sanact & 0x7) << 0));
+ req_hdr.cdw11 = cpu_to_le32(args->ovrpat);
+
+ nvme_mi_calc_req_mic(&req);
+
+ nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+ rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ return nvme_mi_admin_parse_status(&resp, args->result);
+}
+
+static int nvme_mi_read_data(nvme_mi_ep_t ep, __u32 cdw0,
+ void *data, size_t *data_len)
+{
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) |
+ (NVME_MI_MT_MI << 3); /* we always use command slot 0 */
+ req_hdr.opcode = nvme_mi_mi_opcode_mi_data_read;
+ req_hdr.cdw0 = cpu_to_le32(cdw0);
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+ resp.data = data;
+ resp.data_len = *data_len;
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ if (resp_hdr.status)
+ return resp_hdr.status;
+
+ *data_len = resp.data_len;
+
+ return 0;
+}
+
+int nvme_mi_mi_read_mi_data_subsys(nvme_mi_ep_t ep,
+ struct nvme_mi_read_nvm_ss_info *s)
+{
+ size_t len;
+ __u32 cdw0;
+ int rc;
+
+ cdw0 = (__u8)nvme_mi_dtyp_subsys_info << 24;
+ len = sizeof(*s);
+
+ rc = nvme_mi_read_data(ep, cdw0, s, &len);
+ if (rc)
+ return rc;
+
+ if (len != sizeof(*s)) {
+ nvme_msg(ep->root, LOG_WARNING,
+ "MI read data length mismatch: "
+ "got %zd bytes, expected %zd\n",
+ len, sizeof(*s));
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+int nvme_mi_mi_read_mi_data_port(nvme_mi_ep_t ep, __u8 portid,
+ struct nvme_mi_read_port_info *p)
+{
+ size_t len;
+ __u32 cdw0;
+ int rc;
+
+ cdw0 = ((__u8)nvme_mi_dtyp_port_info << 24) | (portid << 16);
+ len = sizeof(*p);
+
+ rc = nvme_mi_read_data(ep, cdw0, p, &len);
+ if (rc)
+ return rc;
+
+ if (len != sizeof(*p)) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+int nvme_mi_mi_read_mi_data_ctrl_list(nvme_mi_ep_t ep, __u8 start_ctrlid,
+ struct nvme_ctrl_list *list)
+{
+ size_t len;
+ __u32 cdw0;
+ int rc;
+
+ cdw0 = ((__u8)nvme_mi_dtyp_ctrl_list << 24) | (start_ctrlid << 16);
+ len = sizeof(*list);
+
+ rc = nvme_mi_read_data(ep, cdw0, list, &len);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+int nvme_mi_mi_read_mi_data_ctrl(nvme_mi_ep_t ep, __u16 ctrl_id,
+ struct nvme_mi_read_ctrl_info *ctrl)
+{
+ size_t len;
+ __u32 cdw0;
+ int rc;
+
+ cdw0 = ((__u8)nvme_mi_dtyp_ctrl_info << 24) | cpu_to_le16(ctrl_id);
+ len = sizeof(*ctrl);
+
+ rc = nvme_mi_read_data(ep, cdw0, ctrl, &len);
+ if (rc)
+ return rc;
+
+ if (len != sizeof(*ctrl)) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+int nvme_mi_mi_subsystem_health_status_poll(nvme_mi_ep_t ep, bool clear,
+ struct nvme_mi_nvm_ss_health_status *sshs)
+{
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) |
+ (NVME_MI_MT_MI << 3);
+ req_hdr.opcode = nvme_mi_mi_opcode_subsys_health_status_poll;
+ req_hdr.cdw1 = (clear ? 1 : 0) << 31;
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+ resp.data = sshs;
+ resp.data_len = sizeof(*sshs);
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ if (resp_hdr.status)
+ return resp_hdr.status;
+
+ if (resp.data_len != sizeof(*sshs)) {
+ nvme_msg(ep->root, LOG_WARNING,
+ "MI Subsystem Health Status length mismatch: "
+ "got %zd bytes, expected %zd\n",
+ resp.data_len, sizeof(*sshs));
+ errno = EPROTO;
+ return -1;
+ }
+
+ return 0;
+}
+
+int nvme_mi_mi_config_get(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1,
+ __u32 *nmresp)
+{
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3);
+ req_hdr.opcode = nvme_mi_mi_opcode_configuration_get;
+ req_hdr.cdw0 = cpu_to_le32(dw0);
+ req_hdr.cdw1 = cpu_to_le32(dw1);
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ if (resp_hdr.status)
+ return resp_hdr.status;
+
+ *nmresp = resp_hdr.nmresp[0] |
+ resp_hdr.nmresp[1] << 8 |
+ resp_hdr.nmresp[2] << 16;
+
+ return 0;
+}
+
+int nvme_mi_mi_config_set(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1)
+{
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3);
+ req_hdr.opcode = nvme_mi_mi_opcode_configuration_set;
+ req_hdr.cdw0 = cpu_to_le32(dw0);
+ req_hdr.cdw1 = cpu_to_le32(dw1);
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ if (rc)
+ return rc;
+
+ if (resp_hdr.status)
+ return resp_hdr.status;
+
+ return 0;
+}
+
+void nvme_mi_close(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_ctrl *ctrl, *tmp;
+
+ /* don't look for controllers during destruction */
+ ep->controllers_scanned = true;
+
+ nvme_mi_for_each_ctrl_safe(ep, ctrl, tmp)
+ nvme_mi_close_ctrl(ctrl);
+
+ if (ep->transport && ep->transport->close)
+ ep->transport->close(ep);
+ list_del(&ep->root_entry);
+ free(ep);
+}
+
+void nvme_mi_close_ctrl(nvme_mi_ctrl_t ctrl)
+{
+ list_del(&ctrl->ep_entry);
+ free(ctrl);
+}
+
+char *nvme_mi_endpoint_desc(nvme_mi_ep_t ep)
+{
+ char tsbuf[101], *s = NULL;
+ size_t tslen;
+ int rc;
+
+ rc = -1;
+ memset(tsbuf, 0, sizeof(tsbuf));
+ if (ep->transport->desc_ep)
+ rc = ep->transport->desc_ep(ep, tsbuf, sizeof(tsbuf) - 1);
+
+ if (!rc) {
+ /* don't overflow if the transport gives us an invalid string */
+ tsbuf[sizeof(tsbuf)-1] = '\0';
+ tslen = strlen(tsbuf);
+ } else {
+ tslen = 0;
+ }
+
+ if (tslen)
+ rc = asprintf(&s, "%s: %s", ep->transport->name, tsbuf);
+ else
+ rc = asprintf(&s, "%s endpoint", ep->transport->name);
+
+ if (rc < 0)
+ return NULL;
+
+ return s;
+}
+
+nvme_mi_ep_t nvme_mi_first_endpoint(nvme_root_t m)
+{
+ return list_top(&m->endpoints, struct nvme_mi_ep, root_entry);
+}
+
+nvme_mi_ep_t nvme_mi_next_endpoint(nvme_root_t m, nvme_mi_ep_t ep)
+{
+ return ep ? list_next(&m->endpoints, ep, root_entry) : NULL;
+}
+
+nvme_mi_ctrl_t nvme_mi_first_ctrl(nvme_mi_ep_t ep)
+{
+ return list_top(&ep->controllers, struct nvme_mi_ctrl, ep_entry);
+}
+
+nvme_mi_ctrl_t nvme_mi_next_ctrl(nvme_mi_ep_t ep, nvme_mi_ctrl_t c)
+{
+ return c ? list_next(&ep->controllers, c, ep_entry) : NULL;
+}
+
+
+static const char *const mi_status[] = {
+ [NVME_MI_RESP_MPR] = "More Processing Required: The command message is in progress and requires more time to complete processing",
+ [NVME_MI_RESP_INTERNAL_ERR] = "Internal Error: The request message could not be processed due to a vendor-specific error",
+ [NVME_MI_RESP_INVALID_OPCODE] = "Invalid Command Opcode",
+ [NVME_MI_RESP_INVALID_PARAM] = "Invalid Parameter",
+ [NVME_MI_RESP_INVALID_CMD_SIZE] = "Invalid Command Size: The size of the message body of the request was different than expected",
+ [NVME_MI_RESP_INVALID_INPUT_SIZE] = "Invalid Command Input Data Size: The command requires data and contains too much or too little data",
+ [NVME_MI_RESP_ACCESS_DENIED] = "Access Denied. Processing prohibited due to a vendor-specific mechanism of the Command and Feature lockdown function",
+ [NVME_MI_RESP_VPD_UPDATES_EXCEEDED] = "VPD Updates Exceeded",
+ [NVME_MI_RESP_PCIE_INACCESSIBLE] = "PCIe Inaccessible. The PCIe functionality is not available at this time",
+ [NVME_MI_RESP_MEB_SANITIZED] = "Management Endpoint Buffer Cleared Due to Sanitize",
+ [NVME_MI_RESP_ENC_SERV_FAILURE] = "Enclosure Services Failure",
+ [NVME_MI_RESP_ENC_SERV_XFER_FAILURE] = "Enclosure Services Transfer Failure: Communication with the Enclosure Services Process has failed",
+ [NVME_MI_RESP_ENC_FAILURE] = "An unrecoverable enclosure failure has been detected by the Enclosuer Services Process",
+ [NVME_MI_RESP_ENC_XFER_REFUSED] = "Enclosure Services Transfer Refused: The NVM Subsystem or Enclosure Services Process indicated an error or an invalid format in communication",
+ [NVME_MI_RESP_ENC_FUNC_UNSUP] = "Unsupported Enclosure Function: An SES Send command has been attempted to a simple Subenclosure",
+ [NVME_MI_RESP_ENC_SERV_UNAVAIL] = "Enclosure Services Unavailable: The NVM Subsystem or Enclosure Services Process has encountered an error but may become available again",
+ [NVME_MI_RESP_ENC_DEGRADED] = "Enclosure Degraded: A noncritical failure has been detected by the Enclosure Services Process",
+ [NVME_MI_RESP_SANITIZE_IN_PROGRESS] = "Sanitize In Progress: The requested command is prohibited while a sanitize operation is in progress",
+};
+
+/* kept in mi.c while we have a split libnvme/libnvme-mi; consider moving
+ * to utils.c (with nvme_status_to_string) if we ever merge. */
+const char *nvme_mi_status_to_string(int status)
+{
+ const char *s = "Unknown status";
+
+ if (status < ARRAY_SIZE(mi_status) && mi_status[status])
+ s = mi_status[status];
+
+ return s;
+}