From a5a0ab9e5c1d871c0b99671c514be205c8c8a6bc Mon Sep 17 00:00:00 2001 From: jakemsr Date: Fri, 23 Jul 2010 12:17:48 +0000 Subject: [PATCH] support for playback sync endpoints sync endpoints allow for the sample clock on USB audio devices to not be synchronized to the USB clock. the sync endpoint gives information on how much data to send to the data endpoint. although devices that require sync endpoints will "work" when the sync endpoint inormation is ignored, there is a possibility of static, echoing, or any other type of clock desync error. requires some small changes to existing code: * tighten up checks for whether a setting needs/supplies a sync endpoint. ignore settings that need a sync endpoint but don't supply one. previously all settings that require a sync endpoint were ignored. * if a sync endpoint is being used, use the same denominator for fractional samples as the sync endpoint uses (2**16). this only implements playback sync endpoints. recording sync endpoints are an odd concept: the driver would control the clock rate of the device. there may be such devices, but I can't even imagine how that could be reliably implemented. I guess you would sync to the USB clock or the system clock ... but then, what's the point? --- sys/dev/usb/uaudio.c | 295 +++++++++++++++++++++++++++++++++---------- 1 file changed, 230 insertions(+), 65 deletions(-) diff --git a/sys/dev/usb/uaudio.c b/sys/dev/usb/uaudio.c index ea535f47ed4..fcdaf4b4ab6 100644 --- a/sys/dev/usb/uaudio.c +++ b/sys/dev/usb/uaudio.c @@ -1,4 +1,4 @@ -/* $OpenBSD: uaudio.c,v 1.83 2010/07/21 10:16:07 jakemsr Exp $ */ +/* $OpenBSD: uaudio.c,v 1.84 2010/07/23 12:17:48 jakemsr Exp $ */ /* $NetBSD: uaudio.c,v 1.90 2004/10/29 17:12:53 kent Exp $ */ /* @@ -68,7 +68,6 @@ #include /* #define UAUDIO_DEBUG */ -/* #define UAUDIO_MULTIPLE_ENDPOINTS */ #ifdef UAUDIO_DEBUG #define DPRINTF(x) do { if (uaudiodebug) printf x; } while (0) #define DPRINTFN(n,x) do { if (uaudiodebug>(n)) printf x; } while (0) @@ -81,6 +80,7 @@ int uaudiodebug = 0; #define UAUDIO_NCHANBUFS 3 /* number of outstanding request */ #define UAUDIO_MIN_FRAMES 2 /* ms of sound in each request */ #define UAUDIO_MAX_FRAMES 16 +#define UAUDIO_NSYNCBUFS 3 /* number of outstanding sync requests */ #define UAUDIO_MAX_ALTS 32 /* max alt settings allowed by driver */ @@ -129,9 +129,11 @@ struct chan { u_int sample_rate; u_int bytes_per_frame; u_int max_bytes_per_frame; - u_int fraction; /* fraction/usb_fps is the extra samples/frame */ + u_int fraction; /* fraction/frac_denom is the extra samples/frame */ + u_int frac_denom; /* denominator for fractional samples */ u_int residue; /* accumulates the fractional samples */ u_int nframes; /* # of frames per transfer */ + u_int nsync_frames; /* # of frames per sync transfer */ u_int usb_fps; u_int maxpktsize; u_int reqms; /* usb request data duration, in ms */ @@ -146,6 +148,8 @@ struct chan { int altidx; /* currently used altidx */ int curchanbuf; + int cursyncbuf; + struct chanbuf { struct chan *chan; usbd_xfer_handle xfer; @@ -155,6 +159,15 @@ struct chan { u_int16_t size; } chanbufs[UAUDIO_NCHANBUFS]; + struct syncbuf { + struct chan *chan; + usbd_xfer_handle xfer; + u_char *buffer; + u_int16_t sizes[UAUDIO_MAX_FRAMES]; + u_int16_t offsets[UAUDIO_MAX_FRAMES]; + u_int16_t size; + } syncbufs[UAUDIO_NSYNCBUFS]; + struct uaudio_softc *sc; /* our softc */ }; @@ -333,6 +346,9 @@ void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); void uaudio_chan_ptransfer(struct chan *); void uaudio_chan_pintr (usbd_xfer_handle, usbd_private_handle, usbd_status); +void uaudio_chan_psync_transfer(struct chan *); +void uaudio_chan_psync_intr + (usbd_xfer_handle, usbd_private_handle, usbd_status); void uaudio_chan_rtransfer(struct chan *); void uaudio_chan_rintr @@ -1604,10 +1620,10 @@ uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, const struct usb_audio_streaming_interface_descriptor *asid; const struct usb_audio_streaming_type1_descriptor *asf1d; const usb_endpoint_descriptor_audio_t *ed; - const usb_endpoint_descriptor_audio_t *epdesc1; + const usb_endpoint_descriptor_audio_t *sync_ed; const struct usb_audio_streaming_endpoint_descriptor *sed; int format, chan, prec, enc, bps; - int dir, type, sync; + int dir, type, sync, sync_addr; struct as_info ai; const char *format_str; @@ -1653,31 +1669,21 @@ uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, dir = UE_GET_DIR(ed->bEndpointAddress); type = UE_GET_ISO_TYPE(ed->bmAttributes); - /* We can't handle endpoints that need a sync pipe yet. */ + /* Check for sync endpoint. */ sync = FALSE; - /* bSynchAddress set to 0 indicates sync pipe is not needed. */ - if (ed->bSynchAddress != 0) { - if (dir == UE_DIR_IN && type == UE_ISO_ADAPT) { - sync = TRUE; -#ifndef UAUDIO_MULTIPLE_ENDPOINTS - printf("%s: ignored input endpoint of type adaptive\n", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); -#endif - } - if (dir != UE_DIR_IN && type == UE_ISO_ASYNC) { - sync = TRUE; -#ifndef UAUDIO_MULTIPLE_ENDPOINTS - printf("%s: ignored output endpoint of type async\n", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); -#endif - } - } - if (sync && id->bNumEndpoints < 2) { - printf("%s: sync pipe needed, but no sync endpoint given\n", - sc->sc_dev.dv_xname); - return (USBD_NORMAL_COMPLETION); + sync_addr = 0; + if (id->bNumEndpoints > 1 && + ((dir == UE_DIR_IN && type == UE_ISO_ADAPT) || + (dir != UE_DIR_IN && type == UE_ISO_ASYNC))) + sync = TRUE; + + /* Check whether sync endpoint address is given. */ + if (ed->bLength >= USB_ENDPOINT_DESCRIPTOR_AUDIO_SIZE) { + /* bSynchAdress set to 0 indicates sync is not used. */ + if (ed->bSynchAddress == 0) + sync = FALSE; + else + sync_addr = ed->bSynchAddress; } sed = (const void *)(buf + offs); @@ -1689,44 +1695,56 @@ uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, if (offs > size) return (USBD_INVAL); - epdesc1 = NULL; -#ifdef UAUDIO_MULTIPLE_ENDPOINTS - if (sync) { - epdesc1 = (const void*)(buf + offs); - if (epdesc1->bDescriptorType != UDESC_ENDPOINT) - return USBD_INVAL; + sync_ed = NULL; + if (sync == TRUE) { + sync_ed = (const void*)(buf + offs); + if (sync_ed->bDescriptorType != UDESC_ENDPOINT) { + printf("%s: sync ep descriptor wrong type\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); + } DPRINTF(("uaudio_process_as: endpoint[1] bLength=%d " "bDescriptorType=%d bEndpointAddress=%d " "bmAttributes=0x%x wMaxPacketSize=%d bInterval=%d " "bRefresh=%d bSynchAddress=%d\n", - epdesc1->bLength, epdesc1->bDescriptorType, - epdesc1->bEndpointAddress, epdesc1->bmAttributes, - UGETW(epdesc1->wMaxPacketSize), epdesc1->bInterval, - epdesc1->bRefresh, epdesc1->bSynchAddress)); - offs += epdesc1->bLength; - if (offs > size) - return USBD_INVAL; - if (epdesc1->bSynchAddress != 0) { - printf("%s: invalid endpoint: bSynchAddress=0\n", + sync_ed->bLength, sync_ed->bDescriptorType, + sync_ed->bEndpointAddress, sync_ed->bmAttributes, + UGETW(sync_ed->wMaxPacketSize), sync_ed->bInterval, + sync_ed->bRefresh, sync_ed->bSynchAddress)); + offs += sync_ed->bLength; + if (offs > size) { + printf("%s: sync ep descriptor too large\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); + } + if (dir == UE_GET_DIR(sync_ed->bEndpointAddress)) { + printf("%s: sync ep wrong direction\n", sc->sc_dev.dv_xname); - return USBD_INVAL; + return (USBD_NORMAL_COMPLETION); } - if (UE_GET_XFERTYPE(epdesc1->bmAttributes) != UE_ISOCHRONOUS) { - printf("%s: invalid endpoint: bmAttributes=0x%x\n", - sc->sc_dev.dv_xname, epdesc1->bmAttributes); - return USBD_INVAL; + if (UE_GET_XFERTYPE(sync_ed->bmAttributes) != UE_ISOCHRONOUS) { + printf("%s: sync ep wrong xfer type\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); } - if (epdesc1->bEndpointAddress != ed->bSynchAddress) { - printf("%s: invalid endpoint addresses: " - "ep[0]->bSynchAddress=0x%x " - "ep[1]->bEndpointAddress=0x%x\n", - sc->sc_dev.dv_xname, ed->bSynchAddress, - epdesc1->bEndpointAddress); - return USBD_INVAL; + if (sync_ed->bLength >= + USB_ENDPOINT_DESCRIPTOR_AUDIO_SIZE && + sync_ed->bSynchAddress != 0) { + printf("%s: sync ep bSynchAddress != 0\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); + } + if (sync_addr && sync_ed->bEndpointAddress != sync_addr) { + printf("%s: sync ep address mismatch\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); } - /* UE_GET_ADDR(epdesc1->bEndpointAddress), and epdesc1->bRefresh */ } -#endif + if (sync_ed != NULL && dir == UE_DIR_IN) { + printf("%s: sync pipe for recording not yet implemented\n", + sc->sc_dev.dv_xname); + return (USBD_NORMAL_COMPLETION); + } format = UGETW(asid->wFormatTag); chan = asf1d->bNrChannels; @@ -1789,7 +1807,7 @@ uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, ai.attributes = sed->bmAttributes; ai.idesc = id; ai.edesc = ed; - ai.edesc1 = epdesc1; + ai.edesc1 = sync_ed; ai.asf1desc = asf1d; ai.sc_busy = 0; if (sc->sc_nalts < UAUDIO_MAX_ALTS) @@ -1835,9 +1853,7 @@ uaudio_identify_as(struct uaudio_softc *sc, sc->sc_nullalt = id->bAlternateSetting; break; case 1: -#ifdef UAUDIO_MULTIPLE_ENDPOINTS case 2: -#endif uaudio_process_as(sc, buf, &offs, size, id); break; default: @@ -2218,6 +2234,8 @@ uaudio_halt_out_dma(void *addr) if (sc->sc_playchan.pipe != NULL) { uaudio_chan_close(sc, &sc->sc_playchan); sc->sc_playchan.pipe = NULL; + if (sc->sc_playchan.sync_pipe != NULL) + sc->sc_playchan.sync_pipe = NULL; uaudio_chan_free_buffers(sc, &sc->sc_playchan); sc->sc_playchan.intr = NULL; } @@ -2233,6 +2251,8 @@ uaudio_halt_in_dma(void *addr) if (sc->sc_recchan.pipe != NULL) { uaudio_chan_close(sc, &sc->sc_recchan); sc->sc_recchan.pipe = NULL; + if (sc->sc_recchan.sync_pipe != NULL) + sc->sc_recchan.sync_pipe = NULL; uaudio_chan_free_buffers(sc, &sc->sc_recchan); sc->sc_recchan.intr = NULL; } @@ -2680,6 +2700,10 @@ uaudio_trigger_output(void *addr, void *start, void *end, int blksize, s = splusb(); for (i = 0; i < UAUDIO_NCHANBUFS; i++) uaudio_chan_ptransfer(ch); + if (ch->sync_pipe) { + for (i = 0; i < UAUDIO_NSYNCBUFS; i++) + uaudio_chan_psync_transfer(ch); + } splx(s); return (0); @@ -2698,8 +2722,10 @@ uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) /* Set alternate interface corresponding to the mode. */ err = usbd_set_interface(as->ifaceh, as->alt); - if (err) + if (err) { + DPRINTF(("%s: usbd_set_interface failed\n", __func__)); return (err); + } /* * If just one sampling rate is supported, @@ -2720,12 +2746,19 @@ uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) ch->sync_pipe = 0; DPRINTF(("uaudio_chan_open: create pipe to 0x%02x\n", endpt)); err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->pipe); - if (err) + if (err) { + printf("%s: error creating pipe: err=%s endpt=0x%02x\n", + __func__, usbd_errstr(err), endpt); return err; + } if (as->edesc1 != NULL) { endpt = as->edesc1->bEndpointAddress; DPRINTF(("uaudio_chan_open: create sync-pipe to 0x%02x\n", endpt)); err = usbd_open_pipe(as->ifaceh, endpt, 0, &ch->sync_pipe); + if (err) { + printf("%s: error creating sync-pipe: err=%s endpt=0x%02x\n", + __func__, usbd_errstr(err), endpt); + } } return err; } @@ -2754,6 +2787,7 @@ uaudio_chan_close(struct uaudio_softc *sc, struct chan *ch) usbd_status uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) { + struct as_info *as = &sc->sc_alts[ch->altidx]; usbd_xfer_handle xfer; void *buf; int i, size; @@ -2775,6 +2809,22 @@ uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) ch->chanbufs[i].buffer = buf; ch->chanbufs[i].chan = ch; } + if (as->edesc1 != NULL) { + size = (ch->hi_speed ? 4 : 3) * ch->nsync_frames; + for (i = 0; i < UAUDIO_NSYNCBUFS; i++) { + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + goto bad_sync; + ch->syncbufs[i].xfer = xfer; + buf = usbd_alloc_buffer(xfer, size); + if (buf == 0) { + i++; + goto bad_sync; + } + ch->syncbufs[i].buffer = buf; + ch->syncbufs[i].chan = ch; + } + } return (USBD_NORMAL_COMPLETION); @@ -2783,15 +2833,27 @@ bad: /* implicit buffer free */ usbd_free_xfer(ch->chanbufs[i].xfer); return (USBD_NOMEM); + +bad_sync: + while (--i >= 0) + /* implicit buffer free */ + usbd_free_xfer(ch->syncbufs[i].xfer); + return (USBD_NOMEM); + } void uaudio_chan_free_buffers(struct uaudio_softc *sc, struct chan *ch) { + struct as_info *as = &sc->sc_alts[ch->altidx]; int i; for (i = 0; i < UAUDIO_NCHANBUFS; i++) usbd_free_xfer(ch->chanbufs[i].xfer); + if (as->edesc1 != NULL) { + for (i = 0; i < UAUDIO_NSYNCBUFS; i++) + usbd_free_xfer(ch->syncbufs[i].xfer); + } } /* Called at splusb() */ @@ -2817,10 +2879,10 @@ uaudio_chan_ptransfer(struct chan *ch) for (i = 0; i < ch->nframes; i++) { size = ch->bytes_per_frame; residue += ch->fraction; - if (residue >= ch->usb_fps) { + if (residue >= ch->frac_denom) { if ((ch->sc->sc_altflags & UA_NOFRAC) == 0) size += ch->sample_size; - residue -= ch->usb_fps; + residue -= ch->frac_denom; } cb->sizes[i] = size; total += size; @@ -2901,6 +2963,91 @@ uaudio_chan_pintr(usbd_xfer_handle xfer, usbd_private_handle priv, uaudio_chan_ptransfer(ch); } +/* Called at splusb() */ +void +uaudio_chan_psync_transfer(struct chan *ch) +{ + struct syncbuf *sb; + int i, size, total = 0; + + if (ch->sc->sc_dying) + return; + + /* Pick the next sync buffer. */ + sb = &ch->syncbufs[ch->cursyncbuf]; + if (++ch->cursyncbuf >= UAUDIO_NSYNCBUFS) + ch->cursyncbuf = 0; + + size = ch->hi_speed ? 4 : 3; + for (i = 0; i < ch->nsync_frames; i++) { + sb->sizes[i] = size; + sb->offsets[i] = total; + total += size; + } + sb->size = total; + + DPRINTFN(5,("%s: transfer xfer=%p\n", __func__, sb->xfer)); + /* Fill the request */ + usbd_setup_isoc_xfer(sb->xfer, ch->sync_pipe, sb, sb->sizes, + ch->nsync_frames, USBD_NO_COPY, uaudio_chan_psync_intr); + + (void)usbd_transfer(sb->xfer); +} + +void +uaudio_chan_psync_intr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct syncbuf *sb = priv; + struct chan *ch = sb->chan; + u_int32_t count, tmp; + u_int32_t freq, freq_w, freq_f; + int i, pos, size; + + /* Return if we are aborting. */ + if (status == USBD_CANCELLED) + return; + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + DPRINTFN(5,("%s: count=%d\n", __func__, count)); + + size = ch->hi_speed ? 4 : 3; + for (i = 0; count > 0 && i < ch->nsync_frames; i++) { + if (sb->sizes[i] != size) + continue; + count -= size; + pos = sb->offsets[i]; + if (ch->hi_speed) { + /* 16.16 (12.13) -> 16.16 (12.16) */ + freq = sb->buffer[pos+3] << 24 | + sb->buffer[pos+2] << 16 | + sb->buffer[pos+1] << 8 | + sb->buffer[pos]; + } else { + /* 10.14 (10.10) -> 16.16 (10.16) */ + freq = sb->buffer[pos+2] << 18 | + sb->buffer[pos+1] << 10 | + sb->buffer[pos] << 2; + } + freq_w = (freq >> 16) & (ch->hi_speed ? 0x0fff : 0x03ff); + freq_f = freq & 0xffff; + DPRINTFN(5,("%s: freq = %d %d/%d\n", __func__, freq_w, freq_f, + ch->frac_denom)); + tmp = freq_w * ch->sample_size; + if (tmp + (freq_f ? ch->sample_size : 0) > + ch->max_bytes_per_frame) { + DPRINTF(("%s: packet size request too large: %d/%d/%d\n", + __func__, tmp, ch->max_bytes_per_frame, ch->maxpktsize)); + } else { + ch->bytes_per_frame = tmp; + ch->fraction = freq_f; + } + } + + /* start next transfer */ + uaudio_chan_psync_transfer(ch); +} + /* Called at splusb() */ void uaudio_chan_rtransfer(struct chan *ch) @@ -3084,6 +3231,24 @@ uaudio_chan_init(struct chan *ch, int mode, int altidx, ch->max_bytes_per_frame = ch->maxpktsize; ch->residue = 0; + ch->frac_denom = ch->usb_fps; + if (ai->edesc1 != NULL) { + /* + * The lower 16-bits of the sync request represent + * fractional samples. Scale up the fraction here once + * so all fractions are using the same denominator. + */ + ch->frac_denom = 1 << 16; + ch->fraction = (ch->fraction * ch->frac_denom) / ch->usb_fps; + + /* + * Have to set nsync_frames somewhere. We can request + * a lot of sync data; the device will reply when it's + * ready, with empty frames meaning to keep using the + * current rate. + */ + ch->nsync_frames = UAUDIO_MAX_FRAMES; + } DPRINTF(("%s: residual sample fraction: %d/%d\n", __func__, ch->fraction, ch->usb_fps)); } -- 2.20.1