xref: /illumos-gate/usr/src/cmd/xhci/xhci_portsc.c (revision 5c0b3261bd16d5eb356ffc864b6eab76c2e760e5)
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
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
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
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
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
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
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
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
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");
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