diff --git a/Documentation/nvme-phy-rx-eom-log.txt b/Documentation/nvme-phy-rx-eom-log.txt new file mode 100644 index 000000000..015c851e1 --- /dev/null +++ b/Documentation/nvme-phy-rx-eom-log.txt @@ -0,0 +1,59 @@ +nvme-phy-rx-eom-log(1) +====================== + +NAME +---- +nvme-phy-rx-eom-log - Retrieves a Physical Interface Receiver Eye Opening Measurement log page from an NVMe device + +SYNOPSIS +-------- +[verse] +'nvme phy-rx-eom-log' [--lsp= | -s ] + [--controller= | -c ] + [--output-format= | -o ] + +DESCRIPTION +----------- +Retrieves a Physical Interface Receiver Eye Opening Measurement log page from +an NVMe device and provides the returned structure. + +The parameter is mandatory and may be either the NVMe character +device (ex: /dev/nvme0), or a namespace block device (ex: /dev/nvme0n1). + +On success it returns 0, error code otherwise. + +OPTIONS +------- +-s :: +--lsp=:: + The log specified field configuring the controller's action to take + during processing of the command and the measurement quality. + +-c :: +--controller=:: + Controller ID of the controller associated wit the PCIe port to be + measured. + +-o :: +--output-format=:: + Set the reporting format to 'normal', 'json', or + 'binary'. Only one output format can be used at a time. + +EXAMPLES +-------- +* Start a best quality measurement and retrieve the log page header ++ +------------ +# nvme phy-rx-eom-log /dev/nvme0 --lsp=10 +------------ + +* Retrieve a finished best quality measurement on controller with ID 3 ++ +------------ +# nvme phy-rx-eom-log /dev/nvme0 --lsp=2 --controller=3 +------------ + + +NVME +---- +Part of the nvme-user suite diff --git a/nvme-builtin.h b/nvme-builtin.h index 784e19db5..9b9a145f0 100644 --- a/nvme-builtin.h +++ b/nvme-builtin.h @@ -49,6 +49,7 @@ COMMAND_LIST( ENTRY("lba-status-log", "Retrieve LBA Status Information Log, show it", get_lba_status_log) ENTRY("resv-notif-log", "Retrieve Reservation Notification Log, show it", get_resv_notif_log) ENTRY("boot-part-log", "Retrieve Boot Partition Log, show it", get_boot_part_log) + ENTRY("phy-rx-eom-log", "Retrieve Physical Interface Receiver Eye Opening Measurement, show it", get_phy_rx_eom_log) ENTRY("get-feature", "Get feature and show the resulting value", get_feature) ENTRY("device-self-test", "Perform the necessary tests to observe the performance", device_self_test) ENTRY("self-test-log", "Retrieve the SELF-TEST Log, show it", self_test_log) diff --git a/nvme-print-binary.c b/nvme-print-binary.c index 3a4617a33..45d86d326 100644 --- a/nvme-print-binary.c +++ b/nvme-print-binary.c @@ -63,6 +63,18 @@ static void binary_boot_part_log(void *bp_log, const char *devname, d_raw((unsigned char *)bp_log, size); } +static void binary_phy_rx_eom_log(struct nvme_phy_rx_eom_log *log, + __u16 controller) +{ + size_t len; + if (log->eomip == NVME_PHY_RX_EOM_COMPLETED) + len = log->hsize + log->dsize * log->nd; + else + len = log->hsize; + + d_raw((unsigned char *)log, len); +} + static void binary_media_unit_stat_log(struct nvme_media_unit_stat_log *mus_log) { d_raw((unsigned char *)mus_log, sizeof(*mus_log)); @@ -288,6 +300,7 @@ static void binary_discovery_log(struct nvmf_discovery_log *log, int numrec) static struct print_ops binary_print_ops = { .ana_log = binary_ana_log, .boot_part_log = binary_boot_part_log, + .phy_rx_eom_log = binary_phy_rx_eom_log, .ctrl_list = binary_list_ctrl, .ctrl_registers = binary_ctrl_registers, .directive = binary_directive, diff --git a/nvme-print-json.c b/nvme-print-json.c index 7e6b05cbb..485c013fa 100644 --- a/nvme-print-json.c +++ b/nvme-print-json.c @@ -1409,6 +1409,116 @@ static void json_boot_part_log(void *bp_log, const char *devname, json_free_object(root); } +/* Printable Eye string is allocated and returned, caller must free */ +static char *json_eom_printable_eye(struct nvme_eom_lane_desc *lane, + struct json_object *root) +{ + char *eye = (char *)lane->eye_desc; + + char *printable = malloc(lane->nrows * lane->ncols + lane->ncols); + char *printable_start = printable; + if (!printable) + goto exit; + + int i, j; + for (i = 0; i < lane->nrows; i++) { + for (j = 0; j < lane->ncols; j++, printable++) + sprintf(printable, "%c", eye[i * lane->ncols + j]); + sprintf(printable++, "\n"); + } + + json_object_add_value_string(root, "printable_eye", printable_start); + +exit: + return printable_start; +} + + +static void json_phy_rx_eom_descs(struct nvme_phy_rx_eom_log *log, + struct json_object *root, char **allocated_eyes) +{ + void *p = log->descs; + uint16_t num_descs = le16_to_cpu(log->nd); + int i; + struct json_object *descs; + + descs = json_create_array(); + json_object_add_value_array(root, "descs", descs); + + for (i = 0; i < num_descs; i++) { + struct nvme_eom_lane_desc *desc = p; + struct json_object *jdesc = json_create_object(); + + json_object_add_value_uint(jdesc, "lid", desc->mstatus); + json_object_add_value_uint(jdesc, "lane", desc->lane); + json_object_add_value_uint(jdesc, "eye", desc->eye); + json_object_add_value_uint(jdesc, "top", le16_to_cpu(desc->top)); + json_object_add_value_uint(jdesc, "bottom", le16_to_cpu(desc->bottom)); + json_object_add_value_uint(jdesc, "left", le16_to_cpu(desc->left)); + json_object_add_value_uint(jdesc, "right", le16_to_cpu(desc->right)); + json_object_add_value_uint(jdesc, "nrows", le16_to_cpu(desc->nrows)); + json_object_add_value_uint(jdesc, "ncols", le16_to_cpu(desc->ncols)); + json_object_add_value_uint(jdesc, "edlen", le16_to_cpu(desc->edlen)); + + if (log->odp & NVME_EOM_PRINTABLE_EYE_PRESENT) + allocated_eyes[i] = json_eom_printable_eye(desc, root); + + /* Eye Data field is vendor specific, doesn't map to JSON */ + + json_array_add_value_object(descs, jdesc); + + p += log->dsize; + } +} + +static void json_phy_rx_eom_log(struct nvme_phy_rx_eom_log *log, __u16 controller) +{ + char **allocated_eyes = NULL; + int i; + + struct json_object *root; + root = json_create_object(); + + json_object_add_value_uint(root, "lid", log->lid); + json_object_add_value_uint(root, "eomip", log->eomip); + json_object_add_value_uint(root, "hsize", le16_to_cpu(log->hsize)); + json_object_add_value_uint(root, "rsize", le32_to_cpu(log->rsize)); + json_object_add_value_uint(root, "eomdgn", log->eomdgn); + json_object_add_value_uint(root, "lr", log->lr); + json_object_add_value_uint(root, "lanes", log->lanes); + json_object_add_value_uint(root, "epl", log->epl); + json_object_add_value_uint(root, "lspfc", log->lspfc); + json_object_add_value_uint(root, "li", log->li); + json_object_add_value_uint(root, "lsic", le16_to_cpu(log->lsic)); + json_object_add_value_uint(root, "dsize", le32_to_cpu(log->dsize)); + json_object_add_value_uint(root, "nd", le16_to_cpu(log->nd)); + json_object_add_value_uint(root, "maxtb", le16_to_cpu(log->maxtb)); + json_object_add_value_uint(root, "maxlr", le16_to_cpu(log->maxlr)); + json_object_add_value_uint(root, "etgood", le16_to_cpu(log->etgood)); + json_object_add_value_uint(root, "etbetter", le16_to_cpu(log->etbetter)); + json_object_add_value_uint(root, "etbest", le16_to_cpu(log->etbest)); + + if (log->eomip == NVME_PHY_RX_EOM_COMPLETED) { + /* Save Printable Eye strings allocated to free later */ + allocated_eyes = malloc(log->nd * sizeof(char *)); + if (allocated_eyes) + json_phy_rx_eom_descs(log, root, allocated_eyes); + } + + json_print_object(root, NULL); + printf("\n"); + + if (allocated_eyes) { + for (i = 0; i < log->nd; i++) { + /* Free any Printable Eye strings allocated */ + if (allocated_eyes[i]) + free(allocated_eyes[i]); + } + free(allocated_eyes); + } + json_free_object(root); +} + static void json_media_unit_stat_log(struct nvme_media_unit_stat_log *mus) { @@ -2921,6 +3031,7 @@ static void json_output_perror(const char *msg) static struct print_ops json_print_ops = { .ana_log = json_ana_log, .boot_part_log = json_boot_part_log, + .phy_rx_eom_log = json_phy_rx_eom_log, .ctrl_list = json_nvme_list_ctrl, .ctrl_registers = json_ctrl_registers, .discovery_log = json_discovery_log, diff --git a/nvme-print-stdout.c b/nvme-print-stdout.c index dd5d7dccb..4990a24a8 100644 --- a/nvme-print-stdout.c +++ b/nvme-print-stdout.c @@ -705,6 +705,111 @@ static void stdout_boot_part_log(void *bp_log, const char *devname, printf("Active BPID: %u\n", (le32_to_cpu(hdr->bpinfo) >> 31) & 0x1); } +static const char *eomip_to_string(__u8 eomip) +{ + const char *string; + switch (eomip) { + case NVME_PHY_RX_EOM_NOT_STARTED: + string = "Not Started"; + break; + case NVME_PHY_RX_EOM_IN_PROGRESS: + string = "In Progress"; + break; + case NVME_PHY_RX_EOM_COMPLETED: + string = "Completed"; + break; + default: + string = "Unknown"; + } + return string; +} + +static void stdout_phy_rx_eom_odp(uint8_t odp) +{ + __u8 rsvd = (odp >> 2) & 0x3F; + __u8 edfp = (odp >> 1) & 0x1; + __u8 pefp = odp & 0x1; + + if (rsvd) + printf(" [7:2] : %#x\tReserved\n", rsvd); + printf(" [1:1] : %#x\tEye Data Field %sPresent\n", + edfp, edfp ? "" : "Not "); + printf(" [0:0] : %#x\tPrintable Eye Field %sPresent\n", + pefp, pefp ? "" : "Not "); +} + +static void stdout_eom_printable_eye(struct nvme_eom_lane_desc *lane) +{ + char *eye = (char *)lane->eye_desc; + int i, j; + for (i = 0; i < lane->nrows; i++) { + for (j = 0; j < lane->ncols; j++) + printf("%c", eye[i * lane->ncols + j]); + printf("\n"); + } +} + +static void stdout_phy_rx_eom_descs(struct nvme_phy_rx_eom_log *log) +{ + void *p = log->descs; + int i; + + for (i = 0; i < log->nd; i++) { + struct nvme_eom_lane_desc *desc = p; + + printf("Measurement Status: %s\n", + desc->mstatus ? "Successful" : "Not Successful"); + printf("Lane: %u\n", desc->lane); + printf("Eye: %u\n", desc->eye); + printf("Top: %u\n", le16_to_cpu(desc->top)); + printf("Bottom: %u\n", le16_to_cpu(desc->bottom)); + printf("Left: %u\n", le16_to_cpu(desc->left)); + printf("Right: %u\n", le16_to_cpu(desc->right)); + printf("Number of Rows: %u\n", le16_to_cpu(desc->nrows)); + printf("Number of Columns: %u\n", le16_to_cpu(desc->ncols)); + printf("Eye Data Length: %u\n", le16_to_cpu(desc->edlen)); + + if (log->odp & NVME_EOM_PRINTABLE_EYE_PRESENT) + stdout_eom_printable_eye(desc); + + /* Eye Data field is vendor specific */ + + p += log->dsize; + } +} + +static void stdout_phy_rx_eom_log(struct nvme_phy_rx_eom_log *log, __u16 controller) +{ + int human = stdout_print_ops.flags & VERBOSE; + + printf("Physical Interface Receiver Eye Opening Measurement Log for controller ID: %u\n", controller); + printf("Log ID: %u\n", log->lid); + printf("EOM In Progress: %s\n", eomip_to_string(log->eomip)); + printf("Header Size: %u\n", le16_to_cpu(log->hsize)); + printf("Result Size: %u\n", le32_to_cpu(log->rsize)); + printf("EOM Data Generation Number: %u\n", log->eomdgn); + printf("Log Revision: %u\n", log->lr); + printf("Optional Data Present: %u\n", log->odp); + if (human) + stdout_phy_rx_eom_odp(log->odp); + printf("Lanes: %u\n", log->lanes); + printf("Eyes Per Lane: %u\n", log->epl); + printf("Log Specific Parameter Field Copy: %u\n", log->lspfc); + printf("Link Information: %u\n", log->li); + printf("Log Specific Identifier Copy: %u\n", le16_to_cpu(log->lsic)); + printf("Descriptor Size: %u\n", le32_to_cpu(log->dsize)); + printf("Number of Descriptors: %u\n", le16_to_cpu(log->nd)); + printf("Maximum Top Bottom: %u\n", le16_to_cpu(log->maxtb)); + printf("Maximum Left Right: %u\n", le16_to_cpu(log->maxlr)); + printf("Estimated Time for Good Quality: %u\n", le16_to_cpu(log->etgood)); + printf("Estimated Time for Better Quality: %u\n", le16_to_cpu(log->etbetter)); + printf("Estimated Time for Best Quality: %u\n", le16_to_cpu(log->etbest)); + + if (log->eomip == NVME_PHY_RX_EOM_COMPLETED) { + stdout_phy_rx_eom_descs(log); + } +} + static void stdout_media_unit_stat_log(struct nvme_media_unit_stat_log *mus_log) { int i; @@ -4942,6 +5047,7 @@ static void stdout_connect_msg(nvme_ctrl_t c) static struct print_ops stdout_print_ops = { .ana_log = stdout_ana_log, .boot_part_log = stdout_boot_part_log, + .phy_rx_eom_log = stdout_phy_rx_eom_log, .ctrl_list = stdout_list_ctrl, .ctrl_registers = stdout_ctrl_registers, .directive = stdout_directive_show, diff --git a/nvme-print.c b/nvme-print.c index 286e4d55f..572b641b1 100644 --- a/nvme-print.c +++ b/nvme-print.c @@ -258,6 +258,12 @@ void nvme_show_boot_part_log(void *bp_log, const char *devname, nvme_print(boot_part_log, flags, bp_log, devname, size); } +void nvme_show_phy_rx_eom_log(struct nvme_phy_rx_eom_log *log, __u16 controller, + enum nvme_print_flags flags) +{ + nvme_print(phy_rx_eom_log, flags, log, controller); +} + void nvme_show_media_unit_stat_log(struct nvme_media_unit_stat_log *mus_log, enum nvme_print_flags flags) { diff --git a/nvme-print.h b/nvme-print.h index 9b81b45bd..1c7e5dcc9 100644 --- a/nvme-print.h +++ b/nvme-print.h @@ -23,6 +23,7 @@ struct print_ops { /* libnvme types.h print functions */ void (*ana_log)(struct nvme_ana_log *ana_log, const char *devname, size_t len); void (*boot_part_log)(void *bp_log, const char *devname, __u32 size); + void (*phy_rx_eom_log)(struct nvme_phy_rx_eom_log *log, __u16 controller); void (*ctrl_list)(struct nvme_ctrl_list *ctrl_list); void (*ctrl_registers)(void *bar, bool fabrics); void (*directive)(__u8 type, __u8 oper, __u16 spec, __u32 nsid, __u32 result, void *buf, __u32 len); @@ -166,6 +167,8 @@ void nvme_show_resv_notif_log(struct nvme_resv_notification_log *resv, const char *devname, enum nvme_print_flags flags); void nvme_show_boot_part_log(void *bp_log, const char *devname, __u32 size, enum nvme_print_flags flags); +void nvme_show_phy_rx_eom_log(struct nvme_phy_rx_eom_log *log, + __u16 controller, enum nvme_print_flags flags); void nvme_show_fid_support_effects_log(struct nvme_fid_supported_effects_log *fid_log, const char *devname, enum nvme_print_flags flags); void nvme_show_mi_cmd_support_effects_log(struct nvme_mi_cmd_supported_effects_log *mi_cmd_log, diff --git a/nvme-wrap.c b/nvme-wrap.c index bec9b69b9..20cc617e2 100644 --- a/nvme-wrap.c +++ b/nvme-wrap.c @@ -302,6 +302,12 @@ int nvme_cli_get_log_boot_partition(struct nvme_dev *dev, bool rae, __u8 lsp, return do_admin_op(get_log_boot_partition, dev, rae, lsp, len, part); } +int nvme_cli_get_log_phy_rx_eom(struct nvme_dev *dev, __u8 lsp, __u16 controller, + __u32 len, struct nvme_phy_rx_eom_log *part) +{ + return do_admin_op(get_log_phy_rx_eom, dev, lsp, controller, len, part); +} + int nvme_cli_get_log_discovery(struct nvme_dev *dev, bool rae, __u32 offset, __u32 len, void *log) { diff --git a/nvme-wrap.h b/nvme-wrap.h index 729dab39b..c18115d22 100644 --- a/nvme-wrap.h +++ b/nvme-wrap.h @@ -105,6 +105,8 @@ int nvme_cli_get_log_mi_cmd_supported_effects(struct nvme_dev *dev, bool rae, int nvme_cli_get_log_boot_partition(struct nvme_dev *dev, bool rae, __u8 lsp, __u32 len, struct nvme_boot_partition *part); +int nvme_cli_get_log_phy_rx_eom(struct nvme_dev *dev, __u8 lsp, __u16 controller, + __u32 len, struct nvme_phy_rx_eom_log *part); int nvme_cli_get_log_discovery(struct nvme_dev *dev, bool rae, __u32 offset, __u32 len, void *log); int nvme_cli_get_log_media_unit_stat(struct nvme_dev *dev, __u16 domid, diff --git a/nvme.c b/nvme.c index 1e4422e0f..7b9f0d4a8 100644 --- a/nvme.c +++ b/nvme.c @@ -1965,6 +1965,104 @@ static int get_boot_part_log(int argc, char **argv, struct command *cmd, struct return err; } +static int get_phy_rx_eom_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve Physical Interface Receiver Eye Opening\n" + "Measurement log for the given device in decoded format\n" + "(default), json or binary."; + const char *controller = "Target Controller ID."; + _cleanup_free_ struct nvme_phy_rx_oem_log *phy_rx_eom_log = NULL; + size_t phy_rx_eom_log_len; + enum nvme_print_flags flags; + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err = -1; + __u8 lsp_tmp; + + struct config { + __u8 lsp; + __u16 controller; + char *output_format; + }; + + struct config cfg = { + .lsp = 0, + .controller = NVME_LOG_LSI_NONE, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_BYTE("lsp", 's', &cfg.lsp, lsp), + OPT_SHRT("controller", 'c', &cfg.controller, controller), + 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 = flags = validate_output_format(cfg.output_format); + if (err < 0) { + nvme_show_error("Invalid output format"); + return err; + } + + if (cfg.lsp > 127) { + nvme_show_error("invalid lsp param: %u", cfg.lsp); + return -1; + } else if ((cfg.lsp & 3) == 3) { + nvme_show_error("invalid measurement quality: %u", cfg.lsp & 3); + return -1; + } else if ((cfg.lsp & 12) == 12) { + nvme_show_error("invalid action: %u", cfg.lsp & 12); + return -1; + } + + /* Fetching header to calculate total log length */ + phy_rx_eom_log_len = sizeof(struct nvme_phy_rx_eom_log); + phy_rx_eom_log = nvme_alloc(phy_rx_eom_log_len); + if (!phy_rx_eom_log) + return -ENOMEM; + + /* Just read measurement, take given action when fetching full log */ + lsp_tmp = cfg.lsp & 0xf3; + + err = nvme_cli_get_log_phy_rx_eom(dev, lsp_tmp, cfg.controller, + phy_rx_eom_log_len, phy_rx_eom_log); + if (err) { + if (err > 0) + nvme_show_status(err); + else + nvme_show_error("phy-rx-eom-log: %s", nvme_strerror(errno)); + + return err; + } + + if (phy_rx_eom_log->eomip == NVME_PHY_RX_EOM_COMPLETED) { + phy_rx_eom_log_len = le16_to_cpu(phy_rx_eom_log->hsize) + + le32_to_cpu(phy_rx_eom_log->dsize) * + le16_to_cpu(phy_rx_eom_log->nd); + } else { + phy_rx_eom_log_len = le16_to_cpu(phy_rx_eom_log->hsize); + } + + phy_rx_eom_log = nvme_realloc(phy_rx_eom_log, phy_rx_eom_log_len); + if (!phy_rx_eom_log) + return -ENOMEM; + + err = nvme_cli_get_log_phy_rx_eom(dev, cfg.lsp, cfg.controller, + phy_rx_eom_log_len, phy_rx_eom_log); + if (!err) + nvme_show_phy_rx_eom_log(phy_rx_eom_log, cfg.controller, flags); + else if (err > 0) + nvme_show_status(err); + else + nvme_show_error("phy-rx-eom-log: %s", nvme_strerror(errno)); + + return err; +} + static int get_media_unit_stat_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) {