root/src/add-ons/kernel/drivers/disk/nvme/libnvme/nvme_admin.c
/*-
 *   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,
                           uint32_t cdw13, uint32_t cdw14, uint32_t cdw15,
                           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;
        cmd.cdw13 = cdw13;
        cmd.cdw14 = cdw14;
        cmd.cdw15 = cdw15;

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