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