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) {