From 6d2a869dd0546ba516ebe845e78e9d62b49bbc8b Mon Sep 17 00:00:00 2001 From: Nate Thornton Date: Fri, 8 Nov 2024 03:50:01 -0800 Subject: [PATCH] plugin/lm: Introduce Live Migration plugin Implementation of TP 4159 PCIe Infrastructure for Live Migration plugin. Includes command support for Track Send, Migration Receive, Migration Send, and Controller Data Queue; Identify Controller LM related fields; Bash and ZSH completions. Changes are isolated to the User Data Migration subset, with Track Memory functionality deferred to a future commit. Signed-off-by: Nate Thornton --- plugins/lm/lm-nvme.c | 669 +++++++++++++++++++++++++++++++++++ plugins/lm/lm-nvme.h | 30 ++ plugins/lm/lm-print-binary.c | 25 ++ plugins/lm/lm-print-json.c | 109 ++++++ plugins/lm/lm-print-stdout.c | 145 ++++++++ plugins/lm/lm-print.c | 38 ++ plugins/lm/lm-print.h | 31 ++ plugins/lm/meson.build | 12 + plugins/meson.build | 1 + 9 files changed, 1060 insertions(+) create mode 100644 plugins/lm/lm-nvme.c create mode 100644 plugins/lm/lm-nvme.h create mode 100644 plugins/lm/lm-print-binary.c create mode 100644 plugins/lm/lm-print-json.c create mode 100644 plugins/lm/lm-print-stdout.c create mode 100644 plugins/lm/lm-print.c create mode 100644 plugins/lm/lm-print.h create mode 100644 plugins/lm/meson.build diff --git a/plugins/lm/lm-nvme.c b/plugins/lm/lm-nvme.c new file mode 100644 index 0000000000..47ea8a4f6a --- /dev/null +++ b/plugins/lm/lm-nvme.c @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2024 Samsung Electronics Co., LTD. + * + * Authors: Nate Thornton + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "nvme-print.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-wrap.h" +#include "util/cleanup.h" + +#define CREATE_CMD +#include "lm-nvme.h" + +#include "lm-print.h" + +static inline const char *arg_str(const char * const *strings, size_t array_size, size_t idx) +{ + if (idx < array_size && strings[idx]) + return strings[idx]; + return "unrecognized"; +} + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define ARGSTR(s, i) arg_str(s, ARRAY_SIZE(s), i) + +static int lm_create_cdq(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Create Controller Data Queue for controller of specific type and size"; + const char *sz = "CDQ Size (in dwords)"; + const char *cntlid = "Controller ID"; + const char *qt = "Queue Type (default: 0 = User Data Migration Queue)"; + const char *consent = "I consent this will not work and understand a CDQ cannot be mapped " + "to user space. If I proceed with the creation of a CDQ, the device " + "will write to invalid memory, inevitably leading to MMU faults or " + "worse."; + + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + struct lba_migration_queue_entry_type_0 *queue = NULL; + int err = -1; + + struct config { + __u32 sz; + __u16 cntlid; + __u8 qt; + bool consent; + char *file; + }; + + struct config cfg = { + .sz = 0, + .cntlid = 0, + .qt = 0, + .consent = false, + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("size", 's', &cfg.sz, sz), + OPT_SHRT("cntlid", 'c', &cfg.cntlid, cntlid), + OPT_BYTE("queue-type", 'q', &cfg.qt, qt), + OPT_FLAG("consent", 0, &cfg.consent, consent), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (!consent) { + nvme_show_error("ERROR: consent required"); + return -EINVAL; + } + + // Not that it really matters, but we setup memory as if the CDQ can be held + // in user space regardless. + queue = nvme_alloc_huge(cfg.sz << 2, &mh); + if (!queue) { + nvme_show_error("ERROR: nvme_alloc of size %dB failed %s", cfg.sz << 2, + strerror(errno)); + return -ENOMEM; + } + + struct nvme_lm_cdq_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .sel = NVME_LM_SEL_CREATE_CDQ, + .mos = NVME_SET(cfg.qt, LM_QT), + .cntlid = cfg.cntlid, + .sz = cfg.sz, + .data = queue + }; + + err = nvme_lm_cdq(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_lm_cdq() failed: %s", nvme_strerror(errno)); + else if (err) + nvme_show_status(err); + else + printf("Create CDQ Successful: CDQID=0x%04x\n", args.cdqid); + + return err; +} + +static int lm_delete_cdq(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Delete Controller Data Queue"; + const char *cdqid = "Controller Data Queue ID"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err = -1; + + struct config { + __u16 cdqid; + }; + + struct config cfg = { + .cdqid = 0 + }; + + OPT_ARGS(opts) = { + OPT_SHRT("cdqid", 'C', &cfg.cdqid, cdqid), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_lm_cdq_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .sel = NVME_LM_SEL_DELETE_CDQ, + .cdqid = cfg.cdqid, + }; + + err = nvme_lm_cdq(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_lm_cdq() failed: %s", nvme_strerror(errno)); + else if (err > 0) + nvme_show_status(err); + else + printf("Delete CDQ Successful: CDQID=0x%04x\n", cfg.cdqid); + + return err; +} + +static const char * const lm_track_send_select_argstr[] = { + [NVME_LM_SEL_LOG_USER_DATA_CHANGES] = "Log User Data Changes", + [NVME_LM_SEL_TRACK_MEMORY_CHANGES] = "Track Memory Changes" +}; + +static int lm_track_send(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Track Send command used to manage the tracking of information by a " + "controller"; + const char *sel = "Type of management operation to perform\n" + " 0h = Log User Data Changes\n" + " 1h = Track Memory Changes"; + const char *mos = "Management operation specific"; + const char *cdqid = "Controller Data Queue ID"; + const char *start = "Equivalent to start tracking with defaults"; + const char *stop = "Equivalent to stop tracking with defaults"; + + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err = -1; + + struct config { + __s8 sel; + __u8 mos; + __u16 cdqid; + bool start; + bool stop; + }; + + struct config cfg = { + .sel = -1, + .mos = 0, + .cdqid = 0, + .start = false, + .stop = false, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_BYTE("mos", 'm', &cfg.mos, mos), + OPT_SHRT("cdqid", 'C', &cfg.cdqid, cdqid), + OPT_FLAG("start", 0, &cfg.start, start), + OPT_FLAG("stop", 0, &cfg.stop, stop), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (cfg.sel == -1) { + nvme_show_error("Select field required"); + return -EINVAL; + } + + if (cfg.sel != NVME_LM_SEL_LOG_USER_DATA_CHANGES) { + nvme_show_error("Unsupported select option %d (%s)", cfg.sel, + ARGSTR(lm_track_send_select_argstr, cfg.sel)); + return -EINVAL; + } + + if (cfg.start && cfg.stop) { + nvme_show_error("Must select one of start & stop, not both"); + return -EINVAL; + } else if (cfg.sel == NVME_LM_SEL_LOG_USER_DATA_CHANGES) { + if (cfg.start) + cfg.mos = NVME_SET(NVME_LM_LACT_START_LOGGING, LM_LACT); + else if (cfg.stop) + cfg.mos = NVME_SET(NVME_LM_LACT_STOP_LOGGING, LM_LACT); + } + + struct nvme_lm_track_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .cdqid = cfg.cdqid, + .sel = cfg.sel, + .mos = cfg.mos, + }; + + err = nvme_lm_track_send(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_lm_track_send() failed %s", strerror(errno)); + else if (err) + nvme_show_status(err); + else + printf("Track Send (%s) Successful\n", + ARGSTR(lm_track_send_select_argstr, cfg.sel)); + + return err; +} + +static const char * const lm_migration_send_select_argstr[] = { + [NVME_LM_SEL_SUSPEND] = "Suspend", + [NVME_LM_SEL_RESUME] = "Resume", + [NVME_LM_SEL_SET_CONTROLLER_STATE] = "Set Controller State" +}; + +static int lm_migration_send(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Migration Send command is used to manage the migration of a controller"; + const char *sel = "Select (SEL) the type of management operation to perform " + "(CDW10[07:00])\n" + " 0h = Suspend\n" + " 1h = Resume\n" + " 2h = Set Controller State"; + const char *cntlid = "Controller Identifier (CDW11[15:00])"; + const char *stype = "Type of suspend (STYPE) (CDW11[23:16]\n" + " 0h = Suspend Notification\n" + " 1h = Suspend"; + const char *dudmq = "Delete user data migration queue (DUDMQ) as part of suspend operation " + "(CDW11[31])"; + const char *seqind = "Sequence Indicator (CDW11[17:16])\n" + " 0h = Not first not last\n" + " 1h = First in two or more\n" + " 2h = Last in two or more\n" + " 3h = Entire state info"; + const char *csuuidi = "Controller State UUID Index (CSUUIDI) (CDW11[31:24])"; + const char *csvi = "Controller State Version Index (CSVI) (CDW11[23:16])"; + const char *uidx = "UUID Index (UIDX) (CDW14[16:00])"; + const char *offset = "Controller State Offset"; + const char *numd = "Number of Dwords (NUMD)"; + const char *input = "Controller State Data input file"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + _cleanup_file_ FILE *file = NULL; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + void *data = NULL; + int err = -1; + + struct config { + __s8 sel; + __u16 cntlid; + __u8 stype; + __u8 seqind; + __u8 csuuidi; + __u8 csvi; + __u8 uidx; + __u64 offset; + __u32 numd; + char *input; + bool dudmq; + }; + + struct config cfg = { + .sel = -1, + .cntlid = 0, + .stype = 0, + .seqind = 0, + .csuuidi = 0, + .csvi = 0, + .uidx = 0, + .offset = 0, + .numd = 0, + .input = NULL, + .dudmq = false + }; + + OPT_ARGS(opts) = { + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_SHRT("cntlid", 'c', &cfg.cntlid, cntlid), + OPT_BYTE("stype", 't', &cfg.stype, stype), + OPT_FLAG("dudmq", 'd', &cfg.dudmq, dudmq), + OPT_BYTE("seq-ind", 'S', &cfg.seqind, seqind), + OPT_BYTE("csuuidi", 'U', &cfg.csuuidi, csuuidi), + OPT_BYTE("csvi", 'V', &cfg.csvi, csvi), + OPT_BYTE("uidx", 'u', &cfg.uidx, uidx), + OPT_LONG("offset", 'o', &cfg.offset, offset), + OPT_UINT("numd", 'n', &cfg.numd, numd), + OPT_FILE("input-file", 'f', &cfg.input, input), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (cfg.sel == -1) { + nvme_show_error("Select field required"); + return -EINVAL; + } + + // Sanity check input parameters + if (cfg.sel == NVME_LM_SEL_SUSPEND || cfg.sel == NVME_LM_SEL_RESUME) { + if (cfg.csuuidi != 0 || cfg.csvi != 0) { + nvme_show_error("Unexpected fields for %s", + ARGSTR(lm_migration_send_select_argstr, cfg.sel)); + return -EINVAL; + } + } else if (cfg.sel == NVME_LM_SEL_SET_CONTROLLER_STATE) { + if (cfg.dudmq || cfg.stype != 0) { + nvme_show_error("Unexpected fields for %s", + ARGSTR(lm_migration_send_select_argstr, cfg.sel)); + return -EINVAL; + } else if (!strlen(cfg.input)) { + nvme_show_error("Expected file for %s", + ARGSTR(lm_migration_send_select_argstr, cfg.sel)); + return -EINVAL; + } + } + + if (cfg.input && strlen(cfg.input)) { + file = fopen(cfg.input, "r"); + if (file == NULL) { + nvme_show_perror(cfg.input); + return -EINVAL; + } + + data = nvme_alloc_huge(cfg.numd << 2, &mh); + if (!data) + return -ENOMEM; + + size_t n_data = fread(data, 1, cfg.numd << 2, file); + + fclose(file); + + if (n_data != (size_t)(cfg.numd << 2)) { + nvme_show_error("failed to read controller state data %s", strerror(errno)); + return -errno; + } + } + + struct nvme_lm_migration_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .sel = cfg.sel, + .mos = NVME_SET(cfg.seqind, LM_MIGRATION_SEND_MOS), + .cntlid = cfg.cntlid, + .csuuidi = cfg.csuuidi, + .uidx = cfg.uidx, + .stype = cfg.stype, + .offset = cfg.offset, + .dudmq = cfg.dudmq, + .numd = cfg.numd, + .data = data, + }; + + err = nvme_lm_migration_send(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_lm_migration_send() failed %s", strerror(errno)); + else if (err > 0) + nvme_show_status(err); + else + printf("Migration Send (%s) Successful\n", + ARGSTR(lm_migration_send_select_argstr, cfg.sel)); + + + return err; +} + +static int lm_migration_recv(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Migration Receive command is used to obtain information used to manage " + " a migratable controller"; + const char *sel = "Select (SEL) the type of management operation to perform " + "(CDW10[07:00])\n" + " 0h = Get Controller State"; + const char *cntlid = "Controller Identifier (CDW10[31:16])"; + const char *csuuidi = "Controller State UUID Index (CSUUIDI) (CDW11[23:16])"; + const char *csvi = "Controller State Version Index (CSVI) (CDW11[7:0])"; + const char *uidx = "UUID Index (UIDX) (CDW14[16:00])"; + const char *offset = "Controller State Offset"; + const char *numd = "Number of Dwords (NUMD)"; + const char *output = "Controller State Data output file"; + const char *human_readable_info = "show info in readable format"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + _cleanup_file_ FILE *fd = NULL; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + nvme_print_flags_t flags; + void *data = NULL; + int err = -1; + + struct config { + __u8 sel; + __u16 cntlid; + __u8 csuuidi; + __u8 csvi; + __u8 uidx; + __u64 offset; + __u32 numd; + char *output; + char *output_format; + bool human_readable; + }; + + struct config cfg = { + .sel = -1, + .cntlid = 0, + .csuuidi = 0, + .csvi = 0, + .uidx = 0, + .offset = 0, + .numd = 0, + .output = NULL, + .output_format = "normal", + .human_readable = false + }; + + OPT_ARGS(opts) = { + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_SHRT("cntlid", 'c', &cfg.cntlid, cntlid), + OPT_BYTE("csuuidi", 'U', &cfg.csuuidi, csuuidi), + OPT_BYTE("csvi", 'V', &cfg.csvi, csvi), + OPT_BYTE("uidx", 'u', &cfg.uidx, uidx), + OPT_LONG("offset", 'o', &cfg.offset, offset), + OPT_UINT("numd", 'n', &cfg.numd, numd), + OPT_FILE("output-file", 'f', &cfg.output, output), + OPT_FMT("output-format", 0, &cfg.output_format, output_format), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable_info), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) { + nvme_show_error("Invalid output format"); + return err; + } + + if (cfg.output_format && cfg.offset != 0 && !(flags & BINARY)) { + nvme_show_error("cannot parse non-zero offset"); + return -EINVAL; + } + + if (cfg.human_readable) + flags |= VERBOSE; + + if (cfg.output && strlen(cfg.output)) { + fd = fopen(cfg.output, "w"); + if (fd < 0) { + nvme_show_perror(cfg.output); + return -errno; + } + } + + data = nvme_alloc_huge((cfg.numd + 1) << 2, &mh); + if (!data) + return -ENOMEM; + + __u32 result = 0; + struct nvme_lm_migration_recv_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .sel = cfg.sel, + .mos = NVME_SET(cfg.csvi, LM_GET_CONTROLLER_STATE_CSVI), + .uidx = cfg.uidx, + .csuuidi = cfg.csuuidi, + .offset = cfg.offset, + .cntlid = cfg.cntlid, + .data = data, + .result = &result, + }; + + err = nvme_lm_migration_recv(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_lm_migration_recv() failed %s", strerror(errno)); + else if (err) + nvme_show_status(err); + else if (cfg.sel == NVME_LM_SEL_GET_CONTROLLER_STATE) { + if (flags == NORMAL) + printf("CDW0: 0x%x: Controller %sSuspended\n", result, + (result & NVME_LM_GET_CONTROLLER_STATE_CSUP) ? "" : "NOT "); + + if (cfg.output && strlen(cfg.output)) { + if (fwrite(data, 1, cfg.numd << 2, fd) != (cfg.numd << 2)) { + nvme_show_error("ERROR: %s: failed to write buffer to output file", + strerror(errno)); + err = -errno; + } + } else { + lm_show_controller_state_data((struct nvme_lm_controller_state_data *)data, + (cfg.numd + 1) << 2, cfg.offset, flags); + } + } + + return 0; +} + +enum lm_controller_data_queue_feature_id { + lm_cdq_feature_id = 0x21 +}; + +static int lm_set_cdq(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "This Feature allows a host to update the status of the head pointer " + "of a CDQ and specify the configuration of a CDQ Tail event."; + const char *cdqid = "Controller Data Queue ID"; + const char *hp = "The slot of the head pointer for the specified CDQ"; + const char *tpt = "If specified, the slot that causes the controller " + " to issue a CDQ Tail Pointer event"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err = -1; + + struct config { + __u16 cdqid; + __u32 hp; + __s32 tpt; + }; + + struct config cfg = { + .cdqid = 0, + .hp = 0, + .tpt = -1, + }; + + OPT_ARGS(opts) = { + OPT_SHRT("cdqid", 'C', &cfg.cdqid, cdqid), + OPT_UINT("hp", 'H', &cfg.hp, hp), + OPT_UINT("tpt", 'T', &cfg.tpt, tpt), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = lm_cdq_feature_id, + .cdw11 = cfg.cdqid | + ((cfg.tpt >= 0) ? NVME_SET(1, LM_CTRL_DATA_QUEUE_ETPT) : 0), + .cdw12 = cfg.hp, + .cdw13 = cfg.tpt + }; + + err = nvme_set_features(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_set_features() failed %s", nvme_strerror(errno)); + else if (err) + nvme_show_status(err); + else + printf("Success. Head Pointer: %d\n", cfg.hp); + + return err; +} + +static int lm_get_cdq(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "This Feature allows a host to retrieve the status of the head pointer " + "of a CDQ and specify the configuration of a CDQ Tail event."; + const char *cdqid = "Controller Data Queue ID"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + nvme_print_flags_t flags; + int err = -1; + + struct config { + __u16 cdqid; + char *output_format; + }; + + struct config cfg = { + .cdqid = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_SHRT("cdqid", 'C', &cfg.cdqid, cdqid), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) { + nvme_show_error("Invalid output format"); + return err; + } + + struct nvme_lm_ctrl_data_queue_fid_data data; + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = lm_cdq_feature_id, + .cdw11 = cfg.cdqid, + .data = &data, + .data_len = sizeof(data) + }; + + err = nvme_get_features(&args); + if (err < 0) + nvme_show_error("ERROR: nvme_get_features() failed %s", nvme_strerror(errno)); + else if (err) + nvme_show_status(err); + else + lm_show_controller_data_queue(&data, flags); + + return err; +} diff --git a/plugins/lm/lm-nvme.h b/plugins/lm/lm-nvme.h new file mode 100644 index 0000000000..56b3a89138 --- /dev/null +++ b/plugins/lm/lm-nvme.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2024 Samsung Electronics Co., LTD. + * + * Authors: Nate Thornton + */ + +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/lm/lm-nvme + +#if !defined(LIVE_MIGRATION_NVME) || defined(CMD_HEADER_MULTI_READ) +#define LIVE_MIGRATION_NVME + +#include "cmd.h" + +PLUGIN(NAME("lm", "Live Migration NVMe extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("create-cdq", "Create Controller Data Queue", lm_create_cdq) + ENTRY("delete-cdq", "Delete Controller Data Queue", lm_delete_cdq) + ENTRY("track-send", "Track Send Command", lm_track_send) + ENTRY("migration-send", "Migration Send", lm_migration_send) + ENTRY("migration-recv", "Migration Receive", lm_migration_recv) + ENTRY("set-cdq", "Set Feature - Controller Data Queue (FID 21h)", lm_set_cdq) + ENTRY("get-cdq", "Get Feature - Controller Data Queue (FID 21h)", lm_get_cdq) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/lm/lm-print-binary.c b/plugins/lm/lm-print-binary.c new file mode 100644 index 0000000000..9b481449fd --- /dev/null +++ b/plugins/lm/lm-print-binary.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "lm-print.h" + +static void binary_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset) +{ + d_raw((unsigned char *)data, len); +} + +static void binary_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data) +{ + d_raw((unsigned char *)data, sizeof(*data)); +} + +static struct lm_print_ops binary_print_ops = { + .controller_state_data = binary_controller_state_data, + .controller_data_queue = binary_controller_data_queue, +}; + +struct lm_print_ops *lm_get_binary_print_ops(nvme_print_flags_t flags) +{ + binary_print_ops.flags = flags; + return &binary_print_ops; +} diff --git a/plugins/lm/lm-print-json.c b/plugins/lm/lm-print-json.c new file mode 100644 index 0000000000..1990b96c43 --- /dev/null +++ b/plugins/lm/lm-print-json.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "lm-print.h" +#include "common.h" + +static void json_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset) +{ + if (offset) { + fprintf(stderr, "cannot understand non-zero offset\n"); + return; + } + + struct json_object *root = json_create_object(); + struct json_object *nvmecs = json_create_object(); + struct json_object *iosqs = json_create_array(); + struct json_object *iocqs = json_create_array(); + + json_object_add_value_uint(root, "version", + le16_to_cpu(data->hdr.ver)); + json_object_add_value_uint(root, "controller state attributes", + data->hdr.csattr); + json_object_add_value_uint128(root, "nvme controller state size", + le128_to_cpu(data->hdr.nvmecss)); + json_object_add_value_uint128(root, "vendor specific size", + le128_to_cpu(data->hdr.vss)); + + json_object_add_value_object(root, "nvme controller state", nvmecs); + + json_object_add_value_uint(nvmecs, "version", + le16_to_cpu(data->data.hdr.ver)); + json_object_add_value_uint(nvmecs, "number of io submission queues", + le16_to_cpu(data->data.hdr.niosq)); + json_object_add_value_uint(nvmecs, "number of io completion queues", + le16_to_cpu(data->data.hdr.niocq)); + + json_object_add_value_array(nvmecs, "io submission queue list", iosqs); + + for (int i = 0; i < data->data.hdr.niosq; i++) { + struct nvme_lm_io_submission_queue_data *sq = &data->data.sqs[i]; + struct json_object *sq_obj = json_create_object(); + + json_object_add_value_uint64(sq_obj, "io submission prp entry 1", + le64_to_cpu(sq->iosqprp1)); + json_object_add_value_uint(sq_obj, "io submission queue size", + le16_to_cpu(sq->iosqqsize)); + json_object_add_value_uint(sq_obj, "io submission queue identifier", + le16_to_cpu(sq->iosqqid)); + json_object_add_value_uint(sq_obj, "io completion queue identifier", + le16_to_cpu(sq->iosqcqid)); + json_object_add_value_uint(sq_obj, "io submission queue attributes", + le16_to_cpu(sq->iosqa)); + json_object_add_value_uint(sq_obj, "io submission queue head pointer", + le16_to_cpu(sq->iosqhp)); + json_object_add_value_uint(sq_obj, "io submission queue tail pointer", + le16_to_cpu(sq->iosqtp)); + + json_array_add_value_object(iosqs, sq_obj); + } + + json_object_add_value_array(nvmecs, "io completion queue list", iocqs); + + for (int i = 0; i < data->data.hdr.niocq; i++) { + struct nvme_lm_io_completion_queue_data *cq = &data->data.cqs[i]; + struct json_object *cq_obj = json_create_object(); + + json_object_add_value_uint64(cq_obj, "io completion prp entry 1", + le64_to_cpu(cq->iocqprp1)); + json_object_add_value_uint(cq_obj, "io completion queue size", + le16_to_cpu(cq->iocqqsize)); + json_object_add_value_uint(cq_obj, "io completion queue identifier", + le16_to_cpu(cq->iocqqid)); + json_object_add_value_uint(cq_obj, "io completion queue head pointer", + le16_to_cpu(cq->iocqhp)); + json_object_add_value_uint(cq_obj, "io completion queue tail pointer", + le16_to_cpu(cq->iocqtp)); + json_object_add_value_uint(cq_obj, "io completion queue attributes", + le32_to_cpu(cq->iocqa)); + + json_array_add_value_object(iocqs, cq_obj); + } + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void json_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data) +{ + struct json_object *root = json_create_object(); + + json_object_add_value_uint(root, "head_pointer", le32_to_cpu(data->hp)); + json_object_add_value_uint(root, "tail_pointer_trigger", le32_to_cpu(data->tpt)); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static struct lm_print_ops json_print_ops = { + .controller_state_data = json_controller_state_data, + .controller_data_queue = json_controller_data_queue +}; + +struct lm_print_ops *lm_get_json_print_ops(nvme_print_flags_t flags) +{ + json_print_ops.flags = flags; + return &json_print_ops; +} diff --git a/plugins/lm/lm-print-stdout.c b/plugins/lm/lm-print-stdout.c new file mode 100644 index 0000000000..44951990d0 --- /dev/null +++ b/plugins/lm/lm-print-stdout.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "lm-print.h" + +#include + +#include "common.h" +#include "util/types.h" + +static struct lm_print_ops stdout_print_ops; + +static void stdout_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset) +{ + if (offset) { + fprintf(stderr, "cannot understand non-zero offset\n"); + return; + } + + int human = stdout_print_ops.flags & VERBOSE; + + if (sizeof(struct nvme_lm_controller_state_data_header) <= len) { + printf("Header:\n"); + printf("%-45s: 0x%x\n", "Version (VER)", data->hdr.ver); + printf("%-45s: 0x%x\n", "Controller State Attributes (CSATTR)", data->hdr.csattr); + if (human) + printf(" [0:0] : 0x%x Controller %sSuspended\n", + data->hdr.csattr & 1, data->hdr.csattr & 1 ? "" : "NOT "); + printf("%-45s: %s\n", "NVMe Controller State Size (NVMECSS)", + uint128_t_to_string(le128_to_cpu(data->hdr.nvmecss))); + printf("%-45s: %s\n", "Vendor Specific Size (VSS)", + uint128_t_to_string(le128_to_cpu(data->hdr.vss))); + + len -= sizeof(struct nvme_lm_controller_state_data_header); + } else { + fprintf(stderr, "WARNING: Header truncated\n"); + len = 0; + } + + if (!len) + return; + + if (sizeof(struct nvme_lm_nvme_controller_state_data_header) <= len) { + int niosq = data->data.hdr.niosq; + int niocq = data->data.hdr.niocq; + + printf("\nNVMe Controller State Data Structure:\n"); + printf("%-45s: 0x%x\n", "Version (VER)", + le16_to_cpu(data->data.hdr.ver)); + printf("%-45s: %d\n", "Number of I/O Submission Queues (NIOSQ)", + le16_to_cpu(niosq)); + printf("%-45s: %d\n", "Number of I/O Completion Queues (NIOCQ)", + le16_to_cpu(niocq)); + + len -= sizeof(struct nvme_lm_nvme_controller_state_data_header); + + if (len < niosq * sizeof(struct nvme_lm_io_submission_queue_data)) { + fprintf(stderr, "WARNING: I/O Submission Queues truncated\n"); + niosq = len / sizeof(struct nvme_lm_io_submission_queue_data); + } + + for (int i = 0; i < niosq; ++i) { + struct nvme_lm_io_submission_queue_data *sq = &(data->data.sqs[i]); + __u16 iosqa = le16_to_cpu(sq->iosqa); + + printf("\nNVMe I/O Submission Queue Data [%d]:\n", i); + printf("%-45s: 0x%"PRIu64"\n", "PRP Entry 1 (IOSQPRP1)", + le64_to_cpu(sq->iosqprp1)); + printf("%-45s: 0x%x\n", "Queue Size (IOSQQSIZE)", + le16_to_cpu(sq->iosqqsize)); + printf("%-45s: 0x%x\n", "Identifier (IOSQQID)", + le16_to_cpu(sq->iosqqid)); + printf("%-45s: 0x%x\n", "Completion Queue Identifier (IOSQCQID)", + le16_to_cpu(sq->iosqcqid)); + printf("%-45s: 0x%x\n", "Attributes (IOSQA)", iosqa); + if (human) { + printf(" [2:1] : 0x%x Queue Priority (IOSQQPRIO)\n", + NVME_GET(iosqa, LM_IOSQPRIO)); + printf(" [0:0] : 0x%x Queue %sPhysically Contiguous (IOSQPC)\n", + NVME_GET(iosqa, LM_IOSQPC), + NVME_GET(iosqa, LM_IOSQPC) ? "" : "NOT "); + } + printf("%-45s: 0x%x\n", "I/O Submission Queue Head Pointer (IOSQHP)", + le16_to_cpu(sq->iosqhp)); + printf("%-45s: 0x%x\n", "I/O Submission Queue Tail Pointer (IOSQTP)", + le16_to_cpu(sq->iosqtp)); + } + + len -= niosq * sizeof(struct nvme_lm_io_submission_queue_data); + + if (len < niocq * sizeof(struct nvme_lm_io_completion_queue_data)) { + fprintf(stderr, "WARNING: I/O Completion Queues truncated\n"); + niocq = len / sizeof(struct nvme_lm_io_completion_queue_data); + } + + for (int i = 0; i < niocq; ++i) { + struct nvme_lm_io_completion_queue_data *cq = &data->data.cqs[niosq + i]; + __u32 iocqa = le32_to_cpu(cq->iocqa); + + printf("\nNVMe I/O Completion Queue Data [%d]:\n", i); + printf("%-45s: 0x%"PRIu64"\n", "I/O Completion PRP Entry 1 (IOCQPRP1)", + le64_to_cpu(cq->iocqprp1)); + printf("%-45s: 0x%x\n", "I/O Completion Queue Size (IOCQQSIZE)", + le16_to_cpu(cq->iocqqsize)); + printf("%-45s: 0x%x\n", "I/O Completion Queue Identifier (IOCQQID)", + le16_to_cpu(cq->iocqqid)); + printf("%-45s: 0x%x\n", "I/O Completion Queue Head Pointer (IOSQHP)", + le16_to_cpu(cq->iocqhp)); + printf("%-45s: 0x%x\n", "I/O Completion Queue Tail Pointer (IOSQTP)", + le16_to_cpu(cq->iocqtp)); + printf("%-45s: 0x%x\n", "I/O Completion Queue Attributes (IOCQA)", iocqa); + if (human) { + printf(" [31:16] : 0x%x I/O Completion Queue Interrupt Vector " + "(IOCQIV)\n", + NVME_GET(iocqa, LM_IOCQIEN)); + printf(" [2:2] : 0x%x Slot 0 Phase Tag (S0PT)\n", + NVME_GET(iocqa, LM_S0PT)); + printf(" [1:1] : 0x%x Interrupts %sEnabled (IOCQIEN)\n", + NVME_GET(iocqa, LM_IOCQIEN), + NVME_GET(iocqa, LM_IOCQIEN) ? "" : "NOT "); + printf(" [0:0] : 0x%x Queue %sPhysically Contiguous (IOCQPC)\n", + NVME_GET(iocqa, LM_IOCQPC), + NVME_GET(iocqa, LM_IOCQPC) ? "" : "NOT "); + } + } + } else + fprintf(stderr, "WARNING: NVMe Controller State Data Structure truncated\n"); +} + +static void stdout_show_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data) +{ + printf("Head Pointer: 0x%x\n", le32_to_cpu(data->hp)); + printf("Tail Pointer Trigger: 0x%x\n", le32_to_cpu(data->tpt)); +} + +static struct lm_print_ops stdout_print_ops = { + .controller_state_data = stdout_controller_state_data, + .controller_data_queue = stdout_show_controller_data_queue +}; + +struct lm_print_ops *lm_get_stdout_print_ops(nvme_print_flags_t flags) +{ + stdout_print_ops.flags = flags; + return &stdout_print_ops; +} diff --git a/plugins/lm/lm-print.c b/plugins/lm/lm-print.c new file mode 100644 index 0000000000..7019af8baf --- /dev/null +++ b/plugins/lm/lm-print.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "lm-print.h" + +#define lm_print(name, flags, ...) \ + do { \ + struct lm_print_ops *ops = lm_print_ops(flags); \ + if (ops && ops->name) \ + ops->name(__VA_ARGS__); \ + else \ + fprintf(stderr, "unhandled output format\n"); \ + } while (false) + +static struct lm_print_ops *lm_print_ops(nvme_print_flags_t flags) +{ + struct lm_print_ops *ops = NULL; + + if (flags & JSON || nvme_is_output_format_json()) + ops = lm_get_json_print_ops(flags); + else if (flags & BINARY) + ops = lm_get_binary_print_ops(flags); + else + ops = lm_get_stdout_print_ops(flags); + + return ops; +} + +void lm_show_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset, nvme_print_flags_t flags) +{ + lm_print(controller_state_data, flags, data, len, offset); +} + +void lm_show_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data, + nvme_print_flags_t flags) +{ + lm_print(controller_data_queue, flags, data); +} diff --git a/plugins/lm/lm-print.h b/plugins/lm/lm-print.h new file mode 100644 index 0000000000..8a647021f6 --- /dev/null +++ b/plugins/lm/lm-print.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef LM_PRINT_H +#define LM_PRINT_H + +#include "nvme.h" +#include "libnvme.h" + +struct lm_print_ops { + void (*controller_state_data)(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset); + void (*controller_data_queue)(struct nvme_lm_ctrl_data_queue_fid_data *data); + nvme_print_flags_t flags; +}; + +struct lm_print_ops *lm_get_stdout_print_ops(nvme_print_flags_t flags); +struct lm_print_ops *lm_get_binary_print_ops(nvme_print_flags_t flags); + +#ifdef CONFIG_JSONC +struct lm_print_ops *lm_get_json_print_ops(nvme_print_flags_t flags); +#else +static inline struct lm_print_ops *lm_get_json_print_ops(nvme_print_flags_t flags) +{ + return NULL; +} +#endif + +void lm_show_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len, + __u32 offset, nvme_print_flags_t flags); +void lm_show_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data, + nvme_print_flags_t flags); +#endif /* LM_PRINT_H */ diff --git a/plugins/lm/meson.build b/plugins/lm/meson.build new file mode 100644 index 0000000000..a161431edb --- /dev/null +++ b/plugins/lm/meson.build @@ -0,0 +1,12 @@ +sources += [ + 'plugins/lm/lm-nvme.c', + 'plugins/lm/lm-print.c', + 'plugins/lm/lm-print-stdout.c', + 'plugins/lm/lm-print-binary.c', +] + +if json_c_dep.found() + sources += [ + 'plugins/lm/lm-print-json.c', + ] +endif \ No newline at end of file diff --git a/plugins/meson.build b/plugins/meson.build index 5082a5113e..53cef08f02 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -36,6 +36,7 @@ sources += [ ] subdir('ocp') +subdir('lm') if conf.get('HAVE_SED_OPAL') != 0 subdir('sed')