/* * usbmidi.c - ALSA USB MIDI driver * * Copyright (c) 2002-2009 Clemens Ladisch * All rights reserved. * * Based on the OSS usb-midi driver by NAGANO Daisuke, * NetBSD's umidi driver by Takuya SHIOZAKI, * the "USB Device Class Definition for MIDI Devices" by Roland * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed and/or modified under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE.
*/
/* * define this to log all USB packets
*/ /* #define DUMP_PACKETS */
/* * how long to wait after some USB errors, so that hub_wq can disconnect() us * without too many spurious errors
*/ #define ERROR_DELAY_JIFFIES (HZ / 10)
/* * Processes the data read from the device.
*/ staticvoid snd_usbmidi_in_urb_complete(struct urb *urb)
{ struct snd_usb_midi_in_endpoint *ep = urb->context;
if (urb->status == 0) {
dump_urb("received", urb->transfer_buffer, urb->actual_length);
ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
urb->actual_length);
} else { int err = snd_usbmidi_urb_error(urb); if (err < 0) { if (err != -ENODEV) {
ep->error_resubmit = 1;
mod_timer(&ep->umidi->error_timer,
jiffies + ERROR_DELAY_JIFFIES);
} return;
}
}
/* * This is called when some data should be transferred to the device * (from one or more substreams).
*/ staticvoid snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep)
{ unsignedint urb_index; struct urb *urb; unsignedlong flags;
spin_lock_irqsave(&ep->buffer_lock, flags); if (ep->umidi->disconnected) {
spin_unlock_irqrestore(&ep->buffer_lock, flags); return;
}
urb_index = ep->next_urb; for (;;) { if (!(ep->active_urbs & (1 << urb_index))) {
urb = ep->urbs[urb_index].urb;
urb->transfer_buffer_length = 0;
ep->umidi->usb_protocol_ops->output(ep, urb); if (urb->transfer_buffer_length == 0) break;
/* called after transfers had been interrupted due to some USB error */ staticvoid snd_usbmidi_error_timer(struct timer_list *t)
{ struct snd_usb_midi *umidi = timer_container_of(umidi, t, error_timer); unsignedint i, j;
spin_lock(&umidi->disc_lock); if (umidi->disconnected) {
spin_unlock(&umidi->disc_lock); return;
} for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in; if (in && in->error_resubmit) {
in->error_resubmit = 0; for (j = 0; j < INPUT_URBS; ++j) { if (atomic_read(&in->urbs[j]->use_count)) continue;
in->urbs[j]->dev = umidi->dev;
snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC);
}
} if (umidi->endpoints[i].out)
snd_usbmidi_do_output(umidi->endpoints[i].out);
}
spin_unlock(&umidi->disc_lock);
}
/* helper function to send static data that may not DMA-able */ staticint send_bulk_static_data(struct snd_usb_midi_out_endpoint *ep, constvoid *data, int len)
{ int err = 0; void *buf = kmemdup(data, len, GFP_KERNEL); if (!buf) return -ENOMEM;
dump_urb("sending", buf, len); if (ep->urbs[0].urb)
err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe,
buf, len, NULL, 250);
kfree(buf); return err;
}
/* * Standard USB MIDI protocol: see the spec. * Midiman protocol: like the standard protocol, but the control byte is the * fourth byte in each packet, and uses length instead of CIN.
*/
staticvoid snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ int i;
for (i = 0; i + 3 < buffer_length; i += 4) if (buffer[i] != 0) { int cable = buffer[i] >> 4; int length = snd_usbmidi_cin_length[buffer[i] & 0x0f];
snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
length);
}
}
staticvoid snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ int i;
for (i = 0; i + 3 < buffer_length; i += 4) if (buffer[i + 3] != 0) { int port = buffer[i + 3] >> 4; int length = buffer[i + 3] & 3;
snd_usbmidi_input_data(ep, port, &buffer[i], length);
}
}
/* * Buggy M-Audio device: running status on input results in a packet that has * the data bytes but not the status byte and that is marked with CIN 4.
*/ staticvoid snd_usbmidi_maudio_broken_running_status_input( struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ int i;
for (i = 0; i + 3 < buffer_length; i += 4) if (buffer[i] != 0) { int cable = buffer[i] >> 4;
u8 cin = buffer[i] & 0x0f; struct usbmidi_in_port *port = &ep->ports[cable]; int length;
length = snd_usbmidi_cin_length[cin]; if (cin == 0xf && buffer[i + 1] >= 0xf8)
; /* realtime msg: no running status change */ elseif (cin >= 0x8 && cin <= 0xe) /* channel msg */
port->running_status_length = length - 1; elseif (cin == 0x4 &&
port->running_status_length != 0 &&
buffer[i + 1] < 0x80) /* CIN 4 that is not a SysEx */
length = port->running_status_length; else /* * All other msgs cannot begin running status. * (A channel msg sent as two or three CIN 0xF * packets could in theory, but this device * doesn't use this format.)
*/
port->running_status_length = 0;
snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
length);
}
}
/* * QinHeng CH345 is buggy: every second packet inside a SysEx has not CIN 4 * but the previously seen CIN, but still with three data bytes.
*/ staticvoid ch345_broken_sysex_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ unsignedint i, cin, length;
for (i = 0; i + 3 < buffer_length; i += 4) { if (buffer[i] == 0 && i > 0) break;
cin = buffer[i] & 0x0f; if (ep->in_sysex &&
cin == ep->last_cin &&
(buffer[i + 1 + (cin == 0x6)] & 0x80) == 0)
cin = 0x4; #if 0 if (buffer[i + 1] == 0x90) { /* * Either a corrupted running status or a real note-on * message; impossible to detect reliably.
*/
} #endif
length = snd_usbmidi_cin_length[cin];
snd_usbmidi_input_data(ep, 0, &buffer[i + 1], length);
ep->in_sysex = cin == 0x4; if (!ep->in_sysex)
ep->last_cin = cin;
}
}
/* * CME protocol: like the standard protocol, but SysEx commands are sent as a * single USB packet preceded by a 0x0F byte, as are system realtime * messages and MIDI Active Sensing. * Also, multiple messages can be sent in the same packet.
*/ staticvoid snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ int remaining = buffer_length;
/* * CME send sysex, song position pointer, system realtime * and active sensing using CIN 0x0f, which in the standard * is only intended for single byte unparsed data. * So we need to interpret these here before sending them on. * By default, we assume single byte data, which is true * for system realtime (midi clock, start, stop and continue) * and active sensing, and handle the other (known) cases * separately. * In contrast to the standard, CME does not split sysex * into multiple 4-byte packets, but lumps everything together * into one. In addition, CME can string multiple messages * together in the same packet; pressing the Record button * on an UF6 sends a sysex message directly followed * by a song position pointer in the same packet. * For it to have any reasonable meaning, a sysex message * needs to be at least 3 bytes in length (0xf0, id, 0xf7), * corresponding to a packet size of 4 bytes, and the ones sent * by CME devices are 6 or 7 bytes, making the packet fragments * 7 or 8 bytes long (six or seven bytes plus preceding CN+CIN byte). * For the other types, the packet size is always 4 bytes, * as per the standard, with the data size being 3 for SPP * and 1 for the others. * Thus all packet fragments are at least 4 bytes long, so we can * skip anything that is shorter; this also conveniantly skips * packets with size 0, which CME devices continuously send when * they have nothing better to do. * Another quirk is that sometimes multiple messages are sent * in the same packet. This has been observed for midi clock * and active sensing i.e. 0x0f 0xf8 0x00 0x00 0x0f 0xfe 0x00 0x00, * but also multiple note ons/offs, and control change together * with MIDI clock. Similarly, some sysex messages are followed by * the song position pointer in the same packet, and occasionally * additionally by a midi clock or active sensing. * We handle this by looping over all data and parsing it along the way.
*/ while (remaining >= 4) { int source_length = 4; /* default */
if ((buffer[0] & 0x0f) == 0x0f) { int data_length = 1; /* default */
if (buffer[1] == 0xf0) { /* Sysex: Find EOX and send on whole message. */ /* To kick off the search, skip the first * two bytes (CN+CIN and SYSEX (0xf0).
*/
uint8_t *tmp_buf = buffer + 2; int tmp_length = remaining - 2;
/* * AKAI MPD16 protocol: * * For control port (endpoint 1): * ============================== * One or more chunks consisting of first byte of (0x10 | msg_len) and then a * SysEx message (msg_len=9 bytes long). * * For data port (endpoint 2): * =========================== * One or more chunks consisting of first byte of (0x20 | msg_len) and then a * MIDI message (msg_len bytes long) * * Messages sent: Active Sense, Note On, Poly Pressure, Control Change.
*/ staticvoid snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ unsignedint pos = 0; unsignedint len = (unsignedint)buffer_length; while (pos < len) { unsignedint port = (buffer[pos] >> 4) - 1; unsignedint msg_len = buffer[pos] & 0x0f;
pos++; if (pos + msg_len <= len && port < 2)
snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
pos += msg_len;
}
}
/* only try adding more data when there's space for at least 1 SysEx */ while (urb->transfer_buffer_length < buf_end) {
count = snd_rawmidi_transmit_peek(substream,
tmp, MAX_AKAI_SYSEX_LEN); if (!count) {
ep->ports[0].active = 0; return;
} /* try to skip non-SysEx data */ for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++)
;
if (pos > 0) {
snd_rawmidi_transmit_ack(substream, pos); continue;
}
/* look for the start or end marker */ for (end = 1; end < count && tmp[end] < 0xF0; end++)
;
/* next SysEx started before the end of current one */ if (end < count && tmp[end] == 0xF0) { /* it's incomplete - drop it */
snd_rawmidi_transmit_ack(substream, end); continue;
} /* SysEx complete */ if (end < count && tmp[end] == 0xF7) { /* queue it, ack it, and get the next one */
count = end + 1;
msg[0] = 0x10 | count;
memcpy(&msg[1], tmp, count);
snd_rawmidi_transmit_ack(substream, count);
urb->transfer_buffer_length += count + 1;
msg += count + 1; continue;
} /* less than 9 bytes and no end byte - wait for more */ if (count < MAX_AKAI_SYSEX_LEN) {
ep->ports[0].active = 0; return;
} /* 9 bytes and no end marker in sight - malformed, skip it */
snd_rawmidi_transmit_ack(substream, count);
}
}
/* * Novation USB MIDI protocol: number of data bytes is in the first byte * (when receiving) (+1!) or in the second byte (when sending); data begins * at the third byte.
*/
/* * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching.
*/
staticvoid snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint *ep)
{ staticconst u8 init_data[] = { /* initialization magic: "get version" */
0xf0,
0x00, 0x20, 0x31, /* Emagic */
0x64, /* Unitor8 */
0x0b, /* version number request */
0x00, /* command version */
0x00, /* EEPROM, box 0 */
0xf7
};
send_bulk_static_data(ep, init_data, sizeof(init_data)); /* while we're at it, pour on more magic */
send_bulk_static_data(ep, init_data, sizeof(init_data));
}
staticvoid snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint *ep)
{ staticconst u8 finish_data[] = { /* switch to patch mode with last preset */
0xf0,
0x00, 0x20, 0x31, /* Emagic */
0x64, /* Unitor8 */
0x10, /* patch switch command */
0x00, /* command version */
0x7f, /* to all boxes */
0x40, /* last preset in EEPROM */
0xf7
};
send_bulk_static_data(ep, finish_data, sizeof(finish_data));
}
staticvoid snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{ int i;
/* FF indicates end of valid data */ for (i = 0; i < buffer_length; ++i) if (buffer[i] == 0xff) {
buffer_length = i; break;
}
/* handle F5 at end of last buffer */ if (ep->seen_f5) goto switch_port;
while (buffer_length > 0) { /* determine size of data until next F5 */ for (i = 0; i < buffer_length; ++i) if (buffer[i] == 0xf5) break;
snd_usbmidi_input_data(ep, ep->current_port, buffer, i);
buffer += i;
buffer_length -= i;
staticvoid snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint *ep, struct urb *urb)
{ int port0 = ep->current_port;
uint8_t *buf = urb->transfer_buffer; int buf_free = ep->max_transfer; int length, i;
for (i = 0; i < 0x10; ++i) { /* round-robin, starting at the last current port */ int portnum = (port0 + i) & 15; struct usbmidi_out_port *port = &ep->ports[portnum];
if (!port->active) continue; if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) {
port->active = 0; continue;
}
staticint substream_open(struct snd_rawmidi_substream *substream, int dir, int open)
{ struct snd_usb_midi *umidi = substream->rmidi->private_data; struct snd_kcontrol *ctl;
down_read(&umidi->disc_rwsem); if (umidi->disconnected) {
up_read(&umidi->disc_rwsem); return open ? -ENODEV : 0;
}
mutex_lock(&umidi->mutex); if (open) { if (!umidi->opened[0] && !umidi->opened[1]) { if (umidi->roland_load_ctl) {
ctl = umidi->roland_load_ctl;
ctl->vd[0].access |=
SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(umidi->card,
SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
update_roland_altsetting(umidi);
}
}
umidi->opened[dir]++; if (umidi->opened[1])
snd_usbmidi_input_start(&umidi->list);
} else {
umidi->opened[dir]--; if (!umidi->opened[1])
snd_usbmidi_input_stop(&umidi->list); if (!umidi->opened[0] && !umidi->opened[1]) { if (umidi->roland_load_ctl) {
ctl = umidi->roland_load_ctl;
ctl->vd[0].access &=
~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(umidi->card,
SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
}
}
}
mutex_unlock(&umidi->mutex);
up_read(&umidi->disc_rwsem); return 0;
}
staticint snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
{ struct snd_usb_midi *umidi = substream->rmidi->private_data; struct usbmidi_out_port *port = NULL; int i, j;
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) if (umidi->endpoints[i].out) for (j = 0; j < 0x10; ++j) if (umidi->endpoints[i].out->ports[j].substream == substream) {
port = &umidi->endpoints[i].out->ports[j]; break;
} if (!port) return -ENXIO;
if (ep->umidi->disconnected) return; /* * The substream buffer is empty, but some data might still be in the * currently active URBs, so we have to wait for those to complete.
*/
spin_lock_irq(&ep->buffer_lock);
drain_urbs = ep->active_urbs; if (drain_urbs) {
ep->drain_urbs |= drain_urbs; do {
prepare_to_wait(&ep->drain_wait, &wait,
TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&ep->buffer_lock);
timeout = schedule_timeout(timeout);
spin_lock_irq(&ep->buffer_lock);
drain_urbs &= ep->drain_urbs;
} while (drain_urbs && timeout);
finish_wait(&ep->drain_wait, &wait);
}
port->active = 0;
spin_unlock_irq(&ep->buffer_lock);
}
/* * Frees an input endpoint. * May be called when ep hasn't been initialized completely.
*/ staticvoid snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint *ep)
{ unsignedint i;
for (i = 0; i < INPUT_URBS; ++i) if (ep->urbs[i])
free_urb_and_buffer(ep->umidi, ep->urbs[i],
ep->urbs[i]->transfer_buffer_length);
kfree(ep);
}
/* * Frees an output endpoint. * May be called when ep hasn't been initialized completely.
*/ staticvoid snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep)
{ unsignedint i;
for (i = 0; i < OUTPUT_URBS; ++i) if (ep->urbs[i].urb) {
free_urb_and_buffer(ep->umidi, ep->urbs[i].urb,
ep->max_transfer);
ep->urbs[i].urb = NULL;
}
}
if (!umidi->disconnected)
snd_usbmidi_disconnect(&umidi->list);
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
kfree(ep->out);
}
mutex_destroy(&umidi->mutex);
kfree(umidi);
}
/* * Unlinks all URBs (must be done before the usb_device is deleted).
*/ void snd_usbmidi_disconnect(struct list_head *p)
{ struct snd_usb_midi *umidi; unsignedint i, j;
umidi = list_entry(p, struct snd_usb_midi, list); /* * an URB's completion handler may start the timer and * a timer may submit an URB. To reliably break the cycle * a flag under lock must be used
*/
down_write(&umidi->disc_rwsem);
spin_lock_irq(&umidi->disc_lock);
umidi->disconnected = 1;
spin_unlock_irq(&umidi->disc_lock);
up_write(&umidi->disc_rwsem);
timer_shutdown_sync(&umidi->error_timer);
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; if (ep->out)
cancel_work_sync(&ep->out->work); if (ep->out) { for (j = 0; j < OUTPUT_URBS; ++j)
usb_kill_urb(ep->out->urbs[j].urb); if (umidi->usb_protocol_ops->finish_out_endpoint)
umidi->usb_protocol_ops->finish_out_endpoint(ep->out);
ep->out->active_urbs = 0; if (ep->out->drain_urbs) {
ep->out->drain_urbs = 0;
wake_up(&ep->out->drain_wait);
}
} if (ep->in) for (j = 0; j < INPUT_URBS; ++j)
usb_kill_urb(ep->in->urbs[j]); /* free endpoints here; later call can result in Oops */ if (ep->out)
snd_usbmidi_out_endpoint_clear(ep->out); if (ep->in) {
snd_usbmidi_in_endpoint_delete(ep->in);
ep->in = NULL;
}
}
}
EXPORT_SYMBOL(snd_usbmidi_disconnect);
next: if (!extra[0]) break;
extralen -= extra[0];
extra += extra[0];
} return 0;
}
staticvoid snd_usbmidi_init_substream(struct snd_usb_midi *umidi, int stream, int number, int jack_id, struct snd_rawmidi_substream **rsubstream)
{ struct port_info *port_info; constchar *name_format; struct usb_interface *intf; struct usb_host_interface *hostif;
uint8_t jack_name_buf[32];
uint8_t *default_jack_name = "MIDI";
uint8_t *jack_name = default_jack_name;
uint8_t iJack; int res;
struct snd_rawmidi_substream *substream =
snd_usbmidi_find_substream(umidi, stream, number); if (!substream) {
dev_err(&umidi->dev->dev, "substream %d:%d not found\n", stream,
number); return;
}
intf = umidi->iface; if (intf && jack_id >= 0) {
hostif = intf->cur_altsetting;
iJack = find_usb_ijack(hostif, jack_id); if (iJack != 0) {
res = usb_string(umidi->dev, iJack, jack_name_buf,
ARRAY_SIZE(jack_name_buf)); if (res)
jack_name = jack_name_buf;
}
}
port_info = find_port_info(umidi, number); if (port_info || jack_name == default_jack_name ||
strncmp(umidi->card->shortname, jack_name, strlen(umidi->card->shortname)) != 0) {
name_format = port_info ? port_info->name :
(jack_name != default_jack_name ? "%s %s" : "%s %s %d");
snprintf(substream->name, sizeof(substream->name),
name_format, umidi->card->shortname, jack_name, number + 1);
} else { /* The manufacturer included the iProduct name in the jack * name, do not use both
*/
strscpy(substream->name, jack_name);
}
*rsubstream = substream;
}
/* * Creates the endpoints and their ports.
*/ staticint snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi, struct snd_usb_midi_endpoint_info *endpoints)
{ int i, j, err; int out_ports = 0, in_ports = 0;
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { if (endpoints[i].out_cables) {
err = snd_usbmidi_out_endpoint_create(umidi,
&endpoints[i],
&umidi->endpoints[i]); if (err < 0) return err;
} if (endpoints[i].in_cables) {
err = snd_usbmidi_in_endpoint_create(umidi,
&endpoints[i],
&umidi->endpoints[i]); if (err < 0) return err;
}
/* * On Roland devices, use the second alternate setting to be able to use * the interrupt input endpoint.
*/ staticvoid snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi *umidi)
{ struct usb_interface *intf; struct usb_host_interface *hostif; struct usb_interface_descriptor *intfd;
intf = umidi->iface; if (!intf || intf->num_altsetting != 2) return;
hostif = &intf->altsetting[1];
intfd = get_iface_desc(hostif); /* If either or both of the endpoints support interrupt transfer, * then use the alternate setting
*/ if (intfd->bNumEndpoints != 2 ||
!((get_endpoint(hostif, 0)->bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT ||
(get_endpoint(hostif, 1)->bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) return;
dev_dbg(&umidi->dev->dev, "switching to altsetting %d with int ep\n",
intfd->bAlternateSetting);
usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
intfd->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.