#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libdevinfo.h>
#include <sys/usb/hcd/xhci/xhci_ioctl.h>
#include <sys/usb/hcd/xhci/xhcireg.h>
static char *xp_devpath = NULL;
static int xp_npaths;
static const char *xp_path;
static const char *xp_state = NULL;
static uint32_t xp_port;
static boolean_t xp_verbose = B_FALSE;
static boolean_t xp_clear = B_FALSE;
static boolean_t xp_list = B_FALSE;
extern const char *__progname;
static int
xp_usage(const char *format, ...)
{
if (format != NULL) {
va_list alist;
va_start(alist, format);
vwarnx(format, alist);
va_end(alist);
}
(void) fprintf(stderr, "usage: %s [-l] [-v] [-c] [-d path] [-p port] "
"[-s state]\n", __progname);
return (2);
}
static const char *xp_pls_strings[] = {
"U0",
"U1",
"U2",
"U3 (suspended)",
"Disabled",
"RxDetect",
"Inactive",
"Polling",
"Recovery",
"Hot Reset",
"Compliance Mode",
"Test Mode",
"Reserved",
"Reserved",
"Reserved",
"Resume",
NULL
};
static void
xp_dump_verbose(uint32_t portsc)
{
if (portsc & XHCI_PS_CCS)
(void) printf("\t\t\tCCS\n");
if (portsc & XHCI_PS_PED)
(void) printf("\t\t\tPED\n");
if (portsc & XHCI_PS_OCA)
(void) printf("\t\t\tOCA\n");
if (portsc & XHCI_PS_PR)
(void) printf("\t\t\tPR\n");
if (portsc & XHCI_PS_PP) {
(void) printf("\t\t\tPLS: %s (%d)\n",
xp_pls_strings[XHCI_PS_PLS_GET(portsc)],
XHCI_PS_PLS_GET(portsc));
(void) printf("\t\t\tPP\n");
} else {
(void) printf("\t\t\tPLS: undefined (No PP)\n");
}
if (XHCI_PS_SPEED_GET(portsc) != 0) {
(void) printf("\t\t\tPort Speed: ");
switch (XHCI_PS_SPEED_GET(portsc)) {
case 0:
(void) printf("Undefined ");
break;
case XHCI_SPEED_FULL:
(void) printf("Full ");
break;
case XHCI_SPEED_LOW:
(void) printf("Low ");
break;
case XHCI_SPEED_HIGH:
(void) printf("High ");
break;
case XHCI_SPEED_SUPER:
(void) printf("Super ");
break;
default:
(void) printf("Unknown ");
break;
}
(void) printf("(%d)\n", XHCI_PS_SPEED_GET(portsc));
}
if (XHCI_PS_PIC_GET(portsc) != 0)
(void) printf("\t\t\tPIC: %d\n", XHCI_PS_PIC_GET(portsc));
if (portsc & XHCI_PS_LWS)
(void) printf("\t\t\tLWS\n");
if (portsc & XHCI_PS_CSC)
(void) printf("\t\t\tCSC\n");
if (portsc & XHCI_PS_PEC)
(void) printf("\t\t\tPEC\n");
if (portsc & XHCI_PS_WRC)
(void) printf("\t\t\tWRC\n");
if (portsc & XHCI_PS_OCC)
(void) printf("\t\t\tOCC\n");
if (portsc & XHCI_PS_PRC)
(void) printf("\t\t\tPRC\n");
if (portsc & XHCI_PS_PLC)
(void) printf("\t\t\tPLC\n");
if (portsc & XHCI_PS_CEC)
(void) printf("\t\t\tCEC\n");
if (portsc & XHCI_PS_CAS)
(void) printf("\t\t\tCAS\n");
if (portsc & XHCI_PS_WCE)
(void) printf("\t\t\tWCE\n");
if (portsc & XHCI_PS_WDE)
(void) printf("\t\t\tWDE\n");
if (portsc & XHCI_PS_WOE)
(void) printf("\t\t\tWOE\n");
if (portsc & XHCI_PS_DR)
(void) printf("\t\t\tDR\n");
if (portsc & XHCI_PS_WPR)
(void) printf("\t\t\tWPR\n");
}
static void
xp_dump(const char *path)
{
int fd, i;
xhci_ioctl_portsc_t xhi = { 0 };
fd = open(path, O_RDWR);
if (fd < 0) {
err(EXIT_FAILURE, "failed to open %s", path);
}
if (ioctl(fd, XHCI_IOCTL_PORTSC, &xhi) != 0)
err(EXIT_FAILURE, "failed to get port status");
(void) close(fd);
for (i = 1; i <= xhi.xhi_nports; i++) {
if (xp_port != 0 && i != xp_port)
continue;
(void) printf("port %2d:\t0x%08x\n", i, xhi.xhi_portsc[i]);
if (xp_verbose == B_TRUE)
xp_dump_verbose(xhi.xhi_portsc[i]);
}
}
static void
xp_set_pls(const char *path, uint32_t port, const char *state)
{
int fd, i;
xhci_ioctl_setpls_t xis;
fd = open(path, O_RDWR);
if (fd < 0) {
err(EXIT_FAILURE, "failed to open %s", path);
}
xis.xis_port = port;
for (i = 0; xp_pls_strings[i] != NULL; i++) {
if (strcasecmp(state, xp_pls_strings[i]) == 0)
break;
}
if (xp_pls_strings[i] == NULL) {
errx(EXIT_FAILURE, "unknown state string: %s\n", state);
}
xis.xis_pls = i;
(void) printf("setting port %d with pls %d\n", port, xis.xis_pls);
if (ioctl(fd, XHCI_IOCTL_SETPLS, &xis) != 0)
err(EXIT_FAILURE, "failed to set port status");
(void) close(fd);
}
static void
xp_clear_change(const char *path, uint32_t port)
{
int fd;
xhci_ioctl_clear_t xic;
fd = open(path, O_RDWR);
if (fd < 0) {
err(EXIT_FAILURE, "failed to open %s", path);
}
xic.xic_port = port;
(void) printf("clearing change bits on port %d\n", port);
if (ioctl(fd, XHCI_IOCTL_CLEAR, &xic) != 0)
err(EXIT_FAILURE, "failed to set port status");
(void) close(fd);
}
static int
xp_devinfo_cb(di_node_t node, void *arg)
{
char *drv;
di_minor_t minor;
boolean_t *do_print = arg;
drv = di_driver_name(node);
if (drv == NULL)
return (DI_WALK_CONTINUE);
if (strcmp(drv, "xhci") != 0)
return (DI_WALK_CONTINUE);
minor = DI_MINOR_NIL;
while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
char *mname, *path;
mname = di_minor_name(minor);
if (mname == NULL)
continue;
if (strcmp(mname, "hubd") != 0)
continue;
path = di_devfs_minor_path(minor);
if (*do_print == B_TRUE) {
(void) printf("/devices%s\n", path);
di_devfs_path_free(path);
} else {
xp_npaths++;
if (xp_devpath == NULL)
xp_devpath = path;
else
di_devfs_path_free(path);
}
}
return (DI_WALK_PRUNECHILD);
}
static void
xp_find_devs(boolean_t print)
{
di_node_t root;
if ((root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
err(EXIT_FAILURE, "failed to initialize devices tree");
}
if (di_walk_node(root, DI_WALK_CLDFIRST, &print, xp_devinfo_cb) != 0)
err(EXIT_FAILURE, "failed to walk devices tree");
}
int
main(int argc, char *argv[])
{
int c;
char devpath[PATH_MAX];
while ((c = getopt(argc, argv, ":d:vlcp:s:")) != -1) {
switch (c) {
case 'c':
xp_clear = B_TRUE;
break;
case 'd':
xp_path = optarg;
break;
case 'l':
xp_list = B_TRUE;
break;
case 'v':
xp_verbose = B_TRUE;
break;
case 'p':
xp_port = atoi(optarg);
if (xp_port < 1 || xp_port > XHCI_PORTSC_NPORTS)
return (xp_usage("invalid port for -p: %d\n",
optarg));
break;
case 's':
xp_state = optarg;
break;
case ':':
return (xp_usage("-%c requires an operand\n", optopt));
case '?':
return (xp_usage("unknown option: -%c\n", optopt));
default:
abort();
}
}
if (xp_list == B_TRUE && (xp_path != NULL || xp_clear == B_TRUE ||
xp_port > 0 || xp_state != NULL)) {
return (xp_usage("-l cannot be used with other options\n"));
}
if (xp_list == B_TRUE) {
xp_find_devs(B_TRUE);
return (0);
}
if (xp_path == NULL) {
xp_find_devs(B_FALSE);
if (xp_npaths == 0) {
errx(EXIT_FAILURE, "no xhci devices found");
} else if (xp_npaths > 1) {
errx(EXIT_FAILURE, "more than one xhci device found, "
"please specify device with -d, use -l to list");
}
if (snprintf(devpath, sizeof (devpath), "/devices/%s",
xp_devpath) >= sizeof (devpath))
errx(EXIT_FAILURE, "xhci path found at %s overflows "
"internal device path", xp_devpath);
di_devfs_path_free(xp_devpath);
xp_devpath = NULL;
xp_path = devpath;
}
if (xp_clear == B_TRUE && xp_state != NULL) {
return (xp_usage("-c and -s can't be used together\n"));
}
if (xp_state != NULL) {
xp_set_pls(xp_path, xp_port, xp_state);
} else if (xp_clear == B_TRUE) {
xp_clear_change(xp_path, xp_port);
} else {
xp_dump(xp_path);
}
return (0);
}