root/usr/src/cmd/xhci/xhci_portsc.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2016 Joyent, Inc.
 */

/*
 * This is a private utility that combines a number of minor debugging routines
 * for xhci.
 */

#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);
}

/* ARGSUSED */
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);

        /*
         * We have an instance of the xhci driver. We need to find the minor
         * node for the hubd instance. These are all usually greater than
         * HUBD_IS_ROOT_HUB. However, to avoid hardcoding that here, we instead
         * rely on the fact that the minor node for the actual device has a
         * :hubd as the intance.
         */
        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);
}

/*
 * We need to find all minor nodes of instances of the xhci driver whose name is
 * 'hubd'.
 */
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);
}