Hi On Sun, Apr 16, 2017 at 10:56 PM, Emmanuel Dreyfus <m...@netbsd.org> wrote:
> I already asked the question hidden at the end of another message [1]: > can we power-cycle a USB port? My understanding is that we have no way > of doing it from userland, but what about from kernel? Even if the capability > is hardware-dependent, it would be nice to have an ioctl to power off/power > on a USB port. There is a patch using sysctl. Regards, -- Kimihiro Nonaka
uhub(4): Add port power control sysctl node. diff --git a/sys/dev/usb/uhub.c b/sys/dev/usb/uhub.c index e5a9a13d710..33d3004ed5a 100644 --- a/sys/dev/usb/uhub.c +++ b/sys/dev/usb/uhub.c @@ -116,6 +116,8 @@ struct uhub_softc { int sc_explorepending; u_char sc_running; + + struct sysctllog *sc_clog; }; #define UHUB_IS_HIGH_SPEED(sc) \ @@ -127,7 +129,8 @@ struct uhub_softc { Static usbd_status uhub_explore(struct usbd_device *); Static void uhub_intr(struct usbd_xfer *, void *, usbd_status); - +Static int uhub_sysctl_port_power(SYSCTLFN_PROTO); +Static int uhub_sysctl_port_reset(SYSCTLFN_PROTO); /* * We need two attachment points: @@ -261,6 +264,8 @@ uhub_attach(device_t parent, device_t self, void *aux) struct usbd_interface *iface; usb_endpoint_descriptor_t *ed; struct usbd_tt *tts = NULL; + const struct sysctlnode *rnode, *cnode, *node; + int error; UHUBHIST_FUNC(); UHUBHIST_CALLED(); @@ -458,6 +463,58 @@ uhub_attach(device_t parent, device_t self, void *aux) if (dev->ud_powersrc->up_parent != NULL) usbd_delay_ms(dev, pwrdly); + /* Port power */ + if ((dev->ud_hub->uh_hubdesc.wHubCharacteristics[0] & UHD_PWR) >= + UHD_PWR_NO_SWITCH) + goto sysctl_done; + if ((error = sysctl_createv(&sc->sc_clog, 0, NULL, &rnode, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "uhub", + SYSCTL_DESCR("uhub global controls"), + NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) { + aprint_normal_dev(sc->sc_dev, + "couldn't create uhub global sysctl node\n"); + goto sysctl_done; + } + if ((error = sysctl_createv(&sc->sc_clog, 0, &rnode, &rnode, + 0, CTLTYPE_NODE, device_xname(self), + SYSCTL_DESCR("per uhub controls"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0) { + aprint_normal_dev(sc->sc_dev, + "couldn't create uhub sysctl node\n"); + goto sysctl_done; + } + for (port = 1; port <= nports; port++) { + char buf[8]; + snprintf(buf, sizeof(buf), "port%d", port); + if ((error = sysctl_createv(&sc->sc_clog, 0, &rnode, &cnode, + 0, CTLTYPE_NODE, buf, + SYSCTL_DESCR("uhub port controls"), + NULL, 0, NULL, 0, port, CTL_EOL)) != 0) { + aprint_normal_dev(sc->sc_dev, + "couldn't create uhub port sysctl node\n"); + goto sysctl_done; + } + if ((error = sysctl_createv(&sc->sc_clog, 0, &cnode, &node, + CTLFLAG_READWRITE, CTLTYPE_INT, "power", + SYSCTL_DESCR("uhub port power control"), + uhub_sysctl_port_power, 0, (void *)sc, 0, + CTL_CREATE, CTL_EOL)) != 0) { + aprint_normal_dev(sc->sc_dev, + "couldn't create uhub port power sysctl node\n"); + goto sysctl_done; + } + if ((error = sysctl_createv(&sc->sc_clog, 0, &cnode, &node, + CTLFLAG_READWRITE, CTLTYPE_INT, "reset", + SYSCTL_DESCR("uhub port reset control"), + uhub_sysctl_port_reset, 0, (void *)sc, 0, + CTL_CREATE, CTL_EOL)) != 0) { + aprint_normal_dev(sc->sc_dev, + "couldn't create uhub port reset sysctl node\n"); + goto sysctl_done; + } + } +sysctl_done: + /* The usual exploration will finish the setup. */ sc->sc_running = 1; @@ -468,6 +525,7 @@ uhub_attach(device_t parent, device_t self, void *aux) return; bad: + sysctl_teardown(&sc->sc_clog); if (sc->sc_status) kmem_free(sc->sc_status, sc->sc_statuslen); if (sc->sc_statuspend) @@ -822,6 +880,8 @@ uhub_detach(device_t self, int flags) /* XXXSMP usb */ KERNEL_LOCK(1, curlwp); + sysctl_teardown(&sc->sc_clog); + nports = hub->uh_hubdesc.bNbrPorts; for (port = 0; port < nports; port++) { rup = &hub->uh_ports[port]; @@ -967,3 +1027,83 @@ uhub_intr(struct usbd_xfer *xfer, void *addr, usbd_status status) mutex_exit(&sc->sc_lock); } } + +Static int +uhub_sysctl_port_power(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + struct sysctlnode *pnode; + struct uhub_softc *sc; + usb_port_status_t status; + usbd_status err; + int t, port, error; + + node = *rnode; + sc = node.sysctl_data; + + pnode = rnode->sysctl_parent; + if (pnode == NULL) + return 0; + port = pnode->sysctl_num; + err = usbd_get_port_status(sc->sc_hub, port, &status); + if (err) { + DPRINTF("uhub %d get port stat failed, err %d", + device_unit(sc->sc_dev), err, 0, 0); + return EINVAL; + } + + t = (UGETW(status.wPortStatus) & UPS_PORT_POWER) ? 1 : 0; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + if (t) { + err = usbd_set_port_feature(sc->sc_hub, port, UHF_PORT_POWER); + if (err) + aprint_error_dev(sc->sc_dev, "port %d power on failed, %s\n", + port, usbd_errstr(err)); + } else { + err = usbd_clear_port_feature(sc->sc_hub, port, UHF_PORT_POWER); + if (err) + aprint_error_dev(sc->sc_dev, "port %d power off failed, %s\n", + port, usbd_errstr(err)); + } + + return (err == USBD_NORMAL_COMPLETION) ? 0 : EINVAL; +} + +Static int +uhub_sysctl_port_reset(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + struct sysctlnode *pnode; + struct uhub_softc *sc; + usbd_status err; + int t, port, error; + + node = *rnode; + sc = node.sysctl_data; + + t = 0; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + pnode = node.sysctl_parent; + if (pnode == NULL) + return 0; + port = pnode->sysctl_num; + + if (t) { + err = usbd_set_port_feature(sc->sc_hub, port, UHF_PORT_RESET); + if (err) { + aprint_error_dev(sc->sc_dev, "port %d power on failed, %s\n", + port, usbd_errstr(err)); + return EINVAL; + } + } + + return 0; +}