Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. =================================================================== RCS file: /ftp/cvs/cvsroot/src/sys/dev/usb/umidi.c,v rcsdiff: /ftp/cvs/cvsroot/src/sys/dev/usb/umidi.c,v: warning: Unknown phrases like `commitid ...;' are present. retrieving revision 1.23.2.1 retrieving revision 1.23.2.2 diff -u -p -r1.23.2.1 -r1.23.2.2 --- src/sys/dev/usb/umidi.c 2006/06/21 15:07:44 1.23.2.1 +++ src/sys/dev/usb/umidi.c 2006/12/30 20:49:39 1.23.2.2 @@ -1,10 +1,11 @@ -/* $NetBSD: umidi.c,v 1.23.2.1 2006/06/21 15:07:44 yamt Exp $ */ +/* $NetBSD: umidi.c,v 1.23.2.2 2006/12/30 20:49:39 yamt Exp $ */ /* * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation - * by Takuya SHIOZAKI (tshiozak@NetBSD.org). + * by Takuya SHIOZAKI (tshiozak@NetBSD.org) and (full-size transfers, extended + * hw_if) Chapman Flack (chap@NetBSD.org). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -36,8 +37,9 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: umidi.c,v 1.23.2.1 2006/06/21 15:07:44 yamt Exp $"); +__KERNEL_RCSID(0, "$NetBSD: umidi.c,v 1.23.2.2 2006/12/30 20:49:39 yamt Exp $"); +#include #include #include #include @@ -52,6 +54,10 @@ __KERNEL_RCSID(0, "$NetBSD: umidi.c,v 1. #include #include +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS +#include +#endif + #include #include #include @@ -67,6 +73,8 @@ __KERNEL_RCSID(0, "$NetBSD: umidi.c,v 1. #ifdef UMIDI_DEBUG #define DPRINTF(x) if (umididebug) printf x #define DPRINTFN(n,x) if (umididebug >= (n)) printf x +#include +static struct timeval umidi_tv; int umididebug = 0; #else #define DPRINTF(x) @@ -77,7 +85,10 @@ int umididebug = 0; static int umidi_open(void *, int, void (*)(void *, int), void (*)(void *), void *); static void umidi_close(void *); -static int umidi_output(void *, int); +static int umidi_channelmsg(void *, int, int, u_char *, int); +static int umidi_commonmsg(void *, int, u_char *, int); +static int umidi_sysex(void *, u_char *, int); +static int umidi_rtmsg(void *, int); static void umidi_getinfo(void *, struct midi_info *); static usbd_status alloc_pipe(struct umidi_endpoint *); @@ -102,8 +113,7 @@ static usbd_status open_in_jack(struct u static void close_out_jack(struct umidi_jack *); static void close_in_jack(struct umidi_jack *); -static usbd_status attach_mididev(struct umidi_softc *, - struct umidi_mididev *); +static usbd_status attach_mididev(struct umidi_softc *, struct umidi_mididev *); static usbd_status detach_mididev(struct umidi_mididev *, int); static usbd_status deactivate_mididev(struct umidi_mididev *); static usbd_status alloc_all_mididevs(struct umidi_softc *, int); @@ -111,6 +121,7 @@ static void free_all_mididevs(struct umi static usbd_status attach_all_mididevs(struct umidi_softc *); static usbd_status detach_all_mididevs(struct umidi_softc *, int); static usbd_status deactivate_all_mididevs(struct umidi_softc *); +static char *describe_mididev(struct umidi_mididev *); #ifdef UMIDI_DEBUG static void dump_sc(struct umidi_softc *); @@ -118,24 +129,35 @@ static void dump_ep(struct umidi_endpoin static void dump_jack(struct umidi_jack *); #endif -static void init_packet(struct umidi_packet *); - static usbd_status start_input_transfer(struct umidi_endpoint *); static usbd_status start_output_transfer(struct umidi_endpoint *); -static int out_jack_output(struct umidi_jack *, int); +static int out_jack_output(struct umidi_jack *, u_char *, int, int); static void in_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); static void out_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); -static void out_build_packet(int, struct umidi_packet *, uByte); +static void out_solicit(void *); /* struct umidi_endpoint* for softintr */ const struct midi_hw_if umidi_hw_if = { umidi_open, umidi_close, - umidi_output, + umidi_rtmsg, umidi_getinfo, 0, /* ioctl */ }; +struct midi_hw_if_ext umidi_hw_if_ext = { + .channel = umidi_channelmsg, + .common = umidi_commonmsg, + .sysex = umidi_sysex, +}; + +struct midi_hw_if_ext umidi_hw_if_mm = { + .channel = umidi_channelmsg, + .common = umidi_commonmsg, + .sysex = umidi_sysex, + .compress = 1, +}; + USB_DECLARE_DRIVER(umidi); USB_MATCH(umidi) @@ -280,6 +302,7 @@ umidi_open(void *addr, { struct umidi_mididev *mididev = addr; struct umidi_softc *sc = mididev->sc; + usbd_status err; DPRINTF(("umidi_open: sc=%p\n", sc)); @@ -292,13 +315,23 @@ umidi_open(void *addr, mididev->opened = 1; mididev->flags = flags; - if ((mididev->flags & FWRITE) && mididev->out_jack) - open_out_jack(mididev->out_jack, arg, ointr); + if ((mididev->flags & FWRITE) && mididev->out_jack) { + err = open_out_jack(mididev->out_jack, arg, ointr); + if ( err != USBD_NORMAL_COMPLETION ) + goto bad; + } if ((mididev->flags & FREAD) && mididev->in_jack) { - open_in_jack(mididev->in_jack, arg, iintr); + err = open_in_jack(mididev->in_jack, arg, iintr); + if ( err != USBD_NORMAL_COMPLETION + && err != USBD_IN_PROGRESS ) + goto bad; } return 0; +bad: + mididev->opened = 0; + DPRINTF(("umidi_open: usbd_status %d\n", err)); + return USBD_IN_USE == err ? EBUSY : EIO; } void @@ -317,26 +350,79 @@ umidi_close(void *addr) } int -umidi_output(void *addr, int d) +umidi_channelmsg(void *addr, int status, int channel, u_char *msg, + int len) +{ + struct umidi_mididev *mididev = addr; + + if (!mididev->out_jack || !mididev->opened) + return EIO; + + return out_jack_output(mididev->out_jack, msg, len, (status>>4)&0xf); +} + +int +umidi_commonmsg(void *addr, int status, u_char *msg, int len) { struct umidi_mididev *mididev = addr; + int cin; if (!mididev->out_jack || !mididev->opened) return EIO; - return out_jack_output(mididev->out_jack, d); + switch ( len ) { + case 1: cin = 5; break; + case 2: cin = 2; break; + case 3: cin = 3; break; + default: return EIO; /* or gcc warns of cin uninitialized */ + } + + return out_jack_output(mididev->out_jack, msg, len, cin); +} + +int +umidi_sysex(void *addr, u_char *msg, int len) +{ + struct umidi_mididev *mididev = addr; + int cin; + + if (!mididev->out_jack || !mididev->opened) + return EIO; + + switch ( len ) { + case 1: cin = 5; break; + case 2: cin = 6; break; + case 3: cin = (msg[2] == 0xf7) ? 7 : 4; break; + default: return EIO; /* or gcc warns of cin uninitialized */ + } + + return out_jack_output(mididev->out_jack, msg, len, cin); +} + +int +umidi_rtmsg(void *addr, int d) +{ + struct umidi_mididev *mididev = addr; + u_char msg = d; + + if (!mididev->out_jack || !mididev->opened) + return EIO; + + return out_jack_output(mididev->out_jack, &msg, 1, 0xf); } void umidi_getinfo(void *addr, struct midi_info *mi) { struct umidi_mididev *mididev = addr; -/* struct umidi_softc *sc = mididev->sc; */ + struct umidi_softc *sc = mididev->sc; + int mm = UMQ_ISTYPE(sc, UMQ_TYPE_MIDIMAN_GARBLE); - mi->name = "USB MIDI I/F"; /* XXX: model name */ + mi->name = mididev->label; mi->props = MIDI_PROP_OUT_INTR; if (mididev->in_jack) mi->props |= MIDI_PROP_CAN_INPUT; + midi_register_hw_if_ext(mm? &umidi_hw_if_mm : &umidi_hw_if_ext); } @@ -350,23 +436,50 @@ alloc_pipe(struct umidi_endpoint *ep) { struct umidi_softc *sc = ep->sc; usbd_status err; - - DPRINTF(("%s: alloc_pipe %p\n", USBDEVNAME(sc->sc_dev), ep)); - LIST_INIT(&ep->queue_head); + usb_endpoint_descriptor_t *epd; + + epd = usbd_get_endpoint_descriptor(sc->sc_iface, ep->addr); + /* + * For output, an improvement would be to have a buffer bigger than + * wMaxPacketSize by num_jacks-1 additional packet slots; that would + * allow out_solicit to fill the buffer to the full packet size in + * all cases. But to use usbd_alloc_buffer to get a slightly larger + * buffer would not be a good way to do that, because if the addition + * would make the buffer exceed USB_MEM_SMALL then a substantially + * larger block may be wastefully allocated. Some flavor of double + * buffering could serve the same purpose, but would increase the + * code complexity, so for now I will live with the current slight + * penalty of reducing max transfer size by (num_open-num_scheduled) + * packet slots. + */ + ep->buffer_size = UGETW(epd->wMaxPacketSize); + ep->buffer_size -= ep->buffer_size % UMIDI_PACKET_SIZE; + + DPRINTF(("%s: alloc_pipe %p, buffer size %u\n", + USBDEVNAME(sc->sc_dev), ep, ep->buffer_size)); + ep->num_scheduled = 0; + ep->this_schedule = 0; + ep->next_schedule = 0; + ep->soliciting = 0; + ep->armed = 0; ep->xfer = usbd_alloc_xfer(sc->sc_udev); if (ep->xfer == NULL) { err = USBD_NOMEM; goto quit; } - ep->buffer = usbd_alloc_buffer(ep->xfer, UMIDI_PACKET_SIZE); + ep->buffer = usbd_alloc_buffer(ep->xfer, ep->buffer_size); if (ep->buffer == NULL) { usbd_free_xfer(ep->xfer); err = USBD_NOMEM; goto quit; } + ep->next_slot = ep->buffer; err = usbd_open_pipe(sc->sc_iface, ep->addr, 0, &ep->pipe); if (err) usbd_free_xfer(ep->xfer); +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + ep->solicit_cookie = softintr_establish(IPL_SOFTCLOCK,out_solicit,ep); +#endif quit: return err; } @@ -378,6 +491,9 @@ free_pipe(struct umidi_endpoint *ep) usbd_abort_pipe(ep->pipe); usbd_close_pipe(ep->pipe); usbd_free_xfer(ep->xfer); +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + softintr_disestablish(ep->solicit_cookie); +#endif } @@ -480,7 +596,6 @@ alloc_all_endpoints_fixed_ep(struct umid sc->sc_out_num_jacks += fp->out_ep[i].num_jacks; ep->num_open = 0; memset(ep->jacks, 0, sizeof(ep->jacks)); - LIST_INIT(&ep->queue_head); ep++; } ep = &sc->sc_in_ep[0]; @@ -494,13 +609,28 @@ alloc_all_endpoints_fixed_ep(struct umid err = USBD_INVAL; goto error; } - if (UE_GET_XFERTYPE(epd->bmAttributes)!=UE_BULK || - UE_GET_DIR(epd->bEndpointAddress)!=UE_DIR_IN) { + /* + * MIDISPORT_2X4 inputs on an interrupt rather than a bulk + * endpoint. The existing input logic in this driver seems + * to work successfully if we just stop treating an interrupt + * endpoint as illegal (or the in_progress status we get on + * the initial transfer). It does not seem necessary to + * actually use the interrupt flavor of alloc_pipe or make + * other serious rearrangements of logic. I like that. + */ + switch ( UE_GET_XFERTYPE(epd->bmAttributes) ) { + case UE_BULK: + case UE_INTERRUPT: + if ( UE_DIR_IN == UE_GET_DIR(epd->bEndpointAddress) ) + break; + /*FALLTHROUGH*/ + default: printf("%s: illegal endpoint(in:%d)\n", USBDEVNAME(sc->sc_dev), fp->in_ep[i].ep); err = USBD_INVAL; goto error; } + ep->sc = sc; ep->addr = epd->bEndpointAddress; ep->num_jacks = fp->in_ep[i].num_jacks; @@ -713,7 +843,34 @@ alloc_all_jacks(struct umidi_softc *sc) { int i, j; struct umidi_endpoint *ep; - struct umidi_jack *jack, **rjack; + struct umidi_jack *jack; + unsigned char *cn_spec; + + if (UMQ_ISTYPE(sc, UMQ_TYPE_CN_SEQ_PER_EP)) + sc->cblnums_global = 0; + else if (UMQ_ISTYPE(sc, UMQ_TYPE_CN_SEQ_GLOBAL)) + sc->cblnums_global = 1; + else { + /* + * I don't think this default is correct, but it preserves + * the prior behavior of the code. That's why I defined two + * complementary quirks. Any device for which the default + * behavior is wrong can be made to work by giving it an + * explicit quirk, and if a pattern ever develops (as I suspect + * it will) that a lot of otherwise standard USB MIDI devices + * need the CN_SEQ_PER_EP "quirk," then this default can be + * changed to 0, and the only devices that will break are those + * listing neither quirk, and they'll easily be fixed by giving + * them the CN_SEQ_GLOBAL quirk. + */ + sc->cblnums_global = 1; + } + + if (UMQ_ISTYPE(sc, UMQ_TYPE_CN_FIXED)) + cn_spec = umidi_get_quirk_data_from_type(sc->sc_quirk, + UMQ_TYPE_CN_FIXED); + else + cn_spec = NULL; /* allocate/initialize structures */ sc->sc_jacks = @@ -733,7 +890,9 @@ alloc_all_jacks(struct umidi_softc *sc) jack->binded = 0; jack->arg = NULL; jack->u.out.intr = NULL; - jack->cable_number = i; + jack->midiman_ppkt = NULL; + if ( sc->cblnums_global ) + jack->cable_number = i; jack++; } jack = &sc->sc_in_jacks[0]; @@ -742,7 +901,8 @@ alloc_all_jacks(struct umidi_softc *sc) jack->binded = 0; jack->arg = NULL; jack->u.in.intr = NULL; - jack->cable_number = i; + if ( sc->cblnums_global ) + jack->cable_number = i; jack++; } @@ -750,24 +910,28 @@ alloc_all_jacks(struct umidi_softc *sc) jack = &sc->sc_out_jacks[0]; ep = &sc->sc_out_ep[0]; for (i=0; isc_out_num_endpoints; i++) { - rjack = &ep->jacks[0]; for (j=0; jnum_jacks; j++) { - *rjack = jack; jack->endpoint = ep; + if ( cn_spec != NULL ) + jack->cable_number = *cn_spec++; + else if ( !sc->cblnums_global ) + jack->cable_number = j; + ep->jacks[jack->cable_number] = jack; jack++; - rjack++; } ep++; } jack = &sc->sc_in_jacks[0]; ep = &sc->sc_in_ep[0]; for (i=0; isc_in_num_endpoints; i++) { - rjack = &ep->jacks[0]; for (j=0; jnum_jacks; j++) { - *rjack = jack; jack->endpoint = ep; + if ( cn_spec != NULL ) + jack->cable_number = *cn_spec++; + else if ( !sc->cblnums_global ) + jack->cable_number = j; + ep->jacks[jack->cable_number] = jack; jack++; - rjack++; } ep++; } @@ -841,6 +1005,7 @@ assign_all_jacks_automatically(struct um usbd_status err; int i; struct umidi_jack *out, *in; + signed char *asg_spec; err = alloc_all_mididevs(sc, @@ -848,9 +1013,30 @@ assign_all_jacks_automatically(struct um if (err!=USBD_NORMAL_COMPLETION) return err; + if ( UMQ_ISTYPE(sc, UMQ_TYPE_MD_FIXED)) + asg_spec = umidi_get_quirk_data_from_type(sc->sc_quirk, + UMQ_TYPE_MD_FIXED); + else + asg_spec = NULL; + for (i=0; isc_num_mididevs; i++) { - out = (isc_out_num_jacks) ? &sc->sc_out_jacks[i]:NULL; - in = (isc_in_num_jacks) ? &sc->sc_in_jacks[i]:NULL; + if ( asg_spec != NULL ) { + if ( *asg_spec == -1 ) + out = NULL; + else + out = &sc->sc_out_jacks[*asg_spec]; + ++ asg_spec; + if ( *asg_spec == -1 ) + in = NULL; + else + in = &sc->sc_in_jacks[*asg_spec]; + ++ asg_spec; + } else { + out = (isc_out_num_jacks) ? &sc->sc_out_jacks[i] + : NULL; + in = (isc_in_num_jacks) ? &sc->sc_in_jacks[i] + : NULL; + } err = bind_jacks_to_mididev(sc, out, in, &sc->sc_mididevs[i]); if (err!=USBD_NORMAL_COMPLETION) { free_all_mididevs(sc); @@ -865,15 +1051,36 @@ static usbd_status open_out_jack(struct umidi_jack *jack, void *arg, void (*intr)(void *)) { struct umidi_endpoint *ep = jack->endpoint; + umidi_packet_bufp end; + int s; + int err; if (jack->opened) return USBD_IN_USE; jack->arg = arg; jack->u.out.intr = intr; - init_packet(&jack->packet); + jack->midiman_ppkt = NULL; + end = ep->buffer + ep->buffer_size / sizeof *ep->buffer; + s = splusb(); jack->opened = 1; ep->num_open++; + /* + * out_solicit maintains an invariant that there will always be + * (num_open - num_scheduled) slots free in the buffer. as we have + * just incremented num_open, the buffer may be too full to satisfy + * the invariant until a transfer completes, for which we must wait. + */ + while ( end - ep->next_slot < ep->num_open - ep->num_scheduled ) { + err = tsleep(ep, PWAIT|PCATCH, "umi op", mstohz(10)); + if ( err ) { + ep->num_open--; + jack->opened = 0; + splx(s); + return USBD_IOERROR; + } + } + splx(s); return USBD_NORMAL_COMPLETION; } @@ -904,31 +1111,25 @@ open_in_jack(struct umidi_jack *jack, vo static void close_out_jack(struct umidi_jack *jack) { - struct umidi_jack *tail; + struct umidi_endpoint *ep; int s; + u_int16_t mask; + int err; if (jack->opened) { + ep = jack->endpoint; + mask = 1 << (jack->cable_number); s = splusb(); - LIST_FOREACH(tail, - &jack->endpoint->queue_head, - u.out.queue_entry) - if (tail == jack) { - LIST_REMOVE(jack, u.out.queue_entry); + while ( mask & (ep->this_schedule | ep->next_schedule) ) { + err = tsleep(ep, PWAIT|PCATCH, "umi dr", mstohz(10)); + if ( err ) break; - } - if (jack == jack->endpoint->queue_tail) { - /* find tail */ - LIST_FOREACH(tail, - &jack->endpoint->queue_head, - u.out.queue_entry) { - if (!LIST_NEXT(tail, u.out.queue_entry)) { - jack->endpoint->queue_tail = tail; - } - } } - splx(s); jack->opened = 0; jack->endpoint->num_open--; + ep->this_schedule &= ~mask; + ep->next_schedule &= ~mask; + splx(s); } } @@ -950,6 +1151,8 @@ attach_mididev(struct umidi_softc *sc, s return USBD_IN_USE; mididev->sc = sc; + + mididev->label = describe_mididev(mididev); mididev->mdev = midi_attach_mi(&umidi_hw_if, mididev, &sc->sc_dev); @@ -969,6 +1172,11 @@ detach_mididev(struct umidi_mididev *mid if (mididev->mdev) config_detach(mididev->mdev, flags); + + if (NULL != mididev->label) { + free(mididev->label, M_USBDEV); + mididev->label = NULL; + } mididev->sc = NULL; @@ -1055,6 +1263,63 @@ deactivate_all_mididevs(struct umidi_sof return USBD_NORMAL_COMPLETION; } +/* + * TODO: the 0-based cable numbers will often not match the labeling of the + * equipment. Ideally: + * For class-compliant devices: get the iJack string from the jack descriptor. + * Otherwise: + * - support a DISPLAY_BASE_CN quirk (add the value to each internal cable + * number for display) + * - support an array quirk explictly giving a char * for each jack. + * For now, you get 0-based cable numbers. If there are multiple endpoints and + * the CNs are not globally unique, each is shown with its associated endpoint + * address in hex also. That should not be necessary when using iJack values + * or a quirk array. + */ +static char * +describe_mididev(struct umidi_mididev *md) +{ + char in_label[16]; + char out_label[16]; + char *unit_label; + char *final_label; + struct umidi_softc *sc; + int show_ep_in; + int show_ep_out; + size_t len; + + sc = md->sc; + show_ep_in = sc-> sc_in_num_endpoints > 1 && !sc->cblnums_global; + show_ep_out = sc->sc_out_num_endpoints > 1 && !sc->cblnums_global; + + if ( NULL != md->in_jack ) + snprintf(in_label, sizeof in_label, + show_ep_in ? "<%d(%x) " : "<%d ", + md->in_jack->cable_number, + md->in_jack->endpoint->addr); + else + in_label[0] = '\0'; + + if ( NULL != md->out_jack ) + snprintf(out_label, sizeof out_label, + show_ep_out ? ">%d(%x) " : ">%d ", + md->out_jack->cable_number, + md->out_jack->endpoint->addr); + else + in_label[0] = '\0'; + + unit_label = USBDEVNAME(sc->sc_dev); + + len = strlen(in_label) + strlen(out_label) + strlen(unit_label) + 4; + + final_label = malloc(len, M_USBDEV, M_WAITOK); + + snprintf(final_label, len, "%s%son %s", + in_label, out_label, unit_label); + + return final_label; +} + #ifdef UMIDI_DEBUG static void dump_sc(struct umidi_softc *sc) @@ -1076,8 +1341,10 @@ static void dump_ep(struct umidi_endpoint *ep) { int i; - for (i=0; inum_jacks; i++) { - DPRINTFN(10, ("\t\tjack(%p):\n", ep->jacks[i])); + for (i=0; ijacks[i]) + continue; + DPRINTFN(10, ("\t\tjack[%d]:%p:\n", i, ep->jacks[i])); dump_jack(ep->jacks[i]); } } @@ -1115,273 +1382,358 @@ static const int packet_length[16] = { /*F*/ 1, }; -static const struct { - int cin; - packet_state_t next; -} packet_0xFX[16] = { - /*F0: SysEx */ { 0x04, PS_EXCL_1 }, - /*F1: MTC */ { 0x02, PS_NORMAL_1OF2 }, - /*F2: S.POS */ { 0x03, PS_NORMAL_1OF3 }, - /*F3: S.SEL */ { 0x02, PS_NORMAL_1OF2 }, - /*F4: UNDEF */ { 0x00, PS_INITIAL }, - /*F5: UNDEF */ { 0x00, PS_INITIAL }, - /*F6: Tune */ { 0x0F, PS_END }, - /*F7: EofEx */ { 0x00, PS_INITIAL }, - /*F8: Timing */ { 0x0F, PS_END }, - /*F9: UNDEF */ { 0x00, PS_INITIAL }, - /*FA: Start */ { 0x0F, PS_END }, - /*FB: Cont */ { 0x0F, PS_END }, - /*FC: Stop */ { 0x0F, PS_END }, - /*FD: UNDEF */ { 0x00, PS_INITIAL }, - /*FE: ActS */ { 0x0F, PS_END }, - /*FF: Reset */ { 0x0F, PS_END }, -}; - #define GET_CN(p) (((unsigned char)(p)>>4)&0x0F) #define GET_CIN(p) ((unsigned char)(p)&0x0F) #define MIX_CN_CIN(cn, cin) \ ((unsigned char)((((unsigned char)(cn)&0x0F)<<4)| \ ((unsigned char)(cin)&0x0F))) -static void -init_packet(struct umidi_packet *packet) -{ - memset(packet->buffer, 0, UMIDI_PACKET_SIZE); - packet->state = PS_INITIAL; -} - static usbd_status start_input_transfer(struct umidi_endpoint *ep) { usbd_setup_xfer(ep->xfer, ep->pipe, (usbd_private_handle)ep, - ep->buffer, UMIDI_PACKET_SIZE, - USBD_NO_COPY, USBD_NO_TIMEOUT, in_intr); + ep->buffer, ep->buffer_size, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, in_intr); return usbd_transfer(ep->xfer); } static usbd_status start_output_transfer(struct umidi_endpoint *ep) { + usbd_status rv; + u_int32_t length; + int i; + + length = (ep->next_slot - ep->buffer) * sizeof *ep->buffer; + DPRINTFN(200,("umidi out transfer: start %p end %p length %u\n", + ep->buffer, ep->next_slot, length)); usbd_setup_xfer(ep->xfer, ep->pipe, (usbd_private_handle)ep, - ep->buffer, UMIDI_PACKET_SIZE, + ep->buffer, length, USBD_NO_COPY, USBD_NO_TIMEOUT, out_intr); - return usbd_transfer(ep->xfer); + rv = usbd_transfer(ep->xfer); + + /* + * Once the transfer is scheduled, no more adding to partial + * packets within it. + */ + if (UMQ_ISTYPE(ep->sc, UMQ_TYPE_MIDIMAN_GARBLE)) { + for (i=0; ijacks[i]) + ep->jacks[i]->midiman_ppkt = NULL; + } + + return rv; } #ifdef UMIDI_DEBUG #define DPR_PACKET(dir, sc, p) \ -if ((unsigned char)(p)->buffer[1]!=0xFE) \ +if ((unsigned char)(p)[1]!=0xFE) \ DPRINTFN(500, \ ("%s: umidi packet(" #dir "): %02X %02X %02X %02X\n", \ USBDEVNAME(sc->sc_dev), \ - (unsigned char)(p)->buffer[0], \ - (unsigned char)(p)->buffer[1], \ - (unsigned char)(p)->buffer[2], \ - (unsigned char)(p)->buffer[3])); + (unsigned char)(p)[0], \ + (unsigned char)(p)[1], \ + (unsigned char)(p)[2], \ + (unsigned char)(p)[3])); #else #define DPR_PACKET(dir, sc, p) #endif +/* + * A 4-byte Midiman packet superficially resembles a 4-byte USB MIDI packet + * with the cable number and length in the last byte instead of the first, + * but there the resemblance ends. Where a USB MIDI packet is a semantic + * unit, a Midiman packet is just a wrapper for 1 to 3 bytes of raw MIDI + * with a cable nybble and a length nybble (which, unlike the CIN of a + * real USB MIDI packet, has no semantics at all besides the length). + * A packet received from a Midiman may contain part of a MIDI message, + * more than one MIDI message, or parts of more than one MIDI message. A + * three-byte MIDI message may arrive in three packets of data length 1, and + * running status may be used. Happily, the midi(4) driver above us will put + * it all back together, so the only cost is in USB bandwidth. The device + * has an easier time with what it receives from us: we'll pack messages in + * and across packets, but filling the packets whenever possible and, + * as midi(4) hands us a complete message at a time, we'll never send one + * in a dribble of short packets. + */ + static int -out_jack_output(struct umidi_jack *out_jack, int d) +out_jack_output(struct umidi_jack *out_jack, u_char *src, int len, int cin) { struct umidi_endpoint *ep = out_jack->endpoint; struct umidi_softc *sc = ep->sc; - int error; + unsigned char *packet; int s; + int plen; + int poff; if (sc->sc_dying) return EIO; - error = 0; - if (out_jack->opened) { - DPRINTFN(1000, ("umidi_output: ep=%p 0x%02x\n", ep, d)); - out_build_packet(out_jack->cable_number, &out_jack->packet, d); - switch (out_jack->packet.state) { - case PS_EXCL_0: - case PS_END: - DPR_PACKET(out, sc, &out_jack->packet); - s = splusb(); - if (LIST_EMPTY(&ep->queue_head)) { - memcpy(ep->buffer, - out_jack->packet.buffer, - UMIDI_PACKET_SIZE); - start_output_transfer(ep); - } - if (LIST_EMPTY(&ep->queue_head)) - LIST_INSERT_HEAD(&ep->queue_head, - out_jack, u.out.queue_entry); - else - LIST_INSERT_AFTER(ep->queue_tail, - out_jack, u.out.queue_entry); - ep->queue_tail = out_jack; - splx(s); - break; - default: - error = EINPROGRESS; - } - } else - error = ENODEV; + if (!out_jack->opened) + return ENODEV; /* XXX as it was, is this the right errno? */ - return error; +#ifdef UMIDI_DEBUG + if ( umididebug >= 100 ) + microtime(&umidi_tv); +#endif + DPRINTFN(100, ("umidi out: %lu.%06lus ep=%p cn=%d len=%d cin=%#x\n", + umidi_tv.tv_sec%100, umidi_tv.tv_usec, + ep, out_jack->cable_number, len, cin)); + + s = splusb(); + packet = *ep->next_slot++; + KASSERT(ep->buffer_size >= + (ep->next_slot - ep->buffer) * sizeof *ep->buffer); + memset(packet, 0, UMIDI_PACKET_SIZE); + if (UMQ_ISTYPE(sc, UMQ_TYPE_MIDIMAN_GARBLE)) { + if (NULL != out_jack->midiman_ppkt) { /* fill out a prev pkt */ + poff = 0x0f & (out_jack->midiman_ppkt[3]); + plen = 3 - poff; + if (plen > len) + plen = len; + memcpy(out_jack->midiman_ppkt+poff, src, plen); + src += plen; + len -= plen; + plen += poff; + out_jack->midiman_ppkt[3] = + MIX_CN_CIN(out_jack->cable_number, plen); + DPR_PACKET(out+, sc, out_jack->midiman_ppkt); + if (3 == plen) + out_jack->midiman_ppkt = NULL; /* no more */ + } + if (0 == len) + ep->next_slot--; /* won't be needed, nevermind */ + else { + memcpy(packet, src, len); + packet[3] = MIX_CN_CIN(out_jack->cable_number, len); + DPR_PACKET(out, sc, packet); + if (len < 3) + out_jack->midiman_ppkt = packet; + } + } else { /* the nice simple USB class-compliant case */ + packet[0] = MIX_CN_CIN(out_jack->cable_number, cin); + memcpy(packet+1, src, len); + DPR_PACKET(out, sc, packet); + } + ep->next_schedule |= 1<<(out_jack->cable_number); + ++ ep->num_scheduled; + if ( !ep->armed && !ep->soliciting ) { +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + /* + * It would be bad to call out_solicit directly here (the + * caller need not be reentrant) but a soft interrupt allows + * solicit to run immediately the caller exits its critical + * section, and if the caller has more to write we can get it + * before starting the USB transfer, and send a longer one. + */ + ep->soliciting = 1; + softintr_schedule(ep->solicit_cookie); +#else + /* + * This alternative is a little less desirable, because if the + * writer has several messages to go at once, the first will go + * in a USB frame all to itself, and the rest in a full-size + * transfer one frame later (solicited on the first frame's + * completion interrupt). But it's simple. + */ + ep->armed = (USBD_IN_PROGRESS == start_output_transfer(ep)); +#endif + } + splx(s); + + return 0; } static void -in_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +in_intr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) { int cn, len, i; struct umidi_endpoint *ep = (struct umidi_endpoint *)priv; struct umidi_jack *jack; + unsigned char *packet; + umidi_packet_bufp slot; + umidi_packet_bufp end; + unsigned char *data; + u_int32_t count; if (ep->sc->sc_dying || !ep->num_open) return; - cn = GET_CN(ep->buffer[0]); - len = packet_length[GET_CIN(ep->buffer[0])]; - jack = ep->jacks[cn]; - if (cn>=ep->num_jacks || !jack) { - DPRINTF(("%s: stray umidi packet (in): %02X %02X %02X %02X\n", - USBDEVNAME(ep->sc->sc_dev), - (unsigned)ep->buffer[0], - (unsigned)ep->buffer[1], - (unsigned)ep->buffer[2], - (unsigned)ep->buffer[3])); - return; - } - if (!jack->binded || !jack->opened) - return; - DPR_PACKET(in, ep->sc, &jack->packet); - if (jack->u.in.intr) { - for (i=0; iu.in.intr)(jack->arg, ep->buffer[i+1]); + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + if ( 0 == count % UMIDI_PACKET_SIZE ) { + DPRINTFN(200,("%s: input endpoint %p transfer length %u\n", + USBDEVNAME(ep->sc->sc_dev), ep, count)); + } else { + DPRINTF(("%s: input endpoint %p odd transfer length %u\n", + USBDEVNAME(ep->sc->sc_dev), ep, count)); + } + + slot = ep->buffer; + end = slot + count / sizeof *slot; + + for ( packet = *slot; slot < end; packet = *++slot ) { + + if ( UMQ_ISTYPE(ep->sc, UMQ_TYPE_MIDIMAN_GARBLE) ) { + cn = (0xf0&(packet[3]))>>4; + len = 0x0f&(packet[3]); + data = packet; + } else { + cn = GET_CN(packet[0]); + len = packet_length[GET_CIN(packet[0])]; + data = packet + 1; + } + /* 0 <= cn <= 15 by inspection of above code */ + if (!(jack = ep->jacks[cn]) || cn != jack->cable_number) { + DPRINTF(("%s: stray input endpoint %p cable %d len %d: " + "%02X %02X %02X (try CN_SEQ quirk?)\n", + USBDEVNAME(ep->sc->sc_dev), ep, cn, len, + (unsigned)data[0], + (unsigned)data[1], + (unsigned)data[2])); + return; + } + + if (!jack->binded || !jack->opened) + continue; + + DPRINTFN(500,("%s: input endpoint %p cable %d len %d: " + "%02X %02X %02X\n", + USBDEVNAME(ep->sc->sc_dev), ep, cn, len, + (unsigned)data[0], + (unsigned)data[1], + (unsigned)data[2])); + + if (jack->u.in.intr) { + for (i=0; iu.in.intr)(jack->arg, data[i]); + } } + } (void)start_input_transfer(ep); } static void -out_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +out_intr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) { struct umidi_endpoint *ep = (struct umidi_endpoint *)priv; struct umidi_softc *sc = ep->sc; - struct umidi_jack *jack; + u_int32_t count; - if (sc->sc_dying || !ep->num_open) + if (sc->sc_dying) return; - jack = LIST_FIRST(&ep->queue_head); - if (jack && jack->opened) { - LIST_REMOVE(jack, u.out.queue_entry); - if (!LIST_EMPTY(&ep->queue_head)) { - memcpy(ep->buffer, - LIST_FIRST(&ep->queue_head)->packet.buffer, - UMIDI_PACKET_SIZE); - (void)start_output_transfer(ep); - } - if (jack->u.out.intr) { - (*jack->u.out.intr)(jack->arg); - } +#ifdef UMIDI_DEBUG + if ( umididebug >= 200 ) + microtime(&umidi_tv); +#endif + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + if ( 0 == count % UMIDI_PACKET_SIZE ) { + DPRINTFN(200,("%s: %lu.%06lus out ep %p xfer length %u\n", + USBDEVNAME(ep->sc->sc_dev), + umidi_tv.tv_sec%100, umidi_tv.tv_usec, ep, count)); + } else { + DPRINTF(("%s: output endpoint %p odd transfer length %u\n", + USBDEVNAME(ep->sc->sc_dev), ep, count)); + } + count /= UMIDI_PACKET_SIZE; + + /* + * If while the transfer was pending we buffered any new messages, + * move them to the start of the buffer. + */ + ep->next_slot -= count; + if ( ep->buffer < ep->next_slot ) { + memcpy(ep->buffer, ep->buffer + count, + (char *)ep->next_slot - (char *)ep->buffer); + } + wakeup(ep); + /* + * Do not want anyone else to see armed <- 0 before soliciting <- 1. + * Running at splusb so the following should happen to be safe. + */ + ep->armed = 0; + if ( !ep->soliciting ) { + ep->soliciting = 1; + out_solicit(ep); } } +/* + * A jack on which we have received a packet must be called back on its + * out.intr handler before it will send us another; it is considered + * 'scheduled'. It is nice and predictable - as long as it is scheduled, + * we need no extra buffer space for it. + * + * In contrast, a jack that is open but not scheduled may supply us a packet + * at any time, driven by the top half, and we must be able to accept it, no + * excuses. So we must ensure that at any point in time there are at least + * (num_open - num_scheduled) slots free. + * + * As long as there are more slots free than that minimum, we can loop calling + * scheduled jacks back on their "interrupt" handlers, soliciting more + * packets, starting the USB transfer only when the buffer space is down to + * the minimum or no jack has any more to send. + */ static void -out_build_packet(int cable_number, struct umidi_packet *packet, uByte in) +out_solicit(void *arg) { - int cin; - uByte prev; + struct umidi_endpoint *ep = arg; + int s; + umidi_packet_bufp end; + u_int16_t which; + struct umidi_jack *jack; + + end = ep->buffer + ep->buffer_size / sizeof *ep->buffer; + + for ( ;; ) { + s = splusb(); + if ( end - ep->next_slot <= ep->num_open - ep->num_scheduled ) + break; /* at splusb */ + if ( ep->this_schedule == 0 ) { + if ( ep->next_schedule == 0 ) + break; /* at splusb */ + ep->this_schedule = ep->next_schedule; + ep->next_schedule = 0; + } + /* + * At least one jack is scheduled. Find and mask off the least + * set bit in this_schedule and decrement num_scheduled. + * Convert mask to bit index to find the corresponding jack, + * and call its intr handler. If it has a message, it will call + * back one of the output methods, which will set its bit in + * next_schedule (not copied into this_schedule until the + * latter is empty). In this way we round-robin the jacks that + * have messages to send, until the buffer is as full as we + * dare, and then start a transfer. + */ + which = ep->this_schedule; + which &= (~which)+1; /* now mask of least set bit */ + ep->this_schedule &= ~which; + -- ep->num_scheduled; + splx(s); -retry: - switch (packet->state) { - case PS_END: - case PS_INITIAL: - prev = packet->buffer[1]; - memset(packet->buffer, 0, UMIDI_PACKET_SIZE); - if (in<0x80) { - if (prev>=0x80 && prev<0xf0) { - /* running status */ - out_build_packet(cable_number, packet, prev); - goto retry; - } - /* ??? */ - break; - } - if (in>=0xf0) { - cin=packet_0xFX[in&0x0F].cin; - packet->state=packet_0xFX[in&0x0F].next; - } else { - cin=(unsigned char)in>>4; - switch (packet_length[cin]) { - case 2: - packet->state = PS_NORMAL_1OF2; - break; - case 3: - packet->state = PS_NORMAL_1OF3; - break; - default: - /* ??? */ - packet->state = PS_INITIAL; - } - } - packet->buffer[0] = MIX_CN_CIN(cable_number, cin); - packet->buffer[1] = in; - break; - case PS_NORMAL_1OF3: - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[2] = in; - packet->state = PS_NORMAL_2OF3; - break; - case PS_NORMAL_2OF3: - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[3] = in; - packet->state = PS_END; - break; - case PS_NORMAL_1OF2: - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[2] = in; - packet->state = PS_END; - break; - case PS_EXCL_0: - memset(packet->buffer, 0, UMIDI_PACKET_SIZE); - if (in==0xF7) { - packet->buffer[0] = MIX_CN_CIN(cable_number, 0x05); - packet->buffer[1] = 0xF7; - packet->state = PS_END; - break; - } - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[1] = in; - packet->state = PS_EXCL_1; - break; - case PS_EXCL_1: - if (in==0xF7) { - packet->buffer[0] = MIX_CN_CIN(cable_number, 0x06); - packet->buffer[2] = 0xF7; - packet->state = PS_END; - break; - } - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[2] = in; - packet->state = PS_EXCL_2; - break; - case PS_EXCL_2: - if (in==0xF7) { - packet->buffer[0] = MIX_CN_CIN(cable_number, 0x07); - packet->buffer[3] = 0xF7; - packet->state = PS_END; - break; - } - if (in>=0x80) { /* ??? */ packet->state = PS_END; break; } - packet->buffer[0] = MIX_CN_CIN(cable_number, 0x04); - packet->buffer[3] = in; - packet->state = PS_EXCL_0; - break; - default: - printf("umidi: ambiguous state.\n"); - packet->state = PS_INITIAL; - goto retry; + -- which; /* now 1s below mask - count 1s to get index */ + which -= ((which >> 1) & 0x5555);/* SWAR credit aggregate.org */ + which = (((which >> 2) & 0x3333) + (which & 0x3333)); + which = (((which >> 4) + which) & 0x0f0f); + which += (which >> 8); + which &= 0x1f; /* the bit index a/k/a jack number */ + + jack = ep->jacks[which]; + if (jack->u.out.intr) + (*jack->u.out.intr)(jack->arg); } + /* splusb at loop exit */ + if ( !ep->armed && ep->next_slot > ep->buffer ) + ep->armed = (USBD_IN_PROGRESS == start_output_transfer(ep)); + ep->soliciting = 0; + splx(s); } -