#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/mman.h>
#include <sys/bitext.h>
#include <ofmt.h>
#include "nvmeadm.h"
#define PHYEYE_BUFSIZE (64 * 1024)
#define PHYEYE_MAX_LANE 16
#define PHYEYE_MAX_EYE 3
typedef struct {
const char *pm_output;
nvme_eom_lsp_mqual_t pm_qual;
} phyeye_measure_t;
typedef enum {
PHYEYE_REPORT_M_ASCII,
PHYEYE_REPORT_M_OED
} phyeye_report_mode_t;
typedef struct {
nvme_process_arg_t *pr_npa;
const char *pr_input;
phyeye_report_mode_t pr_mode;
uint8_t pr_lane;
uint8_t pr_eye;
bool pr_print;
} phyeye_report_t;
static const nvmeadm_field_bit_t phyeye_eom_odp_bits[] = { {
.nfb_lowbit = 0, .nfb_hibit = 0,
.nfb_short = "pefp",
.nfb_desc = "Printable Eye Field Present",
.nfb_type = NVMEADM_FT_STRMAP,
.nfb_strs = { "not present", "present" }
}, {
.nfb_lowbit = 1, .nfb_hibit = 1,
.nfb_short = "edfp",
.nfb_desc = "Eye Data Field Present",
.nfb_type = NVMEADM_FT_STRMAP,
.nfb_strs = { "not present", "present" }
} };
static const nvmeadm_field_bit_t phyeye_eom_lspfc_bits[] = { {
.nfb_lowbit = 0, .nfb_hibit = 6,
.nfb_short = "lspfv",
.nfb_desc = "Log Specific Parameter Field Value",
.nfb_type = NVMEADM_FT_HEX
} };
static const nvmeadm_field_bit_t phyeye_eom_linfo_bits[] = { {
.nfb_lowbit = 0, .nfb_hibit = 3,
.nfb_short = "mls",
.nfb_desc = "Measurement Link Speed",
.nfb_type = NVMEADM_FT_HEX
} };
#define PHYEYE_F_EOM(f) .nf_off = offsetof(nvme_eom_hdr_t, eom_##f), \
.nf_len = sizeof (((nvme_eom_hdr_t *)NULL)->eom_##f), \
.nf_short = #f
static const nvmeadm_field_t phyeye_eom_fields[] = { {
PHYEYE_F_EOM(lid),
.nf_desc = "Log Identifier",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(eomip),
.nf_desc = "EOM In Progress",
.nf_type = NVMEADM_FT_STRMAP,
.nf_strs = { "no measurement", "in progress", "completed" },
}, {
PHYEYE_F_EOM(hsize),
.nf_desc = "Header Size",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(rsz),
.nf_desc = "Result Size",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(edgn),
.nf_desc = "EOM Data Generation Number",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(lrev),
.nf_desc = "Log Revision",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(odp),
.nf_desc = "Optional Data Present",
NVMEADM_F_BITS(phyeye_eom_odp_bits)
}, {
PHYEYE_F_EOM(lns),
.nf_desc = "Lanes",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(epl),
.nf_desc = "Eyes Per Lane",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(lspfc),
.nf_desc = "Log Specific Parameter Field Copy",
NVMEADM_F_BITS(phyeye_eom_lspfc_bits)
}, {
PHYEYE_F_EOM(linfo),
.nf_desc = "Link Information",
NVMEADM_F_BITS(phyeye_eom_linfo_bits)
}, {
PHYEYE_F_EOM(lsic),
.nf_desc = "Log Specific Identifier Copy",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(ds),
.nf_desc = "Descriptor Size",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(nd),
.nf_desc = "Number of Descriptors",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(maxtb),
.nf_desc = "Maximum Top Bottom",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(maxlr),
.nf_desc = "Maximum Left Right",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(etgood),
.nf_desc = "Estimated Time for Good Quality",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(etbetter),
.nf_desc = "Estimated Time for Better Quality",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_EOM(etbest),
.nf_desc = "Estimated Time for Best Quality",
.nf_type = NVMEADM_FT_HEX
} };
static const nvmeadm_field_bit_t phyeye_eld_mstat_bits[] = { {
.nfb_lowbit = 0, .nfb_hibit = 0,
.nfb_short = "mscs",
.nfb_desc = "Measurement Successful",
.nfb_type = NVMEADM_FT_STRMAP,
.nfb_strs = { "no", "yes" }
} };
#define PHYEYE_F_ELD(f) .nf_off = offsetof(nvme_eom_lane_desc_t, eld_##f), \
.nf_len = sizeof (((nvme_eom_lane_desc_t *)NULL)->eld_##f), \
.nf_short = #f
static const nvmeadm_field_t phyeye_elm_fields[] = { {
PHYEYE_F_ELD(mstat),
.nf_desc = "Measurement Status",
NVMEADM_F_BITS(phyeye_eld_mstat_bits)
}, {
PHYEYE_F_ELD(ln),
.nf_desc = "Lane",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(eye),
.nf_desc = "Eye",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(top),
.nf_desc = "Top",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(btm),
.nf_desc = "Bottom",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(lft),
.nf_desc = "Left",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(rgt),
.nf_desc = "Right",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(nrows),
.nf_desc = "Number of Rows",
.nf_type = NVMEADM_FT_HEX
}, {
PHYEYE_F_ELD(ncols),
.nf_desc = "Number of Columns",
.nf_type = NVMEADM_FT_HEX
}, {
.nf_off = offsetof(nvme_eom_lane_desc_t, eld_edlen),
.nf_len = sizeof (uint16_t),
.nf_short = "edlen",
.nf_desc = "Eye Data Length",
.nf_type = NVMEADM_FT_HEX,
.nf_rev = 2
}, {
PHYEYE_F_ELD(edlen),
.nf_desc = "Eye Data Length",
.nf_type = NVMEADM_FT_HEX,
.nf_rev = 3
} };
void
usage_measure_phyeye_cmd(const char *c_name)
{
(void) fprintf(stderr, "%s -o output [-Q good | better | best] "
"<ctl>\n\n", c_name);
(void) fprintf(stderr, " Gather physical eye opening measurements "
"from the named controller and save\n them to the specified "
"output file. The best quality measurement is taken by\n "
"default. No other administrative operations can be executed "
"during the eye\n measurement.\n");
}
void
optparse_measure_phyeye_cmd(nvme_process_arg_t *npa)
{
int c;
phyeye_measure_t *phy;
if ((phy = calloc(1, sizeof (phyeye_measure_t))) == NULL) {
err(-1, "failed to allocate memory for option tracking");
}
phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST;
while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:Q:")) != -1) {
switch (c) {
case 'o':
phy->pm_output = optarg;
break;
case 'Q':
if (strcasecmp(optarg, "good") == 0) {
phy->pm_qual = NVME_EOM_LSP_MQUAL_GOOD;
} else if (strcasecmp(optarg, "better") == 0) {
phy->pm_qual = NVME_EOM_LSP_MQUAL_BETTER;
} else if (strcasecmp(optarg, "best") == 0) {
phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST;
} else {
errx(-1, "invalid quality value %s: valid "
"values are 'good', 'better', or 'best'",
optarg);
}
break;
case '?':
errx(-1, "unknown option: -%c", optopt);
case ':':
errx(-1, "option -%c requires an argument", optopt);
}
}
if (phy->pm_output == NULL) {
errx(-1, "missing required output file (-o)");
}
npa->npa_cmd_arg = phy;
}
static void
nvmeadm_phyeye_wait(const nvme_process_arg_t *npa, nvme_log_req_t *req,
volatile nvme_eom_hdr_t *eom, uint16_t wait, const char *qual)
{
hrtime_t start = gethrtime();
hrtime_t end = start + wait * NANOSEC;
hrtime_t now = start;
hrtime_t max = start + wait * 3 * NANOSEC;
const bool tty = isatty(STDOUT_FILENO);
(void) printf("device indicates a minimum %u second wait for %s "
"quality phyeye measurement\n", wait, qual);
while (now < end) {
if (tty) {
(void) printf("\r%u/%u seconds elapsed",
(now - start) / NANOSEC, wait);
(void) fflush(stdout);
}
(void) sleep(1);
now = gethrtime();
}
if (tty) {
(void) printf("\r%u/%u seconds elapsed\n", wait, wait);
}
if (!nvme_log_req_exec(req)) {
nvmeadm_fatal(npa, "failed to retrieve phyeye measurement log "
"request");
}
if (eom->eom_eomip == NVME_EOM_DONE) {
return;
}
(void) printf("Measurement incomplete, proceeding to check over an "
"additional %u seconds\n", max - wait);
uint32_t extra = 0;
now = gethrtime();
hrtime_t phase2 = now;
while (now < max) {
(void) sleep(1);
if (!nvme_log_req_exec(req)) {
nvmeadm_fatal(npa, "failed to issue start phyeye "
"measurement log request");
}
now = gethrtime();
if (eom->eom_eomip == NVME_EOM_DONE) {
return;
}
extra++;
if (tty) {
(void) printf("\rMeasurement still not available after "
"%u attempts (%u seconds)", extra, (now - phase2) /
NANOSEC);
(void) fflush(stdout);
}
}
errx(-1, "timed out waiting for the phyeye measurement to finish after "
"%u seconds: final measurement state: %u", wait * 3,
eom->eom_eomip);
}
static void
nvmeadm_phyeye_read(const nvme_process_arg_t *npa, nvme_log_req_t *req,
void *buf, size_t len, uint64_t off)
{
if (!nvme_log_req_set_output(req, buf, len)) {
nvmeadm_fatal(npa, "failed to set output buffer");
}
if (!nvme_log_req_set_offset(req, off)) {
nvmeadm_fatal(npa, "failed to set offset to 0x%lx", off);
}
if (!nvme_log_req_exec(req)) {
nvmeadm_fatal(npa, "failed to read %zu bytes at 0x%lx", len,
off);
}
}
static void
nvmeadm_phyeye_write(int fd, const void *buf, size_t len, off_t off)
{
size_t loff = 0;
while (len > 0) {
ssize_t ret = pwrite(fd, buf + loff, len, off + loff);
if (ret < 0) {
err(EXIT_FAILURE, "failed to write to physical eye "
"measurement output file");
}
loff += (size_t)ret;
len -= (size_t)ret;
}
}
int
do_measure_phyeye_cmd(const nvme_process_arg_t *npa)
{
int fd = -1;
nvme_log_req_t *req = NULL;
nvme_log_disc_t *disc = NULL;
nvme_eom_lsp_t lsp;
const phyeye_measure_t *phy = npa->npa_cmd_arg;
void *buf = NULL;
uint64_t min_len;
nvme_log_size_kind_t lkind;
if (!nvme_log_req_init_by_name(npa->npa_ctrl, "phyeye", 0, &disc,
&req)) {
nvmeadm_fatal(npa, "failed to initialize phyeye log request");
}
if ((fd = open(phy->pm_output, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
err(-1, "failed to open output file %s", phy->pm_output);
}
if ((buf = malloc(PHYEYE_BUFSIZE)) == NULL) {
err(-1, "failed to allocate internal phy data buffer");
}
if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) {
nvmeadm_fatal(npa, "failed to set lsi for phyeye measurement");
}
(void) memset(&lsp, 0, sizeof (lsp));
lsp.nel_mqual = phy->pm_qual;
lsp.nel_act = NVME_EOM_LSP_START;
if (!nvme_log_req_set_lsp(req, lsp.r)) {
nvmeadm_fatal(npa, "failed to set lsp for phyeye measurement");
}
lkind = nvme_log_disc_size(disc, &min_len);
VERIFY3U(lkind, ==, NVME_LOG_SIZE_K_VAR);
VERIFY3U(min_len, >=, sizeof (nvme_eom_hdr_t));
if (!nvme_log_req_set_output(req, buf, min_len)) {
nvmeadm_fatal(npa, "failed to set initial output buffer and "
"length");
}
if (!nvme_log_req_exec(req)) {
nvmeadm_fatal(npa, "failed to issue start phyeye measurement "
"log request");
}
lsp.nel_act = NVME_EOM_LSP_READ;
if (!nvme_log_req_set_lsp(req, lsp.r)) {
nvmeadm_fatal(npa, "failed to update lsp for phyeye "
"measurement");
}
const volatile nvme_eom_hdr_t *eom = buf;
if (eom->eom_eomip != NVME_EOM_IN_PROGRESS) {
warnx("EOM in progress in header is not in-progress, found %u: "
"waiting the appropriate time regardless", eom->eom_eomip);
}
const uint8_t eom_gen = eom->eom_edgn;
uint16_t wait;
const char *desc;
if (phy->pm_qual == NVME_EOM_LSP_MQUAL_GOOD) {
wait = eom->eom_etgood;
desc = "good";
} else if (phy->pm_qual == NVME_EOM_LSP_MQUAL_BETTER) {
wait = eom->eom_etbetter;
desc = "better";
} else {
wait = eom->eom_etbest;
desc = "best";
}
nvmeadm_phyeye_wait(npa, req, buf, wait, desc);
uint64_t act_len, off = 0;
if (!nvme_log_disc_calc_size(disc, &act_len, buf, min_len)) {
errx(-1, "failed to determine full phyeye log length");
}
while (off < act_len) {
size_t to_read = MIN(act_len - off, PHYEYE_BUFSIZE);
nvmeadm_phyeye_read(npa, req, buf, to_read, off);
nvmeadm_phyeye_write(fd, buf, to_read, off);
off += to_read;
}
nvmeadm_phyeye_read(npa, req, buf, sizeof (nvme_eom_hdr_t), 0);
if (eom->eom_edgn != eom_gen) {
(void) unlink(phy->pm_output);
errx(-1, "PHY eye measurement generation unexpectedly changed: "
"was 0x%x, now is 0x%x: aborting", eom_gen, eom->eom_edgn);
}
(void) printf("phyeye successfully written to %s\n", phy->pm_output);
free(buf);
if (fd >= 0) {
VERIFY0(close(fd));
}
nvme_log_disc_free(disc);
nvme_log_req_fini(req);
return (0);
}
void
usage_report_phyeye_cmd(const char *c_name)
{
(void) fprintf(stderr, "%s -f file [-l lane] [-e eye] [-m mode] "
"<ctl>\n\n", c_name);
(void) fprintf(stderr, " Report information about a physical eye "
"measurement. Eye measurements can be\n taken with \"nvmeadm "
"measure-phyeye\".\n");
}
void
optparse_report_phyeye_cmd(nvme_process_arg_t *npa)
{
int c;
phyeye_report_t *phy;
if ((phy = calloc(1, sizeof (phyeye_report_t))) == NULL) {
err(-1, "failed to allocate memory for option tracking");
}
phy->pr_npa = npa;
phy->pr_mode = PHYEYE_REPORT_M_ASCII;
phy->pr_lane = UINT8_MAX;
phy->pr_eye = UINT8_MAX;
while ((c = getopt(npa->npa_argc, npa->npa_argv, ":e:f:l:m:")) != -1) {
const char *errstr;
switch (c) {
case 'e':
phy->pr_eye = strtonumx(optarg, 0, PHYEYE_MAX_EYE,
&errstr, 0);
if (errstr != NULL) {
errx(-1, "failed to parse eye: value %s is "
"%s: valid values are in the range [%u, "
"%u]", optarg, errstr, 0, PHYEYE_MAX_EYE);
}
break;
case 'f':
phy->pr_input = optarg;
break;
case 'l':
phy->pr_lane = strtonumx(optarg, 0, PHYEYE_MAX_LANE,
&errstr, 0);
if (errstr != NULL) {
errx(-1, "failed to parse lane: value %s is "
"%s: valid values are in the range [%u, "
"%u]", optarg, errstr, 0, PHYEYE_MAX_LANE);
}
break;
case 'm':
if (strcasecmp(optarg, "print-eye") == 0) {
phy->pr_mode = PHYEYE_REPORT_M_ASCII;
} else if (strcasecmp(optarg, "eye-data") == 0) {
phy->pr_mode = PHYEYE_REPORT_M_OED;
} else {
errx(-1, "invalid mode value: %s: valid values "
"are 'print-eye' or 'eye-data'", optarg);
}
break;
case '?':
errx(-1, "unknown option: -%c", optopt);
case ':':
errx(-1, "option -%c requires an argument", optopt);
}
}
if (phy->pr_input == NULL) {
errx(-1, "missing required input file to process (-f)");
}
npa->npa_cmd_arg = phy;
}
static uint32_t
phyeye_eol_oed_len(const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc)
{
uint16_t u16;
if (hdr->eom_lrev >= 3) {
return (desc->eld_edlen);
}
(void) memcpy(&u16, &desc->eld_edlen, sizeof (uint16_t));
return (u16);
}
typedef void (*phyeye_lane_iter_cb_f)(uint8_t, const nvme_eom_hdr_t *,
const nvme_eom_lane_desc_t *, void *);
static bool
phyeye_lane_iter(const nvme_eom_hdr_t *hdr, off_t max,
void *arg, phyeye_lane_iter_cb_f func, uint8_t lane, uint8_t eye)
{
off_t cur_off = sizeof (nvme_eom_hdr_t);
bool ret = true;
for (uint16_t i = 0; i < hdr->eom_nd; i++) {
const nvme_eom_lane_desc_t *desc = NULL;
size_t dlen = sizeof (nvme_eom_lane_desc_t);
if (cur_off + hdr->eom_ds > max) {
errx(-1, "failed to iterate EOM Lane descriptors: "
"descriptor %u starts at offset 0x%lx, but its "
"size (0x%x) would exceed the maximum file length "
"of 0x%lx", i, cur_off, hdr->eom_ds, max);
}
desc = (const nvme_eom_lane_desc_t *)((uintptr_t)hdr + cur_off);
if (hdr->eom_odp.odp_pefp != 0) {
if (desc->eld_nrows == 0 || desc->eld_ncols == 0) {
errx(-1, "printable eye feature present but "
"both NROWS (0x%x) and NCOLS (0x%x) are "
"not non-zero", desc->eld_nrows,
desc->eld_ncols);
}
dlen += desc->eld_nrows * desc->eld_ncols;
} else if (desc->eld_nrows != 0 || desc->eld_ncols != 0) {
errx(-1, "printable eye feature not present but both "
"NROWS (0x%x) and NCOLS (0x%x) are not zero",
desc->eld_nrows, desc->eld_ncols);
}
const uint32_t oed_len = phyeye_eol_oed_len(hdr, desc);
if (hdr->eom_odp.odp_edfp != 0) {
if (oed_len == 0) {
errx(-1, "optional eye data feature present, "
"but eye data has a zero-length");
}
dlen += oed_len;
} else if (oed_len != 0) {
errx(-1, "optional eye data feature not present, but "
"eye data has a non-zero length (0x%x)", oed_len);
}
if (dlen > hdr->eom_ds) {
errx(-1, "failed to iterate EOM Lane descriptors: "
"descriptor %u starts at offset 0x%lx, has a "
"calculated size (0x%zx) that exceeds the "
"header's max descriptor size (0x%x)", i, cur_off,
dlen, hdr->eom_ds);
}
if (lane != UINT8_MAX && desc->eld_ln != lane)
goto next;
if (eye != UINT8_MAX && desc->eld_eye != eye)
goto next;
if (desc->eld_mstat.mstat_mcsc == 0) {
warnx("lane %u, eye %u data does not have a successful "
"measurement", desc->eld_ln, desc->eld_eye);
ret = false;
goto next;
}
func(i, hdr, desc, arg);
next:
cur_off += dlen;
}
return (ret);
}
static void
phyeye_report_sanity_check(const nvme_eom_hdr_t *hdr, off_t len,
const phyeye_report_t *phy)
{
if (len < sizeof (nvme_eom_hdr_t)) {
errx(-1, "data file is too short: file does not cover the "
"0x%lx bytes required for the Eye Opening Measurement "
"header", sizeof (nvme_eom_hdr_t));
}
if (hdr->eom_lrev < 1 || hdr->eom_lrev > 3) {
errx(-1, "encountered unknown log header revision: 0x%x",
hdr->eom_lrev);
}
if (phy != NULL && hdr->eom_eomip != NVME_EOM_DONE) {
errx(-1, "data file measurement in progress field does not "
"indicate a finished measurement (%u): found %u",
NVME_EOM_DONE, hdr->eom_eomip);
}
if (hdr->eom_hsize != sizeof (nvme_eom_hdr_t)) {
errx(-1, "data file has unexpected header length: found 0x%x, "
"expected 0x%zx", hdr->eom_hsize, sizeof (nvme_eom_hdr_t));
}
if (hdr->eom_rsz > len) {
errx(-1, "data file reports that the log is 0x%x bytes, but "
"file is only 0x%lx bytes", hdr->eom_rsz, len);
}
if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_ASCII &&
hdr->eom_odp.odp_pefp == 0) {
errx(-1, "Printable Eye requested, but field not present in "
"data file");
}
if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_OED &&
hdr->eom_odp.odp_edfp == 0) {
errx(-1, "Eye Data Field requested, but field not present in "
"data file");
}
if (phy != NULL && hdr->eom_nd == 0) {
errx(-1, "data file reports no EOM lane descriptors present");
}
if (hdr->eom_nd > 0 && hdr->eom_ds < sizeof (nvme_eom_lane_desc_t)) {
errx(-1, "data file reports the descriptor size is 0x%x, but "
"that is less than the base descriptor size of 0x%zx",
hdr->eom_ds, sizeof (nvme_eom_lane_desc_t));
}
}
static void
phyeye_report_ascii(uint8_t descno, const nvme_eom_hdr_t *hdr,
const nvme_eom_lane_desc_t *desc, void *arg)
{
phyeye_report_t *phy = arg;
phy->pr_print = true;
(void) printf("Lane %u, Eye %u: Printable Eye\n", desc->eld_ln,
desc->eld_eye);
for (uint16_t row = 0; row < desc->eld_nrows; row++) {
for (uint16_t col = 0; col < desc->eld_ncols; col++) {
const uint32_t off = row * desc->eld_ncols + col;
uint8_t c = desc->eld_data[off];
if (c != '0' && c != '1')
c = '?';
(void) putc(c, stdout);
}
(void) putc('\n', stdout);
}
}
static void
phyeye_report_oed(uint8_t descno, const nvme_eom_hdr_t *hdr,
const nvme_eom_lane_desc_t *desc, void *arg)
{
phyeye_report_t *phy = arg;
size_t off = desc->eld_nrows * desc->eld_ncols;
phy->pr_print = true;
(void) printf("Lane %u, Eye %u: Eye Data\n", desc->eld_ln,
desc->eld_eye);
nvmeadm_dump_hex(&desc->eld_data[off], desc->eld_edlen);
}
int
do_report_phyeye_cmd(const nvme_process_arg_t *npa)
{
int fd = -1, ret = EXIT_SUCCESS;
struct stat st;
void *data;
phyeye_report_t *phy = npa->npa_cmd_arg;
phyeye_lane_iter_cb_f func = NULL;
if (npa->npa_argc > 0) {
errx(-1, "extraneous arguments beginning with '%s'",
npa->npa_argv[0]);
}
if ((fd = open(phy->pr_input, O_RDONLY)) < 0) {
err(-1, "failed to open input file %s", phy->pr_input);
}
if (fstat(fd, &st) != 0) {
err(-1, "failed to stat %s", phy->pr_input);
}
if (st.st_size > NVMEADM_MAX_MMAP) {
errx(-1, "%s file size of 0x%lx exceeds maximum allowed size "
"of 0x%llx", phy->pr_input, st.st_size, NVMEADM_MAX_MMAP);
}
data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
errx(-1, "failed to mmap %s", phy->pr_input);
}
phyeye_report_sanity_check(data, st.st_size, phy);
switch (phy->pr_mode) {
case PHYEYE_REPORT_M_ASCII:
func = phyeye_report_ascii;
break;
case PHYEYE_REPORT_M_OED:
func = phyeye_report_oed;
break;
}
if (!phyeye_lane_iter(data, st.st_size, phy, func, phy->pr_lane,
phy->pr_eye)) {
ret = -1;
}
if (!phy->pr_print) {
warnx("failed to match and print any data");
ret = -1;
}
VERIFY0(munmap(data, st.st_size));
if (fd >= 0) {
VERIFY0(close(fd));
}
return (ret);
}
static uint32_t
phyeye_log_getrev(const void *data, size_t len)
{
const nvme_eom_hdr_t *hdr = data;
return (hdr->eom_lrev);
}
static void
phyeye_log_drive_lane_cb(uint8_t descno, const nvme_eom_hdr_t *hdr,
const nvme_eom_lane_desc_t *desc, void *arg)
{
char base[32];
char header[128];
nvmeadm_field_print_t *print = arg;
(void) snprintf(base, sizeof (base), "eld%u", descno);
(void) snprintf(header, sizeof (header), "EOM Lane Descriptor %u",
descno);
print->fp_header = header;
print->fp_fields = phyeye_elm_fields;
print->fp_nfields = ARRAY_SIZE(phyeye_elm_fields);
print->fp_base = base;
print->fp_data = desc;
print->fp_dlen = hdr->eom_ds;
print->fp_off = (uintptr_t)desc - (uintptr_t)hdr;
nvmeadm_field_print(print);
}
static bool
phyeye_log_drive(nvmeadm_field_print_t *print, const void *data, size_t len)
{
print->fp_header = "EOM Header";
print->fp_fields = phyeye_eom_fields;
print->fp_nfields = ARRAY_SIZE(phyeye_eom_fields);
print->fp_base = "eom";
print->fp_data = data;
print->fp_dlen = len;
print->fp_off = 0;
phyeye_report_sanity_check(data, len, NULL);
nvmeadm_field_print(print);
return (phyeye_lane_iter(data, len, print, phyeye_log_drive_lane_cb,
UINT8_MAX, UINT8_MAX));
}
const nvmeadm_log_field_info_t phyeye_field_info = {
.nlfi_log = "phyeye",
.nlfi_min = sizeof (nvme_eom_hdr_t),
.nlfi_getrev = phyeye_log_getrev,
.nlfi_drive = phyeye_log_drive
};