⛏️ index : haiku.git

/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation. All rights reserved.
 *   Copyright (c) 2017, Western Digital Corporation or its affiliates.
 *
 *   Redistribution and use in sourete and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of sourete code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "nvme_internal.h"

/*
 * Allocate a request, set its command and submit it
 * to the controller admin queue.
 */
static int nvme_admin_submit_cmd(struct nvme_ctrlr *ctrlr,
				 struct nvme_cmd *cmd,
				 void *buf, uint32_t len,
				 nvme_cmd_cb cb_fn, void *cb_arg)
{
	struct nvme_request *req;

	if (buf)
		req = nvme_request_allocate_contig(&ctrlr->adminq, buf, len,
						   cb_fn, cb_arg);
	else
		req = nvme_request_allocate_null(&ctrlr->adminq, cb_fn, cb_arg);
	if (!req)
		return ENOMEM;

	memcpy(&req->cmd, cmd, sizeof(req->cmd));

	return nvme_qpair_submit_request(&ctrlr->adminq, req);
}

/*
 * Poll the controller admin queue waiting for a
 * command completion.
 */
static int nvme_admin_wait_cmd(struct nvme_ctrlr *ctrlr,
			       struct nvme_completion_poll_status *status)
{

	/* Wait for completion and check result */
	while (status->done == false)
		nvme_qpair_poll(&ctrlr->adminq, 0);

	if (nvme_cpl_is_error(&status->cpl)) {
		nvme_notice("Admin command failed\n");
		return ENXIO;
	}

	return 0;
}

/*
 * Execute an admin command.
 */
static int nvme_admin_exec_cmd(struct nvme_ctrlr *ctrlr,
			       struct nvme_cmd *cmd,
			       void *buf, uint32_t len)
{
	struct nvme_completion_poll_status status;
	int ret;

	/* Submit the command */
	status.done = false;
	ret = nvme_admin_submit_cmd(ctrlr, cmd, buf, len,
				    nvme_request_completion_poll_cb,
				    &status);
	if (ret != 0)
		return ret;

	/* Wait for the command completion and check result */
	return nvme_admin_wait_cmd(ctrlr, &status);
}

/*
 * Get a controller information.
 */
int nvme_admin_identify_ctrlr(struct nvme_ctrlr *ctrlr,
			      struct nvme_ctrlr_data *cdata)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_IDENTIFY;
	cmd.cdw10 = NVME_IDENTIFY_CTRLR;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd,
				   cdata, sizeof(struct nvme_ctrlr_data));
}

/*
 * Get a controller feature.
 */
int nvme_admin_get_feature(struct nvme_ctrlr *ctrlr,
			   enum nvme_feat_sel sel,
			   enum nvme_feat feature,
			   uint32_t cdw11,
			   uint32_t *attributes)
{
	struct nvme_completion_poll_status status;
	struct nvme_cmd cmd;
	int ret;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_GET_FEATURES;
	cmd.cdw10 = (sel << 8) | feature;
	cmd.cdw11 = cdw11;

	/* Submit the command */
	status.done = false;
	ret = nvme_admin_submit_cmd(ctrlr, &cmd, NULL, 0,
				    nvme_request_completion_poll_cb,
				    &status);
	if (ret == 0) {
		/* Wait for the command completion and check result */
		ret = nvme_admin_wait_cmd(ctrlr, &status);
		if (ret == 0 && attributes)
			*attributes = status.cpl.cdw0;
	}

	return ret;
}

/*
 * Set a feature.
 */
int nvme_admin_set_feature(struct nvme_ctrlr *ctrlr,
			   bool save,
			   enum nvme_feat feature,
			   uint32_t cdw11,
			   uint32_t cdw12,
			   void *buf, uint32_t len,
			   uint32_t *attributes)
{
	struct nvme_completion_poll_status status;
	struct nvme_cmd cmd;
	int ret;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_SET_FEATURES;
	cmd.cdw10 = feature;
	if (save)
		cmd.cdw10 |= (1 << 31);
	cmd.cdw11 = cdw11;
	cmd.cdw12 = cdw12;

	/* Submit the command */
	status.done = false;
	ret = nvme_admin_submit_cmd(ctrlr, &cmd, buf, len,
				    nvme_request_completion_poll_cb,
				    &status);
	if (ret == 0) {
		/* Wait for the command completion and check result */
		ret = nvme_admin_wait_cmd(ctrlr, &status);
		if (ret == 0 && attributes)
			*attributes = status.cpl.cdw0;
	}

	return ret;
}

/*
 * Create an I/O queue.
 */
int nvme_admin_create_ioq(struct nvme_ctrlr *ctrlr,
			  struct nvme_qpair *qpair,
			  enum nvme_io_queue_type io_qtype)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	switch(io_qtype) {
	case NVME_IO_SUBMISSION_QUEUE:
		cmd.opc = NVME_OPC_CREATE_IO_SQ;
		cmd.cdw11 = (qpair->id << 16) | (qpair->qprio << 1) | 0x1;
		cmd.dptr.prp.prp1 = qpair->cmd_bus_addr;
		break;
	case NVME_IO_COMPLETION_QUEUE:
		cmd.opc = NVME_OPC_CREATE_IO_CQ;
#ifdef __HAIKU__ // TODO: Option!
		cmd.cdw11 = 0x1 | 0x2; /* enable interrupts */
#else
		cmd.cdw11 = 0x1;
#endif
		cmd.dptr.prp.prp1 = qpair->cpl_bus_addr;
		break;
	default:
		return EINVAL;
	}

	cmd.cdw10 = ((qpair->entries - 1) << 16) | qpair->id;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Delete an I/O queue.
 */
int nvme_admin_delete_ioq(struct nvme_ctrlr *ctrlr,
			  struct nvme_qpair *qpair,
			  enum nvme_io_queue_type io_qtype)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	switch(io_qtype) {
	case NVME_IO_SUBMISSION_QUEUE:
		cmd.opc = NVME_OPC_DELETE_IO_SQ;
		break;
	case NVME_IO_COMPLETION_QUEUE:
		cmd.opc = NVME_OPC_DELETE_IO_CQ;
		break;
	default:
		return EINVAL;
	}
	cmd.cdw10 = qpair->id;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Get a namespace information.
 */
int nvme_admin_identify_ns(struct nvme_ctrlr *ctrlr,
			   uint16_t nsid,
			   struct nvme_ns_data *nsdata)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_IDENTIFY;
	cmd.cdw10 = NVME_IDENTIFY_NS;
	cmd.nsid = nsid;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd,
				   nsdata, sizeof(struct nvme_ns_data));
}

/*
 * Attach a namespace.
 */
int nvme_admin_attach_ns(struct nvme_ctrlr *ctrlr,
			 uint32_t nsid,
			 struct nvme_ctrlr_list *clist)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_NS_ATTACHMENT;
	cmd.nsid = nsid;
	cmd.cdw10 = NVME_NS_CTRLR_ATTACH;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd,
				   clist, sizeof(struct nvme_ctrlr_list));
}

/*
 * Detach a namespace.
 */
int nvme_admin_detach_ns(struct nvme_ctrlr *ctrlr,
			 uint32_t nsid,
			 struct nvme_ctrlr_list *clist)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_NS_ATTACHMENT;
	cmd.nsid = nsid;
	cmd.cdw10 = NVME_NS_CTRLR_DETACH;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd,
				   clist, sizeof(struct nvme_ctrlr_list));
}

/*
 * Create a namespace.
 */
int nvme_admin_create_ns(struct nvme_ctrlr *ctrlr,
			 struct nvme_ns_data *nsdata,
			 unsigned int *nsid)
{
	struct nvme_completion_poll_status status;
	struct nvme_cmd cmd;
	int ret;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_NS_MANAGEMENT;
	cmd.cdw10 = NVME_NS_MANAGEMENT_CREATE;

	/* Submit the command */
	status.done = false;
	ret = nvme_admin_submit_cmd(ctrlr, &cmd,
				    nsdata, sizeof(struct nvme_ns_data),
				    nvme_request_completion_poll_cb,
				    &status);
	if (ret == 0)
		/* Wait for the command completion and check result */
		ret = nvme_admin_wait_cmd(ctrlr, &status);

	if (ret != 0)
		return ret;

	*nsid = status.cpl.cdw0;

	return 0;
}

/*
 * Delete a namespace.
 */
int nvme_admin_delete_ns(struct nvme_ctrlr *ctrlr,
			 unsigned int nsid)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_NS_MANAGEMENT;
	cmd.cdw10 = NVME_NS_MANAGEMENT_DELETE;
	cmd.nsid = nsid;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Format media.
 * (entire device or just the specified namespace)
 */
int nvme_admin_format_nvm(struct nvme_ctrlr *ctrlr,
			  unsigned int nsid,
			  struct nvme_format *format)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_FORMAT_NVM;
	cmd.nsid = nsid;
	memcpy(&cmd.cdw10, format, sizeof(uint32_t));

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Get a log page.
 */
int nvme_admin_get_log_page(struct nvme_ctrlr *ctrlr,
			    uint8_t log_page,
			    uint32_t nsid,
			    void *payload,
			    uint32_t payload_size)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_GET_LOG_PAGE;
	cmd.nsid = nsid;
	cmd.cdw10 = ((payload_size / sizeof(uint32_t)) - 1) << 16;
	cmd.cdw10 |= log_page;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, payload, payload_size);
}

/*
 * Abort an admin or an I/O command.
 */
int nvme_admin_abort_cmd(struct nvme_ctrlr *ctrlr,
			 uint16_t cid, uint16_t sqid)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_ABORT;
	cmd.cdw10 = (cid << 16) | sqid;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Validate a FW.
 */
int nvme_admin_fw_commit(struct nvme_ctrlr *ctrlr,
			 const struct nvme_fw_commit *fw_commit)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_FIRMWARE_COMMIT;
	memcpy(&cmd.cdw10, fw_commit, sizeof(uint32_t));

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, NULL, 0);
}

/*
 * Download to the device a firmware.
 */
int nvme_admin_fw_image_dl(struct nvme_ctrlr *ctrlr,
			   void *fw, uint32_t size,
			   uint32_t offset)
{
	struct nvme_cmd cmd;

	/* Setup the command */
	memset(&cmd, 0, sizeof(struct nvme_cmd));
	cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD;
	cmd.cdw10 = (size >> 2) - 1;
	cmd.cdw11 = offset >> 2;

	/* Execute the command */
	return nvme_admin_exec_cmd(ctrlr, &cmd, fw, size);
}