mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-23 20:24:12 +08:00
b1757fa30e
The allocation and initialization errors at alloc_midi_urbs() that is
called at MIDI 2.0 / UMP device are supposed to be handled at the
caller side by invoking free_midi_urbs(). However, free_midi_urbs()
loops only for ep->num_urbs entries, and since ep->num_entries wasn't
updated yet at the allocation / init error in alloc_midi_urbs(), this
entry won't be released.
The intention of free_midi_urbs() is to release the whole elements, so
change the loop size to NUM_URBS to scan over all elements for fixing
the missed releases.
Also, the call of free_midi_urbs() is missing at
snd_usb_midi_v2_open(). Although it'll be released later at
reopen/close or disconnection, it's better to release immediately at
the error path.
Fixes: ff49d1df79
("ALSA: usb-audio: USB MIDI 2.0 UMP support")
Reported-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Closes: https://lore.kernel.org/r/fc275ed315b9157952dcf2744ee7bdb78defdb5f.1693746347.git.christophe.jaillet@wanadoo.fr
Link: https://lore.kernel.org/r/20230905054511.20502-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
1235 lines
33 KiB
C
1235 lines
33 KiB
C
// 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.");
|
|
|
|
static bool midi2_ump_probe = true;
|
|
module_param(midi2_ump_probe, bool, 0444);
|
|
MODULE_PARM_DESC(midi2_ump_probe, "Probe UMP v1.1 support at first.");
|
|
|
|
/* 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 pair */
|
|
struct snd_ump_endpoint *ump; /* 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 */
|
|
bool ump_parsed; /* Parsed UMP 1.1 EP/FB info*/
|
|
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;
|
|
|
|
count = snd_ump_transmit(ep->ump, urb->transfer_buffer,
|
|
ep->packets);
|
|
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) {
|
|
le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2);
|
|
snd_ump_receive(ep->ump, (u32 *)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 < 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 */
|
|
/* the callers should handle allocation errors via free_midi_urbs() */
|
|
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 *
|
|
ump_to_endpoint(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
struct snd_usb_midi2_ump *rmidi = ump->private_data;
|
|
|
|
return rmidi->eps[dir];
|
|
}
|
|
|
|
/* ump open callback */
|
|
static int snd_usb_midi_v2_open(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
|
|
int err = 0;
|
|
|
|
if (!ep || !ep->endpoint)
|
|
return -ENODEV;
|
|
if (ep->disconnected)
|
|
return -EIO;
|
|
if (ep->direction == STR_OUT) {
|
|
err = alloc_midi_urbs(ep);
|
|
if (err) {
|
|
free_midi_urbs(ep);
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ump close callback */
|
|
static void snd_usb_midi_v2_close(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
|
|
|
|
if (ep->direction == STR_OUT) {
|
|
kill_midi_urbs(ep, false);
|
|
drain_urb_queue(ep);
|
|
free_midi_urbs(ep);
|
|
}
|
|
}
|
|
|
|
/* ump trigger callback */
|
|
static void snd_usb_midi_v2_trigger(struct snd_ump_endpoint *ump, int dir,
|
|
int up)
|
|
{
|
|
struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
|
|
|
|
atomic_set(&ep->running, up);
|
|
if (up && ep->direction == STR_OUT && !ep->disconnected)
|
|
submit_io_urbs(ep);
|
|
}
|
|
|
|
/* ump drain callback */
|
|
static void snd_usb_midi_v2_drain(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
|
|
|
|
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_ump_ops snd_usb_midi_v2_ump_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,
|
|
};
|
|
|
|
/* 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_ump_endpoint *ump = rmidi->ump;
|
|
unsigned int protocol, protocol_caps;
|
|
|
|
/* 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:
|
|
protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1;
|
|
break;
|
|
case USB_MS_MIDI_PROTO_2_0:
|
|
case USB_MS_MIDI_PROTO_2_0_JRTS:
|
|
protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (ump->info.protocol && ump->info.protocol != protocol)
|
|
usb_audio_info(rmidi->umidi->chip,
|
|
"Overriding preferred MIDI protocol in GTB %d: %x -> %x\n",
|
|
rmidi->usb_block_id, ump->info.protocol,
|
|
protocol);
|
|
ump->info.protocol = protocol;
|
|
|
|
protocol_caps = 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:
|
|
protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX |
|
|
SNDRV_UMP_EP_INFO_PROTO_JRTS_RX;
|
|
break;
|
|
}
|
|
|
|
if (ump->info.protocol_caps && ump->info.protocol_caps != protocol_caps)
|
|
usb_audio_info(rmidi->umidi->chip,
|
|
"Overriding MIDI protocol caps in GTB %d: %x -> %x\n",
|
|
rmidi->usb_block_id, ump->info.protocol_caps,
|
|
protocol_caps);
|
|
ump->info.protocol_caps = protocol_caps;
|
|
|
|
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;
|
|
ump->ops = &snd_usb_midi_v2_ump_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;
|
|
ep_in->ump = ump;
|
|
}
|
|
if (ep_out) {
|
|
ep_out->pair = ep_in;
|
|
ep_out->rmidi = rmidi;
|
|
ep_out->ump = ump;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Call UMP helper to parse UMP endpoints;
|
|
* this needs to be called after starting the input streams for bi-directional
|
|
* communications
|
|
*/
|
|
static int parse_ump_endpoints(struct snd_usb_midi2_interface *umidi)
|
|
{
|
|
struct snd_usb_midi2_ump *rmidi;
|
|
int err;
|
|
|
|
list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
|
|
if (!rmidi->ump ||
|
|
!(rmidi->ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))
|
|
continue;
|
|
err = snd_ump_parse_endpoint(rmidi->ump);
|
|
if (!err) {
|
|
rmidi->ump_parsed = true;
|
|
} else {
|
|
if (err == -ENOMEM)
|
|
return err;
|
|
/* fall back to GTB later */
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* create a UMP block from a GTB entry */
|
|
static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk)
|
|
{
|
|
struct snd_usb_midi2_interface *umidi = rmidi->umidi;
|
|
const struct usb_ms20_gr_trm_block_descriptor *desc;
|
|
struct snd_ump_block *fb;
|
|
int type, err;
|
|
|
|
desc = find_group_terminal_block(umidi, blk);
|
|
if (!desc)
|
|
return 0;
|
|
|
|
usb_audio_dbg(umidi->chip,
|
|
"GTB %d: type=%d, group=%d/%d, protocol=%d, in bw=%d, out bw=%d\n",
|
|
blk, desc->bGrpTrmBlkType, desc->nGroupTrm,
|
|
desc->nNumGroupTrm, desc->bMIDIProtocol,
|
|
__le16_to_cpu(desc->wMaxInputBandwidth),
|
|
__le16_to_cpu(desc->wMaxOutputBandwidth));
|
|
|
|
/* assign the direction */
|
|
switch (desc->bGrpTrmBlkType) {
|
|
case USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL:
|
|
type = SNDRV_UMP_DIR_BIDIRECTION;
|
|
break;
|
|
case USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY:
|
|
type = SNDRV_UMP_DIR_INPUT;
|
|
break;
|
|
case USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY:
|
|
type = SNDRV_UMP_DIR_OUTPUT;
|
|
break;
|
|
default:
|
|
usb_audio_dbg(umidi->chip, "Unsupported GTB type %d\n",
|
|
desc->bGrpTrmBlkType);
|
|
return 0; /* unsupported */
|
|
}
|
|
|
|
/* guess work: set blk-1 as the (0-based) block ID */
|
|
err = snd_ump_block_new(rmidi->ump, blk - 1, type,
|
|
desc->nGroupTrm, desc->nNumGroupTrm,
|
|
&fb);
|
|
if (err == -EBUSY)
|
|
return 0; /* already present */
|
|
else if (err)
|
|
return err;
|
|
|
|
if (desc->iBlockItem)
|
|
usb_string(rmidi->dev, desc->iBlockItem,
|
|
fb->info.name, sizeof(fb->info.name));
|
|
|
|
if (__le16_to_cpu(desc->wMaxInputBandwidth) == 1 ||
|
|
__le16_to_cpu(desc->wMaxOutputBandwidth) == 1)
|
|
fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 |
|
|
SNDRV_UMP_BLOCK_IS_LOWSPEED;
|
|
|
|
usb_audio_dbg(umidi->chip,
|
|
"Created a UMP block %d from GTB, name=%s\n",
|
|
blk, fb->info.name);
|
|
return 0;
|
|
}
|
|
|
|
/* Create UMP blocks for each UMP EP */
|
|
static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi)
|
|
{
|
|
struct snd_usb_midi2_ump *rmidi;
|
|
int i, blk, err, dir;
|
|
|
|
list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
|
|
if (!rmidi->ump)
|
|
continue;
|
|
/* Blocks have been already created? */
|
|
if (rmidi->ump_parsed || rmidi->ump->info.num_blocks)
|
|
continue;
|
|
/* GTB is static-only */
|
|
rmidi->ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
|
|
/* loop over GTBs */
|
|
for (dir = 0; dir < 2; dir++) {
|
|
if (!rmidi->eps[dir])
|
|
continue;
|
|
for (i = 0; i < rmidi->eps[dir]->ms_ep->bNumGrpTrmBlock; i++) {
|
|
blk = rmidi->eps[dir]->ms_ep->baAssoGrpTrmBlkID[i];
|
|
err = create_gtb_block(rmidi, dir, blk);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* attach legacy rawmidis */
|
|
static int attach_legacy_rawmidi(struct snd_usb_midi2_interface *umidi)
|
|
{
|
|
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
|
|
struct snd_usb_midi2_ump *rmidi;
|
|
int err;
|
|
|
|
list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
|
|
err = snd_ump_attach_legacy_rawmidi(rmidi->ump,
|
|
"Legacy MIDI",
|
|
umidi->chip->num_rawmidis);
|
|
if (err < 0)
|
|
return err;
|
|
umidi->chip->num_rawmidis++;
|
|
}
|
|
#endif
|
|
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 UMP Endpoint name string from USB descriptor */
|
|
static void fill_ump_ep_name(struct snd_ump_endpoint *ump,
|
|
struct usb_device *dev, int id)
|
|
{
|
|
int len;
|
|
|
|
usb_string(dev, id, ump->info.name, sizeof(ump->info.name));
|
|
|
|
/* trim superfluous "MIDI" suffix */
|
|
len = strlen(ump->info.name);
|
|
if (len > 5 && !strcmp(ump->info.name + len - 5, " MIDI"))
|
|
ump->info.name[len - 5] = 0;
|
|
}
|
|
|
|
/* fill the fallback name string for each rawmidi instance */
|
|
static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi)
|
|
{
|
|
struct usb_device *dev = umidi->chip->dev;
|
|
struct snd_usb_midi2_ump *rmidi;
|
|
struct snd_ump_endpoint *ump;
|
|
|
|
list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
|
|
ump = rmidi->ump;
|
|
/* fill UMP EP name from USB descriptors */
|
|
if (!*ump->info.name && umidi->hostif->desc.iInterface)
|
|
fill_ump_ep_name(ump, dev, umidi->hostif->desc.iInterface);
|
|
else if (!*ump->info.name && dev->descriptor.iProduct)
|
|
fill_ump_ep_name(ump, dev, dev->descriptor.iProduct);
|
|
/* fill fallback name */
|
|
if (!*ump->info.name)
|
|
sprintf(ump->info.name, "USB MIDI %d", rmidi->index);
|
|
/* copy as rawmidi name if not set */
|
|
if (!*ump->core.name)
|
|
strscpy(ump->core.name, ump->info.name,
|
|
sizeof(ump->core.name));
|
|
/* use serial number string as unique UMP product id */
|
|
if (!*ump->info.product_id && dev->descriptor.iSerialNumber)
|
|
usb_string(dev, dev->descriptor.iSerialNumber,
|
|
ump->info.product_id,
|
|
sizeof(ump->info.product_id));
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
if (midi2_ump_probe) {
|
|
err = parse_ump_endpoints(umidi);
|
|
if (err < 0) {
|
|
usb_audio_err(chip, "Failed to parse UMP endpoint\n");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
err = create_blocks_from_gtb(umidi);
|
|
if (err < 0) {
|
|
usb_audio_err(chip, "Failed to create GTB blocks\n");
|
|
goto error;
|
|
}
|
|
|
|
set_fallback_rawmidi_names(umidi);
|
|
|
|
err = attach_legacy_rawmidi(umidi);
|
|
if (err < 0) {
|
|
usb_audio_err(chip, "Failed to create legacy rawmidi\n");
|
|
goto error;
|
|
}
|
|
|
|
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);
|
|
}
|