From 5d08e3d383078899c5a226c8b55bea15031b733d Mon Sep 17 00:00:00 2001 From: mglocker Date: Mon, 25 Jan 2021 14:14:42 +0000 Subject: [PATCH] Resolve data toggle out of sync problem for ugen(4) and uhidev(4) devices 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 | 50 +++++++++++++++++++++++++++++++++++++++++++- sys/dev/usb/uhidev.c | 44 +++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/sys/dev/usb/ugen.c b/sys/dev/usb/ugen.c index 6a27d3f6410..39997668bcd 100644 --- a/sys/dev/usb/ugen.c +++ b/sys/dev/usb/ugen.c @@ -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 $ */ @@ -46,9 +46,12 @@ #include #include +#include + #include #include #include +#include #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__); +} diff --git a/sys/dev/usb/uhidev.c b/sys/dev/usb/uhidev.c index 5f2bc07cc96..3549129cbfc 100644 --- a/sys/dev/usb/uhidev.c +++ b/sys/dev/usb/uhidev.c @@ -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__); +} -- 2.20.1