// 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 #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 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 \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 [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 [--partial]\n" " get-log-page []\n" " admin [, , ...]\n" " security-info \n" " get-config [port]\n" " set-config \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; }