]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
plugin/lm: Introduce Live Migration plugin
authorNate Thornton <n.thornton@samsung.com>
Fri, 8 Nov 2024 11:50:01 +0000 (03:50 -0800)
committerDaniel Wagner <wagi@monom.org>
Fri, 20 Dec 2024 11:11:42 +0000 (12:11 +0100)
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 <n.thornton@samsung.com>
plugins/lm/lm-nvme.c [new file with mode: 0644]
plugins/lm/lm-nvme.h [new file with mode: 0644]
plugins/lm/lm-print-binary.c [new file with mode: 0644]
plugins/lm/lm-print-json.c [new file with mode: 0644]
plugins/lm/lm-print-stdout.c [new file with mode: 0644]
plugins/lm/lm-print.c [new file with mode: 0644]
plugins/lm/lm-print.h [new file with mode: 0644]
plugins/lm/meson.build [new file with mode: 0644]
plugins/meson.build

diff --git a/plugins/lm/lm-nvme.c b/plugins/lm/lm-nvme.c
new file mode 100644 (file)
index 0000000..47ea8a4
--- /dev/null
@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., LTD.
+ *
+ * Authors: Nate Thornton <n.thornton@samsung.com>
+ */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/fs.h>
+#include <inttypes.h>
+#include <asm/byteorder.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include <sys/sysinfo.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+
+#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 (file)
index 0000000..56b3a89
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., LTD.
+ *
+ * Authors: Nate Thornton <n.thornton@samsung.com>
+ */
+
+#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 (file)
index 0000000..9b48144
--- /dev/null
@@ -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 (file)
index 0000000..1990b96
--- /dev/null
@@ -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 (file)
index 0000000..4495199
--- /dev/null
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "lm-print.h"
+
+#include <inttypes.h>
+
+#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 (file)
index 0000000..7019af8
--- /dev/null
@@ -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 (file)
index 0000000..8a64702
--- /dev/null
@@ -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 (file)
index 0000000..a161431
--- /dev/null
@@ -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
index 5082a5113e872c125a8138cac63641d066773e58..53cef08f026a26c81eeada94e300aa1f95219a02 100644 (file)
@@ -36,6 +36,7 @@ sources += [
   ]
 
 subdir('ocp')
+subdir('lm')
 
 if conf.get('HAVE_SED_OPAL') != 0
   subdir('sed')