📄 usb-midi.c
字号:
/* usb-midi.c -- USB-MIDI driver Copyright (C) 2001 NAGANO Daisuke <breeze.nagano@nifty.ne.jp> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. This driver is based on: - 'Universal Serial Bus Device Class Definition for MIDI Device' - linux/drivers/sound/es1371.c, linux/drivers/usb/audio.c - alsa/lowlevel/pci/cs64xx.c - umidi.c for NetBSD *//* ------------------------------------------------------------------------- */#include <linux/module.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/list.h>#include <linux/slab.h>#include <linux/usb.h>#include <linux/poll.h>#include <linux/sound.h>#include <linux/init.h>#include <asm/semaphore.h>#include "usb-midi.h"/* ------------------------------------------------------------------------- *//* More verbose on syslog */#undef MIDI_DEBUG#define MIDI_IN_BUFSIZ 1024#define HAVE_SUPPORT_USB_MIDI_CLASS#undef HAVE_SUPPORT_ALSA/* ------------------------------------------------------------------------- */static int singlebyte = 0;MODULE_PARM(singlebyte,"i");MODULE_PARM_DESC(singlebyte,"Enable sending MIDI messages with single message packet");static int maxdevices = 4;MODULE_PARM(maxdevices,"i");MODULE_PARM_DESC(maxdevices,"Max number of allocatable MIDI device");static int uvendor = -1;MODULE_PARM(uvendor,"i");MODULE_PARM_DESC(uvendor, "The USB Vendor ID of a semi-compliant interface");static int uproduct = -1;MODULE_PARM(uproduct,"i");MODULE_PARM_DESC(uproduct, "The USB Product ID of a semi-compliant interface");static int uinterface = -1;MODULE_PARM(uinterface,"i");MODULE_PARM_DESC(uinterface, "The Interface number of a semi-compliant interface");static int ualt = -1;MODULE_PARM(ualt,"i");MODULE_PARM_DESC(ualt, "The optional alternative setting of a semi-compliant interface");static int umin = -1;MODULE_PARM(umin,"i");MODULE_PARM_DESC(umin, "The input endpoint of a semi-compliant interface");static int umout = -1;MODULE_PARM(umout,"i");MODULE_PARM_DESC(umout, "The output endpoint of a semi-compliant interface");static int ucable = -1;MODULE_PARM(ucable,"i");MODULE_PARM_DESC(ucable, "The cable number used for a semi-compliant interface");/** Note -- the usb_string() returns only Latin-1 characters. * (unicode chars <= 255). To support Japanese, a unicode16LE-to-EUC or * unicode16LE-to-JIS routine is needed to wrap around usb_get_string(). **/static unsigned short ulangid = 0x0409; /** 0x0411 for Japanese **/MODULE_PARM(ulangid,"h");MODULE_PARM_DESC(ulangid, "The optional preferred USB Language ID for all devices");MODULE_AUTHOR("NAGANO Daisuke <breeze.nagano@nifty.ne.jp>");MODULE_DESCRIPTION("USB-MIDI driver");MODULE_LICENSE("GPL");/* ------------------------------------------------------------------------- *//** MIDIStreaming Class-Specific Interface Descriptor Subtypes **/#define MS_DESCRIPTOR_UNDEFINED 0#define MS_HEADER 1#define MIDI_IN_JACK 2#define MIDI_OUT_JACK 3/* Spec reads: ELEMENT */#define ELEMENT_DESCRIPTOR 4#define MS_HEADER_LENGTH 7/** MIDIStreaming Class-Specific Endpoint Descriptor Subtypes **/#define DESCRIPTOR_UNDEFINED 0/* Spec reads: MS_GENERAL */#define MS_GENERAL_ENDPOINT 1/** MIDIStreaming MIDI IN and OUT Jack Types **/#define JACK_TYPE_UNDEFINED 0/* Spec reads: EMBEDDED */#define EMBEDDED_JACK 1/* Spec reads: EXTERNAL */#define EXTERNAL_JACK 2/* structure summary usb_midi_state usb_device | | *| *| per ep in_ep out_ep | | *| *| per cable min mout | | (cable to device pairing magic) | | usb_midi_dev dev_id (major,minor) == file->private_data*//* usb_midi_state: corresponds to a USB-MIDI module */struct usb_midi_state { struct list_head mididev; struct usb_device *usbdev; struct list_head midiDevList; struct list_head inEndpointList; struct list_head outEndpointList; spinlock_t lock; unsigned int count; /* usage counter */};/* midi_out_endpoint: corresponds to an output endpoint */struct midi_out_endpoint { struct list_head list; struct usb_device *usbdev; int endpoint; spinlock_t lock; wait_queue_head_t wait; unsigned char *buf; int bufWrPtr; int bufSize; struct urb *urb;};/* midi_in_endpoint: corresponds to an input endpoint */struct midi_in_endpoint { struct list_head list; struct usb_device *usbdev; int endpoint; spinlock_t lock; wait_queue_head_t wait; struct usb_mididev *cables[16]; // cables open for read int readers; // number of cables open for read struct urb *urb; unsigned char *recvBuf; int recvBufSize; int urbSubmitted; //FIXME: == readers > 0};/* usb_mididev: corresponds to a logical device */struct usb_mididev { struct list_head list; struct usb_midi_state *midi; int dev_midi; mode_t open_mode; struct { struct midi_in_endpoint *ep; int cableId; // as we are pushing data from usb_bulk_read to usb_midi_read,// we need a larger, cyclic buffer here. unsigned char buf[MIDI_IN_BUFSIZ]; int bufRdPtr; int bufWrPtr; int bufRemains; } min; struct { struct midi_out_endpoint *ep; int cableId; unsigned char buf[3]; int bufPtr; int bufRemains; int isInExclusive; unsigned char lastEvent; } mout; int singlebyte;};/** Map the high nybble of MIDI voice messages to number of Message bytes. * High nyble ranges from 0x8 to 0xe */static int remains_80e0[] = { 3, /** 0x8X Note Off **/ 3, /** 0x9X Note On **/ 3, /** 0xAX Poly-key pressure **/ 3, /** 0xBX Control Change **/ 2, /** 0xCX Program Change **/ 2, /** 0xDX Channel pressure **/ 3 /** 0xEX PitchBend Change **/};/** Map the messages to a number of Message bytes. * **/static int remains_f0f6[] = { 0, /** 0xF0 **/ 2, /** 0XF1 **/ 3, /** 0XF2 **/ 2, /** 0XF3 **/ 2, /** 0XF4 (Undefined by MIDI Spec, and subject to change) **/ 2, /** 0XF5 (Undefined by MIDI Spec, and subject to change) **/ 1 /** 0XF6 **/};/** Map the messages to a CIN (Code Index Number). * **/static int cin_f0ff[] = { 4, /** 0xF0 System Exclusive Message Start (special cases may be 6 or 7) */ 2, /** 0xF1 **/ 3, /** 0xF2 **/ 2, /** 0xF3 **/ 2, /** 0xF4 **/ 2, /** 0xF5 **/ 5, /** 0xF6 **/ 5, /** 0xF7 End of System Exclusive Message (May be 6 or 7) **/ 5, /** 0xF8 **/ 5, /** 0xF9 **/ 5, /** 0xFA **/ 5, /** 0xFB **/ 5, /** 0xFC **/ 5, /** 0xFD **/ 5, /** 0xFE **/ 5 /** 0xFF **/};/** Map MIDIStreaming Event packet Code Index Number (low nybble of byte 0) * to the number of bytes of valid MIDI data. * * CIN of 0 and 1 are NOT USED in MIDIStreaming 1.0. * **/static int cin_to_len[] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1};/* ------------------------------------------------------------------------- */static struct list_head mididevs = LIST_HEAD_INIT(mididevs);static DECLARE_MUTEX(open_sem);static DECLARE_WAIT_QUEUE_HEAD(open_wait);/* ------------------------------------------------------------------------- */static void usb_write_callback(struct urb *urb, struct pt_regs *regs){ struct midi_out_endpoint *ep = (struct midi_out_endpoint *)urb->context; if ( waitqueue_active( &ep->wait ) ) wake_up_interruptible( &ep->wait );}static int usb_write( struct midi_out_endpoint *ep, unsigned char *buf, int len ){ struct usb_device *d; int pipe; int ret = 0; int status; int maxretry = 50; DECLARE_WAITQUEUE(wait,current); init_waitqueue_head(&ep->wait); d = ep->usbdev; pipe = usb_sndbulkpipe(d, ep->endpoint); usb_fill_bulk_urb( ep->urb, d, pipe, (unsigned char*)buf, len, usb_write_callback, ep ); status = usb_submit_urb(ep->urb, GFP_KERNEL); if (status) { printk(KERN_ERR "usbmidi: Cannot submit urb (%d)\n",status); ret = -EIO; goto error; } add_wait_queue( &ep->wait, &wait ); set_current_state( TASK_INTERRUPTIBLE ); while( ep->urb->status == -EINPROGRESS ) { if ( maxretry-- < 0 ) { printk(KERN_ERR "usbmidi: usb_bulk_msg timed out\n"); ret = -ETIME; break; } interruptible_sleep_on_timeout( &ep->wait, 10 ); } set_current_state( TASK_RUNNING ); remove_wait_queue( &ep->wait, &wait );error: return ret;}/** Copy data from URB to In endpoint buf. * Discard if CIN == 0 or CIN = 1. * * **/static void usb_bulk_read(struct urb *urb, struct pt_regs *regs){ struct midi_in_endpoint *ep = (struct midi_in_endpoint *)(urb->context); unsigned char *data = urb->transfer_buffer; int i, j, wake; if ( !ep->urbSubmitted ) { return; } if ( (urb->status == 0) && (urb->actual_length > 0) ) { wake = 0; spin_lock( &ep->lock ); for(j = 0; j < urb->actual_length; j += 4) { int cin = (data[j]>>0)&0xf; int cab = (data[j]>>4)&0xf; struct usb_mididev *cable = ep->cables[cab]; if ( cable ) { int len = cin_to_len[cin]; /** length of MIDI data **/ for (i = 0; i < len; i++) { cable->min.buf[cable->min.bufWrPtr] = data[1+i+j]; cable->min.bufWrPtr = (cable->min.bufWrPtr+1)%MIDI_IN_BUFSIZ; if (cable->min.bufRemains < MIDI_IN_BUFSIZ) cable->min.bufRemains += 1; else /** need to drop data **/ cable->min.bufRdPtr += (cable->min.bufRdPtr+1)%MIDI_IN_BUFSIZ; wake = 1; } } } spin_unlock ( &ep->lock ); if ( wake ) { wake_up( &ep->wait ); } } /* urb->dev must be reinitialized on 2.4.x kernels */ urb->dev = ep->usbdev; urb->actual_length = 0; usb_submit_urb(urb, GFP_ATOMIC);}/* ------------------------------------------------------------------------- *//* This routine must be called with spin_lock *//** Wrapper around usb_write(). * This routine must be called with spin_lock held on ep. * Called by midiWrite(), putOneMidiEvent(), and usb_midi_write(); **/static int flush_midi_buffer( struct midi_out_endpoint *ep ){ int ret=0; if ( ep->bufWrPtr > 0 ) { ret = usb_write( ep, ep->buf, ep->bufWrPtr ); ep->bufWrPtr = 0; } return ret;}/* ------------------------------------------------------------------------- *//** Given a MIDI Event, determine size of data to be attached to * USB-MIDI packet. * Returns 1, 2 or 3. * Called by midiWrite(); * Uses remains_80e0 and remains_f0f6; **/static int get_remains(int event){ int ret; if ( event < 0x80 ) { ret = 1; } else if ( event < 0xf0 ) { ret = remains_80e0[((event-0x80)>>4)&0x0f]; } else if ( event < 0xf7 ) { ret = remains_f0f6[event-0xf0]; } else { ret = 1; } return ret;}/** Given the output MIDI data in the output buffer, computes a reasonable * CIN. * Called by putOneMidiEvent(). **/static int get_CIN( struct usb_mididev *m ){ int cin; if ( m->mout.buf[0] == 0xf7 ) { cin = 5; } else if ( m->mout.buf[1] == 0xf7 ) { cin = 6; } else if ( m->mout.buf[2] == 0xf7 ) { cin = 7; } else { if ( m->mout.isInExclusive == 1 ) { cin = 4; } else if ( m->mout.buf[0] < 0x80 ) { /** One byte that we know nothing about. **/ cin = 0xF; } else if ( m->mout.buf[0] < 0xf0 ) { /** MIDI Voice messages 0x8X to 0xEX map to cin 0x8 to 0xE. **/ cin = (m->mout.buf[0]>>4)&0x0f; } else { /** Special lookup table exists for real-time events. **/ cin = cin_f0ff[m->mout.buf[0]-0xf0]; } } return cin;}/* ------------------------------------------------------------------------- *//** Move data to USB endpoint buffer. * **/static int put_one_midi_event(struct usb_mididev *m){ int cin; unsigned long flags; struct midi_out_endpoint *ep = m->mout.ep; int ret=0; cin = get_CIN( m ); if ( cin > 0x0f || cin < 0 ) { return -EINVAL; } spin_lock_irqsave( &ep->lock, flags ); ep->buf[ep->bufWrPtr++] = (m->mout.cableId<<4) | cin; ep->buf[ep->bufWrPtr++] = m->mout.buf[0]; ep->buf[ep->bufWrPtr++] = m->mout.buf[1]; ep->buf[ep->bufWrPtr++] = m->mout.buf[2]; if ( ep->bufWrPtr >= ep->bufSize ) { ret = flush_midi_buffer( ep ); } spin_unlock_irqrestore( &ep->lock, flags); m->mout.buf[0] = m->mout.buf[1] = m->mout.buf[2] = 0; m->mout.bufPtr = 0; return ret;}/** Write the MIDI message v on the midi device. * Called by usb_midi_write(); * Responsible for packaging a MIDI data stream into USB-MIDI packets. **/static int midi_write( struct usb_mididev *m, int v ){ unsigned long flags; struct midi_out_endpoint *ep = m->mout.ep; int ret=0; unsigned char c = (unsigned char)v; unsigned char sysrt_buf[4];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -