tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Power-cycle a usb port



Hi

On Sun, Apr 16, 2017 at 10:56 PM, Emmanuel Dreyfus <manu%netbsd.org@localhost> 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;
+}


Home | Main Index | Thread Index | Old Index