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 "Brief usage (see man for all options):\n"
20 "pcilmr [--margin] [<common options>] <link port> [<link options>] [<link port> [<link "
21 "options>] ...]\n"
22 "pcilmr --full [<common options>]\n"
23 "pcilmr --scan\n\n"
24 "You can specify Downstream or Upstream Port of the Link.\nPort Specifier:\n"
25 "<device/component>:\t[<domain>:]<bus>:<dev>.<func>\n\n"
26 "Modes:\n"
27 "--margin\t\tMargin selected Links\n"
28 "--full\t\t\tMargin all ready for testing Links in the system (one by one)\n"
29 "--scan\t\t\tScan for Links available for margining\n\n"
30 "Margining options (see man for all options):\n\n"
31 "Common (for all specified links) options:\n"
32 "-c\t\t\tPrint Device Lane Margining Capabilities only. Do not run margining.\n\n"
33 "Link specific options:\n"
34 "-r <recvn>[,<recvn>...]\tSpecify Receivers to select margining targets.\n"
35 "\t\t\tDefault: all available Receivers (including Retimers).\n"
36 "-t <steps>\t\tSpecify maximum number of steps for Time Margining.\n"
37 "-v <steps>\t\tSpecify maximum number of steps for Voltage Margining.\n";
38
39 static struct pci_dev *
dev_for_filter(struct pci_access * pacc,char * filter)40 dev_for_filter(struct pci_access *pacc, char *filter)
41 {
42 struct pci_filter pci_filter;
43 pci_filter_init(pacc, &pci_filter);
44 if (pci_filter_parse_slot(&pci_filter, filter))
45 die("Invalid device ID: %s\n", filter);
46
47 if (pci_filter.bus == -1 || pci_filter.slot == -1 || pci_filter.func == -1)
48 die("Invalid device ID: %s\n", filter);
49
50 if (pci_filter.domain == -1)
51 pci_filter.domain = 0;
52
53 for (struct pci_dev *p = pacc->devices; p; p = p->next)
54 {
55 if (pci_filter_match(&pci_filter, p))
56 return p;
57 }
58
59 die("No such PCI device: %s or you don't have enough privileges.\n", filter);
60 }
61
62 static u8
parse_csv_arg(char * arg,u8 * vals)63 parse_csv_arg(char *arg, u8 *vals)
64 {
65 u8 cnt = 0;
66 char *token = strtok(arg, ",");
67 while (token)
68 {
69 vals[cnt] = atoi(token);
70 cnt++;
71 token = strtok(NULL, ",");
72 }
73 return cnt;
74 }
75
76 static u8
find_ready_links(struct pci_access * pacc,struct margin_link * links,bool cnt_only)77 find_ready_links(struct pci_access *pacc, struct margin_link *links, bool cnt_only)
78 {
79 u8 cnt = 0;
80 for (struct pci_dev *p = pacc->devices; p; p = p->next)
81 {
82 if (pci_find_cap(p, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED) && margin_port_is_down(p))
83 {
84 struct pci_dev *down = NULL;
85 struct pci_dev *up = NULL;
86 margin_find_pair(pacc, p, &down, &up);
87
88 if (down && margin_verify_link(down, up)
89 && (margin_check_ready_bit(down) || margin_check_ready_bit(up)))
90 {
91 if (!cnt_only)
92 margin_fill_link(down, up, &(links[cnt]));
93 cnt++;
94 }
95 }
96 }
97 return cnt;
98 }
99
100 static void
init_link_args(struct margin_link_args * link_args,struct margin_com_args * com_args)101 init_link_args(struct margin_link_args *link_args, struct margin_com_args *com_args)
102 {
103 memset(link_args, 0, sizeof(*link_args));
104 link_args->common = com_args;
105 link_args->parallel_lanes = 1;
106 }
107
108 static void
parse_dev_args(int argc,char ** argv,struct margin_link_args * args,u8 link_speed)109 parse_dev_args(int argc, char **argv, struct margin_link_args *args, u8 link_speed)
110 {
111 if (argc == optind)
112 return;
113 int c;
114 while ((c = getopt(argc, argv, "+r:l:p:t:v:VTg:")) != -1)
115 {
116 switch (c)
117 {
118 case 't':
119 args->steps_t = atoi(optarg);
120 break;
121 case 'T':
122 args->steps_t = 63;
123 break;
124 case 'v':
125 args->steps_v = atoi(optarg);
126 break;
127 case 'V':
128 args->steps_v = 127;
129 break;
130 case 'p':
131 args->parallel_lanes = atoi(optarg);
132 break;
133 case 'l':
134 args->lanes_n = parse_csv_arg(optarg, args->lanes);
135 break;
136 case 'r':
137 args->recvs_n = parse_csv_arg(optarg, args->recvs);
138 break;
139 case 'g': {
140 char recv[2] = { 0 };
141 char dir[2] = { 0 };
142 char unit[4] = { 0 };
143 float criteria = 0.0;
144 char eye[2] = { 0 };
145 int cons[3] = { 0 };
146
147 int ret = sscanf(optarg, "%1[1-6]%1[tv]=%f%n%3[%,ps]%n%1[f]%n", recv, dir, &criteria,
148 &cons[0], unit, &cons[1], eye, &cons[2]);
149 if (ret < 3)
150 {
151 ret = sscanf(optarg, "%1[1-6]%1[tv]=%1[f]%n,%f%n%2[ps%]%n", recv, dir, eye,
152 &cons[0], &criteria, &cons[1], unit, &cons[2]);
153 if (ret < 3)
154 die("Invalid arguments\n\n%s", usage);
155 }
156
157 int consumed = 0;
158 for (int i = 0; i < 3; i++)
159 if (cons[i] > consumed)
160 consumed = cons[i];
161 if ((size_t)consumed != strlen(optarg))
162 die("Invalid arguments\n\n%s", usage);
163 if (criteria < 0)
164 die("Invalid arguments\n\n%s", usage);
165 if (strstr(unit, ",") && eye[0] == 0)
166 die("Invalid arguments\n\n%s", usage);
167
168 u8 recv_n = recv[0] - '0' - 1;
169 if (dir[0] == 'v')
170 {
171 if (unit[0] != ',' && unit[0] != 0)
172 die("Invalid arguments\n\n%s", usage);
173 args->recv_args[recv_n].v.valid = true;
174 args->recv_args[recv_n].v.criteria = criteria;
175 if (eye[0] != 0)
176 args->recv_args[recv_n].v.one_side_is_whole = true;
177 }
178 else
179 {
180 if (unit[0] == '%')
181 criteria = criteria / 100.0 * margin_ui[link_speed];
182 else if (unit[0] != 0 && (unit[0] != 'p' || unit[1] != 's'))
183 die("Invalid arguments\n\n%s", usage);
184 else if (unit[0] == 0 && criteria != 0)
185 die("Invalid arguments\n\n%s", usage);
186 args->recv_args[recv_n].t.valid = true;
187 args->recv_args[recv_n].t.criteria = criteria;
188 if (eye[0] != 0)
189 args->recv_args[recv_n].t.one_side_is_whole = true;
190 }
191 break;
192 }
193 case '?':
194 die("Invalid arguments\n\n%s", usage);
195 break;
196 default:
197 return;
198 }
199 }
200 }
201
202 struct margin_link *
margin_parse_util_args(struct pci_access * pacc,int argc,char ** argv,enum margin_mode mode,u8 * links_n)203 margin_parse_util_args(struct pci_access *pacc, int argc, char **argv, enum margin_mode mode,
204 u8 *links_n)
205 {
206 struct margin_com_args *com_args = xmalloc(sizeof(*com_args));
207 com_args->error_limit = 4;
208 com_args->run_margin = true;
209 com_args->verbosity = 1;
210 com_args->steps_utility = 0;
211 com_args->dir_for_csv = NULL;
212 com_args->save_csv = false;
213 com_args->dwell_time = 1;
214
215 int c;
216 while ((c = getopt(argc, argv, "+e:co:d:")) != -1)
217 {
218 switch (c)
219 {
220 case 'c':
221 com_args->run_margin = false;
222 break;
223 case 'e':
224 com_args->error_limit = atoi(optarg);
225 break;
226 case 'o':
227 com_args->dir_for_csv = optarg;
228 com_args->save_csv = true;
229 break;
230 case 'd':
231 com_args->dwell_time = atoi(optarg);
232 break;
233 default:
234 die("Invalid arguments\n\n%s", usage);
235 }
236 }
237
238 bool status = true;
239 if (mode == FULL && optind != argc)
240 status = false;
241 if (mode == MARGIN && optind == argc)
242 status = false;
243 if (!status && argc > 1)
244 die("Invalid arguments\n\n%s", usage);
245 if (!status)
246 {
247 printf("%s", usage);
248 exit(0);
249 }
250
251 u8 ports_n = 0;
252 struct margin_link *links = NULL;
253 char err[128];
254
255 if (mode == FULL)
256 {
257 ports_n = find_ready_links(pacc, NULL, true);
258 if (ports_n == 0)
259 die("Links not found or you don't have enough privileges.\n");
260 else
261 {
262 links = xmalloc(ports_n * sizeof(*links));
263 find_ready_links(pacc, links, false);
264 for (int i = 0; i < ports_n; i++)
265 init_link_args(&(links[i].args), com_args);
266 }
267 }
268 else if (mode == MARGIN)
269 {
270 while (optind != argc)
271 {
272 struct pci_dev *dev = dev_for_filter(pacc, argv[optind]);
273 optind++;
274 links = xrealloc(links, (ports_n + 1) * sizeof(*links));
275 struct pci_dev *down;
276 struct pci_dev *up;
277 if (!margin_find_pair(pacc, dev, &down, &up))
278 die("Cannot find pair for the specified device: %s\n", argv[optind - 1]);
279 struct pci_cap *cap = pci_find_cap(down, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
280 if (!cap)
281 die("Looks like you don't have enough privileges to access "
282 "Device Configuration Space.\nTry to run utility as root.\n");
283 if (!margin_fill_link(down, up, &(links[ports_n])))
284 {
285 margin_gen_bdfs(down, up, err, sizeof(err));
286 die("Link %s is not ready for margining.\n"
287 "Link data rate must be 16 GT/s or 32 GT/s.\n"
288 "Downstream Component must be at D0 PM state.\n",
289 err);
290 }
291 init_link_args(&(links[ports_n].args), com_args);
292 parse_dev_args(argc, argv, &(links[ports_n].args),
293 links[ports_n].down_port.link_speed - 4);
294 ports_n++;
295 }
296 }
297 else
298 die("Bug in the args parsing!\n");
299
300 *links_n = ports_n;
301 return links;
302 }
303