summaryrefslogtreecommitdiffstats
path: root/examples/mi-mctp.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/mi-mctp.c')
-rw-r--r--examples/mi-mctp.c778
1 files changed, 778 insertions, 0 deletions
diff --git a/examples/mi-mctp.c b/examples/mi-mctp.c
new file mode 100644
index 0000000..e0b7644
--- /dev/null
+++ b/examples/mi-mctp.c
@@ -0,0 +1,778 @@
+// 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>
+ */
+
+/**
+ * mi-mctp: open a MI connection over MCTP, and query controller info
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <libnvme-mi.h>
+
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+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 do_info(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_nvm_ss_health_status ss_health;
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int i, rc;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ if (rc) {
+ warn("can't perform Read MI Data operation");
+ return -1;
+ }
+
+ 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.ccs));
+
+ return 0;
+}
+
+static int show_ctrl(nvme_mi_ep_t ep, uint16_t ctrl_id)
+{
+ struct nvme_mi_read_ctrl_info ctrl;
+ int rc;
+
+ rc = nvme_mi_mi_read_mi_data_ctrl(ep, ctrl_id, &ctrl);
+ if (rc)
+ return rc;
+
+ printf(" Controller id: %d\n", ctrl_id);
+ printf(" port id: %d\n", ctrl.portid);
+ if (ctrl.prii & 0x1) {
+ uint16_t bdfn = le16_to_cpu(ctrl.pri);
+ printf(" PCIe routing valid\n");
+ printf(" PCIe bus: 0x%02x\n", bdfn >> 8);
+ printf(" PCIe dev: 0x%02x\n", bdfn >> 3 & 0x1f);
+ printf(" PCIe fn : 0x%02x\n", bdfn & 0x7);
+ } else {
+ printf(" PCIe routing invalid\n");
+ }
+ printf(" PCI vendor: %04x\n", le16_to_cpu(ctrl.vid));
+ printf(" PCI device: %04x\n", le16_to_cpu(ctrl.did));
+ printf(" PCI subsys vendor: %04x\n", le16_to_cpu(ctrl.ssvid));
+ printf(" PCI subsys device: %04x\n", le16_to_cpu(ctrl.ssvid));
+
+ return 0;
+}
+
+static int do_controllers(nvme_mi_ep_t ep)
+{
+ struct nvme_ctrl_list ctrl_list;
+ int rc, i;
+
+ rc = nvme_mi_mi_read_mi_data_ctrl_list(ep, 0, &ctrl_list);
+ if (rc) {
+ warnx("Can't perform Controller List operation");
+ return rc;
+ }
+
+ printf("NVMe controller list:\n");
+ for (i = 0; i < le16_to_cpu(ctrl_list.num); i++) {
+ uint16_t id = le16_to_cpu(ctrl_list.identifier[i]);
+ show_ctrl(ep, id);
+ }
+ return 0;
+}
+
+static const char *__copy_id_str(const void *field, size_t size,
+ char *buf, size_t buf_size)
+{
+ assert(size < buf_size);
+ strncpy(buf, field, size);
+ buf[size] = '\0';
+ return buf;
+}
+
+#define copy_id_str(f,b) __copy_id_str(f, sizeof(f), b, sizeof(b))
+
+int do_identify(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ struct nvme_identify_args id_args = { 0 };
+ struct nvme_mi_ctrl *ctrl;
+ struct nvme_id_ctrl id;
+ uint16_t ctrl_id;
+ char buf[41];
+ bool partial;
+ int rc, tmp;
+
+ if (argc < 2) {
+ fprintf(stderr, "no controller ID specified\n");
+ return -1;
+ }
+
+ tmp = atoi(argv[1]);
+ if (tmp < 0 || tmp > 0xffff) {
+ fprintf(stderr, "invalid controller ID\n");
+ return -1;
+ }
+
+ ctrl_id = tmp & 0xffff;
+
+ partial = argc > 2 && !strcmp(argv[2], "--partial");
+
+ ctrl = nvme_mi_init_ctrl(ep, ctrl_id);
+ if (!ctrl) {
+ warn("can't create controller");
+ return -1;
+ }
+
+ id_args.data = &id;
+ id_args.args_size = sizeof(id_args);
+ id_args.cns = NVME_IDENTIFY_CNS_CTRL;
+ id_args.nsid = NVME_NSID_NONE;
+ id_args.cntid = 0;
+ id_args.csi = NVME_CSI_NVM;
+
+ /* for this example code, we can either do a full or partial identify;
+ * since we're only printing the fields before the 'rab' member,
+ * these will be equivalent, aside from the size of the MI
+ * response.
+ */
+ if (partial) {
+ rc = nvme_mi_admin_identify_partial(ctrl, &id_args, 0,
+ offsetof(struct nvme_id_ctrl, rab));
+ } else {
+ rc = nvme_mi_admin_identify(ctrl, &id_args);
+ }
+
+ if (rc) {
+ warn("can't perform Admin Identify command");
+ return -1;
+ }
+
+ printf("NVMe Controller %d identify\n", ctrl_id);
+ printf(" PCI vendor: %04x\n", le16_to_cpu(id.vid));
+ printf(" PCI subsys vendor: %04x\n", le16_to_cpu(id.ssvid));
+ printf(" Serial number: %s\n", copy_id_str(id.sn, buf));
+ printf(" Model number: %s\n", copy_id_str(id.mn, buf));
+ printf(" Firmware rev: %s\n", copy_id_str(id.fr, buf));
+
+ return 0;
+}
+
+void fhexdump(FILE *fp, const unsigned char *buf, int len)
+{
+ const int row_len = 16;
+ int i, j;
+
+ for (i = 0; i < len; i += row_len) {
+ char hbuf[row_len * strlen("00 ") + 1];
+ char cbuf[row_len + strlen("|") + 1];
+
+ for (j = 0; (j < row_len) && ((i+j) < len); j++) {
+ unsigned char c = buf[i + j];
+
+ sprintf(hbuf + j * 3, "%02x ", c);
+
+ if (!isprint(c))
+ c = '.';
+
+ sprintf(cbuf + j, "%c", c);
+ }
+
+ strcat(cbuf, "|");
+
+ fprintf(fp, "%08x %*s |%s\n", i,
+ 0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
+ }
+}
+
+void hexdump(const unsigned char *buf, int len)
+{
+ fhexdump(stdout, buf, len);
+}
+
+int do_get_log_page(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ struct nvme_get_log_args args = { 0 };
+ struct nvme_mi_ctrl *ctrl;
+ uint8_t buf[512];
+ uint16_t ctrl_id;
+ int rc, tmp;
+
+ if (argc < 2) {
+ fprintf(stderr, "no controller ID specified\n");
+ return -1;
+ }
+
+ tmp = atoi(argv[1]);
+ if (tmp < 0 || tmp > 0xffff) {
+ fprintf(stderr, "invalid controller ID\n");
+ return -1;
+ }
+
+ ctrl_id = tmp & 0xffff;
+
+ args.args_size = sizeof(args);
+ args.log = buf;
+ args.len = sizeof(buf);
+
+ if (argc > 2) {
+ tmp = atoi(argv[2]);
+ args.lid = tmp & 0xff;
+ } else {
+ args.lid = 0x1;
+ }
+
+ ctrl = nvme_mi_init_ctrl(ep, ctrl_id);
+ if (!ctrl) {
+ warn("can't create controller");
+ return -1;
+ }
+
+ rc = nvme_mi_admin_get_log(ctrl, &args);
+ if (rc) {
+ warn("can't perform Get Log page command");
+ return -1;
+ }
+
+ printf("Get log page (log id = 0x%02x) data:\n", args.lid);
+ hexdump(buf, args.len);
+
+ return 0;
+}
+
+int do_admin_raw(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ struct nvme_mi_admin_req_hdr req;
+ struct nvme_mi_admin_resp_hdr *resp;
+ struct nvme_mi_ctrl *ctrl;
+ size_t resp_data_len;
+ unsigned long tmp;
+ uint8_t buf[512];
+ uint16_t ctrl_id;
+ uint8_t opcode;
+ __le32 *cdw;
+ int i, rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "no controller ID specified\n");
+ return -1;
+ }
+
+ if (argc < 3) {
+ fprintf(stderr, "no opcode specified\n");
+ return -1;
+ }
+
+ tmp = atoi(argv[1]);
+ if (tmp > 0xffff) {
+ fprintf(stderr, "invalid controller ID\n");
+ return -1;
+ }
+ ctrl_id = tmp & 0xffff;
+
+ tmp = atoi(argv[2]);
+ if (tmp > 0xff) {
+ fprintf(stderr, "invalid opcode\n");
+ return -1;
+ }
+ opcode = tmp & 0xff;
+
+ memset(&req, 0, sizeof(req));
+ req.opcode = opcode;
+ req.ctrl_id = cpu_to_le16(ctrl_id);
+
+ /* The cdw10 - cdw16 fields are contiguous in req; set from argv. */
+ cdw = (void *)&req + offsetof(typeof(req), cdw10);
+ for (i = 0; i < 6; i++) {
+ if (argc >= 4 + i)
+ tmp = strtoul(argv[3 + i], NULL, 0);
+ else
+ tmp = 0;
+ *cdw = cpu_to_le32(tmp & 0xffffffff);
+ cdw++;
+ }
+
+ printf("Admin request:\n");
+ printf(" opcode: 0x%02x\n", req.opcode);
+ printf(" ctrl: 0x%04x\n", le16_to_cpu(req.ctrl_id));
+ printf(" cdw10: 0x%08x\n", le32_to_cpu(req.cdw10));
+ printf(" cdw11: 0x%08x\n", le32_to_cpu(req.cdw11));
+ printf(" cdw12: 0x%08x\n", le32_to_cpu(req.cdw12));
+ printf(" cdw13: 0x%08x\n", le32_to_cpu(req.cdw13));
+ printf(" cdw14: 0x%08x\n", le32_to_cpu(req.cdw14));
+ printf(" cdw15: 0x%08x\n", le32_to_cpu(req.cdw15));
+ printf(" raw:\n");
+ hexdump((void *)&req, sizeof(req));
+
+ memset(buf, 0, sizeof(buf));
+ resp = (void *)buf;
+
+ ctrl = nvme_mi_init_ctrl(ep, ctrl_id);
+ if (!ctrl) {
+ warn("can't create controller");
+ return -1;
+ }
+
+ resp_data_len = sizeof(buf) - sizeof(*resp);
+
+ rc = nvme_mi_admin_xfer(ctrl, &req, 0, resp, 0, &resp_data_len);
+ if (rc) {
+ warn("nvme_admin_xfer failed: %d", rc);
+ return -1;
+ }
+
+ printf("Admin response:\n");
+ printf(" Status: 0x%02x\n", resp->status);
+ printf(" cdw0: 0x%08x\n", le32_to_cpu(resp->cdw0));
+ printf(" cdw1: 0x%08x\n", le32_to_cpu(resp->cdw1));
+ printf(" cdw3: 0x%08x\n", le32_to_cpu(resp->cdw3));
+ printf(" data [%zd bytes]\n", resp_data_len);
+
+ hexdump(buf + sizeof(*resp), resp_data_len);
+ return 0;
+}
+
+static struct {
+ uint8_t id;
+ const char *name;
+} sec_protos[] = {
+ { 0x00, "Security protocol information" },
+ { 0xea, "NVMe" },
+ { 0xec, "JEDEC Universal Flash Storage" },
+ { 0xed, "SDCard TrustedFlash Security" },
+ { 0xee, "IEEE 1667" },
+ { 0xef, "ATA Device Server Password Security" },
+};
+
+static const char *sec_proto_description(uint8_t id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(sec_protos); i++) {
+ if (sec_protos[i].id == id)
+ return sec_protos[i].name;
+ }
+
+ if (id >= 0xf0)
+ return "Vendor specific";
+
+ return "unknown";
+}
+
+int do_security_info(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ struct nvme_security_receive_args args = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ int i, rc, n_proto;
+ unsigned long tmp;
+ uint16_t ctrl_id;
+ struct {
+ uint8_t rsvd[6];
+ uint16_t len;
+ uint8_t protocols[256];
+ } proto_info;
+
+ if (argc != 2) {
+ fprintf(stderr, "no controller ID specified\n");
+ return -1;
+ }
+
+ tmp = atoi(argv[1]);
+ if (tmp > 0xffff) {
+ fprintf(stderr, "invalid controller ID\n");
+ return -1;
+ }
+
+ ctrl_id = tmp & 0xffff;
+
+ ctrl = nvme_mi_init_ctrl(ep, ctrl_id);
+ if (!ctrl) {
+ warn("can't create controller");
+ return -1;
+ }
+
+ /* protocol 0x00, spsp 0x0000: retrieve supported protocols */
+ args.args_size = sizeof(args);
+ args.data = &proto_info;
+ args.data_len = sizeof(proto_info);
+
+ rc = nvme_mi_admin_security_recv(ctrl, &args);
+ if (rc) {
+ warnx("can't perform Security Receive command: rc %d", rc);
+ return -1;
+ }
+
+ if (args.data_len < 6) {
+ warnx("Short response in security receive command (%d bytes)",
+ args.data_len);
+ return -1;
+ }
+
+ n_proto = be16_to_cpu(proto_info.len);
+ if (args.data_len < 6 + n_proto) {
+ warnx("Short response in security receive command (%d bytes), "
+ "for %d protocols", args.data_len, n_proto);
+ return -1;
+ }
+
+ printf("Supported protocols:\n");
+ for (i = 0; i < n_proto; i++) {
+ uint8_t id = proto_info.protocols[i];
+ printf(" 0x%02x: %s\n", id, sec_proto_description(id));
+ }
+
+ return 0;
+}
+
+struct {
+ enum nvme_mi_config_smbus_freq id;
+ const char *str;
+} smbus_freqs[] = {
+ { NVME_MI_CONFIG_SMBUS_FREQ_100kHz, "100k" },
+ { NVME_MI_CONFIG_SMBUS_FREQ_400kHz, "400k" },
+ { NVME_MI_CONFIG_SMBUS_FREQ_1MHz, "1M" },
+};
+
+static const char *smbus_freq_str(enum nvme_mi_config_smbus_freq freq)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(smbus_freqs); i++) {
+ if (smbus_freqs[i].id == freq)
+ return smbus_freqs[i].str;
+ }
+
+ return NULL;
+}
+
+static int smbus_freq_val(const char *str, enum nvme_mi_config_smbus_freq *freq)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(smbus_freqs); i++) {
+ if (!strcmp(smbus_freqs[i].str, str)) {
+ *freq = smbus_freqs[i].id;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+int do_config_get(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ enum nvme_mi_config_smbus_freq freq;
+ uint16_t mtu;
+ uint8_t port;
+ int rc;
+
+ if (argc > 1)
+ port = atoi(argv[1]) & 0xff;
+ else
+ port = 0;
+
+ rc = nvme_mi_mi_config_get_smbus_freq(ep, port, &freq);
+ if (rc) {
+ warn("can't query SMBus freq for port %d\n", port);
+ } else {
+ const char *fstr = smbus_freq_str(freq);
+ printf("SMBus access frequency (port %d): %s [0x%x]\n", port,
+ fstr ?: "unknown", freq);
+ }
+
+ rc = nvme_mi_mi_config_get_mctp_mtu(ep, port, &mtu);
+ if (rc)
+ warn("can't query MCTP MTU for port %d\n", port);
+ else
+ printf("MCTP MTU (port %d): %d\n", port, mtu);
+
+ return 0;
+}
+
+int do_config_set(nvme_mi_ep_t ep, int argc, char **argv)
+{
+ const char *name, *val;
+ uint8_t port;
+ int rc;
+
+ if (argc != 4) {
+ fprintf(stderr, "config set requires <port> <type> <val>\n");
+ return -1;
+ }
+
+ port = atoi(argv[1]) & 0xff;
+ name = argv[2];
+ val = argv[3];
+
+ if (!strcmp(name, "freq")) {
+ enum nvme_mi_config_smbus_freq freq;
+ rc = smbus_freq_val(val, &freq);
+ if (rc) {
+ fprintf(stderr, "unknown SMBus freq %s. "
+ "Try 100k, 400k or 1M\n", val);
+ return -1;
+ }
+ rc = nvme_mi_mi_config_set_smbus_freq(ep, port, freq);
+
+ } else if (!strcmp(name, "mtu")) {
+ uint16_t mtu;
+ mtu = atoi(val) & 0xffff;
+ /* controllers should reject this, but prevent the potential
+ * footgun of disabling futher comunication with the device
+ */
+ if (mtu < 64) {
+ fprintf(stderr, "MTU value too small\n");
+ return -1;
+ }
+ rc = nvme_mi_mi_config_set_mctp_mtu(ep, port, mtu);
+
+ } else {
+ fprintf(stderr, "Invalid configuration '%s', "
+ "try freq or mtu\n", name);
+ return -1;
+ }
+
+ if (rc)
+ fprintf(stderr, "config set failed with status %d\n", rc);
+
+ return rc;
+}
+
+enum action {
+ ACTION_INFO,
+ ACTION_CONTROLLERS,
+ ACTION_IDENTIFY,
+ ACTION_GET_LOG_PAGE,
+ ACTION_ADMIN_RAW,
+ ACTION_SECURITY_INFO,
+ ACTION_CONFIG_GET,
+ ACTION_CONFIG_SET,
+};
+
+static int do_action_endpoint(enum action action, nvme_mi_ep_t ep, int argc, char** argv)
+{
+ int rc;
+
+ switch (action) {
+ case ACTION_INFO:
+ rc = do_info(ep);
+ break;
+ case ACTION_CONTROLLERS:
+ rc = do_controllers(ep);
+ break;
+ case ACTION_IDENTIFY:
+ rc = do_identify(ep, argc, argv);
+ break;
+ case ACTION_GET_LOG_PAGE:
+ rc = do_get_log_page(ep, argc, argv);
+ break;
+ case ACTION_ADMIN_RAW:
+ rc = do_admin_raw(ep, argc, argv);
+ break;
+ case ACTION_SECURITY_INFO:
+ rc = do_security_info(ep, argc, argv);
+ break;
+ case ACTION_CONFIG_GET:
+ rc = do_config_get(ep, argc, argv);
+ break;
+ case ACTION_CONFIG_SET:
+ rc = do_config_set(ep, argc, argv);
+ break;
+ default:
+ /* This shouldn't be possible, as we should be covering all
+ * of the enum action options above. Hoever, keep the compilers
+ * happy and fail gracefully. */
+ fprintf(stderr, "invalid action %d?\n", action);
+ rc = -1;
+ }
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ enum action action;
+ nvme_root_t root;
+ nvme_mi_ep_t ep;
+ bool dbus = false, usage = true;
+ uint8_t eid;
+ int rc = 0, net;
+
+ if (argc >= 2 && strcmp(argv[1], "dbus") == 0) {
+ usage = false;
+ dbus= true;
+ argv += 1;
+ argc -= 1;
+ } else if (argc >= 3) {
+ usage = false;
+ net = atoi(argv[1]);
+ eid = atoi(argv[2]) & 0xff;
+ argv += 2;
+ argc -= 2;
+ }
+
+ if (usage) {
+ fprintf(stderr,
+ "usage: %s <net> <eid> [action] [action args]\n"
+ " %s 'dbus' [action] [action args]\n",
+ argv[0], argv[0]);
+ fprintf(stderr, "where action is:\n"
+ " info\n"
+ " controllers\n"
+ " identify <controller-id> [--partial]\n"
+ " get-log-page <controller-id> [<log-id>]\n"
+ " admin <controller-id> <opcode> [<cdw10>, <cdw11>, ...]\n"
+ " security-info <controller-id>\n"
+ " get-config [port]\n"
+ " set-config <port> <type> <val>\n"
+ "\n"
+ " 'dbus' target will query D-Bus for known MCTP endpoints\n"
+ );
+ return EXIT_FAILURE;
+ }
+
+ if (argc == 1) {
+ action = ACTION_INFO;
+ } else {
+ char *action_str = argv[1];
+ argc--;
+ argv++;
+
+ if (!strcmp(action_str, "info")) {
+ action = ACTION_INFO;
+ } else if (!strcmp(action_str, "controllers")) {
+ action = ACTION_CONTROLLERS;
+ } else if (!strcmp(action_str, "identify")) {
+ action = ACTION_IDENTIFY;
+ } else if (!strcmp(action_str, "get-log-page")) {
+ action = ACTION_GET_LOG_PAGE;
+ } else if (!strcmp(action_str, "admin")) {
+ action = ACTION_ADMIN_RAW;
+ } else if (!strcmp(action_str, "security-info")) {
+ action = ACTION_SECURITY_INFO;
+ } else if (!strcmp(action_str, "get-config")) {
+ action = ACTION_CONFIG_GET;
+ } else if (!strcmp(action_str, "set-config")) {
+ action = ACTION_CONFIG_SET;
+ } else {
+ fprintf(stderr, "invalid action '%s'\n", action_str);
+ return EXIT_FAILURE;
+ }
+ }
+ if (dbus) {
+ nvme_root_t root;
+ int i = 0;
+
+ root = nvme_mi_scan_mctp();
+ if (!root)
+ errx(EXIT_FAILURE, "can't scan D-Bus entries");
+
+ nvme_mi_for_each_endpoint(root, ep) i++;
+ printf("Found %d endpoints in D-Bus:\n", i);
+ nvme_mi_for_each_endpoint(root, ep) {
+ char *desc = nvme_mi_endpoint_desc(ep);
+ printf("%s\n", desc);
+ rc = do_action_endpoint(action, ep, argc, argv);
+ printf("---\n");
+ free(desc);
+ }
+ nvme_mi_free_root(root);
+ } else {
+ 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)
+ errx(EXIT_FAILURE, "can't open MCTP endpoint %d:%d", net, eid);
+ rc = do_action_endpoint(action, ep, argc, argv);
+ nvme_mi_close(ep);
+ nvme_mi_free_root(root);
+ }
+
+ return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+