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