usbmidi.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,273 行 · 第 1/3 页

C
1,273
字号
/* * usbmidi.c - ALSA USB MIDI driver * * Copyright (c) 2002-2004 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/interrupt.h>#include <linux/spinlock.h>#include <linux/string.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/usb.h>#include <sound/core.h>#include <sound/minors.h>#include <sound/rawmidi.h>#include "usbaudio.h"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];	__u16 wTotalLength;} __attribute__ ((packed));struct usb_ms_endpoint_descriptor {	__u8  bLength;	__u8  bDescriptorType;	__u8  bDescriptorSubtype;	__u8  bNumEmbMIDIJack;	__u8  baAssocJackID[0];} __attribute__ ((packed));typedef struct snd_usb_midi snd_usb_midi_t;typedef struct snd_usb_midi_endpoint snd_usb_midi_endpoint_t;typedef struct snd_usb_midi_out_endpoint snd_usb_midi_out_endpoint_t;typedef struct snd_usb_midi_in_endpoint snd_usb_midi_in_endpoint_t;typedef struct usbmidi_out_port usbmidi_out_port_t;typedef struct usbmidi_in_port usbmidi_in_port_t;struct snd_usb_midi {	snd_usb_audio_t *chip;	struct usb_interface *iface;	const snd_usb_audio_quirk_t *quirk;	snd_rawmidi_t* rmidi;	struct list_head list;	struct snd_usb_midi_endpoint {		snd_usb_midi_out_endpoint_t *out;		snd_usb_midi_in_endpoint_t *in;	} endpoints[MIDI_MAX_ENDPOINTS];};struct snd_usb_midi_out_endpoint {	snd_usb_midi_t* umidi;	struct urb* urb;	int max_transfer;		/* size of urb buffer */	struct tasklet_struct tasklet;	spinlock_t buffer_lock;	struct usbmidi_out_port {		snd_usb_midi_out_endpoint_t* ep;		snd_rawmidi_substream_t* 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];};struct snd_usb_midi_in_endpoint {	snd_usb_midi_t* umidi;	struct urb* urb;	struct usbmidi_in_port {		snd_rawmidi_substream_t* substream;	} ports[0x10];};static void snd_usbmidi_do_output(snd_usb_midi_out_endpoint_t* 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, int 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){	if (status == -ENOENT)		return status; /* killed */	if (status == -EILSEQ ||	    status == -ECONNRESET ||	    status == -ETIMEDOUT)		return -ENODEV; /* device removed/shutdown */	snd_printk(KERN_ERR "urb status %d\n", status);	return 0; /* continue */}/* * Receives a USB MIDI packet. */static void snd_usbmidi_input_packet(snd_usb_midi_in_endpoint_t* ep,				     uint8_t packet[4]){	int cable = packet[0] >> 4;	usbmidi_in_port_t* port = &ep->ports[cable];	if (!port->substream) {		snd_printd("unexpected port %d!\n", cable);		return;	}	if (!port->substream->runtime ||	    !port->substream->runtime->trigger)		return;	snd_rawmidi_receive(port->substream, &packet[1],			    snd_usbmidi_cin_length[packet[0] & 0x0f]);}/* * Processes the data read from the device. */static void snd_usbmidi_in_urb_complete(struct urb* urb, struct pt_regs *regs){	snd_usb_midi_in_endpoint_t* ep = urb->context;	if (urb->status == 0) {		uint8_t* buffer = (uint8_t*)ep->urb->transfer_buffer;		int i;		for (i = 0; i + 4 <= urb->actual_length; i += 4)			if (buffer[i] != 0)				snd_usbmidi_input_packet(ep, &buffer[i]);	} else {		if (snd_usbmidi_urb_error(urb->status) < 0)			return;	}	if (usb_pipe_needs_resubmit(urb->pipe)) {		urb->dev = ep->umidi->chip->dev;		snd_usbmidi_submit_urb(urb, GFP_ATOMIC);	}}/* * Converts the data read from a Midiman device to standard USB MIDI packets. */static void snd_usbmidi_in_midiman_complete(struct urb* urb, struct pt_regs *regs){	if (urb->status == 0) {		uint8_t* buffer = (uint8_t*)urb->transfer_buffer;		int i;		for (i = 0; i + 4 <= urb->actual_length; i += 4) {			if (buffer[i + 3] != 0) {				/*				 * snd_usbmidi_input_packet() doesn't check the				 * contents of the message, so we simply use				 * some random CIN with the desired length.				 */				static const uint8_t cin[4] = {					0x0, 0xf, 0x2, 0x3				};				uint8_t ctl = buffer[i + 3];				buffer[i + 3] = buffer[i + 2];				buffer[i + 2] = buffer[i + 1];				buffer[i + 1] = buffer[i + 0];				buffer[i + 0] = (ctl & 0xf0) | cin[ctl & 3];			} else {				buffer[i + 0] = 0;			}		}	}	snd_usbmidi_in_urb_complete(urb, regs);}static void snd_usbmidi_out_urb_complete(struct urb* urb, struct pt_regs *regs){	snd_usb_midi_out_endpoint_t* ep = urb->context;	if (urb->status < 0) {		if (snd_usbmidi_urb_error(urb->status) < 0)			return;	}	snd_usbmidi_do_output(ep);}/* * Converts standard USB MIDI packets to what Midman devices expect. */static void snd_usbmidi_convert_to_midiman(struct urb* urb){	uint8_t* buffer = (uint8_t*)urb->transfer_buffer;	int i;	for (i = 0; i + 4 <= urb->transfer_buffer_length; i += 4) {		uint8_t cin = buffer[i];		buffer[i + 0] = buffer[i + 1];		buffer[i + 1] = buffer[i + 2];		buffer[i + 2] = buffer[i + 3];		buffer[i + 3] = (cin & 0xf0) | snd_usbmidi_cin_length[cin & 0x0f];	}}/* * Adds one USB MIDI packet to the output buffer. */static inline void output_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;}/* * Converts MIDI commands to USB MIDI packets. */static void snd_usbmidi_transmit_byte(usbmidi_out_port_t* port,				      uint8_t b, struct urb* urb){	uint8_t p0 = port->cable;	if (b >= 0xf8) {		output_packet(urb, p0 | 0x0f, b, 0, 0);	} else if (b >= 0xf0) {		switch (b) {		case 0xf0:			port->data[0] = b;			port->state = STATE_SYSEX_1;			break;		case 0xf1:		case 0xf3:			port->data[0] = b;			port->state = STATE_1PARAM;			break;		case 0xf2:			port->data[0] = b;			port->state = STATE_2PARAM_1;			break;		case 0xf4:		case 0xf5:			port->state = STATE_UNKNOWN;			break;		case 0xf6:			output_packet(urb, p0 | 0x05, 0xf6, 0, 0);			port->state = STATE_UNKNOWN;			break;		case 0xf7:			switch (port->state) {			case STATE_SYSEX_0:				output_packet(urb, p0 | 0x05, 0xf7, 0, 0);				break;			case STATE_SYSEX_1:				output_packet(urb, p0 | 0x06, port->data[0], 0xf7, 0);				break;			case STATE_SYSEX_2:				output_packet(urb, p0 | 0x07, port->data[0], port->data[1], 0xf7);				break;			}			port->state = STATE_UNKNOWN;			break;		}	} else if (b >= 0x80) {		port->data[0] = b;		if (b >= 0xc0 && b <= 0xdf)			port->state = STATE_1PARAM;		else			port->state = STATE_2PARAM_1;	} else { /* b < 0x80 */		switch (port->state) {		case STATE_1PARAM:			if (port->data[0] < 0xf0) {				p0 |= port->data[0] >> 4;			} else {				p0 |= 0x02;				port->state = STATE_UNKNOWN;			}			output_packet(urb, p0, port->data[0], b, 0);			break;		case STATE_2PARAM_1:			port->data[1] = b;			port->state = STATE_2PARAM_2;			break;		case STATE_2PARAM_2:			if (port->data[0] < 0xf0) {				p0 |= port->data[0] >> 4;				port->state = STATE_2PARAM_1;			} else {				p0 |= 0x03;				port->state = STATE_UNKNOWN;			}			output_packet(urb, p0, port->data[0], port->data[1], b);			break;		case STATE_SYSEX_0:			port->data[0] = b;			port->state = STATE_SYSEX_1;			break;		case STATE_SYSEX_1:			port->data[1] = b;			port->state = STATE_SYSEX_2;			break;		case STATE_SYSEX_2:			output_packet(urb, p0 | 0x04, port->data[0], port->data[1], b);			port->state = STATE_SYSEX_0;			break;		}	}}/* * Moves data from one substream buffer to the URB transfer buffer. */static void snd_usbmidi_transmit(snd_usb_midi_out_endpoint_t* ep, int port_idx){	struct urb* urb = ep->urb;	usbmidi_out_port_t* port = &ep->ports[port_idx];	while (urb->transfer_buffer_length < ep->max_transfer) {		uint8_t b;		if (snd_rawmidi_transmit_peek(port->substream, &b, 1) != 1) {			port->active = 0;			break;		}		snd_usbmidi_transmit_byte(port, b, urb);		snd_rawmidi_transmit_ack(port->substream, 1);	}}/* * This is called when some data should be transferred to the device * (from one or more substreams). */static void snd_usbmidi_do_output(snd_usb_midi_out_endpoint_t* ep){	int p;	struct urb* urb = ep->urb;	unsigned long flags;	spin_lock_irqsave(&ep->buffer_lock, flags);	if (urb->status == -EINPROGRESS || ep->umidi->chip->shutdown) {		spin_unlock_irqrestore(&ep->buffer_lock, flags);		return;	}	urb->transfer_buffer_length = 0;	for (p= 0; p < 0x10; ++p)		if (ep->ports[p].active)			snd_usbmidi_transmit(ep, p);	if (urb->transfer_buffer_length > 0) {		if (ep->umidi->quirk && ep->umidi->quirk->type == QUIRK_MIDI_MIDIMAN)			snd_usbmidi_convert_to_midiman(urb);		urb->dev = ep->umidi->chip->dev;		snd_usbmidi_submit_urb(urb, GFP_ATOMIC);	}	spin_unlock_irqrestore(&ep->buffer_lock, flags);}static void snd_usbmidi_out_tasklet(unsigned long data){	snd_usb_midi_out_endpoint_t* ep = (snd_usb_midi_out_endpoint_t *) data;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?