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