Resolve data toggle out of sync problem for ugen(4) and uhidev(4) devices
authormglocker <mglocker@openbsd.org>
Mon, 25 Jan 2021 14:14:42 +0000 (14:14 +0000)
committermglocker <mglocker@openbsd.org>
Mon, 25 Jan 2021 14:14:42 +0000 (14:14 +0000)
on xhci(4) controllers by clearing the interface endpoints before opening
the pipes.

Tested by Mikolaj Kucharski for ugen(4) and gnezdo@ for uhidev(4), plus
myself for both.

ok mpi@

sys/dev/usb/ugen.c
sys/dev/usb/uhidev.c

index 6a27d3f..3999766 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ugen.c,v 1.109 2020/12/25 12:59:52 visa Exp $ */
+/*     $OpenBSD: ugen.c,v 1.110 2021/01/25 14:14:42 mglocker Exp $ */
 /*     $NetBSD: ugen.c,v 1.63 2002/11/26 18:49:48 christos Exp $       */
 /*     $FreeBSD: src/sys/dev/usb/ugen.c,v 1.26 1999/11/17 22:33:41 n_hibma Exp $       */
 
 #include <sys/vnode.h>
 #include <sys/poll.h>
 
+#include <machine/bus.h>
+
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
 #include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdivar.h>
 
 #ifdef UGEN_DEBUG
 #define DPRINTF(x)     do { if (ugendebug) printf x; } while (0)
@@ -114,6 +117,7 @@ int ugen_do_close(struct ugen_softc *, int, int);
 int ugen_set_config(struct ugen_softc *sc, int configno);
 int ugen_set_interface(struct ugen_softc *, int, int);
 int ugen_get_alt_index(struct ugen_softc *sc, int ifaceidx);
+void ugen_clear_iface_eps(struct ugen_softc *, struct usbd_interface *);
 
 #define UGENUNIT(n) ((minor(n) >> 4) & 0xf)
 #define UGENENDPOINT(n) (minor(n) & 0xf)
@@ -302,6 +306,8 @@ ugenopen(dev_t dev, int flag, int mode, struct proc *p)
                DPRINTFN(5, ("ugenopen: sc=%p, endpt=%d, dir=%d, sce=%p\n",
                             sc, endpt, dir, sce));
                edesc = sce->edesc;
+               /* Clear device endpoint toggle. */
+               ugen_clear_iface_eps(sc, sce->iface);
                switch (UE_GET_XFERTYPE(edesc->bmAttributes)) {
                case UE_INTERRUPT:
                        if (dir == OUT) {
@@ -329,6 +335,8 @@ ugenopen(dev_t dev, int flag, int mode, struct proc *p)
                                clfree(&sce->q);
                                return (EIO);
                        }
+                       /* Clear HC endpoint toggle. */
+                       usbd_clear_endpoint_toggle(sce->pipeh);
                        DPRINTFN(5, ("ugenopen: interrupt open done\n"));
                        break;
                case UE_BULK:
@@ -336,6 +344,8 @@ ugenopen(dev_t dev, int flag, int mode, struct proc *p)
                                  edesc->bEndpointAddress, 0, &sce->pipeh);
                        if (err)
                                return (EIO);
+                       /* Clear HC endpoint toggle. */
+                       usbd_clear_endpoint_toggle(sce->pipeh);
                        break;
                case UE_ISOCHRONOUS:
                        if (dir == OUT)
@@ -1418,3 +1428,41 @@ ugenkqfilter(dev_t dev, struct knote *kn)
 
        return (0);
 }
+
+void
+ugen_clear_iface_eps(struct ugen_softc *sc, struct usbd_interface *iface)
+{
+       usb_interface_descriptor_t *id;
+       usb_endpoint_descriptor_t *ed;
+       uint8_t xfertype;
+       int i;
+
+       /* Only clear interface endpoints when none are in use. */
+       for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
+               if (i == USB_CONTROL_ENDPOINT)
+                       continue;
+               if (sc->sc_is_open[i] != 0)
+                       return;
+       }
+       DPRINTFN(1,("%s: clear interface eps\n", __func__));
+
+       id = usbd_get_interface_descriptor(iface);
+       if (id == NULL)
+               goto bad;
+
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(iface, i);
+               if (ed == NULL)
+                       goto bad;
+
+               xfertype = UE_GET_XFERTYPE(ed->bmAttributes);
+               if (xfertype == UE_BULK || xfertype == UE_INTERRUPT) {
+                       if (usbd_clear_endpoint_feature(sc->sc_udev,
+                           ed->bEndpointAddress, UF_ENDPOINT_HALT))
+                               goto bad;
+               }
+       }
+       return;
+bad:
+       printf("%s: clear endpoints failed!\n", __func__);
+}
index 5f2bc07..3549129 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: uhidev.c,v 1.83 2020/08/31 12:26:49 patrick Exp $     */
+/*     $OpenBSD: uhidev.c,v 1.84 2021/01/25 14:14:42 mglocker Exp $    */
 /*     $NetBSD: uhidev.c,v 1.14 2003/03/11 16:44:00 augustss Exp $     */
 
 /*
@@ -98,6 +98,7 @@ int uhidev_activate(struct device *, int);
 
 void uhidev_get_report_async_cb(struct usbd_xfer *, void *, usbd_status);
 void uhidev_set_report_async_cb(struct usbd_xfer *, void *, usbd_status);
+void uhidev_clear_iface_eps(struct uhidev_softc *, struct usbd_interface *);
 
 struct cfdriver uhidev_cd = {
        NULL, "uhidev", DV_DULL
@@ -508,6 +509,9 @@ uhidev_open(struct uhidev *scd)
        DPRINTF(("uhidev_open: isize=%d, ep=0x%02x\n", sc->sc_isize,
            sc->sc_iep_addr));
 
+       /* Clear device endpoint toggle. */
+       uhidev_clear_iface_eps(sc, sc->sc_iface);
+
        err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep_addr,
                  USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_ibuf,
                  sc->sc_isize, uhidev_intr, USBD_DEFAULT_INTERVAL);
@@ -517,6 +521,8 @@ uhidev_open(struct uhidev *scd)
                error = EIO;
                goto out1;
        }
+       /* Clear HC endpoint toggle. */
+       usbd_clear_endpoint_toggle(sc->sc_ipipe);
 
        DPRINTF(("uhidev_open: sc->sc_ipipe=%p\n", sc->sc_ipipe));
 
@@ -542,6 +548,8 @@ uhidev_open(struct uhidev *scd)
                        error = EIO;
                        goto out2;
                }
+               /* Clear HC endpoint toggle. */
+               usbd_clear_endpoint_toggle(sc->sc_opipe);
 
                DPRINTF(("uhidev_open: sc->sc_opipe=%p\n", sc->sc_opipe));
 
@@ -950,3 +958,37 @@ uhidev_ioctl(struct uhidev *sc, u_long cmd, caddr_t addr, int flag,
        }
        return 0;
 }
+
+void
+uhidev_clear_iface_eps(struct uhidev_softc *sc, struct usbd_interface *iface)
+{
+       usb_interface_descriptor_t *id;
+       usb_endpoint_descriptor_t *ed;
+       uint8_t xfertype;
+       int i;
+
+       /* Only clear interface endpoints when none are in use. */
+       if (sc->sc_ipipe || sc->sc_opipe)
+               return;
+       DPRINTFN(1,("%s: clear interface eps\n", __func__));
+
+       id = usbd_get_interface_descriptor(iface);
+       if (id == NULL)
+               goto bad;
+
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(iface, i);
+               if (ed == NULL)
+                       goto bad;
+
+               xfertype = UE_GET_XFERTYPE(ed->bmAttributes);
+               if (xfertype == UE_BULK || xfertype == UE_INTERRUPT) {
+                       if (usbd_clear_endpoint_feature(sc->sc_udev,
+                           ed->bEndpointAddress, UF_ENDPOINT_HALT))
+                               goto bad;
+               }
+       }
+       return;
+bad:
+       printf("%s: clear endpoints failed!\n", __func__);
+}