On Mon, Jan 04, 2021 at 09:13:12AM +0100, Marcus Glocker wrote:

> On Sat, 2 Jan 2021 11:11:18 +0000
> Natasha Kerensikova <[email protected]> wrote:
> 
> > Hello,
> > 
> > on Saturday 02 January 2021 at 09:34, Marcus Glocker wrote:
> > > I was not following up the initial conversation on this thread, so I
> > > probably missed a lot of what was already discussed.  
> > 
> > A lot of the conversation was outside of mailing lists, and I'm not
> > sure exactly why. I summed up my understanding, but my knowledge very
> > limit I might have misunderstood or misexplained some things.
> > 
> > > I'm also not very familiar with any xhci specific packet loss
> > > feature mechanism.  But if there was already an proposal from
> > > patrick@ to come over this by doing an EP clearing on device open,
> > > I've just tested the following diff, and it makes my scanner
> > > reliably fail now with xhci :-)  
> > 
> > Here are the explanations I got from patrick@, on 2019-11-05 (in case
> > things changed since then), I hope I'm not committing a vile act by
> > cut-and-pasting a private mail into a public ML:
> > 
> > Interrupt transfers (IN and OUT) use a specific method to find out if
> > packets have been lost.  This can be done by toggling the PID between
> > DATA0 <-> DATA1.
> > 
> > When we open/close uhid(4) we open/close the pipe.  On non-xHCI we
> > can save the endpoint toggle state and keep it.  With xHCI it looks
> > like we can only reset the toggle state... to 0.
> > 
> > This is because there are only add/drop and reset endpoint commands.
> > "Add" and "drop" reset the toggle state, while with "reset" endpoint
> > we have the possibility to keep it.  open/close does add/drop, so we
> > always lose it, but that's life.
> > 
> > If there is a way to save the toggle state between add/drop, then it
> > must have gotten lost somewhere, because it all reads like it does
> > not exist.
> > 
> > Are there other ways to "set" the toggle?  Yes, there is.  We can
> > reset it on _both_ ends by clearing an endpoint stall.  Yes, even
> > though we are not actually stalled, a CLEAR_FEATURE on the endpoint
> > always resets the toggle state, and clearing endpoint stall is perfect
> > for this.
> > 
> > Note that we have this issue on both the IN and OUT pipe.  On the OUT
> > pipe we did always send it with DATA0, even though the device expected
> > DATA1.  On the IN pipe (after drop/add) we expected DATA0, but the
> > device sent DATA1.
> 
> Thanks for sharing this.  It explains the issue quite well.  In a
> very quick summary it means:
> 
> ugen(4) being one of the rare device drivers were we have an open/close
> cycle for interrupt and bulk endpoints without attach/detach cycle, the
> issue manifests with xhci(4), that it will reset the data toggle state on
> the HC, while on the device the data toggle state remains.  This finally
> leads to the common case where on a freshly initialized device the data
> toggles are in sync, and therefore the first run succeeds, while the
> second run fails due to the data toggles got out of sync.
> 
> So what we should do IMO is to reset the data toggle state on our
> device endpoints _before_ we open the pipes.  If we do it as already
> tested with usbd_clear_endpoint_stall() after the pipe has been opened,
> it will lead to the problems we saw.
> 
> NetBSD has introduced a function in their USB stack called
> usbd_clear_endpoint_feature() with which one can issue the UR_CLEAR_FEATURE
> request on endpoints without the need to have the pipe open.  Also this
> function won't try to call the cleartoggle USB pipe method, which is
> anyway not implemented for xhci(4).  And it also makes clear that
> we're not executing it because of a stall situation.  This function we
> can perfectly use in our case to reset the data toggles for all the
> endpoints on the active interface in one go, _before_ we open the pipes.
> 
> On the other hand this approach introduces an issue on ehci(4) HCs.
> While we reset the data toggle on the device now, ehci(4) will keep the
> last data toggle state since the usbd_clear_endpoint_feature() function
> can't call the ehci(4) cleartoggle USB pipe method.  That leads to the
> situation that we run out of sync again when we use ugen(4) on a
> ehci(4) HC.  While I'm not a big fan of running code in device drivers
> which is HC specific, I have no better idea currently than only execute
> the new function on xhci(4) HCs, and leave everything else as is, since
> this runs perfectly fine on non-xhci HCs.
> 
> Just as another side note;  I have also tested the
> usbd_clear_endpoint_stall() on pipe close approach with ehci(4), but
> it will lead to a panic currently when a queue is still active because
> the ehci(4) cleartoggle USB pipe method will check if the queue is
> still in an active state, and panic if so.
> 
> I've tested the following diff on a xhci(4), and a ehci(4) HC with the
> Canon LiDE 400 scanner.

Improved diff without the need for HC specific checks.


Index: dev/usb/ugen.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/ugen.c,v
retrieving revision 1.109
diff -u -p -u -p -r1.109 ugen.c
--- dev/usb/ugen.c      25 Dec 2020 12:59:52 -0000      1.109
+++ dev/usb/ugen.c      6 Jan 2021 07:19:32 -0000
@@ -46,9 +46,12 @@
 #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 *, i
 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);
+int ugen_clear_endpoint_toggle(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, 
                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_endpoint_toggle(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, 
                                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, 
                                  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)
@@ -940,6 +950,50 @@ ugen_get_alt_index(struct ugen_softc *sc
        if (err)
                return (-1);
        return (usbd_get_interface_altindex(iface));
+}
+
+int
+ugen_clear_endpoint_toggle(struct ugen_softc *sc, struct usbd_interface *iface)
+{
+       usb_interface_descriptor_t *id;
+       usb_endpoint_descriptor_t *ed;
+       usbd_status err;
+       int i;
+
+       /* Only halt 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 (0);
+       }
+       DPRINTFN(1,("%s: halt interface endpoints\n", __func__));
+
+       id = usbd_get_interface_descriptor(iface);
+       if (id == NULL)
+               return (-1);
+
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(iface, i);
+               if (ed == NULL)
+                       return (-1);
+
+               switch (UE_GET_XFERTYPE(ed->bmAttributes)) {
+               case UE_BULK:
+               case UE_INTERRUPT:
+                       err = usbd_clear_endpoint_feature(sc->sc_udev,
+                           ed->bEndpointAddress, UF_ENDPOINT_HALT);
+                       if (err) {
+                               printf("%s: halt endpoint failed!\n", __func__);
+                               return (-1);
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return (0);
 }
 
 int
Index: dev/usb/usbdi_util.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdi_util.c,v
retrieving revision 1.44
diff -u -p -u -p -r1.44 usbdi_util.c
--- dev/usb/usbdi_util.c        6 Oct 2019 17:11:51 -0000       1.44
+++ dev/usb/usbdi_util.c        6 Jan 2021 07:19:32 -0000
@@ -192,6 +192,19 @@ usbd_clear_port_feature(struct usbd_devi
 }
 
 usbd_status
+usbd_clear_endpoint_feature(struct usbd_device *dev, int epaddr, int sel)
+{
+       usb_device_request_t req;
+
+       req.bmRequestType = UT_WRITE_ENDPOINT;
+       req.bRequest = UR_CLEAR_FEATURE;
+       USETW(req.wValue, sel);
+       USETW(req.wIndex, epaddr);
+       USETW(req.wLength, 0);
+       return (usbd_do_request(dev, &req, 0));
+}
+
+usbd_status
 usbd_set_port_feature(struct usbd_device *dev, int port, int sel)
 {
        usb_device_request_t req;
Index: dev/usb/usbdi_util.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdi_util.h,v
retrieving revision 1.29
diff -u -p -u -p -r1.29 usbdi_util.h
--- dev/usb/usbdi_util.h        8 Dec 2014 22:00:11 -0000       1.29
+++ dev/usb/usbdi_util.h        6 Jan 2021 07:19:32 -0000
@@ -41,6 +41,7 @@ usbd_status   usbd_set_hub_feature(struct 
 usbd_status    usbd_clear_hub_feature(struct usbd_device *, int);
 usbd_status    usbd_set_port_feature(struct usbd_device *dev, int, int);
 usbd_status    usbd_clear_port_feature(struct usbd_device *, int, int);
+usbd_status    usbd_clear_endpoint_feature(struct usbd_device *, int, int);
 usbd_status    usbd_get_device_status(struct usbd_device *, usb_status_t *);
 usbd_status    usbd_get_hub_status(struct usbd_device *, usb_hub_status_t *);
 usbd_status    usbd_get_hub_descriptor(struct usbd_device *,

Reply via email to