Author: trasz
Date: Mon Apr 16 16:19:31 2018
New Revision: 332598
URL: https://svnweb.freebsd.org/changeset/base/332598

Log:
  MFC r328589:
  
  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.
  
  Relnotes:     yes
  Sponsored by: The FreeBSD Foundation

Modified:
  stable/11/sys/dev/usb/usb_device.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/sys/dev/usb/usb_device.c
==============================================================================
--- stable/11/sys/dev/usb/usb_device.c  Mon Apr 16 16:16:24 2018        
(r332597)
+++ stable/11/sys/dev/usb/usb_device.c  Mon Apr 16 16:19:31 2018        
(r332598)
@@ -85,6 +85,7 @@
 
 /* function prototypes  */
 
+static int     sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS);
 static void    usb_init_endpoint(struct usb_device *, uint8_t,
                    struct usb_endpoint_descriptor *,
                    struct usb_endpoint_ss_comp_descriptor *,
@@ -118,8 +119,137 @@ int       usb_template = USB_TEMPLATE;
 int    usb_template;
 #endif
 
-SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RWTUN,
-    &usb_template, 0, "Selected USB device side template");
+SYSCTL_PROC(_hw_usb, OID_AUTO, 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 */
 
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to