--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MIDI 2.0 support
+ */
+
+#include <linux/bitops.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/midi.h>
+#include <linux/usb/midi-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/ump.h>
+#include "usbaudio.h"
+#include "midi.h"
+#include "midi2.h"
+#include "helper.h"
+
+static bool midi2_enable = true;
+module_param(midi2_enable, bool, 0444);
+MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support.");
+
+/* stream direction; just shorter names */
+enum {
+       STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT,
+       STR_IN = SNDRV_RAWMIDI_STREAM_INPUT
+};
+
+#define NUM_URBS       8
+
+struct snd_usb_midi2_urb;
+struct snd_usb_midi2_endpoint;
+struct snd_usb_midi2_ump;
+struct snd_usb_midi2_interface;
+
+/* URB context */
+struct snd_usb_midi2_urb {
+       struct urb *urb;
+       struct snd_usb_midi2_endpoint *ep;
+       unsigned int index;             /* array index */
+};
+
+/* A USB MIDI input/output endpoint */
+struct snd_usb_midi2_endpoint {
+       struct usb_device *dev;
+       const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */
+       struct snd_usb_midi2_endpoint *pair;    /* bidirectional pair EP */
+       struct snd_usb_midi2_ump *rmidi;        /* assigned UMP EP */
+       int direction;                  /* direction (STR_IN/OUT) */
+       unsigned int endpoint;          /* EP number */
+       unsigned int pipe;              /* URB pipe */
+       unsigned int packets;           /* packet buffer size in bytes */
+       unsigned int interval;          /* interval for INT EP */
+       wait_queue_head_t wait;         /* URB waiter */
+       spinlock_t lock;                /* URB locking */
+       struct snd_rawmidi_substream *substream; /* NULL when closed */
+       unsigned int num_urbs;          /* number of allocated URBs */
+       unsigned long urb_free;         /* bitmap for free URBs */
+       unsigned long urb_free_mask;    /* bitmask for free URBs */
+       atomic_t running;               /* running status */
+       atomic_t suspended;             /* saved running status for suspend */
+       bool disconnected;              /* shadow of umidi->disconnected */
+       struct list_head list;          /* list to umidi->ep_list */
+       struct snd_usb_midi2_urb urbs[NUM_URBS];
+};
+
+/* A UMP endpoint - one or two USB MIDI endpoints are assigned */
+struct snd_usb_midi2_ump {
+       struct usb_device *dev;
+       struct snd_usb_midi2_interface *umidi;  /* reference to MIDI iface */
+       struct snd_ump_endpoint *ump;           /* assigned UMP EP object */
+       struct snd_usb_midi2_endpoint *eps[2];  /* USB MIDI endpoints */
+       int index;                              /* rawmidi device index */
+       unsigned char usb_block_id;             /* USB GTB id used for finding a pair */
+       struct list_head list;          /* list to umidi->rawmidi_list */
+};
+
+/* top-level instance per USB MIDI interface */
+struct snd_usb_midi2_interface {
+       struct snd_usb_audio *chip;     /* assigned USB-audio card */
+       struct usb_interface *iface;    /* assigned USB interface */
+       struct usb_host_interface *hostif;
+       const char *blk_descs;          /* group terminal block descriptors */
+       unsigned int blk_desc_size;     /* size of GTB descriptors */
+       bool disconnected;
+       struct list_head ep_list;       /* list of endpoints */
+       struct list_head rawmidi_list;  /* list of UMP rawmidis */
+       struct list_head list;          /* list to chip->midi_v2_list */
+};
+
+/* submit URBs as much as possible; used for both input and output */
+static void do_submit_urbs_locked(struct snd_usb_midi2_endpoint *ep,
+                                 int (*prepare)(struct snd_usb_midi2_endpoint *,
+                                                struct urb *))
+{
+       struct snd_usb_midi2_urb *ctx;
+       int index, err = 0;
+
+       if (ep->disconnected)
+               return;
+
+       while (ep->urb_free) {
+               index = find_first_bit(&ep->urb_free, ep->num_urbs);
+               if (index >= ep->num_urbs)
+                       return;
+               ctx = &ep->urbs[index];
+               err = prepare(ep, ctx->urb);
+               if (err < 0)
+                       return;
+               if (!ctx->urb->transfer_buffer_length)
+                       return;
+               ctx->urb->dev = ep->dev;
+               err = usb_submit_urb(ctx->urb, GFP_ATOMIC);
+               if (err < 0) {
+                       dev_dbg(&ep->dev->dev,
+                               "usb_submit_urb error %d\n", err);
+                       return;
+               }
+               clear_bit(index, &ep->urb_free);
+       }
+}
+
+/* prepare for output submission: copy from rawmidi buffer to urb packet */
+static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep,
+                             struct urb *urb)
+{
+       int count;
+
+       if (ep->substream)
+               count = snd_rawmidi_transmit(ep->substream,
+                                            urb->transfer_buffer,
+                                            ep->packets);
+       else
+               count = -ENODEV;
+       if (count < 0) {
+               dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count);
+               return count;
+       }
+       cpu_to_le32_array((u32 *)urb->transfer_buffer, count >> 2);
+       urb->transfer_buffer_length = count;
+       return 0;
+}
+
+static void submit_output_urbs_locked(struct snd_usb_midi2_endpoint *ep)
+{
+       do_submit_urbs_locked(ep, prepare_output_urb);
+}
+
+/* URB completion for output; re-filling and re-submit */
+static void output_urb_complete(struct urb *urb)
+{
+       struct snd_usb_midi2_urb *ctx = urb->context;
+       struct snd_usb_midi2_endpoint *ep = ctx->ep;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ep->lock, flags);
+       set_bit(ctx->index, &ep->urb_free);
+       if (urb->status >= 0 && atomic_read(&ep->running))
+               submit_output_urbs_locked(ep);
+       if (ep->urb_free == ep->urb_free_mask)
+               wake_up(&ep->wait);
+       spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* prepare for input submission: just set the buffer length */
+static int prepare_input_urb(struct snd_usb_midi2_endpoint *ep,
+                            struct urb *urb)
+{
+       urb->transfer_buffer_length = ep->packets;
+       return 0;
+}
+
+static void submit_input_urbs_locked(struct snd_usb_midi2_endpoint *ep)
+{
+       do_submit_urbs_locked(ep, prepare_input_urb);
+}
+
+/* URB completion for input; copy into rawmidi buffer and resubmit */
+static void input_urb_complete(struct urb *urb)
+{
+       struct snd_usb_midi2_urb *ctx = urb->context;
+       struct snd_usb_midi2_endpoint *ep = ctx->ep;
+       unsigned long flags;
+       int len;
+
+       spin_lock_irqsave(&ep->lock, flags);
+       if (ep->disconnected || urb->status < 0)
+               goto dequeue;
+       len = urb->actual_length;
+       len &= ~3; /* align UMP */
+       if (len > ep->packets)
+               len = ep->packets;
+       if (len > 0 && ep->substream) {
+               le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2);
+               snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len);
+       }
+ dequeue:
+       set_bit(ctx->index, &ep->urb_free);
+       submit_input_urbs_locked(ep);
+       if (ep->urb_free == ep->urb_free_mask)
+               wake_up(&ep->wait);
+       spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* URB submission helper; for both direction */
+static void submit_io_urbs(struct snd_usb_midi2_endpoint *ep)
+{
+       unsigned long flags;
+
+       if (!ep)
+               return;
+       spin_lock_irqsave(&ep->lock, flags);
+       if (ep->direction == STR_IN)
+               submit_input_urbs_locked(ep);
+       else
+               submit_output_urbs_locked(ep);
+       spin_unlock_irqrestore(&ep->lock, flags);
+}
+
+/* kill URBs for close, suspend and disconnect */
+static void kill_midi_urbs(struct snd_usb_midi2_endpoint *ep, bool suspending)
+{
+       int i;
+
+       if (!ep)
+               return;
+       if (suspending)
+               ep->suspended = ep->running;
+       atomic_set(&ep->running, 0);
+       for (i = 0; i < ep->num_urbs; i++) {
+               if (!ep->urbs[i].urb)
+                       break;
+               usb_kill_urb(ep->urbs[i].urb);
+       }
+}
+
+/* wait until all URBs get freed */
+static void drain_urb_queue(struct snd_usb_midi2_endpoint *ep)
+{
+       if (!ep)
+               return;
+       spin_lock_irq(&ep->lock);
+       atomic_set(&ep->running, 0);
+       wait_event_lock_irq_timeout(ep->wait,
+                                   ep->disconnected ||
+                                   ep->urb_free == ep->urb_free_mask,
+                                   ep->lock, msecs_to_jiffies(500));
+       spin_unlock_irq(&ep->lock);
+}
+
+/* release URBs for an EP */
+static void free_midi_urbs(struct snd_usb_midi2_endpoint *ep)
+{
+       struct snd_usb_midi2_urb *ctx;
+       int i;
+
+       if (!ep)
+               return;
+       for (i = 0; i < ep->num_urbs; ++i) {
+               ctx = &ep->urbs[i];
+               if (!ctx->urb)
+                       break;
+               usb_free_coherent(ep->dev, ep->packets,
+                                 ctx->urb->transfer_buffer,
+                                 ctx->urb->transfer_dma);
+               usb_free_urb(ctx->urb);
+               ctx->urb = NULL;
+       }
+       ep->num_urbs = 0;
+}
+
+/* allocate URBs for an EP */
+static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep)
+{
+       struct snd_usb_midi2_urb *ctx;
+       void (*comp)(struct urb *urb);
+       void *buffer;
+       int i, err;
+       int endpoint, len;
+
+       endpoint = ep->endpoint;
+       len = ep->packets;
+       if (ep->direction == STR_IN)
+               comp = input_urb_complete;
+       else
+               comp = output_urb_complete;
+
+       ep->num_urbs = 0;
+       ep->urb_free = ep->urb_free_mask = 0;
+       for (i = 0; i < NUM_URBS; i++) {
+               ctx = &ep->urbs[i];
+               ctx->index = i;
+               ctx->urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!ctx->urb) {
+                       dev_err(&ep->dev->dev, "URB alloc failed\n");
+                       return -ENOMEM;
+               }
+               ctx->ep = ep;
+               buffer = usb_alloc_coherent(ep->dev, len, GFP_KERNEL,
+                                           &ctx->urb->transfer_dma);
+               if (!buffer) {
+                       dev_err(&ep->dev->dev,
+                               "URB buffer alloc failed (size %d)\n", len);
+                       return -ENOMEM;
+               }
+               if (ep->interval)
+                       usb_fill_int_urb(ctx->urb, ep->dev, ep->pipe,
+                                        buffer, len, comp, ctx, ep->interval);
+               else
+                       usb_fill_bulk_urb(ctx->urb, ep->dev, ep->pipe,
+                                         buffer, len, comp, ctx);
+               err = usb_urb_ep_type_check(ctx->urb);
+               if (err < 0) {
+                       dev_err(&ep->dev->dev, "invalid MIDI EP %x\n",
+                               endpoint);
+                       return err;
+               }
+               ctx->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+               ep->num_urbs++;
+       }
+       ep->urb_free = ep->urb_free_mask = GENMASK(ep->num_urbs - 1, 0);
+       return 0;
+}
+
+static struct snd_usb_midi2_endpoint *
+substream_to_endpoint(struct snd_rawmidi_substream *substream)
+{
+       struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+       struct snd_usb_midi2_ump *rmidi = ump->private_data;
+
+       return rmidi->eps[substream->stream];
+}
+
+/* rawmidi open callback */
+static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream)
+{
+       struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+       int err = 0;
+
+       if (!ep || !ep->endpoint)
+               return -ENODEV;
+       if (ep->disconnected)
+               return -EIO;
+       if (ep->substream)
+               return -EBUSY;
+       if (ep->direction == STR_OUT) {
+               err = alloc_midi_urbs(ep);
+               if (err)
+                       return err;
+       }
+       spin_lock_irq(&ep->lock);
+       ep->substream = substream;
+       spin_unlock_irq(&ep->lock);
+       return 0;
+}
+
+/* rawmidi close callback */
+static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream)
+{
+       struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+
+       spin_lock_irq(&ep->lock);
+       ep->substream = NULL;
+       spin_unlock_irq(&ep->lock);
+       if (ep->direction == STR_OUT) {
+               kill_midi_urbs(ep, false);
+               drain_urb_queue(ep);
+               free_midi_urbs(ep);
+       }
+       return 0;
+}
+
+/* rawmidi trigger callback */
+static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream,
+                                   int up)
+{
+       struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+
+       atomic_set(&ep->running, up);
+       if (up && ep->direction == STR_OUT && !ep->disconnected)
+               submit_io_urbs(ep);
+}
+
+/* rawmidi drain callback */
+static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream)
+{
+       struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+
+       drain_urb_queue(ep);
+}
+
+/* allocate and start all input streams */
+static int start_input_streams(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_endpoint *ep;
+       int err;
+
+       list_for_each_entry(ep, &umidi->ep_list, list) {
+               if (ep->direction == STR_IN) {
+                       err = alloc_midi_urbs(ep);
+                       if (err < 0)
+                               goto error;
+               }
+       }
+
+       list_for_each_entry(ep, &umidi->ep_list, list) {
+               if (ep->direction == STR_IN)
+                       submit_io_urbs(ep);
+       }
+
+       return 0;
+
+ error:
+       list_for_each_entry(ep, &umidi->ep_list, list) {
+               if (ep->direction == STR_IN)
+                       free_midi_urbs(ep);
+       }
+
+       return err;
+}
+
+static const struct snd_rawmidi_ops output_ops = {
+       .open = snd_usb_midi_v2_open,
+       .close = snd_usb_midi_v2_close,
+       .trigger = snd_usb_midi_v2_trigger,
+       .drain = snd_usb_midi_v2_drain,
+};
+
+static const struct snd_rawmidi_ops input_ops = {
+       .open = snd_usb_midi_v2_open,
+       .close = snd_usb_midi_v2_close,
+       .trigger = snd_usb_midi_v2_trigger,
+};
+
+/* create a USB MIDI 2.0 endpoint object */
+static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi,
+                                struct usb_host_endpoint *hostep,
+                                const struct usb_ms20_endpoint_descriptor *ms_ep)
+{
+       struct snd_usb_midi2_endpoint *ep;
+       int endpoint, dir;
+
+       usb_audio_dbg(umidi->chip, "Creating an EP 0x%02x, #GTB=%d\n",
+                     hostep->desc.bEndpointAddress,
+                     ms_ep->bNumGrpTrmBlock);
+
+       ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+       if (!ep)
+               return -ENOMEM;
+
+       spin_lock_init(&ep->lock);
+       init_waitqueue_head(&ep->wait);
+       ep->dev = umidi->chip->dev;
+       endpoint = hostep->desc.bEndpointAddress;
+       dir = (endpoint & USB_DIR_IN) ? STR_IN : STR_OUT;
+
+       ep->endpoint = endpoint;
+       ep->direction = dir;
+       ep->ms_ep = ms_ep;
+       if (usb_endpoint_xfer_int(&hostep->desc))
+               ep->interval = hostep->desc.bInterval;
+       else
+               ep->interval = 0;
+       if (dir == STR_IN) {
+               if (ep->interval)
+                       ep->pipe = usb_rcvintpipe(ep->dev, endpoint);
+               else
+                       ep->pipe = usb_rcvbulkpipe(ep->dev, endpoint);
+       } else {
+               if (ep->interval)
+                       ep->pipe = usb_sndintpipe(ep->dev, endpoint);
+               else
+                       ep->pipe = usb_sndbulkpipe(ep->dev, endpoint);
+       }
+       ep->packets = usb_maxpacket(ep->dev, ep->pipe);
+       list_add_tail(&ep->list, &umidi->ep_list);
+
+       return 0;
+}
+
+/* destructor for endpoint; from snd_usb_midi_v2_free() */
+static void free_midi2_endpoint(struct snd_usb_midi2_endpoint *ep)
+{
+       list_del(&ep->list);
+       free_midi_urbs(ep);
+       kfree(ep);
+}
+
+/* call all endpoint destructors */
+static void free_all_midi2_endpoints(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_endpoint *ep;
+
+       while (!list_empty(&umidi->ep_list)) {
+               ep = list_first_entry(&umidi->ep_list,
+                                     struct snd_usb_midi2_endpoint, list);
+               free_midi2_endpoint(ep);
+       }
+}
+
+/* find a MIDI STREAMING descriptor with a given subtype */
+static void *find_usb_ms_endpoint_descriptor(struct usb_host_endpoint *hostep,
+                                            unsigned char subtype)
+{
+       unsigned char *extra = hostep->extra;
+       int extralen = hostep->extralen;
+
+       while (extralen > 3) {
+               struct usb_ms_endpoint_descriptor *ms_ep =
+                       (struct usb_ms_endpoint_descriptor *)extra;
+
+               if (ms_ep->bLength > 3 &&
+                   ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT &&
+                   ms_ep->bDescriptorSubtype == subtype)
+                       return ms_ep;
+               if (!extra[0])
+                       break;
+               extralen -= extra[0];
+               extra += extra[0];
+       }
+       return NULL;
+}
+
+/* get the full group terminal block descriptors and return the size */
+static int get_group_terminal_block_descs(struct snd_usb_midi2_interface *umidi)
+{
+       struct usb_host_interface *hostif = umidi->hostif;
+       struct usb_device *dev = umidi->chip->dev;
+       struct usb_ms20_gr_trm_block_header_descriptor header = { 0 };
+       unsigned char *data;
+       int err, size;
+
+       err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             USB_REQ_GET_DESCRIPTOR,
+                             USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN,
+                             USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting,
+                             hostif->desc.bInterfaceNumber,
+                             &header, sizeof(header));
+       if (err < 0)
+               return err;
+       size = __le16_to_cpu(header.wTotalLength);
+       if (!size) {
+               dev_err(&dev->dev, "Failed to get GTB descriptors for %d:%d\n",
+                       hostif->desc.bInterfaceNumber, hostif->desc.bAlternateSetting);
+               return -EINVAL;
+       }
+
+       data = kzalloc(size, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+                             USB_REQ_GET_DESCRIPTOR,
+                             USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN,
+                             USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting,
+                             hostif->desc.bInterfaceNumber, data, size);
+       if (err < 0) {
+               kfree(data);
+               return err;
+       }
+
+       umidi->blk_descs = data;
+       umidi->blk_desc_size = size;
+       return 0;
+}
+
+/* find the corresponding group terminal block descriptor */
+static const struct usb_ms20_gr_trm_block_descriptor *
+find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id)
+{
+       const unsigned char *data = umidi->blk_descs;
+       int size = umidi->blk_desc_size;
+       const struct usb_ms20_gr_trm_block_descriptor *desc;
+
+       size -= sizeof(struct usb_ms20_gr_trm_block_header_descriptor);
+       data += sizeof(struct usb_ms20_gr_trm_block_header_descriptor);
+       while (size > 0 && *data && *data <= size) {
+               desc = (const struct usb_ms20_gr_trm_block_descriptor *)data;
+               if (desc->bLength >= sizeof(*desc) &&
+                   desc->bDescriptorType == USB_DT_CS_GR_TRM_BLOCK &&
+                   desc->bDescriptorSubtype == USB_MS_GR_TRM_BLOCK &&
+                   desc->bGrpTrmBlkID == id)
+                       return desc;
+               size -= *data;
+               data += *data;
+       }
+
+       return NULL;
+}
+
+/* fill up the information from GTB */
+static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi,
+                                     const struct usb_ms20_gr_trm_block_descriptor *desc)
+{
+       struct snd_usb_audio *chip = rmidi->umidi->chip;
+       struct snd_ump_endpoint *ump = rmidi->ump;
+
+       usb_audio_dbg(chip,
+                     "GTB id %d: groups = %d / %d, type = %d\n",
+                     desc->bGrpTrmBlkID, desc->nGroupTrm, desc->nNumGroupTrm,
+                     desc->bGrpTrmBlkType);
+
+       /* set default protocol */
+       switch (desc->bMIDIProtocol) {
+       case USB_MS_MIDI_PROTO_1_0_64:
+       case USB_MS_MIDI_PROTO_1_0_64_JRTS:
+       case USB_MS_MIDI_PROTO_1_0_128:
+       case USB_MS_MIDI_PROTO_1_0_128_JRTS:
+               ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1;
+               break;
+       case USB_MS_MIDI_PROTO_2_0:
+       case USB_MS_MIDI_PROTO_2_0_JRTS:
+               ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2;
+               break;
+       }
+
+       ump->info.protocol_caps = ump->info.protocol;
+       switch (desc->bMIDIProtocol) {
+       case USB_MS_MIDI_PROTO_1_0_64_JRTS:
+       case USB_MS_MIDI_PROTO_1_0_128_JRTS:
+       case USB_MS_MIDI_PROTO_2_0_JRTS:
+               ump->info.protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX |
+                       SNDRV_UMP_EP_INFO_PROTO_JRTS_RX;
+               break;
+       }
+
+       return 0;
+}
+
+/* allocate and parse for each assigned group terminal block */
+static int parse_group_terminal_blocks(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_ump *rmidi;
+       const struct usb_ms20_gr_trm_block_descriptor *desc;
+       int err;
+
+       err = get_group_terminal_block_descs(umidi);
+       if (err < 0)
+               return err;
+       if (!umidi->blk_descs)
+               return 0;
+
+       list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
+               desc = find_group_terminal_block(umidi, rmidi->usb_block_id);
+               if (!desc)
+                       continue;
+               err = parse_group_terminal_block(rmidi, desc);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+/* parse endpoints included in the given interface and create objects */
+static int parse_midi_2_0_endpoints(struct snd_usb_midi2_interface *umidi)
+{
+       struct usb_host_interface *hostif = umidi->hostif;
+       struct usb_host_endpoint *hostep;
+       struct usb_ms20_endpoint_descriptor *ms_ep;
+       int i, err;
+
+       for (i = 0; i < hostif->desc.bNumEndpoints; i++) {
+               hostep = &hostif->endpoint[i];
+               if (!usb_endpoint_xfer_bulk(&hostep->desc) &&
+                   !usb_endpoint_xfer_int(&hostep->desc))
+                       continue;
+               ms_ep = find_usb_ms_endpoint_descriptor(hostep, USB_MS_GENERAL_2_0);
+               if (!ms_ep)
+                       continue;
+               if (ms_ep->bLength <= sizeof(*ms_ep))
+                       continue;
+               if (!ms_ep->bNumGrpTrmBlock)
+                       continue;
+               if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumGrpTrmBlock)
+                       continue;
+               err = create_midi2_endpoint(umidi, hostep, ms_ep);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+static void free_all_midi2_umps(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_ump *rmidi;
+
+       while (!list_empty(&umidi->rawmidi_list)) {
+               rmidi = list_first_entry(&umidi->rawmidi_list,
+                                        struct snd_usb_midi2_ump, list);
+               list_del(&rmidi->list);
+               kfree(rmidi);
+       }
+}
+
+static int create_midi2_ump(struct snd_usb_midi2_interface *umidi,
+                           struct snd_usb_midi2_endpoint *ep_in,
+                           struct snd_usb_midi2_endpoint *ep_out,
+                           int blk_id)
+{
+       struct snd_usb_midi2_ump *rmidi;
+       struct snd_ump_endpoint *ump;
+       int input, output;
+       char idstr[16];
+       int err;
+
+       rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL);
+       if (!rmidi)
+               return -ENOMEM;
+       INIT_LIST_HEAD(&rmidi->list);
+       rmidi->dev = umidi->chip->dev;
+       rmidi->umidi = umidi;
+       rmidi->usb_block_id = blk_id;
+
+       rmidi->index = umidi->chip->num_rawmidis;
+       snprintf(idstr, sizeof(idstr), "UMP %d", rmidi->index);
+       input = ep_in ? 1 : 0;
+       output = ep_out ? 1 : 0;
+       err = snd_ump_endpoint_new(umidi->chip->card, idstr, rmidi->index,
+                                  output, input, &ump);
+       if (err < 0) {
+               usb_audio_dbg(umidi->chip, "Failed to create a UMP object\n");
+               kfree(rmidi);
+               return err;
+       }
+
+       rmidi->ump = ump;
+       umidi->chip->num_rawmidis++;
+
+       ump->private_data = rmidi;
+
+       if (input)
+               snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
+                                   &input_ops);
+       if (output)
+               snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
+                                   &output_ops);
+
+       rmidi->eps[STR_IN] = ep_in;
+       rmidi->eps[STR_OUT] = ep_out;
+       if (ep_in) {
+               ep_in->pair = ep_out;
+               ep_in->rmidi = rmidi;
+       }
+       if (ep_out) {
+               ep_out->pair = ep_in;
+               ep_out->rmidi = rmidi;
+       }
+
+       list_add_tail(&rmidi->list, &umidi->rawmidi_list);
+       return 0;
+}
+
+/* find the UMP EP with the given USB block id */
+static struct snd_usb_midi2_ump *
+find_midi2_ump(struct snd_usb_midi2_interface *umidi, int blk_id)
+{
+       struct snd_usb_midi2_ump *rmidi;
+
+       list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
+               if (rmidi->usb_block_id == blk_id)
+                       return rmidi;
+       }
+       return NULL;
+}
+
+/* look for the matching output endpoint and create UMP object if found */
+static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi,
+                                   struct snd_usb_midi2_endpoint *ep,
+                                   int blk_id)
+{
+       struct snd_usb_midi2_endpoint *pair_ep;
+       int blk;
+
+       usb_audio_dbg(umidi->chip, "Looking for a pair for EP-in 0x%02x\n",
+                     ep->endpoint);
+       list_for_each_entry(pair_ep, &umidi->ep_list, list) {
+               if (pair_ep->direction != STR_OUT)
+                       continue;
+               if (pair_ep->pair)
+                       continue; /* already paired */
+               for (blk = 0; blk < pair_ep->ms_ep->bNumGrpTrmBlock; blk++) {
+                       if (pair_ep->ms_ep->baAssoGrpTrmBlkID[blk] == blk_id) {
+                               usb_audio_dbg(umidi->chip,
+                                             "Found a match with EP-out 0x%02x blk %d\n",
+                                             pair_ep->endpoint, blk);
+                               return create_midi2_ump(umidi, ep, pair_ep, blk_id);
+                       }
+               }
+       }
+       return 0;
+}
+
+static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi)
+{
+       free_all_midi2_endpoints(umidi);
+       free_all_midi2_umps(umidi);
+       list_del(&umidi->list);
+       kfree(umidi->blk_descs);
+       kfree(umidi);
+}
+
+/* parse the interface for MIDI 2.0 */
+static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_endpoint *ep;
+       int blk, id, err;
+
+       /* First, create an object for each USB MIDI Endpoint */
+       err = parse_midi_2_0_endpoints(umidi);
+       if (err < 0)
+               return err;
+       if (list_empty(&umidi->ep_list)) {
+               usb_audio_warn(umidi->chip, "No MIDI endpoints found\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Next, look for EP I/O pairs that are found in group terminal blocks
+        * A UMP object is created for each EP I/O pair as bidirecitonal
+        * UMP EP
+        */
+       list_for_each_entry(ep, &umidi->ep_list, list) {
+               /* only input in this loop; output is matched in find_midi_ump() */
+               if (ep->direction != STR_IN)
+                       continue;
+               for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) {
+                       id = ep->ms_ep->baAssoGrpTrmBlkID[blk];
+                       err = find_matching_ep_partner(umidi, ep, id);
+                       if (err < 0)
+                               return err;
+               }
+       }
+
+       /*
+        * For the remaining EPs, treat as singles, create a UMP object with
+        * unidirectional EP
+        */
+       list_for_each_entry(ep, &umidi->ep_list, list) {
+               if (ep->rmidi)
+                       continue; /* already paired */
+               for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) {
+                       id = ep->ms_ep->baAssoGrpTrmBlkID[blk];
+                       if (find_midi2_ump(umidi, id))
+                               continue;
+                       usb_audio_dbg(umidi->chip,
+                                     "Creating a unidirection UMP for EP=0x%02x, blk=%d\n",
+                                     ep->endpoint, id);
+                       if (ep->direction == STR_IN)
+                               err = create_midi2_ump(umidi, ep, NULL, id);
+                       else
+                               err = create_midi2_ump(umidi, NULL, ep, id);
+                       if (err < 0)
+                               return err;
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+/* is the given interface for MIDI 2.0? */
+static bool is_midi2_altset(struct usb_host_interface *hostif)
+{
+       struct usb_ms_header_descriptor *ms_header =
+               (struct usb_ms_header_descriptor *)hostif->extra;
+
+       if (hostif->extralen < 7 ||
+           ms_header->bLength < 7 ||
+           ms_header->bDescriptorType != USB_DT_CS_INTERFACE ||
+           ms_header->bDescriptorSubtype != UAC_HEADER)
+               return false;
+
+       return le16_to_cpu(ms_header->bcdMSC) == USB_MS_REV_MIDI_2_0;
+}
+
+/* change the altsetting */
+static int set_altset(struct snd_usb_midi2_interface *umidi)
+{
+       usb_audio_dbg(umidi->chip, "Setting host iface %d:%d\n",
+                     umidi->hostif->desc.bInterfaceNumber,
+                     umidi->hostif->desc.bAlternateSetting);
+       return usb_set_interface(umidi->chip->dev,
+                                umidi->hostif->desc.bInterfaceNumber,
+                                umidi->hostif->desc.bAlternateSetting);
+}
+
+/* fill the fallback name string for each rawmidi instance */
+static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi)
+{
+       struct snd_usb_midi2_ump *rmidi;
+
+       list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
+               if (!*rmidi->ump->core.name)
+                       sprintf(rmidi->ump->core.name, "USB MIDI %d",
+                               rmidi->index);
+       }
+}
+
+/* create MIDI interface; fallback to MIDI 1.0 if needed */
+int snd_usb_midi_v2_create(struct snd_usb_audio *chip,
+                          struct usb_interface *iface,
+                          const struct snd_usb_audio_quirk *quirk,
+                          unsigned int usb_id)
+{
+       struct snd_usb_midi2_interface *umidi;
+       struct usb_host_interface *hostif;
+       int err;
+
+       usb_audio_dbg(chip, "Parsing interface %d...\n",
+                     iface->altsetting[0].desc.bInterfaceNumber);
+
+       /* fallback to MIDI 1.0? */
+       if (!midi2_enable) {
+               usb_audio_info(chip, "Falling back to MIDI 1.0 by module option\n");
+               goto fallback_to_midi1;
+       }
+       if ((quirk && quirk->type != QUIRK_MIDI_STANDARD_INTERFACE) ||
+           iface->num_altsetting < 2) {
+               usb_audio_info(chip, "Quirk or no altest; falling back to MIDI 1.0\n");
+               goto fallback_to_midi1;
+       }
+       hostif = &iface->altsetting[1];
+       if (!is_midi2_altset(hostif)) {
+               usb_audio_info(chip, "No MIDI 2.0 at altset 1, falling back to MIDI 1.0\n");
+               goto fallback_to_midi1;
+       }
+       if (!hostif->desc.bNumEndpoints) {
+               usb_audio_info(chip, "No endpoint at altset 1, falling back to MIDI 1.0\n");
+               goto fallback_to_midi1;
+       }
+
+       usb_audio_dbg(chip, "Creating a MIDI 2.0 instance for %d:%d\n",
+                     hostif->desc.bInterfaceNumber,
+                     hostif->desc.bAlternateSetting);
+
+       umidi = kzalloc(sizeof(*umidi), GFP_KERNEL);
+       if (!umidi)
+               return -ENOMEM;
+       umidi->chip = chip;
+       umidi->iface = iface;
+       umidi->hostif = hostif;
+       INIT_LIST_HEAD(&umidi->rawmidi_list);
+       INIT_LIST_HEAD(&umidi->ep_list);
+
+       list_add_tail(&umidi->list, &chip->midi_v2_list);
+
+       err = set_altset(umidi);
+       if (err < 0) {
+               usb_audio_err(chip, "Failed to set altset\n");
+               goto error;
+       }
+
+       /* assume only altset 1 corresponding to MIDI 2.0 interface */
+       err = parse_midi_2_0(umidi);
+       if (err < 0) {
+               usb_audio_err(chip, "Failed to parse MIDI 2.0 interface\n");
+               goto error;
+       }
+
+       /* parse USB group terminal blocks */
+       err = parse_group_terminal_blocks(umidi);
+       if (err < 0) {
+               usb_audio_err(chip, "Failed to parse GTB\n");
+               goto error;
+       }
+
+       err = start_input_streams(umidi);
+       if (err < 0) {
+               usb_audio_err(chip, "Failed to start input streams\n");
+               goto error;
+       }
+
+       set_fallback_rawmidi_names(umidi);
+       return 0;
+
+ error:
+       snd_usb_midi_v2_free(umidi);
+       return err;
+
+ fallback_to_midi1:
+       return __snd_usbmidi_create(chip->card, iface, &chip->midi_list,
+                                   quirk, usb_id, &chip->num_rawmidis);
+}
+
+static void suspend_midi2_endpoint(struct snd_usb_midi2_endpoint *ep)
+{
+       kill_midi_urbs(ep, true);
+       drain_urb_queue(ep);
+}
+
+void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip)
+{
+       struct snd_usb_midi2_interface *umidi;
+       struct snd_usb_midi2_endpoint *ep;
+
+       list_for_each_entry(umidi, &chip->midi_v2_list, list) {
+               list_for_each_entry(ep, &umidi->ep_list, list)
+                       suspend_midi2_endpoint(ep);
+       }
+}
+
+static void resume_midi2_endpoint(struct snd_usb_midi2_endpoint *ep)
+{
+       ep->running = ep->suspended;
+       if (ep->direction == STR_IN)
+               submit_io_urbs(ep);
+       /* FIXME: does it all? */
+}
+
+void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip)
+{
+       struct snd_usb_midi2_interface *umidi;
+       struct snd_usb_midi2_endpoint *ep;
+
+       list_for_each_entry(umidi, &chip->midi_v2_list, list) {
+               set_altset(umidi);
+               list_for_each_entry(ep, &umidi->ep_list, list)
+                       resume_midi2_endpoint(ep);
+       }
+}
+
+void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip)
+{
+       struct snd_usb_midi2_interface *umidi;
+       struct snd_usb_midi2_endpoint *ep;
+
+       list_for_each_entry(umidi, &chip->midi_v2_list, list) {
+               umidi->disconnected = 1;
+               list_for_each_entry(ep, &umidi->ep_list, list) {
+                       ep->disconnected = 1;
+                       kill_midi_urbs(ep, false);
+                       drain_urb_queue(ep);
+               }
+       }
+}
+
+/* release the MIDI instance */
+void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip)
+{
+       struct snd_usb_midi2_interface *umidi, *next;
+
+       list_for_each_entry_safe(umidi, next, &chip->midi_v2_list, list)
+               snd_usb_midi_v2_free(umidi);
+}