1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2016 Joyent, Inc.
14 */
15
16 /*
17 * This is a private utility that combines a number of minor debugging routines
18 * for xhci.
19 */
20
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <err.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <libdevinfo.h>
31 #include <sys/usb/hcd/xhci/xhci_ioctl.h>
32 #include <sys/usb/hcd/xhci/xhcireg.h>
33
34 static char *xp_devpath = NULL;
35 static int xp_npaths;
36 static const char *xp_path;
37 static const char *xp_state = NULL;
38 static uint32_t xp_port;
39 static boolean_t xp_verbose = B_FALSE;
40 static boolean_t xp_clear = B_FALSE;
41 static boolean_t xp_list = B_FALSE;
42 extern const char *__progname;
43
44 static int
xp_usage(const char * format,...)45 xp_usage(const char *format, ...)
46 {
47 if (format != NULL) {
48 va_list alist;
49
50 va_start(alist, format);
51 vwarnx(format, alist);
52 va_end(alist);
53 }
54
55 (void) fprintf(stderr, "usage: %s [-l] [-v] [-c] [-d path] [-p port] "
56 "[-s state]\n", __progname);
57 return (2);
58 }
59
60 static const char *xp_pls_strings[] = {
61 "U0",
62 "U1",
63 "U2",
64 "U3 (suspended)",
65 "Disabled",
66 "RxDetect",
67 "Inactive",
68 "Polling",
69 "Recovery",
70 "Hot Reset",
71 "Compliance Mode",
72 "Test Mode",
73 "Reserved",
74 "Reserved",
75 "Reserved",
76 "Resume",
77 NULL
78 };
79
80 static void
xp_dump_verbose(uint32_t portsc)81 xp_dump_verbose(uint32_t portsc)
82 {
83 if (portsc & XHCI_PS_CCS)
84 (void) printf("\t\t\tCCS\n");
85 if (portsc & XHCI_PS_PED)
86 (void) printf("\t\t\tPED\n");
87 if (portsc & XHCI_PS_OCA)
88 (void) printf("\t\t\tOCA\n");
89 if (portsc & XHCI_PS_PR)
90 (void) printf("\t\t\tPR\n");
91 if (portsc & XHCI_PS_PP) {
92 (void) printf("\t\t\tPLS: %s (%d)\n",
93 xp_pls_strings[XHCI_PS_PLS_GET(portsc)],
94 XHCI_PS_PLS_GET(portsc));
95 (void) printf("\t\t\tPP\n");
96 } else {
97 (void) printf("\t\t\tPLS: undefined (No PP)\n");
98 }
99
100 if (XHCI_PS_SPEED_GET(portsc) != 0) {
101 (void) printf("\t\t\tPort Speed: ");
102 switch (XHCI_PS_SPEED_GET(portsc)) {
103 case 0:
104 (void) printf("Undefined ");
105 break;
106 case XHCI_SPEED_FULL:
107 (void) printf("Full ");
108 break;
109 case XHCI_SPEED_LOW:
110 (void) printf("Low ");
111 break;
112 case XHCI_SPEED_HIGH:
113 (void) printf("High ");
114 break;
115 case XHCI_SPEED_SUPER:
116 (void) printf("Super ");
117 break;
118 default:
119 (void) printf("Unknown ");
120 break;
121 }
122 (void) printf("(%d)\n", XHCI_PS_SPEED_GET(portsc));
123 }
124 if (XHCI_PS_PIC_GET(portsc) != 0)
125 (void) printf("\t\t\tPIC: %d\n", XHCI_PS_PIC_GET(portsc));
126
127 if (portsc & XHCI_PS_LWS)
128 (void) printf("\t\t\tLWS\n");
129 if (portsc & XHCI_PS_CSC)
130 (void) printf("\t\t\tCSC\n");
131 if (portsc & XHCI_PS_PEC)
132 (void) printf("\t\t\tPEC\n");
133 if (portsc & XHCI_PS_WRC)
134 (void) printf("\t\t\tWRC\n");
135 if (portsc & XHCI_PS_OCC)
136 (void) printf("\t\t\tOCC\n");
137 if (portsc & XHCI_PS_PRC)
138 (void) printf("\t\t\tPRC\n");
139 if (portsc & XHCI_PS_PLC)
140 (void) printf("\t\t\tPLC\n");
141 if (portsc & XHCI_PS_CEC)
142 (void) printf("\t\t\tCEC\n");
143 if (portsc & XHCI_PS_CAS)
144 (void) printf("\t\t\tCAS\n");
145 if (portsc & XHCI_PS_WCE)
146 (void) printf("\t\t\tWCE\n");
147 if (portsc & XHCI_PS_WDE)
148 (void) printf("\t\t\tWDE\n");
149 if (portsc & XHCI_PS_WOE)
150 (void) printf("\t\t\tWOE\n");
151 if (portsc & XHCI_PS_DR)
152 (void) printf("\t\t\tDR\n");
153 if (portsc & XHCI_PS_WPR)
154 (void) printf("\t\t\tWPR\n");
155 }
156
157 static void
xp_dump(const char * path)158 xp_dump(const char *path)
159 {
160 int fd, i;
161 xhci_ioctl_portsc_t xhi = { 0 };
162
163 fd = open(path, O_RDWR);
164 if (fd < 0) {
165 err(EXIT_FAILURE, "failed to open %s", path);
166 }
167
168 if (ioctl(fd, XHCI_IOCTL_PORTSC, &xhi) != 0)
169 err(EXIT_FAILURE, "failed to get port status");
170
171 (void) close(fd);
172
173 for (i = 1; i <= xhi.xhi_nports; i++) {
174 if (xp_port != 0 && i != xp_port)
175 continue;
176
177 (void) printf("port %2d:\t0x%08x\n", i, xhi.xhi_portsc[i]);
178 if (xp_verbose == B_TRUE)
179 xp_dump_verbose(xhi.xhi_portsc[i]);
180 }
181 }
182
183 static void
xp_set_pls(const char * path,uint32_t port,const char * state)184 xp_set_pls(const char *path, uint32_t port, const char *state)
185 {
186 int fd, i;
187 xhci_ioctl_setpls_t xis;
188
189 fd = open(path, O_RDWR);
190 if (fd < 0) {
191 err(EXIT_FAILURE, "failed to open %s", path);
192 }
193
194 xis.xis_port = port;
195 for (i = 0; xp_pls_strings[i] != NULL; i++) {
196 if (strcasecmp(state, xp_pls_strings[i]) == 0)
197 break;
198 }
199
200 if (xp_pls_strings[i] == NULL) {
201 errx(EXIT_FAILURE, "unknown state string: %s\n", state);
202 }
203
204 xis.xis_pls = i;
205 (void) printf("setting port %d with pls %d\n", port, xis.xis_pls);
206
207 if (ioctl(fd, XHCI_IOCTL_SETPLS, &xis) != 0)
208 err(EXIT_FAILURE, "failed to set port status");
209
210 (void) close(fd);
211 }
212
213 static void
xp_clear_change(const char * path,uint32_t port)214 xp_clear_change(const char *path, uint32_t port)
215 {
216 int fd;
217 xhci_ioctl_clear_t xic;
218
219 fd = open(path, O_RDWR);
220 if (fd < 0) {
221 err(EXIT_FAILURE, "failed to open %s", path);
222 }
223
224 xic.xic_port = port;
225 (void) printf("clearing change bits on port %d\n", port);
226 if (ioctl(fd, XHCI_IOCTL_CLEAR, &xic) != 0)
227 err(EXIT_FAILURE, "failed to set port status");
228
229 (void) close(fd);
230 }
231
232 /* ARGSUSED */
233 static int
xp_devinfo_cb(di_node_t node,void * arg)234 xp_devinfo_cb(di_node_t node, void *arg)
235 {
236 char *drv;
237 di_minor_t minor;
238 boolean_t *do_print = arg;
239
240 drv = di_driver_name(node);
241 if (drv == NULL)
242 return (DI_WALK_CONTINUE);
243 if (strcmp(drv, "xhci") != 0)
244 return (DI_WALK_CONTINUE);
245
246 /*
247 * We have an instance of the xhci driver. We need to find the minor
248 * node for the hubd instance. These are all usually greater than
249 * HUBD_IS_ROOT_HUB. However, to avoid hardcoding that here, we instead
250 * rely on the fact that the minor node for the actual device has a
251 * :hubd as the intance.
252 */
253 minor = DI_MINOR_NIL;
254 while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
255 char *mname, *path;
256
257 mname = di_minor_name(minor);
258 if (mname == NULL)
259 continue;
260 if (strcmp(mname, "hubd") != 0)
261 continue;
262 path = di_devfs_minor_path(minor);
263 if (*do_print == B_TRUE) {
264 (void) printf("/devices%s\n", path);
265 di_devfs_path_free(path);
266 } else {
267 xp_npaths++;
268 if (xp_devpath == NULL)
269 xp_devpath = path;
270 else
271 di_devfs_path_free(path);
272 }
273 }
274
275 return (DI_WALK_PRUNECHILD);
276 }
277
278 /*
279 * We need to find all minor nodes of instances of the xhci driver whose name is
280 * 'hubd'.
281 */
282 static void
xp_find_devs(boolean_t print)283 xp_find_devs(boolean_t print)
284 {
285 di_node_t root;
286
287 if ((root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
288 err(EXIT_FAILURE, "failed to initialize devices tree");
289 }
290
291 if (di_walk_node(root, DI_WALK_CLDFIRST, &print, xp_devinfo_cb) != 0)
292 err(EXIT_FAILURE, "failed to walk devices tree");
293 }
294
295 int
main(int argc,char * argv[])296 main(int argc, char *argv[])
297 {
298 int c;
299 char devpath[PATH_MAX];
300
301 while ((c = getopt(argc, argv, ":d:vlcp:s:")) != -1) {
302 switch (c) {
303 case 'c':
304 xp_clear = B_TRUE;
305 break;
306 case 'd':
307 xp_path = optarg;
308 break;
309 case 'l':
310 xp_list = B_TRUE;
311 break;
312 case 'v':
313 xp_verbose = B_TRUE;
314 break;
315 case 'p':
316 xp_port = atoi(optarg);
317 if (xp_port < 1 || xp_port > XHCI_PORTSC_NPORTS)
318 return (xp_usage("invalid port for -p: %d\n",
319 optarg));
320 break;
321 case 's':
322 xp_state = optarg;
323 break;
324 case ':':
325 return (xp_usage("-%c requires an operand\n", optopt));
326 case '?':
327 return (xp_usage("unknown option: -%c\n", optopt));
328 default:
329 abort();
330 }
331 }
332
333 if (xp_list == B_TRUE && (xp_path != NULL || xp_clear == B_TRUE ||
334 xp_port > 0 || xp_state != NULL)) {
335 return (xp_usage("-l cannot be used with other options\n"));
336 }
337
338 if (xp_list == B_TRUE) {
339 xp_find_devs(B_TRUE);
340 return (0);
341 }
342
343 if (xp_path == NULL) {
344 xp_find_devs(B_FALSE);
345 if (xp_npaths == 0) {
346 errx(EXIT_FAILURE, "no xhci devices found");
347 } else if (xp_npaths > 1) {
348 errx(EXIT_FAILURE, "more than one xhci device found, "
349 "please specify device with -d, use -l to list");
350 }
351 if (snprintf(devpath, sizeof (devpath), "/devices/%s",
352 xp_devpath) >= sizeof (devpath))
353 errx(EXIT_FAILURE, "xhci path found at %s overflows "
354 "internal device path", xp_devpath);
355 di_devfs_path_free(xp_devpath);
356 xp_devpath = NULL;
357 xp_path = devpath;
358 }
359
360 if (xp_clear == B_TRUE && xp_state != NULL) {
361 return (xp_usage("-c and -s can't be used together\n"));
362 }
363
364 if (xp_state != NULL) {
365 xp_set_pls(xp_path, xp_port, xp_state);
366 } else if (xp_clear == B_TRUE) {
367 xp_clear_change(xp_path, xp_port);
368 } else {
369 xp_dump(xp_path);
370 }
371
372 return (0);
373 }
374