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.c406
1 files changed, 384 insertions, 22 deletions
diff --git a/src/nvme/mi.c b/src/nvme/mi.c
index 181a16c..6ff0a6f 100644
--- a/src/nvme/mi.c
+++ b/src/nvme/mi.c
@@ -11,6 +11,7 @@
#include <stdlib.h>
#include <stdio.h>
+#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>
#include "log.h"
@@ -295,6 +296,52 @@ static void nvme_mi_admin_init_resp(struct nvme_mi_resp *resp,
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);
+ nvme_status = le32_to_cpu(admin_hdr->cdw3) >> 16;
+
+ /* 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,
@@ -343,6 +390,7 @@ int nvme_mi_admin_xfer(nvme_mi_ctrl_t ctrl,
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);
@@ -414,11 +462,9 @@ int nvme_mi_admin_identify_partial(nvme_mi_ctrl_t ctrl,
if (rc)
return rc;
- if (resp_hdr.status)
- return resp_hdr.status;
-
- if (args->result)
- *args->result = le32_to_cpu(resp_hdr.cdw0);
+ 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 */
@@ -452,7 +498,7 @@ static int __nvme_mi_admin_get_log(nvme_mi_ctrl_t ctrl,
return -1;
}
- if (offset < 0 || offset >= len) {
+ if (offset < 0 || offset >= args->len || offset + len > args->len) {
errno = EINVAL;
return -1;
}
@@ -489,12 +535,11 @@ static int __nvme_mi_admin_get_log(nvme_mi_ctrl_t ctrl,
if (rc)
return rc;
- if (resp_hdr.status)
- return resp_hdr.status;
-
- *lenp = resp.data_len;
+ rc = nvme_mi_admin_parse_status(&resp, args->result);
+ if (!rc)
+ *lenp = resp.data_len;
- return 0;
+ return rc;
}
int nvme_mi_admin_get_log(nvme_mi_ctrl_t ctrl, struct nvme_get_log_args *args)
@@ -580,13 +625,7 @@ int nvme_mi_admin_security_send(nvme_mi_ctrl_t ctrl,
if (rc)
return rc;
- if (resp_hdr.status)
- return resp_hdr.status;
-
- if (args->result)
- *args->result = le32_to_cpu(resp_hdr.cdw0);
-
- return 0;
+ return nvme_mi_admin_parse_status(&resp, args->result);
}
int nvme_mi_admin_security_recv(nvme_mi_ctrl_t ctrl,
@@ -632,17 +671,306 @@ int nvme_mi_admin_security_recv(nvme_mi_ctrl_t ctrl,
if (rc)
return rc;
- if (resp_hdr.status)
- return resp_hdr.status;
+ 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;
- if (args->result)
- *args->result = resp_hdr.cdw0;
+ 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((args->save ? 1 : 0) << 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)
+{
+ 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_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->ns) {
+ req.data = args->ns;
+ req.data_len = sizeof(*args->ns);
+ req_hdr.dlen = cpu_to_le32(sizeof(*args->ns));
+ 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(((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)
{
@@ -955,3 +1283,37 @@ 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;
+}