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