]> git.ipfire.org Git - thirdparty/pciutils.git/blob - pcilmr.c
pci.h: Document PCI_FILL_xxx flags
[thirdparty/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 }