]> mj.ucw.cz Git - pciutils.git/blob - pcilmr.c
pcilmr: Add option to save margining results in csv form
[pciutils.git] / pcilmr.c
1 /*
2  *      The PCI Utilities -- Margining utility main function
3  *
4  *      Copyright (c) 2023 KNS Group LLC (YADRO)
5  *
6  *      Can be freely distributed and used under the terms of the GNU GPL v2+.
7  *
8  *      SPDX-License-Identifier: GPL-2.0-or-later
9  */
10
11 #include <getopt.h>
12 #include <memory.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15
16 #include "lmr/lmr.h"
17
18 const char program_name[] = "pcilmr";
19
20 enum mode { MARGIN, FULL, SCAN };
21
22 static const char usage_msg[]
23   = "Usage:\n"
24     "pcilmr [--margin] [<margining options>] <downstream component> ...\n"
25     "pcilmr --full [<margining options>]\n"
26     "pcilmr --scan\n\n"
27     "Device Specifier:\n"
28     "<device/component>:\t[<domain>:]<bus>:<dev>.<func>\n\n"
29     "Modes:\n"
30     "--margin\t\tMargin selected Links\n"
31     "--full\t\t\tMargin all ready for testing Links in the system (one by one)\n"
32     "--scan\t\t\tScan for Links available for margining\n\n"
33     "Margining options:\n\n"
34     "Margining Test settings:\n"
35     "-c\t\t\tPrint Device Lane Margining Capabilities only. Do not run margining.\n"
36     "-l <lane>[,<lane>...]\tSpecify lanes for margining. Default: all link lanes.\n"
37     "\t\t\tRemember that Device may use Lane Reversal for Lane numbering.\n"
38     "\t\t\tHowever, utility uses logical lane numbers in arguments and for logging.\n"
39     "\t\t\tUtility will automatically determine Lane Reversal and tune its calls.\n"
40     "-e <errors>\t\tSpecify Error Count Limit for margining. Default: 4.\n"
41     "-r <recvn>[,<recvn>...]\tSpecify Receivers to select margining targets.\n"
42     "\t\t\tDefault: all available Receivers (including Retimers).\n"
43     "-p <parallel_lanes>\tSpecify number of lanes to margin simultaneously.\n"
44     "\t\t\tDefault: 1.\n"
45     "\t\t\tAccording to spec it's possible for Receiver to margin up\n"
46     "\t\t\tto MaxLanes + 1 lanes simultaneously, but usually this works\n"
47     "\t\t\tbad, so this option is for experiments mostly.\n"
48     "-T\t\t\tTime Margining will continue until the Error Count is no more\n"
49     "\t\t\tthan an Error Count Limit. Use this option to find Link limit.\n"
50     "-V\t\t\tSame as -T option, but for Voltage.\n"
51     "-t <steps>\t\tSpecify maximum number of steps for Time Margining.\n"
52     "-v <steps>\t\tSpecify maximum number of steps for Voltage Margining.\n"
53     "Use only one of -T/-t options at the same time (same for -V/-v).\n"
54     "Without these options utility will use MaxSteps from Device\n"
55     "capabilities as test limit.\n\n"
56     "Margining Log settings:\n"
57     "-o <directory>\t\tSave margining results in csv form into the\n"
58     "\t\t\tspecified directory. Utility will generate file with the\n"
59     "\t\t\tname in form of 'lmr_<downstream component>_Rx#_<timestamp>.csv'\n"
60     "\t\t\tfor each successfully tested receiver.\n";
61
62 static struct pci_dev *
63 dev_for_filter(struct pci_access *pacc, char *filter)
64 {
65   struct pci_filter pci_filter;
66   char dev[17] = { 0 };
67   strncpy(dev, filter, sizeof(dev) - 1);
68   pci_filter_init(pacc, &pci_filter);
69   if (pci_filter_parse_slot(&pci_filter, dev))
70     die("Invalid device ID: %s\n", filter);
71
72   if (pci_filter.bus == -1 || pci_filter.slot == -1 || pci_filter.func == -1)
73     die("Invalid device ID: %s\n", filter);
74
75   if (pci_filter.domain == -1)
76     pci_filter.domain = 0;
77
78   for (struct pci_dev *p = pacc->devices; p; p = p->next)
79     {
80       if (pci_filter_match(&pci_filter, p))
81         return p;
82     }
83
84   die("No such PCI device: %s or you don't have enough privileges.\n", filter);
85 }
86
87 static struct pci_dev *
88 find_down_port_for_up(struct pci_access *pacc, struct pci_dev *up)
89 {
90   struct pci_dev *down = NULL;
91   for (struct pci_dev *p = pacc->devices; p; p = p->next)
92     {
93       if (pci_read_byte(p, PCI_SECONDARY_BUS) == up->bus && up->domain == p->domain)
94         {
95           down = p;
96           break;
97         }
98     }
99   return down;
100 }
101
102 static u8
103 parse_csv_arg(char *arg, u8 *vals)
104 {
105   u8 cnt = 0;
106   char *token = strtok(arg, ",");
107   while (token)
108     {
109       vals[cnt] = atoi(token);
110       cnt++;
111       token = strtok(NULL, ",");
112     }
113   return cnt;
114 }
115
116 static void
117 scan_links(struct pci_access *pacc, bool only_ready)
118 {
119   if (only_ready)
120     printf("Links ready for margining:\n");
121   else
122     printf("Links with Lane Margining at the Receiver capabilities:\n");
123   bool flag = true;
124   for (struct pci_dev *up = pacc->devices; up; up = up->next)
125     {
126       if (pci_find_cap(up, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED))
127         {
128           struct pci_dev *down = find_down_port_for_up(pacc, up);
129
130           if (down && margin_verify_link(down, up))
131             {
132               margin_log_bdfs(down, up);
133               if (!only_ready && (margin_check_ready_bit(down) || margin_check_ready_bit(up)))
134                 printf(" - Ready");
135               printf("\n");
136               flag = false;
137             }
138         }
139     }
140   if (flag)
141     printf("Links not found or you don't have enough privileges.\n");
142   pci_cleanup(pacc);
143   exit(0);
144 }
145
146 static u8
147 find_ready_links(struct pci_access *pacc, struct pci_dev **down_ports, struct pci_dev **up_ports,
148                  bool cnt_only)
149 {
150   u8 cnt = 0;
151   for (struct pci_dev *up = pacc->devices; up; up = up->next)
152     {
153       if (pci_find_cap(up, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED))
154         {
155           struct pci_dev *down = find_down_port_for_up(pacc, up);
156
157           if (down && margin_verify_link(down, up)
158               && (margin_check_ready_bit(down) || margin_check_ready_bit(up)))
159             {
160               if (!cnt_only)
161                 {
162                   up_ports[cnt] = up;
163                   down_ports[cnt] = down;
164                 }
165               cnt++;
166             }
167         }
168     }
169   return cnt;
170 }
171
172 int
173 main(int argc, char **argv)
174 {
175   struct pci_access *pacc;
176
177   struct pci_dev **up_ports;
178   struct pci_dev **down_ports;
179   u8 ports_n = 0;
180
181   struct margin_link *links;
182   bool *checks_status_ports;
183
184   bool status = true;
185   enum mode mode;
186
187   /* each link has several receivers -> several results */
188   struct margin_results **results;
189   u8 *results_n;
190
191   struct margin_args *args;
192
193   u8 steps_t_arg = 0;
194   u8 steps_v_arg = 0;
195   u8 parallel_lanes_arg = 1;
196   u8 error_limit = 4;
197   u8 lanes_arg[32];
198   u8 recvs_arg[6];
199
200   u8 lanes_n = 0;
201   u8 recvs_n = 0;
202
203   bool run_margin = true;
204
205   char *dir_for_csv = NULL;
206   bool save_csv = false;
207
208   u64 total_steps = 0;
209
210   pacc = pci_alloc();
211   pci_init(pacc);
212   pci_scan_bus(pacc);
213
214   margin_print_domain = false;
215   for (struct pci_dev *dev = pacc->devices; dev; dev = dev->next)
216     {
217       if (dev->domain != 0)
218         {
219           margin_print_domain = true;
220           break;
221         }
222     }
223
224   margin_global_logging = true;
225
226   struct option long_options[]
227     = { { .name = "margin", .has_arg = no_argument, .flag = NULL, .val = 0 },
228         { .name = "scan", .has_arg = no_argument, .flag = NULL, .val = 1 },
229         { .name = "full", .has_arg = no_argument, .flag = NULL, .val = 2 },
230         { 0, 0, 0, 0 } };
231
232   int c;
233   c = getopt_long(argc, argv, ":", long_options, NULL);
234
235   switch (c)
236     {
237       case -1: /* no options (strings like component are possible) */
238         /* FALLTHROUGH */
239       case 0:
240         mode = MARGIN;
241         break;
242       case 1:
243         mode = SCAN;
244         if (optind == argc)
245           scan_links(pacc, false);
246         optind--;
247         break;
248       case 2:
249         mode = FULL;
250         break;
251       default: /* unknown option symbol */
252         mode = MARGIN;
253         optind--;
254         break;
255     }
256
257   while ((c = getopt(argc, argv, ":r:e:l:cp:t:v:VTo:")) != -1)
258     {
259       switch (c)
260         {
261           case 't':
262             steps_t_arg = atoi(optarg);
263             break;
264           case 'T':
265             steps_t_arg = 63;
266             break;
267           case 'v':
268             steps_v_arg = atoi(optarg);
269             break;
270           case 'V':
271             steps_v_arg = 127;
272             break;
273           case 'p':
274             parallel_lanes_arg = atoi(optarg);
275             break;
276           case 'c':
277             run_margin = false;
278             break;
279           case 'l':
280             lanes_n = parse_csv_arg(optarg, lanes_arg);
281             break;
282           case 'e':
283             error_limit = atoi(optarg);
284             break;
285           case 'r':
286             recvs_n = parse_csv_arg(optarg, recvs_arg);
287             break;
288           case 'o':
289             dir_for_csv = optarg;
290             save_csv = true;
291             break;
292           default:
293             die("Invalid arguments\n\n%s", usage_msg);
294         }
295     }
296
297   if (mode == FULL && optind != argc)
298     status = false;
299   if (mode == MARGIN && optind == argc)
300     status = false;
301   if (!status && argc > 1)
302     die("Invalid arguments\n\n%s", usage_msg);
303   if (!status)
304     {
305       printf("%s", usage_msg);
306       exit(0);
307     }
308
309   if (mode == FULL)
310     {
311       ports_n = find_ready_links(pacc, NULL, NULL, true);
312       if (ports_n == 0)
313         {
314           die("Links not found or you don't have enough privileges.\n");
315         }
316       else
317         {
318           up_ports = xmalloc(ports_n * sizeof(*up_ports));
319           down_ports = xmalloc(ports_n * sizeof(*down_ports));
320           find_ready_links(pacc, down_ports, up_ports, false);
321         }
322     }
323   else if (mode == MARGIN)
324     {
325       ports_n = argc - optind;
326       up_ports = xmalloc(ports_n * sizeof(*up_ports));
327       down_ports = xmalloc(ports_n * sizeof(*down_ports));
328
329       u8 cnt = 0;
330       while (optind != argc)
331         {
332           up_ports[cnt] = dev_for_filter(pacc, argv[optind]);
333           down_ports[cnt] = find_down_port_for_up(pacc, up_ports[cnt]);
334           if (!down_ports[cnt])
335             die("Cannot find Upstream Component for the specified device: %s\n", argv[optind]);
336           cnt++;
337           optind++;
338         }
339     }
340   else
341     die("Bug in the args parsing!\n");
342
343   if (!pci_find_cap(up_ports[0], PCI_CAP_ID_EXP, PCI_CAP_NORMAL))
344     die("Looks like you don't have enough privileges to access "
345         "Device Configuration Space.\nTry to run utility as root.\n");
346
347   results = xmalloc(ports_n * sizeof(*results));
348   results_n = xmalloc(ports_n * sizeof(*results_n));
349   links = xmalloc(ports_n * sizeof(*links));
350   checks_status_ports = xmalloc(ports_n * sizeof(*checks_status_ports));
351   args = xmalloc(ports_n * sizeof(*args));
352
353   for (int i = 0; i < ports_n; i++)
354     {
355       args[i].error_limit = error_limit;
356       args[i].parallel_lanes = parallel_lanes_arg;
357       args[i].run_margin = run_margin;
358       args[i].verbosity = 1;
359       args[i].steps_t = steps_t_arg;
360       args[i].steps_v = steps_v_arg;
361       for (int j = 0; j < recvs_n; j++)
362         args[i].recvs[j] = recvs_arg[j];
363       args[i].recvs_n = recvs_n;
364       for (int j = 0; j < lanes_n; j++)
365         args[i].lanes[j] = lanes_arg[j];
366       args[i].lanes_n = lanes_n;
367       args[i].steps_utility = &total_steps;
368
369       enum margin_test_status args_status;
370
371       if (!margin_fill_link(down_ports[i], up_ports[i], &links[i]))
372         {
373           checks_status_ports[i] = false;
374           results[i] = xmalloc(sizeof(*results[i]));
375           results[i]->test_status = MARGIN_TEST_PREREQS;
376           continue;
377         }
378
379       if ((args_status = margin_process_args(&links[i].down_port, &args[i])) != MARGIN_TEST_OK)
380         {
381           checks_status_ports[i] = false;
382           results[i] = xmalloc(sizeof(*results[i]));
383           results[i]->test_status = args_status;
384           continue;
385         }
386
387       checks_status_ports[i] = true;
388       struct margin_params params;
389
390       for (int j = 0; j < args[i].recvs_n; j++)
391         {
392           if (margin_read_params(pacc, args[i].recvs[j] == 6 ? up_ports[i] : down_ports[i],
393                                  args[i].recvs[j], &params))
394             {
395               u8 steps_t = steps_t_arg ? steps_t_arg : params.timing_steps;
396               u8 steps_v = steps_v_arg ? steps_v_arg : params.volt_steps;
397               u8 parallel_recv = parallel_lanes_arg > params.max_lanes + 1 ? params.max_lanes + 1 :
398                                                                              parallel_lanes_arg;
399
400               u8 step_multiplier
401                 = args[i].lanes_n / parallel_recv + ((args[i].lanes_n % parallel_recv) > 0);
402
403               total_steps += steps_t * step_multiplier;
404               if (params.ind_left_right_tim)
405                 total_steps += steps_t * step_multiplier;
406               if (params.volt_support)
407                 {
408                   total_steps += steps_v * step_multiplier;
409                   if (params.ind_up_down_volt)
410                     total_steps += steps_v * step_multiplier;
411                 }
412             }
413         }
414     }
415
416   for (int i = 0; i < ports_n; i++)
417     {
418       if (checks_status_ports[i])
419         results[i] = margin_test_link(&links[i], &args[i], &results_n[i]);
420       else
421         {
422           results_n[i] = 1;
423           if (results[i]->test_status == MARGIN_TEST_PREREQS)
424             {
425               printf("Link ");
426               margin_log_bdfs(down_ports[i], up_ports[i]);
427               printf(" is not ready for margining.\n"
428                      "Link data rate must be 16 GT/s or 32 GT/s.\n"
429                      "Downstream Component must be at D0 PM state.\n");
430             }
431           else if (results[i]->test_status == MARGIN_TEST_ARGS_RECVS)
432             {
433               margin_log_link(&links[i]);
434               printf("\nInvalid RecNums specified.\n");
435             }
436           else if (results[i]->test_status == MARGIN_TEST_ARGS_LANES)
437             {
438               margin_log_link(&links[i]);
439               printf("\nInvalid lanes specified.\n");
440             }
441         }
442       printf("\n----\n\n");
443     }
444
445   if (run_margin)
446     {
447       printf("Results:\n");
448       printf("\nPass/fail criteria:\nTiming:\n");
449       printf("Minimum Offset (spec): %d %% UI\nRecommended Offset: %d %% UI\n", MARGIN_TIM_MIN,
450              MARGIN_TIM_RECOMMEND);
451       printf("\nVoltage:\nMinimum Offset (spec): %d mV\n\n", MARGIN_VOLT_MIN);
452       printf(
453         "Margining statuses:\nLIM -\tErrorCount exceeded Error Count Limit (found device limit)\n");
454       printf("NAK -\tDevice didn't execute last command, \n\tso result may be less reliable\n");
455       printf("THR -\tThe set (using the utility options) \n\tstep threshold has been reached\n\n");
456       printf("Notations:\nst - steps\n\n");
457
458       for (int i = 0; i < ports_n; i++)
459         {
460           printf("Link ");
461           margin_log_bdfs(down_ports[i], up_ports[i]);
462           printf(":\n\n");
463           margin_results_print_brief(results[i], results_n[i]);
464           if (save_csv)
465             margin_results_save_csv(results[i], results_n[i], dir_for_csv, up_ports[i]);
466           printf("\n");
467         }
468     }
469
470   for (int i = 0; i < ports_n; i++)
471     margin_free_results(results[i], results_n[i]);
472   free(results_n);
473   free(results);
474   free(up_ports);
475   free(down_ports);
476   free(links);
477   free(checks_status_ports);
478   free(args);
479
480   pci_cleanup(pacc);
481   return 0;
482 }