📄 usbmidi.c
字号:
/* * usbmidi.c - ALSA USB MIDI driver * * Copyright (c) 2002-2007 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. */#include <sound/driver.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/bitops.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/string.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/timer.h>#include <linux/usb.h>#include <sound/core.h>#include <sound/rawmidi.h>#include <sound/asequencer.h>#include "usbaudio.h"/* * define this to log all USB packets *//* #define DUMP_PACKETS *//* * how long to wait after some USB errors, so that khubd can disconnect() us * without too many spurious errors */#define ERROR_DELAY_JIFFIES (HZ / 10)MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");MODULE_DESCRIPTION("USB Audio/MIDI helper module");MODULE_LICENSE("Dual BSD/GPL");struct usb_ms_header_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bDescriptorSubtype; __u8 bcdMSC[2]; __le16 wTotalLength;} __attribute__ ((packed));struct usb_ms_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bDescriptorSubtype; __u8 bNumEmbMIDIJack; __u8 baAssocJackID[0];} __attribute__ ((packed));struct snd_usb_midi_in_endpoint;struct snd_usb_midi_out_endpoint;struct snd_usb_midi_endpoint;struct usb_protocol_ops { void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int); void (*output)(struct snd_usb_midi_out_endpoint*); void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t); void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint*); void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint*);};struct snd_usb_midi { struct snd_usb_audio *chip; struct usb_interface *iface; const struct snd_usb_audio_quirk *quirk; struct snd_rawmidi *rmidi; struct usb_protocol_ops* usb_protocol_ops; struct list_head list; struct timer_list error_timer; struct snd_usb_midi_endpoint { struct snd_usb_midi_out_endpoint *out; struct snd_usb_midi_in_endpoint *in; } endpoints[MIDI_MAX_ENDPOINTS]; unsigned long input_triggered;};struct snd_usb_midi_out_endpoint { struct snd_usb_midi* umidi; struct urb* urb; int urb_active; int max_transfer; /* size of urb buffer */ struct tasklet_struct tasklet; spinlock_t buffer_lock; struct usbmidi_out_port { struct snd_usb_midi_out_endpoint* ep; struct snd_rawmidi_substream *substream; int active; uint8_t cable; /* cable number << 4 */ uint8_t state;#define STATE_UNKNOWN 0#define STATE_1PARAM 1#define STATE_2PARAM_1 2#define STATE_2PARAM_2 3#define STATE_SYSEX_0 4#define STATE_SYSEX_1 5#define STATE_SYSEX_2 6 uint8_t data[2]; } ports[0x10]; int current_port;};struct snd_usb_midi_in_endpoint { struct snd_usb_midi* umidi; struct urb* urb; struct usbmidi_in_port { struct snd_rawmidi_substream *substream; u8 running_status_length; } ports[0x10]; u8 seen_f5; u8 error_resubmit; int current_port;};static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep);static const uint8_t snd_usbmidi_cin_length[] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1};/* * Submits the URB, with error handling. */static int snd_usbmidi_submit_urb(struct urb* urb, gfp_t flags){ int err = usb_submit_urb(urb, flags); if (err < 0 && err != -ENODEV) snd_printk(KERN_ERR "usb_submit_urb: %d\n", err); return err;}/* * Error handling for URB completion functions. */static int snd_usbmidi_urb_error(int status){ switch (status) { /* manually unlinked, or device gone */ case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: case -ENODEV: return -ENODEV; /* errors that might occur during unplugging */ case -EPROTO: case -ETIME: case -EILSEQ: return -EIO; default: snd_printk(KERN_ERR "urb status %d\n", status); return 0; /* continue */ }}/* * Receives a chunk of MIDI data. */static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint* ep, int portidx, uint8_t* data, int length){ struct usbmidi_in_port* port = &ep->ports[portidx]; if (!port->substream) { snd_printd("unexpected port %d!\n", portidx); return; } if (!test_bit(port->substream->number, &ep->umidi->input_triggered)) return; snd_rawmidi_receive(port->substream, data, length);}#ifdef DUMP_PACKETSstatic void dump_urb(const char *type, const u8 *data, int length){ snd_printk(KERN_DEBUG "%s packet: [", type); for (; length > 0; ++data, --length) printk(" %02x", *data); printk(" ]\n");}#else#define dump_urb(type, data, length) /* nothing */#endif/* * Processes the data read from the device. */static void 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->status); if (err < 0) { if (err != -ENODEV) { ep->error_resubmit = 1; mod_timer(&ep->umidi->error_timer, jiffies + ERROR_DELAY_JIFFIES); } return; } } urb->dev = ep->umidi->chip->dev; snd_usbmidi_submit_urb(urb, GFP_ATOMIC);}static void snd_usbmidi_out_urb_complete(struct urb* urb){ struct snd_usb_midi_out_endpoint* ep = urb->context; spin_lock(&ep->buffer_lock); ep->urb_active = 0; spin_unlock(&ep->buffer_lock); if (urb->status < 0) { int err = snd_usbmidi_urb_error(urb->status); if (err < 0) { if (err != -ENODEV) mod_timer(&ep->umidi->error_timer, jiffies + ERROR_DELAY_JIFFIES); return; } } snd_usbmidi_do_output(ep);}/* * This is called when some data should be transferred to the device * (from one or more substreams). */static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep){ struct urb* urb = ep->urb; unsigned long flags; spin_lock_irqsave(&ep->buffer_lock, flags); if (ep->urb_active || ep->umidi->chip->shutdown) { spin_unlock_irqrestore(&ep->buffer_lock, flags); return; } urb->transfer_buffer_length = 0; ep->umidi->usb_protocol_ops->output(ep); if (urb->transfer_buffer_length > 0) { dump_urb("sending", urb->transfer_buffer, urb->transfer_buffer_length); urb->dev = ep->umidi->chip->dev; ep->urb_active = snd_usbmidi_submit_urb(urb, GFP_ATOMIC) >= 0; } spin_unlock_irqrestore(&ep->buffer_lock, flags);}static void snd_usbmidi_out_tasklet(unsigned long data){ struct snd_usb_midi_out_endpoint* ep = (struct snd_usb_midi_out_endpoint *) data; snd_usbmidi_do_output(ep);}/* called after transfers had been interrupted due to some USB error */static void snd_usbmidi_error_timer(unsigned long data){ struct snd_usb_midi *umidi = (struct snd_usb_midi *)data; int i; 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; in->urb->dev = umidi->chip->dev; snd_usbmidi_submit_urb(in->urb, GFP_ATOMIC); } if (umidi->endpoints[i].out) snd_usbmidi_do_output(umidi->endpoints[i].out); }}/* helper function to send static data that may not DMA-able */static int send_bulk_static_data(struct snd_usb_midi_out_endpoint* ep, const void *data, int len){ int err; void *buf = kmemdup(data, len, GFP_KERNEL); if (!buf) return -ENOMEM; dump_urb("sending", buf, len); err = usb_bulk_msg(ep->umidi->chip->dev, ep->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. */static void 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); }}static void 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. */static void 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 */ else if (cin >= 0x8 && cin <= 0xe) /* channel msg */ port->running_status_length = length - 1; else if (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); }}/* * CME protocol: like the standard protocol, but SysEx commands are sent as a * single USB packet preceded by a 0x0F byte. */static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep, uint8_t *buffer, int buffer_length){ if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f) snd_usbmidi_standard_input(ep, buffer, buffer_length); else snd_usbmidi_input_data(ep, buffer[0] >> 4, &buffer[1], buffer_length - 1);}/* * Adds one USB MIDI packet to the output buffer. */static void snd_usbmidi_output_standard_packet(struct urb* urb, uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3){ uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length; buf[0] = p0; buf[1] = p1; buf[2] = p2; buf[3] = p3; urb->transfer_buffer_length += 4;}/* * Adds one Midiman packet to the output buffer. */static void snd_usbmidi_output_midiman_packet(struct urb* urb, uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3){ uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -