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", 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