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