1*c96729eaSRobert Mustacchi /*
2*c96729eaSRobert Mustacchi * This file and its contents are supplied under the terms of the
3*c96729eaSRobert Mustacchi * Common Development and Distribution License ("CDDL"), version 1.0.
4*c96729eaSRobert Mustacchi * You may only use this file in accordance with the terms of version
5*c96729eaSRobert Mustacchi * 1.0 of the CDDL.
6*c96729eaSRobert Mustacchi *
7*c96729eaSRobert Mustacchi * A full copy of the text of the CDDL should have accompanied this
8*c96729eaSRobert Mustacchi * source. A copy of the CDDL is also available via the Internet at
9*c96729eaSRobert Mustacchi * http://www.illumos.org/license/CDDL.
10*c96729eaSRobert Mustacchi */
11*c96729eaSRobert Mustacchi
12*c96729eaSRobert Mustacchi /*
13*c96729eaSRobert Mustacchi * Copyright 2026 Oxide Computer Company
14*c96729eaSRobert Mustacchi */
15*c96729eaSRobert Mustacchi
16*c96729eaSRobert Mustacchi /*
17*c96729eaSRobert Mustacchi * Logic to gather the physical interface receiver eye opening measurement and
18*c96729eaSRobert Mustacchi * slice and dice it.
19*c96729eaSRobert Mustacchi */
20*c96729eaSRobert Mustacchi
21*c96729eaSRobert Mustacchi #include <err.h>
22*c96729eaSRobert Mustacchi #include <fcntl.h>
23*c96729eaSRobert Mustacchi #include <stdio.h>
24*c96729eaSRobert Mustacchi #include <stdlib.h>
25*c96729eaSRobert Mustacchi #include <strings.h>
26*c96729eaSRobert Mustacchi #include <unistd.h>
27*c96729eaSRobert Mustacchi #include <sys/stat.h>
28*c96729eaSRobert Mustacchi #include <sys/debug.h>
29*c96729eaSRobert Mustacchi #include <sys/sysmacros.h>
30*c96729eaSRobert Mustacchi #include <sys/mman.h>
31*c96729eaSRobert Mustacchi #include <sys/bitext.h>
32*c96729eaSRobert Mustacchi #include <ofmt.h>
33*c96729eaSRobert Mustacchi
34*c96729eaSRobert Mustacchi #include "nvmeadm.h"
35*c96729eaSRobert Mustacchi
36*c96729eaSRobert Mustacchi /*
37*c96729eaSRobert Mustacchi * Pick a reasonable buffer size that we think will fit within a device's
38*c96729eaSRobert Mustacchi * maximum payload. For now we use 64 KiB somewhat arbitrarily.
39*c96729eaSRobert Mustacchi */
40*c96729eaSRobert Mustacchi #define PHYEYE_BUFSIZE (64 * 1024)
41*c96729eaSRobert Mustacchi
42*c96729eaSRobert Mustacchi /*
43*c96729eaSRobert Mustacchi * Maximum values for PCIe lanes and eyes. We use 16 lanes for PCIe as while
44*c96729eaSRobert Mustacchi * most devices are only x4, that's the largest sized slot that's typically
45*c96729eaSRobert Mustacchi * implemented.
46*c96729eaSRobert Mustacchi *
47*c96729eaSRobert Mustacchi * For the maximum number of eyes there is only 1 for NRZ and today 3 for PAM4.
48*c96729eaSRobert Mustacchi * So we pick the PAM4 default.
49*c96729eaSRobert Mustacchi */
50*c96729eaSRobert Mustacchi #define PHYEYE_MAX_LANE 16
51*c96729eaSRobert Mustacchi #define PHYEYE_MAX_EYE 3
52*c96729eaSRobert Mustacchi
53*c96729eaSRobert Mustacchi typedef struct {
54*c96729eaSRobert Mustacchi const char *pm_output;
55*c96729eaSRobert Mustacchi nvme_eom_lsp_mqual_t pm_qual;
56*c96729eaSRobert Mustacchi } phyeye_measure_t;
57*c96729eaSRobert Mustacchi
58*c96729eaSRobert Mustacchi typedef enum {
59*c96729eaSRobert Mustacchi PHYEYE_REPORT_M_ASCII,
60*c96729eaSRobert Mustacchi PHYEYE_REPORT_M_OED
61*c96729eaSRobert Mustacchi } phyeye_report_mode_t;
62*c96729eaSRobert Mustacchi
63*c96729eaSRobert Mustacchi typedef struct {
64*c96729eaSRobert Mustacchi nvme_process_arg_t *pr_npa;
65*c96729eaSRobert Mustacchi const char *pr_input;
66*c96729eaSRobert Mustacchi phyeye_report_mode_t pr_mode;
67*c96729eaSRobert Mustacchi uint8_t pr_lane;
68*c96729eaSRobert Mustacchi uint8_t pr_eye;
69*c96729eaSRobert Mustacchi bool pr_print;
70*c96729eaSRobert Mustacchi } phyeye_report_t;
71*c96729eaSRobert Mustacchi
72*c96729eaSRobert Mustacchi static const nvmeadm_field_bit_t phyeye_eom_odp_bits[] = { {
73*c96729eaSRobert Mustacchi .nfb_lowbit = 0, .nfb_hibit = 0,
74*c96729eaSRobert Mustacchi .nfb_short = "pefp",
75*c96729eaSRobert Mustacchi .nfb_desc = "Printable Eye Field Present",
76*c96729eaSRobert Mustacchi .nfb_type = NVMEADM_FT_STRMAP,
77*c96729eaSRobert Mustacchi .nfb_strs = { "not present", "present" }
78*c96729eaSRobert Mustacchi }, {
79*c96729eaSRobert Mustacchi .nfb_lowbit = 1, .nfb_hibit = 1,
80*c96729eaSRobert Mustacchi .nfb_short = "edfp",
81*c96729eaSRobert Mustacchi .nfb_desc = "Eye Data Field Present",
82*c96729eaSRobert Mustacchi .nfb_type = NVMEADM_FT_STRMAP,
83*c96729eaSRobert Mustacchi .nfb_strs = { "not present", "present" }
84*c96729eaSRobert Mustacchi } };
85*c96729eaSRobert Mustacchi
86*c96729eaSRobert Mustacchi static const nvmeadm_field_bit_t phyeye_eom_lspfc_bits[] = { {
87*c96729eaSRobert Mustacchi .nfb_lowbit = 0, .nfb_hibit = 6,
88*c96729eaSRobert Mustacchi .nfb_short = "lspfv",
89*c96729eaSRobert Mustacchi .nfb_desc = "Log Specific Parameter Field Value",
90*c96729eaSRobert Mustacchi .nfb_type = NVMEADM_FT_HEX
91*c96729eaSRobert Mustacchi } };
92*c96729eaSRobert Mustacchi
93*c96729eaSRobert Mustacchi static const nvmeadm_field_bit_t phyeye_eom_linfo_bits[] = { {
94*c96729eaSRobert Mustacchi .nfb_lowbit = 0, .nfb_hibit = 3,
95*c96729eaSRobert Mustacchi .nfb_short = "mls",
96*c96729eaSRobert Mustacchi .nfb_desc = "Measurement Link Speed",
97*c96729eaSRobert Mustacchi .nfb_type = NVMEADM_FT_HEX
98*c96729eaSRobert Mustacchi } };
99*c96729eaSRobert Mustacchi
100*c96729eaSRobert Mustacchi #define PHYEYE_F_EOM(f) .nf_off = offsetof(nvme_eom_hdr_t, eom_##f), \
101*c96729eaSRobert Mustacchi .nf_len = sizeof (((nvme_eom_hdr_t *)NULL)->eom_##f), \
102*c96729eaSRobert Mustacchi .nf_short = #f
103*c96729eaSRobert Mustacchi
104*c96729eaSRobert Mustacchi static const nvmeadm_field_t phyeye_eom_fields[] = { {
105*c96729eaSRobert Mustacchi PHYEYE_F_EOM(lid),
106*c96729eaSRobert Mustacchi .nf_desc = "Log Identifier",
107*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
108*c96729eaSRobert Mustacchi }, {
109*c96729eaSRobert Mustacchi PHYEYE_F_EOM(eomip),
110*c96729eaSRobert Mustacchi .nf_desc = "EOM In Progress",
111*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_STRMAP,
112*c96729eaSRobert Mustacchi .nf_strs = { "no measurement", "in progress", "completed" },
113*c96729eaSRobert Mustacchi }, {
114*c96729eaSRobert Mustacchi PHYEYE_F_EOM(hsize),
115*c96729eaSRobert Mustacchi .nf_desc = "Header Size",
116*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
117*c96729eaSRobert Mustacchi }, {
118*c96729eaSRobert Mustacchi PHYEYE_F_EOM(rsz),
119*c96729eaSRobert Mustacchi .nf_desc = "Result Size",
120*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
121*c96729eaSRobert Mustacchi }, {
122*c96729eaSRobert Mustacchi PHYEYE_F_EOM(edgn),
123*c96729eaSRobert Mustacchi .nf_desc = "EOM Data Generation Number",
124*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
125*c96729eaSRobert Mustacchi }, {
126*c96729eaSRobert Mustacchi PHYEYE_F_EOM(lrev),
127*c96729eaSRobert Mustacchi .nf_desc = "Log Revision",
128*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
129*c96729eaSRobert Mustacchi }, {
130*c96729eaSRobert Mustacchi PHYEYE_F_EOM(odp),
131*c96729eaSRobert Mustacchi .nf_desc = "Optional Data Present",
132*c96729eaSRobert Mustacchi NVMEADM_F_BITS(phyeye_eom_odp_bits)
133*c96729eaSRobert Mustacchi }, {
134*c96729eaSRobert Mustacchi PHYEYE_F_EOM(lns),
135*c96729eaSRobert Mustacchi .nf_desc = "Lanes",
136*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
137*c96729eaSRobert Mustacchi }, {
138*c96729eaSRobert Mustacchi PHYEYE_F_EOM(epl),
139*c96729eaSRobert Mustacchi .nf_desc = "Eyes Per Lane",
140*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
141*c96729eaSRobert Mustacchi }, {
142*c96729eaSRobert Mustacchi PHYEYE_F_EOM(lspfc),
143*c96729eaSRobert Mustacchi .nf_desc = "Log Specific Parameter Field Copy",
144*c96729eaSRobert Mustacchi NVMEADM_F_BITS(phyeye_eom_lspfc_bits)
145*c96729eaSRobert Mustacchi }, {
146*c96729eaSRobert Mustacchi PHYEYE_F_EOM(linfo),
147*c96729eaSRobert Mustacchi .nf_desc = "Link Information",
148*c96729eaSRobert Mustacchi NVMEADM_F_BITS(phyeye_eom_linfo_bits)
149*c96729eaSRobert Mustacchi }, {
150*c96729eaSRobert Mustacchi PHYEYE_F_EOM(lsic),
151*c96729eaSRobert Mustacchi .nf_desc = "Log Specific Identifier Copy",
152*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
153*c96729eaSRobert Mustacchi }, {
154*c96729eaSRobert Mustacchi PHYEYE_F_EOM(ds),
155*c96729eaSRobert Mustacchi .nf_desc = "Descriptor Size",
156*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
157*c96729eaSRobert Mustacchi }, {
158*c96729eaSRobert Mustacchi PHYEYE_F_EOM(nd),
159*c96729eaSRobert Mustacchi .nf_desc = "Number of Descriptors",
160*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
161*c96729eaSRobert Mustacchi }, {
162*c96729eaSRobert Mustacchi PHYEYE_F_EOM(maxtb),
163*c96729eaSRobert Mustacchi .nf_desc = "Maximum Top Bottom",
164*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
165*c96729eaSRobert Mustacchi }, {
166*c96729eaSRobert Mustacchi PHYEYE_F_EOM(maxlr),
167*c96729eaSRobert Mustacchi .nf_desc = "Maximum Left Right",
168*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
169*c96729eaSRobert Mustacchi }, {
170*c96729eaSRobert Mustacchi PHYEYE_F_EOM(etgood),
171*c96729eaSRobert Mustacchi .nf_desc = "Estimated Time for Good Quality",
172*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
173*c96729eaSRobert Mustacchi }, {
174*c96729eaSRobert Mustacchi PHYEYE_F_EOM(etbetter),
175*c96729eaSRobert Mustacchi .nf_desc = "Estimated Time for Better Quality",
176*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
177*c96729eaSRobert Mustacchi }, {
178*c96729eaSRobert Mustacchi PHYEYE_F_EOM(etbest),
179*c96729eaSRobert Mustacchi .nf_desc = "Estimated Time for Best Quality",
180*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
181*c96729eaSRobert Mustacchi } };
182*c96729eaSRobert Mustacchi
183*c96729eaSRobert Mustacchi static const nvmeadm_field_bit_t phyeye_eld_mstat_bits[] = { {
184*c96729eaSRobert Mustacchi .nfb_lowbit = 0, .nfb_hibit = 0,
185*c96729eaSRobert Mustacchi .nfb_short = "mscs",
186*c96729eaSRobert Mustacchi .nfb_desc = "Measurement Successful",
187*c96729eaSRobert Mustacchi .nfb_type = NVMEADM_FT_STRMAP,
188*c96729eaSRobert Mustacchi .nfb_strs = { "no", "yes" }
189*c96729eaSRobert Mustacchi } };
190*c96729eaSRobert Mustacchi
191*c96729eaSRobert Mustacchi #define PHYEYE_F_ELD(f) .nf_off = offsetof(nvme_eom_lane_desc_t, eld_##f), \
192*c96729eaSRobert Mustacchi .nf_len = sizeof (((nvme_eom_lane_desc_t *)NULL)->eld_##f), \
193*c96729eaSRobert Mustacchi .nf_short = #f
194*c96729eaSRobert Mustacchi
195*c96729eaSRobert Mustacchi static const nvmeadm_field_t phyeye_elm_fields[] = { {
196*c96729eaSRobert Mustacchi PHYEYE_F_ELD(mstat),
197*c96729eaSRobert Mustacchi .nf_desc = "Measurement Status",
198*c96729eaSRobert Mustacchi NVMEADM_F_BITS(phyeye_eld_mstat_bits)
199*c96729eaSRobert Mustacchi }, {
200*c96729eaSRobert Mustacchi PHYEYE_F_ELD(ln),
201*c96729eaSRobert Mustacchi .nf_desc = "Lane",
202*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
203*c96729eaSRobert Mustacchi }, {
204*c96729eaSRobert Mustacchi PHYEYE_F_ELD(eye),
205*c96729eaSRobert Mustacchi .nf_desc = "Eye",
206*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
207*c96729eaSRobert Mustacchi }, {
208*c96729eaSRobert Mustacchi PHYEYE_F_ELD(top),
209*c96729eaSRobert Mustacchi .nf_desc = "Top",
210*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
211*c96729eaSRobert Mustacchi }, {
212*c96729eaSRobert Mustacchi PHYEYE_F_ELD(btm),
213*c96729eaSRobert Mustacchi .nf_desc = "Bottom",
214*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
215*c96729eaSRobert Mustacchi }, {
216*c96729eaSRobert Mustacchi PHYEYE_F_ELD(lft),
217*c96729eaSRobert Mustacchi .nf_desc = "Left",
218*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
219*c96729eaSRobert Mustacchi }, {
220*c96729eaSRobert Mustacchi PHYEYE_F_ELD(rgt),
221*c96729eaSRobert Mustacchi .nf_desc = "Right",
222*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
223*c96729eaSRobert Mustacchi }, {
224*c96729eaSRobert Mustacchi PHYEYE_F_ELD(nrows),
225*c96729eaSRobert Mustacchi .nf_desc = "Number of Rows",
226*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
227*c96729eaSRobert Mustacchi }, {
228*c96729eaSRobert Mustacchi PHYEYE_F_ELD(ncols),
229*c96729eaSRobert Mustacchi .nf_desc = "Number of Columns",
230*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX
231*c96729eaSRobert Mustacchi }, {
232*c96729eaSRobert Mustacchi /*
233*c96729eaSRobert Mustacchi * The size of this field changed between revision 2 and 3 from 2 bytes
234*c96729eaSRobert Mustacchi * to 4 bytes.
235*c96729eaSRobert Mustacchi */
236*c96729eaSRobert Mustacchi .nf_off = offsetof(nvme_eom_lane_desc_t, eld_edlen),
237*c96729eaSRobert Mustacchi .nf_len = sizeof (uint16_t),
238*c96729eaSRobert Mustacchi .nf_short = "edlen",
239*c96729eaSRobert Mustacchi .nf_desc = "Eye Data Length",
240*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX,
241*c96729eaSRobert Mustacchi .nf_rev = 2
242*c96729eaSRobert Mustacchi }, {
243*c96729eaSRobert Mustacchi PHYEYE_F_ELD(edlen),
244*c96729eaSRobert Mustacchi .nf_desc = "Eye Data Length",
245*c96729eaSRobert Mustacchi .nf_type = NVMEADM_FT_HEX,
246*c96729eaSRobert Mustacchi .nf_rev = 3
247*c96729eaSRobert Mustacchi } };
248*c96729eaSRobert Mustacchi
249*c96729eaSRobert Mustacchi void
usage_measure_phyeye_cmd(const char * c_name)250*c96729eaSRobert Mustacchi usage_measure_phyeye_cmd(const char *c_name)
251*c96729eaSRobert Mustacchi {
252*c96729eaSRobert Mustacchi (void) fprintf(stderr, "%s -o output [-Q good | better | best] "
253*c96729eaSRobert Mustacchi "<ctl>\n\n", c_name);
254*c96729eaSRobert Mustacchi (void) fprintf(stderr, " Gather physical eye opening measurements "
255*c96729eaSRobert Mustacchi "from the named controller and save\n them to the specified "
256*c96729eaSRobert Mustacchi "output file. The best quality measurement is taken by\n "
257*c96729eaSRobert Mustacchi "default. No other administrative operations can be executed "
258*c96729eaSRobert Mustacchi "during the eye\n measurement.\n");
259*c96729eaSRobert Mustacchi }
260*c96729eaSRobert Mustacchi
261*c96729eaSRobert Mustacchi void
optparse_measure_phyeye_cmd(nvme_process_arg_t * npa)262*c96729eaSRobert Mustacchi optparse_measure_phyeye_cmd(nvme_process_arg_t *npa)
263*c96729eaSRobert Mustacchi {
264*c96729eaSRobert Mustacchi int c;
265*c96729eaSRobert Mustacchi phyeye_measure_t *phy;
266*c96729eaSRobert Mustacchi
267*c96729eaSRobert Mustacchi if ((phy = calloc(1, sizeof (phyeye_measure_t))) == NULL) {
268*c96729eaSRobert Mustacchi err(-1, "failed to allocate memory for option tracking");
269*c96729eaSRobert Mustacchi }
270*c96729eaSRobert Mustacchi
271*c96729eaSRobert Mustacchi /*
272*c96729eaSRobert Mustacchi * Default to best quality if not otherwise requested.
273*c96729eaSRobert Mustacchi */
274*c96729eaSRobert Mustacchi phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST;
275*c96729eaSRobert Mustacchi
276*c96729eaSRobert Mustacchi while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:Q:")) != -1) {
277*c96729eaSRobert Mustacchi switch (c) {
278*c96729eaSRobert Mustacchi case 'o':
279*c96729eaSRobert Mustacchi phy->pm_output = optarg;
280*c96729eaSRobert Mustacchi break;
281*c96729eaSRobert Mustacchi case 'Q':
282*c96729eaSRobert Mustacchi if (strcasecmp(optarg, "good") == 0) {
283*c96729eaSRobert Mustacchi phy->pm_qual = NVME_EOM_LSP_MQUAL_GOOD;
284*c96729eaSRobert Mustacchi } else if (strcasecmp(optarg, "better") == 0) {
285*c96729eaSRobert Mustacchi phy->pm_qual = NVME_EOM_LSP_MQUAL_BETTER;
286*c96729eaSRobert Mustacchi
287*c96729eaSRobert Mustacchi } else if (strcasecmp(optarg, "best") == 0) {
288*c96729eaSRobert Mustacchi phy->pm_qual = NVME_EOM_LSP_MQUAL_BEST;
289*c96729eaSRobert Mustacchi
290*c96729eaSRobert Mustacchi } else {
291*c96729eaSRobert Mustacchi errx(-1, "invalid quality value %s: valid "
292*c96729eaSRobert Mustacchi "values are 'good', 'better', or 'best'",
293*c96729eaSRobert Mustacchi optarg);
294*c96729eaSRobert Mustacchi }
295*c96729eaSRobert Mustacchi break;
296*c96729eaSRobert Mustacchi case '?':
297*c96729eaSRobert Mustacchi errx(-1, "unknown option: -%c", optopt);
298*c96729eaSRobert Mustacchi case ':':
299*c96729eaSRobert Mustacchi errx(-1, "option -%c requires an argument", optopt);
300*c96729eaSRobert Mustacchi }
301*c96729eaSRobert Mustacchi }
302*c96729eaSRobert Mustacchi
303*c96729eaSRobert Mustacchi if (phy->pm_output == NULL) {
304*c96729eaSRobert Mustacchi errx(-1, "missing required output file (-o)");
305*c96729eaSRobert Mustacchi }
306*c96729eaSRobert Mustacchi
307*c96729eaSRobert Mustacchi npa->npa_cmd_arg = phy;
308*c96729eaSRobert Mustacchi }
309*c96729eaSRobert Mustacchi
310*c96729eaSRobert Mustacchi /*
311*c96729eaSRobert Mustacchi * Poll the log page until it is done and ready. We always do the initial wait.
312*c96729eaSRobert Mustacchi * The specification says that this may take longer due to activity on the
313*c96729eaSRobert Mustacchi * device. We will wait up to 3x the amount of time that was indicated for this
314*c96729eaSRobert Mustacchi * measurement. We will begin using a 1 second delay after this point.
315*c96729eaSRobert Mustacchi *
316*c96729eaSRobert Mustacchi * This explicitly uses a volatile pointer for 'eom' due to the fact that the
317*c96729eaSRobert Mustacchi * log page execution will update the data that it points to.
318*c96729eaSRobert Mustacchi */
319*c96729eaSRobert Mustacchi 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)320*c96729eaSRobert Mustacchi nvmeadm_phyeye_wait(const nvme_process_arg_t *npa, nvme_log_req_t *req,
321*c96729eaSRobert Mustacchi volatile nvme_eom_hdr_t *eom, uint16_t wait, const char *qual)
322*c96729eaSRobert Mustacchi {
323*c96729eaSRobert Mustacchi hrtime_t start = gethrtime();
324*c96729eaSRobert Mustacchi hrtime_t end = start + wait * NANOSEC;
325*c96729eaSRobert Mustacchi hrtime_t now = start;
326*c96729eaSRobert Mustacchi hrtime_t max = start + wait * 3 * NANOSEC;
327*c96729eaSRobert Mustacchi const bool tty = isatty(STDOUT_FILENO);
328*c96729eaSRobert Mustacchi
329*c96729eaSRobert Mustacchi (void) printf("device indicates a minimum %u second wait for %s "
330*c96729eaSRobert Mustacchi "quality phyeye measurement\n", wait, qual);
331*c96729eaSRobert Mustacchi while (now < end) {
332*c96729eaSRobert Mustacchi if (tty) {
333*c96729eaSRobert Mustacchi (void) printf("\r%u/%u seconds elapsed",
334*c96729eaSRobert Mustacchi (now - start) / NANOSEC, wait);
335*c96729eaSRobert Mustacchi (void) fflush(stdout);
336*c96729eaSRobert Mustacchi }
337*c96729eaSRobert Mustacchi (void) sleep(1);
338*c96729eaSRobert Mustacchi now = gethrtime();
339*c96729eaSRobert Mustacchi }
340*c96729eaSRobert Mustacchi
341*c96729eaSRobert Mustacchi if (tty) {
342*c96729eaSRobert Mustacchi (void) printf("\r%u/%u seconds elapsed\n", wait, wait);
343*c96729eaSRobert Mustacchi }
344*c96729eaSRobert Mustacchi
345*c96729eaSRobert Mustacchi if (!nvme_log_req_exec(req)) {
346*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to retrieve phyeye measurement log "
347*c96729eaSRobert Mustacchi "request");
348*c96729eaSRobert Mustacchi }
349*c96729eaSRobert Mustacchi
350*c96729eaSRobert Mustacchi if (eom->eom_eomip == NVME_EOM_DONE) {
351*c96729eaSRobert Mustacchi return;
352*c96729eaSRobert Mustacchi }
353*c96729eaSRobert Mustacchi
354*c96729eaSRobert Mustacchi (void) printf("Measurement incomplete, proceeding to check over an "
355*c96729eaSRobert Mustacchi "additional %u seconds\n", max - wait);
356*c96729eaSRobert Mustacchi uint32_t extra = 0;
357*c96729eaSRobert Mustacchi now = gethrtime();
358*c96729eaSRobert Mustacchi hrtime_t phase2 = now;
359*c96729eaSRobert Mustacchi while (now < max) {
360*c96729eaSRobert Mustacchi (void) sleep(1);
361*c96729eaSRobert Mustacchi
362*c96729eaSRobert Mustacchi if (!nvme_log_req_exec(req)) {
363*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to issue start phyeye "
364*c96729eaSRobert Mustacchi "measurement log request");
365*c96729eaSRobert Mustacchi }
366*c96729eaSRobert Mustacchi
367*c96729eaSRobert Mustacchi now = gethrtime();
368*c96729eaSRobert Mustacchi
369*c96729eaSRobert Mustacchi if (eom->eom_eomip == NVME_EOM_DONE) {
370*c96729eaSRobert Mustacchi return;
371*c96729eaSRobert Mustacchi }
372*c96729eaSRobert Mustacchi
373*c96729eaSRobert Mustacchi extra++;
374*c96729eaSRobert Mustacchi if (tty) {
375*c96729eaSRobert Mustacchi (void) printf("\rMeasurement still not available after "
376*c96729eaSRobert Mustacchi "%u attempts (%u seconds)", extra, (now - phase2) /
377*c96729eaSRobert Mustacchi NANOSEC);
378*c96729eaSRobert Mustacchi (void) fflush(stdout);
379*c96729eaSRobert Mustacchi }
380*c96729eaSRobert Mustacchi }
381*c96729eaSRobert Mustacchi
382*c96729eaSRobert Mustacchi errx(-1, "timed out waiting for the phyeye measurement to finish after "
383*c96729eaSRobert Mustacchi "%u seconds: final measurement state: %u", wait * 3,
384*c96729eaSRobert Mustacchi eom->eom_eomip);
385*c96729eaSRobert Mustacchi }
386*c96729eaSRobert Mustacchi
387*c96729eaSRobert Mustacchi static void
nvmeadm_phyeye_read(const nvme_process_arg_t * npa,nvme_log_req_t * req,void * buf,size_t len,uint64_t off)388*c96729eaSRobert Mustacchi nvmeadm_phyeye_read(const nvme_process_arg_t *npa, nvme_log_req_t *req,
389*c96729eaSRobert Mustacchi void *buf, size_t len, uint64_t off)
390*c96729eaSRobert Mustacchi {
391*c96729eaSRobert Mustacchi if (!nvme_log_req_set_output(req, buf, len)) {
392*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to set output buffer");
393*c96729eaSRobert Mustacchi }
394*c96729eaSRobert Mustacchi
395*c96729eaSRobert Mustacchi if (!nvme_log_req_set_offset(req, off)) {
396*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to set offset to 0x%lx", off);
397*c96729eaSRobert Mustacchi }
398*c96729eaSRobert Mustacchi
399*c96729eaSRobert Mustacchi if (!nvme_log_req_exec(req)) {
400*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to read %zu bytes at 0x%lx", len,
401*c96729eaSRobert Mustacchi off);
402*c96729eaSRobert Mustacchi }
403*c96729eaSRobert Mustacchi }
404*c96729eaSRobert Mustacchi
405*c96729eaSRobert Mustacchi static void
nvmeadm_phyeye_write(int fd,const void * buf,size_t len,off_t off)406*c96729eaSRobert Mustacchi nvmeadm_phyeye_write(int fd, const void *buf, size_t len, off_t off)
407*c96729eaSRobert Mustacchi {
408*c96729eaSRobert Mustacchi size_t loff = 0;
409*c96729eaSRobert Mustacchi
410*c96729eaSRobert Mustacchi while (len > 0) {
411*c96729eaSRobert Mustacchi ssize_t ret = pwrite(fd, buf + loff, len, off + loff);
412*c96729eaSRobert Mustacchi if (ret < 0) {
413*c96729eaSRobert Mustacchi err(EXIT_FAILURE, "failed to write to physical eye "
414*c96729eaSRobert Mustacchi "measurement output file");
415*c96729eaSRobert Mustacchi }
416*c96729eaSRobert Mustacchi
417*c96729eaSRobert Mustacchi loff += (size_t)ret;
418*c96729eaSRobert Mustacchi len -= (size_t)ret;
419*c96729eaSRobert Mustacchi }
420*c96729eaSRobert Mustacchi }
421*c96729eaSRobert Mustacchi
422*c96729eaSRobert Mustacchi /*
423*c96729eaSRobert Mustacchi * Perform a physical eye measurement. This consists of a few different steps to
424*c96729eaSRobert Mustacchi * execute it successfully:
425*c96729eaSRobert Mustacchi *
426*c96729eaSRobert Mustacchi * 1. First determine that we can actually issue this command.
427*c96729eaSRobert Mustacchi * 2. Open the output file early. While this may mean we truncate something,
428*c96729eaSRobert Mustacchi * given that this command may take some time, that's better than finding out
429*c96729eaSRobert Mustacchi * after you've already done all the work.
430*c96729eaSRobert Mustacchi * 3. We issue the first phy eye get log page command with the request to begin
431*c96729eaSRobert Mustacchi * a new measurement at the requested quality. We need to set the LSP, LSI,
432*c96729eaSRobert Mustacchi * and output buffer for this.
433*c96729eaSRobert Mustacchi * 4. We wait for the requested number of seconds before beginning to query for
434*c96729eaSRobert Mustacchi * result data.
435*c96729eaSRobert Mustacchi * 5. Once a second, we issue commands trying to see if it's done.
436*c96729eaSRobert Mustacchi * 6. Once it's finally done, then we'll go ahead and actually finish getting
437*c96729eaSRobert Mustacchi * the log page data and write it out to disk.
438*c96729eaSRobert Mustacchi * 7. When we're done with all the data, confirm that the generation is still
439*c96729eaSRobert Mustacchi * the same as when we started.
440*c96729eaSRobert Mustacchi */
441*c96729eaSRobert Mustacchi int
do_measure_phyeye_cmd(const nvme_process_arg_t * npa)442*c96729eaSRobert Mustacchi do_measure_phyeye_cmd(const nvme_process_arg_t *npa)
443*c96729eaSRobert Mustacchi {
444*c96729eaSRobert Mustacchi int fd = -1;
445*c96729eaSRobert Mustacchi nvme_log_req_t *req = NULL;
446*c96729eaSRobert Mustacchi nvme_log_disc_t *disc = NULL;
447*c96729eaSRobert Mustacchi nvme_eom_lsp_t lsp;
448*c96729eaSRobert Mustacchi const phyeye_measure_t *phy = npa->npa_cmd_arg;
449*c96729eaSRobert Mustacchi void *buf = NULL;
450*c96729eaSRobert Mustacchi uint64_t min_len;
451*c96729eaSRobert Mustacchi nvme_log_size_kind_t lkind;
452*c96729eaSRobert Mustacchi
453*c96729eaSRobert Mustacchi if (!nvme_log_req_init_by_name(npa->npa_ctrl, "phyeye", 0, &disc,
454*c96729eaSRobert Mustacchi &req)) {
455*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to initialize phyeye log request");
456*c96729eaSRobert Mustacchi }
457*c96729eaSRobert Mustacchi
458*c96729eaSRobert Mustacchi if ((fd = open(phy->pm_output, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
459*c96729eaSRobert Mustacchi err(-1, "failed to open output file %s", phy->pm_output);
460*c96729eaSRobert Mustacchi }
461*c96729eaSRobert Mustacchi
462*c96729eaSRobert Mustacchi if ((buf = malloc(PHYEYE_BUFSIZE)) == NULL) {
463*c96729eaSRobert Mustacchi err(-1, "failed to allocate internal phy data buffer");
464*c96729eaSRobert Mustacchi }
465*c96729eaSRobert Mustacchi
466*c96729eaSRobert Mustacchi if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) {
467*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to set lsi for phyeye measurement");
468*c96729eaSRobert Mustacchi }
469*c96729eaSRobert Mustacchi
470*c96729eaSRobert Mustacchi (void) memset(&lsp, 0, sizeof (lsp));
471*c96729eaSRobert Mustacchi lsp.nel_mqual = phy->pm_qual;
472*c96729eaSRobert Mustacchi lsp.nel_act = NVME_EOM_LSP_START;
473*c96729eaSRobert Mustacchi
474*c96729eaSRobert Mustacchi if (!nvme_log_req_set_lsp(req, lsp.r)) {
475*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to set lsp for phyeye measurement");
476*c96729eaSRobert Mustacchi }
477*c96729eaSRobert Mustacchi
478*c96729eaSRobert Mustacchi lkind = nvme_log_disc_size(disc, &min_len);
479*c96729eaSRobert Mustacchi VERIFY3U(lkind, ==, NVME_LOG_SIZE_K_VAR);
480*c96729eaSRobert Mustacchi VERIFY3U(min_len, >=, sizeof (nvme_eom_hdr_t));
481*c96729eaSRobert Mustacchi
482*c96729eaSRobert Mustacchi if (!nvme_log_req_set_output(req, buf, min_len)) {
483*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to set initial output buffer and "
484*c96729eaSRobert Mustacchi "length");
485*c96729eaSRobert Mustacchi }
486*c96729eaSRobert Mustacchi
487*c96729eaSRobert Mustacchi if (!nvme_log_req_exec(req)) {
488*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to issue start phyeye measurement "
489*c96729eaSRobert Mustacchi "log request");
490*c96729eaSRobert Mustacchi }
491*c96729eaSRobert Mustacchi
492*c96729eaSRobert Mustacchi /*
493*c96729eaSRobert Mustacchi * Update the request for the rest of this to always be a read request
494*c96729eaSRobert Mustacchi * of the existing measurement.
495*c96729eaSRobert Mustacchi */
496*c96729eaSRobert Mustacchi lsp.nel_act = NVME_EOM_LSP_READ;
497*c96729eaSRobert Mustacchi if (!nvme_log_req_set_lsp(req, lsp.r)) {
498*c96729eaSRobert Mustacchi nvmeadm_fatal(npa, "failed to update lsp for phyeye "
499*c96729eaSRobert Mustacchi "measurement");
500*c96729eaSRobert Mustacchi }
501*c96729eaSRobert Mustacchi
502*c96729eaSRobert Mustacchi /*
503*c96729eaSRobert Mustacchi * The use of volatile here is probably a little weird. But this is
504*c96729eaSRobert Mustacchi * aliasing memory that the log req exec will constantly be updating.
505*c96729eaSRobert Mustacchi */
506*c96729eaSRobert Mustacchi const volatile nvme_eom_hdr_t *eom = buf;
507*c96729eaSRobert Mustacchi if (eom->eom_eomip != NVME_EOM_IN_PROGRESS) {
508*c96729eaSRobert Mustacchi warnx("EOM in progress in header is not in-progress, found %u: "
509*c96729eaSRobert Mustacchi "waiting the appropriate time regardless", eom->eom_eomip);
510*c96729eaSRobert Mustacchi }
511*c96729eaSRobert Mustacchi
512*c96729eaSRobert Mustacchi const uint8_t eom_gen = eom->eom_edgn;
513*c96729eaSRobert Mustacchi
514*c96729eaSRobert Mustacchi uint16_t wait;
515*c96729eaSRobert Mustacchi const char *desc;
516*c96729eaSRobert Mustacchi if (phy->pm_qual == NVME_EOM_LSP_MQUAL_GOOD) {
517*c96729eaSRobert Mustacchi wait = eom->eom_etgood;
518*c96729eaSRobert Mustacchi desc = "good";
519*c96729eaSRobert Mustacchi } else if (phy->pm_qual == NVME_EOM_LSP_MQUAL_BETTER) {
520*c96729eaSRobert Mustacchi wait = eom->eom_etbetter;
521*c96729eaSRobert Mustacchi desc = "better";
522*c96729eaSRobert Mustacchi } else {
523*c96729eaSRobert Mustacchi wait = eom->eom_etbest;
524*c96729eaSRobert Mustacchi desc = "best";
525*c96729eaSRobert Mustacchi }
526*c96729eaSRobert Mustacchi nvmeadm_phyeye_wait(npa, req, buf, wait, desc);
527*c96729eaSRobert Mustacchi
528*c96729eaSRobert Mustacchi /*
529*c96729eaSRobert Mustacchi * Go ahead and calculate the final size. At this point we'll issue
530*c96729eaSRobert Mustacchi * requests that adjust the overall offset until we read everything and
531*c96729eaSRobert Mustacchi * write that out.
532*c96729eaSRobert Mustacchi */
533*c96729eaSRobert Mustacchi uint64_t act_len, off = 0;
534*c96729eaSRobert Mustacchi if (!nvme_log_disc_calc_size(disc, &act_len, buf, min_len)) {
535*c96729eaSRobert Mustacchi errx(-1, "failed to determine full phyeye log length");
536*c96729eaSRobert Mustacchi }
537*c96729eaSRobert Mustacchi
538*c96729eaSRobert Mustacchi while (off < act_len) {
539*c96729eaSRobert Mustacchi size_t to_read = MIN(act_len - off, PHYEYE_BUFSIZE);
540*c96729eaSRobert Mustacchi nvmeadm_phyeye_read(npa, req, buf, to_read, off);
541*c96729eaSRobert Mustacchi nvmeadm_phyeye_write(fd, buf, to_read, off);
542*c96729eaSRobert Mustacchi off += to_read;
543*c96729eaSRobert Mustacchi }
544*c96729eaSRobert Mustacchi
545*c96729eaSRobert Mustacchi /*
546*c96729eaSRobert Mustacchi * Now that we're done, get the initial header's worth of data again and
547*c96729eaSRobert Mustacchi * verify its generation to make sure nothing has changed on us.
548*c96729eaSRobert Mustacchi */
549*c96729eaSRobert Mustacchi nvmeadm_phyeye_read(npa, req, buf, sizeof (nvme_eom_hdr_t), 0);
550*c96729eaSRobert Mustacchi if (eom->eom_edgn != eom_gen) {
551*c96729eaSRobert Mustacchi (void) unlink(phy->pm_output);
552*c96729eaSRobert Mustacchi errx(-1, "PHY eye measurement generation unexpectedly changed: "
553*c96729eaSRobert Mustacchi "was 0x%x, now is 0x%x: aborting", eom_gen, eom->eom_edgn);
554*c96729eaSRobert Mustacchi }
555*c96729eaSRobert Mustacchi
556*c96729eaSRobert Mustacchi /*
557*c96729eaSRobert Mustacchi * Note we don't actually clear the data here and basically are willing
558*c96729eaSRobert Mustacchi * to leave this in the controller at this point.
559*c96729eaSRobert Mustacchi */
560*c96729eaSRobert Mustacchi (void) printf("phyeye successfully written to %s\n", phy->pm_output);
561*c96729eaSRobert Mustacchi
562*c96729eaSRobert Mustacchi free(buf);
563*c96729eaSRobert Mustacchi if (fd >= 0) {
564*c96729eaSRobert Mustacchi VERIFY0(close(fd));
565*c96729eaSRobert Mustacchi }
566*c96729eaSRobert Mustacchi nvme_log_disc_free(disc);
567*c96729eaSRobert Mustacchi nvme_log_req_fini(req);
568*c96729eaSRobert Mustacchi return (0);
569*c96729eaSRobert Mustacchi }
570*c96729eaSRobert Mustacchi
571*c96729eaSRobert Mustacchi void
usage_report_phyeye_cmd(const char * c_name)572*c96729eaSRobert Mustacchi usage_report_phyeye_cmd(const char *c_name)
573*c96729eaSRobert Mustacchi {
574*c96729eaSRobert Mustacchi (void) fprintf(stderr, "%s -f file [-l lane] [-e eye] [-m mode] "
575*c96729eaSRobert Mustacchi "<ctl>\n\n", c_name);
576*c96729eaSRobert Mustacchi (void) fprintf(stderr, " Report information about a physical eye "
577*c96729eaSRobert Mustacchi "measurement. Eye measurements can be\n taken with \"nvmeadm "
578*c96729eaSRobert Mustacchi "measure-phyeye\".\n");
579*c96729eaSRobert Mustacchi }
580*c96729eaSRobert Mustacchi
581*c96729eaSRobert Mustacchi void
optparse_report_phyeye_cmd(nvme_process_arg_t * npa)582*c96729eaSRobert Mustacchi optparse_report_phyeye_cmd(nvme_process_arg_t *npa)
583*c96729eaSRobert Mustacchi {
584*c96729eaSRobert Mustacchi int c;
585*c96729eaSRobert Mustacchi phyeye_report_t *phy;
586*c96729eaSRobert Mustacchi
587*c96729eaSRobert Mustacchi if ((phy = calloc(1, sizeof (phyeye_report_t))) == NULL) {
588*c96729eaSRobert Mustacchi err(-1, "failed to allocate memory for option tracking");
589*c96729eaSRobert Mustacchi }
590*c96729eaSRobert Mustacchi
591*c96729eaSRobert Mustacchi phy->pr_npa = npa;
592*c96729eaSRobert Mustacchi phy->pr_mode = PHYEYE_REPORT_M_ASCII;
593*c96729eaSRobert Mustacchi phy->pr_lane = UINT8_MAX;
594*c96729eaSRobert Mustacchi phy->pr_eye = UINT8_MAX;
595*c96729eaSRobert Mustacchi
596*c96729eaSRobert Mustacchi while ((c = getopt(npa->npa_argc, npa->npa_argv, ":e:f:l:m:")) != -1) {
597*c96729eaSRobert Mustacchi const char *errstr;
598*c96729eaSRobert Mustacchi
599*c96729eaSRobert Mustacchi switch (c) {
600*c96729eaSRobert Mustacchi case 'e':
601*c96729eaSRobert Mustacchi phy->pr_eye = strtonumx(optarg, 0, PHYEYE_MAX_EYE,
602*c96729eaSRobert Mustacchi &errstr, 0);
603*c96729eaSRobert Mustacchi if (errstr != NULL) {
604*c96729eaSRobert Mustacchi errx(-1, "failed to parse eye: value %s is "
605*c96729eaSRobert Mustacchi "%s: valid values are in the range [%u, "
606*c96729eaSRobert Mustacchi "%u]", optarg, errstr, 0, PHYEYE_MAX_EYE);
607*c96729eaSRobert Mustacchi }
608*c96729eaSRobert Mustacchi break;
609*c96729eaSRobert Mustacchi case 'f':
610*c96729eaSRobert Mustacchi phy->pr_input = optarg;
611*c96729eaSRobert Mustacchi break;
612*c96729eaSRobert Mustacchi case 'l':
613*c96729eaSRobert Mustacchi phy->pr_lane = strtonumx(optarg, 0, PHYEYE_MAX_LANE,
614*c96729eaSRobert Mustacchi &errstr, 0);
615*c96729eaSRobert Mustacchi if (errstr != NULL) {
616*c96729eaSRobert Mustacchi errx(-1, "failed to parse lane: value %s is "
617*c96729eaSRobert Mustacchi "%s: valid values are in the range [%u, "
618*c96729eaSRobert Mustacchi "%u]", optarg, errstr, 0, PHYEYE_MAX_LANE);
619*c96729eaSRobert Mustacchi }
620*c96729eaSRobert Mustacchi break;
621*c96729eaSRobert Mustacchi case 'm':
622*c96729eaSRobert Mustacchi if (strcasecmp(optarg, "print-eye") == 0) {
623*c96729eaSRobert Mustacchi phy->pr_mode = PHYEYE_REPORT_M_ASCII;
624*c96729eaSRobert Mustacchi } else if (strcasecmp(optarg, "eye-data") == 0) {
625*c96729eaSRobert Mustacchi phy->pr_mode = PHYEYE_REPORT_M_OED;
626*c96729eaSRobert Mustacchi } else {
627*c96729eaSRobert Mustacchi errx(-1, "invalid mode value: %s: valid values "
628*c96729eaSRobert Mustacchi "are 'print-eye' or 'eye-data'", optarg);
629*c96729eaSRobert Mustacchi }
630*c96729eaSRobert Mustacchi break;
631*c96729eaSRobert Mustacchi case '?':
632*c96729eaSRobert Mustacchi errx(-1, "unknown option: -%c", optopt);
633*c96729eaSRobert Mustacchi case ':':
634*c96729eaSRobert Mustacchi errx(-1, "option -%c requires an argument", optopt);
635*c96729eaSRobert Mustacchi
636*c96729eaSRobert Mustacchi }
637*c96729eaSRobert Mustacchi }
638*c96729eaSRobert Mustacchi
639*c96729eaSRobert Mustacchi if (phy->pr_input == NULL) {
640*c96729eaSRobert Mustacchi errx(-1, "missing required input file to process (-f)");
641*c96729eaSRobert Mustacchi }
642*c96729eaSRobert Mustacchi
643*c96729eaSRobert Mustacchi npa->npa_cmd_arg = phy;
644*c96729eaSRobert Mustacchi }
645*c96729eaSRobert Mustacchi
646*c96729eaSRobert Mustacchi /*
647*c96729eaSRobert Mustacchi * Normalize the optional eye data length. This is a uint32_t in revision 3
648*c96729eaSRobert Mustacchi * logs, but a uint16_t in revision 2 logs.
649*c96729eaSRobert Mustacchi */
650*c96729eaSRobert Mustacchi static uint32_t
phyeye_eol_oed_len(const nvme_eom_hdr_t * hdr,const nvme_eom_lane_desc_t * desc)651*c96729eaSRobert Mustacchi phyeye_eol_oed_len(const nvme_eom_hdr_t *hdr, const nvme_eom_lane_desc_t *desc)
652*c96729eaSRobert Mustacchi {
653*c96729eaSRobert Mustacchi uint16_t u16;
654*c96729eaSRobert Mustacchi
655*c96729eaSRobert Mustacchi if (hdr->eom_lrev >= 3) {
656*c96729eaSRobert Mustacchi return (desc->eld_edlen);
657*c96729eaSRobert Mustacchi }
658*c96729eaSRobert Mustacchi
659*c96729eaSRobert Mustacchi (void) memcpy(&u16, &desc->eld_edlen, sizeof (uint16_t));
660*c96729eaSRobert Mustacchi return (u16);
661*c96729eaSRobert Mustacchi }
662*c96729eaSRobert Mustacchi
663*c96729eaSRobert Mustacchi typedef void (*phyeye_lane_iter_cb_f)(uint8_t, const nvme_eom_hdr_t *,
664*c96729eaSRobert Mustacchi const nvme_eom_lane_desc_t *, void *);
665*c96729eaSRobert Mustacchi
666*c96729eaSRobert Mustacchi 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)667*c96729eaSRobert Mustacchi phyeye_lane_iter(const nvme_eom_hdr_t *hdr, off_t max,
668*c96729eaSRobert Mustacchi void *arg, phyeye_lane_iter_cb_f func, uint8_t lane, uint8_t eye)
669*c96729eaSRobert Mustacchi {
670*c96729eaSRobert Mustacchi off_t cur_off = sizeof (nvme_eom_hdr_t);
671*c96729eaSRobert Mustacchi bool ret = true;
672*c96729eaSRobert Mustacchi
673*c96729eaSRobert Mustacchi for (uint16_t i = 0; i < hdr->eom_nd; i++) {
674*c96729eaSRobert Mustacchi const nvme_eom_lane_desc_t *desc = NULL;
675*c96729eaSRobert Mustacchi size_t dlen = sizeof (nvme_eom_lane_desc_t);
676*c96729eaSRobert Mustacchi
677*c96729eaSRobert Mustacchi if (cur_off + hdr->eom_ds > max) {
678*c96729eaSRobert Mustacchi errx(-1, "failed to iterate EOM Lane descriptors: "
679*c96729eaSRobert Mustacchi "descriptor %u starts at offset 0x%lx, but its "
680*c96729eaSRobert Mustacchi "size (0x%x) would exceed the maximum file length "
681*c96729eaSRobert Mustacchi "of 0x%lx", i, cur_off, hdr->eom_ds, max);
682*c96729eaSRobert Mustacchi }
683*c96729eaSRobert Mustacchi
684*c96729eaSRobert Mustacchi desc = (const nvme_eom_lane_desc_t *)((uintptr_t)hdr + cur_off);
685*c96729eaSRobert Mustacchi
686*c96729eaSRobert Mustacchi if (hdr->eom_odp.odp_pefp != 0) {
687*c96729eaSRobert Mustacchi if (desc->eld_nrows == 0 || desc->eld_ncols == 0) {
688*c96729eaSRobert Mustacchi errx(-1, "printable eye feature present but "
689*c96729eaSRobert Mustacchi "both NROWS (0x%x) and NCOLS (0x%x) are "
690*c96729eaSRobert Mustacchi "not non-zero", desc->eld_nrows,
691*c96729eaSRobert Mustacchi desc->eld_ncols);
692*c96729eaSRobert Mustacchi }
693*c96729eaSRobert Mustacchi
694*c96729eaSRobert Mustacchi dlen += desc->eld_nrows * desc->eld_ncols;
695*c96729eaSRobert Mustacchi } else if (desc->eld_nrows != 0 || desc->eld_ncols != 0) {
696*c96729eaSRobert Mustacchi errx(-1, "printable eye feature not present but both "
697*c96729eaSRobert Mustacchi "NROWS (0x%x) and NCOLS (0x%x) are not zero",
698*c96729eaSRobert Mustacchi desc->eld_nrows, desc->eld_ncols);
699*c96729eaSRobert Mustacchi }
700*c96729eaSRobert Mustacchi
701*c96729eaSRobert Mustacchi const uint32_t oed_len = phyeye_eol_oed_len(hdr, desc);
702*c96729eaSRobert Mustacchi if (hdr->eom_odp.odp_edfp != 0) {
703*c96729eaSRobert Mustacchi if (oed_len == 0) {
704*c96729eaSRobert Mustacchi errx(-1, "optional eye data feature present, "
705*c96729eaSRobert Mustacchi "but eye data has a zero-length");
706*c96729eaSRobert Mustacchi }
707*c96729eaSRobert Mustacchi
708*c96729eaSRobert Mustacchi dlen += oed_len;
709*c96729eaSRobert Mustacchi } else if (oed_len != 0) {
710*c96729eaSRobert Mustacchi errx(-1, "optional eye data feature not present, but "
711*c96729eaSRobert Mustacchi "eye data has a non-zero length (0x%x)", oed_len);
712*c96729eaSRobert Mustacchi }
713*c96729eaSRobert Mustacchi
714*c96729eaSRobert Mustacchi if (dlen > hdr->eom_ds) {
715*c96729eaSRobert Mustacchi errx(-1, "failed to iterate EOM Lane descriptors: "
716*c96729eaSRobert Mustacchi "descriptor %u starts at offset 0x%lx, has a "
717*c96729eaSRobert Mustacchi "calculated size (0x%zx) that exceeds the "
718*c96729eaSRobert Mustacchi "header's max descriptor size (0x%x)", i, cur_off,
719*c96729eaSRobert Mustacchi dlen, hdr->eom_ds);
720*c96729eaSRobert Mustacchi }
721*c96729eaSRobert Mustacchi
722*c96729eaSRobert Mustacchi /*
723*c96729eaSRobert Mustacchi * Now that we've validated this we need to check a few things
724*c96729eaSRobert Mustacchi * before we call the command:
725*c96729eaSRobert Mustacchi *
726*c96729eaSRobert Mustacchi * 1. This matches our eye and lane filter.
727*c96729eaSRobert Mustacchi * 2. The data is valid.
728*c96729eaSRobert Mustacchi */
729*c96729eaSRobert Mustacchi if (lane != UINT8_MAX && desc->eld_ln != lane)
730*c96729eaSRobert Mustacchi goto next;
731*c96729eaSRobert Mustacchi if (eye != UINT8_MAX && desc->eld_eye != eye)
732*c96729eaSRobert Mustacchi goto next;
733*c96729eaSRobert Mustacchi if (desc->eld_mstat.mstat_mcsc == 0) {
734*c96729eaSRobert Mustacchi warnx("lane %u, eye %u data does not have a successful "
735*c96729eaSRobert Mustacchi "measurement", desc->eld_ln, desc->eld_eye);
736*c96729eaSRobert Mustacchi ret = false;
737*c96729eaSRobert Mustacchi goto next;
738*c96729eaSRobert Mustacchi }
739*c96729eaSRobert Mustacchi
740*c96729eaSRobert Mustacchi func(i, hdr, desc, arg);
741*c96729eaSRobert Mustacchi
742*c96729eaSRobert Mustacchi next:
743*c96729eaSRobert Mustacchi cur_off += dlen;
744*c96729eaSRobert Mustacchi }
745*c96729eaSRobert Mustacchi
746*c96729eaSRobert Mustacchi return (ret);
747*c96729eaSRobert Mustacchi }
748*c96729eaSRobert Mustacchi
749*c96729eaSRobert Mustacchi /*
750*c96729eaSRobert Mustacchi * Validate the data that we have. In particular we need to confirm:
751*c96729eaSRobert Mustacchi *
752*c96729eaSRobert Mustacchi * 1. The data file covers the entire header.
753*c96729eaSRobert Mustacchi * 2. This is a log revision we know about.
754*c96729eaSRobert Mustacchi * 3. The measurement is completed.
755*c96729eaSRobert Mustacchi * 4. The header size reported is what we expect.
756*c96729eaSRobert Mustacchi * 5. The result size is covered by the file.
757*c96729eaSRobert Mustacchi * 6. If a specific mode requires optional data, it is present.
758*c96729eaSRobert Mustacchi * 7. There is a non-zero number of descriptors.
759*c96729eaSRobert Mustacchi * 8. The descriptor size covers at least the Lane descriptor structure.
760*c96729eaSRobert Mustacchi * 9. DS * NDS is within the result size.
761*c96729eaSRobert Mustacchi *
762*c96729eaSRobert Mustacchi * The specifics of each descriptor are checked when we iterate over them in
763*c96729eaSRobert Mustacchi * phyeye_eol_iter().
764*c96729eaSRobert Mustacchi */
765*c96729eaSRobert Mustacchi static void
phyeye_report_sanity_check(const nvme_eom_hdr_t * hdr,off_t len,const phyeye_report_t * phy)766*c96729eaSRobert Mustacchi phyeye_report_sanity_check(const nvme_eom_hdr_t *hdr, off_t len,
767*c96729eaSRobert Mustacchi const phyeye_report_t *phy)
768*c96729eaSRobert Mustacchi {
769*c96729eaSRobert Mustacchi if (len < sizeof (nvme_eom_hdr_t)) {
770*c96729eaSRobert Mustacchi errx(-1, "data file is too short: file does not cover the "
771*c96729eaSRobert Mustacchi "0x%lx bytes required for the Eye Opening Measurement "
772*c96729eaSRobert Mustacchi "header", sizeof (nvme_eom_hdr_t));
773*c96729eaSRobert Mustacchi }
774*c96729eaSRobert Mustacchi
775*c96729eaSRobert Mustacchi /*
776*c96729eaSRobert Mustacchi * This specification was first introduced in the NVMe PCIe revision 1.1
777*c96729eaSRobert Mustacchi * at log revision 2. It moved to version 3 with NVMe PCIe 1.2. However,
778*c96729eaSRobert Mustacchi * some devices report log revision 1 which means they likely implement
779*c96729eaSRobert Mustacchi * a draft version of the TP. We don't know what's different between
780*c96729eaSRobert Mustacchi * version 1 and 2, but hope for the sake of understanding right now
781*c96729eaSRobert Mustacchi * it doesn't impact our ability to translate this.
782*c96729eaSRobert Mustacchi */
783*c96729eaSRobert Mustacchi if (hdr->eom_lrev < 1 || hdr->eom_lrev > 3) {
784*c96729eaSRobert Mustacchi errx(-1, "encountered unknown log header revision: 0x%x",
785*c96729eaSRobert Mustacchi hdr->eom_lrev);
786*c96729eaSRobert Mustacchi }
787*c96729eaSRobert Mustacchi
788*c96729eaSRobert Mustacchi /*
789*c96729eaSRobert Mustacchi * Only worry about complete measurements if we're doing a report, not
790*c96729eaSRobert Mustacchi * if we're just printing the log.
791*c96729eaSRobert Mustacchi */
792*c96729eaSRobert Mustacchi if (phy != NULL && hdr->eom_eomip != NVME_EOM_DONE) {
793*c96729eaSRobert Mustacchi errx(-1, "data file measurement in progress field does not "
794*c96729eaSRobert Mustacchi "indicate a finished measurement (%u): found %u",
795*c96729eaSRobert Mustacchi NVME_EOM_DONE, hdr->eom_eomip);
796*c96729eaSRobert Mustacchi }
797*c96729eaSRobert Mustacchi
798*c96729eaSRobert Mustacchi if (hdr->eom_hsize != sizeof (nvme_eom_hdr_t)) {
799*c96729eaSRobert Mustacchi errx(-1, "data file has unexpected header length: found 0x%x, "
800*c96729eaSRobert Mustacchi "expected 0x%zx", hdr->eom_hsize, sizeof (nvme_eom_hdr_t));
801*c96729eaSRobert Mustacchi }
802*c96729eaSRobert Mustacchi
803*c96729eaSRobert Mustacchi if (hdr->eom_rsz > len) {
804*c96729eaSRobert Mustacchi errx(-1, "data file reports that the log is 0x%x bytes, but "
805*c96729eaSRobert Mustacchi "file is only 0x%lx bytes", hdr->eom_rsz, len);
806*c96729eaSRobert Mustacchi }
807*c96729eaSRobert Mustacchi
808*c96729eaSRobert Mustacchi if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_ASCII &&
809*c96729eaSRobert Mustacchi hdr->eom_odp.odp_pefp == 0) {
810*c96729eaSRobert Mustacchi errx(-1, "Printable Eye requested, but field not present in "
811*c96729eaSRobert Mustacchi "data file");
812*c96729eaSRobert Mustacchi }
813*c96729eaSRobert Mustacchi
814*c96729eaSRobert Mustacchi if (phy != NULL && phy->pr_mode == PHYEYE_REPORT_M_OED &&
815*c96729eaSRobert Mustacchi hdr->eom_odp.odp_edfp == 0) {
816*c96729eaSRobert Mustacchi errx(-1, "Eye Data Field requested, but field not present in "
817*c96729eaSRobert Mustacchi "data file");
818*c96729eaSRobert Mustacchi }
819*c96729eaSRobert Mustacchi
820*c96729eaSRobert Mustacchi if (phy != NULL && hdr->eom_nd == 0) {
821*c96729eaSRobert Mustacchi errx(-1, "data file reports no EOM lane descriptors present");
822*c96729eaSRobert Mustacchi }
823*c96729eaSRobert Mustacchi
824*c96729eaSRobert Mustacchi if (hdr->eom_nd > 0 && hdr->eom_ds < sizeof (nvme_eom_lane_desc_t)) {
825*c96729eaSRobert Mustacchi errx(-1, "data file reports the descriptor size is 0x%x, but "
826*c96729eaSRobert Mustacchi "that is less than the base descriptor size of 0x%zx",
827*c96729eaSRobert Mustacchi hdr->eom_ds, sizeof (nvme_eom_lane_desc_t));
828*c96729eaSRobert Mustacchi }
829*c96729eaSRobert Mustacchi }
830*c96729eaSRobert Mustacchi
831*c96729eaSRobert Mustacchi static void
phyeye_report_ascii(uint8_t descno,const nvme_eom_hdr_t * hdr,const nvme_eom_lane_desc_t * desc,void * arg)832*c96729eaSRobert Mustacchi phyeye_report_ascii(uint8_t descno, const nvme_eom_hdr_t *hdr,
833*c96729eaSRobert Mustacchi const nvme_eom_lane_desc_t *desc, void *arg)
834*c96729eaSRobert Mustacchi {
835*c96729eaSRobert Mustacchi phyeye_report_t *phy = arg;
836*c96729eaSRobert Mustacchi
837*c96729eaSRobert Mustacchi phy->pr_print = true;
838*c96729eaSRobert Mustacchi (void) printf("Lane %u, Eye %u: Printable Eye\n", desc->eld_ln,
839*c96729eaSRobert Mustacchi desc->eld_eye);
840*c96729eaSRobert Mustacchi for (uint16_t row = 0; row < desc->eld_nrows; row++) {
841*c96729eaSRobert Mustacchi for (uint16_t col = 0; col < desc->eld_ncols; col++) {
842*c96729eaSRobert Mustacchi const uint32_t off = row * desc->eld_ncols + col;
843*c96729eaSRobert Mustacchi uint8_t c = desc->eld_data[off];
844*c96729eaSRobert Mustacchi if (c != '0' && c != '1')
845*c96729eaSRobert Mustacchi c = '?';
846*c96729eaSRobert Mustacchi (void) putc(c, stdout);
847*c96729eaSRobert Mustacchi }
848*c96729eaSRobert Mustacchi (void) putc('\n', stdout);
849*c96729eaSRobert Mustacchi }
850*c96729eaSRobert Mustacchi }
851*c96729eaSRobert Mustacchi
852*c96729eaSRobert Mustacchi static void
phyeye_report_oed(uint8_t descno,const nvme_eom_hdr_t * hdr,const nvme_eom_lane_desc_t * desc,void * arg)853*c96729eaSRobert Mustacchi phyeye_report_oed(uint8_t descno, const nvme_eom_hdr_t *hdr,
854*c96729eaSRobert Mustacchi const nvme_eom_lane_desc_t *desc, void *arg)
855*c96729eaSRobert Mustacchi {
856*c96729eaSRobert Mustacchi phyeye_report_t *phy = arg;
857*c96729eaSRobert Mustacchi
858*c96729eaSRobert Mustacchi size_t off = desc->eld_nrows * desc->eld_ncols;
859*c96729eaSRobert Mustacchi
860*c96729eaSRobert Mustacchi phy->pr_print = true;
861*c96729eaSRobert Mustacchi (void) printf("Lane %u, Eye %u: Eye Data\n", desc->eld_ln,
862*c96729eaSRobert Mustacchi desc->eld_eye);
863*c96729eaSRobert Mustacchi nvmeadm_dump_hex(&desc->eld_data[off], desc->eld_edlen);
864*c96729eaSRobert Mustacchi }
865*c96729eaSRobert Mustacchi
866*c96729eaSRobert Mustacchi int
do_report_phyeye_cmd(const nvme_process_arg_t * npa)867*c96729eaSRobert Mustacchi do_report_phyeye_cmd(const nvme_process_arg_t *npa)
868*c96729eaSRobert Mustacchi {
869*c96729eaSRobert Mustacchi int fd = -1, ret = EXIT_SUCCESS;
870*c96729eaSRobert Mustacchi struct stat st;
871*c96729eaSRobert Mustacchi void *data;
872*c96729eaSRobert Mustacchi phyeye_report_t *phy = npa->npa_cmd_arg;
873*c96729eaSRobert Mustacchi phyeye_lane_iter_cb_f func = NULL;
874*c96729eaSRobert Mustacchi
875*c96729eaSRobert Mustacchi if (npa->npa_argc > 0) {
876*c96729eaSRobert Mustacchi errx(-1, "extraneous arguments beginning with '%s'",
877*c96729eaSRobert Mustacchi npa->npa_argv[0]);
878*c96729eaSRobert Mustacchi }
879*c96729eaSRobert Mustacchi
880*c96729eaSRobert Mustacchi if ((fd = open(phy->pr_input, O_RDONLY)) < 0) {
881*c96729eaSRobert Mustacchi err(-1, "failed to open input file %s", phy->pr_input);
882*c96729eaSRobert Mustacchi }
883*c96729eaSRobert Mustacchi
884*c96729eaSRobert Mustacchi if (fstat(fd, &st) != 0) {
885*c96729eaSRobert Mustacchi err(-1, "failed to stat %s", phy->pr_input);
886*c96729eaSRobert Mustacchi }
887*c96729eaSRobert Mustacchi
888*c96729eaSRobert Mustacchi if (st.st_size > NVMEADM_MAX_MMAP) {
889*c96729eaSRobert Mustacchi errx(-1, "%s file size of 0x%lx exceeds maximum allowed size "
890*c96729eaSRobert Mustacchi "of 0x%llx", phy->pr_input, st.st_size, NVMEADM_MAX_MMAP);
891*c96729eaSRobert Mustacchi }
892*c96729eaSRobert Mustacchi
893*c96729eaSRobert Mustacchi data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
894*c96729eaSRobert Mustacchi if (data == MAP_FAILED) {
895*c96729eaSRobert Mustacchi errx(-1, "failed to mmap %s", phy->pr_input);
896*c96729eaSRobert Mustacchi }
897*c96729eaSRobert Mustacchi
898*c96729eaSRobert Mustacchi phyeye_report_sanity_check(data, st.st_size, phy);
899*c96729eaSRobert Mustacchi
900*c96729eaSRobert Mustacchi switch (phy->pr_mode) {
901*c96729eaSRobert Mustacchi case PHYEYE_REPORT_M_ASCII:
902*c96729eaSRobert Mustacchi func = phyeye_report_ascii;
903*c96729eaSRobert Mustacchi break;
904*c96729eaSRobert Mustacchi case PHYEYE_REPORT_M_OED:
905*c96729eaSRobert Mustacchi func = phyeye_report_oed;
906*c96729eaSRobert Mustacchi break;
907*c96729eaSRobert Mustacchi }
908*c96729eaSRobert Mustacchi
909*c96729eaSRobert Mustacchi if (!phyeye_lane_iter(data, st.st_size, phy, func, phy->pr_lane,
910*c96729eaSRobert Mustacchi phy->pr_eye)) {
911*c96729eaSRobert Mustacchi ret = -1;
912*c96729eaSRobert Mustacchi }
913*c96729eaSRobert Mustacchi
914*c96729eaSRobert Mustacchi /*
915*c96729eaSRobert Mustacchi * If nothing was printed, warn and error about this if we had an eye
916*c96729eaSRobert Mustacchi * constraint, a lane constraint, or the mode isn't decoding. We want to
917*c96729eaSRobert Mustacchi * be able to decode a log that has no data.
918*c96729eaSRobert Mustacchi */
919*c96729eaSRobert Mustacchi if (!phy->pr_print) {
920*c96729eaSRobert Mustacchi warnx("failed to match and print any data");
921*c96729eaSRobert Mustacchi ret = -1;
922*c96729eaSRobert Mustacchi }
923*c96729eaSRobert Mustacchi
924*c96729eaSRobert Mustacchi VERIFY0(munmap(data, st.st_size));
925*c96729eaSRobert Mustacchi if (fd >= 0) {
926*c96729eaSRobert Mustacchi VERIFY0(close(fd));
927*c96729eaSRobert Mustacchi }
928*c96729eaSRobert Mustacchi
929*c96729eaSRobert Mustacchi return (ret);
930*c96729eaSRobert Mustacchi }
931*c96729eaSRobert Mustacchi
932*c96729eaSRobert Mustacchi static uint32_t
phyeye_log_getrev(const void * data,size_t len)933*c96729eaSRobert Mustacchi phyeye_log_getrev(const void *data, size_t len)
934*c96729eaSRobert Mustacchi {
935*c96729eaSRobert Mustacchi const nvme_eom_hdr_t *hdr = data;
936*c96729eaSRobert Mustacchi return (hdr->eom_lrev);
937*c96729eaSRobert Mustacchi }
938*c96729eaSRobert Mustacchi
939*c96729eaSRobert Mustacchi 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)940*c96729eaSRobert Mustacchi phyeye_log_drive_lane_cb(uint8_t descno, const nvme_eom_hdr_t *hdr,
941*c96729eaSRobert Mustacchi const nvme_eom_lane_desc_t *desc, void *arg)
942*c96729eaSRobert Mustacchi {
943*c96729eaSRobert Mustacchi char base[32];
944*c96729eaSRobert Mustacchi char header[128];
945*c96729eaSRobert Mustacchi nvmeadm_field_print_t *print = arg;
946*c96729eaSRobert Mustacchi
947*c96729eaSRobert Mustacchi (void) snprintf(base, sizeof (base), "eld%u", descno);
948*c96729eaSRobert Mustacchi (void) snprintf(header, sizeof (header), "EOM Lane Descriptor %u",
949*c96729eaSRobert Mustacchi descno);
950*c96729eaSRobert Mustacchi
951*c96729eaSRobert Mustacchi print->fp_header = header;
952*c96729eaSRobert Mustacchi print->fp_fields = phyeye_elm_fields;
953*c96729eaSRobert Mustacchi print->fp_nfields = ARRAY_SIZE(phyeye_elm_fields);
954*c96729eaSRobert Mustacchi print->fp_base = base;
955*c96729eaSRobert Mustacchi print->fp_data = desc;
956*c96729eaSRobert Mustacchi print->fp_dlen = hdr->eom_ds;
957*c96729eaSRobert Mustacchi print->fp_off = (uintptr_t)desc - (uintptr_t)hdr;
958*c96729eaSRobert Mustacchi
959*c96729eaSRobert Mustacchi nvmeadm_field_print(print);
960*c96729eaSRobert Mustacchi }
961*c96729eaSRobert Mustacchi
962*c96729eaSRobert Mustacchi static bool
phyeye_log_drive(nvmeadm_field_print_t * print,const void * data,size_t len)963*c96729eaSRobert Mustacchi phyeye_log_drive(nvmeadm_field_print_t *print, const void *data, size_t len)
964*c96729eaSRobert Mustacchi {
965*c96729eaSRobert Mustacchi print->fp_header = "EOM Header";
966*c96729eaSRobert Mustacchi print->fp_fields = phyeye_eom_fields;
967*c96729eaSRobert Mustacchi print->fp_nfields = ARRAY_SIZE(phyeye_eom_fields);
968*c96729eaSRobert Mustacchi print->fp_base = "eom";
969*c96729eaSRobert Mustacchi print->fp_data = data;
970*c96729eaSRobert Mustacchi print->fp_dlen = len;
971*c96729eaSRobert Mustacchi print->fp_off = 0;
972*c96729eaSRobert Mustacchi
973*c96729eaSRobert Mustacchi phyeye_report_sanity_check(data, len, NULL);
974*c96729eaSRobert Mustacchi nvmeadm_field_print(print);
975*c96729eaSRobert Mustacchi
976*c96729eaSRobert Mustacchi return (phyeye_lane_iter(data, len, print, phyeye_log_drive_lane_cb,
977*c96729eaSRobert Mustacchi UINT8_MAX, UINT8_MAX));
978*c96729eaSRobert Mustacchi }
979*c96729eaSRobert Mustacchi
980*c96729eaSRobert Mustacchi const nvmeadm_log_field_info_t phyeye_field_info = {
981*c96729eaSRobert Mustacchi .nlfi_log = "phyeye",
982*c96729eaSRobert Mustacchi .nlfi_min = sizeof (nvme_eom_hdr_t),
983*c96729eaSRobert Mustacchi .nlfi_getrev = phyeye_log_getrev,
984*c96729eaSRobert Mustacchi .nlfi_drive = phyeye_log_drive
985*c96729eaSRobert Mustacchi };
986