// 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 <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 */

/* 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;
	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);
}

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;
}

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;
}

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 = le32_to_cpu(list.identifier[i]);
		if (!id)
			continue;

		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 (resp->data_len & 0x3) {
		errno = EINVAL;
		return -1;
	}

	if (ep->transport->mic_enabled)
		nvme_mi_calc_req_mic(req);

	rc = ep->transport->submit(ep, req, resp);
	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");
			return rc;
		}
	}

	/* 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);
	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,
		       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;
	}

	/* must be aligned */
	if (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_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 */
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)
{
	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(args->lpo & 0xffffffff);
	req_hdr.cdw13 = cpu_to_le32(args->lpo >> 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);
	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->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(nvme_mi_ctrl_t ctrl, struct nvme_get_log_args *args)
{
	const size_t xfer_size = 4096;
	off_t xfer_offset;
	int rc = 0;

	if (args->args_size < sizeof(*args)) {
		errno = EINVAL;
		return -1;
	}

	for (xfer_offset = 0; xfer_offset < args->len;) {
		size_t tmp, cur_xfer_size = xfer_size;
		bool final;

		if (xfer_offset + cur_xfer_size > args->len)
			cur_xfer_size = args->len - xfer_offset;

		tmp = cur_xfer_size;

		final = xfer_offset + cur_xfer_size >= args->len;

		rc = __nvme_mi_admin_get_log(ctrl, args, xfer_offset,
					     &tmp, final);
		if (rc)
			break;

		xfer_offset += tmp;
		/* if we returned less data than expected, consider that
		 * the end of the log page */
		if (tmp != cur_xfer_size)
			break;
	}

	if (!rc)
		args->len = xfer_offset;

	return rc;
}

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->spsp0 << 16 |
				    args->spsp1 << 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->spsp0 << 16 |
				    args->spsp1 << 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((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)
{
	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 = 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->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;
}
