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;
+}

Reply via email to