Make the handler routine for the hw.usb.template sysctl trigger the USB

host to reprobe the bus by switching the USB pull up resistors off and
back on.  In other words - when FreeBSD is configured as a USB device,
changing the sysctl will be immediately noticed by the machine it's
connected to.

Reviewed by:	hselasky@
MFC after:	2 weeks
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Edward Tomasz Napierala 2018-01-30 10:08:11 +00:00
parent 2145d321ac
commit f66c3cfc58

View File

@ -87,6 +87,7 @@
/* function prototypes */ /* function prototypes */
static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS);
static void usb_init_endpoint(struct usb_device *, uint8_t, static void usb_init_endpoint(struct usb_device *, uint8_t,
struct usb_endpoint_descriptor *, struct usb_endpoint_descriptor *,
struct usb_endpoint_ss_comp_descriptor *, struct usb_endpoint_ss_comp_descriptor *,
@ -120,8 +121,137 @@ int usb_template = USB_TEMPLATE;
int usb_template; int usb_template;
#endif #endif
SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RWTUN, SYSCTL_PROC(_hw_usb, OID_AUTO, template,
&usb_template, 0, "Selected USB device side template"); CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
NULL, 0, sysctl_hw_usb_template,
"I", "Selected USB device side template");
/*------------------------------------------------------------------------*
* usb_trigger_reprobe_on_off
*
* This function sets the pull up resistors for all ports currently
* operating in device mode either on (when on_not_off is 1), or off
* (when it's 0).
*------------------------------------------------------------------------*/
static void
usb_trigger_reprobe_on_off(int on_not_off)
{
struct usb_port_status ps;
struct usb_bus *bus;
struct usb_device *udev;
usb_error_t err;
int do_unlock, max;
max = devclass_get_maxunit(usb_devclass_ptr);
while (max >= 0) {
mtx_lock(&usb_ref_lock);
bus = devclass_get_softc(usb_devclass_ptr, max);
max--;
if (bus == NULL || bus->devices == NULL ||
bus->devices[USB_ROOT_HUB_ADDR] == NULL) {
mtx_unlock(&usb_ref_lock);
continue;
}
udev = bus->devices[USB_ROOT_HUB_ADDR];
if (udev->refcount == USB_DEV_REF_MAX) {
mtx_unlock(&usb_ref_lock);
continue;
}
udev->refcount++;
mtx_unlock(&usb_ref_lock);
do_unlock = usbd_enum_lock(udev);
if (do_unlock > 1) {
do_unlock = 0;
goto next;
}
err = usbd_req_get_port_status(udev, NULL, &ps, 1);
if (err != 0) {
DPRINTF("usbd_req_get_port_status() "
"failed: %s\n", usbd_errstr(err));
goto next;
}
if ((UGETW(ps.wPortStatus) & UPS_PORT_MODE_DEVICE) == 0)
goto next;
if (on_not_off) {
err = usbd_req_set_port_feature(udev, NULL, 1,
UHF_PORT_POWER);
if (err != 0) {
DPRINTF("usbd_req_set_port_feature() "
"failed: %s\n", usbd_errstr(err));
}
} else {
err = usbd_req_clear_port_feature(udev, NULL, 1,
UHF_PORT_POWER);
if (err != 0) {
DPRINTF("usbd_req_clear_port_feature() "
"failed: %s\n", usbd_errstr(err));
}
}
next:
mtx_lock(&usb_ref_lock);
if (do_unlock)
usbd_enum_unlock(udev);
if (--(udev->refcount) == 0)
cv_broadcast(&udev->ref_cv);
mtx_unlock(&usb_ref_lock);
}
}
/*------------------------------------------------------------------------*
* usb_trigger_reprobe_all
*
* This function toggles the pull up resistors for all ports currently
* operating in device mode, causing the host machine to reenumerate them.
*------------------------------------------------------------------------*/
static void
usb_trigger_reprobe_all(void)
{
/*
* Set the pull up resistors off for all ports in device mode.
*/
usb_trigger_reprobe_on_off(0);
/*
* According to the DWC OTG spec this must be at least 3ms.
*/
usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME));
/*
* Set the pull up resistors back on.
*/
usb_trigger_reprobe_on_off(1);
}
static int
sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS)
{
int error, val;
val = usb_template;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL || usb_template == val)
return (error);
usb_template = val;
if (usb_template < 0) {
usb_trigger_reprobe_on_off(0);
} else {
usb_trigger_reprobe_all();
}
return (0);
}
/* English is default language */ /* English is default language */