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