root/sys/dev/usb/umidi.c
/*      $NetBSD: umidi.c,v 1.91 2024/02/10 09:21:53 andvar Exp $        */

/*
 * Copyright (c) 2001, 2012, 2014 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Takuya SHIOZAKI (tshiozak@NetBSD.org), (full-size transfers, extended
 * hw_if) Chapman Flack (chap@NetBSD.org), and Matthew R. Green
 * (mrg@eterna23.net).
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: umidi.c,v 1.91 2024/02/10 09:21:53 andvar Exp $");

#ifdef _KERNEL_OPT
#include "opt_usb.h"
#endif

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/select.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/poll.h>
#include <sys/intr.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>

#include <dev/usb/usbdevs.h>
#include <dev/usb/umidi_quirks.h>
#include <dev/midi_if.h>

/* Jack Descriptor */
#define UMIDI_MS_HEADER 0x01
#define UMIDI_IN_JACK   0x02
#define UMIDI_OUT_JACK  0x03

/* Jack Type */
#define UMIDI_EMBEDDED  0x01
#define UMIDI_EXTERNAL  0x02

/* generic, for iteration */
typedef struct {
        uByte           bLength;
        uByte           bDescriptorType;
        uByte           bDescriptorSubtype;
} UPACKED umidi_cs_descriptor_t;

typedef struct {
        uByte           bLength;
        uByte           bDescriptorType;
        uByte           bDescriptorSubtype;
        uWord           bcdMSC;
        uWord           wTotalLength;
} UPACKED umidi_cs_interface_descriptor_t;
#define UMIDI_CS_INTERFACE_DESCRIPTOR_SIZE 7

typedef struct {
        uByte           bLength;
        uByte           bDescriptorType;
        uByte           bDescriptorSubtype;
        uByte           bNumEmbMIDIJack;
} UPACKED umidi_cs_endpoint_descriptor_t;
#define UMIDI_CS_ENDPOINT_DESCRIPTOR_SIZE 4

typedef struct {
        uByte           bLength;
        uByte           bDescriptorType;
        uByte           bDescriptorSubtype;
        uByte           bJackType;
        uByte           bJackID;
} UPACKED umidi_jack_descriptor_t;
#define UMIDI_JACK_DESCRIPTOR_SIZE      5


#define TO_D(p) ((usb_descriptor_t *)(p))
#define NEXT_D(desc) TO_D((char *)(desc)+(desc)->bLength)
#define TO_IFD(desc) ((usb_interface_descriptor_t *)(desc))
#define TO_CSIFD(desc) ((umidi_cs_interface_descriptor_t *)(desc))
#define TO_EPD(desc) ((usb_endpoint_descriptor_t *)(desc))
#define TO_CSEPD(desc) ((umidi_cs_endpoint_descriptor_t *)(desc))


#define UMIDI_PACKET_SIZE 4

/*
 * hierarchy
 *
 * <-- parent          child -->
 *
 * umidi(sc) -> endpoint -> jack   <- (dynamically assignable) - mididev
 *         ^     |    ^     |
 *         +-----+    +-----+
 */

/* midi device */
struct umidi_mididev {
        struct umidi_softc      *sc;
        device_t                mdev;
        /* */
        struct umidi_jack       *in_jack;
        struct umidi_jack       *out_jack;
        char                    *label;
        size_t                  label_len;
        /* */
        int                     opened;
        int                     closing;
        int                     flags;
};

/* Jack Information */
struct umidi_jack {
        struct umidi_endpoint   *endpoint;
        /* */
        int                     cable_number;
        void                    *arg;
        int                     bound;
        int                     opened;
        unsigned char           *midiman_ppkt;
        union {
                struct {
                        void                    (*intr)(void *);
                } out;
                struct {
                        void                    (*intr)(void *, int);
                } in;
        } u;
};

#define UMIDI_MAX_EPJACKS       16
typedef unsigned char (*umidi_packet_bufp)[UMIDI_PACKET_SIZE];
/* endpoint data */
struct umidi_endpoint {
        struct umidi_softc      *sc;
        /* */
        int                     addr;
        struct usbd_pipe        *pipe;
        struct usbd_xfer        *xfer;
        umidi_packet_bufp       buffer;
        umidi_packet_bufp       next_slot;
        uint32_t               buffer_size;
        int                     num_scheduled;
        int                     num_open;
        int                     num_jacks;
        int                     soliciting;
        void                    *solicit_cookie;
        int                     armed;
        struct umidi_jack       *jacks[UMIDI_MAX_EPJACKS];
        uint16_t                this_schedule; /* see UMIDI_MAX_EPJACKS */
        uint16_t                next_schedule;
};

/* software context */
struct umidi_softc {
        device_t                sc_dev;
        struct usbd_device      *sc_udev;
        struct usbd_interface   *sc_iface;
        const struct umidi_quirk        *sc_quirk;

        int                     sc_dying;

        int                     sc_out_num_jacks;
        struct umidi_jack       *sc_out_jacks;
        int                     sc_in_num_jacks;
        struct umidi_jack       *sc_in_jacks;
        struct umidi_jack       *sc_jacks;

        int                     sc_num_mididevs;
        struct umidi_mididev    *sc_mididevs;

        int                     sc_out_num_endpoints;
        struct umidi_endpoint   *sc_out_ep;
        int                     sc_in_num_endpoints;
        struct umidi_endpoint   *sc_in_ep;
        struct umidi_endpoint   *sc_endpoints;
        size_t                  sc_endpoints_len;
        int                     cblnums_global;

        kmutex_t                sc_lock;
        kcondvar_t              sc_cv;
        kcondvar_t              sc_detach_cv;

        int                     sc_refcnt;
};

#ifdef UMIDI_DEBUG
#define DPRINTF(x)      if (umididebug) printf x
#define DPRINTFN(n,x)   if (umididebug >= (n)) printf x
#include <sys/time.h>
static struct timeval umidi_tv;
int     umididebug = 0;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

#define UMIDI_ENDPOINT_SIZE(sc) (sizeof(*(sc)->sc_out_ep) * \
                                 (sc->sc_out_num_endpoints + \
                                  sc->sc_in_num_endpoints))


static int umidi_open(void *, int,
                      void (*)(void *, int), void (*)(void *), void *);
static void umidi_close(void *);
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 void umidi_get_locks(void *, kmutex_t **, kmutex_t **);

static usbd_status alloc_pipe(struct umidi_endpoint *);
static void free_pipe(struct umidi_endpoint *);

static usbd_status alloc_all_endpoints(struct umidi_softc *);
static void free_all_endpoints(struct umidi_softc *);

static usbd_status alloc_all_jacks(struct umidi_softc *);
static void free_all_jacks(struct umidi_softc *);
static usbd_status bind_jacks_to_mididev(struct umidi_softc *,
                                         struct umidi_jack *,
                                         struct umidi_jack *,
                                         struct umidi_mididev *);
static void unbind_jacks_from_mididev(struct umidi_mididev *);
static void unbind_all_jacks(struct umidi_softc *);
static usbd_status assign_all_jacks_automatically(struct umidi_softc *);
static usbd_status open_out_jack(struct umidi_jack *, void *,
                                 void (*)(void *));
static usbd_status open_in_jack(struct umidi_jack *, void *,
                                void (*)(void *, int));
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 detach_mididev(struct umidi_mididev *, int);
static void deactivate_mididev(struct umidi_mididev *);
static usbd_status alloc_all_mididevs(struct umidi_softc *, int);
static void free_all_mididevs(struct umidi_softc *);
static usbd_status attach_all_mididevs(struct umidi_softc *);
static usbd_status detach_all_mididevs(struct umidi_softc *, int);
static void deactivate_all_mididevs(struct umidi_softc *);
static void describe_mididev(struct umidi_mididev *);

#ifdef UMIDI_DEBUG
static void dump_sc(struct umidi_softc *);
static void dump_ep(struct umidi_endpoint *);
static void dump_jack(struct umidi_jack *);
#endif

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 *, u_char *, int, int);
static void in_intr(struct usbd_xfer *, void *, usbd_status);
static void out_intr(struct usbd_xfer *, void *, usbd_status);
static void out_solicit(void *); /* struct umidi_endpoint* for softintr */
static void out_solicit_locked(void *); /* pre-locked version */


const struct midi_hw_if umidi_hw_if = {
        .open = umidi_open,
        .close = umidi_close,
        .output = umidi_rtmsg,
        .getinfo = umidi_getinfo,
        .get_locks = umidi_get_locks,
};

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,
};

static int umidi_match(device_t, cfdata_t, void *);
static void umidi_attach(device_t, device_t, void *);
static void umidi_childdet(device_t, device_t);
static int umidi_detach(device_t, int);
static int umidi_activate(device_t, enum devact);

CFATTACH_DECL2_NEW(umidi, sizeof(struct umidi_softc), umidi_match,
    umidi_attach, umidi_detach, umidi_activate, NULL, umidi_childdet);

static int
umidi_match(device_t parent, cfdata_t match, void *aux)
{
        struct usbif_attach_arg *uiaa = aux;

        DPRINTFN(1,("umidi_match\n"));

        if (umidi_search_quirk(uiaa->uiaa_vendor, uiaa->uiaa_product,
            uiaa->uiaa_ifaceno))
                return UMATCH_IFACECLASS_IFACESUBCLASS;

        if (uiaa->uiaa_class == UICLASS_AUDIO &&
            uiaa->uiaa_subclass == UISUBCLASS_MIDISTREAM)
                return UMATCH_IFACECLASS_IFACESUBCLASS;

        return UMATCH_NONE;
}

static void
umidi_attach(device_t parent, device_t self, void *aux)
{
        usbd_status     err;
        struct umidi_softc *sc = device_private(self);
        struct usbif_attach_arg *uiaa = aux;
        char *devinfop;

        DPRINTFN(1,("umidi_attach\n"));

        sc->sc_dev = self;

        aprint_naive("\n");
        aprint_normal("\n");

        devinfop = usbd_devinfo_alloc(uiaa->uiaa_device, 0);
        aprint_normal_dev(self, "%s\n", devinfop);
        usbd_devinfo_free(devinfop);

        sc->sc_iface = uiaa->uiaa_iface;
        sc->sc_udev = uiaa->uiaa_device;

        sc->sc_quirk = umidi_search_quirk(uiaa->uiaa_vendor,
            uiaa->uiaa_product, uiaa->uiaa_ifaceno);

        aprint_normal_dev(self, "");
        umidi_print_quirk(sc->sc_quirk);

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
        cv_init(&sc->sc_cv, "umidopcl");
        cv_init(&sc->sc_detach_cv, "umidetcv");
        sc->sc_refcnt = 0;

        err = alloc_all_endpoints(sc);
        if (err != USBD_NORMAL_COMPLETION) {
                aprint_error_dev(self,
                    "alloc_all_endpoints failed. (err=%d)\n", err);
                goto out;
        }
        err = alloc_all_jacks(sc);
        if (err != USBD_NORMAL_COMPLETION) {
                aprint_error_dev(self, "alloc_all_jacks failed. (err=%d)\n",
                    err);
                goto out_free_endpoints;
        }
        aprint_normal_dev(self, "out=%d, in=%d\n",
               sc->sc_out_num_jacks, sc->sc_in_num_jacks);

        err = assign_all_jacks_automatically(sc);
        if (err != USBD_NORMAL_COMPLETION) {
                aprint_error_dev(self,
                    "assign_all_jacks_automatically failed. (err=%d)\n", err);
                goto out_free_jacks;
        }
        err = attach_all_mididevs(sc);
        if (err != USBD_NORMAL_COMPLETION) {
                aprint_error_dev(self,
                    "attach_all_mididevs failed. (err=%d)\n", err);
                goto out_free_jacks;
        }

#ifdef UMIDI_DEBUG
        dump_sc(sc);
#endif

        usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev);

        return;

out_free_jacks:
        unbind_all_jacks(sc);
        free_all_jacks(sc);

out_free_endpoints:
        free_all_endpoints(sc);

out:
        aprint_error_dev(self, "disabled.\n");
        sc->sc_dying = 1;
        return;
}

static void
umidi_childdet(device_t self, device_t child)
{
        int i;
        struct umidi_softc *sc = device_private(self);

        KASSERT(sc->sc_mididevs != NULL);

        for (i = 0; i < sc->sc_num_mididevs; i++) {
                if (sc->sc_mididevs[i].mdev == child)
                        break;
        }
        KASSERT(i < sc->sc_num_mididevs);
        sc->sc_mididevs[i].mdev = NULL;
}

static int
umidi_activate(device_t self, enum devact act)
{
        struct umidi_softc *sc = device_private(self);

        switch (act) {
        case DVACT_DEACTIVATE:
                DPRINTFN(1,("umidi_activate (deactivate)\n"));
                sc->sc_dying = 1;
                deactivate_all_mididevs(sc);
                return 0;
        default:
                DPRINTFN(1,("umidi_activate (%d)\n", act));
                return EOPNOTSUPP;
        }
}

static int
umidi_detach(device_t self, int flags)
{
        struct umidi_softc *sc = device_private(self);

        DPRINTFN(1,("umidi_detach\n"));

        mutex_enter(&sc->sc_lock);
        sc->sc_dying = 1;
        if (--sc->sc_refcnt >= 0)
                if (cv_timedwait(&sc->sc_detach_cv, &sc->sc_lock, hz * 60))
                        aprint_error_dev(self, ": didn't detach\n");
        mutex_exit(&sc->sc_lock);

        detach_all_mididevs(sc, flags);
        free_all_mididevs(sc);
        free_all_jacks(sc);
        free_all_endpoints(sc);

        usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev);

        mutex_destroy(&sc->sc_lock);
        cv_destroy(&sc->sc_detach_cv);
        cv_destroy(&sc->sc_cv);

        return 0;
}


/*
 * midi_if stuffs
 */
int
umidi_open(void *addr,
           int flags,
           void (*iintr)(void *, int),
           void (*ointr)(void *),
           void *arg)
{
        struct umidi_mididev *mididev = addr;
        struct umidi_softc *sc = mididev->sc;
        usbd_status err;

        KASSERT(mutex_owned(&sc->sc_lock));
        DPRINTF(("umidi_open: sc=%p\n", sc));

        if (mididev->opened)
                return EBUSY;
        if (sc->sc_dying)
                return EIO;

        mididev->opened = 1;
        mididev->flags = flags;
        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) {
                err = open_in_jack(mididev->in_jack, arg, iintr);
                KASSERT(mididev->opened);
                if (err != USBD_NORMAL_COMPLETION &&
                    err != USBD_IN_PROGRESS) {
                        if (mididev->out_jack)
                                close_out_jack(mididev->out_jack);
                        goto bad;
                }
        }

        return 0;
bad:
        mididev->opened = 0;
        DPRINTF(("umidi_open: usbd_status %d\n", err));
        KASSERT(mutex_owned(&sc->sc_lock));
        return USBD_IN_USE == err ? EBUSY : EIO;
}

void
umidi_close(void *addr)
{
        struct umidi_mididev *mididev = addr;
        struct umidi_softc *sc = mididev->sc;

        KASSERT(mutex_owned(&sc->sc_lock));

        if (mididev->closing)
                return;

        mididev->closing = 1;

        sc->sc_refcnt++;

        if ((mididev->flags & FWRITE) && mididev->out_jack)
                close_out_jack(mididev->out_jack);
        if ((mididev->flags & FREAD) && mididev->in_jack)
                close_in_jack(mididev->in_jack);

        if (--sc->sc_refcnt < 0)
                cv_broadcast(&sc->sc_detach_cv);

        mididev->opened = 0;
        mididev->closing = 0;
}

int
umidi_channelmsg(void *addr, int status, int channel, u_char *msg,
    int len)
{
        struct umidi_mididev *mididev = addr;

        KASSERT(mutex_owned(&mididev->sc->sc_lock));

        if (!mididev->out_jack || !mididev->opened || mididev->closing)
                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;

        KASSERT(mutex_owned(&mididev->sc->sc_lock));

        if (!mididev->out_jack || !mididev->opened || mididev->closing)
                return EIO;

        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;

        KASSERT(mutex_owned(&mididev->sc->sc_lock));

        if (!mididev->out_jack || !mididev->opened || mididev->closing)
                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;

        KASSERT(mutex_owned(&mididev->sc->sc_lock));

        if (!mididev->out_jack || !mididev->opened || mididev->closing)
                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;
        int mm = UMQ_ISTYPE(sc, UMQ_TYPE_MIDIMAN_GARBLE);

        KASSERT(mutex_owned(&sc->sc_lock));

        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);
}

static void
umidi_get_locks(void *addr, kmutex_t **thread, kmutex_t **intr)
{
        struct umidi_mididev *mididev = addr;
        struct umidi_softc *sc = mididev->sc;

        *intr = NULL;
        *thread = &sc->sc_lock;
}

/*
 * each endpoint stuffs
 */

/* alloc/free pipe */
static usbd_status
alloc_pipe(struct umidi_endpoint *ep)
{
        struct umidi_softc *sc = ep->sc;
        usbd_status err;
        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_create_xfer 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",
                device_xname(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;
        err = usbd_open_pipe(sc->sc_iface, ep->addr, USBD_MPSAFE, &ep->pipe);
        if (err)
                goto quit;
        int error = usbd_create_xfer(ep->pipe, ep->buffer_size,
            0, 0, &ep->xfer);
        if (error) {
                usbd_close_pipe(ep->pipe);
                return USBD_NOMEM;
        }
        ep->buffer = usbd_get_buffer(ep->xfer);
        ep->next_slot = ep->buffer;
        ep->solicit_cookie = softint_establish(SOFTINT_CLOCK | SOFTINT_MPSAFE,
            out_solicit, ep);
quit:
        return err;
}

static void
free_pipe(struct umidi_endpoint *ep)
{
        DPRINTF(("%s: free_pipe %p\n", device_xname(ep->sc->sc_dev), ep));
        usbd_abort_pipe(ep->pipe);
        usbd_destroy_xfer(ep->xfer);
        usbd_close_pipe(ep->pipe);
        softint_disestablish(ep->solicit_cookie);
}


/* alloc/free the array of endpoint structures */

static usbd_status alloc_all_endpoints_fixed_ep(struct umidi_softc *);
static usbd_status alloc_all_endpoints_yamaha(struct umidi_softc *);
static usbd_status alloc_all_endpoints_genuine(struct umidi_softc *);

static usbd_status
alloc_all_endpoints(struct umidi_softc *sc)
{
        usbd_status err;
        int i, n;

        if (UMQ_ISTYPE(sc, UMQ_TYPE_FIXED_EP)) {
                err = alloc_all_endpoints_fixed_ep(sc);
        } else if (UMQ_ISTYPE(sc, UMQ_TYPE_YAMAHA)) {
                err = alloc_all_endpoints_yamaha(sc);
        } else {
                err = alloc_all_endpoints_genuine(sc);
        }
        if (err != USBD_NORMAL_COMPLETION)
                return err;

        n = sc->sc_out_num_endpoints + sc->sc_in_num_endpoints;
        for (i = 0; i < n; i++) {
                err = alloc_pipe(&sc->sc_endpoints[i]);
                if (err != USBD_NORMAL_COMPLETION) {
                        while (i --> 0)
                                free_pipe(&sc->sc_endpoints[i]);
                        kmem_free(sc->sc_endpoints, sc->sc_endpoints_len);
                        sc->sc_endpoints = sc->sc_out_ep = sc->sc_in_ep = NULL;
                        break;
                }
        }
        return err;
}

static void
free_all_endpoints(struct umidi_softc *sc)
{
        int i, n;

        if (sc->sc_endpoints == NULL) {
                /* nothing to free */
                return;
        }

        n = sc->sc_in_num_endpoints + sc->sc_out_num_endpoints;
        for (i = 0; i < n; i++)
                free_pipe(&sc->sc_endpoints[i]);
        kmem_free(sc->sc_endpoints, sc->sc_endpoints_len);
        sc->sc_endpoints = sc->sc_out_ep = sc->sc_in_ep = NULL;
}

static usbd_status
alloc_all_endpoints_fixed_ep(struct umidi_softc *sc)
{
        usbd_status err;
        const struct umq_fixed_ep_desc *fp;
        struct umidi_endpoint *ep;
        usb_endpoint_descriptor_t *epd;
        int i;

        fp = umidi_get_quirk_data_from_type(sc->sc_quirk,
                                            UMQ_TYPE_FIXED_EP);
        if (fp->num_in_ep == 0 && fp->num_out_ep == 0)
                return USBD_INVAL;
        sc->sc_out_num_jacks = 0;
        sc->sc_in_num_jacks = 0;
        sc->sc_out_num_endpoints = fp->num_out_ep;
        sc->sc_in_num_endpoints = fp->num_in_ep;
        sc->sc_endpoints_len = UMIDI_ENDPOINT_SIZE(sc);
        sc->sc_endpoints = kmem_zalloc(sc->sc_endpoints_len, KM_SLEEP);
        sc->sc_out_ep = sc->sc_out_num_endpoints ? sc->sc_endpoints : NULL;
        sc->sc_in_ep =
            sc->sc_in_num_endpoints ?
                sc->sc_endpoints+sc->sc_out_num_endpoints : NULL;

        ep = &sc->sc_out_ep[0];
        for (i = 0; i < sc->sc_out_num_endpoints; i++) {
                epd = usbd_interface2endpoint_descriptor(
                        sc->sc_iface,
                        fp->out_ep[i].ep);
                if (!epd) {
                        aprint_error_dev(sc->sc_dev,
                            "cannot get endpoint descriptor(out:%d)\n",
                             fp->out_ep[i].ep);
                        err = USBD_INVAL;
                        goto error;
                }
                if (UE_GET_XFERTYPE(epd->bmAttributes)!=UE_BULK ||
                    UE_GET_DIR(epd->bEndpointAddress)!=UE_DIR_OUT) {
                        aprint_error_dev(sc->sc_dev,
                            "illegal endpoint(out:%d)\n", fp->out_ep[i].ep);
                        err = USBD_INVAL;
                        goto error;
                }
                ep->sc = sc;
                ep->addr = epd->bEndpointAddress;
                ep->num_jacks = fp->out_ep[i].num_jacks;
                sc->sc_out_num_jacks += fp->out_ep[i].num_jacks;
                ep->num_open = 0;
                ep++;
        }
        ep = &sc->sc_in_ep[0];
        for (i = 0; i < sc->sc_in_num_endpoints; i++) {
                epd = usbd_interface2endpoint_descriptor(
                        sc->sc_iface,
                        fp->in_ep[i].ep);
                if (!epd) {
                        aprint_error_dev(sc->sc_dev,
                            "cannot get endpoint descriptor(in:%d)\n",
                             fp->in_ep[i].ep);
                        err = USBD_INVAL;
                        goto error;
                }
                /*
                 * 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:
                        aprint_error_dev(sc->sc_dev,
                            "illegal endpoint(in:%d)\n", 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;
                sc->sc_in_num_jacks += fp->in_ep[i].num_jacks;
                ep->num_open = 0;
                ep++;
        }

        return USBD_NORMAL_COMPLETION;
error:
        kmem_free(sc->sc_endpoints, UMIDI_ENDPOINT_SIZE(sc));
        sc->sc_endpoints = NULL;
        return err;
}

static usbd_status
alloc_all_endpoints_yamaha(struct umidi_softc *sc)
{
        /* This driver currently supports max 1in/1out bulk endpoints */
        char *end;
        usb_config_descriptor_t *cdesc;
        usb_descriptor_t *desc;
        umidi_cs_descriptor_t *csdesc;
        usb_interface_descriptor_t *idesc;
        umidi_cs_interface_descriptor_t *udesc;
        usb_endpoint_descriptor_t *epd;
        int out_addr, in_addr, i;
        int dir;

        sc->sc_out_num_jacks = sc->sc_in_num_jacks = 0;
        out_addr = in_addr = 0;

        /* detect endpoints */
        cdesc = usbd_get_config_descriptor(sc->sc_udev);
        end = (char *)cdesc + UGETW(cdesc->wTotalLength);
        idesc = usbd_get_interface_descriptor(sc->sc_iface);
        KASSERT((char *)cdesc <= (char *)idesc);
        KASSERT((char *)idesc < end);
        KASSERT(end - (char *)idesc >= sizeof(*idesc));
        KASSERT(idesc->bLength >= sizeof(*idesc));
        KASSERT(idesc->bLength <= end - (char *)idesc);
        for (i = idesc->bNumEndpoints; i --> 0;) {
                epd = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
                KASSERT(epd != NULL);
                if (UE_GET_XFERTYPE(epd->bmAttributes) == UE_BULK) {
                        dir = UE_GET_DIR(epd->bEndpointAddress);
                        if (dir == UE_DIR_OUT && !out_addr)
                                out_addr = epd->bEndpointAddress;
                        else if (dir == UE_DIR_IN && !in_addr)
                                in_addr = epd->bEndpointAddress;
                }
        }
        desc = NEXT_D(idesc);
        if ((char *)desc > end || end - (char *)desc < sizeof(*desc) ||
            desc->bLength < sizeof(*desc) ||
            desc->bLength > end - (char *)desc)
                return USBD_INVAL;

        /* count jacks */
        if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
            desc->bLength < sizeof(*csdesc))
                return USBD_INVAL;
        csdesc = (umidi_cs_descriptor_t *)desc;
        if (csdesc->bDescriptorSubtype != UMIDI_MS_HEADER)
                return USBD_INVAL;
        udesc = TO_CSIFD(csdesc);
        if (UGETW(udesc->wTotalLength) > end - (char *)udesc)
                return USBD_INVAL;
        if (UGETW(udesc->wTotalLength) < udesc->bLength)
                return USBD_INVAL;
        end = (char *)udesc + UGETW(udesc->wTotalLength);
        desc = NEXT_D(udesc);

        for (; end - (char *)desc >= sizeof(*desc); desc = NEXT_D(desc)) {
                if (desc->bLength < sizeof(*desc) ||
                    desc->bLength > end - (char *)desc)
                        break;
                if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
                    desc->bLength < sizeof(*csdesc) ||
                    desc->bLength < UMIDI_JACK_DESCRIPTOR_SIZE)
                        continue;
                csdesc = (umidi_cs_descriptor_t *)desc;
                if (csdesc->bDescriptorSubtype == UMIDI_OUT_JACK)
                        sc->sc_out_num_jacks++;
                else if (csdesc->bDescriptorSubtype == UMIDI_IN_JACK)
                        sc->sc_in_num_jacks++;
        }

        /* validate some parameters */
        if (sc->sc_out_num_jacks > UMIDI_MAX_EPJACKS)
                sc->sc_out_num_jacks = UMIDI_MAX_EPJACKS;
        if (sc->sc_in_num_jacks > UMIDI_MAX_EPJACKS)
                sc->sc_in_num_jacks = UMIDI_MAX_EPJACKS;
        if (sc->sc_out_num_jacks && out_addr) {
                sc->sc_out_num_endpoints = 1;
        } else {
                sc->sc_out_num_endpoints = 0;
                sc->sc_out_num_jacks = 0;
        }
        if (sc->sc_in_num_jacks && in_addr) {
                sc->sc_in_num_endpoints = 1;
        } else {
                sc->sc_in_num_endpoints = 0;
                sc->sc_in_num_jacks = 0;
        }
        sc->sc_endpoints_len = UMIDI_ENDPOINT_SIZE(sc);
        if (sc->sc_endpoints_len == 0)
                return USBD_INVAL;
        sc->sc_endpoints = kmem_zalloc(sc->sc_endpoints_len, KM_SLEEP);
        if (sc->sc_out_num_endpoints) {
                sc->sc_out_ep = sc->sc_endpoints;
                sc->sc_out_ep->sc = sc;
                sc->sc_out_ep->addr = out_addr;
                sc->sc_out_ep->num_jacks = sc->sc_out_num_jacks;
                sc->sc_out_ep->num_open = 0;
        } else
                sc->sc_out_ep = NULL;

        if (sc->sc_in_num_endpoints) {
                sc->sc_in_ep = sc->sc_endpoints + sc->sc_out_num_endpoints;
                sc->sc_in_ep->sc = sc;
                sc->sc_in_ep->addr = in_addr;
                sc->sc_in_ep->num_jacks = sc->sc_in_num_jacks;
                sc->sc_in_ep->num_open = 0;
        } else
                sc->sc_in_ep = NULL;

        return USBD_NORMAL_COMPLETION;
}

static usbd_status
alloc_all_endpoints_genuine(struct umidi_softc *sc)
{
        usb_interface_descriptor_t *interface_desc;
        usb_config_descriptor_t *config_desc;
        usb_descriptor_t *desc;
        char *end;
        int num_ep;
        struct umidi_endpoint *p, *q, *lowest, *endep, tmpep;
        int epaddr;

        interface_desc = usbd_get_interface_descriptor(sc->sc_iface);
        num_ep = interface_desc->bNumEndpoints;
        if (num_ep == 0)
                return USBD_INVAL;
        sc->sc_endpoints_len = sizeof(struct umidi_endpoint) * num_ep;
        sc->sc_endpoints = p = kmem_zalloc(sc->sc_endpoints_len, KM_SLEEP);
        sc->sc_out_num_jacks = sc->sc_in_num_jacks = 0;
        sc->sc_out_num_endpoints = sc->sc_in_num_endpoints = 0;
        epaddr = -1;

        /* get the list of endpoints for midi stream */
        config_desc = usbd_get_config_descriptor(sc->sc_udev);
        end = (char *)config_desc + UGETW(config_desc->wTotalLength);
        desc = TO_D(config_desc);
        for (; end - (char *)desc >= sizeof(*desc); desc = NEXT_D(desc)) {
                if (desc->bLength < sizeof(*desc) ||
                    desc->bLength > end - (char *)desc)
                        break;
                if (desc->bDescriptorType == UDESC_ENDPOINT &&
                    desc->bLength >= sizeof(*TO_EPD(desc)) &&
                    UE_GET_XFERTYPE(TO_EPD(desc)->bmAttributes) == UE_BULK) {
                        epaddr = TO_EPD(desc)->bEndpointAddress;
                } else if (desc->bDescriptorType == UDESC_CS_ENDPOINT &&
                    desc->bLength >= sizeof(*TO_CSEPD(desc)) &&
                    epaddr != -1) {
                        if (num_ep > 0) {
                                num_ep--;
                                p->sc = sc;
                                p->addr = epaddr;
                                p->num_jacks = TO_CSEPD(desc)->bNumEmbMIDIJack;
                                if (UE_GET_DIR(epaddr) == UE_DIR_OUT) {
                                        sc->sc_out_num_endpoints++;
                                        sc->sc_out_num_jacks += p->num_jacks;
                                } else {
                                        sc->sc_in_num_endpoints++;
                                        sc->sc_in_num_jacks += p->num_jacks;
                                }
                                p++;
                        }
                } else
                        epaddr = -1;
        }

        /* sort endpoints */
        num_ep = sc->sc_out_num_endpoints + sc->sc_in_num_endpoints;
        p = sc->sc_endpoints;
        endep = p + num_ep;
        while (p<endep) {
                lowest = p;
                for (q=p+1; q<endep; q++) {
                        if ((UE_GET_DIR(lowest->addr)==UE_DIR_IN &&
                             UE_GET_DIR(q->addr)==UE_DIR_OUT) ||
                            ((UE_GET_DIR(lowest->addr)==
                              UE_GET_DIR(q->addr)) &&
                             (UE_GET_ADDR(lowest->addr)>
                              UE_GET_ADDR(q->addr))))
                                lowest = q;
                }
                if (lowest != p) {
                        memcpy((void *)&tmpep, (void *)p, sizeof(tmpep));
                        memcpy((void *)p, (void *)lowest, sizeof(tmpep));
                        memcpy((void *)lowest, (void *)&tmpep, sizeof(tmpep));
                }
                p->num_open = 0;
                p++;
        }

        sc->sc_out_ep = sc->sc_out_num_endpoints ? sc->sc_endpoints : NULL;
        sc->sc_in_ep =
            sc->sc_in_num_endpoints ?
                sc->sc_endpoints+sc->sc_out_num_endpoints : NULL;

        return USBD_NORMAL_COMPLETION;
}


/*
 * jack stuffs
 */

static usbd_status
alloc_all_jacks(struct umidi_softc *sc)
{
        int i, j;
        struct umidi_endpoint *ep;
        struct umidi_jack *jack;
        const 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 */
        if (sc->sc_in_num_jacks == 0 && sc->sc_out_num_jacks == 0)
                return USBD_INVAL;
        sc->sc_jacks = kmem_zalloc(sizeof(*sc->sc_out_jacks) *
            (sc->sc_in_num_jacks + sc->sc_out_num_jacks), KM_SLEEP);
        if (!sc->sc_jacks)
                return USBD_NOMEM;
        sc->sc_out_jacks =
            sc->sc_out_num_jacks ? sc->sc_jacks : NULL;
        sc->sc_in_jacks =
            sc->sc_in_num_jacks ? sc->sc_jacks+sc->sc_out_num_jacks : NULL;

        jack = &sc->sc_out_jacks[0];
        for (i = 0; i < sc->sc_out_num_jacks; i++) {
                jack->opened = 0;
                jack->bound = 0;
                jack->arg = NULL;
                jack->u.out.intr = NULL;
                jack->midiman_ppkt = NULL;
                if (sc->cblnums_global)
                        jack->cable_number = i;
                jack++;
        }
        jack = &sc->sc_in_jacks[0];
        for (i = 0; i < sc->sc_in_num_jacks; i++) {
                jack->opened = 0;
                jack->bound = 0;
                jack->arg = NULL;
                jack->u.in.intr = NULL;
                if (sc->cblnums_global)
                        jack->cable_number = i;
                jack++;
        }

        /* assign each jacks to each endpoints */
        jack = &sc->sc_out_jacks[0];
        ep = &sc->sc_out_ep[0];
        for (i = 0; i < sc->sc_out_num_endpoints; i++) {
                for (j = 0; j < ep->num_jacks; j++) {
                        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++;
                }
                ep++;
        }
        jack = &sc->sc_in_jacks[0];
        ep = &sc->sc_in_ep[0];
        for (i = 0; i < sc->sc_in_num_endpoints; i++) {
                for (j = 0; j < ep->num_jacks; j++) {
                        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++;
                }
                ep++;
        }

        return USBD_NORMAL_COMPLETION;
}

static void
free_all_jacks(struct umidi_softc *sc)
{
        struct umidi_jack *jacks;
        size_t len;

        mutex_enter(&sc->sc_lock);
        jacks = sc->sc_jacks;
        len = sizeof(*sc->sc_out_jacks) *
            (sc->sc_in_num_jacks + sc->sc_out_num_jacks);
        sc->sc_jacks = sc->sc_in_jacks = sc->sc_out_jacks = NULL;
        mutex_exit(&sc->sc_lock);

        if (jacks)
                kmem_free(jacks, len);
}

static usbd_status
bind_jacks_to_mididev(struct umidi_softc *sc,
                      struct umidi_jack *out_jack,
                      struct umidi_jack *in_jack,
                      struct umidi_mididev *mididev)
{
        if ((out_jack && out_jack->bound) || (in_jack && in_jack->bound))
                return USBD_IN_USE;
        if (mididev->out_jack || mididev->in_jack)
                return USBD_IN_USE;

        if (out_jack)
                out_jack->bound = 1;
        if (in_jack)
                in_jack->bound = 1;
        mididev->in_jack = in_jack;
        mididev->out_jack = out_jack;

        mididev->closing = 0;

        return USBD_NORMAL_COMPLETION;
}

static void
unbind_jacks_from_mididev(struct umidi_mididev *mididev)
{
        KASSERT(mutex_owned(&mididev->sc->sc_lock));

        mididev->closing = 1;

        if ((mididev->flags & FWRITE) && mididev->out_jack)
                close_out_jack(mididev->out_jack);
        if ((mididev->flags & FREAD) && mididev->in_jack)
                close_in_jack(mididev->in_jack);

        if (mididev->out_jack) {
                mididev->out_jack->bound = 0;
                mididev->out_jack = NULL;
        }
        if (mididev->in_jack) {
                mididev->in_jack->bound = 0;
                mididev->in_jack = NULL;
        }
}

static void
unbind_all_jacks(struct umidi_softc *sc)
{
        int i;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_mididevs)
                for (i = 0; i < sc->sc_num_mididevs; i++)
                        unbind_jacks_from_mididev(&sc->sc_mididevs[i]);
        mutex_exit(&sc->sc_lock);
}

static usbd_status
assign_all_jacks_automatically(struct umidi_softc *sc)
{
        usbd_status err;
        int i;
        struct umidi_jack *out, *in;
        const signed char *asg_spec;

        err =
            alloc_all_mididevs(sc,
                               uimax(sc->sc_out_num_jacks, sc->sc_in_num_jacks));
        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; i < sc->sc_num_mididevs; i++) {
                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 = (i<sc->sc_out_num_jacks) ? &sc->sc_out_jacks[i]
                                                       : NULL;
                        in = (i<sc->sc_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);
                        return err;
                }
        }

        return USBD_NORMAL_COMPLETION;
}

static usbd_status
open_out_jack(struct umidi_jack *jack, void *arg, void (*intr)(void *))
{
        struct umidi_endpoint *ep = jack->endpoint;
        struct umidi_softc *sc = ep->sc;
        umidi_packet_bufp end;
        int err;

        KASSERT(mutex_owned(&sc->sc_lock));

        if (jack->opened)
                return USBD_IN_USE;

        jack->arg = arg;
        jack->u.out.intr = intr;
        jack->midiman_ppkt = NULL;
        end = ep->buffer + ep->buffer_size / sizeof(*ep->buffer);
        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 = cv_timedwait_sig(&sc->sc_cv, &sc->sc_lock,
                     mstohz(10));
                if (err) {
                        ep->num_open--;
                        jack->opened = 0;
                        return USBD_IOERROR;
                }
        }

        return USBD_NORMAL_COMPLETION;
}

static usbd_status
open_in_jack(struct umidi_jack *jack, void *arg, void (*intr)(void *, int))
{
        usbd_status err = USBD_NORMAL_COMPLETION;
        struct umidi_endpoint *ep = jack->endpoint;

        KASSERT(mutex_owned(&ep->sc->sc_lock));

        if (jack->opened)
                return USBD_IN_USE;

        jack->arg = arg;
        jack->u.in.intr = intr;
        jack->opened = 1;
        if (ep->num_open++ == 0 && UE_GET_DIR(ep->addr)==UE_DIR_IN) {
                /*
                 * Can't hold the interrupt lock while calling into USB,
                 * but we can safely drop it here.
                 */
                mutex_exit(&ep->sc->sc_lock);
                err = start_input_transfer(ep);
                if (err != USBD_NORMAL_COMPLETION &&
                    err != USBD_IN_PROGRESS) {
                        ep->num_open--;
                }
                mutex_enter(&ep->sc->sc_lock);
        }

        return err;
}

static void
close_out_jack(struct umidi_jack *jack)
{
        struct umidi_endpoint *ep;
        struct umidi_softc *sc;
        uint16_t mask;
        int err;

        if (jack->opened) {
                ep = jack->endpoint;
                sc = ep->sc;

                KASSERT(mutex_owned(&sc->sc_lock));
                mask = 1 << (jack->cable_number);
                while (mask & (ep->this_schedule | ep->next_schedule)) {
                        err = cv_timedwait_sig(&sc->sc_cv, &sc->sc_lock,
                             mstohz(10));
                        if (err)
                                break;
                }
                /*
                 * We can re-enter this function from both close() and
                 * detach().  Make sure only one of them does this part.
                 */
                if (jack->opened) {
                        jack->opened = 0;
                        jack->endpoint->num_open--;
                        ep->this_schedule &= ~mask;
                        ep->next_schedule &= ~mask;
                }
        }
}

static void
close_in_jack(struct umidi_jack *jack)
{
        if (jack->opened) {
                struct umidi_softc *sc = jack->endpoint->sc;

                KASSERT(mutex_owned(&sc->sc_lock));

                jack->opened = 0;
                if (--jack->endpoint->num_open == 0) {
                        /*
                         * We have to drop the (interrupt) lock so that
                         * the USB thread lock can be safely taken by
                         * the abort operation.  This is safe as this
                         * either closing or dying will be set properly.
                         */
                        mutex_exit(&sc->sc_lock);
                        usbd_abort_pipe(jack->endpoint->pipe);
                        mutex_enter(&sc->sc_lock);
                }
        }
}

static usbd_status
attach_mididev(struct umidi_softc *sc, struct umidi_mididev *mididev)
{
        if (mididev->sc)
                return USBD_IN_USE;

        mididev->sc = sc;

        describe_mididev(mididev);

        mididev->mdev = midi_attach_mi(&umidi_hw_if, mididev, sc->sc_dev);

        return USBD_NORMAL_COMPLETION;
}

static usbd_status
detach_mididev(struct umidi_mididev *mididev, int flags)
{
        struct umidi_softc *sc = mididev->sc;

        if (!sc)
                return USBD_NO_ADDR;

        mutex_enter(&sc->sc_lock);
        if (mididev->opened) {
                umidi_close(mididev);
        }
        unbind_jacks_from_mididev(mididev);
        mutex_exit(&sc->sc_lock);

        if (mididev->mdev != NULL)
                config_detach(mididev->mdev, flags);

        if (NULL != mididev->label) {
                kmem_free(mididev->label, mididev->label_len);
                mididev->label = NULL;
        }

        mididev->sc = NULL;

        return USBD_NORMAL_COMPLETION;
}

static void
deactivate_mididev(struct umidi_mididev *mididev)
{
        if (mididev->out_jack)
                mididev->out_jack->bound = 0;
        if (mididev->in_jack)
                mididev->in_jack->bound = 0;
}

static usbd_status
alloc_all_mididevs(struct umidi_softc *sc, int nmidi)
{
        sc->sc_num_mididevs = nmidi;
        sc->sc_mididevs = kmem_zalloc(sizeof(*sc->sc_mididevs)*nmidi, KM_SLEEP);
        return USBD_NORMAL_COMPLETION;
}

static void
free_all_mididevs(struct umidi_softc *sc)
{
        struct umidi_mididev *mididevs;
        size_t len;

        mutex_enter(&sc->sc_lock);
        mididevs = sc->sc_mididevs;
        if (mididevs)
                  len = sizeof(*sc->sc_mididevs )* sc->sc_num_mididevs;
        sc->sc_mididevs = NULL;
        sc->sc_num_mididevs = 0;
        mutex_exit(&sc->sc_lock);

        if (mididevs)
                kmem_free(mididevs, len);
}

static usbd_status
attach_all_mididevs(struct umidi_softc *sc)
{
        usbd_status err;
        int i;

        if (sc->sc_mididevs)
                for (i = 0; i < sc->sc_num_mididevs; i++) {
                        err = attach_mididev(sc, &sc->sc_mididevs[i]);
                        if (err != USBD_NORMAL_COMPLETION)
                                return err;
                }

        return USBD_NORMAL_COMPLETION;
}

static usbd_status
detach_all_mididevs(struct umidi_softc *sc, int flags)
{
        usbd_status err;
        int i;

        if (sc->sc_mididevs)
                for (i = 0; i < sc->sc_num_mididevs; i++) {
                        err = detach_mididev(&sc->sc_mididevs[i], flags);
                        if (err != USBD_NORMAL_COMPLETION)
                                return err;
                }

        return USBD_NORMAL_COMPLETION;
}

static void
deactivate_all_mididevs(struct umidi_softc *sc)
{
        int i;

        if (sc->sc_mididevs) {
                for (i = 0; i < sc->sc_num_mididevs; i++)
                        deactivate_mididev(&sc->sc_mididevs[i]);
        }
}

/*
 * 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 explicitly 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.
 */
void
describe_mididev(struct umidi_mididev *md)
{
        char in_label[16];
        char out_label[16];
        const 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)
                in_label[0] = '\0';
        else if (show_ep_in)
                snprintf(in_label, sizeof(in_label), "<%d(%x) ",
                    md->in_jack->cable_number, md->in_jack->endpoint->addr);
        else
                snprintf(in_label, sizeof(in_label), "<%d ",
                    md->in_jack->cable_number);

        if (NULL == md->out_jack)
                out_label[0] = '\0';
        else if (show_ep_out)
                snprintf(out_label, sizeof(out_label), ">%d(%x) ",
                    md->out_jack->cable_number, md->out_jack->endpoint->addr);
        else
                snprintf(out_label, sizeof(out_label), ">%d ",
                    md->out_jack->cable_number);

        unit_label = device_xname(sc->sc_dev);

        len = strlen(in_label) + strlen(out_label) + strlen(unit_label) + 4;

        final_label = kmem_alloc(len, KM_SLEEP);

        snprintf(final_label, len, "%s%son %s",
            in_label, out_label, unit_label);

        md->label = final_label;
        md->label_len = len;
}

#ifdef UMIDI_DEBUG
static void
dump_sc(struct umidi_softc *sc)
{
        int i;

        DPRINTFN(10, ("%s: dump_sc\n", device_xname(sc->sc_dev)));
        for (i=0; i<sc->sc_out_num_endpoints; i++) {
                DPRINTFN(10, ("\tout_ep(%p):\n", &sc->sc_out_ep[i]));
                dump_ep(&sc->sc_out_ep[i]);
        }
        for (i=0; i<sc->sc_in_num_endpoints; i++) {
                DPRINTFN(10, ("\tin_ep(%p):\n", &sc->sc_in_ep[i]));
                dump_ep(&sc->sc_in_ep[i]);
        }
}

static void
dump_ep(struct umidi_endpoint *ep)
{
        int i;
        for (i=0; i<UMIDI_MAX_EPJACKS; i++) {
                if (NULL==ep->jacks[i])
                        continue;
                DPRINTFN(10, ("\t\tjack[%d]:%p:\n", i, ep->jacks[i]));
                dump_jack(ep->jacks[i]);
        }
}
static void
dump_jack(struct umidi_jack *jack)
{
        DPRINTFN(10, ("\t\t\tep=%p\n",
                      jack->endpoint));
}

#endif /* UMIDI_DEBUG */



/*
 * MUX MIDI PACKET
 */

static const int packet_length[16] = {
        /*0*/   -1,
        /*1*/   -1,
        /*2*/   2,
        /*3*/   3,
        /*4*/   3,
        /*5*/   1,
        /*6*/   2,
        /*7*/   3,
        /*8*/   3,
        /*9*/   3,
        /*A*/   3,
        /*B*/   3,
        /*C*/   2,
        /*D*/   2,
        /*E*/   3,
        /*F*/   1,
};

#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 usbd_status
start_input_transfer(struct umidi_endpoint *ep)
{
        usbd_setup_xfer(ep->xfer, ep, ep->buffer, ep->buffer_size,
            USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, in_intr);
        return usbd_transfer(ep->xfer);
}

static usbd_status
start_output_transfer(struct umidi_endpoint *ep)
{
        usbd_status rv;
        uint32_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, ep->buffer, length, 0,
            USBD_NO_TIMEOUT, out_intr);
        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; i<UMIDI_MAX_EPJACKS; ++i)
                        if (NULL != ep->jacks[i])
                                ep->jacks[i]->midiman_ppkt = NULL;
        }

        return rv;
}

#ifdef UMIDI_DEBUG
#define DPR_PACKET(dir, sc, p)                                          \
if ((unsigned char)(p)[1]!=0xFE)                                \
        DPRINTFN(500,                                                   \
                 ("%s: umidi packet(" #dir "): %02X %02X %02X %02X\n",  \
                  device_xname(sc->sc_dev),                             \
                  (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, u_char *src, int len, int cin)
{
        struct umidi_endpoint *ep = out_jack->endpoint;
        struct umidi_softc *sc = ep->sc;
        unsigned char *packet;
        int plen;
        int poff;

        KASSERT(mutex_owned(&sc->sc_lock));

        if (sc->sc_dying)
                return EIO;

        if (!out_jack->opened)
                return ENODEV; /* XXX as it was, is this the right errno? */

        sc->sc_refcnt++;

#ifdef UMIDI_DEBUG
        if (umididebug >= 100)
                microtime(&umidi_tv);
#endif
        DPRINTFN(100, ("umidi out: %"PRIu64".%06"PRIu64
            "s ep=%p cn=%d len=%d cin=%#x\n", umidi_tv.tv_sec%100,
            (uint64_t)umidi_tv.tv_usec, ep, out_jack->cable_number, len, cin));

        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) {
                /*
                 * 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;
                kpreempt_disable();
                softint_schedule(ep->solicit_cookie);
                kpreempt_enable();
        }

        if (--sc->sc_refcnt < 0)
                cv_broadcast(&sc->sc_detach_cv);

        return 0;
}

static void
in_intr(struct usbd_xfer *xfer, void *priv,
    usbd_status status)
{
        int cn, len, i;
        struct umidi_endpoint *ep = (struct umidi_endpoint *)priv;
        struct umidi_softc *sc = ep->sc;
        struct umidi_jack *jack;
        unsigned char *packet;
        umidi_packet_bufp slot;
        umidi_packet_bufp end;
        unsigned char *data;
        uint32_t count;

        if (ep->sc->sc_dying || !ep->num_open)
                return;

        mutex_enter(&sc->sc_lock);
        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",
                             device_xname(ep->sc->sc_dev), ep, count));
        } else {
                DPRINTF(("%s: input endpoint %p odd transfer length %u\n",
                        device_xname(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",
                                 device_xname(ep->sc->sc_dev), ep, cn, len,
                                 (unsigned)data[0],
                                 (unsigned)data[1],
                                 (unsigned)data[2]));
                        mutex_exit(&sc->sc_lock);
                        return;
                }

                if (!jack->bound || !jack->opened)
                        continue;

                DPRINTFN(500,("%s: input endpoint %p cable %d len %d: "
                             "%02X %02X %02X\n",
                             device_xname(ep->sc->sc_dev), ep, cn, len,
                             (unsigned)data[0],
                             (unsigned)data[1],
                             (unsigned)data[2]));

                if (jack->u.in.intr) {
                        for (i = 0; i < len; i++) {
                                (*jack->u.in.intr)(jack->arg, data[i]);
                        }
                }

        }

        (void)start_input_transfer(ep);
        mutex_exit(&sc->sc_lock);
}

static void
out_intr(struct usbd_xfer *xfer, void *priv,
    usbd_status status)
{
        struct umidi_endpoint *ep = (struct umidi_endpoint *)priv;
        struct umidi_softc *sc = ep->sc;
        uint32_t count;

        if (sc->sc_dying)
                return;

        mutex_enter(&sc->sc_lock);
#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: %"PRIu64".%06"PRIu64"s out ep %p xfer "
                    "length %u\n", device_xname(ep->sc->sc_dev),
                    umidi_tv.tv_sec%100, (uint64_t)umidi_tv.tv_usec, ep,
                    count));
        } else {
                DPRINTF(("%s: output endpoint %p odd transfer length %u\n",
                        device_xname(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);
        }
        cv_broadcast(&sc->sc_cv);
        /*
         * Do not want anyone else to see armed <- 0 before soliciting <- 1.
         * Running at IPL_USB so the following should happen to be safe.
         */
        ep->armed = 0;
        if (!ep->soliciting) {
                ep->soliciting = 1;
                out_solicit_locked(ep);
        }
        mutex_exit(&sc->sc_lock);
}

/*
 * 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_solicit_locked(void *arg)
{
        struct umidi_endpoint *ep = arg;
        umidi_packet_bufp end;
        uint16_t which;
        struct umidi_jack *jack;

        KASSERT(mutex_owned(&ep->sc->sc_lock));

        end = ep->buffer + ep->buffer_size / sizeof(*ep->buffer);

        for ( ;; ) {
                if (end - ep->next_slot <= ep->num_open - ep->num_scheduled)
                        break; /* at IPL_USB */
                if (ep->this_schedule == 0) {
                        if (ep->next_schedule == 0)
                                break; /* at IPL_USB */
                        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;

                --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);
        }
        /* intr lock held at loop exit */
        if (!ep->armed && ep->next_slot > ep->buffer) {
                /*
                 * Can't hold the interrupt lock while calling into USB,
                 * but we can safely drop it here.
                 */
                mutex_exit(&ep->sc->sc_lock);
                ep->armed = (USBD_IN_PROGRESS == start_output_transfer(ep));
                mutex_enter(&ep->sc->sc_lock);
        }
        ep->soliciting = 0;
}

/* Entry point for the softintr.  */
static void
out_solicit(void *arg)
{
        struct umidi_endpoint *ep = arg;
        struct umidi_softc *sc = ep->sc;

        mutex_enter(&sc->sc_lock);
        out_solicit_locked(arg);
        mutex_exit(&sc->sc_lock);
}