From f7bd7717c0eab470a413295414f672f1e36aafff Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 29 Jul 2021 13:32:51 +0800 Subject: [PATCH] libnvme-mi: Introduce NVMe Managament Interface library This change adds support for the NVMe-MI protocol, as a secondary library: libnvme-mi.{a,so,h}. This allows management of NVMe devices, typically through NVMe Admin Channel commands, transmitted over a side-band channel - typically MCTP over SMBus. There's a fair amount of shared structure between the direct-ioctl and MI channel implementations; for example, the Admin command set is used mostly as-is over the MI transport. The library is built as separate .so/.a objects as client applications would typically either use one or the other. MI-specific functions are added with a 'nvme_mi' prefix. This change introduces a small set of MI commands using this channel. We'll extend this to further MI commands in subsequent changes, as well as implement Admin Channel commands too. We currently assume a MCTP transport for NVMe-commands, using the new AF_MCTP socket support in Linux. However, the transport-specific code is kept somewhat separate, through the internal struct nvme_mi_transport interface. This allows potential alternative transports in future - for example in-band PCIe. Finally, we start a new example application, mi-mctp, which provides a reference application using the new library. Signed-off-by: Jeremy Kerr --- examples/meson.build | 7 ++ examples/mi-mctp.c | 140 +++++++++++++++++++++++ meson.build | 8 ++ src/libnvme-mi.h | 24 ++++ src/libnvme-mi.map | 14 +++ src/meson.build | 34 ++++++ src/nvme/mi-mctp.c | 232 ++++++++++++++++++++++++++++++++++++++ src/nvme/mi.c | 259 +++++++++++++++++++++++++++++++++++++++++++ src/nvme/mi.h | 102 +++++++++++++++++ src/nvme/private.h | 42 +++++++ 10 files changed, 862 insertions(+) create mode 100644 examples/mi-mctp.c create mode 100644 src/libnvme-mi.h create mode 100644 src/libnvme-mi.map create mode 100644 src/nvme/mi-mctp.c create mode 100644 src/nvme/mi.c create mode 100644 src/nvme/mi.h diff --git a/examples/meson.build b/examples/meson.build index 42459105..fcea3fbb 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -32,3 +32,10 @@ executable( dependencies: libnvme_dep, include_directories: [incdir, internal_incdir] ) + +executable( + 'mi-mctp', + ['mi-mctp.c'], + dependencies: libnvme_mi_dep, + include_directories: [incdir, internal_incdir] +) diff --git a/examples/mi-mctp.c b/examples/mi-mctp.c new file mode 100644 index 00000000..8c86a01e --- /dev/null +++ b/examples/mi-mctp.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/** + * This file is part of libnvme. + * Copyright (c) 2021 Code Construct Pty Ltd. + * + * Authors: Jeremy Kerr + */ + +/** + * mi-mctp: open a MI connection over MCTP, and query controller info + */ + +#include +#include +#include + +#include + +#include +#include + +static void show_port_pcie(struct nvme_mi_read_port_info *port) +{ + printf(" PCIe max payload: 0x%x\n", 0x80 << port->pcie.mps); + printf(" PCIe link speeds: 0x%02x\n", port->pcie.sls); + printf(" PCIe current speed: 0x%02x\n", port->pcie.cls); + printf(" PCIe max link width: 0x%02x\n", port->pcie.mlw); + printf(" PCIe neg link width: 0x%02x\n", port->pcie.nlw); + printf(" PCIe port: 0x%02x\n", port->pcie.pn); +} + +static void show_port_smbus(struct nvme_mi_read_port_info *port) +{ + printf(" SMBus address: 0x%02x\n", port->smb.vpd_addr); + printf(" VPD access freq: 0x%02x\n", port->smb.mvpd_freq); + printf(" MCTP address: 0x%02x\n", port->smb.mme_addr); + printf(" MCTP access freq: 0x%02x\n", port->smb.mme_freq); + printf(" NVMe basic management: %s\n", + (port->smb.nvmebm & 0x1) ? "enabled" : "disabled"); +} + +static struct { + int typeid; + const char *name; + void (*fn)(struct nvme_mi_read_port_info *); +} port_types[] = { + { 0x00, "inactive", NULL }, + { 0x01, "PCIe", show_port_pcie }, + { 0x02, "SMBus", show_port_smbus }, +}; + +static int show_port(nvme_mi_ep_t ep, int portid) +{ + void (*show_fn)(struct nvme_mi_read_port_info *); + struct nvme_mi_read_port_info port; + const char *typestr; + int rc; + + rc = nvme_mi_mi_read_mi_data_port(ep, portid, &port); + if (rc) + return rc; + + if (port.portt < ARRAY_SIZE(port_types)) { + show_fn = port_types[port.portt].fn; + typestr = port_types[port.portt].name; + } else { + show_fn = NULL; + typestr = "INVALID"; + } + + printf(" port %d\n", portid); + printf(" type %s[%d]\n", typestr, port.portt); + printf(" MCTP MTU: %d\n", port.mmctptus); + printf(" MEB size: %d\n", port.meb); + + if (show_fn) + show_fn(&port); + + return 0; +} + +int main(int argc, char **argv) +{ + struct nvme_mi_nvm_ss_health_status ss_health; + struct nvme_mi_read_nvm_ss_info ss_info; + nvme_root_t root; + nvme_mi_ep_t ep; + uint8_t eid; + int net; + int rc; + int i; + + if (argc != 3) { + fprintf(stderr, "usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + net = atoi(argv[1]); + eid = atoi(argv[2]) & 0xff; + + root = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL); + if (!root) + err(EXIT_FAILURE, "can't create NVMe root"); + + ep = nvme_mi_open_mctp(root, net, eid); + if (!ep) + err(EXIT_FAILURE, "can't open MCTP endpoint %d:%d", net, eid); + + rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info); + if (rc) + err(EXIT_FAILURE, "can't perform Read MI Data operation"); + + printf("NVMe MI subsys info:\n"); + printf(" num ports: %d\n", ss_info.nump + 1); + printf(" major ver: %d\n", ss_info.mjr); + printf(" minor ver: %d\n", ss_info.mnr); + + printf("NVMe MI port info:\n"); + for (i = 0; i <= ss_info.nump; i++) + show_port(ep, i); + + rc = nvme_mi_mi_subsystem_health_status_poll(ep, true, &ss_health); + if (rc) + err(EXIT_FAILURE, "can't perform Health Status Poll operation"); + + printf("NVMe MI subsys health:\n"); + printf(" subsystem status: 0x%x\n", ss_health.nss); + printf(" smart warnings: 0x%x\n", ss_health.sw); + printf(" composite temp: %d\n", ss_health.ctemp); + printf(" drive life used: %d%%\n", ss_health.pdlu); + printf(" controller status: 0x%04x\n", le16_to_cpu(ss_health.pdlu)); + + nvme_mi_close(ep); + + nvme_mi_free_root(root); + + return EXIT_SUCCESS; +} + + diff --git a/meson.build b/meson.build index 7eb152a6..327e6760 100644 --- a/meson.build +++ b/meson.build @@ -155,6 +155,14 @@ conf.set10( ), description: 'Is isblank() available?' ) +conf.set10( + 'HAVE_LINUX_MCTP_H', + cc.compiles( + '''#include ''', + name: 'linux/mctp.h' + ), + description: 'Is linux/mctp.h include-able?' +) ################################################################################ substs = configuration_data() diff --git a/src/libnvme-mi.h b/src/libnvme-mi.h new file mode 100644 index 00000000..f0b1a916 --- /dev/null +++ b/src/libnvme-mi.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2021 Code Construct Pty Ltd + * + * Authors: Jeremy Kerr + */ + +#ifndef _LIBNVME_MI_H +#define _LIBNVME_MI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nvme/types.h" +#include "nvme/mi.h" +#include "nvme/log.h" + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBNVME_MI_H */ diff --git a/src/libnvme-mi.map b/src/libnvme-mi.map new file mode 100644 index 00000000..f2c7dca9 --- /dev/null +++ b/src/libnvme-mi.map @@ -0,0 +1,14 @@ +LIBNVME_MI_1_1 { + global: + nvme_mi_create_root; + nvme_mi_free_root; + nvme_mi_init_ctrl; + nvme_mi_close_ctrl; + nvme_mi_close; + nvme_mi_mi_read_mi_data_subsys; + nvme_mi_mi_read_mi_data_port; + nvme_mi_mi_subsystem_health_status_poll; + nvme_mi_open_mctp; + local: + *; +}; diff --git a/src/meson.build b/src/meson.build index 5b29bf05..2afe88e8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,6 +16,13 @@ sources = [ 'nvme/util.c', ] +mi_sources = [ + 'nvme/cleanup.c', + 'nvme/log.c', + 'nvme/mi.c', + 'nvme/mi-mctp.c', +] + if conf.get('CONFIG_JSONC') sources += 'nvme/json.c' endif @@ -26,9 +33,15 @@ deps = [ openssl_dep, ] +mi_deps = [ + libuuid_dep, +] + source_dir = meson.current_source_dir() mapfile = 'libnvme.map' version_script_arg = join_paths(source_dir, mapfile) +mi_mapfile = 'libnvme-mi.map' +mi_version_script_arg = join_paths(source_dir, mi_mapfile) libnvme = library( 'nvme', # produces libnvme.so @@ -60,6 +73,26 @@ libnvme_dep = declare_dependency( link_with: libnvme, ) +libnvme_mi = library( + 'nvme-mi', # produces libnvme-mi.so + mi_sources, + version: library_version, + link_args: ['-Wl,--version-script=' + mi_version_script_arg], + dependencies: mi_deps, + link_depends: mi_mapfile, + include_directories: [incdir, internal_incdir], + install: true, + link_with: libccan, +) + +libnvme_mi_dep = declare_dependency( + include_directories: ['.'], + dependencies: [ + libuuid_dep.partial_dependency(compile_args: true, includes: true), + ], + link_with: libnvme_mi, +) + mode = ['rw-r--r--', 0, 0] install_headers('libnvme.h', install_mode: mode) install_headers([ @@ -71,6 +104,7 @@ install_headers([ 'nvme/tree.h', 'nvme/types.h', 'nvme/util.h', + 'nvme/mi.h', ], subdir: 'nvme', install_mode: mode, diff --git a/src/nvme/mi-mctp.c b/src/nvme/mi-mctp.c new file mode 100644 index 00000000..d37ac132 --- /dev/null +++ b/src/nvme/mi-mctp.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2021 Code Construct Pty Ltd + * + * Authors: Jeremy Kerr + */ + +#include +#include +#include +#include + +#include +#include +#include + +#if HAVE_LINUX_MCTP_H +#include +#endif + +#include + +#include "private.h" +#include "log.h" +#include "mi.h" + + +#if !defined(AF_MCTP) +#define AF_MCTP 45 +#endif + +#if !HAVE_LINUX_MCTP_H +/* As of kernel v5.15, these AF_MCTP-related definitions are provided by + * linux/mctp.h. However, we provide a set here while that header percolates + * through to standard includes. + * + * These were all introduced in the same version as AF_MCTP was defined, + * so we can key off the presence of that. + */ + +typedef __u8 mctp_eid_t; + +struct mctp_addr { + mctp_eid_t s_addr; +}; + +struct sockaddr_mctp { + unsigned short int smctp_family; + __u16 __smctp_pad0; + unsigned int smctp_network; + struct mctp_addr smctp_addr; + __u8 smctp_type; + __u8 smctp_tag; + __u8 __smctp_pad1; +}; + +#define MCTP_NET_ANY 0x0 + +#define MCTP_ADDR_NULL 0x00 +#define MCTP_ADDR_ANY 0xff + +#define MCTP_TAG_MASK 0x07 +#define MCTP_TAG_OWNER 0x08 + +#endif /* !AF_MCTP */ + +#define MCTP_TYPE_NVME 0x04 +#define MCTP_TYPE_MIC 0x80 + +struct nvme_mi_transport_mctp { + int net; + __u8 eid; + int sd; +}; + +static const struct nvme_mi_transport nvme_mi_transport_mctp; + +static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep, + struct nvme_mi_req *req, + struct nvme_mi_resp *resp) +{ + struct nvme_mi_transport_mctp *mctp; + struct iovec req_iov[3], resp_iov[2]; + struct msghdr req_msg, resp_msg; + struct sockaddr_mctp addr; + unsigned char *rspbuf; + ssize_t len; + __le32 mic; + int i; + + if (ep->transport != &nvme_mi_transport_mctp) + return -EINVAL; + + mctp = ep->transport_data; + + memset(&addr, 0, sizeof(addr)); + addr.smctp_family = AF_MCTP; + addr.smctp_network = mctp->net; + addr.smctp_addr.s_addr = mctp->eid; + addr.smctp_type = MCTP_TYPE_NVME | MCTP_TYPE_MIC; + addr.smctp_tag = MCTP_TAG_OWNER; + + i = 0; + req_iov[i].iov_base = ((__u8 *)req->hdr) + 1; + req_iov[i].iov_len = req->hdr_len - 1; + i++; + + if (req->data_len) { + req_iov[i].iov_base = req->data; + req_iov[i].iov_len = req->data_len; + i++; + } + + mic = cpu_to_le32(req->mic); + req_iov[i].iov_base = &mic; + req_iov[i].iov_len = sizeof(mic); + i++; + + memset(&req_msg, 0, sizeof(req_msg)); + req_msg.msg_name = &addr; + req_msg.msg_namelen = sizeof(addr); + req_msg.msg_iov = req_iov; + req_msg.msg_iovlen = i; + + len = sendmsg(mctp->sd, &req_msg, 0); + if (len < 0) { + nvme_msg(ep->root, LOG_ERR, + "Failure sending MCTP message: %m\n"); + return len; + } + + resp_iov[0].iov_base = ((__u8 *)resp->hdr) + 1; + resp_iov[0].iov_len = resp->hdr_len - 1; + + /* we use a temporary buffer to receive the response, and then + * split into data & mic. This avoids having to re-arrange response + * data on a recv that was shorter than expected */ + rspbuf = malloc(resp->data_len + sizeof(mic)); + if (!rspbuf) + return -ENOMEM; + + resp_iov[1].iov_base = rspbuf; + resp_iov[1].iov_len = resp->data_len + sizeof(mic); + + memset(&resp_msg, 0, sizeof(resp_msg)); + resp_msg.msg_name = &addr; + resp_msg.msg_namelen = sizeof(addr); + resp_msg.msg_iov = resp_iov; + resp_msg.msg_iovlen = 2; + + len = recvmsg(mctp->sd, &resp_msg, 0); + + if (len < 0) { + nvme_msg(ep->root, LOG_ERR, + "Failure receiving MCTP message: %m\n"); + free(rspbuf); + return len; + } + + if (len < resp->hdr_len + sizeof(mic) - 1) { + nvme_msg(ep->root, LOG_ERR, + "Invalid MCTP response: too short (%zd bytes, needed %zd)\n", + len, resp->hdr_len + sizeof(mic) - 1); + free(rspbuf); + return -EIO; + } + resp->hdr->type = MCTP_TYPE_NVME | MCTP_TYPE_MIC; + + len -= resp->hdr_len - 1; + + memcpy(&mic, rspbuf + len - sizeof(mic), sizeof(mic)); + len -= sizeof(mic); + + memcpy(resp->data, rspbuf, len); + resp->data_len = len; + + free(rspbuf); + + resp->mic = le32_to_cpu(mic); + + return 0; +} + +static void nvme_mi_mctp_close(struct nvme_mi_ep *ep) +{ + struct nvme_mi_transport_mctp *mctp; + + if (ep->transport != &nvme_mi_transport_mctp) + return; + + mctp = ep->transport_data; + close(mctp->sd); + free(ep->transport_data); +} + +static const struct nvme_mi_transport nvme_mi_transport_mctp = { + .name = "mctp", + .mic_enabled = true, + .submit = nvme_mi_mctp_submit, + .close = nvme_mi_mctp_close, +}; + +nvme_mi_ep_t nvme_mi_open_mctp(nvme_root_t root, unsigned int netid, __u8 eid) +{ + struct nvme_mi_transport_mctp *mctp; + struct nvme_mi_ep *ep; + + ep = nvme_mi_init_ep(root); + if (!ep) + return NULL; + + mctp = malloc(sizeof(*mctp)); + if (!mctp) + goto err_free_ep; + + mctp->net = netid; + mctp->eid = eid; + + mctp->sd = socket(AF_MCTP, SOCK_DGRAM, 0); + if (mctp->sd < 0) + goto err_free_ep; + + ep->transport = &nvme_mi_transport_mctp; + ep->transport_data = mctp; + + return ep; + +err_free_ep: + free(ep); + return NULL; +} diff --git a/src/nvme/mi.c b/src/nvme/mi.c new file mode 100644 index 00000000..84b22e1f --- /dev/null +++ b/src/nvme/mi.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2021 Code Construct Pty Ltd + * + * Authors: Jeremy Kerr + */ + +#include +#include +#include + +#include + +#include "log.h" +#include "mi.h" +#include "private.h" + +/* 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) { + errno = ENOMEM; + return NULL; + } + r->log_level = log_level; + r->fp = stderr; + if (fp) + r->fp = fp; + return r; +} + +void nvme_mi_free_root(nvme_root_t root) +{ + free(root); +} + +struct nvme_mi_ep *nvme_mi_init_ep(nvme_root_t root) +{ + struct nvme_mi_ep *ep; + + ep = malloc(sizeof(*ep)); + ep->root = root; + + return ep; +} + +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; + + return ctrl; +} + +static __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 (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; + } + } + + return 0; +} + +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; + + *data_len = resp.data_len; + + /* check status, map to return value */ + 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)); + return -EPROTO; + } + + 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)) { + /* log? */ + return -EPROTO; + } + + 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.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)); + return -EIO; + } + + /* check status, map to return value */ + return 0; +} + +void nvme_mi_close(nvme_mi_ep_t ep) +{ + if (ep->transport->close) + ep->transport->close(ep); + free(ep); +} + +void nvme_mi_close_ctrl(nvme_mi_ctrl_t ctrl) +{ + free(ctrl); +} diff --git a/src/nvme/mi.h b/src/nvme/mi.h new file mode 100644 index 00000000..cd4f8cba --- /dev/null +++ b/src/nvme/mi.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2021 Code Construct Pty Ltd + * + * Authors: Jeremy Kerr + */ +#ifndef _LIBNVME_MI_MI_H +#define _LIBNVME_MI_MI_H + +#include +#include + +#include "types.h" +#include "tree.h" + +/* Message type; this is defined by MCTP, but is referenced as part of the + * NVMe-MI message spec. This is the MCTP NVMe message type (0x4), with + * the message-integrity bit (0x80) set. + */ +#define NVME_MI_MSGTYPE_NVME 0x84 + +/* Basic message definitions */ +enum nvme_mi_message_type { + NVME_MI_MT_CONTROL = 0, + NVME_MI_MT_MI = 1, + NVME_MI_MT_ADMIN = 2, + NVME_MI_MT_PCIE = 4, +}; + +enum nvme_mi_ror { + NVME_MI_ROR_REQ = 0, + NVME_MI_ROR_RSP = 1, +}; + +struct nvme_mi_msg_hdr { + __u8 type; + __u8 nmp; + __u8 meb; + __u8 rsvd0; +} __attribute__((packed)); + +/* MI command definitions */ +enum nvme_mi_mi_opcode { + nvme_mi_mi_opcode_mi_data_read = 0x00, + nvme_mi_mi_opcode_subsys_health_status_poll = 0x01, +}; + +struct nvme_mi_mi_req_hdr { + struct nvme_mi_msg_hdr hdr; + __u8 opcode; + __u8 rsvd0[3]; + __le32 cdw0, cdw1; +}; + +struct nvme_mi_mi_resp_hdr { + struct nvme_mi_msg_hdr hdr; + __u8 status; + __u8 nmresp[3]; +}; + +enum nvme_mi_dtyp { + nvme_mi_dtyp_subsys_info = 0x00, + nvme_mi_dtyp_port_info = 0x01, + nvme_mi_dtyp_ctrl_list = 0x02, + nvme_mi_dtyp_ctrl_info = 0x03, + nvme_mi_dtyp_opt_cmd_support = 0x04, + nvme_mi_dtyp_meb_support = 0x05, +}; + +/* MI Command API */ + +/* library-level API object */ +nvme_root_t nvme_mi_create_root(FILE *fp, int log_level); +void nvme_mi_free_root(nvme_root_t); + +/* Top level management object: NVMe-MI Management Endpoint */ +struct nvme_mi_ep; +typedef struct nvme_mi_ep *nvme_mi_ep_t; + +/* An endpoint may expose multiple controllers */ +struct nvme_mi_ctrl; +typedef struct nvme_mi_ctrl *nvme_mi_ctrl_t; + +/* Transport-specific endpoint initialisation. Once an endpoint is created, + * the rest of the API is transport-independent. */ +nvme_mi_ep_t nvme_mi_open_mctp(nvme_root_t root, unsigned int netid, uint8_t eid); +void nvme_mi_close(nvme_mi_ep_t ep); + +nvme_mi_ctrl_t nvme_mi_init_ctrl(nvme_mi_ep_t ep, __u16 ctrl_id); +void nvme_mi_close_ctrl(nvme_mi_ctrl_t ctrl); + +/* Management Interface functions; nvme_mi_mi_ prefix. */ +int nvme_mi_mi_read_mi_data_subsys(nvme_mi_ep_t ep, + struct nvme_mi_read_nvm_ss_info *s); +int nvme_mi_mi_read_mi_data_port(nvme_mi_ep_t ep, __u8 portid, + struct nvme_mi_read_port_info *p); +int nvme_mi_mi_subsystem_health_status_poll(nvme_mi_ep_t ep, bool clear, + struct nvme_mi_nvm_ss_health_status *nshds); + + +#endif /* _LIBNVME_MI_MI_H */ diff --git a/src/nvme/private.h b/src/nvme/private.h index 5299b13a..445ad64f 100644 --- a/src/nvme/private.h +++ b/src/nvme/private.h @@ -12,6 +12,7 @@ #include #include "fabrics.h" +#include "mi.h" #include @@ -153,4 +154,45 @@ __nvme_msg(nvme_root_t r, int lvl, const char *func, const char *format, ...); format, ##__VA_ARGS__); \ } while (0) +/* mi internal headers */ + +/* internal transport API */ +struct nvme_mi_req { + struct nvme_mi_msg_hdr *hdr; + size_t hdr_len; + void *data; + size_t data_len; + __u32 mic; +}; + +struct nvme_mi_resp { + struct nvme_mi_msg_hdr *hdr; + size_t hdr_len; + void *data; + size_t data_len; + __u32 mic; +}; + +struct nvme_mi_transport { + const char *name; + bool mic_enabled; + int (*submit)(struct nvme_mi_ep *ep, + struct nvme_mi_req *req, + struct nvme_mi_resp *resp); + void (*close)(struct nvme_mi_ep *ep); +}; + +struct nvme_mi_ep { + struct nvme_root *root; + const struct nvme_mi_transport *transport; + void *transport_data; +}; + +struct nvme_mi_ctrl { + struct nvme_mi_ep *ep; + __u16 id; +}; + +struct nvme_mi_ep *nvme_mi_init_ep(struct nvme_root *root); + #endif /* _LIBNVME_PRIVATE_H */ -- 2.50.1