]> mj.ucw.cz Git - pciutils.git/blob - lmr/margin_args.c
pcilmr: Add new grading option
[pciutils.git] / lmr / margin_args.c
1 /*
2  *      The PCI Utilities -- Parse pcilmr utility arguments
3  *
4  *      Copyright (c) 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 <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include "lmr.h"
16
17 const char *usage
18   = "! Utility requires preliminary preparation of the system. Refer to the pcilmr man page !\n\n"
19     "Usage:\n"
20     "pcilmr [--margin] [<margining options>] <downstream component> ...\n"
21     "pcilmr --full [<margining options>]\n"
22     "pcilmr --scan\n\n"
23     "Device Specifier:\n"
24     "<device/component>:\t[<domain>:]<bus>:<dev>.<func>\n\n"
25     "Modes:\n"
26     "--margin\t\tMargin selected Links\n"
27     "--full\t\t\tMargin all ready for testing Links in the system (one by one)\n"
28     "--scan\t\t\tScan for Links available for margining\n\n"
29     "Margining options:\n\n"
30     "Margining Test settings:\n"
31     "-c\t\t\tPrint Device Lane Margining Capabilities only. Do not run margining.\n"
32     "-l <lane>[,<lane>...]\tSpecify lanes for margining. Default: all link lanes.\n"
33     "\t\t\tRemember that Device may use Lane Reversal for Lane numbering.\n"
34     "\t\t\tHowever, utility uses logical lane numbers in arguments and for logging.\n"
35     "\t\t\tUtility will automatically determine Lane Reversal and tune its calls.\n"
36     "-e <errors>\t\tSpecify Error Count Limit for margining. Default: 4.\n"
37     "-r <recvn>[,<recvn>...]\tSpecify Receivers to select margining targets.\n"
38     "\t\t\tDefault: all available Receivers (including Retimers).\n"
39     "-p <parallel_lanes>\tSpecify number of lanes to margin simultaneously.\n"
40     "\t\t\tDefault: 1.\n"
41     "\t\t\tAccording to spec it's possible for Receiver to margin up\n"
42     "\t\t\tto MaxLanes + 1 lanes simultaneously, but usually this works\n"
43     "\t\t\tbad, so this option is for experiments mostly.\n"
44     "-T\t\t\tTime Margining will continue until the Error Count is no more\n"
45     "\t\t\tthan an Error Count Limit. Use this option to find Link limit.\n"
46     "-V\t\t\tSame as -T option, but for Voltage.\n"
47     "-t <steps>\t\tSpecify maximum number of steps for Time Margining.\n"
48     "-v <steps>\t\tSpecify maximum number of steps for Voltage Margining.\n"
49     "Use only one of -T/-t options at the same time (same for -V/-v).\n"
50     "Without these options utility will use MaxSteps from Device\n"
51     "capabilities as test limit.\n\n"
52     "Margining Log settings:\n"
53     "-o <directory>\t\tSave margining results in csv form into the\n"
54     "\t\t\tspecified directory. Utility will generate file with the\n"
55     "\t\t\tname in form of 'lmr_<downstream component>_Rx#_<timestamp>.csv'\n"
56     "\t\t\tfor each successfully tested receiver.\n";
57
58 static struct pci_dev *
59 dev_for_filter(struct pci_access *pacc, char *filter)
60 {
61   struct pci_filter pci_filter;
62   pci_filter_init(pacc, &pci_filter);
63   if (pci_filter_parse_slot(&pci_filter, filter))
64     die("Invalid device ID: %s\n", filter);
65
66   if (pci_filter.bus == -1 || pci_filter.slot == -1 || pci_filter.func == -1)
67     die("Invalid device ID: %s\n", filter);
68
69   if (pci_filter.domain == -1)
70     pci_filter.domain = 0;
71
72   for (struct pci_dev *p = pacc->devices; p; p = p->next)
73     {
74       if (pci_filter_match(&pci_filter, p))
75         return p;
76     }
77
78   die("No such PCI device: %s or you don't have enough privileges.\n", filter);
79 }
80
81 static u8
82 parse_csv_arg(char *arg, u8 *vals)
83 {
84   u8 cnt = 0;
85   char *token = strtok(arg, ",");
86   while (token)
87     {
88       vals[cnt] = atoi(token);
89       cnt++;
90       token = strtok(NULL, ",");
91     }
92   return cnt;
93 }
94
95 static u8
96 find_ready_links(struct pci_access *pacc, struct margin_link *links, bool cnt_only)
97 {
98   u8 cnt = 0;
99   for (struct pci_dev *p = pacc->devices; p; p = p->next)
100     {
101       if (pci_find_cap(p, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED) && margin_port_is_down(p))
102         {
103           struct pci_dev *down = NULL;
104           struct pci_dev *up = NULL;
105           margin_find_pair(pacc, p, &down, &up);
106
107           if (down && margin_verify_link(down, up)
108               && (margin_check_ready_bit(down) || margin_check_ready_bit(up)))
109             {
110               if (!cnt_only)
111                 margin_fill_link(down, up, &(links[cnt]));
112               cnt++;
113             }
114         }
115     }
116   return cnt;
117 }
118
119 static void
120 init_link_args(struct margin_link_args *link_args, struct margin_com_args *com_args)
121 {
122   memset(link_args, 0, sizeof(*link_args));
123   link_args->common = com_args;
124   link_args->parallel_lanes = 1;
125 }
126
127 static void
128 parse_dev_args(int argc, char **argv, struct margin_link_args *args, u8 link_speed)
129 {
130   if (argc == optind)
131     return;
132   int c;
133   while ((c = getopt(argc, argv, "+r:l:p:t:v:VTg:")) != -1)
134     {
135       switch (c)
136         {
137           case 't':
138             args->steps_t = atoi(optarg);
139             break;
140           case 'T':
141             args->steps_t = 63;
142             break;
143           case 'v':
144             args->steps_v = atoi(optarg);
145             break;
146           case 'V':
147             args->steps_v = 127;
148             break;
149           case 'p':
150             args->parallel_lanes = atoi(optarg);
151             break;
152           case 'l':
153             args->lanes_n = parse_csv_arg(optarg, args->lanes);
154             break;
155           case 'r':
156             args->recvs_n = parse_csv_arg(optarg, args->recvs);
157             break;
158             case 'g': {
159               char recv[2] = { 0 };
160               char dir[2] = { 0 };
161               char unit[4] = { 0 };
162               float criteria = 0.0;
163               char eye[2] = { 0 };
164               int cons[3] = { 0 };
165
166               int ret = sscanf(optarg, "%1[1-6]%1[tv]=%f%n%3[%,ps]%n%1[f]%n", recv, dir, &criteria,
167                                &cons[0], unit, &cons[1], eye, &cons[2]);
168               if (ret < 3)
169                 {
170                   ret = sscanf(optarg, "%1[1-6]%1[tv]=%1[f]%n,%f%n%2[ps%]%n", recv, dir, eye,
171                                &cons[0], &criteria, &cons[1], unit, &cons[2]);
172                   if (ret < 3)
173                     die("Invalid arguments\n\n%s", usage);
174                 }
175
176               int consumed = 0;
177               for (int i = 0; i < 3; i++)
178                 if (cons[i] > consumed)
179                   consumed = cons[i];
180               if ((size_t)consumed != strlen(optarg))
181                 die("Invalid arguments\n\n%s", usage);
182               if (criteria < 0)
183                 die("Invalid arguments\n\n%s", usage);
184               if (strstr(unit, ",") && eye[0] == 0)
185                 die("Invalid arguments\n\n%s", usage);
186
187               u8 recv_n = recv[0] - '0' - 1;
188               if (dir[0] == 'v')
189                 {
190                   if (unit[0] != ',' && unit[0] != 0)
191                     die("Invalid arguments\n\n%s", usage);
192                   args->recv_args[recv_n].v.valid = true;
193                   args->recv_args[recv_n].v.criteria = criteria;
194                   if (eye[0] != 0)
195                     args->recv_args[recv_n].v.one_side_is_whole = true;
196                 }
197               else
198                 {
199                   if (unit[0] == '%')
200                     criteria = criteria / 100.0 * margin_ui[link_speed];
201                   else if (unit[0] != 0 && (unit[0] != 'p' || unit[1] != 's'))
202                     die("Invalid arguments\n\n%s", usage);
203                   else if (unit[0] == 0 && criteria != 0)
204                     die("Invalid arguments\n\n%s", usage);
205                   args->recv_args[recv_n].t.valid = true;
206                   args->recv_args[recv_n].t.criteria = criteria;
207                   if (eye[0] != 0)
208                     args->recv_args[recv_n].t.one_side_is_whole = true;
209                 }
210               break;
211             }
212           case '?':
213             die("Invalid arguments\n\n%s", usage);
214             break;
215           default:
216             return;
217         }
218     }
219 }
220
221 struct margin_link *
222 margin_parse_util_args(struct pci_access *pacc, int argc, char **argv, enum margin_mode mode,
223                        u8 *links_n)
224 {
225   struct margin_com_args *com_args = xmalloc(sizeof(*com_args));
226   com_args->error_limit = 4;
227   com_args->run_margin = true;
228   com_args->verbosity = 1;
229   com_args->steps_utility = 0;
230   com_args->dir_for_csv = NULL;
231   com_args->save_csv = false;
232
233   int c;
234   while ((c = getopt(argc, argv, "+e:co:")) != -1)
235     {
236       switch (c)
237         {
238           case 'c':
239             com_args->run_margin = false;
240             break;
241           case 'e':
242             com_args->error_limit = atoi(optarg);
243             break;
244           case 'o':
245             com_args->dir_for_csv = optarg;
246             com_args->save_csv = true;
247             break;
248           default:
249             die("Invalid arguments\n\n%s", usage);
250         }
251     }
252
253   bool status = true;
254   if (mode == FULL && optind != argc)
255     status = false;
256   if (mode == MARGIN && optind == argc)
257     status = false;
258   if (!status && argc > 1)
259     die("Invalid arguments\n\n%s", usage);
260   if (!status)
261     {
262       printf("%s", usage);
263       exit(0);
264     }
265
266   u8 ports_n = 0;
267   struct margin_link *links = NULL;
268   char err[128];
269
270   if (mode == FULL)
271     {
272       ports_n = find_ready_links(pacc, NULL, true);
273       if (ports_n == 0)
274         die("Links not found or you don't have enough privileges.\n");
275       else
276         {
277           links = xmalloc(ports_n * sizeof(*links));
278           find_ready_links(pacc, links, false);
279           for (int i = 0; i < ports_n; i++)
280             init_link_args(&(links[i].args), com_args);
281         }
282     }
283   else if (mode == MARGIN)
284     {
285       while (optind != argc)
286         {
287           struct pci_dev *dev = dev_for_filter(pacc, argv[optind]);
288           optind++;
289           links = xrealloc(links, (ports_n + 1) * sizeof(*links));
290           struct pci_dev *down;
291           struct pci_dev *up;
292           if (!margin_find_pair(pacc, dev, &down, &up))
293             die("Cannot find pair for the specified device: %s\n", argv[optind - 1]);
294           struct pci_cap *cap = pci_find_cap(down, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
295           if (!cap)
296             die("Looks like you don't have enough privileges to access "
297                 "Device Configuration Space.\nTry to run utility as root.\n");
298           if (!margin_fill_link(down, up, &(links[ports_n])))
299             {
300               margin_gen_bdfs(down, up, err, sizeof(err));
301               die("Link %s is not ready for margining.\n"
302                   "Link data rate must be 16 GT/s or 32 GT/s.\n"
303                   "Downstream Component must be at D0 PM state.\n",
304                   err);
305             }
306           init_link_args(&(links[ports_n].args), com_args);
307           parse_dev_args(argc, argv, &(links[ports_n].args),
308                          links[ports_n].down_port.link_speed - 4);
309           ports_n++;
310         }
311     }
312   else
313     die("Bug in the args parsing!\n");
314
315   *links_n = ports_n;
316   return links;
317 }