root/tools/tools/nvmf/nvmfd/controller.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2023-2024 Chelsio Communications, Inc.
 * Written by: John Baldwin <jhb@FreeBSD.org>
 */

#include <err.h>
#include <errno.h>
#include <libnvmf.h>
#include <stdlib.h>

#include "internal.h"

struct controller {
        struct nvmf_qpair *qp;

        uint64_t cap;
        uint32_t vs;
        uint32_t cc;
        uint32_t csts;

        bool shutdown;

        struct nvme_controller_data cdata;
};

static bool
update_cc(struct controller *c, uint32_t new_cc)
{
        uint32_t changes;

        if (c->shutdown)
                return (false);
        if (!nvmf_validate_cc(c->qp, c->cap, c->cc, new_cc))
                return (false);

        changes = c->cc ^ new_cc;
        c->cc = new_cc;

        /* Handle shutdown requests. */
        if (NVMEV(NVME_CC_REG_SHN, changes) != 0 &&
            NVMEV(NVME_CC_REG_SHN, new_cc) != 0) {
                c->csts &= ~NVMEM(NVME_CSTS_REG_SHST);
                c->csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE);
                c->shutdown = true;
        }

        if (NVMEV(NVME_CC_REG_EN, changes) != 0) {
                if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) {
                        /* Controller reset. */
                        c->csts = 0;
                        c->shutdown = true;
                } else
                        c->csts |= NVMEF(NVME_CSTS_REG_RDY, 1);
        }
        return (true);
}

static void
handle_property_get(const struct controller *c, const struct nvmf_capsule *nc,
    const struct nvmf_fabric_prop_get_cmd *pget)
{
        struct nvmf_fabric_prop_get_rsp rsp;

        nvmf_init_cqe(&rsp, nc, 0);

        switch (le32toh(pget->ofst)) {
        case NVMF_PROP_CAP:
                if (pget->attrib.size != NVMF_PROP_SIZE_8)
                        goto error;
                rsp.value.u64 = htole64(c->cap);
                break;
        case NVMF_PROP_VS:
                if (pget->attrib.size != NVMF_PROP_SIZE_4)
                        goto error;
                rsp.value.u32.low = htole32(c->vs);
                break;
        case NVMF_PROP_CC:
                if (pget->attrib.size != NVMF_PROP_SIZE_4)
                        goto error;
                rsp.value.u32.low = htole32(c->cc);
                break;
        case NVMF_PROP_CSTS:
                if (pget->attrib.size != NVMF_PROP_SIZE_4)
                        goto error;
                rsp.value.u32.low = htole32(c->csts);
                break;
        default:
                goto error;
        }

        nvmf_send_response(nc, &rsp);
        return;
error:
        nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
}

static void
handle_property_set(struct controller *c, const struct nvmf_capsule *nc,
    const struct nvmf_fabric_prop_set_cmd *pset)
{
        switch (le32toh(pset->ofst)) {
        case NVMF_PROP_CC:
                if (pset->attrib.size != NVMF_PROP_SIZE_4)
                        goto error;
                if (!update_cc(c, le32toh(pset->value.u32.low)))
                        goto error;
                break;
        default:
                goto error;
        }

        nvmf_send_success(nc);
        return;
error:
        nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
}

static void
handle_fabrics_command(struct controller *c,
    const struct nvmf_capsule *nc, const struct nvmf_fabric_cmd *fc)
{
        switch (fc->fctype) {
        case NVMF_FABRIC_COMMAND_PROPERTY_GET:
                handle_property_get(c, nc,
                    (const struct nvmf_fabric_prop_get_cmd *)fc);
                break;
        case NVMF_FABRIC_COMMAND_PROPERTY_SET:
                handle_property_set(c, nc,
                    (const struct nvmf_fabric_prop_set_cmd *)fc);
                break;
        case NVMF_FABRIC_COMMAND_CONNECT:
                warnx("CONNECT command on connected queue");
                nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR);
                break;
        case NVMF_FABRIC_COMMAND_DISCONNECT:
                warnx("DISCONNECT command on admin queue");
                nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC,
                    NVMF_FABRIC_SC_INVALID_QUEUE_TYPE);
                break;
        default:
                warnx("Unsupported fabrics command %#x", fc->fctype);
                nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
                break;
        }
}

static void
handle_identify_command(const struct controller *c,
    const struct nvmf_capsule *nc, const struct nvme_command *cmd)
{
        uint8_t cns;

        cns = le32toh(cmd->cdw10) & 0xFF;
        switch (cns) {
        case 1:
                break;
        default:
                warnx("Unsupported CNS %#x for IDENTIFY", cns);
                goto error;
        }

        nvmf_send_controller_data(nc, &c->cdata, sizeof(c->cdata));
        return;
error:
        nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
}

void
controller_handle_admin_commands(struct controller *c, handle_command *cb,
    void *cb_arg)
{
        struct nvmf_qpair *qp = c->qp;
        const struct nvme_command *cmd;
        struct nvmf_capsule *nc;
        int error;

        for (;;) {
                error = nvmf_controller_receive_capsule(qp, &nc);
                if (error != 0) {
                        if (error != ECONNRESET)
                                warnc(error, "Failed to read command capsule");
                        break;
                }

                cmd = nvmf_capsule_sqe(nc);

                /*
                 * Only permit Fabrics commands while a controller is
                 * disabled.
                 */
                if (NVMEV(NVME_CC_REG_EN, c->cc) == 0 &&
                    cmd->opc != NVME_OPC_FABRICS_COMMANDS) {
                        warnx("Unsupported admin opcode %#x while disabled\n",
                            cmd->opc);
                        nvmf_send_generic_error(nc,
                            NVME_SC_COMMAND_SEQUENCE_ERROR);
                        nvmf_free_capsule(nc);
                        continue;
                }

                if (cb(nc, cmd, cb_arg)) {
                        nvmf_free_capsule(nc);
                        continue;
                }

                switch (cmd->opc) {
                case NVME_OPC_FABRICS_COMMANDS:
                        handle_fabrics_command(c, nc,
                            (const struct nvmf_fabric_cmd *)cmd);
                        break;
                case NVME_OPC_IDENTIFY:
                        handle_identify_command(c, nc, cmd);
                        break;
                default:
                        warnx("Unsupported admin opcode %#x", cmd->opc);
                        nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
                        break;
                }
                nvmf_free_capsule(nc);
        }
}

struct controller *
init_controller(struct nvmf_qpair *qp,
    const struct nvme_controller_data *cdata)
{
        struct controller *c;

        c = calloc(1, sizeof(*c));
        c->qp = qp;
        c->cap = nvmf_controller_cap(c->qp);
        c->vs = cdata->ver;
        c->cdata = *cdata;

        return (c);
}

void
free_controller(struct controller *c)
{
        free(c);
}