/* A USB MIDI input/output endpoint */ struct snd_usb_midi2_endpoint { struct usb_device *dev; conststruct 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) */ unsignedint endpoint; /* EP number */ unsignedint pipe; /* URB pipe */ unsignedint packets; /* packet buffer size in bytes */ unsignedint 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 */ unsignedint num_urbs; /* number of allocated URBs */ unsignedlong urb_free; /* bitmap for free URBs */ unsignedlong 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 */ unsignedchar 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; constchar *blk_descs; /* group terminal block descriptors */ unsignedint 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 */ staticvoid 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 */ staticint prepare_output_urb(struct snd_usb_midi2_endpoint *ep, struct urb *urb)
{ int count;
/* URB completion for input; copy into rawmidi buffer and resubmit */ staticvoid input_urb_complete(struct urb *urb)
{ struct snd_usb_midi2_urb *ctx = urb->context; struct snd_usb_midi2_endpoint *ep = ctx->ep; unsignedlong 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 */ staticvoid submit_io_urbs(struct snd_usb_midi2_endpoint *ep)
{ unsignedlong 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 */ staticvoid 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 */ staticvoid 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 */ staticvoid 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() */ staticint 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;
/* fill up the information from GTB */ staticint parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, conststruct usb_ms20_gr_trm_block_descriptor *desc)
{ struct snd_ump_endpoint *ump = rmidi->ump; unsignedint 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;
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;
}
/* allocate and parse for each assigned group terminal block */ staticint parse_group_terminal_blocks(struct snd_usb_midi2_interface *umidi)
{ struct snd_usb_midi2_ump *rmidi; conststruct 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;
/* parse endpoints included in the given interface and create objects */ staticint 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;
}
/* find the UMP EP with the given USB block id */ staticstruct snd_usb_midi2_ump *
find_midi2_ump(struct snd_usb_midi2_interface *umidi, int blk_id)
{ struct snd_usb_midi2_ump *rmidi;
/* look for the matching output endpoint and create UMP object if found */ staticint 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
*/ staticint 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 */ staticint create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk)
{ struct snd_usb_midi2_interface *umidi = rmidi->umidi; conststruct 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 */ elseif (err) return err;
if (desc->iBlockItem)
usb_string(rmidi->dev, desc->iBlockItem,
fb->info.name, sizeof(fb->info.name));
/* if MIDI 2.0 protocol is supported and yet the GTB shows MIDI 1.0, * treat it as a MIDI 1.0-specific block
*/ if (rmidi->ump->info.protocol_caps & SNDRV_UMP_EP_INFO_PROTO_MIDI2) { 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:
fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1; break;
}
}
snd_ump_update_group_attrs(rmidi->ump);
usb_audio_dbg(umidi->chip, "Created a UMP block %d from GTB, name=%s, flags=0x%x\n",
blk, fb->info.name, fb->info.flags); return 0;
}
/* Create UMP blocks for each UMP EP */ staticint 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;
}
}
}
/* parse the interface for MIDI 2.0 */ staticint 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? */ staticbool is_midi2_altset(struct usb_host_interface *hostif)
{ struct usb_ms_header_descriptor *ms_header =
(struct usb_ms_header_descriptor *)hostif->extra;
/* fill UMP Endpoint name string from USB descriptor */ staticvoid fill_ump_ep_name(struct snd_ump_endpoint *ump, struct usb_device *dev, int id)
{ int len;
/* 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 */ staticvoid 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); elseif (!*ump->info.name && dev->descriptor.iProduct)
fill_ump_ep_name(ump, dev, dev->descriptor.iProduct); /* fill fallback name */ if (!*ump->info.name)
scnprintf(ump->info.name, sizeof(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, conststruct snd_usb_audio_quirk *quirk, unsignedint usb_id)
{ struct snd_usb_midi2_interface *umidi; struct usb_host_interface *hostif; int err;
/* 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 altset; 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.