Currenty USB device driver code has no way to obtain how many frames
can be scheduled on the HC. If it attempts to schedule too many
frames, usbd_transfer() fails or silently misbehaves.

For audio this is a big problem because the max frames count
determines the block size which must be reported to upper layers
before any transfer starts. This makes impossible to implement uaudio
properly. Currently there's a temporary hack to workaround this, which
limits the block size. Now that uaudio is in and works, it's time to
start removing such hacks.

Similarly, driver code needs to know the minimum number of frames per
transfer to get an interrupt (ie the completion callback). Indeed, if
the transfer frame count is not rounded to it, we silently miss the
interrupt, the completion call-back is not called and playback
stutters.

The diff below adds a new usbd_bus_info() function which reports the
maximum frames that can be scheduled and the minimum frames per
transfer to get an interrupt.

Index: ehci.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/ehci.c,v
retrieving revision 1.204
diff -u -p -u -p -r1.204 ehci.c
--- ehci.c      31 Mar 2019 06:16:38 -0000      1.204
+++ ehci.c      12 Jun 2019 13:33:38 -0000
@@ -113,6 +113,7 @@ struct ehci_pipe {
 u_int8_t               ehci_reverse_bits(u_int8_t, int);
 
 usbd_status    ehci_open(struct usbd_pipe *);
+void           ehci_info(struct usbd_device *, struct usbd_bus_info *);
 int            ehci_setaddr(struct usbd_device *, int);
 void           ehci_poll(struct usbd_bus *);
 void           ehci_softintr(void *);
@@ -225,6 +226,7 @@ struct usbd_bus_methods ehci_bus_methods
        .do_poll = ehci_poll,
        .allocx = ehci_allocx,
        .freex = ehci_freex,
+       .info = ehci_info,
 };
 
 struct usbd_pipe_methods ehci_root_ctrl_methods = {
@@ -493,6 +498,21 @@ ehci_init(struct ehci_softc *sc)
        return (err);
 }
 
+void
+ehci_info(struct usbd_device *dev, struct usbd_bus_info *info)
+{
+       /*
+        * XXX: even if most hosts have 1024 frame lists, only 256
+        * frame transfers work reliably. As soon as 256 is exceeded,
+        * we start getting zeroed frames if multiple device are
+        * running simultaneously. Set this to sc->sc_fl_size, once
+        * ehci is fixed. Interrups occur every 1m, despite the
+        * EHCI_CMD_ITC_2 setting.
+        */
+       info->nframes_max = 256;
+       info->intr_thres = (dev->speed >= USB_SPEED_HIGH) ? 8 : 1;
+}
+
 int
 ehci_intr(void *v)
 {
Index: ohci.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/ohci.c,v
retrieving revision 1.156
diff -u -p -u -p -r1.156 ohci.c
--- ohci.c      11 Mar 2019 17:50:08 -0000      1.156
+++ ohci.c      12 Jun 2019 13:33:40 -0000
@@ -87,6 +87,7 @@ usbd_status   ohci_alloc_std_chain(struct 
                    struct ohci_soft_td **);
 
 usbd_status    ohci_open(struct usbd_pipe *);
+void           ohci_info(struct usbd_device *, struct usbd_bus_info *);
 int            ohci_setaddr(struct usbd_device *, int);
 void           ohci_poll(struct usbd_bus *);
 void           ohci_softintr(void *);
@@ -236,6 +237,7 @@ struct usbd_bus_methods ohci_bus_methods
        .do_poll = ohci_poll,
        .allocx = ohci_allocx,
        .freex = ohci_freex,
+       .info = ohci_info,
 };
 
 struct usbd_pipe_methods ohci_root_ctrl_methods = {
@@ -931,6 +933,13 @@ ohci_init(struct ohci_softc *sc)
  bad1:
        usb_freemem(&sc->sc_bus, &sc->sc_hccadma);
        return (err);
+}
+
+void
+ohci_info(struct usbd_device *dev, struct usbd_bus_info *info)
+{
+       info->nframes_max = OHCI_SITD_CHUNK;
+       info->intr_thres = 1;
 }
 
 struct usbd_xfer *
Index: uaudio.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/uaudio.c,v
retrieving revision 1.144
diff -u -p -u -p -r1.144 uaudio.c
--- uaudio.c    9 May 2019 07:09:04 -0000       1.144
+++ uaudio.c    12 Jun 2019 13:33:41 -0000
@@ -382,6 +382,7 @@ struct uaudio_softc {
        unsigned int ufps;              /* USB frames per second */
        unsigned int sync_pktsz;        /* size of sync packet */
        unsigned int host_nframes;      /* max frames we can schedule */
+       unsigned int host_intr_thres;   /* min frames to get an interrupt */
 
        int diff_nsamp;                 /* samples play is ahead of rec */
        int diff_nframes;               /* frames play is ahead of rec */
@@ -2820,12 +2821,15 @@ uaudio_stream_open(struct uaudio_softc *
        s->data_pipe = NULL;
        s->sync_pipe = NULL;
 
-       s->nframes_mask = 0;
-       i = a->fps;
-       while (i > 1000) {
-               s->nframes_mask = (s->nframes_mask << 1) | 1;
-               i >>= 1;
-       }
+       /*
+        * Find the smallest transfer size (power of two), larger than
+        * the poll interval and the interrupt threshold.
+        */
+       i = 0;
+       while (a->fps < (sc->ufps >> i) || sc->host_intr_thres > (1 << i))
+               i++;
+
+       s->nframes_mask = (1 << i) - 1;
 
        /* bytes per audio frame */
        bpa = a->bps * a->nch;
@@ -2900,12 +2904,10 @@ uaudio_stream_open(struct uaudio_softc *
        }
 
        /*
-        * Require at least 2ms block size to ensure no
-        * transfer exceeds two blocks.
-        * 
-        * XXX: use s->nframes_mask instead of 1000
+        * Require at least twice the smallest transfer size allowed
+        * as block size to ensure no transfer exceeds two blocks.
         */
-       if (1000 * blksz < 2 * sc->rate * bpa) {
+       if (sc->ufps / (sc->nframes_mask + 1) * blksz < 2 * sc->rate * bpa) {
                printf("%s: audio block too small\n", DEVNAME(sc));
                return EIO;
        }
@@ -3694,6 +3696,7 @@ uaudio_attach(struct device *parent, str
        struct uaudio_softc *sc = (struct uaudio_softc *)self;
        struct usb_attach_arg *arg = aux;
        struct usb_config_descriptor *cdesc;
+       struct usbd_bus_info info;
        struct uaudio_blob desc;
 
        /*
@@ -3720,31 +3723,29 @@ uaudio_attach(struct device *parent, str
        sc->trigger_mode = 0;
        sc->copy_todo = 0;
 
-       /*
-        * Ideally the USB host controller should expose the number of
-        * frames we're allowed to schedule, but there's no such
-        * interface. The uhci(4) driver can buffer up to 128 frames
-        * (or it crashes), ehci(4) starts recording null frames if we
-        * exceed 256 (micro-)frames, ohci(4) works with at most 50
-        * frames.
-        */
        switch (sc->udev->speed) {
        case USB_SPEED_LOW:
        case USB_SPEED_FULL:
                sc->ufps = 1000;
                sc->sync_pktsz = 3;
-               sc->host_nframes = 50;
                break;
        case USB_SPEED_HIGH:
        case USB_SPEED_SUPER:
                sc->ufps = 8000;
                sc->sync_pktsz = 4;
-               sc->host_nframes = 240;
                break;
        default:
                printf("%s: unsupported bus speed\n", DEVNAME(sc));
                return;
        }
+
+       /*
+        * get the max number of frames the host supports, needed to
+        * determine the max block size
+        */
+       usbd_bus_info(sc->udev, &info);
+       sc->host_nframes = info.nframes_max;
+       sc->host_intr_thres = info.intr_thres;
 
        if (!uaudio_process_conf(sc, &desc))
                return;
Index: uhci.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/uhci.c,v
retrieving revision 1.147
diff -u -p -u -p -r1.147 uhci.c
--- uhci.c      12 Mar 2019 08:13:50 -0000      1.147
+++ uhci.c      12 Jun 2019 13:33:41 -0000
@@ -178,6 +178,7 @@ void                uhci_root_intr_close(struct usbd_p
 void           uhci_root_intr_done(struct usbd_xfer *);
 
 usbd_status    uhci_open(struct usbd_pipe *);
+void           uhci_info(struct usbd_device *, struct usbd_bus_info *);
 void           uhci_poll(struct usbd_bus *);
 void           uhci_softintr(void *);
 
@@ -253,6 +254,7 @@ struct usbd_bus_methods uhci_bus_methods
        .do_poll = uhci_poll,
        .allocx = uhci_allocx,
        .freex = uhci_freex,
+       .info = uhci_info,
 };
 
 struct usbd_pipe_methods uhci_root_ctrl_methods = {
@@ -486,6 +488,13 @@ uhci_init(struct uhci_softc *sc)
                UHCI_INTR_IOCE | UHCI_INTR_SPIE);       /* enable interrupts */
 
        return (uhci_run(sc, 1));               /* and here we go... */
+}
+
+void
+uhci_info(struct usbd_device *dev, struct usbd_bus_info *info)
+{
+       info->nframes_max = UHCI_VFRAMELIST_COUNT;
+       info->intr_thres = 1;
 }
 
 int
Index: usbdi.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdi.c,v
retrieving revision 1.100
diff -u -p -u -p -r1.100 usbdi.c
--- usbdi.c     18 Nov 2018 16:33:26 -0000      1.100
+++ usbdi.c     12 Jun 2019 13:33:41 -0000
@@ -275,6 +275,12 @@ usbd_close_pipe(struct usbd_pipe *pipe)
        return (USBD_NORMAL_COMPLETION);
 }
 
+void
+usbd_bus_info(struct usbd_device *dev, struct usbd_bus_info *info)
+{
+       dev->bus->methods->info(dev, info);
+}
+
 usbd_status
 usbd_transfer(struct usbd_xfer *xfer)
 {
Index: usbdi.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdi.h,v
retrieving revision 1.70
diff -u -p -u -p -r1.70 usbdi.h
--- usbdi.h     1 May 2018 18:14:46 -0000       1.70
+++ usbdi.h     12 Jun 2019 13:33:42 -0000
@@ -86,9 +86,15 @@ typedef void (*usbd_callback)(struct usb
 
 #define DEVINFOSIZE 1024
 
+struct usbd_bus_info {
+       int nframes_max;        /* max frames we can schedule */
+       int intr_thres;         /* interrupt threshold in (micro-)frames */
+};
+
 usbd_status usbd_open_pipe(struct usbd_interface *iface, u_int8_t address,
     u_int8_t flags, struct usbd_pipe **pipe);
 usbd_status usbd_close_pipe(struct usbd_pipe *pipe);
+void usbd_bus_info(struct usbd_device *dev, struct usbd_bus_info *info);
 usbd_status usbd_transfer(struct usbd_xfer *req);
 struct usbd_xfer *usbd_alloc_xfer(struct usbd_device *);
 void usbd_free_xfer(struct usbd_xfer *xfer);
Index: usbdivar.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/usbdivar.h,v
retrieving revision 1.79
diff -u -p -u -p -r1.79 usbdivar.h
--- usbdivar.h  27 Nov 2018 14:56:09 -0000      1.79
+++ usbdivar.h  12 Jun 2019 13:33:42 -0000
@@ -65,6 +65,7 @@ struct usbd_bus_methods {
        void                  (*do_poll)(struct usbd_bus *);
        struct usbd_xfer *    (*allocx)(struct usbd_bus *);
        void                  (*freex)(struct usbd_bus *, struct usbd_xfer *);
+       void                  (*info)(struct usbd_device *, struct 
usbd_bus_info *);
 };
 
 struct usbd_pipe_methods {
Index: xhci.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/xhci.c,v
retrieving revision 1.104
diff -u -p -u -p -r1.104 xhci.c
--- xhci.c      21 May 2019 09:20:40 -0000      1.104
+++ xhci.c      12 Jun 2019 13:33:42 -0000
@@ -126,6 +126,7 @@ void        xhci_timeout_task(void *);
 
 /* USBD Bus Interface. */
 usbd_status      xhci_pipe_open(struct usbd_pipe *);
+void             xhci_info(struct usbd_device *, struct usbd_bus_info *);
 int              xhci_setaddr(struct usbd_device *, int);
 void             xhci_softintr(void *);
 void             xhci_poll(struct usbd_bus *);
@@ -161,6 +162,7 @@ struct usbd_bus_methods xhci_bus_methods
        .do_poll = xhci_poll,
        .allocx = xhci_allocx,
        .freex = xhci_freex,
+       .info = xhci_info,
 };
 
 struct usbd_pipe_methods xhci_root_ctrl_methods = {
@@ -468,6 +470,18 @@ xhci_config(struct xhci_softc *sc)
        DPRINTF(("%s: IMAN=%#x\n", DEVNAME(sc), XRREAD4(sc, XHCI_IMAN(0))));
 }
 
+void
+xhci_info(struct usbd_device *dev, struct usbd_bus_info *info)
+{
+       info->nframes_max = XHCI_MAX_XFER;
+
+       /*
+        * We get only one interrupt per frame, ie 1 interrupt every 8
+        * microframes
+        */
+       info->intr_thres = (dev->speed >= USB_SPEED_HIGH) ? 8 : 1;
+}
+
 int
 xhci_detach(struct device *self, int flags)
 {

Reply via email to