diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/usb/class | |
download | op-kernel-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip op-kernel-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/usb/class')
-rw-r--r-- | drivers/usb/class/Kconfig | 86 | ||||
-rw-r--r-- | drivers/usb/class/Makefile | 10 | ||||
-rw-r--r-- | drivers/usb/class/audio.c | 3882 | ||||
-rw-r--r-- | drivers/usb/class/audio.h | 110 | ||||
-rw-r--r-- | drivers/usb/class/bluetty.c | 1279 | ||||
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 942 | ||||
-rw-r--r-- | drivers/usb/class/cdc-acm.h | 82 | ||||
-rw-r--r-- | drivers/usb/class/usb-midi.c | 2154 | ||||
-rw-r--r-- | drivers/usb/class/usb-midi.h | 164 | ||||
-rw-r--r-- | drivers/usb/class/usblp.c | 1214 |
10 files changed, 9923 insertions, 0 deletions
diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig new file mode 100644 index 0000000..0561d02 --- /dev/null +++ b/drivers/usb/class/Kconfig @@ -0,0 +1,86 @@ +# +# USB Class driver configuration +# +comment "USB Device Class drivers" + depends on USB + +config USB_AUDIO + tristate "USB Audio support" + depends on USB && SOUND + help + Say Y here if you want to connect USB audio equipment such as + speakers to your computer's USB port. You only need this if you use + the OSS sound driver; ALSA has its own option for usb audio support. + + To compile this driver as a module, choose M here: the + module will be called audio. + +comment "USB Bluetooth TTY can only be used with disabled Bluetooth subsystem" + depends on USB && BT + +config USB_BLUETOOTH_TTY + tristate "USB Bluetooth TTY support" + depends on USB && BT=n + ---help--- + This driver implements a nonstandard tty interface to a Bluetooth + device that can be used only by specialized Bluetooth HCI software. + + Say Y here if you want to use OpenBT Bluetooth stack (available + at <http://developer.axis.com/software>), or other TTY based + Bluetooth stacks, and want to connect a USB Bluetooth device + to your computer's USB port. + + Do *not* enable this driver if you want to use generic Linux + Bluetooth support. + + If in doubt, say N here. + + To compile this driver as a module, choose M here: the + module will be called bluetty. + +config USB_MIDI + tristate "USB MIDI support" + depends on USB && SOUND + ---help--- + Say Y here if you want to connect a USB MIDI device to your + computer's USB port. This driver is for devices that comply with + 'Universal Serial Bus Device Class Definition for MIDI Device'. + + The following devices are known to work: + * Steinberg USB2MIDI + * Roland MPU64 + * Roland PC-300 + * Roland SC8850 + * Roland UM-1 + * Roland UM-2 + * Roland UA-100 + * Yamaha MU1000 + + To compile this driver as a module, choose M here: the + module will be called usb-midi. + +config USB_ACM + tristate "USB Modem (CDC ACM) support" + depends on USB + ---help--- + This driver supports USB modems and ISDN adapters which support the + Communication Device Class Abstract Control Model interface. + Please read <file:Documentation/usb/acm.txt> for details. + + If your modem only reports "Cls=ff(vend.)" in the descriptors in + /proc/bus/usb/devices, then your modem will not work with this + driver. + + To compile this driver as a module, choose M here: the + module will be called cdc-acm. + +config USB_PRINTER + tristate "USB Printer support" + depends on USB + help + Say Y here if you want to connect a USB printer to your computer's + USB port. + + To compile this driver as a module, choose M here: the + module will be called usblp. + diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile new file mode 100644 index 0000000..971e549 --- /dev/null +++ b/drivers/usb/class/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for USB Class drivers +# (one step up from the misc category) +# + +obj-$(CONFIG_USB_ACM) += cdc-acm.o +obj-$(CONFIG_USB_AUDIO) += audio.o +obj-$(CONFIG_USB_BLUETOOTH_TTY) += bluetty.o +obj-$(CONFIG_USB_MIDI) += usb-midi.o +obj-$(CONFIG_USB_PRINTER) += usblp.o diff --git a/drivers/usb/class/audio.c b/drivers/usb/class/audio.c new file mode 100644 index 0000000..f432b7d --- /dev/null +++ b/drivers/usb/class/audio.c @@ -0,0 +1,3882 @@ +/*****************************************************************************/ + +/* + * audio.c -- USB Audio Class driver + * + * Copyright (C) 1999, 2000, 2001, 2003, 2004 + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * 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 of the License, or + * (at your option) any later version. + * + * Debugging: + * Use the 'lsusb' utility to dump the descriptors. + * + * 1999-09-07: Alan Cox + * Parsing Audio descriptor patch + * 1999-09-08: Thomas Sailer + * Added OSS compatible data io functions; both parts of the + * driver remain to be glued together + * 1999-09-10: Thomas Sailer + * Beautified the driver. Added sample format conversions. + * Still not properly glued with the parsing code. + * The parsing code seems to have its problems btw, + * Since it parses all available configs but doesn't + * store which iface/altsetting belongs to which config. + * 1999-09-20: Thomas Sailer + * Threw out Alan's parsing code and implemented my own one. + * You cannot reasonnably linearly parse audio descriptors, + * especially the AudioClass descriptors have to be considered + * pointer lists. Mixer parsing untested, due to lack of device. + * First stab at synch pipe implementation, the Dallas USB DAC + * wants to use an Asynch out pipe. usb_audio_state now basically + * only contains lists of mixer and wave devices. We can therefore + * now have multiple mixer/wave devices per USB device. + * 1999-10-28: Thomas Sailer + * Converted to URB API. Fixed a taskstate/wakeup semantics mistake + * that made the driver consume all available CPU cycles. + * Now runs stable on UHCI-Acher/Fliegl/Sailer. + * 1999-10-31: Thomas Sailer + * Audio can now be unloaded if it is not in use by any mixer + * or dsp client (formerly you had to disconnect the audio devices + * from the USB port) + * Finally, about three months after ordering, my "Maxxtro SPK222" + * speakers arrived, isn't disdata a great mail order company 8-) + * Parse class specific endpoint descriptor of the audiostreaming + * interfaces and take the endpoint attributes from there. + * Unbelievably, the Philips USB DAC has a sampling rate range + * of over a decade, yet does not support the sampling rate control! + * No wonder it sounds so bad, has very audible sampling rate + * conversion distortion. Don't try to listen to it using + * decent headphones! + * "Let's make things better" -> but please Philips start with your + * own stuff!!!! + * 1999-11-02: Thomas Sailer + * It takes the Philips boxes several seconds to acquire synchronisation + * that means they won't play short sounds. Should probably maintain + * the ISO datastream even if there's nothing to play. + * Fix counting the total_bytes counter, RealPlayer G2 depends on it. + * 1999-12-20: Thomas Sailer + * Fix bad bug in conversion to per interface probing. + * disconnect was called multiple times for the audio device, + * leading to a premature freeing of the audio structures + * 2000-05-13: Thomas Sailer + * I don't remember who changed the find_format routine, + * but the change was completely broken for the Dallas + * chip. Anyway taking sampling rate into account in find_format + * is bad and should not be done unless there are devices with + * completely broken audio descriptors. Unless someone shows + * me such a descriptor, I will not allow find_format to + * take the sampling rate into account. + * Also, the former find_format made: + * - mpg123 play mono instead of stereo + * - sox completely fail for wav's with sample rates < 44.1kHz + * for the Dallas chip. + * Also fix a rather long standing problem with applications that + * use "small" writes producing no sound at all. + * 2000-05-15: Thomas Sailer + * My fears came true, the Philips camera indeed has pretty stupid + * audio descriptors. + * 2000-05-17: Thomas Sailer + * Nemsoft spotted my stupid last minute change, thanks + * 2000-05-19: Thomas Sailer + * Fixed FEATURE_UNIT thinkos found thanks to the KC Technology + * Xtend device. Basically the driver treated FEATURE_UNIT's sourced + * by mono terminals as stereo. + * 2000-05-20: Thomas Sailer + * SELECTOR support (and thus selecting record channels from the mixer). + * Somewhat peculiar due to OSS interface limitations. Only works + * for channels where a "slider" is already in front of it (i.e. + * a MIXER unit or a FEATURE unit with volume capability). + * 2000-11-26: Thomas Sailer + * Workaround for Dallas DS4201. The DS4201 uses PCM8 as format tag for + * its 8 bit modes, but expects signed data (and should therefore have used PCM). + * 2001-03-10: Thomas Sailer + * provide abs function, prevent picking up a bogus kernel macro + * for abs. Bug report by Andrew Morton <andrewm@uow.edu.au> + * 2001-06-16: Bryce Nesbitt <bryce@obviously.com> + * Fix SNDCTL_DSP_STEREO API violation + * 2003-04-08: Oliver Neukum (oliver@neukum.name): + * Setting a configuration is done by usbcore and must not be overridden + * 2004-02-27: Workaround for broken synch descriptors + * 2004-03-07: Alan Stern <stern@rowland.harvard.edu> + * Add usb_ifnum_to_if() and usb_altnum_to_altsetting() support. + * Use the in-memory descriptors instead of reading them from the device. + * + */ + +/* + * Strategy: + * + * Alan Cox and Thomas Sailer are starting to dig at opposite ends and + * are hoping to meet in the middle, just like tunnel diggers :) + * Alan tackles the descriptor parsing, Thomas the actual data IO and the + * OSS compatible interface. + * + * Data IO implementation issues + * + * A mmap'able ring buffer per direction is implemented, because + * almost every OSS app expects it. It is however impractical to + * transmit/receive USB data directly into and out of the ring buffer, + * due to alignment and synchronisation issues. Instead, the ring buffer + * feeds a constant time delay line that handles the USB issues. + * + * Now we first try to find an alternate setting that exactly matches + * the sample format requested by the user. If we find one, we do not + * need to perform any sample rate conversions. If there is no matching + * altsetting, we choose the closest one and perform sample format + * conversions. We never do sample rate conversion; these are too + * expensive to be performed in the kernel. + * + * Current status: no known HCD-specific issues. + * + * Generally: Due to the brokenness of the Audio Class spec + * it seems generally impossible to write a generic Audio Class driver, + * so a reasonable driver should implement the features that are actually + * used. + * + * Parsing implementation issues + * + * One cannot reasonably parse the AudioClass descriptors linearly. + * Therefore the current implementation features routines to look + * for a specific descriptor in the descriptor list. + * + * How does the parsing work? First, all interfaces are searched + * for an AudioControl class interface. If found, the config descriptor + * that belongs to the current configuration is searched and + * the HEADER descriptor is found. It contains a list of + * all AudioStreaming and MIDIStreaming devices. This list is then walked, + * and all AudioStreaming interfaces are classified into input and output + * interfaces (according to the endpoint0 direction in altsetting1) (MIDIStreaming + * is currently not supported). The input & output list is then used + * to group inputs and outputs together and issued pairwise to the + * AudioStreaming class parser. Finally, all OUTPUT_TERMINAL descriptors + * are walked and issued to the mixer construction routine. + * + * The AudioStreaming parser simply enumerates all altsettings belonging + * to the specified interface. It looks for AS_GENERAL and FORMAT_TYPE + * class specific descriptors to extract the sample format/sample rate + * data. Only sample format types PCM and PCM8 are supported right now, and + * only FORMAT_TYPE_I is handled. The isochronous data endpoint needs to + * be the first endpoint of the interface, and the optional synchronisation + * isochronous endpoint the second one. + * + * Mixer construction works as follows: The various TERMINAL and UNIT + * descriptors span a tree from the root (OUTPUT_TERMINAL) through the + * intermediate nodes (UNITs) to the leaves (INPUT_TERMINAL). We walk + * that tree in a depth first manner. FEATURE_UNITs may contribute volume, + * bass and treble sliders to the mixer, MIXER_UNITs volume sliders. + * The terminal type encoded in the INPUT_TERMINALs feeds a heuristic + * to determine "meaningful" OSS slider numbers, however we will see + * how well this works in practice. Other features are not used at the + * moment, they seem less often used. Also, it seems difficult at least + * to construct recording source switches from SELECTOR_UNITs, but + * since there are not many USB ADC's available, we leave that for later. + */ + +/*****************************************************************************/ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/sound.h> +#include <linux/soundcard.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/bitops.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/usb.h> + +#include "audio.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.0.0" +#define DRIVER_AUTHOR "Alan Cox <alan@lxorguk.ukuu.org.uk>, Thomas Sailer (sailer@ife.ee.ethz.ch)" +#define DRIVER_DESC "USB Audio Class driver" + +#define AUDIO_DEBUG 1 + +#define SND_DEV_DSP16 5 + +#define dprintk(x) + +/* --------------------------------------------------------------------- */ + +/* + * Linked list of all audio devices... + */ +static struct list_head audiodevs = LIST_HEAD_INIT(audiodevs); +static DECLARE_MUTEX(open_sem); + +/* + * wait queue for processes wanting to open an USB audio device + */ +static DECLARE_WAIT_QUEUE_HEAD(open_wait); + + +#define MAXFORMATS MAX_ALT +#define DMABUFSHIFT 17 /* 128k worth of DMA buffer */ +#define NRSGBUF (1U<<(DMABUFSHIFT-PAGE_SHIFT)) + +/* + * This influences: + * - Latency + * - Interrupt rate + * - Synchronisation behaviour + * Don't touch this if you don't understand all of the above. + */ +#define DESCFRAMES 5 +#define SYNCFRAMES DESCFRAMES + +#define MIXFLG_STEREOIN 1 +#define MIXFLG_STEREOOUT 2 + +struct mixerchannel { + __u16 value; + __u16 osschannel; /* number of the OSS channel */ + __s16 minval, maxval; + __u16 slctunitid; + __u8 unitid; + __u8 selector; + __u8 chnum; + __u8 flags; +}; + +struct audioformat { + unsigned int format; + unsigned int sratelo; + unsigned int sratehi; + unsigned char altsetting; + unsigned char attributes; +}; + +struct dmabuf { + /* buffer data format */ + unsigned int format; + unsigned int srate; + /* physical buffer */ + unsigned char *sgbuf[NRSGBUF]; + unsigned bufsize; + unsigned numfrag; + unsigned fragshift; + unsigned wrptr, rdptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; +}; + +struct usb_audio_state; + +#define FLG_URB0RUNNING 1 +#define FLG_URB1RUNNING 2 +#define FLG_SYNC0RUNNING 4 +#define FLG_SYNC1RUNNING 8 +#define FLG_RUNNING 16 +#define FLG_CONNECTED 32 + +struct my_data_urb { + struct urb *urb; +}; + +struct my_sync_urb { + struct urb *urb; +}; + + +struct usb_audiodev { + struct list_head list; + struct usb_audio_state *state; + + /* soundcore stuff */ + int dev_audio; + + /* wave stuff */ + mode_t open_mode; + spinlock_t lock; /* DMA buffer access spinlock */ + + struct usbin { + int interface; /* Interface number, -1 means not used */ + unsigned int format; /* USB data format */ + unsigned int datapipe; /* the data input pipe */ + unsigned int syncpipe; /* the synchronisation pipe - 0 for anything but adaptive IN mode */ + unsigned int syncinterval; /* P for adaptive IN mode, 0 otherwise */ + unsigned int freqn; /* nominal sampling rate in USB format, i.e. fs/1000 in Q10.14 */ + unsigned int freqmax; /* maximum sampling rate, used for buffer management */ + unsigned int phase; /* phase accumulator */ + unsigned int flags; /* see FLG_ defines */ + + struct my_data_urb durb[2]; /* ISO descriptors for the data endpoint */ + struct my_sync_urb surb[2]; /* ISO sync pipe descriptor if needed */ + + struct dmabuf dma; + } usbin; + + struct usbout { + int interface; /* Interface number, -1 means not used */ + unsigned int format; /* USB data format */ + unsigned int datapipe; /* the data input pipe */ + unsigned int syncpipe; /* the synchronisation pipe - 0 for anything but asynchronous OUT mode */ + unsigned int syncinterval; /* P for asynchronous OUT mode, 0 otherwise */ + unsigned int freqn; /* nominal sampling rate in USB format, i.e. fs/1000 in Q10.14 */ + unsigned int freqm; /* momentary sampling rate in USB format, i.e. fs/1000 in Q10.14 */ + unsigned int freqmax; /* maximum sampling rate, used for buffer management */ + unsigned int phase; /* phase accumulator */ + unsigned int flags; /* see FLG_ defines */ + + struct my_data_urb durb[2]; /* ISO descriptors for the data endpoint */ + struct my_sync_urb surb[2]; /* ISO sync pipe descriptor if needed */ + + struct dmabuf dma; + } usbout; + + + unsigned int numfmtin, numfmtout; + struct audioformat fmtin[MAXFORMATS]; + struct audioformat fmtout[MAXFORMATS]; +}; + +struct usb_mixerdev { + struct list_head list; + struct usb_audio_state *state; + + /* soundcore stuff */ + int dev_mixer; + + unsigned char iface; /* interface number of the AudioControl interface */ + + /* USB format descriptions */ + unsigned int numch, modcnt; + + /* mixch is last and gets allocated dynamically */ + struct mixerchannel ch[0]; +}; + +struct usb_audio_state { + struct list_head audiodev; + + /* USB device */ + struct usb_device *usbdev; + + struct list_head audiolist; + struct list_head mixerlist; + + unsigned count; /* usage counter; NOTE: the usb stack is also considered a user */ +}; + +/* private audio format extensions */ +#define AFMT_STEREO 0x80000000 +#define AFMT_ISSTEREO(x) ((x) & AFMT_STEREO) +#define AFMT_IS16BIT(x) ((x) & (AFMT_S16_LE|AFMT_S16_BE|AFMT_U16_LE|AFMT_U16_BE)) +#define AFMT_ISUNSIGNED(x) ((x) & (AFMT_U8|AFMT_U16_LE|AFMT_U16_BE)) +#define AFMT_BYTESSHIFT(x) ((AFMT_ISSTEREO(x) ? 1 : 0) + (AFMT_IS16BIT(x) ? 1 : 0)) +#define AFMT_BYTES(x) (1<<AFMT_BYTESSHFIT(x)) + +/* --------------------------------------------------------------------- */ + +static inline unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* --------------------------------------------------------------------- */ + +/* + * OSS compatible ring buffer management. The ring buffer may be mmap'ed into + * an application address space. + * + * I first used the rvmalloc stuff copied from bttv. Alan Cox did not like it, so + * we now use an array of pointers to a single page each. This saves us the + * kernel page table manipulations, but we have to do a page table alike mechanism + * (though only one indirection) in software. + */ + +static void dmabuf_release(struct dmabuf *db) +{ + unsigned int nr; + void *p; + + for(nr = 0; nr < NRSGBUF; nr++) { + if (!(p = db->sgbuf[nr])) + continue; + ClearPageReserved(virt_to_page(p)); + free_page((unsigned long)p); + db->sgbuf[nr] = NULL; + } + db->mapped = db->ready = 0; +} + +static int dmabuf_init(struct dmabuf *db) +{ + unsigned int nr, bytepersec, bufs; + void *p; + + /* initialize some fields */ + db->rdptr = db->wrptr = db->total_bytes = db->count = db->error = 0; + /* calculate required buffer size */ + bytepersec = db->srate << AFMT_BYTESSHIFT(db->format); + bufs = 1U << DMABUFSHIFT; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; + db->dmasize = db->numfrag << db->fragshift; + for(nr = 0; nr < NRSGBUF; nr++) { + if (!db->sgbuf[nr]) { + p = (void *)get_zeroed_page(GFP_KERNEL); + if (!p) + return -ENOMEM; + db->sgbuf[nr] = p; + SetPageReserved(virt_to_page(p)); + } + memset(db->sgbuf[nr], AFMT_ISUNSIGNED(db->format) ? 0x80 : 0, PAGE_SIZE); + if ((nr << PAGE_SHIFT) >= db->dmasize) + break; + } + db->bufsize = nr << PAGE_SHIFT; + db->ready = 1; + dprintk((KERN_DEBUG "usbaudio: dmabuf_init bytepersec %d bufs %d ossfragshift %d ossmaxfrags %d " + "fragshift %d fragsize %d numfrag %d dmasize %d bufsize %d fmt 0x%x srate %d\n", + bytepersec, bufs, db->ossfragshift, db->ossmaxfrags, db->fragshift, db->fragsize, + db->numfrag, db->dmasize, db->bufsize, db->format, db->srate)); + return 0; +} + +static int dmabuf_mmap(struct vm_area_struct *vma, struct dmabuf *db, unsigned long start, unsigned long size, pgprot_t prot) +{ + unsigned int nr; + + if (!db->ready || db->mapped || (start | size) & (PAGE_SIZE-1) || size > db->bufsize) + return -EINVAL; + size >>= PAGE_SHIFT; + for(nr = 0; nr < size; nr++) + if (!db->sgbuf[nr]) + return -EINVAL; + db->mapped = 1; + for(nr = 0; nr < size; nr++) { + unsigned long pfn; + + pfn = virt_to_phys(db->sgbuf[nr]) >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, prot)) + return -EAGAIN; + start += PAGE_SIZE; + } + return 0; +} + +static void dmabuf_copyin(struct dmabuf *db, const void *buffer, unsigned int size) +{ + unsigned int pgrem, rem; + + db->total_bytes += size; + for (;;) { + if (size <= 0) + return; + pgrem = ((~db->wrptr) & (PAGE_SIZE-1)) + 1; + if (pgrem > size) + pgrem = size; + rem = db->dmasize - db->wrptr; + if (pgrem > rem) + pgrem = rem; + memcpy((db->sgbuf[db->wrptr >> PAGE_SHIFT]) + (db->wrptr & (PAGE_SIZE-1)), buffer, pgrem); + size -= pgrem; + buffer += pgrem; + db->wrptr += pgrem; + if (db->wrptr >= db->dmasize) + db->wrptr = 0; + } +} + +static void dmabuf_copyout(struct dmabuf *db, void *buffer, unsigned int size) +{ + unsigned int pgrem, rem; + + db->total_bytes += size; + for (;;) { + if (size <= 0) + return; + pgrem = ((~db->rdptr) & (PAGE_SIZE-1)) + 1; + if (pgrem > size) + pgrem = size; + rem = db->dmasize - db->rdptr; + if (pgrem > rem) + pgrem = rem; + memcpy(buffer, (db->sgbuf[db->rdptr >> PAGE_SHIFT]) + (db->rdptr & (PAGE_SIZE-1)), pgrem); + size -= pgrem; + buffer += pgrem; + db->rdptr += pgrem; + if (db->rdptr >= db->dmasize) + db->rdptr = 0; + } +} + +static int dmabuf_copyin_user(struct dmabuf *db, unsigned int ptr, const void __user *buffer, unsigned int size) +{ + unsigned int pgrem, rem; + + if (!db->ready || db->mapped) + return -EINVAL; + for (;;) { + if (size <= 0) + return 0; + pgrem = ((~ptr) & (PAGE_SIZE-1)) + 1; + if (pgrem > size) + pgrem = size; + rem = db->dmasize - ptr; + if (pgrem > rem) + pgrem = rem; + if (copy_from_user((db->sgbuf[ptr >> PAGE_SHIFT]) + (ptr & (PAGE_SIZE-1)), buffer, pgrem)) + return -EFAULT; + size -= pgrem; + buffer += pgrem; + ptr += pgrem; + if (ptr >= db->dmasize) + ptr = 0; + } +} + +static int dmabuf_copyout_user(struct dmabuf *db, unsigned int ptr, void __user *buffer, unsigned int size) +{ + unsigned int pgrem, rem; + + if (!db->ready || db->mapped) + return -EINVAL; + for (;;) { + if (size <= 0) + return 0; + pgrem = ((~ptr) & (PAGE_SIZE-1)) + 1; + if (pgrem > size) + pgrem = size; + rem = db->dmasize - ptr; + if (pgrem > rem) + pgrem = rem; + if (copy_to_user(buffer, (db->sgbuf[ptr >> PAGE_SHIFT]) + (ptr & (PAGE_SIZE-1)), pgrem)) + return -EFAULT; + size -= pgrem; + buffer += pgrem; + ptr += pgrem; + if (ptr >= db->dmasize) + ptr = 0; + } +} + +/* --------------------------------------------------------------------- */ +/* + * USB I/O code. We do sample format conversion if necessary + */ + +static void usbin_stop(struct usb_audiodev *as) +{ + struct usbin *u = &as->usbin; + unsigned long flags; + unsigned int i, notkilled = 1; + + spin_lock_irqsave(&as->lock, flags); + u->flags &= ~FLG_RUNNING; + i = u->flags; + spin_unlock_irqrestore(&as->lock, flags); + while (i & (FLG_URB0RUNNING|FLG_URB1RUNNING|FLG_SYNC0RUNNING|FLG_SYNC1RUNNING)) { + set_current_state(notkilled ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&as->lock, flags); + i = u->flags; + spin_unlock_irqrestore(&as->lock, flags); + if (notkilled && signal_pending(current)) { + if (i & FLG_URB0RUNNING) + usb_kill_urb(u->durb[0].urb); + if (i & FLG_URB1RUNNING) + usb_kill_urb(u->durb[1].urb); + if (i & FLG_SYNC0RUNNING) + usb_kill_urb(u->surb[0].urb); + if (i & FLG_SYNC1RUNNING) + usb_kill_urb(u->surb[1].urb); + notkilled = 0; + } + } + set_current_state(TASK_RUNNING); + if (u->durb[0].urb->transfer_buffer) + kfree(u->durb[0].urb->transfer_buffer); + if (u->durb[1].urb->transfer_buffer) + kfree(u->durb[1].urb->transfer_buffer); + if (u->surb[0].urb->transfer_buffer) + kfree(u->surb[0].urb->transfer_buffer); + if (u->surb[1].urb->transfer_buffer) + kfree(u->surb[1].urb->transfer_buffer); + u->durb[0].urb->transfer_buffer = u->durb[1].urb->transfer_buffer = + u->surb[0].urb->transfer_buffer = u->surb[1].urb->transfer_buffer = NULL; +} + +static inline void usbin_release(struct usb_audiodev *as) +{ + usbin_stop(as); +} + +static void usbin_disc(struct usb_audiodev *as) +{ + struct usbin *u = &as->usbin; + + unsigned long flags; + + spin_lock_irqsave(&as->lock, flags); + u->flags &= ~(FLG_RUNNING | FLG_CONNECTED); + spin_unlock_irqrestore(&as->lock, flags); + usbin_stop(as); +} + +static void conversion(const void *ibuf, unsigned int ifmt, void *obuf, unsigned int ofmt, void *tmp, unsigned int scnt) +{ + unsigned int cnt, i; + __s16 *sp, *sp2, s; + unsigned char *bp; + + cnt = scnt; + if (AFMT_ISSTEREO(ifmt)) + cnt <<= 1; + sp = ((__s16 *)tmp) + cnt; + switch (ifmt & ~AFMT_STEREO) { + case AFMT_U8: + for (bp = ((unsigned char *)ibuf)+cnt, i = 0; i < cnt; i++) { + bp--; + sp--; + *sp = (*bp ^ 0x80) << 8; + } + break; + + case AFMT_S8: + for (bp = ((unsigned char *)ibuf)+cnt, i = 0; i < cnt; i++) { + bp--; + sp--; + *sp = *bp << 8; + } + break; + + case AFMT_U16_LE: + for (bp = ((unsigned char *)ibuf)+2*cnt, i = 0; i < cnt; i++) { + bp -= 2; + sp--; + *sp = (bp[0] | (bp[1] << 8)) ^ 0x8000; + } + break; + + case AFMT_U16_BE: + for (bp = ((unsigned char *)ibuf)+2*cnt, i = 0; i < cnt; i++) { + bp -= 2; + sp--; + *sp = (bp[1] | (bp[0] << 8)) ^ 0x8000; + } + break; + + case AFMT_S16_LE: + for (bp = ((unsigned char *)ibuf)+2*cnt, i = 0; i < cnt; i++) { + bp -= 2; + sp--; + *sp = bp[0] | (bp[1] << 8); + } + break; + + case AFMT_S16_BE: + for (bp = ((unsigned char *)ibuf)+2*cnt, i = 0; i < cnt; i++) { + bp -= 2; + sp--; + *sp = bp[1] | (bp[0] << 8); + } + break; + } + if (!AFMT_ISSTEREO(ifmt) && AFMT_ISSTEREO(ofmt)) { + /* expand from mono to stereo */ + for (sp = ((__s16 *)tmp)+scnt, sp2 = ((__s16 *)tmp)+2*scnt, i = 0; i < scnt; i++) { + sp--; + sp2 -= 2; + sp2[0] = sp2[1] = sp[0]; + } + } + if (AFMT_ISSTEREO(ifmt) && !AFMT_ISSTEREO(ofmt)) { + /* contract from stereo to mono */ + for (sp = sp2 = ((__s16 *)tmp), i = 0; i < scnt; i++, sp++, sp2 += 2) + sp[0] = (sp2[0] + sp2[1]) >> 1; + } + cnt = scnt; + if (AFMT_ISSTEREO(ofmt)) + cnt <<= 1; + sp = ((__s16 *)tmp); + bp = ((unsigned char *)obuf); + switch (ofmt & ~AFMT_STEREO) { + case AFMT_U8: + for (i = 0; i < cnt; i++, sp++, bp++) + *bp = (*sp >> 8) ^ 0x80; + break; + + case AFMT_S8: + for (i = 0; i < cnt; i++, sp++, bp++) + *bp = *sp >> 8; + break; + + case AFMT_U16_LE: + for (i = 0; i < cnt; i++, sp++, bp += 2) { + s = *sp; + bp[0] = s; + bp[1] = (s >> 8) ^ 0x80; + } + break; + + case AFMT_U16_BE: + for (i = 0; i < cnt; i++, sp++, bp += 2) { + s = *sp; + bp[1] = s; + bp[0] = (s >> 8) ^ 0x80; + } + break; + + case AFMT_S16_LE: + for (i = 0; i < cnt; i++, sp++, bp += 2) { + s = *sp; + bp[0] = s; + bp[1] = s >> 8; + } + break; + + case AFMT_S16_BE: + for (i = 0; i < cnt; i++, sp++, bp += 2) { + s = *sp; + bp[1] = s; + bp[0] = s >> 8; + } + break; + } + +} + +static void usbin_convert(struct usbin *u, unsigned char *buffer, unsigned int samples) +{ + union { + __s16 s[64]; + unsigned char b[0]; + } tmp; + unsigned int scnt, maxs, ufmtsh, dfmtsh; + + ufmtsh = AFMT_BYTESSHIFT(u->format); + dfmtsh = AFMT_BYTESSHIFT(u->dma.format); + maxs = (AFMT_ISSTEREO(u->dma.format | u->format)) ? 32 : 64; + while (samples > 0) { + scnt = samples; + if (scnt > maxs) + scnt = maxs; + conversion(buffer, u->format, tmp.b, u->dma.format, tmp.b, scnt); + dmabuf_copyin(&u->dma, tmp.b, scnt << dfmtsh); + buffer += scnt << ufmtsh; + samples -= scnt; + } +} + +static int usbin_prepare_desc(struct usbin *u, struct urb *urb) +{ + unsigned int i, maxsize, offs; + + maxsize = (u->freqmax + 0x3fff) >> (14 - AFMT_BYTESSHIFT(u->format)); + //printk(KERN_DEBUG "usbin_prepare_desc: maxsize %d freq 0x%x format 0x%x\n", maxsize, u->freqn, u->format); + for (i = offs = 0; i < DESCFRAMES; i++, offs += maxsize) { + urb->iso_frame_desc[i].length = maxsize; + urb->iso_frame_desc[i].offset = offs; + } + urb->interval = 1; + return 0; +} + +/* + * return value: 0 if descriptor should be restarted, -1 otherwise + * convert sample format on the fly if necessary + */ +static int usbin_retire_desc(struct usbin *u, struct urb *urb) +{ + unsigned int i, ufmtsh, dfmtsh, err = 0, cnt, scnt, dmafree; + unsigned char *cp; + + ufmtsh = AFMT_BYTESSHIFT(u->format); + dfmtsh = AFMT_BYTESSHIFT(u->dma.format); + for (i = 0; i < DESCFRAMES; i++) { + cp = ((unsigned char *)urb->transfer_buffer) + urb->iso_frame_desc[i].offset; + if (urb->iso_frame_desc[i].status) { + dprintk((KERN_DEBUG "usbin_retire_desc: frame %u status %d\n", i, urb->iso_frame_desc[i].status)); + continue; + } + scnt = urb->iso_frame_desc[i].actual_length >> ufmtsh; + if (!scnt) + continue; + cnt = scnt << dfmtsh; + if (!u->dma.mapped) { + dmafree = u->dma.dmasize - u->dma.count; + if (cnt > dmafree) { + scnt = dmafree >> dfmtsh; + cnt = scnt << dfmtsh; + err++; + } + } + u->dma.count += cnt; + if (u->format == u->dma.format) { + /* we do not need format conversion */ + dprintk((KERN_DEBUG "usbaudio: no sample format conversion\n")); + dmabuf_copyin(&u->dma, cp, cnt); + } else { + /* we need sampling format conversion */ + dprintk((KERN_DEBUG "usbaudio: sample format conversion %x != %x\n", u->format, u->dma.format)); + usbin_convert(u, cp, scnt); + } + } + if (err) + u->dma.error++; + if (u->dma.count >= (signed)u->dma.fragsize) + wake_up(&u->dma.wait); + return err ? -1 : 0; +} + +static void usbin_completed(struct urb *urb, struct pt_regs *regs) +{ + struct usb_audiodev *as = (struct usb_audiodev *)urb->context; + struct usbin *u = &as->usbin; + unsigned long flags; + unsigned int mask; + int suret = 0; + +#if 0 + printk(KERN_DEBUG "usbin_completed: status %d errcnt %d flags 0x%x\n", urb->status, urb->error_count, u->flags); +#endif + if (urb == u->durb[0].urb) + mask = FLG_URB0RUNNING; + else if (urb == u->durb[1].urb) + mask = FLG_URB1RUNNING; + else { + mask = 0; + printk(KERN_ERR "usbin_completed: panic: unknown URB\n"); + } + urb->dev = as->state->usbdev; + spin_lock_irqsave(&as->lock, flags); + if (!usbin_retire_desc(u, urb) && + u->flags & FLG_RUNNING && + !usbin_prepare_desc(u, urb) && + (suret = usb_submit_urb(urb, GFP_ATOMIC)) == 0) { + u->flags |= mask; + } else { + u->flags &= ~(mask | FLG_RUNNING); + wake_up(&u->dma.wait); + printk(KERN_DEBUG "usbin_completed: descriptor not restarted (usb_submit_urb: %d)\n", suret); + } + spin_unlock_irqrestore(&as->lock, flags); +} + +/* + * we output sync data + */ +static int usbin_sync_prepare_desc(struct usbin *u, struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + unsigned int i, offs; + + for (i = offs = 0; i < SYNCFRAMES; i++, offs += 3, cp += 3) { + urb->iso_frame_desc[i].length = 3; + urb->iso_frame_desc[i].offset = offs; + cp[0] = u->freqn; + cp[1] = u->freqn >> 8; + cp[2] = u->freqn >> 16; + } + urb->interval = 1; + return 0; +} + +/* + * return value: 0 if descriptor should be restarted, -1 otherwise + */ +static int usbin_sync_retire_desc(struct usbin *u, struct urb *urb) +{ + unsigned int i; + + for (i = 0; i < SYNCFRAMES; i++) + if (urb->iso_frame_desc[0].status) + dprintk((KERN_DEBUG "usbin_sync_retire_desc: frame %u status %d\n", i, urb->iso_frame_desc[i].status)); + return 0; +} + +static void usbin_sync_completed(struct urb *urb, struct pt_regs *regs) +{ + struct usb_audiodev *as = (struct usb_audiodev *)urb->context; + struct usbin *u = &as->usbin; + unsigned long flags; + unsigned int mask; + int suret = 0; + +#if 0 + printk(KERN_DEBUG "usbin_sync_completed: status %d errcnt %d flags 0x%x\n", urb->status, urb->error_count, u->flags); +#endif + if (urb == u->surb[0].urb) + mask = FLG_SYNC0RUNNING; + else if (urb == u->surb[1].urb) + mask = FLG_SYNC1RUNNING; + else { + mask = 0; + printk(KERN_ERR "usbin_sync_completed: panic: unknown URB\n"); + } + urb->dev = as->state->usbdev; + spin_lock_irqsave(&as->lock, flags); + if (!usbin_sync_retire_desc(u, urb) && + u->flags & FLG_RUNNING && + !usbin_sync_prepare_desc(u, urb) && + (suret = usb_submit_urb(urb, GFP_ATOMIC)) == 0) { + u->flags |= mask; + } else { + u->flags &= ~(mask | FLG_RUNNING); + wake_up(&u->dma.wait); + dprintk((KERN_DEBUG "usbin_sync_completed: descriptor not restarted (usb_submit_urb: %d)\n", suret)); + } + spin_unlock_irqrestore(&as->lock, flags); +} + +static int usbin_start(struct usb_audiodev *as) +{ + struct usb_device *dev = as->state->usbdev; + struct usbin *u = &as->usbin; + struct urb *urb; + unsigned long flags; + unsigned int maxsze, bufsz; + +#if 0 + printk(KERN_DEBUG "usbin_start: device %d ufmt 0x%08x dfmt 0x%08x srate %d\n", + dev->devnum, u->format, u->dma.format, u->dma.srate); +#endif + /* allocate USB storage if not already done */ + spin_lock_irqsave(&as->lock, flags); + if (!(u->flags & FLG_CONNECTED)) { + spin_unlock_irqrestore(&as->lock, flags); + return -EIO; + } + if (!(u->flags & FLG_RUNNING)) { + spin_unlock_irqrestore(&as->lock, flags); + u->freqn = ((u->dma.srate << 11) + 62) / 125; /* this will overflow at approx 2MSPS */ + u->freqmax = u->freqn + (u->freqn >> 2); + u->phase = 0; + maxsze = (u->freqmax + 0x3fff) >> (14 - AFMT_BYTESSHIFT(u->format)); + bufsz = DESCFRAMES * maxsze; + if (u->durb[0].urb->transfer_buffer) + kfree(u->durb[0].urb->transfer_buffer); + u->durb[0].urb->transfer_buffer = kmalloc(bufsz, GFP_KERNEL); + u->durb[0].urb->transfer_buffer_length = bufsz; + if (u->durb[1].urb->transfer_buffer) + kfree(u->durb[1].urb->transfer_buffer); + u->durb[1].urb->transfer_buffer = kmalloc(bufsz, GFP_KERNEL); + u->durb[1].urb->transfer_buffer_length = bufsz; + if (u->syncpipe) { + if (u->surb[0].urb->transfer_buffer) + kfree(u->surb[0].urb->transfer_buffer); + u->surb[0].urb->transfer_buffer = kmalloc(3*SYNCFRAMES, GFP_KERNEL); + u->surb[0].urb->transfer_buffer_length = 3*SYNCFRAMES; + if (u->surb[1].urb->transfer_buffer) + kfree(u->surb[1].urb->transfer_buffer); + u->surb[1].urb->transfer_buffer = kmalloc(3*SYNCFRAMES, GFP_KERNEL); + u->surb[1].urb->transfer_buffer_length = 3*SYNCFRAMES; + } + if (!u->durb[0].urb->transfer_buffer || !u->durb[1].urb->transfer_buffer || + (u->syncpipe && (!u->surb[0].urb->transfer_buffer || !u->surb[1].urb->transfer_buffer))) { + printk(KERN_ERR "usbaudio: cannot start playback device %d\n", dev->devnum); + return 0; + } + spin_lock_irqsave(&as->lock, flags); + } + if (u->dma.count >= u->dma.dmasize && !u->dma.mapped) { + spin_unlock_irqrestore(&as->lock, flags); + return 0; + } + u->flags |= FLG_RUNNING; + if (!(u->flags & FLG_URB0RUNNING)) { + urb = u->durb[0].urb; + urb->dev = dev; + urb->pipe = u->datapipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = DESCFRAMES; + urb->context = as; + urb->complete = usbin_completed; + if (!usbin_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_KERNEL)) + u->flags |= FLG_URB0RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->flags & FLG_RUNNING && !(u->flags & FLG_URB1RUNNING)) { + urb = u->durb[1].urb; + urb->dev = dev; + urb->pipe = u->datapipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = DESCFRAMES; + urb->context = as; + urb->complete = usbin_completed; + if (!usbin_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_KERNEL)) + u->flags |= FLG_URB1RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->syncpipe) { + if (u->flags & FLG_RUNNING && !(u->flags & FLG_SYNC0RUNNING)) { + urb = u->surb[0].urb; + urb->dev = dev; + urb->pipe = u->syncpipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = SYNCFRAMES; + urb->context = as; + urb->complete = usbin_sync_completed; + /* stride: u->syncinterval */ + if (!usbin_sync_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_KERNEL)) + u->flags |= FLG_SYNC0RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->flags & FLG_RUNNING && !(u->flags & FLG_SYNC1RUNNING)) { + urb = u->surb[1].urb; + urb->dev = dev; + urb->pipe = u->syncpipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = SYNCFRAMES; + urb->context = as; + urb->complete = usbin_sync_completed; + /* stride: u->syncinterval */ + if (!usbin_sync_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_KERNEL)) + u->flags |= FLG_SYNC1RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + } + spin_unlock_irqrestore(&as->lock, flags); + return 0; +} + +static void usbout_stop(struct usb_audiodev *as) +{ + struct usbout *u = &as->usbout; + unsigned long flags; + unsigned int i, notkilled = 1; + + spin_lock_irqsave(&as->lock, flags); + u->flags &= ~FLG_RUNNING; + i = u->flags; + spin_unlock_irqrestore(&as->lock, flags); + while (i & (FLG_URB0RUNNING|FLG_URB1RUNNING|FLG_SYNC0RUNNING|FLG_SYNC1RUNNING)) { + set_current_state(notkilled ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&as->lock, flags); + i = u->flags; + spin_unlock_irqrestore(&as->lock, flags); + if (notkilled && signal_pending(current)) { + if (i & FLG_URB0RUNNING) + usb_kill_urb(u->durb[0].urb); + if (i & FLG_URB1RUNNING) + usb_kill_urb(u->durb[1].urb); + if (i & FLG_SYNC0RUNNING) + usb_kill_urb(u->surb[0].urb); + if (i & FLG_SYNC1RUNNING) + usb_kill_urb(u->surb[1].urb); + notkilled = 0; + } + } + set_current_state(TASK_RUNNING); + if (u->durb[0].urb->transfer_buffer) + kfree(u->durb[0].urb->transfer_buffer); + if (u->durb[1].urb->transfer_buffer) + kfree(u->durb[1].urb->transfer_buffer); + if (u->surb[0].urb->transfer_buffer) + kfree(u->surb[0].urb->transfer_buffer); + if (u->surb[1].urb->transfer_buffer) + kfree(u->surb[1].urb->transfer_buffer); + u->durb[0].urb->transfer_buffer = u->durb[1].urb->transfer_buffer = + u->surb[0].urb->transfer_buffer = u->surb[1].urb->transfer_buffer = NULL; +} + +static inline void usbout_release(struct usb_audiodev *as) +{ + usbout_stop(as); +} + +static void usbout_disc(struct usb_audiodev *as) +{ + struct usbout *u = &as->usbout; + unsigned long flags; + + spin_lock_irqsave(&as->lock, flags); + u->flags &= ~(FLG_RUNNING | FLG_CONNECTED); + spin_unlock_irqrestore(&as->lock, flags); + usbout_stop(as); +} + +static void usbout_convert(struct usbout *u, unsigned char *buffer, unsigned int samples) +{ + union { + __s16 s[64]; + unsigned char b[0]; + } tmp; + unsigned int scnt, maxs, ufmtsh, dfmtsh; + + ufmtsh = AFMT_BYTESSHIFT(u->format); + dfmtsh = AFMT_BYTESSHIFT(u->dma.format); + maxs = (AFMT_ISSTEREO(u->dma.format | u->format)) ? 32 : 64; + while (samples > 0) { + scnt = samples; + if (scnt > maxs) + scnt = maxs; + dmabuf_copyout(&u->dma, tmp.b, scnt << dfmtsh); + conversion(tmp.b, u->dma.format, buffer, u->format, tmp.b, scnt); + buffer += scnt << ufmtsh; + samples -= scnt; + } +} + +static int usbout_prepare_desc(struct usbout *u, struct urb *urb) +{ + unsigned int i, ufmtsh, dfmtsh, err = 0, cnt, scnt, offs; + unsigned char *cp = urb->transfer_buffer; + + ufmtsh = AFMT_BYTESSHIFT(u->format); + dfmtsh = AFMT_BYTESSHIFT(u->dma.format); + for (i = offs = 0; i < DESCFRAMES; i++) { + urb->iso_frame_desc[i].offset = offs; + u->phase = (u->phase & 0x3fff) + u->freqm; + scnt = u->phase >> 14; + if (!scnt) { + urb->iso_frame_desc[i].length = 0; + continue; + } + cnt = scnt << dfmtsh; + if (!u->dma.mapped) { + if (cnt > u->dma.count) { + scnt = u->dma.count >> dfmtsh; + cnt = scnt << dfmtsh; + err++; + } + u->dma.count -= cnt; + } else + u->dma.count += cnt; + if (u->format == u->dma.format) { + /* we do not need format conversion */ + dmabuf_copyout(&u->dma, cp, cnt); + } else { + /* we need sampling format conversion */ + usbout_convert(u, cp, scnt); + } + cnt = scnt << ufmtsh; + urb->iso_frame_desc[i].length = cnt; + offs += cnt; + cp += cnt; + } + urb->interval = 1; + if (err) + u->dma.error++; + if (u->dma.mapped) { + if (u->dma.count >= (signed)u->dma.fragsize) + wake_up(&u->dma.wait); + } else { + if ((signed)u->dma.dmasize >= u->dma.count + (signed)u->dma.fragsize) + wake_up(&u->dma.wait); + } + return err ? -1 : 0; +} + +/* + * return value: 0 if descriptor should be restarted, -1 otherwise + */ +static int usbout_retire_desc(struct usbout *u, struct urb *urb) +{ + unsigned int i; + + for (i = 0; i < DESCFRAMES; i++) { + if (urb->iso_frame_desc[i].status) { + dprintk((KERN_DEBUG "usbout_retire_desc: frame %u status %d\n", i, urb->iso_frame_desc[i].status)); + continue; + } + } + return 0; +} + +static void usbout_completed(struct urb *urb, struct pt_regs *regs) +{ + struct usb_audiodev *as = (struct usb_audiodev *)urb->context; + struct usbout *u = &as->usbout; + unsigned long flags; + unsigned int mask; + int suret = 0; + +#if 0 + printk(KERN_DEBUG "usbout_completed: status %d errcnt %d flags 0x%x\n", urb->status, urb->error_count, u->flags); +#endif + if (urb == u->durb[0].urb) + mask = FLG_URB0RUNNING; + else if (urb == u->durb[1].urb) + mask = FLG_URB1RUNNING; + else { + mask = 0; + printk(KERN_ERR "usbout_completed: panic: unknown URB\n"); + } + urb->dev = as->state->usbdev; + spin_lock_irqsave(&as->lock, flags); + if (!usbout_retire_desc(u, urb) && + u->flags & FLG_RUNNING && + !usbout_prepare_desc(u, urb) && + (suret = usb_submit_urb(urb, GFP_ATOMIC)) == 0) { + u->flags |= mask; + } else { + u->flags &= ~(mask | FLG_RUNNING); + wake_up(&u->dma.wait); + dprintk((KERN_DEBUG "usbout_completed: descriptor not restarted (usb_submit_urb: %d)\n", suret)); + } + spin_unlock_irqrestore(&as->lock, flags); +} + +static int usbout_sync_prepare_desc(struct usbout *u, struct urb *urb) +{ + unsigned int i, offs; + + for (i = offs = 0; i < SYNCFRAMES; i++, offs += 3) { + urb->iso_frame_desc[i].length = 3; + urb->iso_frame_desc[i].offset = offs; + } + urb->interval = 1; + return 0; +} + +/* + * return value: 0 if descriptor should be restarted, -1 otherwise + */ +static int usbout_sync_retire_desc(struct usbout *u, struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + unsigned int f, i; + + for (i = 0; i < SYNCFRAMES; i++, cp += 3) { + if (urb->iso_frame_desc[i].status) { + dprintk((KERN_DEBUG "usbout_sync_retire_desc: frame %u status %d\n", i, urb->iso_frame_desc[i].status)); + continue; + } + if (urb->iso_frame_desc[i].actual_length < 3) { + dprintk((KERN_DEBUG "usbout_sync_retire_desc: frame %u length %d\n", i, urb->iso_frame_desc[i].actual_length)); + continue; + } + f = cp[0] | (cp[1] << 8) | (cp[2] << 16); + if (abs(f - u->freqn) > (u->freqn >> 3) || f > u->freqmax) { + printk(KERN_WARNING "usbout_sync_retire_desc: requested frequency %u (nominal %u) out of range!\n", f, u->freqn); + continue; + } + u->freqm = f; + } + return 0; +} + +static void usbout_sync_completed(struct urb *urb, struct pt_regs *regs) +{ + struct usb_audiodev *as = (struct usb_audiodev *)urb->context; + struct usbout *u = &as->usbout; + unsigned long flags; + unsigned int mask; + int suret = 0; + +#if 0 + printk(KERN_DEBUG "usbout_sync_completed: status %d errcnt %d flags 0x%x\n", urb->status, urb->error_count, u->flags); +#endif + if (urb == u->surb[0].urb) + mask = FLG_SYNC0RUNNING; + else if (urb == u->surb[1].urb) + mask = FLG_SYNC1RUNNING; + else { + mask = 0; + printk(KERN_ERR "usbout_sync_completed: panic: unknown URB\n"); + } + urb->dev = as->state->usbdev; + spin_lock_irqsave(&as->lock, flags); + if (!usbout_sync_retire_desc(u, urb) && + u->flags & FLG_RUNNING && + !usbout_sync_prepare_desc(u, urb) && + (suret = usb_submit_urb(urb, GFP_ATOMIC)) == 0) { + u->flags |= mask; + } else { + u->flags &= ~(mask | FLG_RUNNING); + wake_up(&u->dma.wait); + dprintk((KERN_DEBUG "usbout_sync_completed: descriptor not restarted (usb_submit_urb: %d)\n", suret)); + } + spin_unlock_irqrestore(&as->lock, flags); +} + +static int usbout_start(struct usb_audiodev *as) +{ + struct usb_device *dev = as->state->usbdev; + struct usbout *u = &as->usbout; + struct urb *urb; + unsigned long flags; + unsigned int maxsze, bufsz; + +#if 0 + printk(KERN_DEBUG "usbout_start: device %d ufmt 0x%08x dfmt 0x%08x srate %d\n", + dev->devnum, u->format, u->dma.format, u->dma.srate); +#endif + /* allocate USB storage if not already done */ + spin_lock_irqsave(&as->lock, flags); + if (!(u->flags & FLG_CONNECTED)) { + spin_unlock_irqrestore(&as->lock, flags); + return -EIO; + } + if (!(u->flags & FLG_RUNNING)) { + spin_unlock_irqrestore(&as->lock, flags); + u->freqn = u->freqm = ((u->dma.srate << 11) + 62) / 125; /* this will overflow at approx 2MSPS */ + u->freqmax = u->freqn + (u->freqn >> 2); + u->phase = 0; + maxsze = (u->freqmax + 0x3fff) >> (14 - AFMT_BYTESSHIFT(u->format)); + bufsz = DESCFRAMES * maxsze; + if (u->durb[0].urb->transfer_buffer) + kfree(u->durb[0].urb->transfer_buffer); + u->durb[0].urb->transfer_buffer = kmalloc(bufsz, GFP_KERNEL); + u->durb[0].urb->transfer_buffer_length = bufsz; + if (u->durb[1].urb->transfer_buffer) + kfree(u->durb[1].urb->transfer_buffer); + u->durb[1].urb->transfer_buffer = kmalloc(bufsz, GFP_KERNEL); + u->durb[1].urb->transfer_buffer_length = bufsz; + if (u->syncpipe) { + if (u->surb[0].urb->transfer_buffer) + kfree(u->surb[0].urb->transfer_buffer); + u->surb[0].urb->transfer_buffer = kmalloc(3*SYNCFRAMES, GFP_KERNEL); + u->surb[0].urb->transfer_buffer_length = 3*SYNCFRAMES; + if (u->surb[1].urb->transfer_buffer) + kfree(u->surb[1].urb->transfer_buffer); + u->surb[1].urb->transfer_buffer = kmalloc(3*SYNCFRAMES, GFP_KERNEL); + u->surb[1].urb->transfer_buffer_length = 3*SYNCFRAMES; + } + if (!u->durb[0].urb->transfer_buffer || !u->durb[1].urb->transfer_buffer || + (u->syncpipe && (!u->surb[0].urb->transfer_buffer || !u->surb[1].urb->transfer_buffer))) { + printk(KERN_ERR "usbaudio: cannot start playback device %d\n", dev->devnum); + return 0; + } + spin_lock_irqsave(&as->lock, flags); + } + if (u->dma.count <= 0 && !u->dma.mapped) { + spin_unlock_irqrestore(&as->lock, flags); + return 0; + } + u->flags |= FLG_RUNNING; + if (!(u->flags & FLG_URB0RUNNING)) { + urb = u->durb[0].urb; + urb->dev = dev; + urb->pipe = u->datapipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = DESCFRAMES; + urb->context = as; + urb->complete = usbout_completed; + if (!usbout_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_ATOMIC)) + u->flags |= FLG_URB0RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->flags & FLG_RUNNING && !(u->flags & FLG_URB1RUNNING)) { + urb = u->durb[1].urb; + urb->dev = dev; + urb->pipe = u->datapipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = DESCFRAMES; + urb->context = as; + urb->complete = usbout_completed; + if (!usbout_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_ATOMIC)) + u->flags |= FLG_URB1RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->syncpipe) { + if (u->flags & FLG_RUNNING && !(u->flags & FLG_SYNC0RUNNING)) { + urb = u->surb[0].urb; + urb->dev = dev; + urb->pipe = u->syncpipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = SYNCFRAMES; + urb->context = as; + urb->complete = usbout_sync_completed; + /* stride: u->syncinterval */ + if (!usbout_sync_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_ATOMIC)) + u->flags |= FLG_SYNC0RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + if (u->flags & FLG_RUNNING && !(u->flags & FLG_SYNC1RUNNING)) { + urb = u->surb[1].urb; + urb->dev = dev; + urb->pipe = u->syncpipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = SYNCFRAMES; + urb->context = as; + urb->complete = usbout_sync_completed; + /* stride: u->syncinterval */ + if (!usbout_sync_prepare_desc(u, urb) && !usb_submit_urb(urb, GFP_ATOMIC)) + u->flags |= FLG_SYNC1RUNNING; + else + u->flags &= ~FLG_RUNNING; + } + } + spin_unlock_irqrestore(&as->lock, flags); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static unsigned int format_goodness(struct audioformat *afp, unsigned int fmt, unsigned int srate) +{ + unsigned int g = 0; + + if (srate < afp->sratelo) + g += afp->sratelo - srate; + if (srate > afp->sratehi) + g += srate - afp->sratehi; + if (AFMT_ISSTEREO(afp->format) && !AFMT_ISSTEREO(fmt)) + g += 0x100000; + if (!AFMT_ISSTEREO(afp->format) && AFMT_ISSTEREO(fmt)) + g += 0x400000; + if (AFMT_IS16BIT(afp->format) && !AFMT_IS16BIT(fmt)) + g += 0x100000; + if (!AFMT_IS16BIT(afp->format) && AFMT_IS16BIT(fmt)) + g += 0x400000; + return g; +} + +static int find_format(struct audioformat *afp, unsigned int nr, unsigned int fmt, unsigned int srate) +{ + unsigned int i, g, gb = ~0; + int j = -1; /* default to failure */ + + /* find "best" format (according to format_goodness) */ + for (i = 0; i < nr; i++) { + g = format_goodness(&afp[i], fmt, srate); + if (g >= gb) + continue; + j = i; + gb = g; + } + return j; +} + +static int set_format_in(struct usb_audiodev *as) +{ + struct usb_device *dev = as->state->usbdev; + struct usb_host_interface *alts; + struct usb_interface *iface; + struct usbin *u = &as->usbin; + struct dmabuf *d = &u->dma; + struct audioformat *fmt; + unsigned int ep; + unsigned char data[3]; + int fmtnr, ret; + + iface = usb_ifnum_to_if(dev, u->interface); + if (!iface) + return 0; + + fmtnr = find_format(as->fmtin, as->numfmtin, d->format, d->srate); + if (fmtnr < 0) { + printk(KERN_ERR "usbaudio: set_format_in(): failed to find desired format/speed combination.\n"); + return -1; + } + + fmt = as->fmtin + fmtnr; + alts = usb_altnum_to_altsetting(iface, fmt->altsetting); + u->format = fmt->format; + u->datapipe = usb_rcvisocpipe(dev, alts->endpoint[0].desc.bEndpointAddress & 0xf); + u->syncpipe = u->syncinterval = 0; + if ((alts->endpoint[0].desc.bmAttributes & 0x0c) == 0x08) { + if (alts->desc.bNumEndpoints < 2 || + alts->endpoint[1].desc.bmAttributes != 0x01 || + alts->endpoint[1].desc.bSynchAddress != 0 || + alts->endpoint[1].desc.bEndpointAddress != (alts->endpoint[0].desc.bSynchAddress & 0x7f)) { + printk(KERN_WARNING "usbaudio: device %d interface %d altsetting %d claims adaptive in " + "but has invalid synch pipe; treating as asynchronous in\n", + dev->devnum, u->interface, fmt->altsetting); + } else { + u->syncpipe = usb_sndisocpipe(dev, alts->endpoint[1].desc.bEndpointAddress & 0xf); + u->syncinterval = alts->endpoint[1].desc.bRefresh; + } + } + if (d->srate < fmt->sratelo) + d->srate = fmt->sratelo; + if (d->srate > fmt->sratehi) + d->srate = fmt->sratehi; + dprintk((KERN_DEBUG "usbaudio: set_format_in: usb_set_interface %u %u\n", + u->interface, fmt->altsetting)); + if (usb_set_interface(dev, alts->desc.bInterfaceNumber, fmt->altsetting) < 0) { + printk(KERN_WARNING "usbaudio: usb_set_interface failed, device %d interface %d altsetting %d\n", + dev->devnum, u->interface, fmt->altsetting); + return -1; + } + if (fmt->sratelo == fmt->sratehi) + return 0; + ep = usb_pipeendpoint(u->datapipe) | (u->datapipe & USB_DIR_IN); + /* if endpoint has pitch control, enable it */ + if (fmt->attributes & 0x02) { + data[0] = 1; + if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to set output pitch control device %d interface %u endpoint 0x%x to %u\n", + ret, dev->devnum, u->interface, ep, d->srate); + return -1; + } + } + /* if endpoint has sampling rate control, set it */ + if (fmt->attributes & 0x01) { + data[0] = d->srate; + data[1] = d->srate >> 8; + data[2] = d->srate >> 16; + if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to set input sampling frequency device %d interface %u endpoint 0x%x to %u\n", + ret, dev->devnum, u->interface, ep, d->srate); + return -1; + } + if ((ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, + SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to get input sampling frequency device %d interface %u endpoint 0x%x\n", + ret, dev->devnum, u->interface, ep); + return -1; + } + dprintk((KERN_DEBUG "usbaudio: set_format_in: device %d interface %d altsetting %d srate req: %u real %u\n", + dev->devnum, u->interface, fmt->altsetting, d->srate, data[0] | (data[1] << 8) | (data[2] << 16))); + d->srate = data[0] | (data[1] << 8) | (data[2] << 16); + } + dprintk((KERN_DEBUG "usbaudio: set_format_in: USB format 0x%x, DMA format 0x%x srate %u\n", u->format, d->format, d->srate)); + return 0; +} + +static int set_format_out(struct usb_audiodev *as) +{ + struct usb_device *dev = as->state->usbdev; + struct usb_host_interface *alts; + struct usb_interface *iface; + struct usbout *u = &as->usbout; + struct dmabuf *d = &u->dma; + struct audioformat *fmt; + unsigned int ep; + unsigned char data[3]; + int fmtnr, ret; + + iface = usb_ifnum_to_if(dev, u->interface); + if (!iface) + return 0; + + fmtnr = find_format(as->fmtout, as->numfmtout, d->format, d->srate); + if (fmtnr < 0) { + printk(KERN_ERR "usbaudio: set_format_out(): failed to find desired format/speed combination.\n"); + return -1; + } + + fmt = as->fmtout + fmtnr; + u->format = fmt->format; + alts = usb_altnum_to_altsetting(iface, fmt->altsetting); + u->datapipe = usb_sndisocpipe(dev, alts->endpoint[0].desc.bEndpointAddress & 0xf); + u->syncpipe = u->syncinterval = 0; + if ((alts->endpoint[0].desc.bmAttributes & 0x0c) == 0x04) { +#if 0 + printk(KERN_DEBUG "bNumEndpoints 0x%02x endpoint[1].bmAttributes 0x%02x\n" + KERN_DEBUG "endpoint[1].bSynchAddress 0x%02x endpoint[1].bEndpointAddress 0x%02x\n" + KERN_DEBUG "endpoint[0].bSynchAddress 0x%02x\n", alts->bNumEndpoints, + alts->endpoint[1].bmAttributes, alts->endpoint[1].bSynchAddress, + alts->endpoint[1].bEndpointAddress, alts->endpoint[0].bSynchAddress); +#endif + if (alts->desc.bNumEndpoints < 2 || + alts->endpoint[1].desc.bmAttributes != 0x01 || + alts->endpoint[1].desc.bSynchAddress != 0 || + alts->endpoint[1].desc.bEndpointAddress != (alts->endpoint[0].desc.bSynchAddress | 0x80)) { + printk(KERN_WARNING "usbaudio: device %d interface %d altsetting %d claims asynch out " + "but has invalid synch pipe; treating as adaptive out\n", + dev->devnum, u->interface, fmt->altsetting); + } else { + u->syncpipe = usb_rcvisocpipe(dev, alts->endpoint[1].desc.bEndpointAddress & 0xf); + u->syncinterval = alts->endpoint[1].desc.bRefresh; + } + } + if (d->srate < fmt->sratelo) + d->srate = fmt->sratelo; + if (d->srate > fmt->sratehi) + d->srate = fmt->sratehi; + dprintk((KERN_DEBUG "usbaudio: set_format_out: usb_set_interface %u %u\n", + u->interface, fmt->altsetting)); + if (usb_set_interface(dev, u->interface, fmt->altsetting) < 0) { + printk(KERN_WARNING "usbaudio: usb_set_interface failed, device %d interface %d altsetting %d\n", + dev->devnum, u->interface, fmt->altsetting); + return -1; + } + if (fmt->sratelo == fmt->sratehi) + return 0; + ep = usb_pipeendpoint(u->datapipe) | (u->datapipe & USB_DIR_IN); + /* if endpoint has pitch control, enable it */ + if (fmt->attributes & 0x02) { + data[0] = 1; + if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to set output pitch control device %d interface %u endpoint 0x%x to %u\n", + ret, dev->devnum, u->interface, ep, d->srate); + return -1; + } + } + /* if endpoint has sampling rate control, set it */ + if (fmt->attributes & 0x01) { + data[0] = d->srate; + data[1] = d->srate >> 8; + data[2] = d->srate >> 16; + if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to set output sampling frequency device %d interface %u endpoint 0x%x to %u\n", + ret, dev->devnum, u->interface, ep, d->srate); + return -1; + } + if ((ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, + SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { + printk(KERN_ERR "usbaudio: failure (error %d) to get output sampling frequency device %d interface %u endpoint 0x%x\n", + ret, dev->devnum, u->interface, ep); + return -1; + } + dprintk((KERN_DEBUG "usbaudio: set_format_out: device %d interface %d altsetting %d srate req: %u real %u\n", + dev->devnum, u->interface, fmt->altsetting, d->srate, data[0] | (data[1] << 8) | (data[2] << 16))); + d->srate = data[0] | (data[1] << 8) | (data[2] << 16); + } + dprintk((KERN_DEBUG "usbaudio: set_format_out: USB format 0x%x, DMA format 0x%x srate %u\n", u->format, d->format, d->srate)); + return 0; +} + +static int set_format(struct usb_audiodev *s, unsigned int fmode, unsigned int fmt, unsigned int srate) +{ + int ret1 = 0, ret2 = 0; + + if (!(fmode & (FMODE_READ|FMODE_WRITE))) + return -EINVAL; + if (fmode & FMODE_READ) { + usbin_stop(s); + s->usbin.dma.ready = 0; + if (fmt == AFMT_QUERY) + fmt = s->usbin.dma.format; + else + s->usbin.dma.format = fmt; + if (!srate) + srate = s->usbin.dma.srate; + else + s->usbin.dma.srate = srate; + } + if (fmode & FMODE_WRITE) { + usbout_stop(s); + s->usbout.dma.ready = 0; + if (fmt == AFMT_QUERY) + fmt = s->usbout.dma.format; + else + s->usbout.dma.format = fmt; + if (!srate) + srate = s->usbout.dma.srate; + else + s->usbout.dma.srate = srate; + } + if (fmode & FMODE_READ) + ret1 = set_format_in(s); + if (fmode & FMODE_WRITE) + ret2 = set_format_out(s); + return ret1 ? ret1 : ret2; +} + +/* --------------------------------------------------------------------- */ + +static int wrmixer(struct usb_mixerdev *ms, unsigned mixch, unsigned value) +{ + struct usb_device *dev = ms->state->usbdev; + unsigned char data[2]; + struct mixerchannel *ch; + int v1, v2, v3; + + if (mixch >= ms->numch) + return -1; + ch = &ms->ch[mixch]; + v3 = ch->maxval - ch->minval; + v1 = value & 0xff; + v2 = (value >> 8) & 0xff; + if (v1 > 100) + v1 = 100; + if (v2 > 100) + v2 = 100; + if (!(ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT))) + v2 = v1; + ch->value = v1 | (v2 << 8); + v1 = (v1 * v3) / 100 + ch->minval; + v2 = (v2 * v3) / 100 + ch->minval; + switch (ch->selector) { + case 0: /* mixer unit request */ + data[0] = v1; + data[1] = v1 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (ch->chnum << 8) | 1, ms->iface | (ch->unitid << 8), data, 2, 1000) < 0) + goto err; + if (!(ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT))) + return 0; + data[0] = v2; + data[1] = v2 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + ((ch->chnum + !!(ch->flags & MIXFLG_STEREOIN)) << 8) | (1 + !!(ch->flags & MIXFLG_STEREOOUT)), + ms->iface | (ch->unitid << 8), data, 2, 1000) < 0) + goto err; + return 0; + + /* various feature unit controls */ + case VOLUME_CONTROL: + data[0] = v1; + data[1] = v1 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (ch->selector << 8) | ch->chnum, ms->iface | (ch->unitid << 8), data, 2, 1000) < 0) + goto err; + if (!(ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT))) + return 0; + data[0] = v2; + data[1] = v2 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (ch->selector << 8) | (ch->chnum + 1), ms->iface | (ch->unitid << 8), data, 2, 1000) < 0) + goto err; + return 0; + + case BASS_CONTROL: + case MID_CONTROL: + case TREBLE_CONTROL: + data[0] = v1 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (ch->selector << 8) | ch->chnum, ms->iface | (ch->unitid << 8), data, 1, 1000) < 0) + goto err; + if (!(ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT))) + return 0; + data[0] = v2 >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (ch->selector << 8) | (ch->chnum + 1), ms->iface | (ch->unitid << 8), data, 1, 1000) < 0) + goto err; + return 0; + + default: + return -1; + } + return 0; + + err: + printk(KERN_ERR "usbaudio: mixer request device %u if %u unit %u ch %u selector %u failed\n", + dev->devnum, ms->iface, ch->unitid, ch->chnum, ch->selector); + return -1; +} + +static int get_rec_src(struct usb_mixerdev *ms) +{ + struct usb_device *dev = ms->state->usbdev; + unsigned int mask = 0, retmask = 0; + unsigned int i, j; + unsigned char buf; + int err = 0; + + for (i = 0; i < ms->numch; i++) { + if (!ms->ch[i].slctunitid || (mask & (1 << i))) + continue; + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, ms->iface | (ms->ch[i].slctunitid << 8), &buf, 1, 1000) < 0) { + err = -EIO; + printk(KERN_ERR "usbaudio: selector read request device %u if %u unit %u failed\n", + dev->devnum, ms->iface, ms->ch[i].slctunitid & 0xff); + continue; + } + for (j = i; j < ms->numch; j++) { + if ((ms->ch[i].slctunitid ^ ms->ch[j].slctunitid) & 0xff) + continue; + mask |= 1 << j; + if (buf == (ms->ch[j].slctunitid >> 8)) + retmask |= 1 << ms->ch[j].osschannel; + } + } + if (err) + return -EIO; + return retmask; +} + +static int set_rec_src(struct usb_mixerdev *ms, int srcmask) +{ + struct usb_device *dev = ms->state->usbdev; + unsigned int mask = 0, smask, bmask; + unsigned int i, j; + unsigned char buf; + int err = 0; + + for (i = 0; i < ms->numch; i++) { + if (!ms->ch[i].slctunitid || (mask & (1 << i))) + continue; + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + 0, ms->iface | (ms->ch[i].slctunitid << 8), &buf, 1, 1000) < 0) { + err = -EIO; + printk(KERN_ERR "usbaudio: selector read request device %u if %u unit %u failed\n", + dev->devnum, ms->iface, ms->ch[i].slctunitid & 0xff); + continue; + } + /* first generate smask */ + smask = bmask = 0; + for (j = i; j < ms->numch; j++) { + if ((ms->ch[i].slctunitid ^ ms->ch[j].slctunitid) & 0xff) + continue; + smask |= 1 << ms->ch[j].osschannel; + if (buf == (ms->ch[j].slctunitid >> 8)) + bmask |= 1 << ms->ch[j].osschannel; + mask |= 1 << j; + } + /* check for multiple set sources */ + j = hweight32(srcmask & smask); + if (j == 0) + continue; + if (j > 1) + srcmask &= ~bmask; + for (j = i; j < ms->numch; j++) { + if ((ms->ch[i].slctunitid ^ ms->ch[j].slctunitid) & 0xff) + continue; + if (!(srcmask & (1 << ms->ch[j].osschannel))) + continue; + buf = ms->ch[j].slctunitid >> 8; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0, ms->iface | (ms->ch[j].slctunitid << 8), &buf, 1, 1000) < 0) { + err = -EIO; + printk(KERN_ERR "usbaudio: selector write request device %u if %u unit %u failed\n", + dev->devnum, ms->iface, ms->ch[j].slctunitid & 0xff); + continue; + } + } + } + return err ? -EIO : 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * should be called with open_sem hold, so that no new processes + * look at the audio device to be destroyed + */ + +static void release(struct usb_audio_state *s) +{ + struct usb_audiodev *as; + struct usb_mixerdev *ms; + + s->count--; + if (s->count) { + up(&open_sem); + return; + } + up(&open_sem); + wake_up(&open_wait); + while (!list_empty(&s->audiolist)) { + as = list_entry(s->audiolist.next, struct usb_audiodev, list); + list_del(&as->list); + usbin_release(as); + usbout_release(as); + dmabuf_release(&as->usbin.dma); + dmabuf_release(&as->usbout.dma); + usb_free_urb(as->usbin.durb[0].urb); + usb_free_urb(as->usbin.durb[1].urb); + usb_free_urb(as->usbin.surb[0].urb); + usb_free_urb(as->usbin.surb[1].urb); + usb_free_urb(as->usbout.durb[0].urb); + usb_free_urb(as->usbout.durb[1].urb); + usb_free_urb(as->usbout.surb[0].urb); + usb_free_urb(as->usbout.surb[1].urb); + kfree(as); + } + while (!list_empty(&s->mixerlist)) { + ms = list_entry(s->mixerlist.next, struct usb_mixerdev, list); + list_del(&ms->list); + kfree(ms); + } + kfree(s); +} + +static inline int prog_dmabuf_in(struct usb_audiodev *as) +{ + usbin_stop(as); + return dmabuf_init(&as->usbin.dma); +} + +static inline int prog_dmabuf_out(struct usb_audiodev *as) +{ + usbout_stop(as); + return dmabuf_init(&as->usbout.dma); +} + +/* --------------------------------------------------------------------- */ + +static int usb_audio_open_mixdev(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct usb_mixerdev *ms; + struct usb_audio_state *s; + + down(&open_sem); + list_for_each_entry(s, &audiodevs, audiodev) { + list_for_each_entry(ms, &s->mixerlist, list) { + if (ms->dev_mixer == minor) + goto mixer_found; + } + } + up(&open_sem); + return -ENODEV; + + mixer_found: + if (!s->usbdev) { + up(&open_sem); + return -EIO; + } + file->private_data = ms; + s->count++; + + up(&open_sem); + return nonseekable_open(inode, file); +} + +static int usb_audio_release_mixdev(struct inode *inode, struct file *file) +{ + struct usb_mixerdev *ms = (struct usb_mixerdev *)file->private_data; + struct usb_audio_state *s; + + lock_kernel(); + s = ms->state; + down(&open_sem); + release(s); + unlock_kernel(); + return 0; +} + +static int usb_audio_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usb_mixerdev *ms = (struct usb_mixerdev *)file->private_data; + int i, j, val; + int __user *user_arg = (int __user *)arg; + + if (!ms->state->usbdev) + return -ENODEV; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + + memset(&info, 0, sizeof(info)); + strncpy(info.id, "USB_AUDIO", sizeof(info.id)); + strncpy(info.name, "USB Audio Class Driver", sizeof(info.name)); + info.modify_counter = ms->modcnt; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + + memset(&info, 0, sizeof(info)); + strncpy(info.id, "USB_AUDIO", sizeof(info.id)); + strncpy(info.name, "USB Audio Class Driver", sizeof(info.name)); + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, user_arg); + if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_IOC_DIR(cmd) == _IOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + val = get_rec_src(ms); + if (val < 0) + return val; + return put_user(val, user_arg); + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + for (val = i = 0; i < ms->numch; i++) + val |= 1 << ms->ch[i].osschannel; + return put_user(val, user_arg); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < ms->numch; i++) + if (ms->ch[i].slctunitid) + val |= 1 << ms->ch[i].osschannel; + return put_user(val, user_arg); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + for (val = i = 0; i < ms->numch; i++) + if (ms->ch[i].flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT)) + val |= 1 << ms->ch[i].osschannel; + return put_user(val, user_arg); + + case SOUND_MIXER_CAPS: + return put_user(SOUND_CAP_EXCL_INPUT, user_arg); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES) + return -EINVAL; + for (j = 0; j < ms->numch; j++) { + if (ms->ch[j].osschannel == i) { + return put_user(ms->ch[j].value, user_arg); + } + } + return -EINVAL; + } + } + if (_IOC_DIR(cmd) != (_IOC_READ|_IOC_WRITE)) + return -EINVAL; + ms->modcnt++; + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + if (get_user(val, user_arg)) + return -EFAULT; + return set_rec_src(ms, val); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES) + return -EINVAL; + for (j = 0; j < ms->numch && ms->ch[j].osschannel != i; j++); + if (j >= ms->numch) + return -EINVAL; + if (get_user(val, user_arg)) + return -EFAULT; + if (wrmixer(ms, j, val)) + return -EIO; + return put_user(ms->ch[j].value, user_arg); + } +} + +static /*const*/ struct file_operations usb_mixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = usb_audio_ioctl_mixdev, + .open = usb_audio_open_mixdev, + .release = usb_audio_release_mixdev, +}; + +/* --------------------------------------------------------------------- */ + +static int drain_out(struct usb_audiodev *as, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (as->usbout.dma.mapped || !as->usbout.dma.ready) + return 0; + usbout_start(as); + add_wait_queue(&as->usbout.dma.wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&as->lock, flags); + count = as->usbout.dma.count; + spin_unlock_irqrestore(&as->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&as->usbout.dma.wait, &wait); + set_current_state(TASK_RUNNING); + return -EBUSY; + } + tmo = 3 * HZ * count / as->usbout.dma.srate; + tmo >>= AFMT_BYTESSHIFT(as->usbout.dma.format); + if (!schedule_timeout(tmo + 1)) { + printk(KERN_DEBUG "usbaudio: dma timed out??\n"); + break; + } + } + remove_wait_queue(&as->usbout.dma.wait, &wait); + set_current_state(TASK_RUNNING); + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t usb_audio_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned int ptr; + int cnt, err; + + if (as->usbin.dma.mapped) + return -ENXIO; + if (!as->usbin.dma.ready && (ret = prog_dmabuf_in(as))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + add_wait_queue(&as->usbin.dma.wait, &wait); + while (count > 0) { + spin_lock_irqsave(&as->lock, flags); + ptr = as->usbin.dma.rdptr; + cnt = as->usbin.dma.count; + /* set task state early to avoid wakeup races */ + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&as->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (usbin_start(as)) { + if (!ret) + ret = -ENODEV; + break; + } + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if ((err = dmabuf_copyout_user(&as->usbin.dma, ptr, buffer, cnt))) { + if (!ret) + ret = err; + break; + } + ptr += cnt; + if (ptr >= as->usbin.dma.dmasize) + ptr -= as->usbin.dma.dmasize; + spin_lock_irqsave(&as->lock, flags); + as->usbin.dma.rdptr = ptr; + as->usbin.dma.count -= cnt; + spin_unlock_irqrestore(&as->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&as->usbin.dma.wait, &wait); + return ret; +} + +static ssize_t usb_audio_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t ret = 0; + unsigned long flags; + unsigned int ptr; + unsigned int start_thr; + int cnt, err; + + if (as->usbout.dma.mapped) + return -ENXIO; + if (!as->usbout.dma.ready && (ret = prog_dmabuf_out(as))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + start_thr = (as->usbout.dma.srate << AFMT_BYTESSHIFT(as->usbout.dma.format)) / (1000 / (3 * DESCFRAMES)); + add_wait_queue(&as->usbout.dma.wait, &wait); + while (count > 0) { +#if 0 + printk(KERN_DEBUG "usb_audio_write: count %u dma: count %u rdptr %u wrptr %u dmasize %u fragsize %u flags 0x%02x taskst 0x%lx\n", + count, as->usbout.dma.count, as->usbout.dma.rdptr, as->usbout.dma.wrptr, as->usbout.dma.dmasize, as->usbout.dma.fragsize, + as->usbout.flags, current->state); +#endif + spin_lock_irqsave(&as->lock, flags); + if (as->usbout.dma.count < 0) { + as->usbout.dma.count = 0; + as->usbout.dma.rdptr = as->usbout.dma.wrptr; + } + ptr = as->usbout.dma.wrptr; + cnt = as->usbout.dma.dmasize - as->usbout.dma.count; + /* set task state early to avoid wakeup races */ + if (cnt <= 0) + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&as->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (usbout_start(as)) { + if (!ret) + ret = -ENODEV; + break; + } + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + continue; + } + if ((err = dmabuf_copyin_user(&as->usbout.dma, ptr, buffer, cnt))) { + if (!ret) + ret = err; + break; + } + ptr += cnt; + if (ptr >= as->usbout.dma.dmasize) + ptr -= as->usbout.dma.dmasize; + spin_lock_irqsave(&as->lock, flags); + as->usbout.dma.wrptr = ptr; + as->usbout.dma.count += cnt; + spin_unlock_irqrestore(&as->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + if (as->usbout.dma.count >= start_thr && usbout_start(as)) { + if (!ret) + ret = -ENODEV; + break; + } + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&as->usbout.dma.wait, &wait); + return ret; +} + +/* Called without the kernel lock - fine */ +static unsigned int usb_audio_poll(struct file *file, struct poll_table_struct *wait) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + if (file->f_mode & FMODE_WRITE) { + if (!as->usbout.dma.ready) + prog_dmabuf_out(as); + poll_wait(file, &as->usbout.dma.wait, wait); + } + if (file->f_mode & FMODE_READ) { + if (!as->usbin.dma.ready) + prog_dmabuf_in(as); + poll_wait(file, &as->usbin.dma.wait, wait); + } + spin_lock_irqsave(&as->lock, flags); + if (file->f_mode & FMODE_READ) { + if (as->usbin.dma.count >= (signed)as->usbin.dma.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (as->usbout.dma.mapped) { + if (as->usbout.dma.count >= (signed)as->usbout.dma.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)as->usbout.dma.dmasize >= as->usbout.dma.count + (signed)as->usbout.dma.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&as->lock, flags); + return mask; +} + +static int usb_audio_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + struct dmabuf *db; + int ret = -EINVAL; + + lock_kernel(); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf_out(as)) != 0) + goto out; + db = &as->usbout.dma; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf_in(as)) != 0) + goto out; + db = &as->usbin.dma; + } else + goto out; + + ret = -EINVAL; + if (vma->vm_pgoff != 0) + goto out; + + ret = dmabuf_mmap(vma, db, vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_page_prot); +out: + unlock_kernel(); + return ret; +} + +static int usb_audio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + struct usb_audio_state *s = as->state; + int __user *user_arg = (int __user *)arg; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val = 0; + int val2, mapped, ret; + + if (!s->usbdev) + return -EIO; + mapped = ((file->f_mode & FMODE_WRITE) && as->usbout.dma.mapped) || + ((file->f_mode & FMODE_READ) && as->usbin.dma.mapped); +#if 0 + if (arg) + get_user(val, (int *)arg); + printk(KERN_DEBUG "usbaudio: usb_audio_ioctl cmd=%x arg=%lx *arg=%d\n", cmd, arg, val) +#endif + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, user_arg); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_out(as, 0/*file->f_flags & O_NONBLOCK*/); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | + DSP_CAP_MMAP | DSP_CAP_BATCH, user_arg); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + usbout_stop(as); + as->usbout.dma.rdptr = as->usbout.dma.wrptr = as->usbout.dma.count = as->usbout.dma.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + usbin_stop(as); + as->usbin.dma.rdptr = as->usbin.dma.wrptr = as->usbin.dma.count = as->usbin.dma.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, user_arg)) + return -EFAULT; + if (val >= 0) { + if (val < 4000) + val = 4000; + if (val > 100000) + val = 100000; + if (set_format(as, file->f_mode, AFMT_QUERY, val)) + return -EIO; + } + return put_user((file->f_mode & FMODE_READ) ? + as->usbin.dma.srate : as->usbout.dma.srate, + user_arg); + + case SNDCTL_DSP_STEREO: + if (get_user(val, user_arg)) + return -EFAULT; + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + if (val) + val2 |= AFMT_STEREO; + else + val2 &= ~AFMT_STEREO; + if (set_format(as, file->f_mode, val2, 0)) + return -EIO; + return 0; + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, user_arg)) + return -EFAULT; + if (val != 0) { + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + if (val == 1) + val2 &= ~AFMT_STEREO; + else + val2 |= AFMT_STEREO; + if (set_format(as, file->f_mode, val2, 0)) + return -EIO; + } + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + return put_user(AFMT_ISSTEREO(val2) ? 2 : 1, user_arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_U8 | AFMT_U16_LE | AFMT_U16_BE | + AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE, user_arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + if (get_user(val, user_arg)) + return -EFAULT; + if (val != AFMT_QUERY) { + if (hweight32(val) != 1) + return -EINVAL; + if (!(val & (AFMT_U8 | AFMT_U16_LE | AFMT_U16_BE | + AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE))) + return -EINVAL; + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + val |= val2 & AFMT_STEREO; + if (set_format(as, file->f_mode, val, 0)) + return -EIO; + } + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + return put_user(val2 & ~AFMT_STEREO, user_arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && as->usbin.flags & FLG_RUNNING) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && as->usbout.flags & FLG_RUNNING) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, user_arg); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, user_arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!as->usbin.dma.ready && (ret = prog_dmabuf_in(as))) + return ret; + if (usbin_start(as)) + return -ENODEV; + } else + usbin_stop(as); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!as->usbout.dma.ready && (ret = prog_dmabuf_out(as))) + return ret; + if (usbout_start(as)) + return -ENODEV; + } else + usbout_stop(as); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!(as->usbout.flags & FLG_RUNNING) && (val = prog_dmabuf_out(as)) != 0) + return val; + spin_lock_irqsave(&as->lock, flags); + abinfo.fragsize = as->usbout.dma.fragsize; + abinfo.bytes = as->usbout.dma.dmasize - as->usbout.dma.count; + abinfo.fragstotal = as->usbout.dma.numfrag; + abinfo.fragments = abinfo.bytes >> as->usbout.dma.fragshift; + spin_unlock_irqrestore(&as->lock, flags); + return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!(as->usbin.flags & FLG_RUNNING) && (val = prog_dmabuf_in(as)) != 0) + return val; + spin_lock_irqsave(&as->lock, flags); + abinfo.fragsize = as->usbin.dma.fragsize; + abinfo.bytes = as->usbin.dma.count; + abinfo.fragstotal = as->usbin.dma.numfrag; + abinfo.fragments = abinfo.bytes >> as->usbin.dma.fragshift; + spin_unlock_irqrestore(&as->lock, flags); + return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&as->lock, flags); + val = as->usbout.dma.count; + spin_unlock_irqrestore(&as->lock, flags); + return put_user(val, user_arg); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&as->lock, flags); + cinfo.bytes = as->usbin.dma.total_bytes; + cinfo.blocks = as->usbin.dma.count >> as->usbin.dma.fragshift; + cinfo.ptr = as->usbin.dma.wrptr; + if (as->usbin.dma.mapped) + as->usbin.dma.count &= as->usbin.dma.fragsize-1; + spin_unlock_irqrestore(&as->lock, flags); + if (copy_to_user((void __user *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&as->lock, flags); + cinfo.bytes = as->usbout.dma.total_bytes; + cinfo.blocks = as->usbout.dma.count >> as->usbout.dma.fragshift; + cinfo.ptr = as->usbout.dma.rdptr; + if (as->usbout.dma.mapped) + as->usbout.dma.count &= as->usbout.dma.fragsize-1; + spin_unlock_irqrestore(&as->lock, flags); + if (copy_to_user((void __user *)arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf_out(as))) + return val; + return put_user(as->usbout.dma.fragsize, user_arg); + } + if ((val = prog_dmabuf_in(as))) + return val; + return put_user(as->usbin.dma.fragsize, user_arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, user_arg)) + return -EFAULT; + if (file->f_mode & FMODE_READ) { + as->usbin.dma.ossfragshift = val & 0xffff; + as->usbin.dma.ossmaxfrags = (val >> 16) & 0xffff; + if (as->usbin.dma.ossfragshift < 4) + as->usbin.dma.ossfragshift = 4; + if (as->usbin.dma.ossfragshift > 15) + as->usbin.dma.ossfragshift = 15; + if (as->usbin.dma.ossmaxfrags < 4) + as->usbin.dma.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + as->usbout.dma.ossfragshift = val & 0xffff; + as->usbout.dma.ossmaxfrags = (val >> 16) & 0xffff; + if (as->usbout.dma.ossfragshift < 4) + as->usbout.dma.ossfragshift = 4; + if (as->usbout.dma.ossfragshift > 15) + as->usbout.dma.ossfragshift = 15; + if (as->usbout.dma.ossmaxfrags < 4) + as->usbout.dma.ossmaxfrags = 4; + } + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if ((file->f_mode & FMODE_READ && as->usbin.dma.subdivision) || + (file->f_mode & FMODE_WRITE && as->usbout.dma.subdivision)) + return -EINVAL; + if (get_user(val, user_arg)) + return -EFAULT; + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + as->usbin.dma.subdivision = val; + if (file->f_mode & FMODE_WRITE) + as->usbout.dma.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? + as->usbin.dma.srate : as->usbout.dma.srate, + user_arg); + + case SOUND_PCM_READ_CHANNELS: + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + return put_user(AFMT_ISSTEREO(val2) ? 2 : 1, user_arg); + + case SOUND_PCM_READ_BITS: + val2 = (file->f_mode & FMODE_READ) ? as->usbin.dma.format : as->usbout.dma.format; + return put_user(AFMT_IS16BIT(val2) ? 16 : 8, user_arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + } + dprintk((KERN_DEBUG "usbaudio: usb_audio_ioctl - no command found\n")); + return -ENOIOCTLCMD; +} + +static int usb_audio_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct usb_audiodev *as; + struct usb_audio_state *s; + + for (;;) { + down(&open_sem); + list_for_each_entry(s, &audiodevs, audiodev) { + list_for_each_entry(as, &s->audiolist, list) { + if (!((as->dev_audio ^ minor) & ~0xf)) + goto device_found; + } + } + up(&open_sem); + return -ENODEV; + + device_found: + if (!s->usbdev) { + up(&open_sem); + return -EIO; + } + /* wait for device to become free */ + if (!(as->open_mode & file->f_mode)) + break; + if (file->f_flags & O_NONBLOCK) { + up(&open_sem); + return -EBUSY; + } + __set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&open_wait, &wait); + up(&open_sem); + schedule(); + __set_current_state(TASK_RUNNING); + remove_wait_queue(&open_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + } + if (file->f_mode & FMODE_READ) + as->usbin.dma.ossfragshift = as->usbin.dma.ossmaxfrags = as->usbin.dma.subdivision = 0; + if (file->f_mode & FMODE_WRITE) + as->usbout.dma.ossfragshift = as->usbout.dma.ossmaxfrags = as->usbout.dma.subdivision = 0; + if (set_format(as, file->f_mode, ((minor & 0xf) == SND_DEV_DSP16) ? AFMT_S16_LE : AFMT_U8 /* AFMT_ULAW */, 8000)) { + up(&open_sem); + return -EIO; + } + file->private_data = as; + as->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + s->count++; + up(&open_sem); + return nonseekable_open(inode, file); +} + +static int usb_audio_release(struct inode *inode, struct file *file) +{ + struct usb_audiodev *as = (struct usb_audiodev *)file->private_data; + struct usb_audio_state *s; + struct usb_device *dev; + + lock_kernel(); + s = as->state; + dev = s->usbdev; + if (file->f_mode & FMODE_WRITE) + drain_out(as, file->f_flags & O_NONBLOCK); + down(&open_sem); + if (file->f_mode & FMODE_WRITE) { + usbout_stop(as); + if (dev && as->usbout.interface >= 0) + usb_set_interface(dev, as->usbout.interface, 0); + dmabuf_release(&as->usbout.dma); + usbout_release(as); + } + if (file->f_mode & FMODE_READ) { + usbin_stop(as); + if (dev && as->usbin.interface >= 0) + usb_set_interface(dev, as->usbin.interface, 0); + dmabuf_release(&as->usbin.dma); + usbin_release(as); + } + as->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + release(s); + wake_up(&open_wait); + unlock_kernel(); + return 0; +} + +static /*const*/ struct file_operations usb_audio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = usb_audio_read, + .write = usb_audio_write, + .poll = usb_audio_poll, + .ioctl = usb_audio_ioctl, + .mmap = usb_audio_mmap, + .open = usb_audio_open, + .release = usb_audio_release, +}; + +/* --------------------------------------------------------------------- */ + +static int usb_audio_probe(struct usb_interface *iface, + const struct usb_device_id *id); +static void usb_audio_disconnect(struct usb_interface *iface); + +static struct usb_device_id usb_audio_ids [] = { + { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), + .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = 1}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_audio_ids); + +static struct usb_driver usb_audio_driver = { + .owner = THIS_MODULE, + .name = "audio", + .probe = usb_audio_probe, + .disconnect = usb_audio_disconnect, + .id_table = usb_audio_ids, +}; + +static void *find_descriptor(void *descstart, unsigned int desclen, void *after, + u8 dtype, int iface, int altsetting) +{ + u8 *p, *end, *next; + int ifc = -1, as = -1; + + p = descstart; + end = p + desclen; + for (; p < end;) { + if (p[0] < 2) + return NULL; + next = p + p[0]; + if (next > end) + return NULL; + if (p[1] == USB_DT_INTERFACE) { + /* minimum length of interface descriptor */ + if (p[0] < 9) + return NULL; + ifc = p[2]; + as = p[3]; + } + if (p[1] == dtype && (!after || (void *)p > after) && + (iface == -1 || iface == ifc) && (altsetting == -1 || altsetting == as)) { + return p; + } + p = next; + } + return NULL; +} + +static void *find_csinterface_descriptor(void *descstart, unsigned int desclen, void *after, u8 dsubtype, int iface, int altsetting) +{ + unsigned char *p; + + p = find_descriptor(descstart, desclen, after, USB_DT_CS_INTERFACE, iface, altsetting); + while (p) { + if (p[0] >= 3 && p[2] == dsubtype) + return p; + p = find_descriptor(descstart, desclen, p, USB_DT_CS_INTERFACE, iface, altsetting); + } + return NULL; +} + +static void *find_audiocontrol_unit(void *descstart, unsigned int desclen, void *after, u8 unit, int iface) +{ + unsigned char *p; + + p = find_descriptor(descstart, desclen, after, USB_DT_CS_INTERFACE, iface, -1); + while (p) { + if (p[0] >= 4 && p[2] >= INPUT_TERMINAL && p[2] <= EXTENSION_UNIT && p[3] == unit) + return p; + p = find_descriptor(descstart, desclen, p, USB_DT_CS_INTERFACE, iface, -1); + } + return NULL; +} + +static void usb_audio_parsestreaming(struct usb_audio_state *s, unsigned char *buffer, unsigned int buflen, int asifin, int asifout) +{ + struct usb_device *dev = s->usbdev; + struct usb_audiodev *as; + struct usb_host_interface *alts; + struct usb_interface *iface; + struct audioformat *fp; + unsigned char *fmt, *csep; + unsigned int i, j, k, format, idx; + + if (!(as = kmalloc(sizeof(struct usb_audiodev), GFP_KERNEL))) + return; + memset(as, 0, sizeof(struct usb_audiodev)); + init_waitqueue_head(&as->usbin.dma.wait); + init_waitqueue_head(&as->usbout.dma.wait); + spin_lock_init(&as->lock); + as->usbin.durb[0].urb = usb_alloc_urb (DESCFRAMES, GFP_KERNEL); + as->usbin.durb[1].urb = usb_alloc_urb (DESCFRAMES, GFP_KERNEL); + as->usbin.surb[0].urb = usb_alloc_urb (SYNCFRAMES, GFP_KERNEL); + as->usbin.surb[1].urb = usb_alloc_urb (SYNCFRAMES, GFP_KERNEL); + as->usbout.durb[0].urb = usb_alloc_urb (DESCFRAMES, GFP_KERNEL); + as->usbout.durb[1].urb = usb_alloc_urb (DESCFRAMES, GFP_KERNEL); + as->usbout.surb[0].urb = usb_alloc_urb (SYNCFRAMES, GFP_KERNEL); + as->usbout.surb[1].urb = usb_alloc_urb (SYNCFRAMES, GFP_KERNEL); + if ((!as->usbin.durb[0].urb) || + (!as->usbin.durb[1].urb) || + (!as->usbin.surb[0].urb) || + (!as->usbin.surb[1].urb) || + (!as->usbout.durb[0].urb) || + (!as->usbout.durb[1].urb) || + (!as->usbout.surb[0].urb) || + (!as->usbout.surb[1].urb)) { + usb_free_urb(as->usbin.durb[0].urb); + usb_free_urb(as->usbin.durb[1].urb); + usb_free_urb(as->usbin.surb[0].urb); + usb_free_urb(as->usbin.surb[1].urb); + usb_free_urb(as->usbout.durb[0].urb); + usb_free_urb(as->usbout.durb[1].urb); + usb_free_urb(as->usbout.surb[0].urb); + usb_free_urb(as->usbout.surb[1].urb); + kfree(as); + return; + } + as->state = s; + as->usbin.interface = asifin; + as->usbout.interface = asifout; + /* search for input formats */ + if (asifin >= 0) { + as->usbin.flags = FLG_CONNECTED; + iface = usb_ifnum_to_if(dev, asifin); + for (idx = 0; idx < iface->num_altsetting; idx++) { + alts = &iface->altsetting[idx]; + i = alts->desc.bAlternateSetting; + if (alts->desc.bInterfaceClass != USB_CLASS_AUDIO || alts->desc.bInterfaceSubClass != 2) + continue; + if (alts->desc.bNumEndpoints < 1) { + if (i != 0) { /* altsetting 0 has no endpoints (Section B.3.4.1) */ + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u does not have an endpoint\n", + dev->devnum, asifin, i); + } + continue; + } + if ((alts->endpoint[0].desc.bmAttributes & 0x03) != 0x01 || + !(alts->endpoint[0].desc.bEndpointAddress & 0x80)) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u first endpoint not isochronous in\n", + dev->devnum, asifin, i); + continue; + } + fmt = find_csinterface_descriptor(buffer, buflen, NULL, AS_GENERAL, asifin, i); + if (!fmt) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not found\n", + dev->devnum, asifin, i); + continue; + } + if (fmt[0] < 7 || fmt[6] != 0 || (fmt[5] != 1 && fmt[5] != 2)) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u format not supported\n", + dev->devnum, asifin, i); + continue; + } + format = (fmt[5] == 2) ? (AFMT_U16_LE | AFMT_U8) : (AFMT_S16_LE | AFMT_S8); + fmt = find_csinterface_descriptor(buffer, buflen, NULL, FORMAT_TYPE, asifin, i); + if (!fmt) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not found\n", + dev->devnum, asifin, i); + continue; + } + if (fmt[0] < 8+3*(fmt[7] ? fmt[7] : 2) || fmt[3] != 1) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not supported\n", + dev->devnum, asifin, i); + continue; + } + if (fmt[4] < 1 || fmt[4] > 2 || fmt[5] < 1 || fmt[5] > 2) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u unsupported channels %u framesize %u\n", + dev->devnum, asifin, i, fmt[4], fmt[5]); + continue; + } + csep = find_descriptor(buffer, buflen, NULL, USB_DT_CS_ENDPOINT, asifin, i); + if (!csep || csep[0] < 7 || csep[2] != EP_GENERAL) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u no or invalid class specific endpoint descriptor\n", + dev->devnum, asifin, i); + continue; + } + if (as->numfmtin >= MAXFORMATS) + continue; + fp = &as->fmtin[as->numfmtin++]; + if (fmt[5] == 2) + format &= (AFMT_U16_LE | AFMT_S16_LE); + else + format &= (AFMT_U8 | AFMT_S8); + if (fmt[4] == 2) + format |= AFMT_STEREO; + fp->format = format; + fp->altsetting = i; + fp->sratelo = fp->sratehi = fmt[8] | (fmt[9] << 8) | (fmt[10] << 16); + printk(KERN_INFO "usbaudio: valid input sample rate %u\n", fp->sratelo); + for (j = fmt[7] ? (fmt[7]-1) : 1; j > 0; j--) { + k = fmt[8+3*j] | (fmt[9+3*j] << 8) | (fmt[10+3*j] << 16); + printk(KERN_INFO "usbaudio: valid input sample rate %u\n", k); + if (k > fp->sratehi) + fp->sratehi = k; + if (k < fp->sratelo) + fp->sratelo = k; + } + fp->attributes = csep[3]; + printk(KERN_INFO "usbaudio: device %u interface %u altsetting %u: format 0x%08x sratelo %u sratehi %u attributes 0x%02x\n", + dev->devnum, asifin, i, fp->format, fp->sratelo, fp->sratehi, fp->attributes); + } + } + /* search for output formats */ + if (asifout >= 0) { + as->usbout.flags = FLG_CONNECTED; + iface = usb_ifnum_to_if(dev, asifout); + for (idx = 0; idx < iface->num_altsetting; idx++) { + alts = &iface->altsetting[idx]; + i = alts->desc.bAlternateSetting; + if (alts->desc.bInterfaceClass != USB_CLASS_AUDIO || alts->desc.bInterfaceSubClass != 2) + continue; + if (alts->desc.bNumEndpoints < 1) { + /* altsetting 0 should never have iso EPs */ + if (i != 0) + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u does not have an endpoint\n", + dev->devnum, asifout, i); + continue; + } + if ((alts->endpoint[0].desc.bmAttributes & 0x03) != 0x01 || + (alts->endpoint[0].desc.bEndpointAddress & 0x80)) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u first endpoint not isochronous out\n", + dev->devnum, asifout, i); + continue; + } + /* See USB audio formats manual, section 2 */ + fmt = find_csinterface_descriptor(buffer, buflen, NULL, AS_GENERAL, asifout, i); + if (!fmt) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not found\n", + dev->devnum, asifout, i); + continue; + } + if (fmt[0] < 7 || fmt[6] != 0 || (fmt[5] != 1 && fmt[5] != 2)) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u format not supported\n", + dev->devnum, asifout, i); + continue; + } + format = (fmt[5] == 2) ? (AFMT_U16_LE | AFMT_U8) : (AFMT_S16_LE | AFMT_S8); + /* Dallas DS4201 workaround */ + if (le16_to_cpu(dev->descriptor.idVendor) == 0x04fa && + le16_to_cpu(dev->descriptor.idProduct) == 0x4201) + format = (AFMT_S16_LE | AFMT_S8); + fmt = find_csinterface_descriptor(buffer, buflen, NULL, FORMAT_TYPE, asifout, i); + if (!fmt) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not found\n", + dev->devnum, asifout, i); + continue; + } + if (fmt[0] < 8+3*(fmt[7] ? fmt[7] : 2) || fmt[3] != 1) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u FORMAT_TYPE descriptor not supported\n", + dev->devnum, asifout, i); + continue; + } + if (fmt[4] < 1 || fmt[4] > 2 || fmt[5] < 1 || fmt[5] > 2) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u unsupported channels %u framesize %u\n", + dev->devnum, asifout, i, fmt[4], fmt[5]); + continue; + } + csep = find_descriptor(buffer, buflen, NULL, USB_DT_CS_ENDPOINT, asifout, i); + if (!csep || csep[0] < 7 || csep[2] != EP_GENERAL) { + printk(KERN_ERR "usbaudio: device %u interface %u altsetting %u no or invalid class specific endpoint descriptor\n", + dev->devnum, asifout, i); + continue; + } + if (as->numfmtout >= MAXFORMATS) + continue; + fp = &as->fmtout[as->numfmtout++]; + if (fmt[5] == 2) + format &= (AFMT_U16_LE | AFMT_S16_LE); + else + format &= (AFMT_U8 | AFMT_S8); + if (fmt[4] == 2) + format |= AFMT_STEREO; + fp->format = format; + fp->altsetting = i; + fp->sratelo = fp->sratehi = fmt[8] | (fmt[9] << 8) | (fmt[10] << 16); + printk(KERN_INFO "usbaudio: valid output sample rate %u\n", fp->sratelo); + for (j = fmt[7] ? (fmt[7]-1) : 1; j > 0; j--) { + k = fmt[8+3*j] | (fmt[9+3*j] << 8) | (fmt[10+3*j] << 16); + printk(KERN_INFO "usbaudio: valid output sample rate %u\n", k); + if (k > fp->sratehi) + fp->sratehi = k; + if (k < fp->sratelo) + fp->sratelo = k; + } + fp->attributes = csep[3]; + printk(KERN_INFO "usbaudio: device %u interface %u altsetting %u: format 0x%08x sratelo %u sratehi %u attributes 0x%02x\n", + dev->devnum, asifout, i, fp->format, fp->sratelo, fp->sratehi, fp->attributes); + } + } + if (as->numfmtin == 0 && as->numfmtout == 0) { + usb_free_urb(as->usbin.durb[0].urb); + usb_free_urb(as->usbin.durb[1].urb); + usb_free_urb(as->usbin.surb[0].urb); + usb_free_urb(as->usbin.surb[1].urb); + usb_free_urb(as->usbout.durb[0].urb); + usb_free_urb(as->usbout.durb[1].urb); + usb_free_urb(as->usbout.surb[0].urb); + usb_free_urb(as->usbout.surb[1].urb); + kfree(as); + return; + } + if ((as->dev_audio = register_sound_dsp(&usb_audio_fops, -1)) < 0) { + printk(KERN_ERR "usbaudio: cannot register dsp\n"); + usb_free_urb(as->usbin.durb[0].urb); + usb_free_urb(as->usbin.durb[1].urb); + usb_free_urb(as->usbin.surb[0].urb); + usb_free_urb(as->usbin.surb[1].urb); + usb_free_urb(as->usbout.durb[0].urb); + usb_free_urb(as->usbout.durb[1].urb); + usb_free_urb(as->usbout.surb[0].urb); + usb_free_urb(as->usbout.surb[1].urb); + kfree(as); + return; + } + printk(KERN_INFO "usbaudio: registered dsp 14,%d\n", as->dev_audio); + /* everything successful */ + list_add_tail(&as->list, &s->audiolist); +} + +struct consmixstate { + struct usb_audio_state *s; + unsigned char *buffer; + unsigned int buflen; + unsigned int ctrlif; + struct mixerchannel mixch[SOUND_MIXER_NRDEVICES]; + unsigned int nrmixch; + unsigned int mixchmask; + unsigned long unitbitmap[32/sizeof(unsigned long)]; + /* return values */ + unsigned int nrchannels; + unsigned int termtype; + unsigned int chconfig; +}; + +static struct mixerchannel *getmixchannel(struct consmixstate *state, unsigned int nr) +{ + struct mixerchannel *c; + + if (nr >= SOUND_MIXER_NRDEVICES) { + printk(KERN_ERR "usbaudio: invalid OSS mixer channel %u\n", nr); + return NULL; + } + if (!(state->mixchmask & (1 << nr))) { + printk(KERN_WARNING "usbaudio: OSS mixer channel %u already in use\n", nr); + return NULL; + } + c = &state->mixch[state->nrmixch++]; + c->osschannel = nr; + state->mixchmask &= ~(1 << nr); + return c; +} + +static unsigned int getvolchannel(struct consmixstate *state) +{ + unsigned int u; + + if ((state->termtype & 0xff00) == 0x0000 && (state->mixchmask & SOUND_MASK_VOLUME)) + return SOUND_MIXER_VOLUME; + if ((state->termtype & 0xff00) == 0x0100) { + if (state->mixchmask & SOUND_MASK_PCM) + return SOUND_MIXER_PCM; + if (state->mixchmask & SOUND_MASK_ALTPCM) + return SOUND_MIXER_ALTPCM; + } + if ((state->termtype & 0xff00) == 0x0200 && (state->mixchmask & SOUND_MASK_MIC)) + return SOUND_MIXER_MIC; + if ((state->termtype & 0xff00) == 0x0300 && (state->mixchmask & SOUND_MASK_SPEAKER)) + return SOUND_MIXER_SPEAKER; + if ((state->termtype & 0xff00) == 0x0500) { + if (state->mixchmask & SOUND_MASK_PHONEIN) + return SOUND_MIXER_PHONEIN; + if (state->mixchmask & SOUND_MASK_PHONEOUT) + return SOUND_MIXER_PHONEOUT; + } + if (state->termtype >= 0x710 && state->termtype <= 0x711 && (state->mixchmask & SOUND_MASK_RADIO)) + return SOUND_MIXER_RADIO; + if (state->termtype >= 0x709 && state->termtype <= 0x70f && (state->mixchmask & SOUND_MASK_VIDEO)) + return SOUND_MIXER_VIDEO; + u = ffs(state->mixchmask & (SOUND_MASK_LINE | SOUND_MASK_CD | SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | SOUND_MASK_LINE3 | + SOUND_MASK_DIGITAL1 | SOUND_MASK_DIGITAL2 | SOUND_MASK_DIGITAL3)); + return u-1; +} + +static void prepmixch(struct consmixstate *state) +{ + struct usb_device *dev = state->s->usbdev; + struct mixerchannel *ch; + unsigned char *buf; + __s16 v1; + unsigned int v2, v3; + + if (!state->nrmixch || state->nrmixch > SOUND_MIXER_NRDEVICES) + return; + buf = kmalloc(sizeof(*buf) * 2, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "prepmixch: out of memory\n") ; + return; + } + + ch = &state->mixch[state->nrmixch-1]; + switch (ch->selector) { + case 0: /* mixer unit request */ + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MIN, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->chnum << 8) | 1, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + ch->minval = buf[0] | (buf[1] << 8); + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MAX, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->chnum << 8) | 1, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + ch->maxval = buf[0] | (buf[1] << 8); + v2 = ch->maxval - ch->minval; + if (!v2) + v2 = 1; + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->chnum << 8) | 1, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + v1 = buf[0] | (buf[1] << 8); + v3 = v1 - ch->minval; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + ch->value = v3; + if (ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT)) { + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + ((ch->chnum + !!(ch->flags & MIXFLG_STEREOIN)) << 8) | (1 + !!(ch->flags & MIXFLG_STEREOOUT)), + state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + v1 = buf[0] | (buf[1] << 8); + v3 = v1 - ch->minval; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + } + ch->value |= v3 << 8; + break; + + /* various feature unit controls */ + case VOLUME_CONTROL: + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MIN, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + ch->minval = buf[0] | (buf[1] << 8); + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MAX, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + ch->maxval = buf[0] | (buf[1] << 8); + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + v1 = buf[0] | (buf[1] << 8); + v2 = ch->maxval - ch->minval; + v3 = v1 - ch->minval; + if (!v2) + v2 = 1; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + ch->value = v3; + if (ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT)) { + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | (ch->chnum + 1), state->ctrlif | (ch->unitid << 8), buf, 2, 1000) < 0) + goto err; + v1 = buf[0] | (buf[1] << 8); + v3 = v1 - ch->minval; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + } + ch->value |= v3 << 8; + break; + + case BASS_CONTROL: + case MID_CONTROL: + case TREBLE_CONTROL: + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MIN, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 1, 1000) < 0) + goto err; + ch->minval = buf[0] << 8; + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_MAX, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 1, 1000) < 0) + goto err; + ch->maxval = buf[0] << 8; + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | ch->chnum, state->ctrlif | (ch->unitid << 8), buf, 1, 1000) < 0) + goto err; + v1 = buf[0] << 8; + v2 = ch->maxval - ch->minval; + v3 = v1 - ch->minval; + if (!v2) + v2 = 1; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + ch->value = v3; + if (ch->flags & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT)) { + if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + (ch->selector << 8) | (ch->chnum + 1), state->ctrlif | (ch->unitid << 8), buf, 1, 1000) < 0) + goto err; + v1 = buf[0] << 8; + v3 = v1 - ch->minval; + v3 = 100 * v3 / v2; + if (v3 > 100) + v3 = 100; + } + ch->value |= v3 << 8; + break; + + default: + goto err; + } + + freebuf: + kfree(buf); + return; + err: + printk(KERN_ERR "usbaudio: mixer request device %u if %u unit %u ch %u selector %u failed\n", + dev->devnum, state->ctrlif, ch->unitid, ch->chnum, ch->selector); + if (state->nrmixch) + state->nrmixch--; + goto freebuf; +} + + +static void usb_audio_recurseunit(struct consmixstate *state, unsigned char unitid); + +static inline int checkmixbmap(unsigned char *bmap, unsigned char flg, unsigned int inidx, unsigned int numoch) +{ + unsigned int idx; + + idx = inidx*numoch; + if (!(bmap[-(idx >> 3)] & (0x80 >> (idx & 7)))) + return 0; + if (!(flg & (MIXFLG_STEREOIN | MIXFLG_STEREOOUT))) + return 1; + idx = (inidx+!!(flg & MIXFLG_STEREOIN))*numoch+!!(flg & MIXFLG_STEREOOUT); + if (!(bmap[-(idx >> 3)] & (0x80 >> (idx & 7)))) + return 0; + return 1; +} + +static void usb_audio_mixerunit(struct consmixstate *state, unsigned char *mixer) +{ + unsigned int nroutch = mixer[5+mixer[4]]; + unsigned int chidx[SOUND_MIXER_NRDEVICES+1]; + unsigned int termt[SOUND_MIXER_NRDEVICES]; + unsigned char flg = (nroutch >= 2) ? MIXFLG_STEREOOUT : 0; + unsigned char *bmap = &mixer[9+mixer[4]]; + unsigned int bmapsize; + struct mixerchannel *ch; + unsigned int i; + + if (!mixer[4]) { + printk(KERN_ERR "usbaudio: unit %u invalid MIXER_UNIT descriptor\n", mixer[3]); + return; + } + if (mixer[4] > SOUND_MIXER_NRDEVICES) { + printk(KERN_ERR "usbaudio: mixer unit %u: too many input pins\n", mixer[3]); + return; + } + chidx[0] = 0; + for (i = 0; i < mixer[4]; i++) { + usb_audio_recurseunit(state, mixer[5+i]); + chidx[i+1] = chidx[i] + state->nrchannels; + termt[i] = state->termtype; + } + state->termtype = 0; + state->chconfig = mixer[6+mixer[4]] | (mixer[7+mixer[4]] << 8); + bmapsize = (nroutch * chidx[mixer[4]] + 7) >> 3; + bmap += bmapsize - 1; + if (mixer[0] < 10+mixer[4]+bmapsize) { + printk(KERN_ERR "usbaudio: unit %u invalid MIXER_UNIT descriptor (bitmap too small)\n", mixer[3]); + return; + } + for (i = 0; i < mixer[4]; i++) { + state->termtype = termt[i]; + if (chidx[i+1]-chidx[i] >= 2) { + flg |= MIXFLG_STEREOIN; + if (checkmixbmap(bmap, flg, chidx[i], nroutch)) { + ch = getmixchannel(state, getvolchannel(state)); + if (ch) { + ch->unitid = mixer[3]; + ch->selector = 0; + ch->chnum = chidx[i]+1; + ch->flags = flg; + prepmixch(state); + } + continue; + } + } + flg &= ~MIXFLG_STEREOIN; + if (checkmixbmap(bmap, flg, chidx[i], nroutch)) { + ch = getmixchannel(state, getvolchannel(state)); + if (ch) { + ch->unitid = mixer[3]; + ch->selector = 0; + ch->chnum = chidx[i]+1; + ch->flags = flg; + prepmixch(state); + } + } + } + state->termtype = 0; +} + +static struct mixerchannel *slctsrc_findunit(struct consmixstate *state, __u8 unitid) +{ + unsigned int i; + + for (i = 0; i < state->nrmixch; i++) + if (state->mixch[i].unitid == unitid) + return &state->mixch[i]; + return NULL; +} + +static void usb_audio_selectorunit(struct consmixstate *state, unsigned char *selector) +{ + unsigned int chnum, i, mixch; + struct mixerchannel *mch; + + if (!selector[4]) { + printk(KERN_ERR "usbaudio: unit %u invalid SELECTOR_UNIT descriptor\n", selector[3]); + return; + } + mixch = state->nrmixch; + usb_audio_recurseunit(state, selector[5]); + if (state->nrmixch != mixch) { + mch = &state->mixch[state->nrmixch-1]; + mch->slctunitid = selector[3] | (1 << 8); + } else if ((mch = slctsrc_findunit(state, selector[5]))) { + mch->slctunitid = selector[3] | (1 << 8); + } else { + printk(KERN_INFO "usbaudio: selector unit %u: ignoring channel 1\n", selector[3]); + } + chnum = state->nrchannels; + for (i = 1; i < selector[4]; i++) { + mixch = state->nrmixch; + usb_audio_recurseunit(state, selector[5+i]); + if (chnum != state->nrchannels) { + printk(KERN_ERR "usbaudio: selector unit %u: input pins with varying channel numbers\n", selector[3]); + state->termtype = 0; + state->chconfig = 0; + state->nrchannels = 0; + return; + } + if (state->nrmixch != mixch) { + mch = &state->mixch[state->nrmixch-1]; + mch->slctunitid = selector[3] | ((i + 1) << 8); + } else if ((mch = slctsrc_findunit(state, selector[5+i]))) { + mch->slctunitid = selector[3] | ((i + 1) << 8); + } else { + printk(KERN_INFO "usbaudio: selector unit %u: ignoring channel %u\n", selector[3], i+1); + } + } + state->termtype = 0; + state->chconfig = 0; +} + +/* in the future we might try to handle 3D etc. effect units */ + +static void usb_audio_processingunit(struct consmixstate *state, unsigned char *proc) +{ + unsigned int i; + + for (i = 0; i < proc[6]; i++) + usb_audio_recurseunit(state, proc[7+i]); + state->nrchannels = proc[7+proc[6]]; + state->termtype = 0; + state->chconfig = proc[8+proc[6]] | (proc[9+proc[6]] << 8); +} + + +/* See Audio Class Spec, section 4.3.2.5 */ +static void usb_audio_featureunit(struct consmixstate *state, unsigned char *ftr) +{ + struct mixerchannel *ch; + unsigned short chftr, mchftr; +#if 0 + struct usb_device *dev = state->s->usbdev; + unsigned char data[1]; +#endif + unsigned char nr_logical_channels, i; + + usb_audio_recurseunit(state, ftr[4]); + + if (ftr[5] == 0 ) { + printk(KERN_ERR "usbaudio: wrong controls size in feature unit %u\n",ftr[3]); + return; + } + + if (state->nrchannels == 0) { + printk(KERN_ERR "usbaudio: feature unit %u source has no channels\n", ftr[3]); + return; + } + if (state->nrchannels > 2) + printk(KERN_WARNING "usbaudio: feature unit %u: OSS mixer interface does not support more than 2 channels\n", ftr[3]); + + nr_logical_channels=(ftr[0]-7)/ftr[5]-1; + + if (nr_logical_channels != state->nrchannels) { + printk(KERN_WARNING "usbaudio: warning: found %d of %d logical channels.\n", state->nrchannels,nr_logical_channels); + + if (state->nrchannels == 1 && nr_logical_channels==0) { + printk(KERN_INFO "usbaudio: assuming the channel found is the master channel (got a Philips camera?). Should be fine.\n"); + } else if (state->nrchannels == 1 && nr_logical_channels==2) { + printk(KERN_INFO "usbaudio: assuming that a stereo channel connected directly to a mixer is missing in search (got Labtec headset?). Should be fine.\n"); + state->nrchannels=nr_logical_channels; + } else { + printk(KERN_WARNING "usbaudio: no idea what's going on..., contact linux-usb-devel@lists.sourceforge.net\n"); + } + } + + /* There is always a master channel */ + mchftr = ftr[6]; + /* Binary AND over logical channels if they exist */ + if (nr_logical_channels) { + chftr = ftr[6+ftr[5]]; + for (i = 2; i <= nr_logical_channels; i++) + chftr &= ftr[6+i*ftr[5]]; + } else { + chftr = 0; + } + + /* volume control */ + if (chftr & 2) { + ch = getmixchannel(state, getvolchannel(state)); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = VOLUME_CONTROL; + ch->chnum = 1; + ch->flags = (state->nrchannels > 1) ? (MIXFLG_STEREOIN | MIXFLG_STEREOOUT) : 0; + prepmixch(state); + } + } else if (mchftr & 2) { + ch = getmixchannel(state, getvolchannel(state)); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = VOLUME_CONTROL; + ch->chnum = 0; + ch->flags = 0; + prepmixch(state); + } + } + /* bass control */ + if (chftr & 4) { + ch = getmixchannel(state, SOUND_MIXER_BASS); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = BASS_CONTROL; + ch->chnum = 1; + ch->flags = (state->nrchannels > 1) ? (MIXFLG_STEREOIN | MIXFLG_STEREOOUT) : 0; + prepmixch(state); + } + } else if (mchftr & 4) { + ch = getmixchannel(state, SOUND_MIXER_BASS); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = BASS_CONTROL; + ch->chnum = 0; + ch->flags = 0; + prepmixch(state); + } + } + /* treble control */ + if (chftr & 16) { + ch = getmixchannel(state, SOUND_MIXER_TREBLE); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = TREBLE_CONTROL; + ch->chnum = 1; + ch->flags = (state->nrchannels > 1) ? (MIXFLG_STEREOIN | MIXFLG_STEREOOUT) : 0; + prepmixch(state); + } + } else if (mchftr & 16) { + ch = getmixchannel(state, SOUND_MIXER_TREBLE); + if (ch) { + ch->unitid = ftr[3]; + ch->selector = TREBLE_CONTROL; + ch->chnum = 0; + ch->flags = 0; + prepmixch(state); + } + } +#if 0 + /* if there are mute controls, unmute them */ + /* does not seem to be necessary, and the Dallas chip does not seem to support the "all" channel (255) */ + if ((chftr & 1) || (mchftr & 1)) { + printk(KERN_DEBUG "usbaudio: unmuting feature unit %u interface %u\n", ftr[3], state->ctrlif); + data[0] = 0; + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + (MUTE_CONTROL << 8) | 0xff, state->ctrlif | (ftr[3] << 8), data, 1, 1000) < 0) + printk(KERN_WARNING "usbaudio: failure to unmute feature unit %u interface %u\n", ftr[3], state->ctrlif); + } +#endif +} + +static void usb_audio_recurseunit(struct consmixstate *state, unsigned char unitid) +{ + unsigned char *p1; + unsigned int i, j; + + if (test_and_set_bit(unitid, state->unitbitmap)) { + printk(KERN_INFO "usbaudio: mixer path revisits unit %d\n", unitid); + return; + } + p1 = find_audiocontrol_unit(state->buffer, state->buflen, NULL, unitid, state->ctrlif); + if (!p1) { + printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid); + return; + } + state->nrchannels = 0; + state->termtype = 0; + state->chconfig = 0; + switch (p1[2]) { + case INPUT_TERMINAL: + if (p1[0] < 12) { + printk(KERN_ERR "usbaudio: unit %u: invalid INPUT_TERMINAL descriptor\n", unitid); + return; + } + state->nrchannels = p1[7]; + state->termtype = p1[4] | (p1[5] << 8); + state->chconfig = p1[8] | (p1[9] << 8); + return; + + case MIXER_UNIT: + if (p1[0] < 10 || p1[0] < 10+p1[4]) { + printk(KERN_ERR "usbaudio: unit %u: invalid MIXER_UNIT descriptor\n", unitid); + return; + } + usb_audio_mixerunit(state, p1); + return; + + case SELECTOR_UNIT: + if (p1[0] < 6 || p1[0] < 6+p1[4]) { + printk(KERN_ERR "usbaudio: unit %u: invalid SELECTOR_UNIT descriptor\n", unitid); + return; + } + usb_audio_selectorunit(state, p1); + return; + + case FEATURE_UNIT: /* See USB Audio Class Spec 4.3.2.5 */ + if (p1[0] < 7 || p1[0] < 7+p1[5]) { + printk(KERN_ERR "usbaudio: unit %u: invalid FEATURE_UNIT descriptor\n", unitid); + return; + } + usb_audio_featureunit(state, p1); + return; + + case PROCESSING_UNIT: + if (p1[0] < 13 || p1[0] < 13+p1[6] || p1[0] < 13+p1[6]+p1[11+p1[6]]) { + printk(KERN_ERR "usbaudio: unit %u: invalid PROCESSING_UNIT descriptor\n", unitid); + return; + } + usb_audio_processingunit(state, p1); + return; + + case EXTENSION_UNIT: + if (p1[0] < 13 || p1[0] < 13+p1[6] || p1[0] < 13+p1[6]+p1[11+p1[6]]) { + printk(KERN_ERR "usbaudio: unit %u: invalid EXTENSION_UNIT descriptor\n", unitid); + return; + } + for (j = i = 0; i < p1[6]; i++) { + usb_audio_recurseunit(state, p1[7+i]); + if (!i) + j = state->termtype; + else if (j != state->termtype) + j = 0; + } + state->nrchannels = p1[7+p1[6]]; + state->chconfig = p1[8+p1[6]] | (p1[9+p1[6]] << 8); + state->termtype = j; + return; + + default: + printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]); + return; + } +} + +static void usb_audio_constructmixer(struct usb_audio_state *s, unsigned char *buffer, unsigned int buflen, unsigned int ctrlif, unsigned char *oterm) +{ + struct usb_mixerdev *ms; + struct consmixstate state; + + memset(&state, 0, sizeof(state)); + state.s = s; + state.nrmixch = 0; + state.mixchmask = ~0; + state.buffer = buffer; + state.buflen = buflen; + state.ctrlif = ctrlif; + set_bit(oterm[3], state.unitbitmap); /* mark terminal ID as visited */ + printk(KERN_DEBUG "usbaudio: constructing mixer for Terminal %u type 0x%04x\n", + oterm[3], oterm[4] | (oterm[5] << 8)); + usb_audio_recurseunit(&state, oterm[7]); + if (!state.nrmixch) { + printk(KERN_INFO "usbaudio: no mixer controls found for Terminal %u\n", oterm[3]); + return; + } + if (!(ms = kmalloc(sizeof(struct usb_mixerdev)+state.nrmixch*sizeof(struct mixerchannel), GFP_KERNEL))) + return; + memset(ms, 0, sizeof(struct usb_mixerdev)); + memcpy(&ms->ch, &state.mixch, state.nrmixch*sizeof(struct mixerchannel)); + ms->state = s; + ms->iface = ctrlif; + ms->numch = state.nrmixch; + if ((ms->dev_mixer = register_sound_mixer(&usb_mixer_fops, -1)) < 0) { + printk(KERN_ERR "usbaudio: cannot register mixer\n"); + kfree(ms); + return; + } + printk(KERN_INFO "usbaudio: registered mixer 14,%d\n", ms->dev_mixer); + list_add_tail(&ms->list, &s->mixerlist); +} + +/* arbitrary limit, we won't check more interfaces than this */ +#define USB_MAXINTERFACES 32 + +static struct usb_audio_state *usb_audio_parsecontrol(struct usb_device *dev, unsigned char *buffer, unsigned int buflen, unsigned int ctrlif) +{ + struct usb_audio_state *s; + struct usb_interface *iface; + struct usb_host_interface *alt; + unsigned char ifin[USB_MAXINTERFACES], ifout[USB_MAXINTERFACES]; + unsigned char *p1; + unsigned int i, j, k, numifin = 0, numifout = 0; + + if (!(s = kmalloc(sizeof(struct usb_audio_state), GFP_KERNEL))) + return NULL; + memset(s, 0, sizeof(struct usb_audio_state)); + INIT_LIST_HEAD(&s->audiolist); + INIT_LIST_HEAD(&s->mixerlist); + s->usbdev = dev; + s->count = 1; + + /* find audiocontrol interface */ + if (!(p1 = find_csinterface_descriptor(buffer, buflen, NULL, HEADER, ctrlif, -1))) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u no HEADER found\n", + dev->devnum, ctrlif); + goto ret; + } + if (p1[0] < 8 + p1[7]) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u HEADER error\n", + dev->devnum, ctrlif); + goto ret; + } + if (!p1[7]) + printk(KERN_INFO "usbaudio: device %d audiocontrol interface %u has no AudioStreaming and MidiStreaming interfaces\n", + dev->devnum, ctrlif); + for (i = 0; i < p1[7]; i++) { + j = p1[8+i]; + iface = usb_ifnum_to_if(dev, j); + if (!iface) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u does not exist\n", + dev->devnum, ctrlif, j); + continue; + } + if (iface->num_altsetting == 1) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u has only 1 altsetting.\n", dev->devnum, ctrlif); + continue; + } + alt = usb_altnum_to_altsetting(iface, 0); + if (!alt) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u has no altsetting 0\n", + dev->devnum, ctrlif, j); + continue; + } + if (alt->desc.bInterfaceClass != USB_CLASS_AUDIO) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u is not an AudioClass interface\n", + dev->devnum, ctrlif, j); + continue; + } + if (alt->desc.bInterfaceSubClass == 3) { + printk(KERN_INFO "usbaudio: device %d audiocontrol interface %u interface %u MIDIStreaming not supported\n", + dev->devnum, ctrlif, j); + continue; + } + if (alt->desc.bInterfaceSubClass != 2) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u invalid AudioClass subtype\n", + dev->devnum, ctrlif, j); + continue; + } + if (alt->desc.bNumEndpoints > 0) { + /* Check all endpoints; should they all have a bandwidth of 0 ? */ + for (k = 0; k < alt->desc.bNumEndpoints; k++) { + if (le16_to_cpu(alt->endpoint[k].desc.wMaxPacketSize) > 0) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u endpoint %d does not have 0 bandwidth at alt[0]\n", dev->devnum, ctrlif, k); + break; + } + } + if (k < alt->desc.bNumEndpoints) + continue; + } + + alt = usb_altnum_to_altsetting(iface, 1); + if (!alt) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u has no altsetting 1\n", + dev->devnum, ctrlif, j); + continue; + } + if (alt->desc.bNumEndpoints < 1) { + printk(KERN_ERR "usbaudio: device %d audiocontrol interface %u interface %u has no endpoint\n", + dev->devnum, ctrlif, j); + continue; + } + /* note: this requires the data endpoint to be ep0 and the optional sync + ep to be ep1, which seems to be the case */ + if (alt->endpoint[0].desc.bEndpointAddress & USB_DIR_IN) { + if (numifin < USB_MAXINTERFACES) { + ifin[numifin++] = j; + usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1); + } + } else { + if (numifout < USB_MAXINTERFACES) { + ifout[numifout++] = j; + usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1); + } + } + } + printk(KERN_INFO "usbaudio: device %d audiocontrol interface %u has %u input and %u output AudioStreaming interfaces\n", + dev->devnum, ctrlif, numifin, numifout); + for (i = 0; i < numifin && i < numifout; i++) + usb_audio_parsestreaming(s, buffer, buflen, ifin[i], ifout[i]); + for (j = i; j < numifin; j++) + usb_audio_parsestreaming(s, buffer, buflen, ifin[i], -1); + for (j = i; j < numifout; j++) + usb_audio_parsestreaming(s, buffer, buflen, -1, ifout[i]); + /* now walk through all OUTPUT_TERMINAL descriptors to search for mixers */ + p1 = find_csinterface_descriptor(buffer, buflen, NULL, OUTPUT_TERMINAL, ctrlif, -1); + while (p1) { + if (p1[0] >= 9) + usb_audio_constructmixer(s, buffer, buflen, ctrlif, p1); + p1 = find_csinterface_descriptor(buffer, buflen, p1, OUTPUT_TERMINAL, ctrlif, -1); + } + +ret: + if (list_empty(&s->audiolist) && list_empty(&s->mixerlist)) { + kfree(s); + return NULL; + } + /* everything successful */ + down(&open_sem); + list_add_tail(&s->audiodev, &audiodevs); + up(&open_sem); + printk(KERN_DEBUG "usb_audio_parsecontrol: usb_audio_state at %p\n", s); + return s; +} + +/* we only care for the currently active configuration */ + +static int usb_audio_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev (intf); + struct usb_audio_state *s; + unsigned char *buffer; + unsigned int buflen; + +#if 0 + printk(KERN_DEBUG "usbaudio: Probing if %i: IC %x, ISC %x\n", ifnum, + config->interface[ifnum].altsetting[0].desc.bInterfaceClass, + config->interface[ifnum].altsetting[0].desc.bInterfaceSubClass); +#endif + + /* + * audiocontrol interface found + * find which configuration number is active + */ + buffer = dev->rawdescriptors[dev->actconfig - dev->config]; + buflen = le16_to_cpu(dev->actconfig->desc.wTotalLength); + s = usb_audio_parsecontrol(dev, buffer, buflen, intf->altsetting->desc.bInterfaceNumber); + if (s) { + usb_set_intfdata (intf, s); + return 0; + } + return -ENODEV; +} + + +/* a revoke facility would make things simpler */ + +static void usb_audio_disconnect(struct usb_interface *intf) +{ + struct usb_audio_state *s = usb_get_intfdata (intf); + struct usb_audiodev *as; + struct usb_mixerdev *ms; + + if (!s) + return; + + /* we get called with -1 for every audiostreaming interface registered */ + if (s == (struct usb_audio_state *)-1) { + dprintk((KERN_DEBUG "usbaudio: note, usb_audio_disconnect called with -1\n")); + return; + } + if (!s->usbdev) { + dprintk((KERN_DEBUG "usbaudio: error, usb_audio_disconnect already called for %p!\n", s)); + return; + } + down(&open_sem); + list_del_init(&s->audiodev); + s->usbdev = NULL; + usb_set_intfdata (intf, NULL); + + /* deregister all audio and mixer devices, so no new processes can open this device */ + list_for_each_entry(as, &s->audiolist, list) { + usbin_disc(as); + usbout_disc(as); + wake_up(&as->usbin.dma.wait); + wake_up(&as->usbout.dma.wait); + if (as->dev_audio >= 0) { + unregister_sound_dsp(as->dev_audio); + printk(KERN_INFO "usbaudio: unregister dsp 14,%d\n", as->dev_audio); + } + as->dev_audio = -1; + } + list_for_each_entry(ms, &s->mixerlist, list) { + if (ms->dev_mixer >= 0) { + unregister_sound_mixer(ms->dev_mixer); + printk(KERN_INFO "usbaudio: unregister mixer 14,%d\n", ms->dev_mixer); + } + ms->dev_mixer = -1; + } + release(s); + wake_up(&open_wait); +} + +static int __init usb_audio_init(void) +{ + int result = usb_register(&usb_audio_driver); + if (result == 0) + info(DRIVER_VERSION ":" DRIVER_DESC); + return result; +} + + +static void __exit usb_audio_cleanup(void) +{ + usb_deregister(&usb_audio_driver); +} + +module_init(usb_audio_init); +module_exit(usb_audio_cleanup); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/class/audio.h b/drivers/usb/class/audio.h new file mode 100644 index 0000000..45916eb --- /dev/null +++ b/drivers/usb/class/audio.h @@ -0,0 +1,110 @@ +#define CS_AUDIO_UNDEFINED 0x20 +#define CS_AUDIO_DEVICE 0x21 +#define CS_AUDIO_CONFIGURATION 0x22 +#define CS_AUDIO_STRING 0x23 +#define CS_AUDIO_INTERFACE 0x24 +#define CS_AUDIO_ENDPOINT 0x25 + +#define HEADER 0x01 +#define INPUT_TERMINAL 0x02 +#define OUTPUT_TERMINAL 0x03 +#define MIXER_UNIT 0x04 +#define SELECTOR_UNIT 0x05 +#define FEATURE_UNIT 0x06 +#define PROCESSING_UNIT 0x07 +#define EXTENSION_UNIT 0x08 + +#define AS_GENERAL 0x01 +#define FORMAT_TYPE 0x02 +#define FORMAT_SPECIFIC 0x03 + +#define EP_GENERAL 0x01 + +#define MAX_CHAN 9 +#define MAX_FREQ 16 +#define MAX_IFACE 8 +#define MAX_FORMAT 8 +#define MAX_ALT 32 /* Sorry, we need quite a few for the Philips webcams */ + +struct usb_audio_terminal +{ + u8 flags; + u8 assoc; + u16 type; /* Mic etc */ + u8 channels; + u8 source; + u16 chancfg; +}; + +struct usb_audio_format +{ + u8 type; + u8 channels; + u8 num_freq; + u8 sfz; + u8 bits; + u16 freq[MAX_FREQ]; +}; + +struct usb_audio_interface +{ + u8 terminal; + u8 delay; + u16 num_formats; + u16 format_type; + u8 flags; + u8 idleconf; /* Idle config */ +#define AU_IFACE_FOUND 1 + struct usb_audio_format format[MAX_FORMAT]; +}; + +struct usb_audio_device +{ + struct list_head list; + u8 mixer; + u8 selector; + void *irq_handle; + u8 num_channels; + u8 num_dsp_iface; + u8 channel_map[MAX_CHAN]; + struct usb_audio_terminal terminal[MAX_CHAN]; + struct usb_audio_interface interface[MAX_IFACE][MAX_ALT]; +}; + + + +/* Audio Class specific Request Codes */ + +#define SET_CUR 0x01 +#define GET_CUR 0x81 +#define SET_MIN 0x02 +#define GET_MIN 0x82 +#define SET_MAX 0x03 +#define GET_MAX 0x83 +#define SET_RES 0x04 +#define GET_RES 0x84 +#define SET_MEM 0x05 +#define GET_MEM 0x85 +#define GET_STAT 0xff + +/* Terminal Control Selectors */ + +#define COPY_PROTECT_CONTROL 0x01 + +/* Feature Unit Control Selectors */ + +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 +#define BASS_CONTROL 0x03 +#define MID_CONTROL 0x04 +#define TREBLE_CONTROL 0x05 +#define GRAPHIC_EQUALIZER_CONTROL 0x06 +#define AUTOMATIC_GAIN_CONTROL 0x07 +#define DELAY_CONTROL 0x08 +#define BASS_BOOST_CONTROL 0x09 +#define LOUDNESS_CONTROL 0x0a + +/* Endpoint Control Selectors */ + +#define SAMPLING_FREQ_CONTROL 0x01 +#define PITCH_CONTROL 0x02 diff --git a/drivers/usb/class/bluetty.c b/drivers/usb/class/bluetty.c new file mode 100644 index 0000000..6bac65e --- /dev/null +++ b/drivers/usb/class/bluetty.c @@ -0,0 +1,1279 @@ +/* + * bluetty.c Version 0.13 + * + * Copyright (C) 2000, 2001 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2000 Mark Douglas Corner <mcorner@umich.edu> + * + * USB Bluetooth TTY driver, based on the Bluetooth Spec version 1.0B + * + * (2001/11/30) Version 0.13 gkh + * - added locking patch from Masoodur Rahman <rmasoodu@in.ibm.com> + * - removed active variable, as open_count will do. + * + * (2001/07/09) Version 0.12 gkh + * - removed in_interrupt() call, as it doesn't make sense to do + * that anymore. + * + * (2001/06/05) Version 0.11 gkh + * - Fixed problem with read urb status saying that we have shutdown, + * and that we shouldn't resubmit the urb. Patch from unknown. + * + * (2001/05/28) Version 0.10 gkh + * - Fixed problem with using data from userspace in the bluetooth_write + * function as found by the CHECKER project. + * - Added a buffer to the write_urb_pool which reduces the number of + * buffers being created and destroyed for ever write. Also cleans + * up the logic a bit. + * - Added a buffer to the control_urb_pool which fixes a memory leak + * when the device is removed from the system. + * + * (2001/05/28) Version 0.9 gkh + * Fixed problem with bluetooth==NULL for bluetooth_read_bulk_callback + * which was found by both the CHECKER project and Mikko Rahkonen. + * + * (08/04/2001) gb + * Identify version on module load. + * + * (2001/03/10) Version 0.8 gkh + * Fixed problem with not unlinking interrupt urb on device close + * and resubmitting the read urb on error with bluetooth struct. + * Thanks to Narayan Mohanram <narayan@RovingNetworks.com> for the + * fixes. + * + * (11/29/2000) Version 0.7 gkh + * Fixed problem with overrunning the tty flip buffer. + * Removed unneeded NULL pointer initialization. + * + * (10/05/2000) Version 0.6 gkh + * Fixed bug with urb->dev not being set properly, now that the usb + * core needs it. + * Got a real major id number and name. + * + * (08/06/2000) Version 0.5 gkh + * Fixed problem of not resubmitting the bulk read urb if there is + * an error in the callback. Ericsson devices seem to need this. + * + * (07/11/2000) Version 0.4 gkh + * Fixed bug in disconnect for when we call tty_hangup + * Fixed bug in bluetooth_ctrl_msg where the bluetooth struct was not + * getting attached to the control urb properly. + * Fixed bug in bluetooth_write where we pay attention to the result + * of bluetooth_ctrl_msg. + * + * (08/03/2000) Version 0.3 gkh mdc + * Merged in Mark's changes to make the driver play nice with the Axis + * stack. + * Made the write bulk use an urb pool to enable larger transfers with + * fewer calls to the driver. + * Fixed off by one bug in acl pkt receive + * Made packet counters specific to each bluetooth device + * Added checks for zero length callbacks + * Added buffers for int and bulk packets. Had to do this otherwise + * packet types could intermingle. + * Made a control urb pool for the control messages. + * + * (07/11/2000) Version 0.2 gkh + * Fixed a small bug found by Nils Faerber in the usb_bluetooth_probe + * function. + * + * (07/09/2000) Version 0.1 gkh + * Initial release. Has support for sending ACL data (which is really just + * a HCI frame.) Raw HCI commands and HCI events are not supported. + * A ioctl will probably be needed for the HCI commands and events in the + * future. All isoch endpoints are ignored at this time also. + * This driver should work for all currently shipping USB Bluetooth + * devices at this time :) + * + */ + +/* + * 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +#define DEBUG +#include <linux/usb.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.13" +#define DRIVER_AUTHOR "Greg Kroah-Hartman, Mark Douglas Corner" +#define DRIVER_DESC "USB Bluetooth tty driver" + +/* define this if you have hardware that is not good */ +/*#define BTBUGGYHARDWARE */ + +/* Class, SubClass, and Protocol codes that describe a Bluetooth device */ +#define WIRELESS_CLASS_CODE 0xe0 +#define RF_SUBCLASS_CODE 0x01 +#define BLUETOOTH_PROGRAMMING_PROTOCOL_CODE 0x01 + + +#define BLUETOOTH_TTY_MAJOR 216 /* real device node major id */ +#define BLUETOOTH_TTY_MINORS 256 /* whole lotta bluetooth devices */ + +#define USB_BLUETOOTH_MAGIC 0x6d02 /* magic number for bluetooth struct */ + +#define BLUETOOTH_CONTROL_REQUEST_TYPE 0x20 + +/* Bluetooth packet types */ +#define CMD_PKT 0x01 +#define ACL_PKT 0x02 +#define SCO_PKT 0x03 +#define EVENT_PKT 0x04 +#define ERROR_PKT 0x05 +#define NEG_PKT 0x06 + +/* Message sizes */ +#define MAX_EVENT_SIZE 0xFF +#define EVENT_HDR_SIZE 3 /* 2 for the header + 1 for the type indicator */ +#define EVENT_BUFFER_SIZE (MAX_EVENT_SIZE + EVENT_HDR_SIZE) + +#define MAX_ACL_SIZE 0xFFFF +#define ACL_HDR_SIZE 5 /* 4 for the header + 1 for the type indicator */ +#define ACL_BUFFER_SIZE (MAX_ACL_SIZE + ACL_HDR_SIZE) + +/* parity check flag */ +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +#define CHAR2INT16(c1,c0) (((u32)((c1) & 0xff) << 8) + (u32)((c0) & 0xff)) + +#define NUM_BULK_URBS 24 +#define NUM_CONTROL_URBS 16 + +struct usb_bluetooth { + int magic; + struct usb_device * dev; + struct tty_driver * tty_driver; /* the tty_driver for this device */ + struct tty_struct * tty; /* the corresponding tty for this port */ + + unsigned char minor; /* the starting minor number for this device */ + int throttle; /* throttled by tty layer */ + int open_count; + + __u8 control_out_bInterfaceNum; + struct urb * control_urb_pool[NUM_CONTROL_URBS]; + struct usb_ctrlrequest dr[NUM_CONTROL_URBS]; + + unsigned char * interrupt_in_buffer; + struct urb * interrupt_in_urb; + __u8 interrupt_in_endpointAddress; + __u8 interrupt_in_interval; + int interrupt_in_buffer_size; + + unsigned char * bulk_in_buffer; + struct urb * read_urb; + __u8 bulk_in_endpointAddress; + int bulk_in_buffer_size; + + int bulk_out_buffer_size; + __u8 bulk_out_endpointAddress; + + wait_queue_head_t write_wait; + + struct work_struct work; /* work queue entry for line discipline waking up */ + + unsigned int int_packet_pos; + unsigned char int_buffer[EVENT_BUFFER_SIZE]; + unsigned int bulk_packet_pos; + unsigned char bulk_buffer[ACL_BUFFER_SIZE]; /* 64k preallocated, fix? */ + struct semaphore lock; +}; + + +/* local function prototypes */ +static int bluetooth_open (struct tty_struct *tty, struct file *filp); +static void bluetooth_close (struct tty_struct *tty, struct file *filp); +static int bluetooth_write (struct tty_struct *tty, const unsigned char *buf, int count); +static int bluetooth_write_room (struct tty_struct *tty); +static int bluetooth_chars_in_buffer (struct tty_struct *tty); +static void bluetooth_throttle (struct tty_struct *tty); +static void bluetooth_unthrottle (struct tty_struct *tty); +static int bluetooth_ioctl (struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg); +static void bluetooth_set_termios (struct tty_struct *tty, struct termios *old); + +static void bluetooth_int_callback (struct urb *urb, struct pt_regs *regs); +static void bluetooth_ctrl_callback (struct urb *urb, struct pt_regs *regs); +static void bluetooth_read_bulk_callback (struct urb *urb, struct pt_regs *regs); +static void bluetooth_write_bulk_callback (struct urb *urb, struct pt_regs *regs); + +static int usb_bluetooth_probe (struct usb_interface *intf, + const struct usb_device_id *id); +static void usb_bluetooth_disconnect (struct usb_interface *intf); + + +static struct usb_device_id usb_bluetooth_ids [] = { + { USB_DEVICE_INFO(WIRELESS_CLASS_CODE, RF_SUBCLASS_CODE, BLUETOOTH_PROGRAMMING_PROTOCOL_CODE) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_bluetooth_ids); + +static struct usb_driver usb_bluetooth_driver = { + .owner = THIS_MODULE, + .name = "bluetty", + .probe = usb_bluetooth_probe, + .disconnect = usb_bluetooth_disconnect, + .id_table = usb_bluetooth_ids, +}; + +static struct tty_driver *bluetooth_tty_driver; +static struct usb_bluetooth *bluetooth_table[BLUETOOTH_TTY_MINORS]; + + +static inline int bluetooth_paranoia_check (struct usb_bluetooth *bluetooth, const char *function) +{ + if (!bluetooth) { + dbg("%s - bluetooth == NULL", function); + return -1; + } + if (bluetooth->magic != USB_BLUETOOTH_MAGIC) { + dbg("%s - bad magic number for bluetooth", function); + return -1; + } + + return 0; +} + + +static inline struct usb_bluetooth* get_usb_bluetooth (struct usb_bluetooth *bluetooth, const char *function) +{ + if (!bluetooth || + bluetooth_paranoia_check (bluetooth, function)) { + /* then say that we don't have a valid usb_bluetooth thing, which will + * end up generating -ENODEV return values */ + return NULL; + } + + return bluetooth; +} + + +static inline struct usb_bluetooth *get_bluetooth_by_index (int index) +{ + return bluetooth_table[index]; +} + + +static int bluetooth_ctrl_msg (struct usb_bluetooth *bluetooth, int request, int value, const unsigned char *buf, int len) +{ + struct urb *urb = NULL; + struct usb_ctrlrequest *dr = NULL; + int i; + int status; + + dbg ("%s", __FUNCTION__); + + /* try to find a free urb in our list */ + for (i = 0; i < NUM_CONTROL_URBS; ++i) { + if (bluetooth->control_urb_pool[i]->status != -EINPROGRESS) { + urb = bluetooth->control_urb_pool[i]; + dr = &bluetooth->dr[i]; + break; + } + } + if (urb == NULL) { + dbg ("%s - no free urbs", __FUNCTION__); + return -ENOMEM; + } + + /* keep increasing the urb transfer buffer to fit the size of the message */ + if (urb->transfer_buffer == NULL) { + urb->transfer_buffer = kmalloc (len, GFP_KERNEL); + if (urb->transfer_buffer == NULL) { + err ("%s - out of memory", __FUNCTION__); + return -ENOMEM; + } + } + if (urb->transfer_buffer_length < len) { + kfree (urb->transfer_buffer); + urb->transfer_buffer = kmalloc (len, GFP_KERNEL); + if (urb->transfer_buffer == NULL) { + err ("%s - out of memory", __FUNCTION__); + return -ENOMEM; + } + } + memcpy (urb->transfer_buffer, buf, len); + + dr->bRequestType= BLUETOOTH_CONTROL_REQUEST_TYPE; + dr->bRequest = request; + dr->wValue = cpu_to_le16((u16) value); + dr->wIndex = cpu_to_le16((u16) bluetooth->control_out_bInterfaceNum); + dr->wLength = cpu_to_le16((u16) len); + + usb_fill_control_urb (urb, bluetooth->dev, usb_sndctrlpipe(bluetooth->dev, 0), + (unsigned char*)dr, urb->transfer_buffer, len, bluetooth_ctrl_callback, bluetooth); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_KERNEL); + if (status) + dbg("%s - usb_submit_urb(control) failed with status = %d", __FUNCTION__, status); + + return status; +} + + + + + +/***************************************************************************** + * Driver tty interface functions + *****************************************************************************/ +static int bluetooth_open (struct tty_struct *tty, struct file * filp) +{ + struct usb_bluetooth *bluetooth; + int result; + + dbg("%s", __FUNCTION__); + + /* initialize the pointer incase something fails */ + tty->driver_data = NULL; + + /* get the bluetooth object associated with this tty pointer */ + bluetooth = get_bluetooth_by_index (tty->index); + + if (bluetooth_paranoia_check (bluetooth, __FUNCTION__)) { + return -ENODEV; + } + + down (&bluetooth->lock); + + ++bluetooth->open_count; + if (bluetooth->open_count == 1) { + /* set up our structure making the tty driver remember our object, and us it */ + tty->driver_data = bluetooth; + bluetooth->tty = tty; + + /* force low_latency on so that our tty_push actually forces the data through, + * otherwise it is scheduled, and with high data rates (like with OHCI) data + * can get lost. */ + bluetooth->tty->low_latency = 1; + + /* Reset the packet position counters */ + bluetooth->int_packet_pos = 0; + bluetooth->bulk_packet_pos = 0; + +#ifndef BTBUGGYHARDWARE + /* Start reading from the device */ + usb_fill_bulk_urb (bluetooth->read_urb, bluetooth->dev, + usb_rcvbulkpipe(bluetooth->dev, bluetooth->bulk_in_endpointAddress), + bluetooth->bulk_in_buffer, + bluetooth->bulk_in_buffer_size, + bluetooth_read_bulk_callback, bluetooth); + result = usb_submit_urb(bluetooth->read_urb, GFP_KERNEL); + if (result) + dbg("%s - usb_submit_urb(read bulk) failed with status %d", __FUNCTION__, result); +#endif + usb_fill_int_urb (bluetooth->interrupt_in_urb, bluetooth->dev, + usb_rcvintpipe(bluetooth->dev, bluetooth->interrupt_in_endpointAddress), + bluetooth->interrupt_in_buffer, + bluetooth->interrupt_in_buffer_size, + bluetooth_int_callback, bluetooth, + bluetooth->interrupt_in_interval); + result = usb_submit_urb(bluetooth->interrupt_in_urb, GFP_KERNEL); + if (result) + dbg("%s - usb_submit_urb(interrupt in) failed with status %d", __FUNCTION__, result); + } + + up(&bluetooth->lock); + + return 0; +} + + +static void bluetooth_close (struct tty_struct *tty, struct file * filp) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not opened", __FUNCTION__); + return; + } + + down (&bluetooth->lock); + + --bluetooth->open_count; + if (bluetooth->open_count <= 0) { + bluetooth->open_count = 0; + + /* shutdown any in-flight urbs that we know about */ + usb_kill_urb (bluetooth->read_urb); + usb_kill_urb (bluetooth->interrupt_in_urb); + } + up(&bluetooth->lock); +} + + +static int bluetooth_write (struct tty_struct * tty, const unsigned char *buf, int count) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + struct urb *urb = NULL; + unsigned char *temp_buffer = NULL; + const unsigned char *current_buffer; + unsigned char *urb_buffer; + int i; + int retval = 0; + + if (!bluetooth) { + return -ENODEV; + } + + dbg("%s - %d byte(s)", __FUNCTION__, count); + + if (!bluetooth->open_count) { + dbg ("%s - device not opened", __FUNCTION__); + return -EINVAL; + } + + if (count == 0) { + dbg("%s - write request of 0 bytes", __FUNCTION__); + return 0; + } + if (count == 1) { + dbg("%s - write request only included type %d", __FUNCTION__, buf[0]); + return 1; + } + +#ifdef DEBUG + printk (KERN_DEBUG __FILE__ ": %s - length = %d, data = ", __FUNCTION__, count); + for (i = 0; i < count; ++i) { + printk ("%.2x ", buf[i]); + } + printk ("\n"); +#endif + + current_buffer = buf; + + switch (*current_buffer) { + /* First byte indicates the type of packet */ + case CMD_PKT: + /* dbg("%s- Send cmd_pkt len:%d", __FUNCTION__, count);*/ + + retval = bluetooth_ctrl_msg (bluetooth, 0x00, 0x00, ¤t_buffer[1], count-1); + if (retval) { + goto exit; + } + retval = count; + break; + + case ACL_PKT: + ++current_buffer; + --count; + + urb_buffer = kmalloc (count, GFP_ATOMIC); + if (!urb_buffer) { + dev_err(&bluetooth->dev->dev, "out of memory\n"); + retval = -ENOMEM; + goto exit; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&bluetooth->dev->dev, "no more free urbs\n"); + kfree(urb_buffer); + retval = -ENOMEM; + goto exit; + } + memcpy (urb_buffer, current_buffer, count); + + /* build up our urb */ + usb_fill_bulk_urb(urb, bluetooth->dev, + usb_sndbulkpipe(bluetooth->dev, + bluetooth->bulk_out_endpointAddress), + urb_buffer, + count, + bluetooth_write_bulk_callback, + bluetooth); + + + /* send it down the pipe */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dbg("%s - usb_submit_urb(write bulk) failed with error = %d", __FUNCTION__, retval); + goto exit; + } + + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb (urb); + retval = count + 1; + break; + + default : + dbg("%s - unsupported (at this time) write type", __FUNCTION__); + retval = -EINVAL; + break; + } + +exit: + kfree (temp_buffer); + + return retval; +} + + +static int bluetooth_write_room (struct tty_struct *tty) +{ + dbg("%s", __FUNCTION__); + + /* + * We really can take anything the user throws at us + * but let's pick a nice big number to tell the tty + * layer that we have lots of free space + */ + return 2048; +} + + +static int bluetooth_chars_in_buffer (struct tty_struct *tty) +{ + dbg("%s", __FUNCTION__); + + /* + * We can't really account for how much data we + * have sent out, but hasn't made it through to the + * device, so just tell the tty layer that everything + * is flushed. + */ + return 0; +} + + +static void bluetooth_throttle (struct tty_struct * tty) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return; + } + + dbg("%s unsupported (at this time)", __FUNCTION__); + + return; +} + + +static void bluetooth_unthrottle (struct tty_struct * tty) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return; + } + + dbg("%s unsupported (at this time)", __FUNCTION__); +} + + +static int bluetooth_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return -ENODEV; + } + + dbg("%s - cmd 0x%.4x", __FUNCTION__, cmd); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return -ENODEV; + } + + /* FIXME!!! */ + return -ENOIOCTLCMD; +} + + +static void bluetooth_set_termios (struct tty_struct *tty, struct termios * old) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return; + } + + /* FIXME!!! */ + + return; +} + + +#ifdef BTBUGGYHARDWARE +void btusb_enable_bulk_read(struct tty_struct *tty){ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + int result; + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return; + } + + if (bluetooth->read_urb) { + usb_fill_bulk_urb(bluetooth->read_urb, bluetooth->dev, + usb_rcvbulkpipe(bluetooth->dev, bluetooth->bulk_in_endpointAddress), + bluetooth->bulk_in_buffer, bluetooth->bulk_in_buffer_size, + bluetooth_read_bulk_callback, bluetooth); + result = usb_submit_urb(bluetooth->read_urb, GFP_KERNEL); + if (result) + err ("%s - failed submitting read urb, error %d", __FUNCTION__, result); + } +} + +void btusb_disable_bulk_read(struct tty_struct *tty){ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)tty->driver_data, __FUNCTION__); + + if (!bluetooth) { + return; + } + + dbg("%s", __FUNCTION__); + + if (!bluetooth->open_count) { + dbg ("%s - device not open", __FUNCTION__); + return; + } + + if ((bluetooth->read_urb) && (bluetooth->read_urb->actual_length)) + usb_kill_urb(bluetooth->read_urb); +} +#endif + + +/***************************************************************************** + * urb callback functions + *****************************************************************************/ + + +static void bluetooth_int_callback (struct urb *urb, struct pt_regs *regs) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)urb->context, __FUNCTION__); + unsigned char *data = urb->transfer_buffer; + unsigned int i; + unsigned int count = urb->actual_length; + unsigned int packet_size; + int status; + + dbg("%s", __FUNCTION__); + + if (!bluetooth) { + dbg("%s - bad bluetooth pointer, exiting", __FUNCTION__); + return; + } + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto exit; + } + + if (!count) { + dbg("%s - zero length int", __FUNCTION__); + goto exit; + } + + +#ifdef DEBUG + if (count) { + printk (KERN_DEBUG __FILE__ ": %s- length = %d, data = ", __FUNCTION__, count); + for (i = 0; i < count; ++i) { + printk ("%.2x ", data[i]); + } + printk ("\n"); + } +#endif + +#ifdef BTBUGGYHARDWARE + if ((count >= 2) && (data[0] == 0xFF) && (data[1] == 0x00)) { + data += 2; + count -= 2; + } + if (count == 0) { + urb->actual_length = 0; + goto exit; + } +#endif + /* We add a packet type identifier to the beginning of each + HCI frame. This makes the data in the tty look like a + serial USB devices. Each HCI frame can be broken across + multiple URBs so we buffer them until we have a full hci + packet */ + + if (!bluetooth->int_packet_pos) { + bluetooth->int_buffer[0] = EVENT_PKT; + bluetooth->int_packet_pos++; + } + + if (bluetooth->int_packet_pos + count > EVENT_BUFFER_SIZE) { + err("%s - exceeded EVENT_BUFFER_SIZE", __FUNCTION__); + bluetooth->int_packet_pos = 0; + goto exit; + } + + memcpy (&bluetooth->int_buffer[bluetooth->int_packet_pos], + urb->transfer_buffer, count); + bluetooth->int_packet_pos += count; + urb->actual_length = 0; + + if (bluetooth->int_packet_pos >= EVENT_HDR_SIZE) + packet_size = bluetooth->int_buffer[2]; + else + goto exit; + + if (packet_size + EVENT_HDR_SIZE < bluetooth->int_packet_pos) { + err("%s - packet was too long", __FUNCTION__); + bluetooth->int_packet_pos = 0; + goto exit; + } + + if (packet_size + EVENT_HDR_SIZE == bluetooth->int_packet_pos) { + for (i = 0; i < bluetooth->int_packet_pos; ++i) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them */ + if (bluetooth->tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(bluetooth->tty); + } + tty_insert_flip_char(bluetooth->tty, bluetooth->int_buffer[i], 0); + } + tty_flip_buffer_push(bluetooth->tty); + + bluetooth->int_packet_pos = 0; + } + +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, status); +} + + +static void bluetooth_ctrl_callback (struct urb *urb, struct pt_regs *regs) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)urb->context, __FUNCTION__); + + dbg("%s", __FUNCTION__); + + if (!bluetooth) { + dbg("%s - bad bluetooth pointer, exiting", __FUNCTION__); + return; + } + + if (urb->status) { + dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status); + return; + } +} + + +static void bluetooth_read_bulk_callback (struct urb *urb, struct pt_regs *regs) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)urb->context, __FUNCTION__); + unsigned char *data = urb->transfer_buffer; + unsigned int count = urb->actual_length; + unsigned int i; + unsigned int packet_size; + int result; + + + dbg("%s", __FUNCTION__); + + if (!bluetooth) { + dbg("%s - bad bluetooth pointer, exiting", __FUNCTION__); + return; + } + + if (urb->status) { + dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status); + if (urb->status == -ENOENT) { + dbg("%s - URB canceled, won't reschedule", __FUNCTION__); + return; + } + goto exit; + } + + if (!count) { + dbg("%s - zero length read bulk", __FUNCTION__); + goto exit; + } + +#ifdef DEBUG + if (count) { + printk (KERN_DEBUG __FILE__ ": %s- length = %d, data = ", __FUNCTION__, count); + for (i = 0; i < count; ++i) { + printk ("%.2x ", data[i]); + } + printk ("\n"); + } +#endif +#ifdef BTBUGGYHARDWARE + if ((count == 4) && (data[0] == 0x00) && (data[1] == 0x00) + && (data[2] == 0x00) && (data[3] == 0x00)) { + urb->actual_length = 0; + usb_fill_bulk_urb(bluetooth->read_urb, bluetooth->dev, + usb_rcvbulkpipe(bluetooth->dev, bluetooth->bulk_in_endpointAddress), + bluetooth->bulk_in_buffer, bluetooth->bulk_in_buffer_size, + bluetooth_read_bulk_callback, bluetooth); + result = usb_submit_urb(bluetooth->read_urb, GFP_KERNEL); + if (result) + err ("%s - failed resubmitting read urb, error %d", __FUNCTION__, result); + + return; + } +#endif + /* We add a packet type identifier to the beginning of each + HCI frame. This makes the data in the tty look like a + serial USB devices. Each HCI frame can be broken across + multiple URBs so we buffer them until we have a full hci + packet */ + + if (!bluetooth->bulk_packet_pos) { + bluetooth->bulk_buffer[0] = ACL_PKT; + bluetooth->bulk_packet_pos++; + } + + if (bluetooth->bulk_packet_pos + count > ACL_BUFFER_SIZE) { + err("%s - exceeded ACL_BUFFER_SIZE", __FUNCTION__); + bluetooth->bulk_packet_pos = 0; + goto exit; + } + + memcpy (&bluetooth->bulk_buffer[bluetooth->bulk_packet_pos], + urb->transfer_buffer, count); + bluetooth->bulk_packet_pos += count; + urb->actual_length = 0; + + if (bluetooth->bulk_packet_pos >= ACL_HDR_SIZE) { + packet_size = CHAR2INT16(bluetooth->bulk_buffer[4],bluetooth->bulk_buffer[3]); + } else { + goto exit; + } + + if (packet_size + ACL_HDR_SIZE < bluetooth->bulk_packet_pos) { + err("%s - packet was too long", __FUNCTION__); + bluetooth->bulk_packet_pos = 0; + goto exit; + } + + if (packet_size + ACL_HDR_SIZE == bluetooth->bulk_packet_pos) { + for (i = 0; i < bluetooth->bulk_packet_pos; ++i) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */ + if (bluetooth->tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(bluetooth->tty); + } + tty_insert_flip_char(bluetooth->tty, bluetooth->bulk_buffer[i], 0); + } + tty_flip_buffer_push(bluetooth->tty); + bluetooth->bulk_packet_pos = 0; + } + +exit: + if (!bluetooth || !bluetooth->open_count) + return; + + usb_fill_bulk_urb(bluetooth->read_urb, bluetooth->dev, + usb_rcvbulkpipe(bluetooth->dev, bluetooth->bulk_in_endpointAddress), + bluetooth->bulk_in_buffer, bluetooth->bulk_in_buffer_size, + bluetooth_read_bulk_callback, bluetooth); + result = usb_submit_urb(bluetooth->read_urb, GFP_KERNEL); + if (result) + err ("%s - failed resubmitting read urb, error %d", __FUNCTION__, result); + + return; +} + + +static void bluetooth_write_bulk_callback (struct urb *urb, struct pt_regs *regs) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)urb->context, __FUNCTION__); + + dbg("%s", __FUNCTION__); + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); + + if (!bluetooth) { + dbg("%s - bad bluetooth pointer, exiting", __FUNCTION__); + return; + } + + if (urb->status) { + dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); + return; + } + + /* wake up our little function to let the tty layer know that something happened */ + schedule_work(&bluetooth->work); +} + + +static void bluetooth_softint(void *private) +{ + struct usb_bluetooth *bluetooth = get_usb_bluetooth ((struct usb_bluetooth *)private, __FUNCTION__); + + dbg("%s", __FUNCTION__); + + if (!bluetooth) + return; + + tty_wakeup(bluetooth->tty); +} + + +static int usb_bluetooth_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev (intf); + struct usb_bluetooth *bluetooth = NULL; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_endpoint_descriptor *interrupt_in_endpoint[8]; + struct usb_endpoint_descriptor *bulk_in_endpoint[8]; + struct usb_endpoint_descriptor *bulk_out_endpoint[8]; + int control_out_endpoint; + + int minor; + int buffer_size; + int i; + int num_interrupt_in = 0; + int num_bulk_in = 0; + int num_bulk_out = 0; + + interface = intf->cur_altsetting; + control_out_endpoint = interface->desc.bInterfaceNumber; + + /* find the endpoints that we need */ + for (i = 0; i < interface->desc.bNumEndpoints; ++i) { + endpoint = &interface->endpoint[i].desc; + + if ((endpoint->bEndpointAddress & 0x80) && + ((endpoint->bmAttributes & 3) == 0x02)) { + /* we found a bulk in endpoint */ + dbg("found bulk in"); + bulk_in_endpoint[num_bulk_in] = endpoint; + ++num_bulk_in; + } + + if (((endpoint->bEndpointAddress & 0x80) == 0x00) && + ((endpoint->bmAttributes & 3) == 0x02)) { + /* we found a bulk out endpoint */ + dbg("found bulk out"); + bulk_out_endpoint[num_bulk_out] = endpoint; + ++num_bulk_out; + } + + if ((endpoint->bEndpointAddress & 0x80) && + ((endpoint->bmAttributes & 3) == 0x03)) { + /* we found a interrupt in endpoint */ + dbg("found interrupt in"); + interrupt_in_endpoint[num_interrupt_in] = endpoint; + ++num_interrupt_in; + } + } + + /* according to the spec, we can only have 1 bulk_in, 1 bulk_out, and 1 interrupt_in endpoints */ + if ((num_bulk_in != 1) || + (num_bulk_out != 1) || + (num_interrupt_in != 1)) { + dbg ("%s - improper number of endpoints. Bluetooth driver not bound.", __FUNCTION__); + return -EIO; + } + + info("USB Bluetooth converter detected"); + + for (minor = 0; minor < BLUETOOTH_TTY_MINORS && bluetooth_table[minor]; ++minor) + ; + if (bluetooth_table[minor]) { + err("No more free Bluetooth devices"); + return -ENODEV; + } + + if (!(bluetooth = kmalloc(sizeof(struct usb_bluetooth), GFP_KERNEL))) { + err("Out of memory"); + return -ENOMEM; + } + + memset(bluetooth, 0, sizeof(struct usb_bluetooth)); + + bluetooth->magic = USB_BLUETOOTH_MAGIC; + bluetooth->dev = dev; + bluetooth->minor = minor; + INIT_WORK(&bluetooth->work, bluetooth_softint, bluetooth); + init_MUTEX(&bluetooth->lock); + + /* record the interface number for the control out */ + bluetooth->control_out_bInterfaceNum = control_out_endpoint; + + /* create our control out urb pool */ + for (i = 0; i < NUM_CONTROL_URBS; ++i) { + struct urb *urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) { + err("No free urbs available"); + goto probe_error; + } + urb->transfer_buffer = NULL; + bluetooth->control_urb_pool[i] = urb; + } + + /* set up the endpoint information */ + endpoint = bulk_in_endpoint[0]; + bluetooth->read_urb = usb_alloc_urb (0, GFP_KERNEL); + if (!bluetooth->read_urb) { + err("No free urbs available"); + goto probe_error; + } + bluetooth->bulk_in_buffer_size = buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + bluetooth->bulk_in_endpointAddress = endpoint->bEndpointAddress; + bluetooth->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL); + if (!bluetooth->bulk_in_buffer) { + err("Couldn't allocate bulk_in_buffer"); + goto probe_error; + } + usb_fill_bulk_urb(bluetooth->read_urb, dev, usb_rcvbulkpipe(dev, endpoint->bEndpointAddress), + bluetooth->bulk_in_buffer, buffer_size, bluetooth_read_bulk_callback, bluetooth); + + endpoint = bulk_out_endpoint[0]; + bluetooth->bulk_out_endpointAddress = endpoint->bEndpointAddress; + bluetooth->bulk_out_buffer_size = le16_to_cpu(endpoint->wMaxPacketSize) * 2; + + endpoint = interrupt_in_endpoint[0]; + bluetooth->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!bluetooth->interrupt_in_urb) { + err("No free urbs available"); + goto probe_error; + } + bluetooth->interrupt_in_buffer_size = buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + bluetooth->interrupt_in_endpointAddress = endpoint->bEndpointAddress; + bluetooth->interrupt_in_interval = endpoint->bInterval; + bluetooth->interrupt_in_buffer = kmalloc (buffer_size, GFP_KERNEL); + if (!bluetooth->interrupt_in_buffer) { + err("Couldn't allocate interrupt_in_buffer"); + goto probe_error; + } + usb_fill_int_urb(bluetooth->interrupt_in_urb, dev, usb_rcvintpipe(dev, endpoint->bEndpointAddress), + bluetooth->interrupt_in_buffer, buffer_size, bluetooth_int_callback, + bluetooth, endpoint->bInterval); + + /* initialize the devfs nodes for this device and let the user know what bluetooths we are bound to */ + tty_register_device (bluetooth_tty_driver, minor, &intf->dev); + info("Bluetooth converter now attached to ttyUB%d (or usb/ttub/%d for devfs)", minor, minor); + + bluetooth_table[minor] = bluetooth; + + /* success */ + usb_set_intfdata (intf, bluetooth); + return 0; + +probe_error: + if (bluetooth->read_urb) + usb_free_urb (bluetooth->read_urb); + if (bluetooth->bulk_in_buffer) + kfree (bluetooth->bulk_in_buffer); + if (bluetooth->interrupt_in_urb) + usb_free_urb (bluetooth->interrupt_in_urb); + if (bluetooth->interrupt_in_buffer) + kfree (bluetooth->interrupt_in_buffer); + for (i = 0; i < NUM_CONTROL_URBS; ++i) + if (bluetooth->control_urb_pool[i]) { + if (bluetooth->control_urb_pool[i]->transfer_buffer) + kfree (bluetooth->control_urb_pool[i]->transfer_buffer); + usb_free_urb (bluetooth->control_urb_pool[i]); + } + + bluetooth_table[minor] = NULL; + + /* free up any memory that we allocated */ + kfree (bluetooth); + return -EIO; +} + + +static void usb_bluetooth_disconnect(struct usb_interface *intf) +{ + struct usb_bluetooth *bluetooth = usb_get_intfdata (intf); + int i; + + usb_set_intfdata (intf, NULL); + if (bluetooth) { + if ((bluetooth->open_count) && (bluetooth->tty)) + tty_hangup(bluetooth->tty); + + bluetooth->open_count = 0; + + if (bluetooth->read_urb) { + usb_kill_urb (bluetooth->read_urb); + usb_free_urb (bluetooth->read_urb); + } + if (bluetooth->bulk_in_buffer) + kfree (bluetooth->bulk_in_buffer); + + if (bluetooth->interrupt_in_urb) { + usb_kill_urb (bluetooth->interrupt_in_urb); + usb_free_urb (bluetooth->interrupt_in_urb); + } + if (bluetooth->interrupt_in_buffer) + kfree (bluetooth->interrupt_in_buffer); + + tty_unregister_device (bluetooth_tty_driver, bluetooth->minor); + + for (i = 0; i < NUM_CONTROL_URBS; ++i) { + if (bluetooth->control_urb_pool[i]) { + usb_kill_urb (bluetooth->control_urb_pool[i]); + if (bluetooth->control_urb_pool[i]->transfer_buffer) + kfree (bluetooth->control_urb_pool[i]->transfer_buffer); + usb_free_urb (bluetooth->control_urb_pool[i]); + } + } + + info("Bluetooth converter now disconnected from ttyUB%d", bluetooth->minor); + + bluetooth_table[bluetooth->minor] = NULL; + + /* free up any memory that we allocated */ + kfree (bluetooth); + } else { + info("device disconnected"); + } +} + +static struct tty_operations bluetooth_ops = { + .open = bluetooth_open, + .close = bluetooth_close, + .write = bluetooth_write, + .write_room = bluetooth_write_room, + .ioctl = bluetooth_ioctl, + .set_termios = bluetooth_set_termios, + .throttle = bluetooth_throttle, + .unthrottle = bluetooth_unthrottle, + .chars_in_buffer = bluetooth_chars_in_buffer, +}; + +static int usb_bluetooth_init(void) +{ + int i; + int result; + + /* Initialize our global data */ + for (i = 0; i < BLUETOOTH_TTY_MINORS; ++i) { + bluetooth_table[i] = NULL; + } + + info ("USB Bluetooth support registered"); + + bluetooth_tty_driver = alloc_tty_driver(BLUETOOTH_TTY_MINORS); + if (!bluetooth_tty_driver) + return -ENOMEM; + + bluetooth_tty_driver->owner = THIS_MODULE; + bluetooth_tty_driver->driver_name = "usb-bluetooth"; + bluetooth_tty_driver->name = "ttyUB"; + bluetooth_tty_driver->devfs_name = "usb/ttub/"; + bluetooth_tty_driver->major = BLUETOOTH_TTY_MAJOR; + bluetooth_tty_driver->minor_start = 0; + bluetooth_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + bluetooth_tty_driver->subtype = SERIAL_TYPE_NORMAL; + bluetooth_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; + bluetooth_tty_driver->init_termios = tty_std_termios; + bluetooth_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(bluetooth_tty_driver, &bluetooth_ops); + if (tty_register_driver (bluetooth_tty_driver)) { + err("%s - failed to register tty driver", __FUNCTION__); + put_tty_driver(bluetooth_tty_driver); + return -1; + } + + /* register the USB driver */ + result = usb_register(&usb_bluetooth_driver); + if (result < 0) { + tty_unregister_driver(bluetooth_tty_driver); + put_tty_driver(bluetooth_tty_driver); + err("usb_register failed for the USB bluetooth driver. Error number %d", result); + return -1; + } + + info(DRIVER_DESC " " DRIVER_VERSION); + + return 0; +} + + +static void usb_bluetooth_exit(void) +{ + usb_deregister(&usb_bluetooth_driver); + tty_unregister_driver(bluetooth_tty_driver); + put_tty_driver(bluetooth_tty_driver); +} + + +module_init(usb_bluetooth_init); +module_exit(usb_bluetooth_exit); + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c new file mode 100644 index 0000000..6d1f9b6 --- /dev/null +++ b/drivers/usb/class/cdc-acm.c @@ -0,0 +1,942 @@ +/* + * cdc-acm.c + * + * Copyright (c) 1999 Armin Fuerst <fuerst@in.tum.de> + * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> + * Copyright (c) 1999 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2004 Oliver Neukum <oliver@neukum.name> + * + * USB Abstract Control Model driver for USB modems and ISDN adapters + * + * Sponsored by SuSE + * + * ChangeLog: + * v0.9 - thorough cleaning, URBification, almost a rewrite + * v0.10 - some more cleanups + * v0.11 - fixed flow control, read error doesn't stop reads + * v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced + * v0.13 - added termios, added hangup + * v0.14 - sized down struct acm + * v0.15 - fixed flow control again - characters could be lost + * v0.16 - added code for modems with swapped data and control interfaces + * v0.17 - added new style probing + * v0.18 - fixed new style probing for devices with more configurations + * v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan) + * v0.20 - switched to probing on interface (rather than device) class + * v0.21 - revert to probing on device for devices with multiple configs + * v0.22 - probe only the control interface. if usbcore doesn't choose the + * config we want, sysadmin changes bConfigurationValue in sysfs. + * v0.23 - use softirq for rx processing, as needed by tty layer + * v0.24 - change probe method to evaluate CDC union descriptor + */ + +/* + * 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/smp_lock.h> +#include <asm/uaccess.h> +#include <linux/usb.h> +#include <linux/usb_cdc.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +#include "cdc-acm.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.23" +#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik" +#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters" + +static struct usb_driver acm_driver; +static struct tty_driver *acm_tty_driver; +static struct acm *acm_table[ACM_TTY_MINORS]; + +static DECLARE_MUTEX(open_sem); + +#define ACM_READY(acm) (acm && acm->dev && acm->used) + +/* + * Functions for ACM control messages. + */ + +static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len) +{ + int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), + request, USB_RT_ACM, value, + acm->control->altsetting[0].desc.bInterfaceNumber, + buf, len, 5000); + dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); + return retval < 0 ? retval : 0; +} + +/* devices aren't required to support these requests. + * the cdc acm descriptor tells whether they do... + */ +#define acm_set_control(acm, control) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0) +#define acm_set_line(acm, line) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line)) +#define acm_send_break(acm, ms) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) + +/* + * Interrupt handlers for various ACM device responses + */ + +/* control interface reports status changes with "interrupt" transfers */ +static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs) +{ + struct acm *acm = urb->context; + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned char *data; + int newctrl; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto exit; + } + + if (!ACM_READY(acm)) + goto exit; + + data = (unsigned char *)(dr + 1); + switch (dr->bNotificationType) { + + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + + dbg("%s network", dr->wValue ? "connected to" : "disconnected from"); + break; + + case USB_CDC_NOTIFY_SERIAL_STATE: + + newctrl = le16_to_cpu(get_unaligned((__le16 *) data)); + + if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { + dbg("calling hangup"); + tty_hangup(acm->tty); + } + + acm->ctrlin = newctrl; + + dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c", + acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', acm->ctrlin & ACM_CTRL_DSR ? '+' : '-', + acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', acm->ctrlin & ACM_CTRL_RI ? '+' : '-', + acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-', + acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-'); + + break; + + default: + dbg("unknown notification %d received: index %d len %d data0 %d data1 %d", + dr->bNotificationType, dr->wIndex, + dr->wLength, data[0], data[1]); + break; + } +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, status); +} + +/* data interface returns incoming bytes, or we got unthrottled */ +static void acm_read_bulk(struct urb *urb, struct pt_regs *regs) +{ + struct acm *acm = urb->context; + dbg("Entering acm_read_bulk with status %d\n", urb->status); + + if (!ACM_READY(acm)) + return; + + if (urb->status) + dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status); + + /* calling tty_flip_buffer_push() in_irq() isn't allowed */ + tasklet_schedule(&acm->bh); +} + +static void acm_rx_tasklet(unsigned long _acm) +{ + struct acm *acm = (void *)_acm; + struct urb *urb = acm->readurb; + struct tty_struct *tty = acm->tty; + unsigned char *data = urb->transfer_buffer; + int i = 0; + dbg("Entering acm_rx_tasklet"); + + if (urb->actual_length > 0 && !acm->throttle) { + for (i = 0; i < urb->actual_length && !acm->throttle; i++) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, + * we drop them. */ + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + tty_insert_flip_char(tty, data[i], 0); + } + dbg("Handed %d bytes to tty layer", i+1); + tty_flip_buffer_push(tty); + } + + spin_lock(&acm->throttle_lock); + if (acm->throttle) { + dbg("Throtteling noticed"); + memmove(data, data + i, urb->actual_length - i); + urb->actual_length -= i; + acm->resubmit_to_unthrottle = 1; + spin_unlock(&acm->throttle_lock); + return; + } + spin_unlock(&acm->throttle_lock); + + urb->actual_length = 0; + urb->dev = acm->dev; + + i = usb_submit_urb(urb, GFP_ATOMIC); + if (i) + dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i); +} + +/* data interface wrote those outgoing bytes */ +static void acm_write_bulk(struct urb *urb, struct pt_regs *regs) +{ + struct acm *acm = (struct acm *)urb->context; + dbg("Entering acm_write_bulk with status %d\n", urb->status); + + if (!ACM_READY(acm)) + goto out; + + if (urb->status) + dbg("nonzero write bulk status received: %d", urb->status); + + schedule_work(&acm->work); +out: + acm->ready_for_write = 1; +} + +static void acm_softint(void *private) +{ + struct acm *acm = private; + dbg("Entering acm_softint.\n"); + + if (!ACM_READY(acm)) + return; + tty_wakeup(acm->tty); +} + +/* + * TTY handlers + */ + +static int acm_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm; + int rv = -EINVAL; + dbg("Entering acm_tty_open.\n"); + + down(&open_sem); + + acm = acm_table[tty->index]; + if (!acm || !acm->dev) + goto err_out; + else + rv = 0; + + tty->driver_data = acm; + acm->tty = tty; + + + + if (acm->used++) { + goto done; + } + + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) { + dbg("usb_submit_urb(ctrl irq) failed"); + goto bail_out; + } + + acm->readurb->dev = acm->dev; + if (usb_submit_urb(acm->readurb, GFP_KERNEL)) { + dbg("usb_submit_urb(read bulk) failed"); + goto bail_out_and_unlink; + } + + if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS)) + goto full_bailout; + + /* force low_latency on so that our tty_push actually forces the data through, + otherwise it is scheduled, and with high data rates data can get lost. */ + tty->low_latency = 1; + +done: +err_out: + up(&open_sem); + return rv; + +full_bailout: + usb_kill_urb(acm->readurb); +bail_out_and_unlink: + usb_kill_urb(acm->ctrlurb); +bail_out: + acm->used--; + up(&open_sem); + return -EIO; +} + +static void acm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; + + if (!acm || !acm->used) + return; + + down(&open_sem); + if (!--acm->used) { + if (acm->dev) { + acm_set_control(acm, acm->ctrlout = 0); + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->writeurb); + usb_kill_urb(acm->readurb); + } else { + tty_unregister_device(acm_tty_driver, acm->minor); + acm_table[acm->minor] = NULL; + usb_free_urb(acm->ctrlurb); + usb_free_urb(acm->readurb); + usb_free_urb(acm->writeurb); + kfree(acm); + } + } + up(&open_sem); +} + +static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct acm *acm = tty->driver_data; + int stat; + dbg("Entering acm_tty_write to write %d bytes,\n", count); + + if (!ACM_READY(acm)) + return -EINVAL; + if (!acm->ready_for_write) + return 0; + if (!count) + return 0; + + count = (count > acm->writesize) ? acm->writesize : count; + + dbg("Get %d bytes...", count); + memcpy(acm->write_buffer, buf, count); + dbg(" Successfully copied.\n"); + + acm->writeurb->transfer_buffer_length = count; + acm->writeurb->dev = acm->dev; + + acm->ready_for_write = 0; + stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC); + if (stat < 0) { + dbg("usb_submit_urb(write bulk) failed"); + acm->ready_for_write = 1; + return stat; + } + + return count; +} + +static int acm_tty_write_room(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) + return -EINVAL; + return !acm->ready_for_write ? 0 : acm->writesize; +} + +static int acm_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) + return -EINVAL; + return !acm->ready_for_write ? acm->writeurb->transfer_buffer_length : 0; +} + +static void acm_tty_throttle(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) + return; + spin_lock_bh(&acm->throttle_lock); + acm->throttle = 1; + spin_unlock_bh(&acm->throttle_lock); +} + +static void acm_tty_unthrottle(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) + return; + spin_lock_bh(&acm->throttle_lock); + acm->throttle = 0; + spin_unlock_bh(&acm->throttle_lock); + if (acm->resubmit_to_unthrottle) { + acm->resubmit_to_unthrottle = 0; + acm_read_bulk(acm->readurb, NULL); + } +} + +static void acm_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct acm *acm = tty->driver_data; + if (!ACM_READY(acm)) + return; + if (acm_send_break(acm, state ? 0xffff : 0)) + dbg("send break failed"); +} + +static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct acm *acm = tty->driver_data; + + if (!ACM_READY(acm)) + return -EINVAL; + + return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) | + (acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) | + (acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | + (acm->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) | + (acm->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) | + TIOCM_CTS; +} + +static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct acm *acm = tty->driver_data; + unsigned int newctrl; + + if (!ACM_READY(acm)) + return -EINVAL; + + newctrl = acm->ctrlout; + set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); + + newctrl = (newctrl & ~clear) | set; + + if (acm->ctrlout == newctrl) + return 0; + return acm_set_control(acm, acm->ctrlout = newctrl); +} + +static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct acm *acm = tty->driver_data; + + if (!ACM_READY(acm)) + return -EINVAL; + + return -ENOIOCTLCMD; +} + +static __u32 acm_tty_speed[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, + 1200, 1800, 2400, 4800, 9600, 19200, 38400, + 57600, 115200, 230400, 460800, 500000, 576000, + 921600, 1000000, 1152000, 1500000, 2000000, + 2500000, 3000000, 3500000, 4000000 +}; + +static __u8 acm_tty_size[] = { + 5, 6, 7, 8 +}; + +static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_old) +{ + struct acm *acm = tty->driver_data; + struct termios *termios = tty->termios; + struct usb_cdc_line_coding newline; + int newctrl = acm->ctrlout; + + if (!ACM_READY(acm)) + return; + + newline.dwDTERate = cpu_to_le32p(acm_tty_speed + + (termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0)); + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0; + newline.bParityType = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4]; + + acm->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (!newline.dwDTERate) { + newline.dwDTERate = acm->line.dwDTERate; + newctrl &= ~ACM_CTRL_DTR; + } else newctrl |= ACM_CTRL_DTR; + + if (newctrl != acm->ctrlout) + acm_set_control(acm, acm->ctrlout = newctrl); + + if (memcmp(&acm->line, &newline, sizeof newline)) { + memcpy(&acm->line, &newline, sizeof newline); + dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate), + newline.bCharFormat, newline.bParityType, + newline.bDataBits); + acm_set_line(acm, &acm->line); + } +} + +/* + * USB probe and disconnect routines. + */ + +static int acm_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_cdc_union_desc *union_header = NULL; + char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl; + struct usb_endpoint_descriptor *epread; + struct usb_endpoint_descriptor *epwrite; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct acm *acm; + int minor; + int ctrlsize,readsize; + u8 *buf; + u8 ac_management_function = 0; + u8 call_management_function = 0; + int call_interface_num = -1; + int data_interface_num; + unsigned long quirks; + + /* handle quirks deadly to normal probing*/ + quirks = (unsigned long)id->driver_info; + if (quirks == NO_UNION_NORMAL) { + data_interface = usb_ifnum_to_if(usb_dev, 1); + control_interface = usb_ifnum_to_if(usb_dev, 0); + goto skip_normal_probe; + } + + /* normal probing*/ + if (!buffer) { + err("Wierd descriptor references\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint\n"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + err("Zero length descriptor references\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buffer [1] != USB_DT_CS_INTERFACE) { + err("skipping garbage\n"); + goto next_desc; + } + + switch (buffer [2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + err("More than one union descriptor, skipping ..."); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *) + buffer; + break; + case USB_CDC_COUNTRY_TYPE: /* maybe somehow export */ + break; /* for now we ignore it */ + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_ACM_TYPE: + ac_management_function = buffer[3]; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_management_function = buffer[3]; + call_interface_num = buffer[4]; + if ((call_management_function & 3) != 3) + err("This device cannot do calls on its own. It is no modem."); + break; + + default: + err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + if (!union_header) { + if (call_interface_num > 0) { + dev_dbg(&intf->dev,"No union descriptor, using call management descriptor\n"); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); + control_interface = intf; + } else { + dev_dbg(&intf->dev,"No union descriptor, giving up\n"); + return -ENODEV; + } + } else { + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0)); + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev,"no interfaces\n"); + return -ENODEV; + } + } + + if (data_interface_num != call_interface_num) + dev_dbg(&intf->dev,"Seperate call control interface. That is not fully supported.\n"); + +skip_normal_probe: + + /*workaround for switched interfaces */ + if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) { + if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) { + struct usb_interface *t; + dev_dbg(&intf->dev,"Your device has switched interfaces.\n"); + + t = control_interface; + control_interface = data_interface; + data_interface = t; + } else { + return -EINVAL; + } + } + + if (usb_interface_claimed(data_interface)) { /* valid in this context */ + dev_dbg(&intf->dev,"The data interface isn't available\n"); + return -EBUSY; + } + + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + + + /* workaround for switched endpoints */ + if ((epread->bEndpointAddress & USB_DIR_IN) != USB_DIR_IN) { + /* descriptors are swapped */ + struct usb_endpoint_descriptor *t; + dev_dbg(&intf->dev,"The data interface has switched endpoints\n"); + + t = epread; + epread = epwrite; + epwrite = t; + } + dbg("interfaces are valid"); + for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); + + if (minor == ACM_TTY_MINORS) { + err("no more free acm devices"); + return -ENODEV; + } + + if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) { + dev_dbg(&intf->dev, "out of memory (acm kmalloc)\n"); + goto alloc_fail; + } + memset(acm, 0, sizeof(struct acm)); + + ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize); + readsize = le16_to_cpu(epread->wMaxPacketSize); + acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize); + acm->control = control_interface; + acm->data = data_interface; + acm->minor = minor; + acm->dev = usb_dev; + acm->ctrl_caps = ac_management_function; + acm->ctrlsize = ctrlsize; + acm->readsize = readsize; + acm->bh.func = acm_rx_tasklet; + acm->bh.data = (unsigned long) acm; + INIT_WORK(&acm->work, acm_softint, acm); + spin_lock_init(&acm->throttle_lock); + acm->ready_for_write = 1; + + buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); + if (!buf) { + dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)\n"); + goto alloc_fail2; + } + acm->ctrl_buffer = buf; + + buf = usb_buffer_alloc(usb_dev, readsize, GFP_KERNEL, &acm->read_dma); + if (!buf) { + dev_dbg(&intf->dev, "out of memory (read buffer alloc)\n"); + goto alloc_fail3; + } + acm->read_buffer = buf; + + buf = usb_buffer_alloc(usb_dev, acm->writesize, GFP_KERNEL, &acm->write_dma); + if (!buf) { + dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n"); + goto alloc_fail4; + } + acm->write_buffer = buf; + + acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->ctrlurb) { + dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); + goto alloc_fail5; + } + acm->readurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->readurb) { + dev_dbg(&intf->dev, "out of memory (readurb kmalloc)\n"); + goto alloc_fail6; + } + acm->writeurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->writeurb) { + dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)\n"); + goto alloc_fail7; + } + + usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); + acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + acm->ctrlurb->transfer_dma = acm->ctrl_dma; + + usb_fill_bulk_urb(acm->readurb, usb_dev, usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress), + acm->read_buffer, readsize, acm_read_bulk, acm); + acm->readurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP; + acm->readurb->transfer_dma = acm->read_dma; + + usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + acm->write_buffer, acm->writesize, acm_write_bulk, acm); + acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP; + acm->writeurb->transfer_dma = acm->write_dma; + + dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); + + acm_set_control(acm, acm->ctrlout); + + acm->line.dwDTERate = cpu_to_le32(9600); + acm->line.bDataBits = 8; + acm_set_line(acm, &acm->line); + + usb_driver_claim_interface(&acm_driver, data_interface, acm); + + tty_register_device(acm_tty_driver, minor, &intf->dev); + + acm_table[minor] = acm; + usb_set_intfdata (intf, acm); + return 0; + +alloc_fail7: + usb_free_urb(acm->readurb); +alloc_fail6: + usb_free_urb(acm->ctrlurb); +alloc_fail5: + usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma); +alloc_fail4: + usb_buffer_free(usb_dev, readsize, acm->read_buffer, acm->read_dma); +alloc_fail3: + usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); +alloc_fail2: + kfree(acm); +alloc_fail: + return -ENOMEM; +} + +static void acm_disconnect(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata (intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + + if (!acm || !acm->dev) { + dbg("disconnect on nonexisting interface"); + return; + } + + down(&open_sem); + acm->dev = NULL; + usb_set_intfdata (intf, NULL); + + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->readurb); + usb_kill_urb(acm->writeurb); + + flush_scheduled_work(); /* wait for acm_softint */ + + usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma); + usb_buffer_free(usb_dev, acm->readsize, acm->read_buffer, acm->read_dma); + usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + + usb_driver_release_interface(&acm_driver, acm->data); + + if (!acm->used) { + tty_unregister_device(acm_tty_driver, acm->minor); + acm_table[acm->minor] = NULL; + usb_free_urb(acm->ctrlurb); + usb_free_urb(acm->readurb); + usb_free_urb(acm->writeurb); + kfree(acm); + up(&open_sem); + return; + } + + up(&open_sem); + + if (acm->tty) + tty_hangup(acm->tty); +} + +/* + * USB driver structure. + */ + +static struct usb_device_id acm_ids[] = { + /* quirky and broken devices */ + { USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + /* control interfaces with various AT-command sets */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_V25TER) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_GSM) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_3G ) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_CDMA) }, + + /* NOTE: COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */ + { } +}; + +MODULE_DEVICE_TABLE (usb, acm_ids); + +static struct usb_driver acm_driver = { + .owner = THIS_MODULE, + .name = "cdc_acm", + .probe = acm_probe, + .disconnect = acm_disconnect, + .id_table = acm_ids, +}; + +/* + * TTY driver structures. + */ + +static struct tty_operations acm_ops = { + .open = acm_tty_open, + .close = acm_tty_close, + .write = acm_tty_write, + .write_room = acm_tty_write_room, + .ioctl = acm_tty_ioctl, + .throttle = acm_tty_throttle, + .unthrottle = acm_tty_unthrottle, + .chars_in_buffer = acm_tty_chars_in_buffer, + .break_ctl = acm_tty_break_ctl, + .set_termios = acm_tty_set_termios, + .tiocmget = acm_tty_tiocmget, + .tiocmset = acm_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init acm_init(void) +{ + int retval; + acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS); + if (!acm_tty_driver) + return -ENOMEM; + acm_tty_driver->owner = THIS_MODULE, + acm_tty_driver->driver_name = "acm", + acm_tty_driver->name = "ttyACM", + acm_tty_driver->devfs_name = "usb/acm/", + acm_tty_driver->major = ACM_TTY_MAJOR, + acm_tty_driver->minor_start = 0, + acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + acm_tty_driver->subtype = SERIAL_TYPE_NORMAL, + acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS, + acm_tty_driver->init_termios = tty_std_termios; + acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(acm_tty_driver, &acm_ops); + + retval = tty_register_driver(acm_tty_driver); + if (retval) { + put_tty_driver(acm_tty_driver); + return retval; + } + + retval = usb_register(&acm_driver); + if (retval) { + tty_unregister_driver(acm_tty_driver); + put_tty_driver(acm_tty_driver); + return retval; + } + + info(DRIVER_VERSION ":" DRIVER_DESC); + + return 0; +} + +static void __exit acm_exit(void) +{ + usb_deregister(&acm_driver); + tty_unregister_driver(acm_tty_driver); + put_tty_driver(acm_tty_driver); +} + +module_init(acm_init); +module_exit(acm_exit); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h new file mode 100644 index 0000000..9009114 --- /dev/null +++ b/drivers/usb/class/cdc-acm.h @@ -0,0 +1,82 @@ +/* + * + * Includes for cdc-acm.c + * + * Mainly take from usbnet's cdc-ether part + * + */ + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define ACM_TTY_MAJOR 166 +#define ACM_TTY_MINORS 32 + +/* + * Requests. + */ + +#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +/* + * Output control lines. + */ + +#define ACM_CTRL_DTR 0x01 +#define ACM_CTRL_RTS 0x02 + +/* + * Input control lines and line errors. + */ + +#define ACM_CTRL_DCD 0x01 +#define ACM_CTRL_DSR 0x02 +#define ACM_CTRL_BRK 0x04 +#define ACM_CTRL_RI 0x08 + +#define ACM_CTRL_FRAMING 0x10 +#define ACM_CTRL_PARITY 0x20 +#define ACM_CTRL_OVERRUN 0x40 + +/* + * Internal driver structures. + */ + +struct acm { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + struct tty_struct *tty; /* the corresponding tty */ + struct urb *ctrlurb, *readurb, *writeurb; /* urbs */ + u8 *ctrl_buffer, *read_buffer, *write_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma, read_dma, write_dma; /* dma handles of buffers */ + struct usb_cdc_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + struct tasklet_struct bh; /* rx processing */ + spinlock_t throttle_lock; /* synchronize throtteling and read callback */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ + unsigned int used; /* someone has this acm's device open */ + unsigned int minor; /* acm minor number */ + unsigned char throttle; /* throttled by tty layer */ + unsigned char clocal; /* termios CLOCAL */ + unsigned char ready_for_write; /* write urb can be used */ + unsigned char resubmit_to_unthrottle; /* throtteling has disabled the read urb */ + unsigned int ctrl_caps; /* control capabilities from the class specific header */ +}; + +#define CDC_DATA_INTERFACE_TYPE 0x0a + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL 1 diff --git a/drivers/usb/class/usb-midi.c b/drivers/usb/class/usb-midi.c new file mode 100644 index 0000000..5f8af35 --- /dev/null +++ b/drivers/usb/class/usb-midi.c @@ -0,0 +1,2154 @@ +/* + 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_param(singlebyte, int, 0); +MODULE_PARM_DESC(singlebyte,"Enable sending MIDI messages with single message packet"); + +static int maxdevices = 4; +module_param(maxdevices, int, 0); +MODULE_PARM_DESC(maxdevices,"Max number of allocatable MIDI device"); + +static int uvendor = -1; +module_param(uvendor, int, 0); +MODULE_PARM_DESC(uvendor, "The USB Vendor ID of a semi-compliant interface"); + +static int uproduct = -1; +module_param(uproduct, int, 0); +MODULE_PARM_DESC(uproduct, "The USB Product ID of a semi-compliant interface"); + +static int uinterface = -1; +module_param(uinterface, int, 0); +MODULE_PARM_DESC(uinterface, "The Interface number of a semi-compliant interface"); + +static int ualt = -1; +module_param(ualt, int, 0); +MODULE_PARM_DESC(ualt, "The optional alternative setting of a semi-compliant interface"); + +static int umin = -1; +module_param(umin, int, 0); +MODULE_PARM_DESC(umin, "The input endpoint of a semi-compliant interface"); + +static int umout = -1; +module_param(umout, int, 0); +MODULE_PARM_DESC(umout, "The output endpoint of a semi-compliant interface"); + +static int ucable = -1; +module_param(ucable, int, 0); +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_param(ulangid, ushort, 0); +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]; + + if ( m->singlebyte != 0 ) { + /** Simple code to handle the single-byte USB-MIDI protocol. */ + spin_lock_irqsave( &ep->lock, flags ); + if ( ep->bufWrPtr+4 > ep->bufSize ) { + ret = flush_midi_buffer( ep ); + if ( !ret ) { + spin_unlock_irqrestore( &ep->lock, flags ); + return ret; + } + } + ep->buf[ep->bufWrPtr++] = (m->mout.cableId<<4) | 0x0f; /* single byte */ + ep->buf[ep->bufWrPtr++] = c; + ep->buf[ep->bufWrPtr++] = 0; + ep->buf[ep->bufWrPtr++] = 0; + if ( ep->bufWrPtr >= ep->bufSize ) { + ret = flush_midi_buffer( ep ); + } + spin_unlock_irqrestore( &ep->lock, flags ); + + return ret; + } + /** Normal USB-MIDI protocol begins here. */ + + if ( c > 0xf7 ) { /* system: Realtime messages */ + /** Realtime messages are written IMMEDIATELY. */ + sysrt_buf[0] = (m->mout.cableId<<4) | 0x0f; + sysrt_buf[1] = c; + sysrt_buf[2] = 0; + sysrt_buf[3] = 0; + spin_lock_irqsave( &ep->lock, flags ); + ret = usb_write( ep, sysrt_buf, 4 ); + spin_unlock_irqrestore( &ep->lock, flags ); + /* m->mout.lastEvent = 0; */ + + return ret; + } + + if ( c >= 0x80 ) { + if ( c < 0xf0 ) { + m->mout.lastEvent = c; + m->mout.isInExclusive = 0; + m->mout.bufRemains = get_remains(c); + } else if ( c == 0xf0 ) { + /* m->mout.lastEvent = 0; */ + m->mout.isInExclusive = 1; + m->mout.bufRemains = get_remains(c); + } else if ( c == 0xf7 && m->mout.isInExclusive == 1 ) { + /* m->mout.lastEvent = 0; */ + m->mout.isInExclusive = 0; + m->mout.bufRemains = 1; + } else if ( c > 0xf0 ) { + /* m->mout.lastEvent = 0; */ + m->mout.isInExclusive = 0; + m->mout.bufRemains = get_remains(c); + } + + } else if ( m->mout.bufRemains == 0 && m->mout.isInExclusive == 0 ) { + if ( m->mout.lastEvent == 0 ) { + return 0; /* discard, waiting for the first event */ + } + /** track status **/ + m->mout.buf[0] = m->mout.lastEvent; + m->mout.bufPtr = 1; + m->mout.bufRemains = get_remains(m->mout.lastEvent)-1; + } + + m->mout.buf[m->mout.bufPtr++] = c; + m->mout.bufRemains--; + if ( m->mout.bufRemains == 0 || m->mout.bufPtr >= 3) { + ret = put_one_midi_event(m); + } + + return ret; +} + + +/* ------------------------------------------------------------------------- */ + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic contract: Used to change the current read/write position in a file. + * On success, the non-negative position is reported. + * On failure, the negative of an error code is reported. + * + * Because a MIDIStream is not a file, all seek operations are doomed to fail. + * + **/ +static loff_t usb_midi_llseek(struct file *file, loff_t offset, int origin) +{ + /** Tell user you cannot seek on a PIPE-like device. **/ + return -ESPIPE; +} + + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic contract: Block until count bytes have been read or an error occurs. + * + **/ + +static ssize_t usb_midi_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct usb_mididev *m = (struct usb_mididev *)file->private_data; + struct midi_in_endpoint *ep = m->min.ep; + ssize_t ret; + DECLARE_WAITQUEUE(wait, current); + + if ( !access_ok(VERIFY_READ, buffer, count) ) { + return -EFAULT; + } + if ( count == 0 ) { + return 0; + } + + add_wait_queue( &ep->wait, &wait ); + ret = 0; + while( count > 0 ) { + int cnt; + int d = (int)count; + + cnt = m->min.bufRemains; + if ( cnt > d ) { + cnt = d; + } + + if ( cnt <= 0 ) { + if ( file->f_flags & O_NONBLOCK ) { + if (!ret) + ret = -EAGAIN; + break; + } + __set_current_state(TASK_INTERRUPTIBLE); + schedule(); + if (signal_pending(current)) { + if(!ret) + ret=-ERESTARTSYS; + break; + } + continue; + } + + { + int i; + unsigned long flags; /* used to synchronize access to the endpoint */ + spin_lock_irqsave( &ep->lock, flags ); + for (i = 0; i < cnt; i++) { + if ( copy_to_user( buffer+i, m->min.buf+m->min.bufRdPtr, 1 ) ) { + if ( !ret ) + ret = -EFAULT; + break; + } + m->min.bufRdPtr = (m->min.bufRdPtr+1)%MIDI_IN_BUFSIZ; + m->min.bufRemains -= 1; + } + spin_unlock_irqrestore( &ep->lock, flags ); + } + + count-=cnt; + buffer+=cnt; + ret+=cnt; + + break; + } + + remove_wait_queue( &ep->wait, &wait ); + set_current_state(TASK_RUNNING); + + return ret; +} + + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic Contract: Take MIDI data byte-by-byte and pass it to + * writeMidi() which packages MIDI data into USB-MIDI stream. + * Then flushMidiData() is called to ensure all bytes have been written + * in a timely fashion. + * + **/ + +static ssize_t usb_midi_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct usb_mididev *m = (struct usb_mididev *)file->private_data; + ssize_t ret; + unsigned long int flags; + + if ( !access_ok(VERIFY_READ, buffer, count) ) { + return -EFAULT; + } + if ( count == 0 ) { + return 0; + } + + ret = 0; + while( count > 0 ) { + unsigned char c; + + if (copy_from_user((unsigned char *)&c, buffer, 1)) { + if ( ret == 0 ) + ret = -EFAULT; + break; + } + if( midi_write(m, (int)c) ) { + if ( ret == 0 ) + ret = -EFAULT; + break; + } + count--; + buffer++; + ret++; + } + + spin_lock_irqsave( &m->mout.ep->lock, flags ); + if ( flush_midi_buffer(m->mout.ep) < 0 ) { + ret = -EFAULT; + } + spin_unlock_irqrestore( &m->mout.ep->lock, flags ); + + return ret; +} + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic contract: Wait (spin) until ready to read or write on the file. + * + **/ +static unsigned int usb_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct usb_mididev *m = (struct usb_mididev *)file->private_data; + struct midi_in_endpoint *iep = m->min.ep; + struct midi_out_endpoint *oep = m->mout.ep; + unsigned long flags; + unsigned int mask = 0; + + if ( file->f_mode & FMODE_READ ) { + poll_wait( file, &iep->wait, wait ); + spin_lock_irqsave( &iep->lock, flags ); + if ( m->min.bufRemains > 0 ) + mask |= POLLIN | POLLRDNORM; + spin_unlock_irqrestore( &iep->lock, flags ); + } + + if ( file->f_mode & FMODE_WRITE ) { + poll_wait( file, &oep->wait, wait ); + spin_lock_irqsave( &oep->lock, flags ); + if ( oep->bufWrPtr < oep->bufSize ) + mask |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore( &oep->lock, flags ); + } + + return mask; +} + + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic contract: This is always the first operation performed on the + * device node. If no method is defined, the open succeeds without any + * notification given to the module. + * + **/ + +static int usb_midi_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + DECLARE_WAITQUEUE(wait, current); + struct usb_midi_state *s; + struct usb_mididev *m; + unsigned long flags; + int succeed = 0; + +#if 0 + printk(KERN_INFO "usb-midi: Open minor= %d.\n", minor); +#endif + + for(;;) { + down(&open_sem); + list_for_each_entry(s, &mididevs, mididev) { + list_for_each_entry(m, &s->midiDevList, list) { + if ( !((m->dev_midi ^ minor) & ~0xf) ) + goto device_found; + } + } + up(&open_sem); + return -ENODEV; + + device_found: + if ( !s->usbdev ) { + up(&open_sem); + return -EIO; + } + if ( !(m->open_mode & file->f_mode) ) { + break; + } + if ( file->f_flags & O_NONBLOCK ) { + up(&open_sem); + return -EBUSY; + } + __set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue( &open_wait, &wait ); + up(&open_sem); + schedule(); + remove_wait_queue( &open_wait, &wait ); + if ( signal_pending(current) ) { + return -ERESTARTSYS; + } + } + + file->private_data = m; + spin_lock_irqsave( &s->lock, flags ); + + if ( !(m->open_mode & (FMODE_READ | FMODE_WRITE)) ) { + //FIXME: intented semantics unclear here + m->min.bufRdPtr = 0; + m->min.bufWrPtr = 0; + m->min.bufRemains = 0; + spin_lock_init(&m->min.ep->lock); + + m->mout.bufPtr = 0; + m->mout.bufRemains = 0; + m->mout.isInExclusive = 0; + m->mout.lastEvent = 0; + spin_lock_init(&m->mout.ep->lock); + } + + if ( (file->f_mode & FMODE_READ) && m->min.ep != NULL ) { + unsigned long int flagsep; + spin_lock_irqsave( &m->min.ep->lock, flagsep ); + m->min.ep->cables[m->min.cableId] = m; + m->min.ep->readers += 1; + m->min.bufRdPtr = 0; + m->min.bufWrPtr = 0; + m->min.bufRemains = 0; + spin_unlock_irqrestore( &m->min.ep->lock, flagsep ); + + if ( !(m->min.ep->urbSubmitted)) { + + /* urb->dev must be reinitialized on 2.4.x kernels */ + m->min.ep->urb->dev = m->min.ep->usbdev; + + if ( usb_submit_urb(m->min.ep->urb, GFP_ATOMIC) ) { + printk(KERN_ERR "usbmidi: Cannot submit urb for MIDI-IN\n"); + } + m->min.ep->urbSubmitted = 1; + } + m->open_mode |= FMODE_READ; + succeed = 1; + } + + if ( (file->f_mode & FMODE_WRITE) && m->mout.ep != NULL ) { + m->mout.bufPtr = 0; + m->mout.bufRemains = 0; + m->mout.isInExclusive = 0; + m->mout.lastEvent = 0; + m->open_mode |= FMODE_WRITE; + succeed = 1; + } + + spin_unlock_irqrestore( &s->lock, flags ); + + s->count++; + up(&open_sem); + + /** Changed to prevent extra increments to USE_COUNT. **/ + if (!succeed) { + return -EBUSY; + } + +#if 0 + printk(KERN_INFO "usb-midi: Open Succeeded. minor= %d.\n", minor); +#endif + + return nonseekable_open(inode, file); /** Success. **/ +} + + +/** Basic operation on /dev/midiXX as registered through struct file_operations. + * + * Basic contract: Close an opened file and deallocate anything we allocated. + * Like open(), this can be missing. If open set file->private_data, + * release() must clear it. + * + **/ + +static int usb_midi_release(struct inode *inode, struct file *file) +{ + struct usb_mididev *m = (struct usb_mididev *)file->private_data; + struct usb_midi_state *s = (struct usb_midi_state *)m->midi; + +#if 0 + printk(KERN_INFO "usb-midi: Close.\n"); +#endif + + down(&open_sem); + + if ( m->open_mode & FMODE_WRITE ) { + m->open_mode &= ~FMODE_WRITE; + usb_kill_urb( m->mout.ep->urb ); + } + + if ( m->open_mode & FMODE_READ ) { + unsigned long int flagsep; + spin_lock_irqsave( &m->min.ep->lock, flagsep ); + m->min.ep->cables[m->min.cableId] = NULL; // discard cable + m->min.ep->readers -= 1; + m->open_mode &= ~FMODE_READ; + if ( m->min.ep->readers == 0 && + m->min.ep->urbSubmitted ) { + m->min.ep->urbSubmitted = 0; + usb_kill_urb(m->min.ep->urb); + } + spin_unlock_irqrestore( &m->min.ep->lock, flagsep ); + } + + s->count--; + + up(&open_sem); + wake_up(&open_wait); + + file->private_data = NULL; + return 0; +} + +static struct file_operations usb_midi_fops = { + .owner = THIS_MODULE, + .llseek = usb_midi_llseek, + .read = usb_midi_read, + .write = usb_midi_write, + .poll = usb_midi_poll, + .open = usb_midi_open, + .release = usb_midi_release, +}; + +/* ------------------------------------------------------------------------- */ + +/** Returns filled midi_in_endpoint structure or null on failure. + * + * Parameters: + * d - a usb_device + * endPoint - An usb endpoint in the range 0 to 15. + * Called by allocUsbMidiDev(); + * + **/ + +static struct midi_in_endpoint *alloc_midi_in_endpoint( struct usb_device *d, int endPoint ) +{ + struct midi_in_endpoint *ep; + int bufSize; + int pipe; + + endPoint &= 0x0f; /* Silently force endPoint to lie in range 0 to 15. */ + + pipe = usb_rcvbulkpipe( d, endPoint ); + bufSize = usb_maxpacket( d, pipe, 0 ); + /* usb_pipein() = ! usb_pipeout() = true for an in Endpoint */ + + ep = (struct midi_in_endpoint *)kmalloc(sizeof(struct midi_in_endpoint), GFP_KERNEL); + if ( !ep ) { + printk(KERN_ERR "usbmidi: no memory for midi in-endpoint\n"); + return NULL; + } + memset( ep, 0, sizeof(struct midi_in_endpoint) ); +// this sets cables[] and readers to 0, too. +// for (i=0; i<16; i++) ep->cables[i] = 0; // discard cable +// ep->readers = 0; + + ep->endpoint = endPoint; + + ep->recvBuf = (unsigned char *)kmalloc(sizeof(unsigned char)*(bufSize), GFP_KERNEL); + if ( !ep->recvBuf ) { + printk(KERN_ERR "usbmidi: no memory for midi in-endpoint buffer\n"); + kfree(ep); + return NULL; + } + + ep->urb = usb_alloc_urb(0, GFP_KERNEL); /* no ISO */ + if ( !ep->urb ) { + printk(KERN_ERR "usbmidi: no memory for midi in-endpoint urb\n"); + kfree(ep->recvBuf); + kfree(ep); + return NULL; + } + usb_fill_bulk_urb( ep->urb, d, + usb_rcvbulkpipe(d, endPoint), + (unsigned char *)ep->recvBuf, bufSize, + usb_bulk_read, ep ); + + /* ep->bufRdPtr = 0; */ + /* ep->bufWrPtr = 0; */ + /* ep->bufRemains = 0; */ + /* ep->urbSubmitted = 0; */ + ep->recvBufSize = bufSize; + + init_waitqueue_head(&ep->wait); + + return ep; +} + +static int remove_midi_in_endpoint( struct midi_in_endpoint *min ) +{ + usb_kill_urb( min->urb ); + usb_free_urb( min->urb ); + kfree( min->recvBuf ); + kfree( min ); + + return 0; +} + +/** Returns filled midi_out_endpoint structure or null on failure. + * + * Parameters: + * d - a usb_device + * endPoint - An usb endpoint in the range 0 to 15. + * Called by allocUsbMidiDev(); + * + **/ +static struct midi_out_endpoint *alloc_midi_out_endpoint( struct usb_device *d, int endPoint ) +{ + struct midi_out_endpoint *ep = NULL; + int pipe; + int bufSize; + + endPoint &= 0x0f; + pipe = usb_sndbulkpipe( d, endPoint ); + bufSize = usb_maxpacket( d, pipe, 1 ); + + ep = (struct midi_out_endpoint *)kmalloc(sizeof(struct midi_out_endpoint), GFP_KERNEL); + if ( !ep ) { + printk(KERN_ERR "usbmidi: no memory for midi out-endpoint\n"); + return NULL; + } + memset( ep, 0, sizeof(struct midi_out_endpoint) ); + + ep->endpoint = endPoint; + ep->buf = (unsigned char *)kmalloc(sizeof(unsigned char)*bufSize, GFP_KERNEL); + if ( !ep->buf ) { + printk(KERN_ERR "usbmidi: no memory for midi out-endpoint buffer\n"); + kfree(ep); + return NULL; + } + + ep->urb = usb_alloc_urb(0, GFP_KERNEL); /* no ISO */ + if ( !ep->urb ) { + printk(KERN_ERR "usbmidi: no memory for midi out-endpoint urb\n"); + kfree(ep->buf); + kfree(ep); + return NULL; + } + + ep->bufSize = bufSize; + /* ep->bufWrPtr = 0; */ + + init_waitqueue_head(&ep->wait); + + return ep; +} + + +static int remove_midi_out_endpoint( struct midi_out_endpoint *mout ) +{ + usb_kill_urb( mout->urb ); + usb_free_urb( mout->urb ); + kfree( mout->buf ); + kfree( mout ); + + return 0; +} + + +/** Returns a filled usb_mididev structure, registered as a Linux MIDI device. + * + * Returns null if memory is not available or the device cannot be registered. + * Called by allocUsbMidiDev(); + * + **/ +static struct usb_mididev *allocMidiDev( + struct usb_midi_state *s, + struct midi_in_endpoint *min, + struct midi_out_endpoint *mout, + int inCableId, + int outCableId ) +{ + struct usb_mididev *m; + + m = (struct usb_mididev *)kmalloc(sizeof(struct usb_mididev), GFP_KERNEL); + if (!m) { + printk(KERN_ERR "usbmidi: no memory for midi device\n"); + return NULL; + } + + memset(m, 0, sizeof(struct usb_mididev)); + + if ((m->dev_midi = register_sound_midi(&usb_midi_fops, -1)) < 0) { + printk(KERN_ERR "usbmidi: cannot register midi device\n"); + kfree(m); + return NULL; + } + + m->midi = s; + /* m->open_mode = 0; */ + + if ( min ) { + m->min.ep = min; + m->min.ep->usbdev = s->usbdev; + m->min.cableId = inCableId; + } + /* m->min.bufPtr = 0; */ + /* m->min.bufRemains = 0; */ + + if ( mout ) { + m->mout.ep = mout; + m->mout.ep->usbdev = s->usbdev; + m->mout.cableId = outCableId; + } + /* m->mout.bufPtr = 0; */ + /* m->mout.bufRemains = 0; */ + /* m->mout.isInExclusive = 0; */ + /* m->mout.lastEvent = 0; */ + + m->singlebyte = singlebyte; + + return m; +} + + +static void release_midi_device( struct usb_midi_state *s ) +{ + struct usb_mididev *m; + struct midi_in_endpoint *min; + struct midi_out_endpoint *mout; + + if ( s->count > 0 ) { + up(&open_sem); + return; + } + up( &open_sem ); + wake_up( &open_wait ); + + while(!list_empty(&s->inEndpointList)) { + min = list_entry(s->inEndpointList.next, struct midi_in_endpoint, list); + list_del(&min->list); + remove_midi_in_endpoint(min); + } + + while(!list_empty(&s->outEndpointList)) { + mout = list_entry(s->outEndpointList.next, struct midi_out_endpoint, list); + list_del(&mout->list); + remove_midi_out_endpoint(mout); + } + + while(!list_empty(&s->midiDevList)) { + m = list_entry(s->midiDevList.next, struct usb_mididev, list); + list_del(&m->list); + kfree(m); + } + + kfree(s); + + return; +} + + +/* ------------------------------------------------------------------------- */ + +/** Utility routine to find a descriptor in a dump of many descriptors. + * Returns start of descriptor or NULL if not found. + * descStart pointer to list of interfaces. + * descLength length (in bytes) of dump + * after (ignored if NULL) this routine returns only descriptors after "after" + * dtype (mandatory) The descriptor type. + * iface (ignored if -1) returns descriptor at/following given interface + * altSetting (ignored if -1) returns descriptor at/following given altSetting + * + * + * Called by parseDescriptor(), find_csinterface_descriptor(); + * + */ +static void *find_descriptor( void *descStart, unsigned int descLength, void *after, unsigned char dtype, int iface, int altSetting ) +{ + unsigned char *p, *end, *next; + int interfaceNumber = -1, altSet = -1; + + p = descStart; + end = p + descLength; + for( ; p < end; ) { + if ( p[0] < 2 ) + return NULL; + next = p + p[0]; + if ( next > end ) + return NULL; + if ( p[1] == USB_DT_INTERFACE ) { + if ( p[0] < USB_DT_INTERFACE_SIZE ) + return NULL; + interfaceNumber = p[2]; + altSet = p[3]; + } + if ( p[1] == dtype && + ( !after || ( p > (unsigned char *)after) ) && + ( ( iface == -1) || (iface == interfaceNumber) ) && + ( (altSetting == -1) || (altSetting == altSet) )) { + return p; + } + p = next; + } + return NULL; +} + +/** Utility to find a class-specific interface descriptor. + * dsubtype is a descriptor subtype + * Called by parseDescriptor(); + **/ +static void *find_csinterface_descriptor(void *descStart, unsigned int descLength, void *after, u8 dsubtype, int iface, int altSetting) +{ + unsigned char *p; + + p = find_descriptor( descStart, descLength, after, USB_DT_CS_INTERFACE, iface, altSetting ); + while ( p ) { + if ( p[0] >= 3 && p[2] == dsubtype ) + return p; + p = find_descriptor( descStart, descLength, p, USB_DT_CS_INTERFACE, + iface, altSetting ); + } + return NULL; +} + + +/** The magic of making a new usb_midi_device from config happens here. + * + * The caller is responsible for free-ing this return value (if not NULL). + * + **/ +static struct usb_midi_device *parse_descriptor( struct usb_device *d, unsigned char *buffer, int bufSize, unsigned int ifnum , unsigned int altSetting, int quirks) +{ + struct usb_midi_device *u; + unsigned char *p1; + unsigned char *p2; + unsigned char *next; + int iep, oep; + int length; + unsigned long longBits; + int pins, nbytes, offset, shift, jack; +#ifdef HAVE_JACK_STRINGS + /** Jacks can have associated names. **/ + unsigned char jack2string[256]; +#endif + + u = NULL; + /* find audiocontrol interface */ + p1 = find_csinterface_descriptor( buffer, bufSize, NULL, + MS_HEADER, ifnum, altSetting); + + if ( !p1 ) { + goto error_end; + } + + if ( p1[0] < MS_HEADER_LENGTH ) { + goto error_end; + } + + /* Assume success. Since the device corresponds to USB-MIDI spec, we assume + that the rest of the USB 2.0 spec is obeyed. */ + + u = (struct usb_midi_device *)kmalloc( sizeof(struct usb_midi_device), GFP_KERNEL ); + if ( !u ) { + return NULL; + } + u->deviceName = NULL; + u->idVendor = le16_to_cpu(d->descriptor.idVendor); + u->idProduct = le16_to_cpu(d->descriptor.idProduct); + u->interface = ifnum; + u->altSetting = altSetting; + u->in[0].endpoint = -1; + u->in[0].cableId = -1; + u->out[0].endpoint = -1; + u->out[0].cableId = -1; + + + printk(KERN_INFO "usb-midi: Found MIDIStreaming device corresponding to Release %d.%02d of spec.\n", + (p1[4] >> 4) * 10 + (p1[4] & 0x0f ), + (p1[3] >> 4) * 10 + (p1[3] & 0x0f ) + ); + + length = p1[5] | (p1[6] << 8); + +#ifdef HAVE_JACK_STRINGS + memset(jack2string, 0, sizeof(unsigned char) * 256); +#endif + + length -= p1[0]; + for (p2 = p1 + p1[0]; length > 0; p2 = next) { + next = p2 + p2[0]; + length -= p2[0]; + + if (p2[0] < 2 ) + break; + if (p2[1] != USB_DT_CS_INTERFACE) + break; + if (p2[2] == MIDI_IN_JACK && p2[0] >= 6 ) { + jack = p2[4]; +#ifdef HAVE_JACK_STRINGS + jack2string[jack] = p2[5]; +#endif + printk(KERN_INFO "usb-midi: Found IN Jack 0x%02x %s\n", + jack, (p2[3] == EMBEDDED_JACK)?"EMBEDDED":"EXTERNAL" ); + } else if ( p2[2] == MIDI_OUT_JACK && p2[0] >= 6) { + pins = p2[5]; + if ( p2[0] < (6 + 2 * pins) ) + continue; + jack = p2[4]; +#ifdef HAVE_JACK_STRINGS + jack2string[jack] = p2[5 + 2 * pins]; +#endif + printk(KERN_INFO "usb-midi: Found OUT Jack 0x%02x %s, %d pins\n", + jack, (p2[3] == EMBEDDED_JACK)?"EMBEDDED":"EXTERNAL", pins ); + } else if ( p2[2] == ELEMENT_DESCRIPTOR && p2[0] >= 10) { + pins = p2[4]; + if ( p2[0] < (9 + 2 * pins ) ) + continue; + nbytes = p2[8 + 2 * pins ]; + if ( p2[0] < (10 + 2 * pins + nbytes) ) + continue; + longBits = 0L; + for ( offset = 0, shift = 0; offset < nbytes && offset < 8; offset ++, shift += 8) { + longBits |= ((long)(p2[9 + 2 * pins + offset])) << shift; + } + jack = p2[3]; +#ifdef HAVE_JACK_STRINGS + jack2string[jack] = p2[9 + 2 * pins + nbytes]; +#endif + printk(KERN_INFO "usb-midi: Found ELEMENT 0x%02x, %d/%d pins in/out, bits: 0x%016lx\n", + jack, pins, (int)(p2[5 + 2 * pins]), (long)longBits ); + } else { + } + } + + iep=0; + oep=0; + + if (quirks==0) { + /* MIDISTREAM */ + p2 = NULL; + for (p1 = find_descriptor(buffer, bufSize, NULL, USB_DT_ENDPOINT, + ifnum, altSetting ); p1; p1 = next ) { + next = find_descriptor(buffer, bufSize, p1, USB_DT_ENDPOINT, + ifnum, altSetting ); + p2 = find_descriptor(buffer, bufSize, p1, USB_DT_CS_ENDPOINT, + ifnum, altSetting ); + + if ( p2 && next && ( p2 > next ) ) + p2 = NULL; + + if ( p1[0] < 9 || !p2 || p2[0] < 4 ) + continue; + + if ( (p1[2] & 0x80) == 0x80 ) { + if ( iep < 15 ) { + pins = p2[3]; /* not pins -- actually "cables" */ + if ( pins > 16 ) + pins = 16; + u->in[iep].endpoint = p1[2]; + u->in[iep].cableId = ( 1 << pins ) - 1; + if ( u->in[iep].cableId ) + iep ++; + if ( iep < 15 ) { + u->in[iep].endpoint = -1; + u->in[iep].cableId = -1; + } + } + } else { + if ( oep < 15 ) { + pins = p2[3]; /* not pins -- actually "cables" */ + if ( pins > 16 ) + pins = 16; + u->out[oep].endpoint = p1[2]; + u->out[oep].cableId = ( 1 << pins ) - 1; + if ( u->out[oep].cableId ) + oep ++; + if ( oep < 15 ) { + u->out[oep].endpoint = -1; + u->out[oep].cableId = -1; + } + } + } + + } + } else if (quirks==1) { + /* YAMAHA quirks */ + for (p1 = find_descriptor(buffer, bufSize, NULL, USB_DT_ENDPOINT, + ifnum, altSetting ); p1; p1 = next ) { + next = find_descriptor(buffer, bufSize, p1, USB_DT_ENDPOINT, + ifnum, altSetting ); + + if ( p1[0] < 7 ) + continue; + + if ( (p1[2] & 0x80) == 0x80 ) { + if ( iep < 15 ) { + pins = iep+1; + if ( pins > 16 ) + pins = 16; + u->in[iep].endpoint = p1[2]; + u->in[iep].cableId = ( 1 << pins ) - 1; + if ( u->in[iep].cableId ) + iep ++; + if ( iep < 15 ) { + u->in[iep].endpoint = -1; + u->in[iep].cableId = -1; + } + } + } else { + if ( oep < 15 ) { + pins = oep+1; + u->out[oep].endpoint = p1[2]; + u->out[oep].cableId = ( 1 << pins ) - 1; + if ( u->out[oep].cableId ) + oep ++; + if ( oep < 15 ) { + u->out[oep].endpoint = -1; + u->out[oep].cableId = -1; + } + } + } + + } + } + + if ( !iep && ! oep ) { + goto error_end; + } + + return u; + +error_end: + kfree(u); + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +/** Returns number between 0 and 16. + * + **/ +static int on_bits( unsigned short v ) +{ + int i; + int ret=0; + + for ( i=0 ; i<16 ; i++ ) { + if ( v & (1<<i) ) + ret++; + } + + return ret; +} + + +/** USB-device will be interrogated for altSetting. + * + * Returns negative on error. + * Called by allocUsbMidiDev(); + * + **/ + +static int get_alt_setting( struct usb_device *d, int ifnum ) +{ + int alts, alt=0; + struct usb_interface *iface; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *ep; + int epin, epout; + int i; + + iface = usb_ifnum_to_if( d, ifnum ); + alts = iface->num_altsetting; + + for ( alt=0 ; alt<alts ; alt++ ) { + interface = &iface->altsetting[alt]; + epin = -1; + epout = -1; + + for ( i=0 ; i<interface->desc.bNumEndpoints ; i++ ) { + ep = &interface->endpoint[i].desc; + if ( (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK ) { + continue; + } + if ( (ep->bEndpointAddress & USB_DIR_IN) && epin < 0 ) { + epin = i; + } else if ( epout < 0 ) { + epout = i; + } + if ( epin >= 0 && epout >= 0 ) { + return interface->desc.bAlternateSetting; + } + } + } + + return -ENODEV; +} + + +/* ------------------------------------------------------------------------- */ + + +/** Returns 0 if successful in allocating and registering internal structures. + * Returns negative on failure. + * Calls allocMidiDev which additionally registers /dev/midiXX devices. + * Writes messages on success to indicate which /dev/midiXX is which physical + * endpoint. + * + **/ +static int alloc_usb_midi_device( struct usb_device *d, struct usb_midi_state *s, struct usb_midi_device *u ) +{ + struct usb_mididev **mdevs=NULL; + struct midi_in_endpoint *mins[15], *min; + struct midi_out_endpoint *mouts[15], *mout; + int inDevs=0, outDevs=0; + int inEndpoints=0, outEndpoints=0; + int inEndpoint, outEndpoint; + int inCableId, outCableId; + int i; + int devices = 0; + int alt = 0; + + /* Obtain altSetting or die.. */ + alt = u->altSetting; + if ( alt < 0 ) { + alt = get_alt_setting( d, u->interface ); + } + if ( alt < 0 ) + return -ENXIO; + + /* Configure interface */ + if ( usb_set_interface( d, u->interface, alt ) < 0 ) { + return -ENXIO; + } + + for ( i = 0 ; i < 15 ; i++ ) { + mins[i] = NULL; + mouts[i] = NULL; + } + + /* Begin Allocation */ + while( inEndpoints < 15 + && inDevs < maxdevices + && u->in[inEndpoints].cableId >= 0 ) { + inDevs += on_bits((unsigned short)u->in[inEndpoints].cableId); + mins[inEndpoints] = alloc_midi_in_endpoint( d, u->in[inEndpoints].endpoint ); + if ( mins[inEndpoints] == NULL ) + goto error_end; + inEndpoints++; + } + + while( outEndpoints < 15 + && outDevs < maxdevices + && u->out[outEndpoints].cableId >= 0 ) { + outDevs += on_bits((unsigned short)u->out[outEndpoints].cableId); + mouts[outEndpoints] = alloc_midi_out_endpoint( d, u->out[outEndpoints].endpoint ); + if ( mouts[outEndpoints] == NULL ) + goto error_end; + outEndpoints++; + } + + devices = inDevs > outDevs ? inDevs : outDevs; + devices = maxdevices > devices ? devices : maxdevices; + + /* obtain space for device name (iProduct) if not known. */ + if ( ! u->deviceName ) { + mdevs = (struct usb_mididev **) + kmalloc(sizeof(struct usb_mididevs *)*devices + + sizeof(char) * 256, GFP_KERNEL); + } else { + mdevs = (struct usb_mididev **) + kmalloc(sizeof(struct usb_mididevs *)*devices, GFP_KERNEL); + } + + if ( !mdevs ) { + /* devices = 0; */ + /* mdevs = NULL; */ + goto error_end; + } + for ( i=0 ; i<devices ; i++ ) { + mdevs[i] = NULL; + } + + /* obtain device name (iProduct) if not known. */ + if ( ! u->deviceName ) { + u->deviceName = (char *) (mdevs + devices); + if ( ! d->have_langid && d->descriptor.iProduct) { + alt = usb_get_string(d, 0, 0, u->deviceName, 250); + if (alt < 0) { + printk(KERN_INFO "error getting string descriptor 0 (error=%d)\n", alt); + } else if (u->deviceName[0] < 4) { + printk(KERN_INFO "string descriptor 0 too short (length = %d)\n", alt); + } else { + printk(KERN_INFO "string descriptor 0 found (length = %d)\n", alt); + for(; alt >= 4; alt -= 2) { + i = u->deviceName[alt-2] | (u->deviceName[alt-1]<< 8); + printk(KERN_INFO "usb-midi: langid(%d) 0x%04x\n", + (alt-4) >> 1, i); + if ( ( ( i ^ ulangid ) & 0xff ) == 0 ) { + d->have_langid = 1; + d->string_langid = i; + printk(KERN_INFO "usb-midi: langid(match) 0x%04x\n", i); + if ( i == ulangid ) + break; + } + } + } + } + u->deviceName[0] = (char) 0; + if (d->descriptor.iProduct) { + printk(KERN_INFO "usb-midi: fetchString(%d)\n", d->descriptor.iProduct); + alt = usb_string(d, d->descriptor.iProduct, u->deviceName, 255); + if( alt < 0 ) { + u->deviceName[0] = (char) 0; + } + printk(KERN_INFO "usb-midi: fetchString = %d\n", alt); + } + /* Failsafe */ + if ( !u->deviceName[0] ) { + if (le16_to_cpu(d->descriptor.idVendor) == USB_VENDOR_ID_ROLAND ) { + strcpy(u->deviceName, "Unknown Roland"); + } else if (le16_to_cpu(d->descriptor.idVendor) == USB_VENDOR_ID_STEINBERG ) { + strcpy(u->deviceName, "Unknown Steinberg"); + } else if (le16_to_cpu(d->descriptor.idVendor) == USB_VENDOR_ID_YAMAHA ) { + strcpy(u->deviceName, "Unknown Yamaha"); + } else { + strcpy(u->deviceName, "Unknown"); + } + } + } + + inEndpoint = 0; inCableId = -1; + outEndpoint = 0; outCableId = -1; + + for ( i=0 ; i<devices ; i++ ) { + for ( inCableId ++ ; + inEndpoint <15 + && mins[inEndpoint] + && !(u->in[inEndpoint].cableId & (1<<inCableId)) ; + inCableId++ ) { + if ( inCableId >= 16 ) { + inEndpoint ++; + inCableId = 0; + } + } + min = mins[inEndpoint]; + for ( outCableId ++ ; + outEndpoint <15 + && mouts[outEndpoint] + && !(u->out[outEndpoint].cableId & (1<<outCableId)) ; + outCableId++ ) { + if ( outCableId >= 16 ) { + outEndpoint ++; + outCableId = 0; + } + } + mout = mouts[outEndpoint]; + + mdevs[i] = allocMidiDev( s, min, mout, inCableId, outCableId ); + if ( mdevs[i] == NULL ) + goto error_end; + + } + + /* Success! */ + for ( i=0 ; i<devices ; i++ ) { + list_add_tail( &mdevs[i]->list, &s->midiDevList ); + } + for ( i=0 ; i<inEndpoints ; i++ ) { + list_add_tail( &mins[i]->list, &s->inEndpointList ); + } + for ( i=0 ; i<outEndpoints ; i++ ) { + list_add_tail( &mouts[i]->list, &s->outEndpointList ); + } + + printk(KERN_INFO "usbmidi: found [ %s ] (0x%04x:0x%04x), attached:\n", u->deviceName, u->idVendor, u->idProduct ); + for ( i=0 ; i<devices ; i++ ) { + int dm = (mdevs[i]->dev_midi-2)>>4; + if ( mdevs[i]->mout.ep != NULL && mdevs[i]->min.ep != NULL ) { + printk(KERN_INFO "usbmidi: /dev/midi%02d: in (ep:%02x cid:%2d bufsiz:%2d) out (ep:%02x cid:%2d bufsiz:%2d)\n", + dm, + mdevs[i]->min.ep->endpoint|USB_DIR_IN, mdevs[i]->min.cableId, mdevs[i]->min.ep->recvBufSize, + mdevs[i]->mout.ep->endpoint, mdevs[i]->mout.cableId, mdevs[i]->mout.ep->bufSize); + } else if ( mdevs[i]->min.ep != NULL ) { + printk(KERN_INFO "usbmidi: /dev/midi%02d: in (ep:%02x cid:%2d bufsiz:%02d)\n", + dm, + mdevs[i]->min.ep->endpoint|USB_DIR_IN, mdevs[i]->min.cableId, mdevs[i]->min.ep->recvBufSize); + } else if ( mdevs[i]->mout.ep != NULL ) { + printk(KERN_INFO "usbmidi: /dev/midi%02d: out (ep:%02x cid:%2d bufsiz:%02d)\n", + dm, + mdevs[i]->mout.ep->endpoint, mdevs[i]->mout.cableId, mdevs[i]->mout.ep->bufSize); + } + } + + kfree(mdevs); + return 0; + + error_end: + if ( mdevs != NULL ) { + for ( i=0 ; i<devices ; i++ ) { + if ( mdevs[i] != NULL ) { + unregister_sound_midi( mdevs[i]->dev_midi ); + kfree(mdevs[i]); + } + } + kfree(mdevs); + } + + for ( i=0 ; i<15 ; i++ ) { + if ( mins[i] != NULL ) { + remove_midi_in_endpoint( mins[i] ); + } + if ( mouts[i] != NULL ) { + remove_midi_out_endpoint( mouts[i] ); + } + } + + return -ENOMEM; +} + +/* ------------------------------------------------------------------------- */ + +/** Attempt to scan YAMAHA's device descriptor and detect correct values of + * them. + * Return 0 on succes, negative on failure. + * Called by usb_midi_probe(); + **/ + +static int detect_yamaha_device( struct usb_device *d, + struct usb_interface *iface, unsigned int ifnum, + struct usb_midi_state *s) +{ + struct usb_host_interface *interface; + struct usb_midi_device *u; + unsigned char *buffer; + int bufSize; + int i; + int alts=-1; + int ret; + + if (le16_to_cpu(d->descriptor.idVendor) != USB_VENDOR_ID_YAMAHA) { + return -EINVAL; + } + + for ( i=0 ; i < iface->num_altsetting; i++ ) { + interface = iface->altsetting + i; + + if ( interface->desc.bInterfaceClass != 255 || + interface->desc.bInterfaceSubClass != 0 ) + continue; + alts = interface->desc.bAlternateSetting; + } + if ( alts == -1 ) { + return -EINVAL; + } + + printk(KERN_INFO "usb-midi: Found YAMAHA USB-MIDI device on dev %04x:%04x, iface %d\n", + le16_to_cpu(d->descriptor.idVendor), + le16_to_cpu(d->descriptor.idProduct), ifnum); + + i = d->actconfig - d->config; + buffer = d->rawdescriptors[i]; + bufSize = le16_to_cpu(d->actconfig->desc.wTotalLength); + + u = parse_descriptor( d, buffer, bufSize, ifnum, alts, 1); + if ( u == NULL ) { + return -EINVAL; + } + + ret = alloc_usb_midi_device( d, s, u ); + + kfree(u); + + return ret; +} + + +/** Scan table of known devices which are only partially compliant with + * the MIDIStreaming specification. + * Called by usb_midi_probe(); + * + **/ + +static int detect_vendor_specific_device( struct usb_device *d, unsigned int ifnum, struct usb_midi_state *s ) +{ + struct usb_midi_device *u; + int i; + int ret = -ENXIO; + + for ( i=0; i<VENDOR_SPECIFIC_USB_MIDI_DEVICES ; i++ ) { + u=&(usb_midi_devices[i]); + + if ( le16_to_cpu(d->descriptor.idVendor) != u->idVendor || + le16_to_cpu(d->descriptor.idProduct) != u->idProduct || + ifnum != u->interface ) + continue; + + ret = alloc_usb_midi_device( d, s, u ); + break; + } + + return ret; +} + + +/** Attempt to match any config of an interface to a MIDISTREAMING interface. + * Returns 0 on success, negative on failure. + * Called by usb_midi_probe(); + **/ +static int detect_midi_subclass(struct usb_device *d, + struct usb_interface *iface, unsigned int ifnum, + struct usb_midi_state *s) +{ + struct usb_host_interface *interface; + struct usb_midi_device *u; + unsigned char *buffer; + int bufSize; + int i; + int alts=-1; + int ret; + + for ( i=0 ; i < iface->num_altsetting; i++ ) { + interface = iface->altsetting + i; + + if ( interface->desc.bInterfaceClass != USB_CLASS_AUDIO || + interface->desc.bInterfaceSubClass != USB_SUBCLASS_MIDISTREAMING ) + continue; + alts = interface->desc.bAlternateSetting; + } + if ( alts == -1 ) { + return -EINVAL; + } + + printk(KERN_INFO "usb-midi: Found MIDISTREAMING on dev %04x:%04x, iface %d\n", + le16_to_cpu(d->descriptor.idVendor), + le16_to_cpu(d->descriptor.idProduct), ifnum); + + + /* From USB Spec v2.0, Section 9.5. + If the class or vendor specific descriptors use the same format + as standard descriptors (e.g., start with a length byte and + followed by a type byte), they must be returned interleaved with + standard descriptors in the configuration information returned by + a GetDescriptor(Configuration) request. In this case, the class + or vendor-specific descriptors must follow a related standard + descriptor they modify or extend. + */ + + i = d->actconfig - d->config; + buffer = d->rawdescriptors[i]; + bufSize = le16_to_cpu(d->actconfig->desc.wTotalLength); + + u = parse_descriptor( d, buffer, bufSize, ifnum, alts, 0); + if ( u == NULL ) { + return -EINVAL; + } + + ret = alloc_usb_midi_device( d, s, u ); + + kfree(u); + + return ret; +} + + +/** When user has requested a specific device, match it exactly. + * + * Uses uvendor, uproduct, uinterface, ualt, umin, umout and ucable. + * Called by usb_midi_probe(); + * + **/ +static int detect_by_hand(struct usb_device *d, unsigned int ifnum, struct usb_midi_state *s) +{ + struct usb_midi_device u; + + if ( le16_to_cpu(d->descriptor.idVendor) != uvendor || + le16_to_cpu(d->descriptor.idProduct) != uproduct || + ifnum != uinterface ) { + return -EINVAL; + } + + if ( ualt < 0 ) + ualt = -1; + + if ( umin < 0 || umin > 15 ) + umin = 0x01 | USB_DIR_IN; + if ( umout < 0 || umout > 15 ) + umout = 0x01; + if ( ucable < 0 || ucable > 15 ) + ucable = 0; + + u.deviceName = NULL; /* A flag for alloc_usb_midi_device to get device + name from device. */ + u.idVendor = uvendor; + u.idProduct = uproduct; + u.interface = uinterface; + u.altSetting = ualt; + + u.in[0].endpoint = umin; + u.in[0].cableId = (1<<ucable); + + u.out[0].endpoint = umout; + u.out[0].cableId = (1<<ucable); + + return alloc_usb_midi_device( d, s, &u ); +} + + + +/* ------------------------------------------------------------------------- */ + +static int usb_midi_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_midi_state *s; + struct usb_device *dev = interface_to_usbdev(intf); + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + s = (struct usb_midi_state *)kmalloc(sizeof(struct usb_midi_state), GFP_KERNEL); + if ( !s ) + return -ENOMEM; + + memset( s, 0, sizeof(struct usb_midi_state) ); + INIT_LIST_HEAD(&s->midiDevList); + INIT_LIST_HEAD(&s->inEndpointList); + INIT_LIST_HEAD(&s->outEndpointList); + s->usbdev = dev; + s->count = 0; + spin_lock_init(&s->lock); + + if ( + detect_by_hand( dev, ifnum, s ) && + detect_midi_subclass( dev, intf, ifnum, s ) && + detect_vendor_specific_device( dev, ifnum, s ) && + detect_yamaha_device( dev, intf, ifnum, s) ) { + kfree(s); + return -EIO; + } + + down(&open_sem); + list_add_tail(&s->mididev, &mididevs); + up(&open_sem); + + usb_set_intfdata (intf, s); + return 0; +} + + +static void usb_midi_disconnect(struct usb_interface *intf) +{ + struct usb_midi_state *s = usb_get_intfdata (intf); + struct usb_mididev *m; + + if ( !s ) + return; + + if ( s == (struct usb_midi_state *)-1 ) { + return; + } + if ( !s->usbdev ) { + return; + } + down(&open_sem); + list_del(&s->mididev); + INIT_LIST_HEAD(&s->mididev); + s->usbdev = NULL; + usb_set_intfdata (intf, NULL); + + list_for_each_entry(m, &s->midiDevList, list) { + wake_up(&(m->min.ep->wait)); + wake_up(&(m->mout.ep->wait)); + if ( m->dev_midi >= 0 ) { + unregister_sound_midi(m->dev_midi); + } + m->dev_midi = -1; + } + release_midi_device(s); + wake_up(&open_wait); +} + +/* we want to look at all devices by hand */ +static struct usb_device_id id_table[] = { + {.driver_info = 42}, + {} +}; + +static struct usb_driver usb_midi_driver = { + .owner = THIS_MODULE, + .name = "midi", + .probe = usb_midi_probe, + .disconnect = usb_midi_disconnect, + .id_table = id_table, +}; + +/* ------------------------------------------------------------------------- */ + +static int __init usb_midi_init(void) +{ + return usb_register(&usb_midi_driver); +} + +static void __exit usb_midi_exit(void) +{ + usb_deregister(&usb_midi_driver); +} + +module_init(usb_midi_init) ; +module_exit(usb_midi_exit) ; + +#ifdef HAVE_ALSA_SUPPORT +#define SNDRV_MAIN_OBJECT_FILE +#include "../../include/driver.h" +#include "../../include/control.h" +#include "../../include/info.h" +#include "../../include/cs46xx.h" + +/* ------------------------------------------------------------------------- */ + +static int snd_usbmidi_input_close(snd_rawmidi_substream_t * substream) +{ + return 0; +} + +static int snd_usbmidi_input_open(snd_rawmidi_substream_t * substream ) +{ + return 0; +} + +static void snd_usbmidi_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + return 0; +} + + +/* ------------------------------------------------------------------------- */ + +static int snd_usbmidi_output_close(snd_rawmidi_substream_t * substream) +{ + return 0; +} + +static int snd_usbmidi_output_open(snd_rawmidi_substream_t * substream) +{ + return 0; +} + +static void snd_usb_midi_output_trigger(snd_rawmidi_substream_t * substream, + int up) +{ + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static snd_rawmidi_ops_t snd_usbmidi_output = +{ + .open = snd_usbmidi_output_open, + .close = snd_usbmidi_output_close, + .trigger = snd_usbmidi_output_trigger, +}; +static snd_rawmidi_ops_t snd_usbmidi_input = +{ + .open = snd_usbmidi_input_open, + .close = snd_usbmidi_input_close, + .trigger = snd_usbmidi_input_trigger, +}; + +int snd_usbmidi_midi(cs46xx_t *chip, int device, snd_rawmidi_t **rrawmidi) +{ + snd_rawmidi_t *rmidi; + int err; + + if (rrawmidi) + *rrawmidi = NULL; + if ((err = snd_rawmidi_new(chip->card, "USB-MIDI", device, 1, 1, &rmidi)) < 0) + return err; + strcpy(rmidi->name, "USB-MIDI"); + + snd_rawmidi_set_ops( rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_usbmidi_output ); + snd_rawmidi_set_ops( rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usbmidi_input ); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + + rmidi->private_data = chip; + chip->rmidi = rmidi; + if (rrawmidi) + *rrawmidi = NULL; + + return 0; +} + +int snd_usbmidi_create( snd_card_t * card, + struct pci_dev * pci, + usbmidi_t ** rchip ) +{ + usbmidi_t *chip; + int err, idx; + snd_region_t *region; + static snd_device_opt_t ops = { + .dev_free = snd_usbmidi_dev_free, + }; + + *rchip = NULL; + chip = snd_magic_kcalloc( usbmidi_t, 0, GFP_KERNEL ); + if ( chip == NULL ) + return -ENOMEM; +} + +EXPORT_SYMBOL(snd_usbmidi_create); +EXPORT_SYMBOL(snd_usbmidi_midi); +#endif /* HAVE_ALSA_SUPPORT */ + diff --git a/drivers/usb/class/usb-midi.h b/drivers/usb/class/usb-midi.h new file mode 100644 index 0000000..358cdef --- /dev/null +++ b/drivers/usb/class/usb-midi.h @@ -0,0 +1,164 @@ +/* + usb-midi.h -- 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. + */ + +/* ------------------------------------------------------------------------- */ + +#ifndef _USB_MIDI_H_ +#define _USB_MIDI_H_ + +#ifndef USB_SUBCLASS_MIDISTREAMING +#define USB_SUBCLASS_MIDISTREAMING 3 +#endif + +/* ------------------------------------------------------------------------- */ +/* Roland MIDI Devices */ + +#define USB_VENDOR_ID_ROLAND 0x0582 +#define USBMIDI_ROLAND_UA100G 0x0000 +#define USBMIDI_ROLAND_MPU64 0x0002 +#define USBMIDI_ROLAND_SC8850 0x0003 +#define USBMIDI_ROLAND_SC8820 0x0007 +#define USBMIDI_ROLAND_UM2 0x0005 +#define USBMIDI_ROLAND_UM1 0x0009 +#define USBMIDI_ROLAND_PC300 0x0008 + +/* YAMAHA MIDI Devices */ +#define USB_VENDOR_ID_YAMAHA 0x0499 +#define USBMIDI_YAMAHA_MU1000 0x1001 + +/* Steinberg MIDI Devices */ +#define USB_VENDOR_ID_STEINBERG 0x0763 +#define USBMIDI_STEINBERG_USB2MIDI 0x1001 + +/* Mark of the Unicorn MIDI Devices */ +#define USB_VENDOR_ID_MOTU 0x07fd +#define USBMIDI_MOTU_FASTLANE 0x0001 + +/* ------------------------------------------------------------------------- */ +/* Supported devices */ + +struct usb_midi_endpoint { + int endpoint; + int cableId; /* if bit-n == 1 then cableId-n is enabled (n: 0 - 15) */ +}; + +struct usb_midi_device { + char *deviceName; + + u16 idVendor; + u16 idProduct; + int interface; + int altSetting; /* -1: auto detect */ + + struct usb_midi_endpoint in[15]; + struct usb_midi_endpoint out[15]; +}; + +static struct usb_midi_device usb_midi_devices[] = { + { /* Roland UM-1 */ + "Roland UM-1", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM1, 2, -1, + { { 0x81, 1 }, {-1, -1} }, + { { 0x01, 1,}, {-1, -1} }, + }, + + { /* Roland UM-2 */ + "Roland UM-2" , + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM2, 2, -1, + { { 0x81, 3 }, {-1, -1} }, + { { 0x01, 3,}, {-1, -1} }, + }, + +/** Next entry courtesy research by Michael Minn <michael@michaelminn.com> **/ + { /* Roland UA-100 */ + "Roland UA-100", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UA100G, 2, -1, + { { 0x82, 7 }, {-1, -1} }, /** cables 0,1 and 2 for SYSEX **/ + { { 0x02, 7 }, {-1, -1} }, + }, + +/** Next entry courtesy research by Michael Minn <michael@michaelminn.com> **/ + { /* Roland SC8850 */ + "Roland SC8850", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8850, 2, -1, + { { 0x81, 0x3f }, {-1, -1} }, + { { 0x01, 0x3f }, {-1, -1} }, + }, + + { /* Roland SC8820 */ + "Roland SC8820", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820, 2, -1, + { { 0x81, 0x13 }, {-1, -1} }, + { { 0x01, 0x13 }, {-1, -1} }, + }, + + { /* Roland SC8820 */ + "Roland SC8820", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820, 2, -1, + { { 0x81, 17 }, {-1, -1} }, + { { 0x01, 17 }, {-1, -1} }, + }, + + { /* YAMAHA MU1000 */ + "YAMAHA MU1000", + USB_VENDOR_ID_YAMAHA, USBMIDI_YAMAHA_MU1000, 0, -1, + { { 0x81, 1 }, {-1, -1} }, + { { 0x01, 15 }, {-1, -1} }, + }, + { /* Roland PC-300 */ + "Roland PC-300", + USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_PC300, 2, -1, + { { 0x81, 1 }, {-1, -1} }, + { { 0x01, 1 }, {-1, -1} }, + }, + { /* MOTU Fastlane USB */ + "MOTU Fastlane USB", + USB_VENDOR_ID_MOTU, USBMIDI_MOTU_FASTLANE, 1, 0, + { { 0x82, 3 }, {-1, -1} }, + { { 0x02, 3 }, {-1, -1} }, + } +}; + +#define VENDOR_SPECIFIC_USB_MIDI_DEVICES (sizeof(usb_midi_devices)/sizeof(struct usb_midi_device)) + +/* for Hot-Plugging */ + +static struct usb_device_id usb_midi_ids [] = { + { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), + .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING}, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM1 ) }, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UM2 ) }, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_UA100G ) }, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_PC300 ) }, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8850 ) }, + { USB_DEVICE( USB_VENDOR_ID_ROLAND, USBMIDI_ROLAND_SC8820 ) }, + { USB_DEVICE( USB_VENDOR_ID_YAMAHA, USBMIDI_YAMAHA_MU1000 ) }, + { USB_DEVICE( USB_VENDOR_ID_MOTU, USBMIDI_MOTU_FASTLANE ) }, +/* { USB_DEVICE( USB_VENDOR_ID_STEINBERG, USBMIDI_STEINBERG_USB2MIDI ) },*/ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_midi_ids); + +/* ------------------------------------------------------------------------- */ +#endif /* _USB_MIDI_H_ */ + + diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c new file mode 100644 index 0000000..bba22e9 --- /dev/null +++ b/drivers/usb/class/usblp.c @@ -0,0 +1,1214 @@ +/* + * usblp.c Version 0.13 + * + * Copyright (c) 1999 Michael Gee <michael@linuxspecific.com> + * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> + * Copyright (c) 2000 Randy Dunlap <rddunlap@osdl.org> + * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> + # Copyright (c) 2001 Pete Zaitcev <zaitcev@redhat.com> + # Copyright (c) 2001 David Paschal <paschal@rcsis.com> + * + * USB Printer Device Class driver for USB printers and printer cables + * + * Sponsored by SuSE + * + * ChangeLog: + * v0.1 - thorough cleaning, URBification, almost a rewrite + * v0.2 - some more cleanups + * v0.3 - cleaner again, waitqueue fixes + * v0.4 - fixes in unidirectional mode + * v0.5 - add DEVICE_ID string support + * v0.6 - never time out + * v0.7 - fixed bulk-IN read and poll (David Paschal) + * v0.8 - add devfs support + * v0.9 - fix unplug-while-open paths + * v0.10- remove sleep_on, fix error on oom (oliver@neukum.org) + * v0.11 - add proto_bias option (Pete Zaitcev) + * v0.12 - add hpoj.sourceforge.net ioctls (David Paschal) + * v0.13 - alloc space for statusbuf (<status> not on stack); + * use usb_buffer_alloc() for read buf & write buf; + */ + +/* + * 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/signal.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/lp.h> +#undef DEBUG +#include <linux/usb.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.13" +#define DRIVER_AUTHOR "Michael Gee, Pavel Machek, Vojtech Pavlik, Randy Dunlap, Pete Zaitcev, David Paschal" +#define DRIVER_DESC "USB Printer Device Class driver" + +#define USBLP_BUF_SIZE 8192 +#define USBLP_DEVICE_ID_SIZE 1024 + +/* ioctls: */ +#define LPGETSTATUS 0x060b /* same as in drivers/char/lp.c */ +#define IOCNR_GET_DEVICE_ID 1 +#define IOCNR_GET_PROTOCOLS 2 +#define IOCNR_SET_PROTOCOL 3 +#define IOCNR_HP_SET_CHANNEL 4 +#define IOCNR_GET_BUS_ADDRESS 5 +#define IOCNR_GET_VID_PID 6 +#define IOCNR_SOFT_RESET 7 +/* Get device_id string: */ +#define LPIOC_GET_DEVICE_ID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_DEVICE_ID, len) +/* The following ioctls were added for http://hpoj.sourceforge.net: */ +/* Get two-int array: + * [0]=current protocol (1=7/1/1, 2=7/1/2, 3=7/1/3), + * [1]=supported protocol mask (mask&(1<<n)!=0 means 7/1/n supported): */ +#define LPIOC_GET_PROTOCOLS(len) _IOC(_IOC_READ, 'P', IOCNR_GET_PROTOCOLS, len) +/* Set protocol (arg: 1=7/1/1, 2=7/1/2, 3=7/1/3): */ +#define LPIOC_SET_PROTOCOL _IOC(_IOC_WRITE, 'P', IOCNR_SET_PROTOCOL, 0) +/* Set channel number (HP Vendor-specific command): */ +#define LPIOC_HP_SET_CHANNEL _IOC(_IOC_WRITE, 'P', IOCNR_HP_SET_CHANNEL, 0) +/* Get two-int array: [0]=bus number, [1]=device address: */ +#define LPIOC_GET_BUS_ADDRESS(len) _IOC(_IOC_READ, 'P', IOCNR_GET_BUS_ADDRESS, len) +/* Get two-int array: [0]=vendor ID, [1]=product ID: */ +#define LPIOC_GET_VID_PID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_VID_PID, len) +/* Perform class specific soft reset */ +#define LPIOC_SOFT_RESET _IOC(_IOC_NONE, 'P', IOCNR_SOFT_RESET, 0); + +/* + * A DEVICE_ID string may include the printer's serial number. + * It should end with a semi-colon (';'). + * An example from an HP 970C DeskJet printer is (this is one long string, + * with the serial number changed): +MFG:HEWLETT-PACKARD;MDL:DESKJET 970C;CMD:MLC,PCL,PML;CLASS:PRINTER;DESCRIPTION:Hewlett-Packard DeskJet 970C;SERN:US970CSEPROF;VSTATUS:$HB0$NC0,ff,DN,IDLE,CUT,K1,C0,DP,NR,KP000,CP027;VP:0800,FL,B0;VJ: ; + */ + +/* + * USB Printer Requests + */ + +#define USBLP_REQ_GET_ID 0x00 +#define USBLP_REQ_GET_STATUS 0x01 +#define USBLP_REQ_RESET 0x02 +#define USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST 0x00 /* HP Vendor-specific */ + +#define USBLP_MINORS 16 +#define USBLP_MINOR_BASE 0 + +#define USBLP_WRITE_TIMEOUT (5000) /* 5 seconds */ + +#define USBLP_FIRST_PROTOCOL 1 +#define USBLP_LAST_PROTOCOL 3 +#define USBLP_MAX_PROTOCOLS (USBLP_LAST_PROTOCOL+1) + +/* + * some arbitrary status buffer size; + * need a status buffer that is allocated via kmalloc(), not on stack + */ +#define STATUS_BUF_SIZE 8 + +struct usblp { + struct usb_device *dev; /* USB device */ + struct semaphore sem; /* locks this struct, especially "dev" */ + char *writebuf; /* write transfer_buffer */ + char *readbuf; /* read transfer_buffer */ + char *statusbuf; /* status transfer_buffer */ + struct urb *readurb, *writeurb; /* The urbs */ + wait_queue_head_t wait; /* Zzzzz ... */ + int readcount; /* Counter for reads */ + int ifnum; /* Interface number */ + struct usb_interface *intf; /* The interface */ + /* Alternate-setting numbers and endpoints for each protocol + * (7/1/{index=1,2,3}) that the device supports: */ + struct { + int alt_setting; + struct usb_endpoint_descriptor *epwrite; + struct usb_endpoint_descriptor *epread; + } protocol[USBLP_MAX_PROTOCOLS]; + int current_protocol; + int minor; /* minor number of device */ + int wcomplete; /* writing is completed */ + int rcomplete; /* reading is completed */ + unsigned int quirks; /* quirks flags */ + unsigned char used; /* True if open */ + unsigned char present; /* True if not disconnected */ + unsigned char bidir; /* interface is bidirectional */ + unsigned char *device_id_string; /* IEEE 1284 DEVICE ID string (ptr) */ + /* first 2 bytes are (big-endian) length */ +}; + +#ifdef DEBUG +static void usblp_dump(struct usblp *usblp) { + int p; + + dbg("usblp=0x%p", usblp); + dbg("dev=0x%p", usblp->dev); + dbg("present=%d", usblp->present); + dbg("readbuf=0x%p", usblp->readbuf); + dbg("writebuf=0x%p", usblp->writebuf); + dbg("readurb=0x%p", usblp->readurb); + dbg("writeurb=0x%p", usblp->writeurb); + dbg("readcount=%d", usblp->readcount); + dbg("ifnum=%d", usblp->ifnum); + for (p = USBLP_FIRST_PROTOCOL; p <= USBLP_LAST_PROTOCOL; p++) { + dbg("protocol[%d].alt_setting=%d", p, usblp->protocol[p].alt_setting); + dbg("protocol[%d].epwrite=%p", p, usblp->protocol[p].epwrite); + dbg("protocol[%d].epread=%p", p, usblp->protocol[p].epread); + } + dbg("current_protocol=%d", usblp->current_protocol); + dbg("minor=%d", usblp->minor); + dbg("wcomplete=%d", usblp->wcomplete); + dbg("rcomplete=%d", usblp->rcomplete); + dbg("quirks=%d", usblp->quirks); + dbg("used=%d", usblp->used); + dbg("bidir=%d", usblp->bidir); + dbg("device_id_string=\"%s\"", + usblp->device_id_string ? + usblp->device_id_string + 2 : + (unsigned char *)"(null)"); +} +#endif + +/* Quirks: various printer quirks are handled by this table & its flags. */ + +struct quirk_printer_struct { + __u16 vendorId; + __u16 productId; + unsigned int quirks; +}; + +#define USBLP_QUIRK_BIDIR 0x1 /* reports bidir but requires unidirectional mode (no INs/reads) */ +#define USBLP_QUIRK_USB_INIT 0x2 /* needs vendor USB init string */ + +static struct quirk_printer_struct quirk_printers[] = { + { 0x03f0, 0x0004, USBLP_QUIRK_BIDIR }, /* HP DeskJet 895C */ + { 0x03f0, 0x0104, USBLP_QUIRK_BIDIR }, /* HP DeskJet 880C */ + { 0x03f0, 0x0204, USBLP_QUIRK_BIDIR }, /* HP DeskJet 815C */ + { 0x03f0, 0x0304, USBLP_QUIRK_BIDIR }, /* HP DeskJet 810C/812C */ + { 0x03f0, 0x0404, USBLP_QUIRK_BIDIR }, /* HP DeskJet 830C */ + { 0x03f0, 0x0504, USBLP_QUIRK_BIDIR }, /* HP DeskJet 885C */ + { 0x03f0, 0x0604, USBLP_QUIRK_BIDIR }, /* HP DeskJet 840C */ + { 0x03f0, 0x0804, USBLP_QUIRK_BIDIR }, /* HP DeskJet 816C */ + { 0x03f0, 0x1104, USBLP_QUIRK_BIDIR }, /* HP Deskjet 959C */ + { 0x0409, 0xefbe, USBLP_QUIRK_BIDIR }, /* NEC Picty900 (HP OEM) */ + { 0x0409, 0xbef4, USBLP_QUIRK_BIDIR }, /* NEC Picty760 (HP OEM) */ + { 0x0409, 0xf0be, USBLP_QUIRK_BIDIR }, /* NEC Picty920 (HP OEM) */ + { 0x0409, 0xf1be, USBLP_QUIRK_BIDIR }, /* NEC Picty800 (HP OEM) */ + { 0, 0 } +}; + +static int usblp_select_alts(struct usblp *usblp); +static int usblp_set_protocol(struct usblp *usblp, int protocol); +static int usblp_cache_device_id_string(struct usblp *usblp); + +/* forward reference to make our lives easier */ +static struct usb_driver usblp_driver; +static DECLARE_MUTEX(usblp_sem); /* locks the existence of usblp's */ + +/* + * Functions for usblp control messages. + */ + +static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int dir, int recip, int value, void *buf, int len) +{ + int retval; + int index = usblp->ifnum; + + /* High byte has the interface index. + Low byte has the alternate setting. + */ + if ((request == USBLP_REQ_GET_ID) && (type == USB_TYPE_CLASS)) { + index = (usblp->ifnum<<8)|usblp->protocol[usblp->current_protocol].alt_setting; + } + + retval = usb_control_msg(usblp->dev, + dir ? usb_rcvctrlpipe(usblp->dev, 0) : usb_sndctrlpipe(usblp->dev, 0), + request, type | dir | recip, value, index, buf, len, USBLP_WRITE_TIMEOUT); + dbg("usblp_control_msg: rq: 0x%02x dir: %d recip: %d value: %d idx: %d len: %#x result: %d", + request, !!dir, recip, value, index, len, retval); + return retval < 0 ? retval : 0; +} + +#define usblp_read_status(usblp, status)\ + usblp_ctrl_msg(usblp, USBLP_REQ_GET_STATUS, USB_TYPE_CLASS, USB_DIR_IN, USB_RECIP_INTERFACE, 0, status, 1) +#define usblp_get_id(usblp, config, id, maxlen)\ + usblp_ctrl_msg(usblp, USBLP_REQ_GET_ID, USB_TYPE_CLASS, USB_DIR_IN, USB_RECIP_INTERFACE, config, id, maxlen) +#define usblp_reset(usblp)\ + usblp_ctrl_msg(usblp, USBLP_REQ_RESET, USB_TYPE_CLASS, USB_DIR_OUT, USB_RECIP_OTHER, 0, NULL, 0) + +#define usblp_hp_channel_change_request(usblp, channel, buffer) \ + usblp_ctrl_msg(usblp, USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST, USB_TYPE_VENDOR, USB_DIR_IN, USB_RECIP_INTERFACE, channel, buffer, 1) + +/* + * See the description for usblp_select_alts() below for the usage + * explanation. Look into your /proc/bus/usb/devices and dmesg in + * case of any trouble. + */ +static int proto_bias = -1; + +/* + * URB callback. + */ + +static void usblp_bulk_read(struct urb *urb, struct pt_regs *regs) +{ + struct usblp *usblp = urb->context; + + if (!usblp || !usblp->dev || !usblp->used || !usblp->present) + return; + + if (unlikely(urb->status)) + warn("usblp%d: nonzero read/write bulk status received: %d", + usblp->minor, urb->status); + usblp->rcomplete = 1; + wake_up_interruptible(&usblp->wait); +} + +static void usblp_bulk_write(struct urb *urb, struct pt_regs *regs) +{ + struct usblp *usblp = urb->context; + + if (!usblp || !usblp->dev || !usblp->used || !usblp->present) + return; + + if (unlikely(urb->status)) + warn("usblp%d: nonzero read/write bulk status received: %d", + usblp->minor, urb->status); + usblp->wcomplete = 1; + wake_up_interruptible(&usblp->wait); +} + +/* + * Get and print printer errors. + */ + +static char *usblp_messages[] = { "ok", "out of paper", "off-line", "on fire" }; + +static int usblp_check_status(struct usblp *usblp, int err) +{ + unsigned char status, newerr = 0; + int error; + + error = usblp_read_status (usblp, usblp->statusbuf); + if (error < 0) { + err("usblp%d: error %d reading printer status", + usblp->minor, error); + return 0; + } + + status = *usblp->statusbuf; + + if (~status & LP_PERRORP) + newerr = 3; + if (status & LP_POUTPA) + newerr = 1; + if (~status & LP_PSELECD) + newerr = 2; + + if (newerr != err) + info("usblp%d: %s", usblp->minor, usblp_messages[newerr]); + + return newerr; +} + +/* + * File op functions. + */ + +static int usblp_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct usblp *usblp; + struct usb_interface *intf; + int retval; + + if (minor < 0) + return -ENODEV; + + down (&usblp_sem); + + retval = -ENODEV; + intf = usb_find_interface(&usblp_driver, minor); + if (!intf) { + goto out; + } + usblp = usb_get_intfdata (intf); + if (!usblp || !usblp->dev || !usblp->present) + goto out; + + retval = -EBUSY; + if (usblp->used) + goto out; + + /* + * TODO: need to implement LP_ABORTOPEN + O_NONBLOCK as in drivers/char/lp.c ??? + * This is #if 0-ed because we *don't* want to fail an open + * just because the printer is off-line. + */ +#if 0 + if ((retval = usblp_check_status(usblp, 0))) { + retval = retval > 1 ? -EIO : -ENOSPC; + goto out; + } +#else + retval = 0; +#endif + + usblp->used = 1; + file->private_data = usblp; + + usblp->writeurb->transfer_buffer_length = 0; + usblp->wcomplete = 1; /* we begin writeable */ + usblp->rcomplete = 0; + + if (usblp->bidir) { + usblp->readcount = 0; + usblp->readurb->dev = usblp->dev; + if (usb_submit_urb(usblp->readurb, GFP_KERNEL) < 0) { + retval = -EIO; + usblp->used = 0; + file->private_data = NULL; + } + } +out: + up (&usblp_sem); + return retval; +} + +static void usblp_cleanup (struct usblp *usblp) +{ + info("usblp%d: removed", usblp->minor); + + kfree (usblp->device_id_string); + kfree (usblp->statusbuf); + usb_free_urb(usblp->writeurb); + usb_free_urb(usblp->readurb); + kfree (usblp); +} + +static void usblp_unlink_urbs(struct usblp *usblp) +{ + usb_kill_urb(usblp->writeurb); + if (usblp->bidir) + usb_kill_urb(usblp->readurb); +} + +static int usblp_release(struct inode *inode, struct file *file) +{ + struct usblp *usblp = file->private_data; + + down (&usblp_sem); + usblp->used = 0; + if (usblp->present) { + usblp_unlink_urbs(usblp); + } else /* finish cleanup from disconnect */ + usblp_cleanup (usblp); + up (&usblp_sem); + return 0; +} + +/* No kernel lock - fine */ +static unsigned int usblp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct usblp *usblp = file->private_data; + poll_wait(file, &usblp->wait, wait); + return ((!usblp->bidir || !usblp->rcomplete) ? 0 : POLLIN | POLLRDNORM) + | (!usblp->wcomplete ? 0 : POLLOUT | POLLWRNORM); +} + +static int usblp_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usblp *usblp = file->private_data; + int length, err, i; + unsigned char newChannel; + int status; + int twoints[2]; + int retval = 0; + + down (&usblp->sem); + if (!usblp->present) { + retval = -ENODEV; + goto done; + } + + dbg("usblp_ioctl: cmd=0x%x (%c nr=%d len=%d dir=%d)", cmd, _IOC_TYPE(cmd), + _IOC_NR(cmd), _IOC_SIZE(cmd), _IOC_DIR(cmd) ); + + if (_IOC_TYPE(cmd) == 'P') /* new-style ioctl number */ + + switch (_IOC_NR(cmd)) { + + case IOCNR_GET_DEVICE_ID: /* get the DEVICE_ID string */ + if (_IOC_DIR(cmd) != _IOC_READ) { + retval = -EINVAL; + goto done; + } + + length = usblp_cache_device_id_string(usblp); + if (length < 0) { + retval = length; + goto done; + } + if (length > _IOC_SIZE(cmd)) + length = _IOC_SIZE(cmd); /* truncate */ + + if (copy_to_user((void __user *) arg, + usblp->device_id_string, + (unsigned long) length)) { + retval = -EFAULT; + goto done; + } + + break; + + case IOCNR_GET_PROTOCOLS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = usblp->current_protocol; + twoints[1] = 0; + for (i = USBLP_FIRST_PROTOCOL; + i <= USBLP_LAST_PROTOCOL; i++) { + if (usblp->protocol[i].alt_setting >= 0) + twoints[1] |= (1<<i); + } + + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } + + break; + + case IOCNR_SET_PROTOCOL: + if (_IOC_DIR(cmd) != _IOC_WRITE) { + retval = -EINVAL; + goto done; + } + +#ifdef DEBUG + if (arg == -10) { + usblp_dump(usblp); + break; + } +#endif + + usblp_unlink_urbs(usblp); + retval = usblp_set_protocol(usblp, arg); + if (retval < 0) { + usblp_set_protocol(usblp, + usblp->current_protocol); + } + break; + + case IOCNR_HP_SET_CHANNEL: + if (_IOC_DIR(cmd) != _IOC_WRITE || + le16_to_cpu(usblp->dev->descriptor.idVendor) != 0x03F0 || + usblp->quirks & USBLP_QUIRK_BIDIR) { + retval = -EINVAL; + goto done; + } + + err = usblp_hp_channel_change_request(usblp, + arg, &newChannel); + if (err < 0) { + err("usblp%d: error = %d setting " + "HP channel", + usblp->minor, err); + retval = -EIO; + goto done; + } + + dbg("usblp%d requested/got HP channel %ld/%d", + usblp->minor, arg, newChannel); + break; + + case IOCNR_GET_BUS_ADDRESS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = usblp->dev->bus->busnum; + twoints[1] = usblp->dev->devnum; + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } + + dbg("usblp%d is bus=%d, device=%d", + usblp->minor, twoints[0], twoints[1]); + break; + + case IOCNR_GET_VID_PID: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = le16_to_cpu(usblp->dev->descriptor.idVendor); + twoints[1] = le16_to_cpu(usblp->dev->descriptor.idProduct); + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } + + dbg("usblp%d is VID=0x%4.4X, PID=0x%4.4X", + usblp->minor, twoints[0], twoints[1]); + break; + + case IOCNR_SOFT_RESET: + if (_IOC_DIR(cmd) != _IOC_NONE) { + retval = -EINVAL; + goto done; + } + retval = usblp_reset(usblp); + break; + default: + retval = -ENOTTY; + } + else /* old-style ioctl value */ + switch (cmd) { + + case LPGETSTATUS: + if (usblp_read_status(usblp, usblp->statusbuf)) { + err("usblp%d: failed reading printer status", usblp->minor); + retval = -EIO; + goto done; + } + status = *usblp->statusbuf; + if (copy_to_user ((void __user *)arg, &status, sizeof(int))) + retval = -EFAULT; + break; + + default: + retval = -ENOTTY; + } + +done: + up (&usblp->sem); + return retval; +} + +static ssize_t usblp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + DECLARE_WAITQUEUE(wait, current); + struct usblp *usblp = file->private_data; + int timeout, err = 0, transfer_length = 0; + size_t writecount = 0; + + while (writecount < count) { + if (!usblp->wcomplete) { + barrier(); + if (file->f_flags & O_NONBLOCK) { + writecount += transfer_length; + return writecount ? writecount : -EAGAIN; + } + + timeout = USBLP_WRITE_TIMEOUT; + add_wait_queue(&usblp->wait, &wait); + while ( 1==1 ) { + + if (signal_pending(current)) { + remove_wait_queue(&usblp->wait, &wait); + return writecount ? writecount : -EINTR; + } + set_current_state(TASK_INTERRUPTIBLE); + if (timeout && !usblp->wcomplete) { + timeout = schedule_timeout(timeout); + } else { + set_current_state(TASK_RUNNING); + break; + } + } + remove_wait_queue(&usblp->wait, &wait); + } + + down (&usblp->sem); + if (!usblp->present) { + up (&usblp->sem); + return -ENODEV; + } + + if (usblp->writeurb->status != 0) { + if (usblp->quirks & USBLP_QUIRK_BIDIR) { + if (!usblp->wcomplete) + err("usblp%d: error %d writing to printer", + usblp->minor, usblp->writeurb->status); + err = usblp->writeurb->status; + } else + err = usblp_check_status(usblp, err); + up (&usblp->sem); + + /* if the fault was due to disconnect, let khubd's + * call to usblp_disconnect() grab usblp->sem ... + */ + schedule (); + continue; + } + + /* We must increment writecount here, and not at the + * end of the loop. Otherwise, the final loop iteration may + * be skipped, leading to incomplete printer output. + */ + writecount += transfer_length; + if (writecount == count) { + up(&usblp->sem); + break; + } + + transfer_length=(count - writecount); + if (transfer_length > USBLP_BUF_SIZE) + transfer_length = USBLP_BUF_SIZE; + + usblp->writeurb->transfer_buffer_length = transfer_length; + + if (copy_from_user(usblp->writeurb->transfer_buffer, + buffer + writecount, transfer_length)) { + up(&usblp->sem); + return writecount ? writecount : -EFAULT; + } + + usblp->writeurb->dev = usblp->dev; + usblp->wcomplete = 0; + err = usb_submit_urb(usblp->writeurb, GFP_KERNEL); + if (err) { + if (err != -ENOMEM) + count = -EIO; + else + count = writecount ? writecount : -ENOMEM; + up (&usblp->sem); + break; + } + up (&usblp->sem); + } + + return count; +} + +static ssize_t usblp_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct usblp *usblp = file->private_data; + DECLARE_WAITQUEUE(wait, current); + + if (!usblp->bidir) + return -EINVAL; + + down (&usblp->sem); + if (!usblp->present) { + count = -ENODEV; + goto done; + } + + if (!usblp->rcomplete) { + barrier(); + + if (file->f_flags & O_NONBLOCK) { + count = -EAGAIN; + goto done; + } + + add_wait_queue(&usblp->wait, &wait); + while (1==1) { + if (signal_pending(current)) { + count = -EINTR; + remove_wait_queue(&usblp->wait, &wait); + goto done; + } + up (&usblp->sem); + set_current_state(TASK_INTERRUPTIBLE); + if (!usblp->rcomplete) { + schedule(); + } else { + set_current_state(TASK_RUNNING); + break; + } + down (&usblp->sem); + } + remove_wait_queue(&usblp->wait, &wait); + } + + if (!usblp->present) { + count = -ENODEV; + goto done; + } + + if (usblp->readurb->status) { + err("usblp%d: error %d reading from printer", + usblp->minor, usblp->readurb->status); + usblp->readurb->dev = usblp->dev; + usblp->readcount = 0; + usblp->rcomplete = 0; + if (usb_submit_urb(usblp->readurb, GFP_KERNEL) < 0) + dbg("error submitting urb"); + count = -EIO; + goto done; + } + + count = count < usblp->readurb->actual_length - usblp->readcount ? + count : usblp->readurb->actual_length - usblp->readcount; + + if (copy_to_user(buffer, usblp->readurb->transfer_buffer + usblp->readcount, count)) { + count = -EFAULT; + goto done; + } + + if ((usblp->readcount += count) == usblp->readurb->actual_length) { + usblp->readcount = 0; + usblp->readurb->dev = usblp->dev; + usblp->rcomplete = 0; + if (usb_submit_urb(usblp->readurb, GFP_KERNEL)) { + count = -EIO; + goto done; + } + } + +done: + up (&usblp->sem); + return count; +} + +/* + * Checks for printers that have quirks, such as requiring unidirectional + * communication but reporting bidirectional; currently some HP printers + * have this flaw (HP 810, 880, 895, etc.), or needing an init string + * sent at each open (like some Epsons). + * Returns 1 if found, 0 if not found. + * + * HP recommended that we use the bidirectional interface but + * don't attempt any bulk IN transfers from the IN endpoint. + * Here's some more detail on the problem: + * The problem is not that it isn't bidirectional though. The problem + * is that if you request a device ID, or status information, while + * the buffers are full, the return data will end up in the print data + * buffer. For example if you make sure you never request the device ID + * while you are sending print data, and you don't try to query the + * printer status every couple of milliseconds, you will probably be OK. + */ +static unsigned int usblp_quirks (__u16 vendor, __u16 product) +{ + int i; + + for (i = 0; quirk_printers[i].vendorId; i++) { + if (vendor == quirk_printers[i].vendorId && + product == quirk_printers[i].productId) + return quirk_printers[i].quirks; + } + return 0; +} + +static struct file_operations usblp_fops = { + .owner = THIS_MODULE, + .read = usblp_read, + .write = usblp_write, + .poll = usblp_poll, + .ioctl = usblp_ioctl, + .open = usblp_open, + .release = usblp_release, +}; + +static struct usb_class_driver usblp_class = { + .name = "usb/lp%d", + .fops = &usblp_fops, + .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, + .minor_base = USBLP_MINOR_BASE, +}; + +static int usblp_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev (intf); + struct usblp *usblp = NULL; + int protocol; + int retval; + + /* Malloc and start initializing usblp structure so we can use it + * directly. */ + if (!(usblp = kmalloc(sizeof(struct usblp), GFP_KERNEL))) { + err("out of memory for usblp"); + goto abort; + } + memset(usblp, 0, sizeof(struct usblp)); + usblp->dev = dev; + init_MUTEX (&usblp->sem); + init_waitqueue_head(&usblp->wait); + usblp->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + usblp->intf = intf; + + usblp->writeurb = usb_alloc_urb(0, GFP_KERNEL); + if (!usblp->writeurb) { + err("out of memory"); + goto abort; + } + usblp->readurb = usb_alloc_urb(0, GFP_KERNEL); + if (!usblp->readurb) { + err("out of memory"); + goto abort; + } + + /* Malloc device ID string buffer to the largest expected length, + * since we can re-query it on an ioctl and a dynamic string + * could change in length. */ + if (!(usblp->device_id_string = kmalloc(USBLP_DEVICE_ID_SIZE, GFP_KERNEL))) { + err("out of memory for device_id_string"); + goto abort; + } + + usblp->writebuf = usblp->readbuf = NULL; + usblp->writeurb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + usblp->readurb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + /* Malloc write & read buffers. We somewhat wastefully + * malloc both regardless of bidirectionality, because the + * alternate setting can be changed later via an ioctl. */ + if (!(usblp->writebuf = usb_buffer_alloc(dev, USBLP_BUF_SIZE, + GFP_KERNEL, &usblp->writeurb->transfer_dma))) { + err("out of memory for write buf"); + goto abort; + } + if (!(usblp->readbuf = usb_buffer_alloc(dev, USBLP_BUF_SIZE, + GFP_KERNEL, &usblp->readurb->transfer_dma))) { + err("out of memory for read buf"); + goto abort; + } + + /* Allocate buffer for printer status */ + usblp->statusbuf = kmalloc(STATUS_BUF_SIZE, GFP_KERNEL); + if (!usblp->statusbuf) { + err("out of memory for statusbuf"); + goto abort; + } + + /* Lookup quirks for this printer. */ + usblp->quirks = usblp_quirks( + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + /* Analyze and pick initial alternate settings and endpoints. */ + protocol = usblp_select_alts(usblp); + if (protocol < 0) { + dbg("incompatible printer-class device 0x%4.4X/0x%4.4X", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + goto abort; + } + + /* Setup the selected alternate setting and endpoints. */ + if (usblp_set_protocol(usblp, protocol) < 0) + goto abort; + + /* Retrieve and store the device ID string. */ + usblp_cache_device_id_string(usblp); + +#ifdef DEBUG + usblp_check_status(usblp, 0); +#endif + + info("usblp%d: USB %sdirectional printer dev %d " + "if %d alt %d proto %d vid 0x%4.4X pid 0x%4.4X", + usblp->minor, usblp->bidir ? "Bi" : "Uni", dev->devnum, + usblp->ifnum, + usblp->protocol[usblp->current_protocol].alt_setting, + usblp->current_protocol, + le16_to_cpu(usblp->dev->descriptor.idVendor), + le16_to_cpu(usblp->dev->descriptor.idProduct)); + + usb_set_intfdata (intf, usblp); + + usblp->present = 1; + + retval = usb_register_dev(intf, &usblp_class); + if (retval) { + err("Not able to get a minor for this device."); + goto abort_intfdata; + } + usblp->minor = intf->minor; + + return 0; + +abort_intfdata: + usb_set_intfdata (intf, NULL); +abort: + if (usblp) { + if (usblp->writebuf) + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->writebuf, usblp->writeurb->transfer_dma); + if (usblp->readbuf) + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->readbuf, usblp->writeurb->transfer_dma); + kfree(usblp->statusbuf); + kfree(usblp->device_id_string); + usb_free_urb(usblp->writeurb); + usb_free_urb(usblp->readurb); + kfree(usblp); + } + return -EIO; +} + +/* + * We are a "new" style driver with usb_device_id table, + * but our requirements are too intricate for simple match to handle. + * + * The "proto_bias" option may be used to specify the preferred protocol + * for all USB printers (1=7/1/1, 2=7/1/2, 3=7/1/3). If the device + * supports the preferred protocol, then we bind to it. + * + * The best interface for us is 7/1/2, because it is compatible + * with a stream of characters. If we find it, we bind to it. + * + * Note that the people from hpoj.sourceforge.net need to be able to + * bind to 7/1/3 (MLC/1284.4), so we provide them ioctls for this purpose. + * + * Failing 7/1/2, we look for 7/1/3, even though it's probably not + * stream-compatible, because this matches the behaviour of the old code. + * + * If nothing else, we bind to 7/1/1 - the unidirectional interface. + */ +static int usblp_select_alts(struct usblp *usblp) +{ + struct usb_interface *if_alt; + struct usb_host_interface *ifd; + struct usb_endpoint_descriptor *epd, *epwrite, *epread; + int p, i, e; + + if_alt = usblp->intf; + + for (p = 0; p < USBLP_MAX_PROTOCOLS; p++) + usblp->protocol[p].alt_setting = -1; + + /* Find out what we have. */ + for (i = 0; i < if_alt->num_altsetting; i++) { + ifd = &if_alt->altsetting[i]; + + if (ifd->desc.bInterfaceClass != 7 || ifd->desc.bInterfaceSubClass != 1) + continue; + + if (ifd->desc.bInterfaceProtocol < USBLP_FIRST_PROTOCOL || + ifd->desc.bInterfaceProtocol > USBLP_LAST_PROTOCOL) + continue; + + /* Look for bulk OUT and IN endpoints. */ + epwrite = epread = NULL; + for (e = 0; e < ifd->desc.bNumEndpoints; e++) { + epd = &ifd->endpoint[e].desc; + + if ((epd->bmAttributes&USB_ENDPOINT_XFERTYPE_MASK)!= + USB_ENDPOINT_XFER_BULK) + continue; + + if (!(epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK)) { + if (!epwrite) + epwrite = epd; + + } else { + if (!epread) + epread = epd; + } + } + + /* Ignore buggy hardware without the right endpoints. */ + if (!epwrite || (ifd->desc.bInterfaceProtocol > 1 && !epread)) + continue; + + /* Turn off reads for 7/1/1 (unidirectional) interfaces + * and buggy bidirectional printers. */ + if (ifd->desc.bInterfaceProtocol == 1) { + epread = NULL; + } else if (usblp->quirks & USBLP_QUIRK_BIDIR) { + info("Disabling reads from problem bidirectional " + "printer on usblp%d", usblp->minor); + epread = NULL; + } + + usblp->protocol[ifd->desc.bInterfaceProtocol].alt_setting = + ifd->desc.bAlternateSetting; + usblp->protocol[ifd->desc.bInterfaceProtocol].epwrite = epwrite; + usblp->protocol[ifd->desc.bInterfaceProtocol].epread = epread; + } + + /* If our requested protocol is supported, then use it. */ + if (proto_bias >= USBLP_FIRST_PROTOCOL && + proto_bias <= USBLP_LAST_PROTOCOL && + usblp->protocol[proto_bias].alt_setting != -1) + return proto_bias; + + /* Ordering is important here. */ + if (usblp->protocol[2].alt_setting != -1) + return 2; + if (usblp->protocol[1].alt_setting != -1) + return 1; + if (usblp->protocol[3].alt_setting != -1) + return 3; + + /* If nothing is available, then don't bind to this device. */ + return -1; +} + +static int usblp_set_protocol(struct usblp *usblp, int protocol) +{ + int r, alts; + + if (protocol < USBLP_FIRST_PROTOCOL || protocol > USBLP_LAST_PROTOCOL) + return -EINVAL; + + alts = usblp->protocol[protocol].alt_setting; + if (alts < 0) + return -EINVAL; + r = usb_set_interface(usblp->dev, usblp->ifnum, alts); + if (r < 0) { + err("can't set desired altsetting %d on interface %d", + alts, usblp->ifnum); + return r; + } + + usb_fill_bulk_urb(usblp->writeurb, usblp->dev, + usb_sndbulkpipe(usblp->dev, + usblp->protocol[protocol].epwrite->bEndpointAddress), + usblp->writebuf, 0, + usblp_bulk_write, usblp); + + usblp->bidir = (usblp->protocol[protocol].epread != NULL); + if (usblp->bidir) + usb_fill_bulk_urb(usblp->readurb, usblp->dev, + usb_rcvbulkpipe(usblp->dev, + usblp->protocol[protocol].epread->bEndpointAddress), + usblp->readbuf, USBLP_BUF_SIZE, + usblp_bulk_read, usblp); + + usblp->current_protocol = protocol; + dbg("usblp%d set protocol %d", usblp->minor, protocol); + return 0; +} + +/* Retrieves and caches device ID string. + * Returns length, including length bytes but not null terminator. + * On error, returns a negative errno value. */ +static int usblp_cache_device_id_string(struct usblp *usblp) +{ + int err, length; + + err = usblp_get_id(usblp, 0, usblp->device_id_string, USBLP_DEVICE_ID_SIZE - 1); + if (err < 0) { + dbg("usblp%d: error = %d reading IEEE-1284 Device ID string", + usblp->minor, err); + usblp->device_id_string[0] = usblp->device_id_string[1] = '\0'; + return -EIO; + } + + /* First two bytes are length in big-endian. + * They count themselves, and we copy them into + * the user's buffer. */ + length = be16_to_cpu(*((__be16 *)usblp->device_id_string)); + if (length < 2) + length = 2; + else if (length >= USBLP_DEVICE_ID_SIZE) + length = USBLP_DEVICE_ID_SIZE - 1; + usblp->device_id_string[length] = '\0'; + + dbg("usblp%d Device ID string [len=%d]=\"%s\"", + usblp->minor, length, &usblp->device_id_string[2]); + + return length; +} + +static void usblp_disconnect(struct usb_interface *intf) +{ + struct usblp *usblp = usb_get_intfdata (intf); + + usb_deregister_dev(intf, &usblp_class); + + if (!usblp || !usblp->dev) { + err("bogus disconnect"); + BUG (); + } + + down (&usblp_sem); + down (&usblp->sem); + usblp->present = 0; + usb_set_intfdata (intf, NULL); + + usblp_unlink_urbs(usblp); + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->writebuf, usblp->writeurb->transfer_dma); + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->readbuf, usblp->readurb->transfer_dma); + up (&usblp->sem); + + if (!usblp->used) + usblp_cleanup (usblp); + up (&usblp_sem); +} + +static struct usb_device_id usblp_ids [] = { + { USB_DEVICE_INFO(7, 1, 1) }, + { USB_DEVICE_INFO(7, 1, 2) }, + { USB_DEVICE_INFO(7, 1, 3) }, + { USB_INTERFACE_INFO(7, 1, 1) }, + { USB_INTERFACE_INFO(7, 1, 2) }, + { USB_INTERFACE_INFO(7, 1, 3) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usblp_ids); + +static struct usb_driver usblp_driver = { + .owner = THIS_MODULE, + .name = "usblp", + .probe = usblp_probe, + .disconnect = usblp_disconnect, + .id_table = usblp_ids, +}; + +static int __init usblp_init(void) +{ + int retval; + retval = usb_register(&usblp_driver); + if (retval) + goto out; + info(DRIVER_VERSION ": " DRIVER_DESC); +out: + return retval; +} + +static void __exit usblp_exit(void) +{ + usb_deregister(&usblp_driver); +} + +module_init(usblp_init); +module_exit(usblp_exit); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +module_param(proto_bias, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(proto_bias, "Favourite protocol number"); +MODULE_LICENSE("GPL"); |