@@ -15,6 +15,7 @@ config SND_USB_AUDIO
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
+ select SND_UMP if SND_USB_AUDIO_MIDI_V2
select BITREVERSE
select SND_USB_AUDIO_USE_MEDIA_CONTROLLER if MEDIA_CONTROLLER && (MEDIA_SUPPORT=y || MEDIA_SUPPORT=SND_USB_AUDIO)
help
@@ -24,6 +25,16 @@ config SND_USB_AUDIO
To compile this driver as a module, choose M here: the module
will be called snd-usb-audio.
+config SND_USB_AUDIO_MIDI_V2
+ bool "MIDI 2.0 support by USB Audio driver"
+ depends on SND_USB_AUDIO
+ help
+ Say Y here to include the support for MIDI 2.0 by USB Audio driver.
+ When the config is set, the driver tries to probe MIDI 2.0 interface
+ at first, then falls back to MIDI 1.0 interface as default.
+ The MIDI 2.0 support can be disabled dynamically via midi2_enable
+ module option, too.
+
config SND_USB_AUDIO_USE_MEDIA_CONTROLLER
bool
@@ -22,6 +22,7 @@ snd-usb-audio-objs := card.o \
stream.o \
validate.o
+snd-usb-audio-$(CONFIG_SND_USB_AUDIO_MIDI_V2) += midi2.o
snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o
snd-usbmidi-lib-objs := midi.o
@@ -44,6 +44,7 @@
#include "usbaudio.h"
#include "card.h"
#include "midi.h"
+#include "midi2.h"
#include "mixer.h"
#include "proc.h"
#include "quirks.h"
@@ -178,10 +179,8 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int
if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
- int err = __snd_usbmidi_create(chip->card, iface,
- &chip->midi_list, NULL,
- chip->usb_id,
- &chip->num_rawmidis);
+ int err = snd_usb_midi_v2_create(chip, iface, NULL,
+ chip->usb_id);
if (err < 0) {
dev_err(&dev->dev,
"%u:%d: cannot create sequencer device\n",
@@ -486,6 +485,7 @@ static void snd_usb_audio_free(struct snd_card *card)
struct snd_usb_audio *chip = card->private_data;
snd_usb_endpoint_free_all(chip);
+ snd_usb_midi_v2_free_all(chip);
mutex_destroy(&chip->mutex);
if (!atomic_read(&chip->shutdown))
@@ -645,6 +645,7 @@ static int snd_usb_audio_create(struct usb_interface *intf,
INIT_LIST_HEAD(&chip->iface_ref_list);
INIT_LIST_HEAD(&chip->clock_ref_list);
INIT_LIST_HEAD(&chip->midi_list);
+ INIT_LIST_HEAD(&chip->midi_v2_list);
INIT_LIST_HEAD(&chip->mixer_list);
if (quirk_flags[idx])
@@ -969,6 +970,7 @@ static void usb_audio_disconnect(struct usb_interface *intf)
list_for_each(p, &chip->midi_list) {
snd_usbmidi_disconnect(p);
}
+ snd_usb_midi_v2_disconnect_all(chip);
/*
* Nice to check quirk && quirk->shares_media_device and
* then call the snd_media_device_delete(). Don't have
@@ -1080,6 +1082,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
snd_usbmidi_suspend(p);
list_for_each_entry(mixer, &chip->mixer_list, list)
snd_usb_mixer_suspend(mixer);
+ snd_usb_midi_v2_suspend_all(chip);
}
if (!PMSG_IS_AUTO(message) && !chip->system_suspend) {
@@ -1125,6 +1128,8 @@ static int usb_audio_resume(struct usb_interface *intf)
snd_usbmidi_resume(p);
}
+ snd_usb_midi_v2_resume_all(chip);
+
out:
if (chip->num_suspended_intf == chip->system_suspend) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
new file mode 100644
@@ -0,0 +1,1052 @@
+// 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);
+}
new file mode 100644
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __USB_AUDIO_MIDI2_H
+#define __USB_AUDIO_MIDI2_H
+
+#include "midi.h"
+
+#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2)
+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);
+void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip);
+void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip);
+void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip);
+void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip);
+#else /* CONFIG_SND_USB_AUDIO_MIDI_V2 */
+/* fallback to MIDI 1.0 creation */
+static inline 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)
+{
+ return __snd_usbmidi_create(chip->card, iface, &chip->midi_list,
+ quirk, usb_id, &chip->num_rawmidis);
+}
+
+static inline void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) {}
+static inline void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) {}
+static inline void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) {}
+static inline void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) {}
+#endif /* CONFIG_SND_USB_AUDIO_MIDI_V2 */
+
+#endif /* __USB_AUDIO_MIDI2_H */
@@ -19,6 +19,7 @@
#include "mixer.h"
#include "mixer_quirks.h"
#include "midi.h"
+#include "midi2.h"
#include "quirks.h"
#include "helper.h"
#include "endpoint.h"
@@ -80,7 +81,7 @@ static int create_any_midi_quirk(struct snd_usb_audio *chip,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
- return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
+ return snd_usb_midi_v2_create(chip, intf, quirk, 0);
}
/*
@@ -51,6 +51,7 @@ struct snd_usb_audio {
unsigned int num_rawmidis; /* number of created rawmidi devices */
struct list_head midi_list; /* list of midi interfaces */
+ struct list_head midi_v2_list; /* list of MIDI 2 interfaces */
struct list_head mixer_list; /* list of mixer interfaces */