aboutsummaryrefslogtreecommitdiff
path: root/sound/core
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core')
-rw-r--r--sound/core/Kconfig133
-rw-r--r--sound/core/Makefile33
-rw-r--r--sound/core/control.c1375
-rw-r--r--sound/core/control_compat.c412
-rw-r--r--sound/core/device.c240
-rw-r--r--sound/core/hwdep.c524
-rw-r--r--sound/core/hwdep_compat.c77
-rw-r--r--sound/core/info.c989
-rw-r--r--sound/core/info_oss.c137
-rw-r--r--sound/core/init.c887
-rw-r--r--sound/core/isadma.c103
-rw-r--r--sound/core/memalloc.c663
-rw-r--r--sound/core/memory.c306
-rw-r--r--sound/core/misc.c76
-rw-r--r--sound/core/oss/Makefile12
-rw-r--r--sound/core/oss/copy.c87
-rw-r--r--sound/core/oss/io.c134
-rw-r--r--sound/core/oss/linear.c158
-rw-r--r--sound/core/oss/mixer_oss.c1340
-rw-r--r--sound/core/oss/mulaw.c308
-rw-r--r--sound/core/oss/pcm_oss.c2530
-rw-r--r--sound/core/oss/pcm_plugin.c921
-rw-r--r--sound/core/oss/pcm_plugin.h250
-rw-r--r--sound/core/oss/plugin_ops.h536
-rw-r--r--sound/core/oss/rate.c378
-rw-r--r--sound/core/oss/route.c519
-rw-r--r--sound/core/pcm.c1074
-rw-r--r--sound/core/pcm_compat.c513
-rw-r--r--sound/core/pcm_lib.c2612
-rw-r--r--sound/core/pcm_memory.c363
-rw-r--r--sound/core/pcm_misc.c481
-rw-r--r--sound/core/pcm_native.c3364
-rw-r--r--sound/core/pcm_timer.c161
-rw-r--r--sound/core/rawmidi.c1680
-rw-r--r--sound/core/rawmidi_compat.c120
-rw-r--r--sound/core/rtctimer.c188
-rw-r--r--sound/core/seq/Makefile44
-rw-r--r--sound/core/seq/instr/Makefile23
-rw-r--r--sound/core/seq/instr/ainstr_fm.c156
-rw-r--r--sound/core/seq/instr/ainstr_gf1.c358
-rw-r--r--sound/core/seq/instr/ainstr_iw.c622
-rw-r--r--sound/core/seq/instr/ainstr_simple.c215
-rw-r--r--sound/core/seq/oss/Makefile10
-rw-r--r--sound/core/seq/oss/seq_oss.c317
-rw-r--r--sound/core/seq/oss/seq_oss_device.h198
-rw-r--r--sound/core/seq/oss/seq_oss_event.c447
-rw-r--r--sound/core/seq/oss/seq_oss_event.h112
-rw-r--r--sound/core/seq/oss/seq_oss_init.c555
-rw-r--r--sound/core/seq/oss/seq_oss_ioctl.c209
-rw-r--r--sound/core/seq/oss/seq_oss_midi.c710
-rw-r--r--sound/core/seq/oss/seq_oss_midi.h49
-rw-r--r--sound/core/seq/oss/seq_oss_readq.c234
-rw-r--r--sound/core/seq/oss/seq_oss_readq.h56
-rw-r--r--sound/core/seq/oss/seq_oss_rw.c216
-rw-r--r--sound/core/seq/oss/seq_oss_synth.c659
-rw-r--r--sound/core/seq/oss/seq_oss_synth.h49
-rw-r--r--sound/core/seq/oss/seq_oss_timer.c283
-rw-r--r--sound/core/seq/oss/seq_oss_timer.h70
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.c170
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.h50
-rw-r--r--sound/core/seq/seq.c147
-rw-r--r--sound/core/seq/seq_clientmgr.c2503
-rw-r--r--sound/core/seq/seq_clientmgr.h104
-rw-r--r--sound/core/seq/seq_compat.c137
-rw-r--r--sound/core/seq/seq_device.c575
-rw-r--r--sound/core/seq/seq_dummy.c273
-rw-r--r--sound/core/seq/seq_fifo.c264
-rw-r--r--sound/core/seq/seq_fifo.h72
-rw-r--r--sound/core/seq/seq_info.c75
-rw-r--r--sound/core/seq/seq_info.h36
-rw-r--r--sound/core/seq/seq_instr.c653
-rw-r--r--sound/core/seq/seq_lock.c48
-rw-r--r--sound/core/seq/seq_lock.h33
-rw-r--r--sound/core/seq/seq_memory.c510
-rw-r--r--sound/core/seq/seq_memory.h104
-rw-r--r--sound/core/seq/seq_midi.c489
-rw-r--r--sound/core/seq/seq_midi_emul.c735
-rw-r--r--sound/core/seq/seq_midi_event.c539
-rw-r--r--sound/core/seq/seq_ports.c674
-rw-r--r--sound/core/seq/seq_ports.h128
-rw-r--r--sound/core/seq/seq_prioq.c449
-rw-r--r--sound/core/seq/seq_prioq.h62
-rw-r--r--sound/core/seq/seq_queue.c783
-rw-r--r--sound/core/seq/seq_queue.h140
-rw-r--r--sound/core/seq/seq_system.c190
-rw-r--r--sound/core/seq/seq_system.h46
-rw-r--r--sound/core/seq/seq_timer.c435
-rw-r--r--sound/core/seq/seq_timer.h141
-rw-r--r--sound/core/seq/seq_virmidi.c551
-rw-r--r--sound/core/sgbuf.c111
-rw-r--r--sound/core/sound.c491
-rw-r--r--sound/core/sound_oss.c250
-rw-r--r--sound/core/timer.c1901
-rw-r--r--sound/core/timer_compat.c119
-rw-r--r--sound/core/wrappers.c50
95 files changed, 43314 insertions, 0 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
new file mode 100644
index 00000000000..d1e800b9866
--- /dev/null
+++ b/sound/core/Kconfig
@@ -0,0 +1,133 @@
+# ALSA soundcard-configuration
+config SND_TIMER
+ tristate
+ depends on SND
+
+config SND_PCM
+ tristate
+ select SND_TIMER
+ depends on SND
+
+config SND_HWDEP
+ tristate
+ depends on SND
+
+config SND_RAWMIDI
+ tristate
+ depends on SND
+
+config SND_SEQUENCER
+ tristate "Sequencer support"
+ depends on SND
+ select SND_TIMER
+ help
+ Say Y or M to enable MIDI sequencer and router support. This
+ feature allows routing and enqueueing of MIDI events. Events
+ can be processed at a given time.
+
+ Many programs require this feature, so you should enable it
+ unless you know what you're doing.
+
+config SND_SEQ_DUMMY
+ tristate "Sequencer dummy client"
+ depends on SND_SEQUENCER
+ help
+ Say Y here to enable the dummy sequencer client. This client
+ is a simple MIDI-through client: all normal input events are
+ redirected to the output port immediately.
+
+ You don't need this unless you want to connect many MIDI
+ devices or applications together.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-seq-dummy.
+
+config SND_OSSEMUL
+ bool
+ depends on SND
+
+config SND_MIXER_OSS
+ tristate "OSS Mixer API"
+ depends on SND
+ select SND_OSSEMUL
+ help
+ To enable OSS mixer API emulation (/dev/mixer*), say Y here
+ and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
+
+ Many programs still use the OSS API, so say Y.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-mixer-oss.
+
+config SND_PCM_OSS
+ tristate "OSS PCM (digital audio) API"
+ depends on SND
+ select SND_OSSEMUL
+ select SND_PCM
+ help
+ To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y
+ here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
+
+ Many programs still use the OSS API, so say Y.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-pcm-oss.
+
+config SND_SEQUENCER_OSS
+ bool "OSS Sequencer API"
+ depends on SND && SND_SEQUENCER
+ select SND_OSSEMUL
+ help
+ Say Y here to enable OSS sequencer emulation (both
+ /dev/sequencer and /dev/music interfaces).
+
+ Many programs still use the OSS API, so say Y.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-seq-oss.
+
+config SND_RTCTIMER
+ tristate "RTC Timer support"
+ depends on SND && RTC
+ select SND_TIMER
+ help
+ Say Y here to enable RTC timer support for ALSA. ALSA uses
+ the RTC timer as a precise timing source and maps the RTC
+ timer to ALSA's timer interface. The ALSA sequencer code also
+ can use this timing source.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-rtctimer.
+
+config SND_VERBOSE_PRINTK
+ bool "Verbose printk"
+ depends on SND
+ help
+ Say Y here to enable verbose log messages. These messages
+ will help to identify source file and position containing
+ printed messages.
+
+ You don't need this unless you're debugging ALSA.
+
+config SND_DEBUG
+ bool "Debug"
+ depends on SND
+ help
+ Say Y here to enable ALSA debug code.
+
+config SND_DEBUG_MEMORY
+ bool "Debug memory"
+ depends on SND_DEBUG
+ help
+ Say Y here to enable debugging of memory allocations.
+
+config SND_DEBUG_DETECT
+ bool "Debug detection"
+ depends on SND_DEBUG
+ help
+ Say Y here to enable extra-verbose log messages printed when
+ detecting devices.
+
+config SND_GENERIC_PM
+ bool
+ depends on SND
diff --git a/sound/core/Makefile b/sound/core/Makefile
new file mode 100644
index 00000000000..764ac184b22
--- /dev/null
+++ b/sound/core/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-objs := sound.o init.o memory.o info.o control.o misc.o \
+ device.o wrappers.o
+ifeq ($(CONFIG_ISA),y)
+snd-objs += isadma.o
+endif
+ifeq ($(CONFIG_SND_OSSEMUL),y)
+snd-objs += sound_oss.o info_oss.o
+endif
+
+snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
+ pcm_memory.o
+
+snd-page-alloc-objs := memalloc.o sgbuf.o
+
+snd-rawmidi-objs := rawmidi.o
+snd-timer-objs := timer.o
+snd-rtctimer-objs := rtctimer.o
+snd-hwdep-objs := hwdep.o
+
+obj-$(CONFIG_SND) += snd.o
+obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
+obj-$(CONFIG_SND_TIMER) += snd-timer.o
+obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o
+obj-$(CONFIG_SND_PCM) += snd-pcm.o snd-page-alloc.o
+obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
+
+obj-$(CONFIG_SND_OSSEMUL) += oss/
+obj-$(CONFIG_SND_SEQUENCER) += seq/
diff --git a/sound/core/control.c b/sound/core/control.c
new file mode 100644
index 00000000000..f4ea6bff1dd
--- /dev/null
+++ b/sound/core/control.c
@@ -0,0 +1,1375 @@
+/*
+ * Routines for driver control interface
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/threads.h>
+#include <linux/interrupt.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/control.h>
+
+/* max number of user-defined controls */
+#define MAX_USER_CONTROLS 32
+
+typedef struct _snd_kctl_ioctl {
+ struct list_head list; /* list of all ioctls */
+ snd_kctl_ioctl_func_t fioctl;
+} snd_kctl_ioctl_t;
+
+#define snd_kctl_ioctl(n) list_entry(n, snd_kctl_ioctl_t, list)
+
+static DECLARE_RWSEM(snd_ioctl_rwsem);
+static LIST_HEAD(snd_control_ioctls);
+#ifdef CONFIG_COMPAT
+static LIST_HEAD(snd_control_compat_ioctls);
+#endif
+
+static int snd_ctl_open(struct inode *inode, struct file *file)
+{
+ int cardnum = SNDRV_MINOR_CARD(iminor(inode));
+ unsigned long flags;
+ snd_card_t *card;
+ snd_ctl_file_t *ctl;
+ int err;
+
+ card = snd_cards[cardnum];
+ if (!card) {
+ err = -ENODEV;
+ goto __error1;
+ }
+ err = snd_card_file_add(card, file);
+ if (err < 0) {
+ err = -ENODEV;
+ goto __error1;
+ }
+ if (!try_module_get(card->module)) {
+ err = -EFAULT;
+ goto __error2;
+ }
+ ctl = kcalloc(1, sizeof(*ctl), GFP_KERNEL);
+ if (ctl == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ INIT_LIST_HEAD(&ctl->events);
+ init_waitqueue_head(&ctl->change_sleep);
+ spin_lock_init(&ctl->read_lock);
+ ctl->card = card;
+ ctl->pid = current->pid;
+ file->private_data = ctl;
+ write_lock_irqsave(&card->ctl_files_rwlock, flags);
+ list_add_tail(&ctl->list, &card->ctl_files);
+ write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
+ return 0;
+
+ __error:
+ module_put(card->module);
+ __error2:
+ snd_card_file_remove(card, file);
+ __error1:
+ return err;
+}
+
+static void snd_ctl_empty_read_queue(snd_ctl_file_t * ctl)
+{
+ snd_kctl_event_t *cread;
+
+ spin_lock(&ctl->read_lock);
+ while (!list_empty(&ctl->events)) {
+ cread = snd_kctl_event(ctl->events.next);
+ list_del(&cread->list);
+ kfree(cread);
+ }
+ spin_unlock(&ctl->read_lock);
+}
+
+static int snd_ctl_release(struct inode *inode, struct file *file)
+{
+ unsigned long flags;
+ struct list_head *list;
+ snd_card_t *card;
+ snd_ctl_file_t *ctl;
+ snd_kcontrol_t *control;
+ unsigned int idx;
+
+ ctl = file->private_data;
+ fasync_helper(-1, file, 0, &ctl->fasync);
+ file->private_data = NULL;
+ card = ctl->card;
+ write_lock_irqsave(&card->ctl_files_rwlock, flags);
+ list_del(&ctl->list);
+ write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
+ down_write(&card->controls_rwsem);
+ list_for_each(list, &card->controls) {
+ control = snd_kcontrol(list);
+ for (idx = 0; idx < control->count; idx++)
+ if (control->vd[idx].owner == ctl)
+ control->vd[idx].owner = NULL;
+ }
+ up_write(&card->controls_rwsem);
+ snd_ctl_empty_read_queue(ctl);
+ kfree(ctl);
+ module_put(card->module);
+ snd_card_file_remove(card, file);
+ return 0;
+}
+
+void snd_ctl_notify(snd_card_t *card, unsigned int mask, snd_ctl_elem_id_t *id)
+{
+ unsigned long flags;
+ struct list_head *flist;
+ snd_ctl_file_t *ctl;
+ snd_kctl_event_t *ev;
+
+ snd_runtime_check(card != NULL && id != NULL, return);
+ read_lock(&card->ctl_files_rwlock);
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+ card->mixer_oss_change_count++;
+#endif
+ list_for_each(flist, &card->ctl_files) {
+ struct list_head *elist;
+ ctl = snd_ctl_file(flist);
+ if (!ctl->subscribed)
+ continue;
+ spin_lock_irqsave(&ctl->read_lock, flags);
+ list_for_each(elist, &ctl->events) {
+ ev = snd_kctl_event(elist);
+ if (ev->id.numid == id->numid) {
+ ev->mask |= mask;
+ goto _found;
+ }
+ }
+ ev = kcalloc(1, sizeof(*ev), GFP_ATOMIC);
+ if (ev) {
+ ev->id = *id;
+ ev->mask = mask;
+ list_add_tail(&ev->list, &ctl->events);
+ } else {
+ snd_printk(KERN_ERR "No memory available to allocate event\n");
+ }
+ _found:
+ wake_up(&ctl->change_sleep);
+ spin_unlock_irqrestore(&ctl->read_lock, flags);
+ kill_fasync(&ctl->fasync, SIGIO, POLL_IN);
+ }
+ read_unlock(&card->ctl_files_rwlock);
+}
+
+/**
+ * snd_ctl_new - create a control instance from the template
+ * @control: the control template
+ * @access: the default control access
+ *
+ * Allocates a new snd_kcontrol_t instance and copies the given template
+ * to the new instance. It does not copy volatile data (access).
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control, unsigned int access)
+{
+ snd_kcontrol_t *kctl;
+ unsigned int idx;
+
+ snd_runtime_check(control != NULL, return NULL);
+ snd_runtime_check(control->count > 0, return NULL);
+ kctl = kcalloc(1, sizeof(*kctl) + sizeof(snd_kcontrol_volatile_t) * control->count, GFP_KERNEL);
+ if (kctl == NULL)
+ return NULL;
+ *kctl = *control;
+ for (idx = 0; idx < kctl->count; idx++)
+ kctl->vd[idx].access = access;
+ return kctl;
+}
+
+/**
+ * snd_ctl_new1 - create a control instance from the template
+ * @ncontrol: the initialization record
+ * @private_data: the private data to set
+ *
+ * Allocates a new snd_kcontrol_t instance and initialize from the given
+ * template. When the access field of ncontrol is 0, it's assumed as
+ * READWRITE access. When the count field is 0, it's assumes as one.
+ *
+ * Returns the pointer of the newly generated instance, or NULL on failure.
+ */
+snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data)
+{
+ snd_kcontrol_t kctl;
+ unsigned int access;
+
+ snd_runtime_check(ncontrol != NULL, return NULL);
+ snd_assert(ncontrol->info != NULL, return NULL);
+ memset(&kctl, 0, sizeof(kctl));
+ kctl.id.iface = ncontrol->iface;
+ kctl.id.device = ncontrol->device;
+ kctl.id.subdevice = ncontrol->subdevice;
+ if (ncontrol->name)
+ strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name));
+ kctl.id.index = ncontrol->index;
+ kctl.count = ncontrol->count ? ncontrol->count : 1;
+ access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
+ (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
+ SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT));
+ kctl.info = ncontrol->info;
+ kctl.get = ncontrol->get;
+ kctl.put = ncontrol->put;
+ kctl.private_value = ncontrol->private_value;
+ kctl.private_data = private_data;
+ return snd_ctl_new(&kctl, access);
+}
+
+/**
+ * snd_ctl_free_one - release the control instance
+ * @kcontrol: the control instance
+ *
+ * Releases the control instance created via snd_ctl_new()
+ * or snd_ctl_new1().
+ * Don't call this after the control was added to the card.
+ */
+void snd_ctl_free_one(snd_kcontrol_t * kcontrol)
+{
+ if (kcontrol) {
+ if (kcontrol->private_free)
+ kcontrol->private_free(kcontrol);
+ kfree(kcontrol);
+ }
+}
+
+static unsigned int snd_ctl_hole_check(snd_card_t * card,
+ unsigned int count)
+{
+ struct list_head *list;
+ snd_kcontrol_t *kctl;
+
+ list_for_each(list, &card->controls) {
+ kctl = snd_kcontrol(list);
+ if ((kctl->id.numid <= card->last_numid &&
+ kctl->id.numid + kctl->count > card->last_numid) ||
+ (kctl->id.numid <= card->last_numid + count - 1 &&
+ kctl->id.numid + kctl->count > card->last_numid + count - 1))
+ return card->last_numid = kctl->id.numid + kctl->count - 1;
+ }
+ return card->last_numid;
+}
+
+static int snd_ctl_find_hole(snd_card_t * card, unsigned int count)
+{
+ unsigned int last_numid, iter = 100000;
+
+ last_numid = card->last_numid;
+ while (last_numid != snd_ctl_hole_check(card, count)) {
+ if (--iter == 0) {
+ /* this situation is very unlikely */
+ snd_printk(KERN_ERR "unable to allocate new control numid\n");
+ return -ENOMEM;
+ }
+ last_numid = card->last_numid;
+ }
+ return 0;
+}
+
+/**
+ * snd_ctl_add - add the control instance to the card
+ * @card: the card instance
+ * @kcontrol: the control instance to add
+ *
+ * Adds the control instance created via snd_ctl_new() or
+ * snd_ctl_new1() to the given card. Assigns also an unique
+ * numid used for fast search.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ *
+ * It frees automatically the control which cannot be added.
+ */
+int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
+{
+ snd_ctl_elem_id_t id;
+ unsigned int idx;
+
+ snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
+ snd_assert(kcontrol->info != NULL, return -EINVAL);
+ id = kcontrol->id;
+ down_write(&card->controls_rwsem);
+ if (snd_ctl_find_id(card, &id)) {
+ up_write(&card->controls_rwsem);
+ snd_ctl_free_one(kcontrol);
+ snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",
+ id.iface,
+ id.device,
+ id.subdevice,
+ id.name,
+ id.index);
+ return -EBUSY;
+ }
+ if (snd_ctl_find_hole(card, kcontrol->count) < 0) {
+ up_write(&card->controls_rwsem);
+ snd_ctl_free_one(kcontrol);
+ return -ENOMEM;
+ }
+ list_add_tail(&kcontrol->list, &card->controls);
+ card->controls_count += kcontrol->count;
+ kcontrol->id.numid = card->last_numid + 1;
+ card->last_numid += kcontrol->count;
+ up_write(&card->controls_rwsem);
+ for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
+ return 0;
+}
+
+/**
+ * snd_ctl_remove - remove the control from the card and release it
+ * @card: the card instance
+ * @kcontrol: the control instance to remove
+ *
+ * Removes the control from the card and then releases the instance.
+ * You don't need to call snd_ctl_free_one(). You must be in
+ * the write lock - down_write(&card->controls_rwsem).
+ *
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
+{
+ snd_ctl_elem_id_t id;
+ unsigned int idx;
+
+ snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
+ list_del(&kcontrol->list);
+ card->controls_count -= kcontrol->count;
+ id = kcontrol->id;
+ for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id);
+ snd_ctl_free_one(kcontrol);
+ return 0;
+}
+
+/**
+ * snd_ctl_remove_id - remove the control of the given id and release it
+ * @card: the card instance
+ * @id: the control id to remove
+ *
+ * Finds the control instance with the given id, removes it from the
+ * card list and releases it.
+ *
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id)
+{
+ snd_kcontrol_t *kctl;
+ int ret;
+
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, id);
+ if (kctl == NULL) {
+ up_write(&card->controls_rwsem);
+ return -ENOENT;
+ }
+ ret = snd_ctl_remove(card, kctl);
+ up_write(&card->controls_rwsem);
+ return ret;
+}
+
+/**
+ * snd_ctl_remove_unlocked_id - remove the unlocked control of the given id and release it
+ * @file: active control handle
+ * @id: the control id to remove
+ *
+ * Finds the control instance with the given id, removes it from the
+ * card list and releases it.
+ *
+ * Returns 0 if successful, or a negative error code on failure.
+ */
+static int snd_ctl_remove_unlocked_id(snd_ctl_file_t * file, snd_ctl_elem_id_t *id)
+{
+ snd_card_t *card = file->card;
+ snd_kcontrol_t *kctl;
+ int idx, ret;
+
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, id);
+ if (kctl == NULL) {
+ up_write(&card->controls_rwsem);
+ return -ENOENT;
+ }
+ for (idx = 0; idx < kctl->count; idx++)
+ if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) {
+ up_write(&card->controls_rwsem);
+ return -EBUSY;
+ }
+ ret = snd_ctl_remove(card, kctl);
+ up_write(&card->controls_rwsem);
+ return ret;
+}
+
+/**
+ * snd_ctl_rename_id - replace the id of a control on the card
+ * @card: the card instance
+ * @src_id: the old id
+ * @dst_id: the new id
+ *
+ * Finds the control with the old id from the card, and replaces the
+ * id with the new one.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_ctl_rename_id(snd_card_t * card, snd_ctl_elem_id_t *src_id, snd_ctl_elem_id_t *dst_id)
+{
+ snd_kcontrol_t *kctl;
+
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, src_id);
+ if (kctl == NULL) {
+ up_write(&card->controls_rwsem);
+ return -ENOENT;
+ }
+ kctl->id = *dst_id;
+ kctl->id.numid = card->last_numid + 1;
+ card->last_numid += kctl->count;
+ up_write(&card->controls_rwsem);
+ return 0;
+}
+
+/**
+ * snd_ctl_find_numid - find the control instance with the given number-id
+ * @card: the card instance
+ * @numid: the number-id to search
+ *
+ * Finds the control instance with the given number-id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ *
+ * The caller must down card->controls_rwsem before calling this function
+ * (if the race condition can happen).
+ */
+snd_kcontrol_t *snd_ctl_find_numid(snd_card_t * card, unsigned int numid)
+{
+ struct list_head *list;
+ snd_kcontrol_t *kctl;
+
+ snd_runtime_check(card != NULL && numid != 0, return NULL);
+ list_for_each(list, &card->controls) {
+ kctl = snd_kcontrol(list);
+ if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
+ return kctl;
+ }
+ return NULL;
+}
+
+/**
+ * snd_ctl_find_id - find the control instance with the given id
+ * @card: the card instance
+ * @id: the id to search
+ *
+ * Finds the control instance with the given id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ *
+ * The caller must down card->controls_rwsem before calling this function
+ * (if the race condition can happen).
+ */
+snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id)
+{
+ struct list_head *list;
+ snd_kcontrol_t *kctl;
+
+ snd_runtime_check(card != NULL && id != NULL, return NULL);
+ if (id->numid != 0)
+ return snd_ctl_find_numid(card, id->numid);
+ list_for_each(list, &card->controls) {
+ kctl = snd_kcontrol(list);
+ if (kctl->id.iface != id->iface)
+ continue;
+ if (kctl->id.device != id->device)
+ continue;
+ if (kctl->id.subdevice != id->subdevice)
+ continue;
+ if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
+ continue;
+ if (kctl->id.index > id->index)
+ continue;
+ if (kctl->id.index + kctl->count <= id->index)
+ continue;
+ return kctl;
+ }
+ return NULL;
+}
+
+static int snd_ctl_card_info(snd_card_t * card, snd_ctl_file_t * ctl,
+ unsigned int cmd, void __user *arg)
+{
+ snd_ctl_card_info_t *info;
+
+ info = kcalloc(1, sizeof(*info), GFP_KERNEL);
+ if (! info)
+ return -ENOMEM;
+ down_read(&snd_ioctl_rwsem);
+ info->card = card->number;
+ strlcpy(info->id, card->id, sizeof(info->id));
+ strlcpy(info->driver, card->driver, sizeof(info->driver));
+ strlcpy(info->name, card->shortname, sizeof(info->name));
+ strlcpy(info->longname, card->longname, sizeof(info->longname));
+ strlcpy(info->mixername, card->mixername, sizeof(info->mixername));
+ strlcpy(info->components, card->components, sizeof(info->components));
+ up_read(&snd_ioctl_rwsem);
+ if (copy_to_user(arg, info, sizeof(snd_ctl_card_info_t))) {
+ kfree(info);
+ return -EFAULT;
+ }
+ kfree(info);
+ return 0;
+}
+
+static int snd_ctl_elem_list(snd_card_t *card, snd_ctl_elem_list_t __user *_list)
+{
+ struct list_head *plist;
+ snd_ctl_elem_list_t list;
+ snd_kcontrol_t *kctl;
+ snd_ctl_elem_id_t *dst, *id;
+ unsigned int offset, space, first, jidx;
+
+ if (copy_from_user(&list, _list, sizeof(list)))
+ return -EFAULT;
+ offset = list.offset;
+ space = list.space;
+ first = 0;
+ /* try limit maximum space */
+ if (space > 16384)
+ return -ENOMEM;
+ if (space > 0) {
+ /* allocate temporary buffer for atomic operation */
+ dst = vmalloc(space * sizeof(snd_ctl_elem_id_t));
+ if (dst == NULL)
+ return -ENOMEM;
+ down_read(&card->controls_rwsem);
+ list.count = card->controls_count;
+ plist = card->controls.next;
+ while (plist != &card->controls) {
+ if (offset == 0)
+ break;
+ kctl = snd_kcontrol(plist);
+ if (offset < kctl->count)
+ break;
+ offset -= kctl->count;
+ plist = plist->next;
+ }
+ list.used = 0;
+ id = dst;
+ while (space > 0 && plist != &card->controls) {
+ kctl = snd_kcontrol(plist);
+ for (jidx = offset; space > 0 && jidx < kctl->count; jidx++) {
+ snd_ctl_build_ioff(id, kctl, jidx);
+ id++;
+ space--;
+ list.used++;
+ }
+ plist = plist->next;
+ offset = 0;
+ }
+ up_read(&card->controls_rwsem);
+ if (list.used > 0 && copy_to_user(list.pids, dst, list.used * sizeof(snd_ctl_elem_id_t))) {
+ vfree(dst);
+ return -EFAULT;
+ }
+ vfree(dst);
+ } else {
+ down_read(&card->controls_rwsem);
+ list.count = card->controls_count;
+ up_read(&card->controls_rwsem);
+ }
+ if (copy_to_user(_list, &list, sizeof(list)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_ctl_elem_info(snd_ctl_file_t *ctl, snd_ctl_elem_info_t *info)
+{
+ snd_card_t *card = ctl->card;
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_volatile_t *vd;
+ unsigned int index_offset;
+ int result;
+
+ down_read(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, &info->id);
+ if (kctl == NULL) {
+ up_read(&card->controls_rwsem);
+ return -ENOENT;
+ }
+#ifdef CONFIG_SND_DEBUG
+ info->access = 0;
+#endif
+ result = kctl->info(kctl, info);
+ if (result >= 0) {
+ snd_assert(info->access == 0, );
+ index_offset = snd_ctl_get_ioff(kctl, &info->id);
+ vd = &kctl->vd[index_offset];
+ snd_ctl_build_ioff(&info->id, kctl, index_offset);
+ info->access = vd->access;
+ if (vd->owner) {
+ info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK;
+ if (vd->owner == ctl)
+ info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER;
+ info->owner = vd->owner_pid;
+ } else {
+ info->owner = -1;
+ }
+ }
+ up_read(&card->controls_rwsem);
+ return result;
+}
+
+static int snd_ctl_elem_info_user(snd_ctl_file_t *ctl, snd_ctl_elem_info_t __user *_info)
+{
+ snd_ctl_elem_info_t info;
+ int result;
+
+ if (copy_from_user(&info, _info, sizeof(info)))
+ return -EFAULT;
+ result = snd_ctl_elem_info(ctl, &info);
+ if (result >= 0)
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return result;
+}
+
+int snd_ctl_elem_read(snd_card_t *card, snd_ctl_elem_value_t *control)
+{
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_volatile_t *vd;
+ unsigned int index_offset;
+ int result, indirect;
+
+ down_read(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, &control->id);
+ if (kctl == NULL) {
+ result = -ENOENT;
+ } else {
+ index_offset = snd_ctl_get_ioff(kctl, &control->id);
+ vd = &kctl->vd[index_offset];
+ indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0;
+ if (control->indirect != indirect) {
+ result = -EACCES;
+ } else {
+ if ((vd->access & SNDRV_CTL_ELEM_ACCESS_READ) && kctl->get != NULL) {
+ snd_ctl_build_ioff(&control->id, kctl, index_offset);
+ result = kctl->get(kctl, control);
+ } else {
+ result = -EPERM;
+ }
+ }
+ }
+ up_read(&card->controls_rwsem);
+ return result;
+}
+
+static int snd_ctl_elem_read_user(snd_card_t *card, snd_ctl_elem_value_t __user *_control)
+{
+ snd_ctl_elem_value_t *control;
+ int result;
+
+ control = kmalloc(sizeof(*control), GFP_KERNEL);
+ if (control == NULL)
+ return -ENOMEM;
+ if (copy_from_user(control, _control, sizeof(*control))) {
+ kfree(control);
+ return -EFAULT;
+ }
+ result = snd_ctl_elem_read(card, control);
+ if (result >= 0)
+ if (copy_to_user(_control, control, sizeof(*control)))
+ result = -EFAULT;
+ kfree(control);
+ return result;
+}
+
+int snd_ctl_elem_write(snd_card_t *card, snd_ctl_file_t *file, snd_ctl_elem_value_t *control)
+{
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_volatile_t *vd;
+ unsigned int index_offset;
+ int result, indirect;
+
+ down_read(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, &control->id);
+ if (kctl == NULL) {
+ result = -ENOENT;
+ } else {
+ index_offset = snd_ctl_get_ioff(kctl, &control->id);
+ vd = &kctl->vd[index_offset];
+ indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0;
+ if (control->indirect != indirect) {
+ result = -EACCES;
+ } else {
+ if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) ||
+ kctl->put == NULL ||
+ (file && vd->owner != NULL && vd->owner != file)) {
+ result = -EPERM;
+ } else {
+ snd_ctl_build_ioff(&control->id, kctl, index_offset);
+ result = kctl->put(kctl, control);
+ }
+ if (result > 0) {
+ up_read(&card->controls_rwsem);
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &control->id);
+ return 0;
+ }
+ }
+ }
+ up_read(&card->controls_rwsem);
+ return result;
+}
+
+static int snd_ctl_elem_write_user(snd_ctl_file_t *file, snd_ctl_elem_value_t __user *_control)
+{
+ snd_ctl_elem_value_t *control;
+ int result;
+
+ control = kmalloc(sizeof(*control), GFP_KERNEL);
+ if (control == NULL)
+ return -ENOMEM;
+ if (copy_from_user(control, _control, sizeof(*control))) {
+ kfree(control);
+ return -EFAULT;
+ }
+ result = snd_ctl_elem_write(file->card, file, control);
+ if (result >= 0)
+ if (copy_to_user(_control, control, sizeof(*control)))
+ result = -EFAULT;
+ kfree(control);
+ return result;
+}
+
+static int snd_ctl_elem_lock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+ snd_card_t *card = file->card;
+ snd_ctl_elem_id_t id;
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_volatile_t *vd;
+ int result;
+
+ if (copy_from_user(&id, _id, sizeof(id)))
+ return -EFAULT;
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, &id);
+ if (kctl == NULL) {
+ result = -ENOENT;
+ } else {
+ vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
+ if (vd->owner != NULL)
+ result = -EBUSY;
+ else {
+ vd->owner = file;
+ vd->owner_pid = current->pid;
+ result = 0;
+ }
+ }
+ up_write(&card->controls_rwsem);
+ return result;
+}
+
+static int snd_ctl_elem_unlock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+ snd_card_t *card = file->card;
+ snd_ctl_elem_id_t id;
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_volatile_t *vd;
+ int result;
+
+ if (copy_from_user(&id, _id, sizeof(id)))
+ return -EFAULT;
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, &id);
+ if (kctl == NULL) {
+ result = -ENOENT;
+ } else {
+ vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
+ if (vd->owner == NULL)
+ result = -EINVAL;
+ else if (vd->owner != file)
+ result = -EPERM;
+ else {
+ vd->owner = NULL;
+ vd->owner_pid = 0;
+ result = 0;
+ }
+ }
+ up_write(&card->controls_rwsem);
+ return result;
+}
+
+struct user_element {
+ snd_ctl_elem_info_t info;
+ void *elem_data; /* element data */
+ unsigned long elem_data_size; /* size of element data in bytes */
+ void *priv_data; /* private data (like strings for enumerated type) */
+ unsigned long priv_data_size; /* size of private data in bytes */
+};
+
+static int snd_ctl_elem_user_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct user_element *ue = kcontrol->private_data;
+
+ *uinfo = ue->info;
+ return 0;
+}
+
+static int snd_ctl_elem_user_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ struct user_element *ue = kcontrol->private_data;
+
+ memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size);
+ return 0;
+}
+
+static int snd_ctl_elem_user_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ int change;
+ struct user_element *ue = kcontrol->private_data;
+
+ change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0;
+ if (change)
+ memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size);
+ return change;
+}
+
+static void snd_ctl_elem_user_free(snd_kcontrol_t * kcontrol)
+{
+ kfree(kcontrol->private_data);
+}
+
+static int snd_ctl_elem_add(snd_ctl_file_t *file, snd_ctl_elem_info_t *info, int replace)
+{
+ snd_card_t *card = file->card;
+ snd_kcontrol_t kctl, *_kctl;
+ unsigned int access;
+ long private_size;
+ struct user_element *ue;
+ int idx, err;
+
+ if (card->user_ctl_count >= MAX_USER_CONTROLS)
+ return -ENOMEM;
+ if (info->count > 1024)
+ return -EINVAL;
+ access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
+ (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE));
+ info->id.numid = 0;
+ memset(&kctl, 0, sizeof(kctl));
+ down_write(&card->controls_rwsem);
+ _kctl = snd_ctl_find_id(card, &info->id);
+ err = 0;
+ if (_kctl) {
+ if (replace)
+ err = snd_ctl_remove(card, _kctl);
+ else
+ err = -EBUSY;
+ } else {
+ if (replace)
+ err = -ENOENT;
+ }
+ up_write(&card->controls_rwsem);
+ if (err < 0)
+ return err;
+ memcpy(&kctl.id, &info->id, sizeof(info->id));
+ kctl.count = info->owner ? info->owner : 1;
+ access |= SNDRV_CTL_ELEM_ACCESS_USER;
+ kctl.info = snd_ctl_elem_user_info;
+ if (access & SNDRV_CTL_ELEM_ACCESS_READ)
+ kctl.get = snd_ctl_elem_user_get;
+ if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
+ kctl.put = snd_ctl_elem_user_put;
+ switch (info->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ private_size = sizeof(char);
+ if (info->count > 128)
+ return -EINVAL;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ private_size = sizeof(long);
+ if (info->count > 128)
+ return -EINVAL;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+ private_size = sizeof(long long);
+ if (info->count > 64)
+ return -EINVAL;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ private_size = sizeof(unsigned char);
+ if (info->count > 512)
+ return -EINVAL;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_IEC958:
+ private_size = sizeof(struct sndrv_aes_iec958);
+ if (info->count != 1)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ private_size *= info->count;
+ ue = kcalloc(1, sizeof(struct user_element) + private_size, GFP_KERNEL);
+ if (ue == NULL)
+ return -ENOMEM;
+ ue->info = *info;
+ ue->elem_data = (char *)ue + sizeof(*ue);
+ ue->elem_data_size = private_size;
+ kctl.private_free = snd_ctl_elem_user_free;
+ _kctl = snd_ctl_new(&kctl, access);
+ if (_kctl == NULL) {
+ kfree(_kctl->private_data);
+ return -ENOMEM;
+ }
+ _kctl->private_data = ue;
+ for (idx = 0; idx < _kctl->count; idx++)
+ _kctl->vd[idx].owner = file;
+ err = snd_ctl_add(card, _kctl);
+ if (err < 0) {
+ snd_ctl_free_one(_kctl);
+ return err;
+ }
+
+ down_write(&card->controls_rwsem);
+ card->user_ctl_count++;
+ up_write(&card->controls_rwsem);
+
+ return 0;
+}
+
+static int snd_ctl_elem_add_user(snd_ctl_file_t *file, snd_ctl_elem_info_t __user *_info, int replace)
+{
+ snd_ctl_elem_info_t info;
+ if (copy_from_user(&info, _info, sizeof(info)))
+ return -EFAULT;
+ return snd_ctl_elem_add(file, &info, replace);
+}
+
+static int snd_ctl_elem_remove(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id)
+{
+ snd_ctl_elem_id_t id;
+ int err;
+
+ if (copy_from_user(&id, _id, sizeof(id)))
+ return -EFAULT;
+ err = snd_ctl_remove_unlocked_id(file, &id);
+ if (! err) {
+ snd_card_t *card = file->card;
+ down_write(&card->controls_rwsem);
+ card->user_ctl_count--;
+ up_write(&card->controls_rwsem);
+ }
+ return err;
+}
+
+static int snd_ctl_subscribe_events(snd_ctl_file_t *file, int __user *ptr)
+{
+ int subscribe;
+ if (get_user(subscribe, ptr))
+ return -EFAULT;
+ if (subscribe < 0) {
+ subscribe = file->subscribed;
+ if (put_user(subscribe, ptr))
+ return -EFAULT;
+ return 0;
+ }
+ if (subscribe) {
+ file->subscribed = 1;
+ return 0;
+ } else if (file->subscribed) {
+ snd_ctl_empty_read_queue(file);
+ file->subscribed = 0;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * change the power state
+ */
+static int snd_ctl_set_power_state(snd_card_t *card, unsigned int power_state)
+{
+ switch (power_state) {
+ case SNDRV_CTL_POWER_D0:
+ if (card->power_state != power_state) {
+ card->pm_resume(card);
+ snd_power_change_state(card, power_state);
+ }
+ break;
+ case SNDRV_CTL_POWER_D3hot:
+ if (card->power_state != power_state) {
+ card->pm_suspend(card, PMSG_SUSPEND);
+ snd_power_change_state(card, power_state);
+ }
+ break;
+ case SNDRV_CTL_POWER_D1:
+ case SNDRV_CTL_POWER_D2:
+ case SNDRV_CTL_POWER_D3cold:
+ /* not supported yet */
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+#endif
+
+static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_ctl_file_t *ctl;
+ snd_card_t *card;
+ struct list_head *list;
+ snd_kctl_ioctl_t *p;
+ void __user *argp = (void __user *)arg;
+ int __user *ip = argp;
+ int err;
+
+ ctl = file->private_data;
+ card = ctl->card;
+ snd_assert(card != NULL, return -ENXIO);
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_PVERSION:
+ return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
+ case SNDRV_CTL_IOCTL_CARD_INFO:
+ return snd_ctl_card_info(card, ctl, cmd, argp);
+ case SNDRV_CTL_IOCTL_ELEM_LIST:
+ return snd_ctl_elem_list(ctl->card, argp);
+ case SNDRV_CTL_IOCTL_ELEM_INFO:
+ return snd_ctl_elem_info_user(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_READ:
+ return snd_ctl_elem_read_user(ctl->card, argp);
+ case SNDRV_CTL_IOCTL_ELEM_WRITE:
+ return snd_ctl_elem_write_user(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_LOCK:
+ return snd_ctl_elem_lock(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
+ return snd_ctl_elem_unlock(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_ADD:
+ return snd_ctl_elem_add_user(ctl, argp, 0);
+ case SNDRV_CTL_IOCTL_ELEM_REPLACE:
+ return snd_ctl_elem_add_user(ctl, argp, 1);
+ case SNDRV_CTL_IOCTL_ELEM_REMOVE:
+ return snd_ctl_elem_remove(ctl, argp);
+ case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+ return snd_ctl_subscribe_events(ctl, ip);
+ case SNDRV_CTL_IOCTL_POWER:
+ if (get_user(err, ip))
+ return -EFAULT;
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+#ifdef CONFIG_PM
+ if (card->pm_suspend && card->pm_resume) {
+ snd_power_lock(card);
+ err = snd_ctl_set_power_state(card, err);
+ snd_power_unlock(card);
+ } else
+#endif
+ err = -ENOPROTOOPT;
+ return err;
+ case SNDRV_CTL_IOCTL_POWER_STATE:
+#ifdef CONFIG_PM
+ return put_user(card->power_state, ip) ? -EFAULT : 0;
+#else
+ return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;
+#endif
+ }
+ down_read(&snd_ioctl_rwsem);
+ list_for_each(list, &snd_control_ioctls) {
+ p = list_entry(list, snd_kctl_ioctl_t, list);
+ err = p->fioctl(card, ctl, cmd, arg);
+ if (err != -ENOIOCTLCMD) {
+ up_read(&snd_ioctl_rwsem);
+ return err;
+ }
+ }
+ up_read(&snd_ioctl_rwsem);
+ snd_printd("unknown ioctl = 0x%x\n", cmd);
+ return -ENOTTY;
+}
+
+static ssize_t snd_ctl_read(struct file *file, char __user *buffer, size_t count, loff_t * offset)
+{
+ snd_ctl_file_t *ctl;
+ int err = 0;
+ ssize_t result = 0;
+
+ ctl = file->private_data;
+ snd_assert(ctl != NULL && ctl->card != NULL, return -ENXIO);
+ if (!ctl->subscribed)
+ return -EBADFD;
+ if (count < sizeof(snd_ctl_event_t))
+ return -EINVAL;
+ spin_lock_irq(&ctl->read_lock);
+ while (count >= sizeof(snd_ctl_event_t)) {
+ snd_ctl_event_t ev;
+ snd_kctl_event_t *kev;
+ while (list_empty(&ctl->events)) {
+ wait_queue_t wait;
+ if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+ err = -EAGAIN;
+ goto __end_lock;
+ }
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&ctl->change_sleep, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&ctl->read_lock);
+ schedule();
+ remove_wait_queue(&ctl->change_sleep, &wait);
+ if (signal_pending(current))
+ return result > 0 ? result : -ERESTARTSYS;
+ spin_lock_irq(&ctl->read_lock);
+ }
+ kev = snd_kctl_event(ctl->events.next);
+ ev.type = SNDRV_CTL_EVENT_ELEM;
+ ev.data.elem.mask = kev->mask;
+ ev.data.elem.id = kev->id;
+ list_del(&kev->list);
+ spin_unlock_irq(&ctl->read_lock);
+ kfree(kev);
+ if (copy_to_user(buffer, &ev, sizeof(snd_ctl_event_t))) {
+ err = -EFAULT;
+ goto __end;
+ }
+ spin_lock_irq(&ctl->read_lock);
+ buffer += sizeof(snd_ctl_event_t);
+ count -= sizeof(snd_ctl_event_t);
+ result += sizeof(snd_ctl_event_t);
+ }
+ __end_lock:
+ spin_unlock_irq(&ctl->read_lock);
+ __end:
+ return result > 0 ? result : err;
+}
+
+static unsigned int snd_ctl_poll(struct file *file, poll_table * wait)
+{
+ unsigned int mask;
+ snd_ctl_file_t *ctl;
+
+ ctl = file->private_data;
+ if (!ctl->subscribed)
+ return 0;
+ poll_wait(file, &ctl->change_sleep, wait);
+
+ mask = 0;
+ if (!list_empty(&ctl->events))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+/*
+ * register the device-specific control-ioctls.
+ * called from each device manager like pcm.c, hwdep.c, etc.
+ */
+static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists)
+{
+ snd_kctl_ioctl_t *pn;
+
+ pn = kcalloc(1, sizeof(snd_kctl_ioctl_t), GFP_KERNEL);
+ if (pn == NULL)
+ return -ENOMEM;
+ pn->fioctl = fcn;
+ down_write(&snd_ioctl_rwsem);
+ list_add_tail(&pn->list, lists);
+ up_write(&snd_ioctl_rwsem);
+ return 0;
+}
+
+int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
+{
+ return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls);
+}
+
+#ifdef CONFIG_COMPAT
+int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn)
+{
+ return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls);
+}
+#endif
+
+/*
+ * de-register the device-specific control-ioctls.
+ */
+static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists)
+{
+ struct list_head *list;
+ snd_kctl_ioctl_t *p;
+
+ snd_runtime_check(fcn != NULL, return -EINVAL);
+ down_write(&snd_ioctl_rwsem);
+ list_for_each(list, lists) {
+ p = list_entry(list, snd_kctl_ioctl_t, list);
+ if (p->fioctl == fcn) {
+ list_del(&p->list);
+ up_write(&snd_ioctl_rwsem);
+ kfree(p);
+ return 0;
+ }
+ }
+ up_write(&snd_ioctl_rwsem);
+ snd_BUG();
+ return -EINVAL;
+}
+
+int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn)
+{
+ return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls);
+}
+
+#ifdef CONFIG_COMPAT
+int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn)
+{
+ return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls);
+}
+
+#endif
+
+static int snd_ctl_fasync(int fd, struct file * file, int on)
+{
+ snd_ctl_file_t *ctl;
+ int err;
+ ctl = file->private_data;
+ err = fasync_helper(fd, file, on, &ctl->fasync);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/*
+ * ioctl32 compat
+ */
+#ifdef CONFIG_COMPAT
+#include "control_compat.c"
+#else
+#define snd_ctl_ioctl_compat NULL
+#endif
+
+/*
+ * INIT PART
+ */
+
+static struct file_operations snd_ctl_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = snd_ctl_read,
+ .open = snd_ctl_open,
+ .release = snd_ctl_release,
+ .poll = snd_ctl_poll,
+ .unlocked_ioctl = snd_ctl_ioctl,
+ .compat_ioctl = snd_ctl_ioctl_compat,
+ .fasync = snd_ctl_fasync,
+};
+
+static snd_minor_t snd_ctl_reg =
+{
+ .comment = "ctl",
+ .f_ops = &snd_ctl_f_ops,
+};
+
+/*
+ * registration of the control device
+ */
+static int snd_ctl_dev_register(snd_device_t *device)
+{
+ snd_card_t *card = device->device_data;
+ int err, cardnum;
+ char name[16];
+
+ snd_assert(card != NULL, return -ENXIO);
+ cardnum = card->number;
+ snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+ sprintf(name, "controlC%i", cardnum);
+ if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL,
+ card, 0, &snd_ctl_reg, name)) < 0)
+ return err;
+ return 0;
+}
+
+/*
+ * disconnection of the control device
+ */
+static int snd_ctl_dev_disconnect(snd_device_t *device)
+{
+ snd_card_t *card = device->device_data;
+ struct list_head *flist;
+ snd_ctl_file_t *ctl;
+
+ down_read(&card->controls_rwsem);
+ list_for_each(flist, &card->ctl_files) {
+ ctl = snd_ctl_file(flist);
+ wake_up(&ctl->change_sleep);
+ kill_fasync(&ctl->fasync, SIGIO, POLL_ERR);
+ }
+ up_read(&card->controls_rwsem);
+ return 0;
+}
+
+/*
+ * free all controls
+ */
+static int snd_ctl_dev_free(snd_device_t *device)
+{
+ snd_card_t *card = device->device_data;
+ snd_kcontrol_t *control;
+
+ down_write(&card->controls_rwsem);
+ while (!list_empty(&card->controls)) {
+ control = snd_kcontrol(card->controls.next);
+ snd_ctl_remove(card, control);
+ }
+ up_write(&card->controls_rwsem);
+ return 0;
+}
+
+/*
+ * de-registration of the control device
+ */
+static int snd_ctl_dev_unregister(snd_device_t *device)
+{
+ snd_card_t *card = device->device_data;
+ int err, cardnum;
+
+ snd_assert(card != NULL, return -ENXIO);
+ cardnum = card->number;
+ snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+ if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL, card, 0)) < 0)
+ return err;
+ return snd_ctl_dev_free(device);
+}
+
+/*
+ * create control core:
+ * called from init.c
+ */
+int snd_ctl_create(snd_card_t *card)
+{
+ static snd_device_ops_t ops = {
+ .dev_free = snd_ctl_dev_free,
+ .dev_register = snd_ctl_dev_register,
+ .dev_disconnect = snd_ctl_dev_disconnect,
+ .dev_unregister = snd_ctl_dev_unregister
+ };
+
+ snd_assert(card != NULL, return -ENXIO);
+ return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
+}
diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c
new file mode 100644
index 00000000000..7fdabea4bfc
--- /dev/null
+++ b/sound/core/control_compat.c
@@ -0,0 +1,412 @@
+/*
+ * compat ioctls for control API
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+/* this file included from control.c */
+
+#include <linux/compat.h>
+
+struct sndrv_ctl_elem_list32 {
+ u32 offset;
+ u32 space;
+ u32 used;
+ u32 count;
+ u32 pids;
+ unsigned char reserved[50];
+} /* don't set packed attribute here */;
+
+static int snd_ctl_elem_list_compat(snd_card_t *card, struct sndrv_ctl_elem_list32 __user *data32)
+{
+ struct sndrv_ctl_elem_list __user *data;
+ compat_caddr_t ptr;
+ int err;
+
+ data = compat_alloc_user_space(sizeof(*data));
+
+ /* offset, space, used, count */
+ if (copy_in_user(data, data32, 4 * sizeof(u32)))
+ return -EFAULT;
+ /* pids */
+ if (get_user(ptr, &data32->pids) ||
+ put_user(compat_ptr(ptr), &data->pids))
+ return -EFAULT;
+ err = snd_ctl_elem_list(card, data);
+ if (err < 0)
+ return err;
+ /* copy the result */
+ if (copy_in_user(data32, data, 4 * sizeof(u32)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * control element info
+ * it uses union, so the things are not easy..
+ */
+
+struct sndrv_ctl_elem_info32 {
+ struct sndrv_ctl_elem_id id; // the size of struct is same
+ s32 type;
+ u32 access;
+ u32 count;
+ s32 owner;
+ union {
+ struct {
+ s32 min;
+ s32 max;
+ s32 step;
+ } integer;
+ struct {
+ u64 min;
+ u64 max;
+ u64 step;
+ } integer64;
+ struct {
+ u32 items;
+ u32 item;
+ char name[64];
+ } enumerated;
+ unsigned char reserved[128];
+ } value;
+ unsigned char reserved[64];
+} __attribute__((packed));
+
+static int snd_ctl_elem_info_compat(snd_ctl_file_t *ctl, struct sndrv_ctl_elem_info32 __user *data32)
+{
+ struct sndrv_ctl_elem_info *data;
+ int err;
+
+ data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+ if (! data)
+ return -ENOMEM;
+
+ err = -EFAULT;
+ /* copy id */
+ if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
+ goto error;
+ /* we need to copy the item index.
+ * hope this doesn't break anything..
+ */
+ if (get_user(data->value.enumerated.item, &data32->value.enumerated.item))
+ goto error;
+ err = snd_ctl_elem_info(ctl, data);
+ if (err < 0)
+ goto error;
+ /* restore info to 32bit */
+ err = -EFAULT;
+ /* id, type, access, count */
+ if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) ||
+ copy_to_user(&data32->type, &data->type, 3 * sizeof(u32)))
+ goto error;
+ if (put_user(data->owner, &data32->owner))
+ goto error;
+ switch (data->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ if (put_user(data->value.integer.min, &data32->value.integer.min) ||
+ put_user(data->value.integer.max, &data32->value.integer.max) ||
+ put_user(data->value.integer.step, &data32->value.integer.step))
+ goto error;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+ if (copy_to_user(&data32->value.integer64,
+ &data->value.integer64,
+ sizeof(data->value.integer64)))
+ goto error;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ if (copy_to_user(&data32->value.enumerated,
+ &data->value.enumerated,
+ sizeof(data->value.enumerated)))
+ goto error;
+ break;
+ default:
+ break;
+ }
+ err = 0;
+ error:
+ kfree(data);
+ return err;
+}
+
+/* read / write */
+struct sndrv_ctl_elem_value32 {
+ struct sndrv_ctl_elem_id id;
+ unsigned int indirect; /* bit-field causes misalignment */
+ union {
+ s32 integer[128];
+ unsigned char data[512];
+#ifndef CONFIG_X86_64
+ s64 integer64[64];
+#endif
+ } value;
+ unsigned char reserved[128];
+};
+
+
+/* get the value type and count of the control */
+static int get_ctl_type(snd_card_t *card, snd_ctl_elem_id_t *id, int *countp)
+{
+ snd_kcontrol_t *kctl;
+ snd_ctl_elem_info_t info;
+ int err;
+
+ down_read(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, id);
+ if (! kctl) {
+ up_read(&card->controls_rwsem);
+ return -ENXIO;
+ }
+ info.id = *id;
+ err = kctl->info(kctl, &info);
+ up_read(&card->controls_rwsem);
+ if (err >= 0) {
+ err = info.type;
+ *countp = info.count;
+ }
+ return err;
+}
+
+static int get_elem_size(int type, int count)
+{
+ switch (type) {
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+ return sizeof(s64) * count;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ return sizeof(int) * count;
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ return 512;
+ case SNDRV_CTL_ELEM_TYPE_IEC958:
+ return sizeof(struct sndrv_aes_iec958);
+ default:
+ return -1;
+ }
+}
+
+static int copy_ctl_value_from_user(snd_card_t *card,
+ struct sndrv_ctl_elem_value *data,
+ struct sndrv_ctl_elem_value32 __user *data32,
+ int *typep, int *countp)
+{
+ int i, type, count, size;
+ unsigned int indirect;
+
+ if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
+ return -EFAULT;
+ if (get_user(indirect, &data32->indirect))
+ return -EFAULT;
+ if (indirect)
+ return -EINVAL;
+ type = get_ctl_type(card, &data->id, &count);
+ if (type < 0)
+ return type;
+
+ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
+ type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
+ for (i = 0; i < count; i++) {
+ int val;
+ if (get_user(val, &data32->value.integer[i]))
+ return -EFAULT;
+ data->value.integer.value[i] = val;
+ }
+ } else {
+ size = get_elem_size(type, count);
+ if (size < 0) {
+ printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
+ return -EINVAL;
+ }
+ if (copy_from_user(data->value.bytes.data,
+ data32->value.data, size))
+ return -EFAULT;
+ }
+
+ *typep = type;
+ *countp = count;
+ return 0;
+}
+
+/* restore the value to 32bit */
+static int copy_ctl_value_to_user(struct sndrv_ctl_elem_value32 __user *data32,
+ struct sndrv_ctl_elem_value *data,
+ int type, int count)
+{
+ int i, size;
+
+ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
+ type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
+ for (i = 0; i < count; i++) {
+ int val;
+ val = data->value.integer.value[i];
+ if (put_user(val, &data32->value.integer[i]))
+ return -EFAULT;
+ }
+ } else {
+ size = get_elem_size(type, count);
+ if (copy_to_user(data32->value.data,
+ data->value.bytes.data, size))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int snd_ctl_elem_read_user_compat(snd_card_t *card,
+ struct sndrv_ctl_elem_value32 __user *data32)
+{
+ struct sndrv_ctl_elem_value *data;
+ int err, type, count;
+
+ data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
+ goto error;
+ if ((err = snd_ctl_elem_read(card, data)) < 0)
+ goto error;
+ err = copy_ctl_value_to_user(data32, data, type, count);
+ error:
+ kfree(data);
+ return err;
+}
+
+static int snd_ctl_elem_write_user_compat(snd_ctl_file_t *file,
+ struct sndrv_ctl_elem_value32 __user *data32)
+{
+ struct sndrv_ctl_elem_value *data;
+ int err, type, count;
+
+ data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ if ((err = copy_ctl_value_from_user(file->card, data, data32, &type, &count)) < 0)
+ goto error;
+ if ((err = snd_ctl_elem_write(file->card, file, data)) < 0)
+ goto error;
+ err = copy_ctl_value_to_user(data32, data, type, count);
+ error:
+ kfree(data);
+ return err;
+}
+
+/* add or replace a user control */
+static int snd_ctl_elem_add_compat(snd_ctl_file_t *file,
+ struct sndrv_ctl_elem_info32 __user *data32,
+ int replace)
+{
+ struct sndrv_ctl_elem_info *data;
+ int err;
+
+ data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+ if (! data)
+ return -ENOMEM;
+
+ err = -EFAULT;
+ /* id, type, access, count */ \
+ if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) ||
+ copy_from_user(&data->type, &data32->type, 3 * sizeof(u32)))
+ goto error;
+ if (get_user(data->owner, &data32->owner) ||
+ get_user(data->type, &data32->type))
+ goto error;
+ switch (data->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ if (get_user(data->value.integer.min, &data32->value.integer.min) ||
+ get_user(data->value.integer.max, &data32->value.integer.max) ||
+ get_user(data->value.integer.step, &data32->value.integer.step))
+ goto error;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+ if (copy_from_user(&data->value.integer64,
+ &data32->value.integer64,
+ sizeof(data->value.integer64)))
+ goto error;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ if (copy_from_user(&data->value.enumerated,
+ &data32->value.enumerated,
+ sizeof(data->value.enumerated)))
+ goto error;
+ break;
+ default:
+ break;
+ }
+ err = snd_ctl_elem_add(file, data, replace);
+ error:
+ kfree(data);
+ return err;
+}
+
+enum {
+ SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct sndrv_ctl_elem_list32),
+ SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct sndrv_ctl_elem_info32),
+ SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct sndrv_ctl_elem_value32),
+ SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct sndrv_ctl_elem_value32),
+ SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct sndrv_ctl_elem_info32),
+ SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct sndrv_ctl_elem_info32),
+};
+
+static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_ctl_file_t *ctl;
+ struct list_head *list;
+ void __user *argp = compat_ptr(arg);
+ int err;
+
+ ctl = file->private_data;
+ snd_assert(ctl && ctl->card, return -ENXIO);
+
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_PVERSION:
+ case SNDRV_CTL_IOCTL_CARD_INFO:
+ case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+ case SNDRV_CTL_IOCTL_POWER:
+ case SNDRV_CTL_IOCTL_POWER_STATE:
+ case SNDRV_CTL_IOCTL_ELEM_LOCK:
+ case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
+ return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
+ case SNDRV_CTL_IOCTL_ELEM_LIST32:
+ return snd_ctl_elem_list_compat(ctl->card, argp);
+ case SNDRV_CTL_IOCTL_ELEM_INFO32:
+ return snd_ctl_elem_info_compat(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_READ32:
+ return snd_ctl_elem_read_user_compat(ctl->card, argp);
+ case SNDRV_CTL_IOCTL_ELEM_WRITE32:
+ return snd_ctl_elem_write_user_compat(ctl, argp);
+ case SNDRV_CTL_IOCTL_ELEM_ADD32:
+ return snd_ctl_elem_add_compat(ctl, argp, 0);
+ case SNDRV_CTL_IOCTL_ELEM_REPLACE32:
+ return snd_ctl_elem_add_compat(ctl, argp, 1);
+ }
+
+ down_read(&snd_ioctl_rwsem);
+ list_for_each(list, &snd_control_compat_ioctls) {
+ snd_kctl_ioctl_t *p = list_entry(list, snd_kctl_ioctl_t, list);
+ if (p->fioctl) {
+ err = p->fioctl(ctl->card, ctl, cmd, arg);
+ if (err != -ENOIOCTLCMD) {
+ up_read(&snd_ioctl_rwsem);
+ return err;
+ }
+ }
+ }
+ up_read(&snd_ioctl_rwsem);
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/device.c b/sound/core/device.c
new file mode 100644
index 00000000000..18c71f913d2
--- /dev/null
+++ b/sound/core/device.c
@@ -0,0 +1,240 @@
+/*
+ * Device management routines
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+
+/**
+ * snd_device_new - create an ALSA device component
+ * @card: the card instance
+ * @type: the device type, SNDRV_DEV_TYPE_XXX
+ * @device_data: the data pointer of this device
+ * @ops: the operator table
+ *
+ * Creates a new device component for the given data pointer.
+ * The device will be assigned to the card and managed together
+ * by the card.
+ *
+ * The data pointer plays a role as the identifier, too, so the
+ * pointer address must be unique and unchanged.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_device_new(snd_card_t *card, snd_device_type_t type,
+ void *device_data, snd_device_ops_t *ops)
+{
+ snd_device_t *dev;
+
+ snd_assert(card != NULL && device_data != NULL && ops != NULL, return -ENXIO);
+ dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+ dev->card = card;
+ dev->type = type;
+ dev->state = SNDRV_DEV_BUILD;
+ dev->device_data = device_data;
+ dev->ops = ops;
+ list_add(&dev->list, &card->devices); /* add to the head of list */
+ return 0;
+}
+
+/**
+ * snd_device_free - release the device from the card
+ * @card: the card instance
+ * @device_data: the data pointer to release
+ *
+ * Removes the device from the list on the card and invokes the
+ * callback, dev_unregister or dev_free, corresponding to the state.
+ * Then release the device.
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_free(snd_card_t *card, void *device_data)
+{
+ struct list_head *list;
+ snd_device_t *dev;
+
+ snd_assert(card != NULL, return -ENXIO);
+ snd_assert(device_data != NULL, return -ENXIO);
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (dev->device_data != device_data)
+ continue;
+ /* unlink */
+ list_del(&dev->list);
+ if ((dev->state == SNDRV_DEV_REGISTERED || dev->state == SNDRV_DEV_DISCONNECTED) &&
+ dev->ops->dev_unregister) {
+ if (dev->ops->dev_unregister(dev))
+ snd_printk(KERN_ERR "device unregister failure\n");
+ } else {
+ if (dev->ops->dev_free) {
+ if (dev->ops->dev_free(dev))
+ snd_printk(KERN_ERR "device free failure\n");
+ }
+ }
+ kfree(dev);
+ return 0;
+ }
+ snd_printd("device free %p (from %p), not found\n", device_data, __builtin_return_address(0));
+ return -ENXIO;
+}
+
+/**
+ * snd_device_free - disconnect the device
+ * @card: the card instance
+ * @device_data: the data pointer to disconnect
+ *
+ * Turns the device into the disconnection state, invoking
+ * dev_disconnect callback, if the device was already registered.
+ *
+ * Usually called from snd_card_disconnect().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_disconnect(snd_card_t *card, void *device_data)
+{
+ struct list_head *list;
+ snd_device_t *dev;
+
+ snd_assert(card != NULL, return -ENXIO);
+ snd_assert(device_data != NULL, return -ENXIO);
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (dev->device_data != device_data)
+ continue;
+ if (dev->state == SNDRV_DEV_REGISTERED && dev->ops->dev_disconnect) {
+ if (dev->ops->dev_disconnect(dev))
+ snd_printk(KERN_ERR "device disconnect failure\n");
+ dev->state = SNDRV_DEV_DISCONNECTED;
+ }
+ return 0;
+ }
+ snd_printd("device disconnect %p (from %p), not found\n", device_data, __builtin_return_address(0));
+ return -ENXIO;
+}
+
+/**
+ * snd_device_register - register the device
+ * @card: the card instance
+ * @device_data: the data pointer to register
+ *
+ * Registers the device which was already created via
+ * snd_device_new(). Usually this is called from snd_card_register(),
+ * but it can be called later if any new devices are created after
+ * invocation of snd_card_register().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
+int snd_device_register(snd_card_t *card, void *device_data)
+{
+ struct list_head *list;
+ snd_device_t *dev;
+ int err;
+
+ snd_assert(card != NULL && device_data != NULL, return -ENXIO);
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (dev->device_data != device_data)
+ continue;
+ if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
+ if ((err = dev->ops->dev_register(dev)) < 0)
+ return err;
+ dev->state = SNDRV_DEV_REGISTERED;
+ return 0;
+ }
+ return -EBUSY;
+ }
+ snd_BUG();
+ return -ENXIO;
+}
+
+/*
+ * register all the devices on the card.
+ * called from init.c
+ */
+int snd_device_register_all(snd_card_t *card)
+{
+ struct list_head *list;
+ snd_device_t *dev;
+ int err;
+
+ snd_assert(card != NULL, return -ENXIO);
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
+ if ((err = dev->ops->dev_register(dev)) < 0)
+ return err;
+ dev->state = SNDRV_DEV_REGISTERED;
+ }
+ }
+ return 0;
+}
+
+/*
+ * disconnect all the devices on the card.
+ * called from init.c
+ */
+int snd_device_disconnect_all(snd_card_t *card)
+{
+ snd_device_t *dev;
+ struct list_head *list;
+ int err = 0;
+
+ snd_assert(card != NULL, return -ENXIO);
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (snd_device_disconnect(card, dev->device_data) < 0)
+ err = -ENXIO;
+ }
+ return err;
+}
+
+/*
+ * release all the devices on the card.
+ * called from init.c
+ */
+int snd_device_free_all(snd_card_t *card, snd_device_cmd_t cmd)
+{
+ snd_device_t *dev;
+ struct list_head *list;
+ int err;
+ unsigned int range_low, range_high;
+
+ snd_assert(card != NULL, return -ENXIO);
+ range_low = cmd * SNDRV_DEV_TYPE_RANGE_SIZE;
+ range_high = range_low + SNDRV_DEV_TYPE_RANGE_SIZE - 1;
+ __again:
+ list_for_each(list, &card->devices) {
+ dev = snd_device(list);
+ if (dev->type >= range_low && dev->type <= range_high) {
+ if ((err = snd_device_free(card, dev->device_data)) < 0)
+ return err;
+ goto __again;
+ }
+ }
+ return 0;
+}
diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c
new file mode 100644
index 00000000000..997dd41c584
--- /dev/null
+++ b/sound/core/hwdep.c
@@ -0,0 +1,524 @@
+/*
+ * Hardware dependent layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Hardware dependent layer");
+MODULE_LICENSE("GPL");
+
+static snd_hwdep_t *snd_hwdep_devices[SNDRV_CARDS * SNDRV_MINOR_HWDEPS];
+
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_hwdep_free(snd_hwdep_t *hwdep);
+static int snd_hwdep_dev_free(snd_device_t *device);
+static int snd_hwdep_dev_register(snd_device_t *device);
+static int snd_hwdep_dev_unregister(snd_device_t *device);
+
+/*
+
+ */
+
+static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
+{
+ snd_hwdep_t *hw = file->private_data;
+ if (hw->ops.llseek)
+ return hw->ops.llseek(hw, file, offset, orig);
+ return -ENXIO;
+}
+
+static ssize_t snd_hwdep_read(struct file * file, char __user *buf, size_t count, loff_t *offset)
+{
+ snd_hwdep_t *hw = file->private_data;
+ if (hw->ops.read)
+ return hw->ops.read(hw, buf, count, offset);
+ return -ENXIO;
+}
+
+static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, size_t count, loff_t *offset)
+{
+ snd_hwdep_t *hw = file->private_data;
+ if (hw->ops.write)
+ return hw->ops.write(hw, buf, count, offset);
+ return -ENXIO;
+}
+
+static int snd_hwdep_open(struct inode *inode, struct file * file)
+{
+ int major = imajor(inode);
+ int cardnum;
+ int device;
+ snd_hwdep_t *hw;
+ int err;
+ wait_queue_t wait;
+
+ switch (major) {
+ case CONFIG_SND_MAJOR:
+ cardnum = SNDRV_MINOR_CARD(iminor(inode));
+ device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_HWDEP;
+ break;
+#ifdef CONFIG_SND_OSSEMUL
+ case SOUND_MAJOR:
+ cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+ device = 0;
+ break;
+#endif
+ default:
+ return -ENXIO;
+ }
+ cardnum %= SNDRV_CARDS;
+ device %= SNDRV_MINOR_HWDEPS;
+ hw = snd_hwdep_devices[(cardnum * SNDRV_MINOR_HWDEPS) + device];
+ if (hw == NULL)
+ return -ENODEV;
+
+ if (!hw->ops.open)
+ return -ENXIO;
+#ifdef CONFIG_SND_OSSEMUL
+ if (major == SOUND_MAJOR && hw->oss_type < 0)
+ return -ENXIO;
+#endif
+
+ if (!try_module_get(hw->card->module))
+ return -EFAULT;
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&hw->open_wait, &wait);
+ down(&hw->open_mutex);
+ while (1) {
+ if (hw->exclusive && hw->used > 0) {
+ err = -EBUSY;
+ break;
+ }
+ err = hw->ops.open(hw, file);
+ if (err >= 0)
+ break;
+ if (err == -EAGAIN) {
+ if (file->f_flags & O_NONBLOCK) {
+ err = -EBUSY;
+ break;
+ }
+ } else
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ up(&hw->open_mutex);
+ schedule();
+ down(&hw->open_mutex);
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ }
+ remove_wait_queue(&hw->open_wait, &wait);
+ if (err >= 0) {
+ err = snd_card_file_add(hw->card, file);
+ if (err >= 0) {
+ file->private_data = hw;
+ hw->used++;
+ } else {
+ if (hw->ops.release)
+ hw->ops.release(hw, file);
+ }
+ }
+ up(&hw->open_mutex);
+ if (err < 0)
+ module_put(hw->card->module);
+ return err;
+}
+
+static int snd_hwdep_release(struct inode *inode, struct file * file)
+{
+ int err = -ENXIO;
+ snd_hwdep_t *hw = file->private_data;
+ down(&hw->open_mutex);
+ if (hw->ops.release) {
+ err = hw->ops.release(hw, file);
+ wake_up(&hw->open_wait);
+ }
+ if (hw->used > 0)
+ hw->used--;
+ snd_card_file_remove(hw->card, file);
+ up(&hw->open_mutex);
+ module_put(hw->card->module);
+ return err;
+}
+
+static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
+{
+ snd_hwdep_t *hw = file->private_data;
+ if (hw->ops.poll)
+ return hw->ops.poll(hw, file, wait);
+ return 0;
+}
+
+static int snd_hwdep_info(snd_hwdep_t *hw, snd_hwdep_info_t __user *_info)
+{
+ snd_hwdep_info_t info;
+
+ memset(&info, 0, sizeof(info));
+ info.card = hw->card->number;
+ strlcpy(info.id, hw->id, sizeof(info.id));
+ strlcpy(info.name, hw->name, sizeof(info.name));
+ info.iface = hw->iface;
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t __user *_info)
+{
+ snd_hwdep_dsp_status_t info;
+ int err;
+
+ if (! hw->ops.dsp_status)
+ return -ENXIO;
+ memset(&info, 0, sizeof(info));
+ info.dsp_loaded = hw->dsp_loaded;
+ if ((err = hw->ops.dsp_status(hw, &info)) < 0)
+ return err;
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t __user *_info)
+{
+ snd_hwdep_dsp_image_t info;
+ int err;
+
+ if (! hw->ops.dsp_load)
+ return -ENXIO;
+ memset(&info, 0, sizeof(info));
+ if (copy_from_user(&info, _info, sizeof(info)))
+ return -EFAULT;
+ /* check whether the dsp was already loaded */
+ if (hw->dsp_loaded & (1 << info.index))
+ return -EBUSY;
+ if (!access_ok(VERIFY_READ, info.image, info.length))
+ return -EFAULT;
+ err = hw->ops.dsp_load(hw, &info);
+ if (err < 0)
+ return err;
+ hw->dsp_loaded |= (1 << info.index);
+ return 0;
+}
+
+static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
+{
+ snd_hwdep_t *hw = file->private_data;
+ void __user *argp = (void __user *)arg;
+ switch (cmd) {
+ case SNDRV_HWDEP_IOCTL_PVERSION:
+ return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
+ case SNDRV_HWDEP_IOCTL_INFO:
+ return snd_hwdep_info(hw, argp);
+ case SNDRV_HWDEP_IOCTL_DSP_STATUS:
+ return snd_hwdep_dsp_status(hw, argp);
+ case SNDRV_HWDEP_IOCTL_DSP_LOAD:
+ return snd_hwdep_dsp_load(hw, argp);
+ }
+ if (hw->ops.ioctl)
+ return hw->ops.ioctl(hw, file, cmd, arg);
+ return -ENOTTY;
+}
+
+static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
+{
+ snd_hwdep_t *hw = file->private_data;
+ if (hw->ops.mmap)
+ return hw->ops.mmap(hw, file, vma);
+ return -ENXIO;
+}
+
+static int snd_hwdep_control_ioctl(snd_card_t * card, snd_ctl_file_t * control,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int tmp;
+
+ tmp = card->number * SNDRV_MINOR_HWDEPS;
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
+ {
+ int device;
+
+ if (get_user(device, (int __user *)arg))
+ return -EFAULT;
+ device = device < 0 ? 0 : device + 1;
+ while (device < SNDRV_MINOR_HWDEPS) {
+ if (snd_hwdep_devices[tmp + device])
+ break;
+ device++;
+ }
+ if (device >= SNDRV_MINOR_HWDEPS)
+ device = -1;
+ if (put_user(device, (int __user *)arg))
+ return -EFAULT;
+ return 0;
+ }
+ case SNDRV_CTL_IOCTL_HWDEP_INFO:
+ {
+ snd_hwdep_info_t __user *info = (snd_hwdep_info_t __user *)arg;
+ int device;
+ snd_hwdep_t *hwdep;
+
+ if (get_user(device, &info->device))
+ return -EFAULT;
+ if (device < 0 || device >= SNDRV_MINOR_HWDEPS)
+ return -ENXIO;
+ hwdep = snd_hwdep_devices[tmp + device];
+ if (hwdep == NULL)
+ return -ENXIO;
+ return snd_hwdep_info(hwdep, info);
+ }
+ }
+ return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+#include "hwdep_compat.c"
+#else
+#define snd_hwdep_ioctl_compat NULL
+#endif
+
+/*
+
+ */
+
+static struct file_operations snd_hwdep_f_ops =
+{
+ .owner = THIS_MODULE,
+ .llseek = snd_hwdep_llseek,
+ .read = snd_hwdep_read,
+ .write = snd_hwdep_write,
+ .open = snd_hwdep_open,
+ .release = snd_hwdep_release,
+ .poll = snd_hwdep_poll,
+ .unlocked_ioctl = snd_hwdep_ioctl,
+ .compat_ioctl = snd_hwdep_ioctl_compat,
+ .mmap = snd_hwdep_mmap,
+};
+
+static snd_minor_t snd_hwdep_reg =
+{
+ .comment = "hardware dependent",
+ .f_ops = &snd_hwdep_f_ops,
+};
+
+/**
+ * snd_hwdep_new - create a new hwdep instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero-based)
+ * @rhwdep: the pointer to store the new hwdep instance
+ *
+ * Creates a new hwdep instance with the given index on the card.
+ * The callbacks (hwdep->ops) must be set on the returned instance
+ * after this call manually by the caller.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_hwdep_new(snd_card_t * card, char *id, int device, snd_hwdep_t ** rhwdep)
+{
+ snd_hwdep_t *hwdep;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_hwdep_dev_free,
+ .dev_register = snd_hwdep_dev_register,
+ .dev_unregister = snd_hwdep_dev_unregister
+ };
+
+ snd_assert(rhwdep != NULL, return -EINVAL);
+ *rhwdep = NULL;
+ snd_assert(card != NULL, return -ENXIO);
+ hwdep = kcalloc(1, sizeof(*hwdep), GFP_KERNEL);
+ if (hwdep == NULL)
+ return -ENOMEM;
+ hwdep->card = card;
+ hwdep->device = device;
+ if (id) {
+ strlcpy(hwdep->id, id, sizeof(hwdep->id));
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ hwdep->oss_type = -1;
+#endif
+ if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
+ snd_hwdep_free(hwdep);
+ return err;
+ }
+ init_waitqueue_head(&hwdep->open_wait);
+ init_MUTEX(&hwdep->open_mutex);
+ *rhwdep = hwdep;
+ return 0;
+}
+
+static int snd_hwdep_free(snd_hwdep_t *hwdep)
+{
+ snd_assert(hwdep != NULL, return -ENXIO);
+ if (hwdep->private_free)
+ hwdep->private_free(hwdep);
+ kfree(hwdep);
+ return 0;
+}
+
+static int snd_hwdep_dev_free(snd_device_t *device)
+{
+ snd_hwdep_t *hwdep = device->device_data;
+ return snd_hwdep_free(hwdep);
+}
+
+static int snd_hwdep_dev_register(snd_device_t *device)
+{
+ snd_hwdep_t *hwdep = device->device_data;
+ int idx, err;
+ char name[32];
+
+ down(&register_mutex);
+ idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
+ if (snd_hwdep_devices[idx]) {
+ up(&register_mutex);
+ return -EBUSY;
+ }
+ snd_hwdep_devices[idx] = hwdep;
+ sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
+ if ((err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,
+ hwdep->card, hwdep->device,
+ &snd_hwdep_reg, name)) < 0) {
+ snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n",
+ hwdep->card->number, hwdep->device);
+ snd_hwdep_devices[idx] = NULL;
+ up(&register_mutex);
+ return err;
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ hwdep->ossreg = 0;
+ if (hwdep->oss_type >= 0) {
+ if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
+ snd_printk (KERN_WARNING "only hwdep device 0 can be registered as OSS direct FM device!\n");
+ } else {
+ if (snd_register_oss_device(hwdep->oss_type,
+ hwdep->card, hwdep->device,
+ &snd_hwdep_reg, hwdep->oss_dev) < 0) {
+ snd_printk(KERN_ERR "unable to register OSS compatibility device %i:%i\n",
+ hwdep->card->number, hwdep->device);
+ } else
+ hwdep->ossreg = 1;
+ }
+ }
+#endif
+ up(&register_mutex);
+ return 0;
+}
+
+static int snd_hwdep_dev_unregister(snd_device_t *device)
+{
+ snd_hwdep_t *hwdep = device->device_data;
+ int idx;
+
+ snd_assert(hwdep != NULL, return -ENXIO);
+ down(&register_mutex);
+ idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device;
+ if (snd_hwdep_devices[idx] != hwdep) {
+ up(&register_mutex);
+ return -EINVAL;
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ if (hwdep->ossreg)
+ snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
+#endif
+ snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
+ snd_hwdep_devices[idx] = NULL;
+ up(&register_mutex);
+ return snd_hwdep_free(hwdep);
+}
+
+/*
+ * Info interface
+ */
+
+static void snd_hwdep_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ int idx;
+ snd_hwdep_t *hwdep;
+
+ down(&register_mutex);
+ for (idx = 0; idx < SNDRV_CARDS * SNDRV_MINOR_HWDEPS; idx++) {
+ hwdep = snd_hwdep_devices[idx];
+ if (hwdep == NULL)
+ continue;
+ snd_iprintf(buffer, "%02i-%02i: %s\n",
+ idx / SNDRV_MINOR_HWDEPS,
+ idx % SNDRV_MINOR_HWDEPS,
+ hwdep->name);
+ }
+ up(&register_mutex);
+}
+
+/*
+ * ENTRY functions
+ */
+
+static snd_info_entry_t *snd_hwdep_proc_entry = NULL;
+
+static int __init alsa_hwdep_init(void)
+{
+ snd_info_entry_t *entry;
+
+ memset(snd_hwdep_devices, 0, sizeof(snd_hwdep_devices));
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
+ entry->c.text.read_size = 512;
+ entry->c.text.read = snd_hwdep_proc_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_hwdep_proc_entry = entry;
+ snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
+ snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
+ return 0;
+}
+
+static void __exit alsa_hwdep_exit(void)
+{
+ snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
+ snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
+ if (snd_hwdep_proc_entry) {
+ snd_info_unregister(snd_hwdep_proc_entry);
+ snd_hwdep_proc_entry = NULL;
+ }
+}
+
+module_init(alsa_hwdep_init)
+module_exit(alsa_hwdep_exit)
+
+EXPORT_SYMBOL(snd_hwdep_new);
diff --git a/sound/core/hwdep_compat.c b/sound/core/hwdep_compat.c
new file mode 100644
index 00000000000..6866f423d4b
--- /dev/null
+++ b/sound/core/hwdep_compat.c
@@ -0,0 +1,77 @@
+/*
+ * 32bit -> 64bit ioctl wrapper for hwdep API
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ */
+
+/* This file is included from hwdep.c */
+
+#include <linux/compat.h>
+
+struct sndrv_hwdep_dsp_image32 {
+ u32 index;
+ unsigned char name[64];
+ u32 image; /* pointer */
+ u32 length;
+ u32 driver_data;
+} /* don't set packed attribute here */;
+
+static int snd_hwdep_dsp_load_compat(snd_hwdep_t *hw,
+ struct sndrv_hwdep_dsp_image32 __user *src)
+{
+ struct sndrv_hwdep_dsp_image *dst;
+ compat_caddr_t ptr;
+ u32 val;
+
+ dst = compat_alloc_user_space(sizeof(*dst));
+
+ /* index and name */
+ if (copy_in_user(dst, src, 4 + 64))
+ return -EFAULT;
+ if (get_user(ptr, &src->image) ||
+ put_user(compat_ptr(ptr), &dst->image))
+ return -EFAULT;
+ if (get_user(val, &src->length) ||
+ put_user(val, &dst->length))
+ return -EFAULT;
+ if (get_user(val, &src->driver_data) ||
+ put_user(val, &dst->driver_data))
+ return -EFAULT;
+
+ return snd_hwdep_dsp_load(hw, dst);
+}
+
+enum {
+ SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct sndrv_hwdep_dsp_image32)
+};
+
+static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, unsigned long arg)
+{
+ snd_hwdep_t *hw = file->private_data;
+ void __user *argp = compat_ptr(arg);
+ switch (cmd) {
+ case SNDRV_HWDEP_IOCTL_PVERSION:
+ case SNDRV_HWDEP_IOCTL_INFO:
+ case SNDRV_HWDEP_IOCTL_DSP_STATUS:
+ return snd_hwdep_ioctl(file, cmd, (unsigned long)argp);
+ case SNDRV_HWDEP_IOCTL_DSP_LOAD32:
+ return snd_hwdep_dsp_load_compat(hw, argp);
+ }
+ if (hw->ops.ioctl_compat)
+ return hw->ops.ioctl_compat(hw, file, cmd, arg);
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/info.c b/sound/core/info.c
new file mode 100644
index 00000000000..31faffe01cb
--- /dev/null
+++ b/sound/core/info.c
@@ -0,0 +1,989 @@
+/*
+ * Information interface for ALSA driver
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include <linux/smp_lock.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/proc_fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <stdarg.h>
+
+/*
+ *
+ */
+
+int snd_info_check_reserved_words(const char *str)
+{
+ static char *reserved[] =
+ {
+ "version",
+ "meminfo",
+ "memdebug",
+ "detect",
+ "devices",
+ "oss",
+ "cards",
+ "timers",
+ "synth",
+ "pcm",
+ "seq",
+ NULL
+ };
+ char **xstr = reserved;
+
+ while (*xstr) {
+ if (!strcmp(*xstr, str))
+ return 0;
+ xstr++;
+ }
+ if (!strncmp(str, "card", 4))
+ return 0;
+ return 1;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static DECLARE_MUTEX(info_mutex);
+
+typedef struct _snd_info_private_data {
+ snd_info_buffer_t *rbuffer;
+ snd_info_buffer_t *wbuffer;
+ snd_info_entry_t *entry;
+ void *file_private_data;
+} snd_info_private_data_t;
+
+static int snd_info_version_init(void);
+static int snd_info_version_done(void);
+
+
+/**
+ * snd_iprintf - printf on the procfs buffer
+ * @buffer: the procfs buffer
+ * @fmt: the printf format
+ *
+ * Outputs the string on the procfs buffer just like printf().
+ *
+ * Returns the size of output string.
+ */
+int snd_iprintf(snd_info_buffer_t * buffer, char *fmt,...)
+{
+ va_list args;
+ int len, res;
+
+ if (buffer->stop || buffer->error)
+ return 0;
+ len = buffer->len - buffer->size;
+ va_start(args, fmt);
+ res = vsnprintf(buffer->curr, len, fmt, args);
+ va_end(args);
+ if (res >= len) {
+ buffer->stop = 1;
+ return 0;
+ }
+ buffer->curr += res;
+ buffer->size += res;
+ return res;
+}
+
+/*
+
+ */
+
+static struct proc_dir_entry *snd_proc_root = NULL;
+snd_info_entry_t *snd_seq_root = NULL;
+#ifdef CONFIG_SND_OSSEMUL
+snd_info_entry_t *snd_oss_root = NULL;
+#endif
+
+static inline void snd_info_entry_prepare(struct proc_dir_entry *de)
+{
+ de->owner = THIS_MODULE;
+}
+
+static void snd_remove_proc_entry(struct proc_dir_entry *parent,
+ struct proc_dir_entry *de)
+{
+ if (de)
+ remove_proc_entry(de->name, parent);
+}
+
+static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
+{
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+ loff_t ret;
+
+ data = file->private_data;
+ entry = data->entry;
+ lock_kernel();
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_TEXT:
+ switch (orig) {
+ case 0: /* SEEK_SET */
+ file->f_pos = offset;
+ ret = file->f_pos;
+ goto out;
+ case 1: /* SEEK_CUR */
+ file->f_pos += offset;
+ ret = file->f_pos;
+ goto out;
+ case 2: /* SEEK_END */
+ default:
+ ret = -EINVAL;
+ goto out;
+ }
+ break;
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->llseek) {
+ ret = entry->c.ops->llseek(entry,
+ data->file_private_data,
+ file, offset, orig);
+ goto out;
+ }
+ break;
+ }
+ ret = -ENXIO;
+out:
+ unlock_kernel();
+ return ret;
+}
+
+static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
+ size_t count, loff_t * offset)
+{
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+ snd_info_buffer_t *buf;
+ size_t size = 0;
+ loff_t pos;
+
+ data = file->private_data;
+ snd_assert(data != NULL, return -ENXIO);
+ pos = *offset;
+ if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
+ return -EIO;
+ if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
+ return -EIO;
+ entry = data->entry;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_TEXT:
+ buf = data->rbuffer;
+ if (buf == NULL)
+ return -EIO;
+ if (pos >= buf->size)
+ return 0;
+ size = buf->size - pos;
+ size = min(count, size);
+ if (copy_to_user(buffer, buf->buffer + pos, size))
+ return -EFAULT;
+ break;
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->read)
+ size = entry->c.ops->read(entry,
+ data->file_private_data,
+ file, buffer, count, pos);
+ break;
+ }
+ if ((ssize_t) size > 0)
+ *offset = pos + size;
+ return size;
+}
+
+static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t * offset)
+{
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+ snd_info_buffer_t *buf;
+ size_t size = 0;
+ loff_t pos;
+
+ data = file->private_data;
+ snd_assert(data != NULL, return -ENXIO);
+ entry = data->entry;
+ pos = *offset;
+ if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
+ return -EIO;
+ if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
+ return -EIO;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_TEXT:
+ buf = data->wbuffer;
+ if (buf == NULL)
+ return -EIO;
+ if (pos >= buf->len)
+ return -ENOMEM;
+ size = buf->len - pos;
+ size = min(count, size);
+ if (copy_from_user(buf->buffer + pos, buffer, size))
+ return -EFAULT;
+ if ((long)buf->size < pos + size)
+ buf->size = pos + size;
+ break;
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->write)
+ size = entry->c.ops->write(entry,
+ data->file_private_data,
+ file, buffer, count, pos);
+ break;
+ }
+ if ((ssize_t) size > 0)
+ *offset = pos + size;
+ return size;
+}
+
+static int snd_info_entry_open(struct inode *inode, struct file *file)
+{
+ snd_info_entry_t *entry;
+ snd_info_private_data_t *data;
+ snd_info_buffer_t *buffer;
+ struct proc_dir_entry *p;
+ int mode, err;
+
+ down(&info_mutex);
+ p = PDE(inode);
+ entry = p == NULL ? NULL : (snd_info_entry_t *)p->data;
+ if (entry == NULL || entry->disconnected) {
+ up(&info_mutex);
+ return -ENODEV;
+ }
+ if (!try_module_get(entry->module)) {
+ err = -EFAULT;
+ goto __error1;
+ }
+ mode = file->f_flags & O_ACCMODE;
+ if (mode == O_RDONLY || mode == O_RDWR) {
+ if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
+ !entry->c.text.read_size) ||
+ (entry->content == SNDRV_INFO_CONTENT_DATA &&
+ entry->c.ops->read == NULL)) {
+ err = -ENODEV;
+ goto __error;
+ }
+ }
+ if (mode == O_WRONLY || mode == O_RDWR) {
+ if ((entry->content == SNDRV_INFO_CONTENT_TEXT &&
+ !entry->c.text.write_size) ||
+ (entry->content == SNDRV_INFO_CONTENT_DATA &&
+ entry->c.ops->write == NULL)) {
+ err = -ENODEV;
+ goto __error;
+ }
+ }
+ data = kcalloc(1, sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ err = -ENOMEM;
+ goto __error;
+ }
+ data->entry = entry;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_TEXT:
+ if (mode == O_RDONLY || mode == O_RDWR) {
+ buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
+ if (buffer == NULL) {
+ kfree(data);
+ err = -ENOMEM;
+ goto __error;
+ }
+ buffer->len = (entry->c.text.read_size +
+ (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+ buffer->buffer = vmalloc(buffer->len);
+ if (buffer->buffer == NULL) {
+ kfree(buffer);
+ kfree(data);
+ err = -ENOMEM;
+ goto __error;
+ }
+ buffer->curr = buffer->buffer;
+ data->rbuffer = buffer;
+ }
+ if (mode == O_WRONLY || mode == O_RDWR) {
+ buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL);
+ if (buffer == NULL) {
+ if (mode == O_RDWR) {
+ vfree(data->rbuffer->buffer);
+ kfree(data->rbuffer);
+ }
+ kfree(data);
+ err = -ENOMEM;
+ goto __error;
+ }
+ buffer->len = (entry->c.text.write_size +
+ (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+ buffer->buffer = vmalloc(buffer->len);
+ if (buffer->buffer == NULL) {
+ if (mode == O_RDWR) {
+ vfree(data->rbuffer->buffer);
+ kfree(data->rbuffer);
+ }
+ kfree(buffer);
+ kfree(data);
+ err = -ENOMEM;
+ goto __error;
+ }
+ buffer->curr = buffer->buffer;
+ data->wbuffer = buffer;
+ }
+ break;
+ case SNDRV_INFO_CONTENT_DATA: /* data */
+ if (entry->c.ops->open) {
+ if ((err = entry->c.ops->open(entry, mode,
+ &data->file_private_data)) < 0) {
+ kfree(data);
+ goto __error;
+ }
+ }
+ break;
+ }
+ file->private_data = data;
+ up(&info_mutex);
+ if (entry->content == SNDRV_INFO_CONTENT_TEXT &&
+ (mode == O_RDONLY || mode == O_RDWR)) {
+ if (entry->c.text.read) {
+ down(&entry->access);
+ entry->c.text.read(entry, data->rbuffer);
+ up(&entry->access);
+ }
+ }
+ return 0;
+
+ __error:
+ module_put(entry->module);
+ __error1:
+ up(&info_mutex);
+ return err;
+}
+
+static int snd_info_entry_release(struct inode *inode, struct file *file)
+{
+ snd_info_entry_t *entry;
+ snd_info_private_data_t *data;
+ int mode;
+
+ mode = file->f_flags & O_ACCMODE;
+ data = file->private_data;
+ entry = data->entry;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_TEXT:
+ if (mode == O_RDONLY || mode == O_RDWR) {
+ vfree(data->rbuffer->buffer);
+ kfree(data->rbuffer);
+ }
+ if (mode == O_WRONLY || mode == O_RDWR) {
+ if (entry->c.text.write) {
+ entry->c.text.write(entry, data->wbuffer);
+ if (data->wbuffer->error) {
+ snd_printk(KERN_WARNING "data write error to %s (%i)\n",
+ entry->name,
+ data->wbuffer->error);
+ }
+ }
+ vfree(data->wbuffer->buffer);
+ kfree(data->wbuffer);
+ }
+ break;
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->release)
+ entry->c.ops->release(entry, mode,
+ data->file_private_data);
+ break;
+ }
+ module_put(entry->module);
+ kfree(data);
+ return 0;
+}
+
+static unsigned int snd_info_entry_poll(struct file *file, poll_table * wait)
+{
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+ unsigned int mask;
+
+ data = file->private_data;
+ if (data == NULL)
+ return 0;
+ entry = data->entry;
+ mask = 0;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->poll)
+ return entry->c.ops->poll(entry,
+ data->file_private_data,
+ file, wait);
+ if (entry->c.ops->read)
+ mask |= POLLIN | POLLRDNORM;
+ if (entry->c.ops->write)
+ mask |= POLLOUT | POLLWRNORM;
+ break;
+ }
+ return mask;
+}
+
+static inline int _snd_info_entry_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+
+ data = file->private_data;
+ if (data == NULL)
+ return 0;
+ entry = data->entry;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->ioctl)
+ return entry->c.ops->ioctl(entry,
+ data->file_private_data,
+ file, cmd, arg);
+ break;
+ }
+ return -ENOTTY;
+}
+
+/* FIXME: need to unlock BKL to allow preemption */
+static int snd_info_entry_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err;
+ unlock_kernel();
+ err = _snd_info_entry_ioctl(inode, file, cmd, arg);
+ lock_kernel();
+ return err;
+}
+
+static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ snd_info_private_data_t *data;
+ struct snd_info_entry *entry;
+
+ data = file->private_data;
+ if (data == NULL)
+ return 0;
+ entry = data->entry;
+ switch (entry->content) {
+ case SNDRV_INFO_CONTENT_DATA:
+ if (entry->c.ops->mmap)
+ return entry->c.ops->mmap(entry,
+ data->file_private_data,
+ inode, file, vma);
+ break;
+ }
+ return -ENXIO;
+}
+
+static struct file_operations snd_info_entry_operations =
+{
+ .owner = THIS_MODULE,
+ .llseek = snd_info_entry_llseek,
+ .read = snd_info_entry_read,
+ .write = snd_info_entry_write,
+ .poll = snd_info_entry_poll,
+ .ioctl = snd_info_entry_ioctl,
+ .mmap = snd_info_entry_mmap,
+ .open = snd_info_entry_open,
+ .release = snd_info_entry_release,
+};
+
+/**
+ * snd_create_proc_entry - create a procfs entry
+ * @name: the name of the proc file
+ * @mode: the file permission bits, S_Ixxx
+ * @parent: the parent proc-directory entry
+ *
+ * Creates a new proc file entry with the given name and permission
+ * on the given directory.
+ *
+ * Returns the pointer of new instance or NULL on failure.
+ */
+static struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode,
+ struct proc_dir_entry *parent)
+{
+ struct proc_dir_entry *p;
+ p = create_proc_entry(name, mode, parent);
+ if (p)
+ snd_info_entry_prepare(p);
+ return p;
+}
+
+int __init snd_info_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = snd_create_proc_entry("asound", S_IFDIR | S_IRUGO | S_IXUGO, &proc_root);
+ if (p == NULL)
+ return -ENOMEM;
+ snd_proc_root = p;
+#ifdef CONFIG_SND_OSSEMUL
+ {
+ snd_info_entry_t *entry;
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "oss", NULL)) == NULL)
+ return -ENOMEM;
+ entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ snd_oss_root = entry;
+ }
+#endif
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+ {
+ snd_info_entry_t *entry;
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "seq", NULL)) == NULL)
+ return -ENOMEM;
+ entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ snd_seq_root = entry;
+ }
+#endif
+ snd_info_version_init();
+ snd_memory_info_init();
+ snd_minor_info_init();
+ snd_minor_info_oss_init();
+ snd_card_info_init();
+ return 0;
+}
+
+int __exit snd_info_done(void)
+{
+ snd_card_info_done();
+ snd_minor_info_oss_done();
+ snd_minor_info_done();
+ snd_memory_info_done();
+ snd_info_version_done();
+ if (snd_proc_root) {
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+ if (snd_seq_root)
+ snd_info_unregister(snd_seq_root);
+#endif
+#ifdef CONFIG_SND_OSSEMUL
+ if (snd_oss_root)
+ snd_info_unregister(snd_oss_root);
+#endif
+ snd_remove_proc_entry(&proc_root, snd_proc_root);
+ }
+ return 0;
+}
+
+/*
+
+ */
+
+
+/*
+ * create a card proc file
+ * called from init.c
+ */
+int snd_info_card_create(snd_card_t * card)
+{
+ char str[8];
+ snd_info_entry_t *entry;
+
+ snd_assert(card != NULL, return -ENXIO);
+
+ sprintf(str, "card%i", card->number);
+ if ((entry = snd_info_create_module_entry(card->module, str, NULL)) == NULL)
+ return -ENOMEM;
+ entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ card->proc_root = entry;
+ return 0;
+}
+
+/*
+ * register the card proc file
+ * called from init.c
+ */
+int snd_info_card_register(snd_card_t * card)
+{
+ struct proc_dir_entry *p;
+
+ snd_assert(card != NULL, return -ENXIO);
+
+ if (!strcmp(card->id, card->proc_root->name))
+ return 0;
+
+ p = proc_symlink(card->id, snd_proc_root, card->proc_root->name);
+ if (p == NULL)
+ return -ENOMEM;
+ card->proc_root_link = p;
+ return 0;
+}
+
+/*
+ * de-register the card proc file
+ * called from init.c
+ */
+int snd_info_card_free(snd_card_t * card)
+{
+ snd_assert(card != NULL, return -ENXIO);
+ if (card->proc_root_link) {
+ snd_remove_proc_entry(snd_proc_root, card->proc_root_link);
+ card->proc_root_link = NULL;
+ }
+ if (card->proc_root) {
+ snd_info_unregister(card->proc_root);
+ card->proc_root = NULL;
+ }
+ return 0;
+}
+
+
+/**
+ * snd_info_get_line - read one line from the procfs buffer
+ * @buffer: the procfs buffer
+ * @line: the buffer to store
+ * @len: the max. buffer size - 1
+ *
+ * Reads one line from the buffer and stores the string.
+ *
+ * Returns zero if successful, or 1 if error or EOF.
+ */
+int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len)
+{
+ int c = -1;
+
+ if (len <= 0 || buffer->stop || buffer->error)
+ return 1;
+ while (--len > 0) {
+ c = *buffer->curr++;
+ if (c == '\n') {
+ if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+ buffer->stop = 1;
+ }
+ break;
+ }
+ *line++ = c;
+ if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+ buffer->stop = 1;
+ break;
+ }
+ }
+ while (c != '\n' && !buffer->stop) {
+ c = *buffer->curr++;
+ if ((buffer->curr - buffer->buffer) >= (long)buffer->size) {
+ buffer->stop = 1;
+ }
+ }
+ *line = '\0';
+ return 0;
+}
+
+/**
+ * snd_info_get_line - parse a string token
+ * @dest: the buffer to store the string token
+ * @src: the original string
+ * @len: the max. length of token - 1
+ *
+ * Parses the original string and copy a token to the given
+ * string buffer.
+ *
+ * Returns the updated pointer of the original string so that
+ * it can be used for the next call.
+ */
+char *snd_info_get_str(char *dest, char *src, int len)
+{
+ int c;
+
+ while (*src == ' ' || *src == '\t')
+ src++;
+ if (*src == '"' || *src == '\'') {
+ c = *src++;
+ while (--len > 0 && *src && *src != c) {
+ *dest++ = *src++;
+ }
+ if (*src == c)
+ src++;
+ } else {
+ while (--len > 0 && *src && *src != ' ' && *src != '\t') {
+ *dest++ = *src++;
+ }
+ }
+ *dest = 0;
+ while (*src == ' ' || *src == '\t')
+ src++;
+ return src;
+}
+
+/**
+ * snd_info_create_entry - create an info entry
+ * @name: the proc file name
+ *
+ * Creates an info entry with the given file name and initializes as
+ * the default state.
+ *
+ * Usually called from other functions such as
+ * snd_info_create_card_entry().
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+static snd_info_entry_t *snd_info_create_entry(const char *name)
+{
+ snd_info_entry_t *entry;
+ entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+ if (entry == NULL)
+ return NULL;
+ entry->name = snd_kmalloc_strdup(name, GFP_KERNEL);
+ if (entry->name == NULL) {
+ kfree(entry);
+ return NULL;
+ }
+ entry->mode = S_IFREG | S_IRUGO;
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ init_MUTEX(&entry->access);
+ return entry;
+}
+
+/**
+ * snd_info_create_module_entry - create an info entry for the given module
+ * @module: the module pointer
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given module.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_info_entry_t *snd_info_create_module_entry(struct module * module,
+ const char *name,
+ snd_info_entry_t *parent)
+{
+ snd_info_entry_t *entry = snd_info_create_entry(name);
+ if (entry) {
+ entry->module = module;
+ entry->parent = parent;
+ }
+ return entry;
+}
+
+/**
+ * snd_info_create_card_entry - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given card.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
+snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card,
+ const char *name,
+ snd_info_entry_t * parent)
+{
+ snd_info_entry_t *entry = snd_info_create_entry(name);
+ if (entry) {
+ entry->module = card->module;
+ entry->card = card;
+ entry->parent = parent;
+ }
+ return entry;
+}
+
+static int snd_info_dev_free_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = device->device_data;
+ snd_info_free_entry(entry);
+ return 0;
+}
+
+static int snd_info_dev_register_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = device->device_data;
+ return snd_info_register(entry);
+}
+
+static int snd_info_dev_disconnect_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = device->device_data;
+ entry->disconnected = 1;
+ return 0;
+}
+
+static int snd_info_dev_unregister_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = device->device_data;
+ return snd_info_unregister(entry);
+}
+
+/**
+ * snd_card_proc_new - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @entryp: the pointer to store the new info entry
+ *
+ * Creates a new info entry and assigns it to the given card.
+ * Unlike snd_info_create_card_entry(), this function registers the
+ * info entry as an ALSA device component, so that it can be
+ * unregistered/released without explicit call.
+ * Also, you don't have to register this entry via snd_info_register(),
+ * since this will be registered by snd_card_register() automatically.
+ *
+ * The parent is assumed as card->proc_root.
+ *
+ * For releasing this entry, use snd_device_free() instead of
+ * snd_info_free_entry().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_card_proc_new(snd_card_t *card, const char *name,
+ snd_info_entry_t **entryp)
+{
+ static snd_device_ops_t ops = {
+ .dev_free = snd_info_dev_free_entry,
+ .dev_register = snd_info_dev_register_entry,
+ .dev_disconnect = snd_info_dev_disconnect_entry,
+ .dev_unregister = snd_info_dev_unregister_entry
+ };
+ snd_info_entry_t *entry;
+ int err;
+
+ entry = snd_info_create_card_entry(card, name, card->proc_root);
+ if (! entry)
+ return -ENOMEM;
+ if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) {
+ snd_info_free_entry(entry);
+ return err;
+ }
+ if (entryp)
+ *entryp = entry;
+ return 0;
+}
+
+/**
+ * snd_info_free_entry - release the info entry
+ * @entry: the info entry
+ *
+ * Releases the info entry. Don't call this after registered.
+ */
+void snd_info_free_entry(snd_info_entry_t * entry)
+{
+ if (entry == NULL)
+ return;
+ kfree(entry->name);
+ if (entry->private_free)
+ entry->private_free(entry);
+ kfree(entry);
+}
+
+/**
+ * snd_info_register - register the info entry
+ * @entry: the info entry
+ *
+ * Registers the proc info entry.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_info_register(snd_info_entry_t * entry)
+{
+ struct proc_dir_entry *root, *p = NULL;
+
+ snd_assert(entry != NULL, return -ENXIO);
+ root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+ down(&info_mutex);
+ p = snd_create_proc_entry(entry->name, entry->mode, root);
+ if (!p) {
+ up(&info_mutex);
+ return -ENOMEM;
+ }
+ p->owner = entry->module;
+ if (!S_ISDIR(entry->mode))
+ p->proc_fops = &snd_info_entry_operations;
+ p->size = entry->size;
+ p->data = entry;
+ entry->p = p;
+ up(&info_mutex);
+ return 0;
+}
+
+/**
+ * snd_info_unregister - de-register the info entry
+ * @entry: the info entry
+ *
+ * De-registers the info entry and releases the instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_info_unregister(snd_info_entry_t * entry)
+{
+ struct proc_dir_entry *root;
+
+ snd_assert(entry != NULL && entry->p != NULL, return -ENXIO);
+ root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+ snd_assert(root, return -ENXIO);
+ down(&info_mutex);
+ snd_remove_proc_entry(root, entry->p);
+ up(&info_mutex);
+ snd_info_free_entry(entry);
+ return 0;
+}
+
+/*
+
+ */
+
+static snd_info_entry_t *snd_info_version_entry = NULL;
+
+static void snd_info_version_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ snd_iprintf(buffer,
+ "Advanced Linux Sound Architecture Driver Version "
+ CONFIG_SND_VERSION CONFIG_SND_DATE ".\n"
+ );
+}
+
+static int __init snd_info_version_init(void)
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
+ if (entry == NULL)
+ return -ENOMEM;
+ entry->c.text.read_size = 256;
+ entry->c.text.read = snd_info_version_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ snd_info_version_entry = entry;
+ return 0;
+}
+
+static int __exit snd_info_version_done(void)
+{
+ if (snd_info_version_entry)
+ snd_info_unregister(snd_info_version_entry);
+ return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/sound/core/info_oss.c b/sound/core/info_oss.c
new file mode 100644
index 00000000000..f9e4ce44345
--- /dev/null
+++ b/sound/core/info_oss.c
@@ -0,0 +1,137 @@
+/*
+ * Information interface for ALSA driver
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/utsname.h>
+
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+
+/*
+ * OSS compatible part
+ */
+
+static DECLARE_MUTEX(strings);
+static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
+static snd_info_entry_t *snd_sndstat_proc_entry;
+
+int snd_oss_info_register(int dev, int num, char *string)
+{
+ char *x;
+
+ snd_assert(dev >= 0 && dev < SNDRV_OSS_INFO_DEV_COUNT, return -ENXIO);
+ snd_assert(num >= 0 && num < SNDRV_CARDS, return -ENXIO);
+ down(&strings);
+ if (string == NULL) {
+ if ((x = snd_sndstat_strings[num][dev]) != NULL) {
+ kfree(x);
+ x = NULL;
+ }
+ } else {
+ x = snd_kmalloc_strdup(string, GFP_KERNEL);
+ if (x == NULL) {
+ up(&strings);
+ return -ENOMEM;
+ }
+ }
+ snd_sndstat_strings[num][dev] = x;
+ up(&strings);
+ return 0;
+}
+
+extern void snd_card_info_read_oss(snd_info_buffer_t * buffer);
+
+static int snd_sndstat_show_strings(snd_info_buffer_t * buf, char *id, int dev)
+{
+ int idx, ok = -1;
+ char *str;
+
+ snd_iprintf(buf, "\n%s:", id);
+ down(&strings);
+ for (idx = 0; idx < SNDRV_CARDS; idx++) {
+ str = snd_sndstat_strings[idx][dev];
+ if (str) {
+ if (ok < 0) {
+ snd_iprintf(buf, "\n");
+ ok++;
+ }
+ snd_iprintf(buf, "%i: %s\n", idx, str);
+ }
+ }
+ up(&strings);
+ if (ok < 0)
+ snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
+ return ok;
+}
+
+static void snd_sndstat_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA v" CONFIG_SND_VERSION " emulation code)\n");
+ snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n",
+ system_utsname.sysname,
+ system_utsname.nodename,
+ system_utsname.release,
+ system_utsname.version,
+ system_utsname.machine);
+ snd_iprintf(buffer, "Config options: 0\n");
+ snd_iprintf(buffer, "\nInstalled drivers: \n");
+ snd_iprintf(buffer, "Type 10: ALSA emulation\n");
+ snd_iprintf(buffer, "\nCard config: \n");
+ snd_card_info_read_oss(buffer);
+ snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO);
+ snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH);
+ snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI);
+ snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS);
+ snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS);
+}
+
+int snd_info_minor_register(void)
+{
+ snd_info_entry_t *entry;
+
+ memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings));
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) {
+ entry->c.text.read_size = 2048;
+ entry->c.text.read = snd_sndstat_proc_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_sndstat_proc_entry = entry;
+ return 0;
+}
+
+int snd_info_minor_unregister(void)
+{
+ if (snd_sndstat_proc_entry) {
+ snd_info_unregister(snd_sndstat_proc_entry);
+ snd_sndstat_proc_entry = NULL;
+ }
+ return 0;
+}
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/sound/core/init.c b/sound/core/init.c
new file mode 100644
index 00000000000..3f1fa8eabb7
--- /dev/null
+++ b/sound/core/init.c
@@ -0,0 +1,887 @@
+/*
+ * Initialization routines
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/ctype.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+struct snd_shutdown_f_ops {
+ struct file_operations f_ops;
+ struct snd_shutdown_f_ops *next;
+};
+
+unsigned int snd_cards_lock = 0; /* locked for registering/using */
+snd_card_t *snd_cards[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = NULL};
+DEFINE_RWLOCK(snd_card_rwlock);
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+int (*snd_mixer_oss_notify_callback)(snd_card_t *card, int free_flag);
+#endif
+
+static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ snd_iprintf(buffer, "%s\n", entry->card->id);
+}
+
+static void snd_card_free_thread(void * __card);
+
+/**
+ * snd_card_new - create and initialize a soundcard structure
+ * @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
+ * @xid: card identification (ASCII string)
+ * @module: top level module for locking
+ * @extra_size: allocate this extra size after the main soundcard structure
+ *
+ * Creates and initializes a soundcard structure.
+ *
+ * Returns kmallocated snd_card_t structure. Creates the ALSA control interface
+ * (which is blocked until snd_card_register function is called).
+ */
+snd_card_t *snd_card_new(int idx, const char *xid,
+ struct module *module, int extra_size)
+{
+ snd_card_t *card;
+ int err;
+
+ if (extra_size < 0)
+ extra_size = 0;
+ card = kcalloc(1, sizeof(*card) + extra_size, GFP_KERNEL);
+ if (card == NULL)
+ return NULL;
+ if (xid) {
+ if (!snd_info_check_reserved_words(xid))
+ goto __error;
+ strlcpy(card->id, xid, sizeof(card->id));
+ }
+ err = 0;
+ write_lock(&snd_card_rwlock);
+ if (idx < 0) {
+ int idx2;
+ for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
+ if (~snd_cards_lock & idx & 1<<idx2) {
+ idx = idx2;
+ if (idx >= snd_ecards_limit)
+ snd_ecards_limit = idx + 1;
+ break;
+ }
+ } else if (idx < snd_ecards_limit) {
+ if (snd_cards_lock & (1 << idx))
+ err = -ENODEV; /* invalid */
+ } else if (idx < SNDRV_CARDS)
+ snd_ecards_limit = idx + 1; /* increase the limit */
+ else
+ err = -ENODEV;
+ if (idx < 0 || err < 0) {
+ write_unlock(&snd_card_rwlock);
+ snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i)\n", idx, snd_ecards_limit - 1);
+ goto __error;
+ }
+ snd_cards_lock |= 1 << idx; /* lock it */
+ write_unlock(&snd_card_rwlock);
+ card->number = idx;
+ card->module = module;
+ INIT_LIST_HEAD(&card->devices);
+ init_rwsem(&card->controls_rwsem);
+ rwlock_init(&card->ctl_files_rwlock);
+ INIT_LIST_HEAD(&card->controls);
+ INIT_LIST_HEAD(&card->ctl_files);
+ spin_lock_init(&card->files_lock);
+ init_waitqueue_head(&card->shutdown_sleep);
+ INIT_WORK(&card->free_workq, snd_card_free_thread, card);
+#ifdef CONFIG_PM
+ init_MUTEX(&card->power_lock);
+ init_waitqueue_head(&card->power_sleep);
+#endif
+ /* the control interface cannot be accessed from the user space until */
+ /* snd_cards_bitmask and snd_cards are set with snd_card_register */
+ if ((err = snd_ctl_create(card)) < 0) {
+ snd_printd("unable to register control minors\n");
+ goto __error;
+ }
+ if ((err = snd_info_card_create(card)) < 0) {
+ snd_printd("unable to create card info\n");
+ goto __error_ctl;
+ }
+ if (extra_size > 0)
+ card->private_data = (char *)card + sizeof(snd_card_t);
+ return card;
+
+ __error_ctl:
+ snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
+ __error:
+ kfree(card);
+ return NULL;
+}
+
+static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
+{
+ return POLLERR | POLLNVAL;
+}
+
+/**
+ * snd_card_disconnect - disconnect all APIs from the file-operations (user space)
+ * @card: soundcard structure
+ *
+ * Disconnects all APIs from the file-operations (user space).
+ *
+ * Returns zero, otherwise a negative error code.
+ *
+ * Note: The current implementation replaces all active file->f_op with special
+ * dummy file operations (they do nothing except release).
+ */
+int snd_card_disconnect(snd_card_t * card)
+{
+ struct snd_monitor_file *mfile;
+ struct file *file;
+ struct snd_shutdown_f_ops *s_f_ops;
+ struct file_operations *f_ops, *old_f_ops;
+ int err;
+
+ spin_lock(&card->files_lock);
+ if (card->shutdown) {
+ spin_unlock(&card->files_lock);
+ return 0;
+ }
+ card->shutdown = 1;
+ spin_unlock(&card->files_lock);
+
+ /* phase 1: disable fops (user space) operations for ALSA API */
+ write_lock(&snd_card_rwlock);
+ snd_cards[card->number] = NULL;
+ write_unlock(&snd_card_rwlock);
+
+ /* phase 2: replace file->f_op with special dummy operations */
+
+ spin_lock(&card->files_lock);
+ mfile = card->files;
+ while (mfile) {
+ file = mfile->file;
+
+ /* it's critical part, use endless loop */
+ /* we have no room to fail */
+ s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC);
+ if (s_f_ops == NULL)
+ panic("Atomic allocation failed for snd_shutdown_f_ops!");
+
+ f_ops = &s_f_ops->f_ops;
+
+ memset(f_ops, 0, sizeof(*f_ops));
+ f_ops->owner = file->f_op->owner;
+ f_ops->release = file->f_op->release;
+ f_ops->poll = snd_disconnect_poll;
+
+ s_f_ops->next = card->s_f_ops;
+ card->s_f_ops = s_f_ops;
+
+ f_ops = fops_get(f_ops);
+
+ old_f_ops = file->f_op;
+ file->f_op = f_ops; /* must be atomic */
+ fops_put(old_f_ops);
+
+ mfile = mfile->next;
+ }
+ spin_unlock(&card->files_lock);
+
+ /* phase 3: notify all connected devices about disconnection */
+ /* at this point, they cannot respond to any calls except release() */
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+ if (snd_mixer_oss_notify_callback)
+ snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
+#endif
+
+ /* notify all devices that we are disconnected */
+ err = snd_device_disconnect_all(card);
+ if (err < 0)
+ snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number);
+
+ return 0;
+}
+
+#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
+static void snd_generic_device_unregister(struct snd_generic_device *dev);
+#endif
+
+/**
+ * snd_card_free - frees given soundcard structure
+ * @card: soundcard structure
+ *
+ * This function releases the soundcard structure and the all assigned
+ * devices automatically. That is, you don't have to release the devices
+ * by yourself.
+ *
+ * Returns zero. Frees all associated devices and frees the control
+ * interface associated to given soundcard.
+ */
+int snd_card_free(snd_card_t * card)
+{
+ struct snd_shutdown_f_ops *s_f_ops;
+
+ if (card == NULL)
+ return -EINVAL;
+ write_lock(&snd_card_rwlock);
+ snd_cards[card->number] = NULL;
+ write_unlock(&snd_card_rwlock);
+
+#ifdef CONFIG_PM
+ wake_up(&card->power_sleep);
+#ifdef CONFIG_SND_GENERIC_PM
+ if (card->pm_dev) {
+ snd_generic_device_unregister(card->pm_dev);
+ card->pm_dev = NULL;
+ }
+#endif
+#endif
+
+ /* wait, until all devices are ready for the free operation */
+ wait_event(card->shutdown_sleep, card->files == NULL);
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+ if (snd_mixer_oss_notify_callback)
+ snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
+#endif
+ if (snd_device_free_all(card, SNDRV_DEV_CMD_PRE) < 0) {
+ snd_printk(KERN_ERR "unable to free all devices (pre)\n");
+ /* Fatal, but this situation should never occur */
+ }
+ if (snd_device_free_all(card, SNDRV_DEV_CMD_NORMAL) < 0) {
+ snd_printk(KERN_ERR "unable to free all devices (normal)\n");
+ /* Fatal, but this situation should never occur */
+ }
+ if (snd_device_free_all(card, SNDRV_DEV_CMD_POST) < 0) {
+ snd_printk(KERN_ERR "unable to free all devices (post)\n");
+ /* Fatal, but this situation should never occur */
+ }
+ if (card->private_free)
+ card->private_free(card);
+ if (card->proc_id)
+ snd_info_unregister(card->proc_id);
+ if (snd_info_card_free(card) < 0) {
+ snd_printk(KERN_WARNING "unable to free card info\n");
+ /* Not fatal error */
+ }
+ while (card->s_f_ops) {
+ s_f_ops = card->s_f_ops;
+ card->s_f_ops = s_f_ops->next;
+ kfree(s_f_ops);
+ }
+ write_lock(&snd_card_rwlock);
+ snd_cards_lock &= ~(1 << card->number);
+ write_unlock(&snd_card_rwlock);
+ kfree(card);
+ return 0;
+}
+
+static void snd_card_free_thread(void * __card)
+{
+ snd_card_t *card = __card;
+ struct module * module = card->module;
+
+ if (!try_module_get(module)) {
+ snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number);
+ module = NULL;
+ }
+
+ snd_card_free(card);
+
+ module_put(module);
+}
+
+/**
+ * snd_card_free_in_thread - call snd_card_free() in thread
+ * @card: soundcard structure
+ *
+ * This function schedules the call of snd_card_free() function in a
+ * work queue. When all devices are released (non-busy), the work
+ * is woken up and calls snd_card_free().
+ *
+ * When a card can be disconnected at any time by hotplug service,
+ * this function should be used in disconnect (or detach) callback
+ * instead of calling snd_card_free() directly.
+ *
+ * Returns - zero otherwise a negative error code if the start of thread failed.
+ */
+int snd_card_free_in_thread(snd_card_t * card)
+{
+ if (card->files == NULL) {
+ snd_card_free(card);
+ return 0;
+ }
+
+ if (schedule_work(&card->free_workq))
+ return 0;
+
+ snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number);
+ /* try to free the structure immediately */
+ snd_card_free(card);
+ return -EFAULT;
+}
+
+static void choose_default_id(snd_card_t * card)
+{
+ int i, len, idx_flag = 0, loops = 8;
+ char *id, *spos;
+
+ id = spos = card->shortname;
+ while (*id != '\0') {
+ if (*id == ' ')
+ spos = id + 1;
+ id++;
+ }
+ id = card->id;
+ while (*spos != '\0' && !isalnum(*spos))
+ spos++;
+ if (isdigit(*spos))
+ *id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D';
+ while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) {
+ if (isalnum(*spos))
+ *id++ = *spos;
+ spos++;
+ }
+ *id = '\0';
+
+ id = card->id;
+
+ if (*id == '\0')
+ strcpy(id, "default");
+
+ while (1) {
+ if (loops-- == 0) {
+ snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id);
+ strcpy(card->id, card->proc_root->name);
+ return;
+ }
+ if (!snd_info_check_reserved_words(id))
+ goto __change;
+ for (i = 0; i < snd_ecards_limit; i++) {
+ if (snd_cards[i] && !strcmp(snd_cards[i]->id, id))
+ goto __change;
+ }
+ break;
+
+ __change:
+ len = strlen(id);
+ if (idx_flag)
+ id[len-1]++;
+ else if ((size_t)len <= sizeof(card->id) - 3) {
+ strcat(id, "_1");
+ idx_flag++;
+ } else {
+ spos = id + len - 2;
+ if ((size_t)len <= sizeof(card->id) - 2)
+ spos++;
+ *spos++ = '_';
+ *spos++ = '1';
+ *spos++ = '\0';
+ idx_flag++;
+ }
+ }
+}
+
+/**
+ * snd_card_register - register the soundcard
+ * @card: soundcard structure
+ *
+ * This function registers all the devices assigned to the soundcard.
+ * Until calling this, the ALSA control interface is blocked from the
+ * external accesses. Thus, you should call this function at the end
+ * of the initialization of the card.
+ *
+ * Returns zero otherwise a negative error code if the registrain failed.
+ */
+int snd_card_register(snd_card_t * card)
+{
+ int err;
+ snd_info_entry_t *entry;
+
+ snd_runtime_check(card != NULL, return -EINVAL);
+ if ((err = snd_device_register_all(card)) < 0)
+ return err;
+ write_lock(&snd_card_rwlock);
+ if (snd_cards[card->number]) {
+ /* already registered */
+ write_unlock(&snd_card_rwlock);
+ return 0;
+ }
+ if (card->id[0] == '\0')
+ choose_default_id(card);
+ snd_cards[card->number] = card;
+ write_unlock(&snd_card_rwlock);
+ if ((err = snd_info_card_register(card)) < 0) {
+ snd_printd("unable to create card info\n");
+ goto __skip_info;
+ }
+ if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
+ snd_printd("unable to create card entry\n");
+ goto __skip_info;
+ }
+ entry->c.text.read_size = PAGE_SIZE;
+ entry->c.text.read = snd_card_id_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ card->proc_id = entry;
+ __skip_info:
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+ if (snd_mixer_oss_notify_callback)
+ snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
+#endif
+ return 0;
+}
+
+static snd_info_entry_t *snd_card_info_entry = NULL;
+
+static void snd_card_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int idx, count;
+ snd_card_t *card;
+
+ for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+ read_lock(&snd_card_rwlock);
+ if ((card = snd_cards[idx]) != NULL) {
+ count++;
+ snd_iprintf(buffer, "%i [%-15s]: %s - %s\n",
+ idx,
+ card->id,
+ card->driver,
+ card->shortname);
+ snd_iprintf(buffer, " %s\n",
+ card->longname);
+ }
+ read_unlock(&snd_card_rwlock);
+ }
+ if (!count)
+ snd_iprintf(buffer, "--- no soundcards ---\n");
+}
+
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+
+void snd_card_info_read_oss(snd_info_buffer_t * buffer)
+{
+ int idx, count;
+ snd_card_t *card;
+
+ for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+ read_lock(&snd_card_rwlock);
+ if ((card = snd_cards[idx]) != NULL) {
+ count++;
+ snd_iprintf(buffer, "%s\n", card->longname);
+ }
+ read_unlock(&snd_card_rwlock);
+ }
+ if (!count) {
+ snd_iprintf(buffer, "--- no soundcards ---\n");
+ }
+}
+
+#endif
+
+#ifdef MODULE
+static snd_info_entry_t *snd_card_module_info_entry;
+static void snd_card_module_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int idx;
+ snd_card_t *card;
+
+ for (idx = 0; idx < SNDRV_CARDS; idx++) {
+ read_lock(&snd_card_rwlock);
+ if ((card = snd_cards[idx]) != NULL)
+ snd_iprintf(buffer, "%i %s\n", idx, card->module->name);
+ read_unlock(&snd_card_rwlock);
+ }
+}
+#endif
+
+int __init snd_card_info_init(void)
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
+ snd_runtime_check(entry != NULL, return -ENOMEM);
+ entry->c.text.read_size = PAGE_SIZE;
+ entry->c.text.read = snd_card_info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ snd_card_info_entry = entry;
+
+#ifdef MODULE
+ entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL);
+ if (entry) {
+ entry->c.text.read_size = PAGE_SIZE;
+ entry->c.text.read = snd_card_module_info_read;
+ if (snd_info_register(entry) < 0)
+ snd_info_free_entry(entry);
+ else
+ snd_card_module_info_entry = entry;
+ }
+#endif
+
+ return 0;
+}
+
+int __exit snd_card_info_done(void)
+{
+ if (snd_card_info_entry)
+ snd_info_unregister(snd_card_info_entry);
+#ifdef MODULE
+ if (snd_card_module_info_entry)
+ snd_info_unregister(snd_card_module_info_entry);
+#endif
+ return 0;
+}
+
+/**
+ * snd_component_add - add a component string
+ * @card: soundcard structure
+ * @component: the component id string
+ *
+ * This function adds the component id string to the supported list.
+ * The component can be referred from the alsa-lib.
+ *
+ * Returns zero otherwise a negative error code.
+ */
+
+int snd_component_add(snd_card_t *card, const char *component)
+{
+ char *ptr;
+ int len = strlen(component);
+
+ ptr = strstr(card->components, component);
+ if (ptr != NULL) {
+ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
+ return 1;
+ }
+ if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
+ snd_BUG();
+ return -ENOMEM;
+ }
+ if (card->components[0] != '\0')
+ strcat(card->components, " ");
+ strcat(card->components, component);
+ return 0;
+}
+
+/**
+ * snd_card_file_add - add the file to the file list of the card
+ * @card: soundcard structure
+ * @file: file pointer
+ *
+ * This function adds the file to the file linked-list of the card.
+ * This linked-list is used to keep tracking the connection state,
+ * and to avoid the release of busy resources by hotplug.
+ *
+ * Returns zero or a negative error code.
+ */
+int snd_card_file_add(snd_card_t *card, struct file *file)
+{
+ struct snd_monitor_file *mfile;
+
+ mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
+ if (mfile == NULL)
+ return -ENOMEM;
+ mfile->file = file;
+ mfile->next = NULL;
+ spin_lock(&card->files_lock);
+ if (card->shutdown) {
+ spin_unlock(&card->files_lock);
+ kfree(mfile);
+ return -ENODEV;
+ }
+ mfile->next = card->files;
+ card->files = mfile;
+ spin_unlock(&card->files_lock);
+ return 0;
+}
+
+/**
+ * snd_card_file_remove - remove the file from the file list
+ * @card: soundcard structure
+ * @file: file pointer
+ *
+ * This function removes the file formerly added to the card via
+ * snd_card_file_add() function.
+ * If all files are removed and the release of the card is
+ * scheduled, it will wake up the the thread to call snd_card_free()
+ * (see snd_card_free_in_thread() function).
+ *
+ * Returns zero or a negative error code.
+ */
+int snd_card_file_remove(snd_card_t *card, struct file *file)
+{
+ struct snd_monitor_file *mfile, *pfile = NULL;
+
+ spin_lock(&card->files_lock);
+ mfile = card->files;
+ while (mfile) {
+ if (mfile->file == file) {
+ if (pfile)
+ pfile->next = mfile->next;
+ else
+ card->files = mfile->next;
+ break;
+ }
+ pfile = mfile;
+ mfile = mfile->next;
+ }
+ spin_unlock(&card->files_lock);
+ if (card->files == NULL)
+ wake_up(&card->shutdown_sleep);
+ if (!mfile) {
+ snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
+ return -ENOENT;
+ }
+ kfree(mfile);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/**
+ * snd_power_wait - wait until the power-state is changed.
+ * @card: soundcard structure
+ * @power_state: expected power state
+ * @file: file structure for the O_NONBLOCK check (optional)
+ *
+ * Waits until the power-state is changed.
+ *
+ * Note: the power lock must be active before call.
+ */
+int snd_power_wait(snd_card_t *card, unsigned int power_state, struct file *file)
+{
+ wait_queue_t wait;
+ int result = 0;
+
+ /* fastpath */
+ if (snd_power_get_state(card) == power_state)
+ return 0;
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&card->power_sleep, &wait);
+ while (1) {
+ if (card->shutdown) {
+ result = -ENODEV;
+ break;
+ }
+ if (snd_power_get_state(card) == power_state)
+ break;
+#if 0 /* block all devices */
+ if (file && (file->f_flags & O_NONBLOCK)) {
+ result = -EAGAIN;
+ break;
+ }
+#endif
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ snd_power_unlock(card);
+ schedule_timeout(30 * HZ);
+ snd_power_lock(card);
+ }
+ remove_wait_queue(&card->power_sleep, &wait);
+ return result;
+}
+
+/**
+ * snd_card_set_pm_callback - set the PCI power-management callbacks
+ * @card: soundcard structure
+ * @suspend: suspend callback function
+ * @resume: resume callback function
+ * @private_data: private data to pass to the callback functions
+ *
+ * Sets the power-management callback functions of the card.
+ * These callbacks are called from ALSA's common PCI suspend/resume
+ * handler and from the control API.
+ */
+int snd_card_set_pm_callback(snd_card_t *card,
+ int (*suspend)(snd_card_t *, pm_message_t),
+ int (*resume)(snd_card_t *),
+ void *private_data)
+{
+ card->pm_suspend = suspend;
+ card->pm_resume = resume;
+ card->pm_private_data = private_data;
+ return 0;
+}
+
+#ifdef CONFIG_SND_GENERIC_PM
+/*
+ * use platform_device for generic power-management without a proper bus
+ * (e.g. ISA)
+ */
+struct snd_generic_device {
+ struct platform_device pdev;
+ snd_card_t *card;
+};
+
+#define get_snd_generic_card(dev) container_of(to_platform_device(dev), struct snd_generic_device, pdev)->card
+
+#define SND_GENERIC_NAME "snd_generic_pm"
+
+static int snd_generic_suspend(struct device *dev, u32 state, u32 level);
+static int snd_generic_resume(struct device *dev, u32 level);
+
+static struct device_driver snd_generic_driver = {
+ .name = SND_GENERIC_NAME,
+ .bus = &platform_bus_type,
+ .suspend = snd_generic_suspend,
+ .resume = snd_generic_resume,
+};
+
+static int generic_driver_registered;
+
+static void generic_driver_unregister(void)
+{
+ if (generic_driver_registered) {
+ generic_driver_registered--;
+ if (! generic_driver_registered)
+ driver_unregister(&snd_generic_driver);
+ }
+}
+
+static struct snd_generic_device *snd_generic_device_register(snd_card_t *card)
+{
+ struct snd_generic_device *dev;
+
+ if (! generic_driver_registered) {
+ if (driver_register(&snd_generic_driver) < 0)
+ return NULL;
+ }
+ generic_driver_registered++;
+
+ dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+ if (! dev) {
+ generic_driver_unregister();
+ return NULL;
+ }
+
+ dev->pdev.name = SND_GENERIC_NAME;
+ dev->pdev.id = card->number;
+ dev->card = card;
+ if (platform_device_register(&dev->pdev) < 0) {
+ kfree(dev);
+ generic_driver_unregister();
+ return NULL;
+ }
+ return dev;
+}
+
+static void snd_generic_device_unregister(struct snd_generic_device *dev)
+{
+ platform_device_unregister(&dev->pdev);
+ kfree(dev);
+ generic_driver_unregister();
+}
+
+/* suspend/resume callbacks for snd_generic platform device */
+static int snd_generic_suspend(struct device *dev, u32 state, u32 level)
+{
+ snd_card_t *card;
+
+ if (level != SUSPEND_DISABLE)
+ return 0;
+
+ card = get_snd_generic_card(dev);
+ if (card->power_state == SNDRV_CTL_POWER_D3hot)
+ return 0;
+ card->pm_suspend(card, PMSG_SUSPEND);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+
+static int snd_generic_resume(struct device *dev, u32 level)
+{
+ snd_card_t *card;
+
+ if (level != RESUME_ENABLE)
+ return 0;
+
+ card = get_snd_generic_card(dev);
+ if (card->power_state == SNDRV_CTL_POWER_D0)
+ return 0;
+ card->pm_resume(card);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+/**
+ * snd_card_set_generic_pm_callback - set the generic power-management callbacks
+ * @card: soundcard structure
+ * @suspend: suspend callback function
+ * @resume: resume callback function
+ * @private_data: private data to pass to the callback functions
+ *
+ * Registers the power-management and sets the lowlevel callbacks for
+ * the given card. These callbacks are called from the ALSA's common
+ * PM handler and from the control API.
+ */
+int snd_card_set_generic_pm_callback(snd_card_t *card,
+ int (*suspend)(snd_card_t *, pm_message_t),
+ int (*resume)(snd_card_t *),
+ void *private_data)
+{
+ card->pm_dev = snd_generic_device_register(card);
+ if (! card->pm_dev)
+ return -ENOMEM;
+ snd_card_set_pm_callback(card, suspend, resume, private_data);
+ return 0;
+}
+#endif /* CONFIG_SND_GENERIC_PM */
+
+#ifdef CONFIG_PCI
+int snd_card_pci_suspend(struct pci_dev *dev, pm_message_t state)
+{
+ snd_card_t *card = pci_get_drvdata(dev);
+ int err;
+ if (! card || ! card->pm_suspend)
+ return 0;
+ if (card->power_state == SNDRV_CTL_POWER_D3hot)
+ return 0;
+ err = card->pm_suspend(card, PMSG_SUSPEND);
+ pci_save_state(dev);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return err;
+}
+
+int snd_card_pci_resume(struct pci_dev *dev)
+{
+ snd_card_t *card = pci_get_drvdata(dev);
+ if (! card || ! card->pm_resume)
+ return 0;
+ if (card->power_state == SNDRV_CTL_POWER_D0)
+ return 0;
+ /* restore the PCI config space */
+ pci_restore_state(dev);
+ card->pm_resume(card);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
+#endif /* CONFIG_PM */
diff --git a/sound/core/isadma.c b/sound/core/isadma.c
new file mode 100644
index 00000000000..1a378951da5
--- /dev/null
+++ b/sound/core/isadma.c
@@ -0,0 +1,103 @@
+/*
+ * ISA DMA support functions
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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
+ *
+ */
+
+/*
+ * Defining following add some delay. Maybe this helps for some broken
+ * ISA DMA controllers.
+ */
+
+#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <asm/dma.h>
+
+/**
+ * snd_dma_program - program an ISA DMA transfer
+ * @dma: the dma number
+ * @addr: the physical address of the buffer
+ * @size: the DMA transfer size
+ * @mode: the DMA transfer mode, DMA_MODE_XXX
+ *
+ * Programs an ISA DMA transfer for the given buffer.
+ */
+void snd_dma_program(unsigned long dma,
+ unsigned long addr, unsigned int size,
+ unsigned short mode)
+{
+ unsigned long flags;
+
+ flags = claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+ set_dma_mode(dma, mode);
+ set_dma_addr(dma, addr);
+ set_dma_count(dma, size);
+ if (!(mode & DMA_MODE_NO_ENABLE))
+ enable_dma(dma);
+ release_dma_lock(flags);
+}
+
+/**
+ * snd_dma_disable - stop the ISA DMA transfer
+ * @dma: the dma number
+ *
+ * Stops the ISA DMA transfer.
+ */
+void snd_dma_disable(unsigned long dma)
+{
+ unsigned long flags;
+
+ flags = claim_dma_lock();
+ clear_dma_ff(dma);
+ disable_dma(dma);
+ release_dma_lock(flags);
+}
+
+/**
+ * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
+ * @dma: the dma number
+ * @size: the dma transfer size
+ *
+ * Returns the current pointer in DMA tranfer buffer in bytes
+ */
+unsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
+{
+ unsigned long flags;
+ unsigned int result;
+
+ flags = claim_dma_lock();
+ clear_dma_ff(dma);
+ if (!isa_dma_bridge_buggy)
+ disable_dma(dma);
+ result = get_dma_residue(dma);
+ if (!isa_dma_bridge_buggy)
+ enable_dma(dma);
+ release_dma_lock(flags);
+#ifdef CONFIG_SND_DEBUG
+ if (result > size)
+ snd_printk(KERN_ERR "pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
+#endif
+ if (result >= size || result == 0)
+ return 0;
+ else
+ return size - result;
+}
diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c
new file mode 100644
index 00000000000..344a83fd7c2
--- /dev/null
+++ b/sound/core/memalloc.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ * Generic memory allocators
+ *
+ *
+ * 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/config.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <asm/semaphore.h>
+#include <sound/memalloc.h>
+#ifdef CONFIG_SBUS
+#include <asm/sbus.h>
+#endif
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Memory allocator for ALSA system.");
+MODULE_LICENSE("GPL");
+
+
+#ifndef SNDRV_CARDS
+#define SNDRV_CARDS 8
+#endif
+
+/* FIXME: so far only some PCI devices have the preallocation table */
+#ifdef CONFIG_PCI
+static int enable[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable cards to allocate buffers.");
+#endif
+
+/*
+ */
+
+void *snd_malloc_sgbuf_pages(struct device *device,
+ size_t size, struct snd_dma_buffer *dmab,
+ size_t *res_size);
+int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab);
+
+/*
+ */
+
+static DECLARE_MUTEX(list_mutex);
+static LIST_HEAD(mem_list_head);
+
+/* buffer preservation list */
+struct snd_mem_list {
+ struct snd_dma_buffer buffer;
+ unsigned int id;
+ struct list_head list;
+};
+
+/* id for pre-allocated buffers */
+#define SNDRV_DMA_DEVICE_UNUSED (unsigned int)-1
+
+#ifdef CONFIG_SND_DEBUG
+#define __ASTRING__(x) #x
+#define snd_assert(expr, args...) do {\
+ if (!(expr)) {\
+ printk(KERN_ERR "snd-malloc: BUG? (%s) (called from %p)\n", __ASTRING__(expr), __builtin_return_address(0));\
+ args;\
+ }\
+} while (0)
+#else
+#define snd_assert(expr, args...) /**/
+#endif
+
+/*
+ * Hacks
+ */
+
+#if defined(__i386__) || defined(__ppc__) || defined(__x86_64__)
+/*
+ * A hack to allocate large buffers via dma_alloc_coherent()
+ *
+ * since dma_alloc_coherent always tries GFP_DMA when the requested
+ * pci memory region is below 32bit, it happens quite often that even
+ * 2 order of pages cannot be allocated.
+ *
+ * so in the following, we allocate at first without dma_mask, so that
+ * allocation will be done without GFP_DMA. if the area doesn't match
+ * with the requested region, then realloate with the original dma_mask
+ * again.
+ *
+ * Really, we want to move this type of thing into dma_alloc_coherent()
+ * so dma_mask doesn't have to be messed with.
+ */
+
+static void *snd_dma_hack_alloc_coherent(struct device *dev, size_t size,
+ dma_addr_t *dma_handle, int flags)
+{
+ void *ret;
+ u64 dma_mask, coherent_dma_mask;
+
+ if (dev == NULL || !dev->dma_mask)
+ return dma_alloc_coherent(dev, size, dma_handle, flags);
+ dma_mask = *dev->dma_mask;
+ coherent_dma_mask = dev->coherent_dma_mask;
+ *dev->dma_mask = 0xffffffff; /* do without masking */
+ dev->coherent_dma_mask = 0xffffffff; /* do without masking */
+ ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+ *dev->dma_mask = dma_mask; /* restore */
+ dev->coherent_dma_mask = coherent_dma_mask; /* restore */
+ if (ret) {
+ /* obtained address is out of range? */
+ if (((unsigned long)*dma_handle + size - 1) & ~dma_mask) {
+ /* reallocate with the proper mask */
+ dma_free_coherent(dev, size, ret, *dma_handle);
+ ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+ }
+ } else {
+ /* wish to success now with the proper mask... */
+ if (dma_mask != 0xffffffffUL) {
+ /* allocation with GFP_ATOMIC to avoid the long stall */
+ flags &= ~GFP_KERNEL;
+ flags |= GFP_ATOMIC;
+ ret = dma_alloc_coherent(dev, size, dma_handle, flags);
+ }
+ }
+ return ret;
+}
+
+/* redefine dma_alloc_coherent for some architectures */
+#undef dma_alloc_coherent
+#define dma_alloc_coherent snd_dma_hack_alloc_coherent
+
+#endif /* arch */
+
+#if ! defined(__arm__)
+#define NEED_RESERVE_PAGES
+#endif
+
+/*
+ *
+ * Generic memory allocators
+ *
+ */
+
+static long snd_allocated_pages; /* holding the number of allocated pages */
+
+static inline void inc_snd_pages(int order)
+{
+ snd_allocated_pages += 1 << order;
+}
+
+static inline void dec_snd_pages(int order)
+{
+ snd_allocated_pages -= 1 << order;
+}
+
+static void mark_pages(struct page *page, int order)
+{
+ struct page *last_page = page + (1 << order);
+ while (page < last_page)
+ SetPageReserved(page++);
+}
+
+static void unmark_pages(struct page *page, int order)
+{
+ struct page *last_page = page + (1 << order);
+ while (page < last_page)
+ ClearPageReserved(page++);
+}
+
+/**
+ * snd_malloc_pages - allocate pages with the given size
+ * @size: the size to allocate in bytes
+ * @gfp_flags: the allocation conditions, GFP_XXX
+ *
+ * Allocates the physically contiguous pages with the given size.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
+void *snd_malloc_pages(size_t size, unsigned int gfp_flags)
+{
+ int pg;
+ void *res;
+
+ snd_assert(size > 0, return NULL);
+ snd_assert(gfp_flags != 0, return NULL);
+ pg = get_order(size);
+ if ((res = (void *) __get_free_pages(gfp_flags, pg)) != NULL) {
+ mark_pages(virt_to_page(res), pg);
+ inc_snd_pages(pg);
+ }
+ return res;
+}
+
+/**
+ * snd_free_pages - release the pages
+ * @ptr: the buffer pointer to release
+ * @size: the allocated buffer size
+ *
+ * Releases the buffer allocated via snd_malloc_pages().
+ */
+void snd_free_pages(void *ptr, size_t size)
+{
+ int pg;
+
+ if (ptr == NULL)
+ return;
+ pg = get_order(size);
+ dec_snd_pages(pg);
+ unmark_pages(virt_to_page(ptr), pg);
+ free_pages((unsigned long) ptr, pg);
+}
+
+/*
+ *
+ * Bus-specific memory allocators
+ *
+ */
+
+/* allocate the coherent DMA pages */
+static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma)
+{
+ int pg;
+ void *res;
+ unsigned int gfp_flags;
+
+ snd_assert(size > 0, return NULL);
+ snd_assert(dma != NULL, return NULL);
+ pg = get_order(size);
+ gfp_flags = GFP_KERNEL
+ | __GFP_NORETRY /* don't trigger OOM-killer */
+ | __GFP_NOWARN; /* no stack trace print - this call is non-critical */
+ res = dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
+ if (res != NULL) {
+#ifdef NEED_RESERVE_PAGES
+ mark_pages(virt_to_page(res), pg); /* should be dma_to_page() */
+#endif
+ inc_snd_pages(pg);
+ }
+
+ return res;
+}
+
+/* free the coherent DMA pages */
+static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr,
+ dma_addr_t dma)
+{
+ int pg;
+
+ if (ptr == NULL)
+ return;
+ pg = get_order(size);
+ dec_snd_pages(pg);
+#ifdef NEED_RESERVE_PAGES
+ unmark_pages(virt_to_page(ptr), pg); /* should be dma_to_page() */
+#endif
+ dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
+}
+
+#ifdef CONFIG_SBUS
+
+static void *snd_malloc_sbus_pages(struct device *dev, size_t size,
+ dma_addr_t *dma_addr)
+{
+ struct sbus_dev *sdev = (struct sbus_dev *)dev;
+ int pg;
+ void *res;
+
+ snd_assert(size > 0, return NULL);
+ snd_assert(dma_addr != NULL, return NULL);
+ pg = get_order(size);
+ res = sbus_alloc_consistent(sdev, PAGE_SIZE * (1 << pg), dma_addr);
+ if (res != NULL)
+ inc_snd_pages(pg);
+ return res;
+}
+
+static void snd_free_sbus_pages(struct device *dev, size_t size,
+ void *ptr, dma_addr_t dma_addr)
+{
+ struct sbus_dev *sdev = (struct sbus_dev *)dev;
+ int pg;
+
+ if (ptr == NULL)
+ return;
+ pg = get_order(size);
+ dec_snd_pages(pg);
+ sbus_free_consistent(sdev, PAGE_SIZE * (1 << pg), ptr, dma_addr);
+}
+
+#endif /* CONFIG_SBUS */
+
+/*
+ *
+ * ALSA generic memory management
+ *
+ */
+
+
+/**
+ * snd_dma_alloc_pages - allocate the buffer area according to the given type
+ * @type: the DMA buffer type
+ * @device: the device pointer
+ * @size: the buffer size to allocate
+ * @dmab: buffer allocation record to store the allocated data
+ *
+ * Calls the memory-allocator function for the corresponding
+ * buffer type.
+ *
+ * Returns zero if the buffer with the given size is allocated successfuly,
+ * other a negative value at error.
+ */
+int snd_dma_alloc_pages(int type, struct device *device, size_t size,
+ struct snd_dma_buffer *dmab)
+{
+ snd_assert(size > 0, return -ENXIO);
+ snd_assert(dmab != NULL, return -ENXIO);
+
+ dmab->dev.type = type;
+ dmab->dev.dev = device;
+ dmab->bytes = 0;
+ switch (type) {
+ case SNDRV_DMA_TYPE_CONTINUOUS:
+ dmab->area = snd_malloc_pages(size, (unsigned long)device);
+ dmab->addr = 0;
+ break;
+#ifdef CONFIG_SBUS
+ case SNDRV_DMA_TYPE_SBUS:
+ dmab->area = snd_malloc_sbus_pages(device, size, &dmab->addr);
+ break;
+#endif
+ case SNDRV_DMA_TYPE_DEV:
+ dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
+ break;
+ case SNDRV_DMA_TYPE_DEV_SG:
+ snd_malloc_sgbuf_pages(device, size, dmab, NULL);
+ break;
+ default:
+ printk(KERN_ERR "snd-malloc: invalid device type %d\n", type);
+ dmab->area = NULL;
+ dmab->addr = 0;
+ return -ENXIO;
+ }
+ if (! dmab->area)
+ return -ENOMEM;
+ dmab->bytes = size;
+ return 0;
+}
+
+/**
+ * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
+ * @type: the DMA buffer type
+ * @device: the device pointer
+ * @size: the buffer size to allocate
+ * @dmab: buffer allocation record to store the allocated data
+ *
+ * Calls the memory-allocator function for the corresponding
+ * buffer type. When no space is left, this function reduces the size and
+ * tries to allocate again. The size actually allocated is stored in
+ * res_size argument.
+ *
+ * Returns zero if the buffer with the given size is allocated successfuly,
+ * other a negative value at error.
+ */
+int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size,
+ struct snd_dma_buffer *dmab)
+{
+ int err;
+
+ snd_assert(size > 0, return -ENXIO);
+ snd_assert(dmab != NULL, return -ENXIO);
+
+ while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
+ if (err != -ENOMEM)
+ return err;
+ size >>= 1;
+ if (size <= PAGE_SIZE)
+ return -ENOMEM;
+ }
+ if (! dmab->area)
+ return -ENOMEM;
+ return 0;
+}
+
+
+/**
+ * snd_dma_free_pages - release the allocated buffer
+ * @dmab: the buffer allocation record to release
+ *
+ * Releases the allocated buffer via snd_dma_alloc_pages().
+ */
+void snd_dma_free_pages(struct snd_dma_buffer *dmab)
+{
+ switch (dmab->dev.type) {
+ case SNDRV_DMA_TYPE_CONTINUOUS:
+ snd_free_pages(dmab->area, dmab->bytes);
+ break;
+#ifdef CONFIG_SBUS
+ case SNDRV_DMA_TYPE_SBUS:
+ snd_free_sbus_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
+ break;
+#endif
+ case SNDRV_DMA_TYPE_DEV:
+ snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
+ break;
+ case SNDRV_DMA_TYPE_DEV_SG:
+ snd_free_sgbuf_pages(dmab);
+ break;
+ default:
+ printk(KERN_ERR "snd-malloc: invalid device type %d\n", dmab->dev.type);
+ }
+}
+
+
+/**
+ * snd_dma_get_reserved - get the reserved buffer for the given device
+ * @dmab: the buffer allocation record to store
+ * @id: the buffer id
+ *
+ * Looks for the reserved-buffer list and re-uses if the same buffer
+ * is found in the list. When the buffer is found, it's removed from the free list.
+ *
+ * Returns the size of buffer if the buffer is found, or zero if not found.
+ */
+size_t snd_dma_get_reserved_buf(struct snd_dma_buffer *dmab, unsigned int id)
+{
+ struct list_head *p;
+ struct snd_mem_list *mem;
+
+ snd_assert(dmab, return 0);
+
+ down(&list_mutex);
+ list_for_each(p, &mem_list_head) {
+ mem = list_entry(p, struct snd_mem_list, list);
+ if (mem->id == id &&
+ ! memcmp(&mem->buffer.dev, &dmab->dev, sizeof(dmab->dev))) {
+ list_del(p);
+ *dmab = mem->buffer;
+ kfree(mem);
+ up(&list_mutex);
+ return dmab->bytes;
+ }
+ }
+ up(&list_mutex);
+ return 0;
+}
+
+/**
+ * snd_dma_reserve_buf - reserve the buffer
+ * @dmab: the buffer to reserve
+ * @id: the buffer id
+ *
+ * Reserves the given buffer as a reserved buffer.
+ *
+ * Returns zero if successful, or a negative code at error.
+ */
+int snd_dma_reserve_buf(struct snd_dma_buffer *dmab, unsigned int id)
+{
+ struct snd_mem_list *mem;
+
+ snd_assert(dmab, return -EINVAL);
+ mem = kmalloc(sizeof(*mem), GFP_KERNEL);
+ if (! mem)
+ return -ENOMEM;
+ down(&list_mutex);
+ mem->buffer = *dmab;
+ mem->id = id;
+ list_add_tail(&mem->list, &mem_list_head);
+ up(&list_mutex);
+ return 0;
+}
+
+/*
+ * purge all reserved buffers
+ */
+static void free_all_reserved_pages(void)
+{
+ struct list_head *p;
+ struct snd_mem_list *mem;
+
+ down(&list_mutex);
+ while (! list_empty(&mem_list_head)) {
+ p = mem_list_head.next;
+ mem = list_entry(p, struct snd_mem_list, list);
+ list_del(p);
+ snd_dma_free_pages(&mem->buffer);
+ kfree(mem);
+ }
+ up(&list_mutex);
+}
+
+
+
+/*
+ * allocation of buffers for pre-defined devices
+ */
+
+#ifdef CONFIG_PCI
+/* FIXME: for pci only - other bus? */
+struct prealloc_dev {
+ unsigned short vendor;
+ unsigned short device;
+ unsigned long dma_mask;
+ unsigned int size;
+ unsigned int buffers;
+};
+
+#define HAMMERFALL_BUFFER_SIZE (16*1024*4*(26+1)+0x10000)
+
+static struct prealloc_dev prealloc_devices[] __initdata = {
+ {
+ /* hammerfall */
+ .vendor = 0x10ee,
+ .device = 0x3fc4,
+ .dma_mask = 0xffffffff,
+ .size = HAMMERFALL_BUFFER_SIZE,
+ .buffers = 2
+ },
+ {
+ /* HDSP */
+ .vendor = 0x10ee,
+ .device = 0x3fc5,
+ .dma_mask = 0xffffffff,
+ .size = HAMMERFALL_BUFFER_SIZE,
+ .buffers = 2
+ },
+ { }, /* terminator */
+};
+
+static void __init preallocate_cards(void)
+{
+ struct pci_dev *pci = NULL;
+ int card;
+
+ card = 0;
+
+ while ((pci = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci)) != NULL) {
+ struct prealloc_dev *dev;
+ unsigned int i;
+ if (card >= SNDRV_CARDS)
+ break;
+ for (dev = prealloc_devices; dev->vendor; dev++) {
+ if (dev->vendor == pci->vendor && dev->device == pci->device)
+ break;
+ }
+ if (! dev->vendor)
+ continue;
+ if (! enable[card++]) {
+ printk(KERN_DEBUG "snd-page-alloc: skipping card %d, device %04x:%04x\n", card, pci->vendor, pci->device);
+ continue;
+ }
+
+ if (pci_set_dma_mask(pci, dev->dma_mask) < 0 ||
+ pci_set_consistent_dma_mask(pci, dev->dma_mask) < 0) {
+ printk(KERN_ERR "snd-page-alloc: cannot set DMA mask %lx for pci %04x:%04x\n", dev->dma_mask, dev->vendor, dev->device);
+ continue;
+ }
+ for (i = 0; i < dev->buffers; i++) {
+ struct snd_dma_buffer dmab;
+ memset(&dmab, 0, sizeof(dmab));
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+ dev->size, &dmab) < 0)
+ printk(KERN_WARNING "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", dev->size);
+ else
+ snd_dma_reserve_buf(&dmab, snd_dma_pci_buf_id(pci));
+ }
+ }
+}
+#else
+#define preallocate_cards() /* NOP */
+#endif
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc file interface
+ */
+static int snd_mem_proc_read(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = 0;
+ long pages = snd_allocated_pages >> (PAGE_SHIFT-12);
+ struct list_head *p;
+ struct snd_mem_list *mem;
+ int devno;
+ static char *types[] = { "UNKNOWN", "CONT", "DEV", "DEV-SG", "SBUS" };
+
+ down(&list_mutex);
+ len += snprintf(page + len, count - len,
+ "pages : %li bytes (%li pages per %likB)\n",
+ pages * PAGE_SIZE, pages, PAGE_SIZE / 1024);
+ devno = 0;
+ list_for_each(p, &mem_list_head) {
+ mem = list_entry(p, struct snd_mem_list, list);
+ devno++;
+ len += snprintf(page + len, count - len,
+ "buffer %d : ID %08x : type %s\n",
+ devno, mem->id, types[mem->buffer.dev.type]);
+ len += snprintf(page + len, count - len,
+ " addr = 0x%lx, size = %d bytes\n",
+ (unsigned long)mem->buffer.addr, (int)mem->buffer.bytes);
+ }
+ up(&list_mutex);
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * module entry
+ */
+
+static int __init snd_mem_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("driver/snd-page-alloc", 0, NULL, snd_mem_proc_read, NULL);
+#endif
+ preallocate_cards();
+ return 0;
+}
+
+static void __exit snd_mem_exit(void)
+{
+ remove_proc_entry("driver/snd-page-alloc", NULL);
+ free_all_reserved_pages();
+ if (snd_allocated_pages > 0)
+ printk(KERN_ERR "snd-malloc: Memory leak? pages not freed = %li\n", snd_allocated_pages);
+}
+
+
+module_init(snd_mem_init)
+module_exit(snd_mem_exit)
+
+
+/*
+ * exports
+ */
+EXPORT_SYMBOL(snd_dma_alloc_pages);
+EXPORT_SYMBOL(snd_dma_alloc_pages_fallback);
+EXPORT_SYMBOL(snd_dma_free_pages);
+
+EXPORT_SYMBOL(snd_dma_get_reserved_buf);
+EXPORT_SYMBOL(snd_dma_reserve_buf);
+
+EXPORT_SYMBOL(snd_malloc_pages);
+EXPORT_SYMBOL(snd_free_pages);
diff --git a/sound/core/memory.c b/sound/core/memory.c
new file mode 100644
index 00000000000..20860fec936
--- /dev/null
+++ b/sound/core/memory.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ * Memory allocation helpers.
+ *
+ *
+ * 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 <sound/driver.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/info.h>
+
+/*
+ * memory allocation helpers and debug routines
+ */
+
+#ifdef CONFIG_SND_DEBUG_MEMORY
+
+struct snd_alloc_track {
+ unsigned long magic;
+ void *caller;
+ size_t size;
+ struct list_head list;
+ long data[0];
+};
+
+#define snd_alloc_track_entry(obj) (struct snd_alloc_track *)((char*)obj - (unsigned long)((struct snd_alloc_track *)0)->data)
+
+static long snd_alloc_kmalloc;
+static long snd_alloc_vmalloc;
+static LIST_HEAD(snd_alloc_kmalloc_list);
+static LIST_HEAD(snd_alloc_vmalloc_list);
+static DEFINE_SPINLOCK(snd_alloc_kmalloc_lock);
+static DEFINE_SPINLOCK(snd_alloc_vmalloc_lock);
+#define KMALLOC_MAGIC 0x87654321
+#define VMALLOC_MAGIC 0x87654320
+static snd_info_entry_t *snd_memory_info_entry;
+
+void snd_memory_init(void)
+{
+ snd_alloc_kmalloc = 0;
+ snd_alloc_vmalloc = 0;
+}
+
+void snd_memory_done(void)
+{
+ struct list_head *head;
+ struct snd_alloc_track *t;
+
+ if (snd_alloc_kmalloc > 0)
+ snd_printk(KERN_ERR "Not freed snd_alloc_kmalloc = %li\n", snd_alloc_kmalloc);
+ if (snd_alloc_vmalloc > 0)
+ snd_printk(KERN_ERR "Not freed snd_alloc_vmalloc = %li\n", snd_alloc_vmalloc);
+ list_for_each_prev(head, &snd_alloc_kmalloc_list) {
+ t = list_entry(head, struct snd_alloc_track, list);
+ if (t->magic != KMALLOC_MAGIC) {
+ snd_printk(KERN_ERR "Corrupted kmalloc\n");
+ break;
+ }
+ snd_printk(KERN_ERR "kmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
+ }
+ list_for_each_prev(head, &snd_alloc_vmalloc_list) {
+ t = list_entry(head, struct snd_alloc_track, list);
+ if (t->magic != VMALLOC_MAGIC) {
+ snd_printk(KERN_ERR "Corrupted vmalloc\n");
+ break;
+ }
+ snd_printk(KERN_ERR "vmalloc(%ld) from %p not freed\n", (long) t->size, t->caller);
+ }
+}
+
+static void *__snd_kmalloc(size_t size, int flags, void *caller)
+{
+ unsigned long cpu_flags;
+ struct snd_alloc_track *t;
+ void *ptr;
+
+ ptr = snd_wrapper_kmalloc(size + sizeof(struct snd_alloc_track), flags);
+ if (ptr != NULL) {
+ t = (struct snd_alloc_track *)ptr;
+ t->magic = KMALLOC_MAGIC;
+ t->caller = caller;
+ spin_lock_irqsave(&snd_alloc_kmalloc_lock, cpu_flags);
+ list_add_tail(&t->list, &snd_alloc_kmalloc_list);
+ spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, cpu_flags);
+ t->size = size;
+ snd_alloc_kmalloc += size;
+ ptr = t->data;
+ }
+ return ptr;
+}
+
+#define _snd_kmalloc(size, flags) __snd_kmalloc((size), (flags), __builtin_return_address(0));
+void *snd_hidden_kmalloc(size_t size, int flags)
+{
+ return _snd_kmalloc(size, flags);
+}
+
+void *snd_hidden_kcalloc(size_t n, size_t size, int flags)
+{
+ void *ret = NULL;
+ if (n != 0 && size > INT_MAX / n)
+ return ret;
+ ret = _snd_kmalloc(n * size, flags);
+ if (ret)
+ memset(ret, 0, n * size);
+ return ret;
+}
+
+void snd_hidden_kfree(const void *obj)
+{
+ unsigned long flags;
+ struct snd_alloc_track *t;
+ if (obj == NULL)
+ return;
+ t = snd_alloc_track_entry(obj);
+ if (t->magic != KMALLOC_MAGIC) {
+ snd_printk(KERN_WARNING "bad kfree (called from %p)\n", __builtin_return_address(0));
+ return;
+ }
+ spin_lock_irqsave(&snd_alloc_kmalloc_lock, flags);
+ list_del(&t->list);
+ spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, flags);
+ t->magic = 0;
+ snd_alloc_kmalloc -= t->size;
+ obj = t;
+ snd_wrapper_kfree(obj);
+}
+
+void *snd_hidden_vmalloc(unsigned long size)
+{
+ void *ptr;
+ ptr = snd_wrapper_vmalloc(size + sizeof(struct snd_alloc_track));
+ if (ptr) {
+ struct snd_alloc_track *t = (struct snd_alloc_track *)ptr;
+ t->magic = VMALLOC_MAGIC;
+ t->caller = __builtin_return_address(0);
+ spin_lock(&snd_alloc_vmalloc_lock);
+ list_add_tail(&t->list, &snd_alloc_vmalloc_list);
+ spin_unlock(&snd_alloc_vmalloc_lock);
+ t->size = size;
+ snd_alloc_vmalloc += size;
+ ptr = t->data;
+ }
+ return ptr;
+}
+
+void snd_hidden_vfree(void *obj)
+{
+ struct snd_alloc_track *t;
+ if (obj == NULL)
+ return;
+ t = snd_alloc_track_entry(obj);
+ if (t->magic != VMALLOC_MAGIC) {
+ snd_printk(KERN_ERR "bad vfree (called from %p)\n", __builtin_return_address(0));
+ return;
+ }
+ spin_lock(&snd_alloc_vmalloc_lock);
+ list_del(&t->list);
+ spin_unlock(&snd_alloc_vmalloc_lock);
+ t->magic = 0;
+ snd_alloc_vmalloc -= t->size;
+ obj = t;
+ snd_wrapper_vfree(obj);
+}
+
+static void snd_memory_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ snd_iprintf(buffer, "kmalloc: %li bytes\n", snd_alloc_kmalloc);
+ snd_iprintf(buffer, "vmalloc: %li bytes\n", snd_alloc_vmalloc);
+}
+
+int __init snd_memory_info_init(void)
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, "meminfo", NULL);
+ if (entry) {
+ entry->c.text.read_size = 256;
+ entry->c.text.read = snd_memory_info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_memory_info_entry = entry;
+ return 0;
+}
+
+int __exit snd_memory_info_done(void)
+{
+ if (snd_memory_info_entry)
+ snd_info_unregister(snd_memory_info_entry);
+ return 0;
+}
+
+#else
+
+#define _snd_kmalloc kmalloc
+
+#endif /* CONFIG_SND_DEBUG_MEMORY */
+
+/**
+ * snd_kmalloc_strdup - copy the string
+ * @string: the original string
+ * @flags: allocation conditions, GFP_XXX
+ *
+ * Allocates a memory chunk via kmalloc() and copies the string to it.
+ *
+ * Returns the pointer, or NULL if no enoguh memory.
+ */
+char *snd_kmalloc_strdup(const char *string, int flags)
+{
+ size_t len;
+ char *ptr;
+
+ if (!string)
+ return NULL;
+ len = strlen(string) + 1;
+ ptr = _snd_kmalloc(len, flags);
+ if (ptr)
+ memcpy(ptr, string, len);
+ return ptr;
+}
+
+/**
+ * copy_to_user_fromio - copy data from mmio-space to user-space
+ * @dst: the destination pointer on user-space
+ * @src: the source pointer on mmio
+ * @count: the data size to copy in bytes
+ *
+ * Copies the data from mmio-space to user-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
+int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
+{
+#if defined(__i386__) || defined(CONFIG_SPARC32)
+ return copy_to_user(dst, (const void*)src, count) ? -EFAULT : 0;
+#else
+ char buf[256];
+ while (count) {
+ size_t c = count;
+ if (c > sizeof(buf))
+ c = sizeof(buf);
+ memcpy_fromio(buf, (void __iomem *)src, c);
+ if (copy_to_user(dst, buf, c))
+ return -EFAULT;
+ count -= c;
+ dst += c;
+ src += c;
+ }
+ return 0;
+#endif
+}
+
+/**
+ * copy_from_user_toio - copy data from user-space to mmio-space
+ * @dst: the destination pointer on mmio-space
+ * @src: the source pointer on user-space
+ * @count: the data size to copy in bytes
+ *
+ * Copies the data from user-space to mmio-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
+int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
+{
+#if defined(__i386__) || defined(CONFIG_SPARC32)
+ return copy_from_user((void*)dst, src, count) ? -EFAULT : 0;
+#else
+ char buf[256];
+ while (count) {
+ size_t c = count;
+ if (c > sizeof(buf))
+ c = sizeof(buf);
+ if (copy_from_user(buf, src, c))
+ return -EFAULT;
+ memcpy_toio(dst, buf, c);
+ count -= c;
+ dst += c;
+ src += c;
+ }
+ return 0;
+#endif
+}
diff --git a/sound/core/misc.c b/sound/core/misc.c
new file mode 100644
index 00000000000..1a81fe4df21
--- /dev/null
+++ b/sound/core/misc.c
@@ -0,0 +1,76 @@
+/*
+ * Misc and compatibility things
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <sound/core.h>
+
+int snd_task_name(struct task_struct *task, char *name, size_t size)
+{
+ unsigned int idx;
+
+ snd_assert(task != NULL && name != NULL && size >= 2, return -EINVAL);
+ for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++)
+ name[idx] = task->comm[idx];
+ name[idx] = '\0';
+ return 0;
+}
+
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+void snd_verbose_printk(const char *file, int line, const char *format, ...)
+{
+ va_list args;
+
+ if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
+ char tmp[] = "<0>";
+ tmp[1] = format[1];
+ printk("%sALSA %s:%d: ", tmp, file, line);
+ format += 3;
+ } else {
+ printk("ALSA %s:%d: ", file, line);
+ }
+ va_start(args, format);
+ vprintk(format, args);
+ va_end(args);
+}
+#endif
+
+#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
+void snd_verbose_printd(const char *file, int line, const char *format, ...)
+{
+ va_list args;
+
+ if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') {
+ char tmp[] = "<0>";
+ tmp[1] = format[1];
+ printk("%sALSA %s:%d: ", tmp, file, line);
+ format += 3;
+ } else {
+ printk(KERN_DEBUG "ALSA %s:%d: ", file, line);
+ }
+ va_start(args, format);
+ vprintk(format, args);
+ va_end(args);
+
+}
+#endif
diff --git a/sound/core/oss/Makefile b/sound/core/oss/Makefile
new file mode 100644
index 00000000000..e6d5a045ba2
--- /dev/null
+++ b/sound/core/oss/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-mixer-oss-objs := mixer_oss.o
+
+snd-pcm-oss-objs := pcm_oss.o pcm_plugin.o \
+ io.o copy.o linear.o mulaw.o route.o rate.o
+
+obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o
+obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o
diff --git a/sound/core/oss/copy.c b/sound/core/oss/copy.c
new file mode 100644
index 00000000000..edecbe7417b
--- /dev/null
+++ b/sound/core/oss/copy.c
@@ -0,0 +1,87 @@
+/*
+ * Linear conversion Plug-In
+ * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+static snd_pcm_sframes_t copy_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ unsigned int channel;
+ unsigned int nchannels;
+
+ snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+ nchannels = plugin->src_format.channels;
+ for (channel = 0; channel < nchannels; channel++) {
+ snd_assert(src_channels->area.first % 8 == 0 &&
+ src_channels->area.step % 8 == 0,
+ return -ENXIO);
+ snd_assert(dst_channels->area.first % 8 == 0 &&
+ dst_channels->area.step % 8 == 0,
+ return -ENXIO);
+ if (!src_channels->enabled) {
+ if (dst_channels->wanted)
+ snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format);
+ dst_channels->enabled = 0;
+ continue;
+ }
+ dst_channels->enabled = 1;
+ snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format);
+ src_channels++;
+ dst_channels++;
+ }
+ return frames;
+}
+
+int snd_pcm_plugin_build_copy(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin)
+{
+ int err;
+ snd_pcm_plugin_t *plugin;
+ int width;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+
+ snd_assert(src_format->format == dst_format->format, return -ENXIO);
+ snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+ snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+
+ width = snd_pcm_format_physical_width(src_format->format);
+ snd_assert(width > 0, return -ENXIO);
+
+ err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format,
+ 0, &plugin);
+ if (err < 0)
+ return err;
+ plugin->transfer = copy_transfer;
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/oss/io.c b/sound/core/oss/io.c
new file mode 100644
index 00000000000..bb1c99a5b73
--- /dev/null
+++ b/sound/core/oss/io.c
@@ -0,0 +1,134 @@
+/*
+ * PCM I/O Plug-In Interface
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+
+#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1)
+#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1)
+#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1)
+#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1)
+
+/*
+ * Basic io plugin
+ */
+
+static snd_pcm_sframes_t io_playback_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels ATTRIBUTE_UNUSED,
+ snd_pcm_uframes_t frames)
+{
+ snd_assert(plugin != NULL, return -ENXIO);
+ snd_assert(src_channels != NULL, return -ENXIO);
+ if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ return pcm_write(plugin->plug, src_channels->area.addr, frames);
+ } else {
+ int channel, channels = plugin->dst_format.channels;
+ void **bufs = (void**)plugin->extra_data;
+ snd_assert(bufs != NULL, return -ENXIO);
+ for (channel = 0; channel < channels; channel++) {
+ if (src_channels[channel].enabled)
+ bufs[channel] = src_channels[channel].area.addr;
+ else
+ bufs[channel] = NULL;
+ }
+ return pcm_writev(plugin->plug, bufs, frames);
+ }
+}
+
+static snd_pcm_sframes_t io_capture_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ snd_assert(plugin != NULL, return -ENXIO);
+ snd_assert(dst_channels != NULL, return -ENXIO);
+ if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ return pcm_read(plugin->plug, dst_channels->area.addr, frames);
+ } else {
+ int channel, channels = plugin->dst_format.channels;
+ void **bufs = (void**)plugin->extra_data;
+ snd_assert(bufs != NULL, return -ENXIO);
+ for (channel = 0; channel < channels; channel++) {
+ if (dst_channels[channel].enabled)
+ bufs[channel] = dst_channels[channel].area.addr;
+ else
+ bufs[channel] = NULL;
+ }
+ return pcm_readv(plugin->plug, bufs, frames);
+ }
+ return 0;
+}
+
+static snd_pcm_sframes_t io_src_channels(snd_pcm_plugin_t *plugin,
+ snd_pcm_uframes_t frames,
+ snd_pcm_plugin_channel_t **channels)
+{
+ int err;
+ unsigned int channel;
+ snd_pcm_plugin_channel_t *v;
+ err = snd_pcm_plugin_client_channels(plugin, frames, &v);
+ if (err < 0)
+ return err;
+ *channels = v;
+ if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v)
+ v->wanted = 1;
+ }
+ return frames;
+}
+
+int snd_pcm_plugin_build_io(snd_pcm_plug_t *plug,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_plugin_t **r_plugin)
+{
+ int err;
+ snd_pcm_plugin_format_t format;
+ snd_pcm_plugin_t *plugin;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+ snd_assert(plug != NULL && params != NULL, return -ENXIO);
+ format.format = params_format(params);
+ format.rate = params_rate(params);
+ format.channels = params_channels(params);
+ err = snd_pcm_plugin_build(plug, "I/O io",
+ &format, &format,
+ sizeof(void *) * format.channels,
+ &plugin);
+ if (err < 0)
+ return err;
+ plugin->access = params_access(params);
+ if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
+ plugin->transfer = io_playback_transfer;
+ if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED)
+ plugin->client_channels = io_src_channels;
+ } else {
+ plugin->transfer = io_capture_transfer;
+ }
+
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/oss/linear.c b/sound/core/oss/linear.c
new file mode 100644
index 00000000000..12ed27a57b2
--- /dev/null
+++ b/sound/core/oss/linear.c
@@ -0,0 +1,158 @@
+/*
+ * Linear conversion Plug-In
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>,
+ * Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+/*
+ * Basic linear conversion plugin
+ */
+
+typedef struct linear_private_data {
+ int conv;
+} linear_t;
+
+static void convert(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+ linear_t *data = (linear_t *)plugin->extra_data;
+ void *conv = conv_labels[data->conv];
+ int channel;
+ int nchannels = plugin->src_format.channels;
+ for (channel = 0; channel < nchannels; ++channel) {
+ char *src;
+ char *dst;
+ int src_step, dst_step;
+ snd_pcm_uframes_t frames1;
+ if (!src_channels[channel].enabled) {
+ if (dst_channels[channel].wanted)
+ snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+ dst_channels[channel].enabled = 0;
+ continue;
+ }
+ dst_channels[channel].enabled = 1;
+ src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+ dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+ src_step = src_channels[channel].area.step / 8;
+ dst_step = dst_channels[channel].area.step / 8;
+ frames1 = frames;
+ while (frames1-- > 0) {
+ goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+ after:
+ src += src_step;
+ dst += dst_step;
+ }
+ }
+}
+
+static snd_pcm_sframes_t linear_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ linear_t *data;
+
+ snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+ data = (linear_t *)plugin->extra_data;
+ if (frames == 0)
+ return 0;
+#ifdef CONFIG_SND_DEBUG
+ {
+ unsigned int channel;
+ for (channel = 0; channel < plugin->src_format.channels; channel++) {
+ snd_assert(src_channels[channel].area.first % 8 == 0 &&
+ src_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+ dst_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ }
+ }
+#endif
+ convert(plugin, src_channels, dst_channels, frames);
+ return frames;
+}
+
+int conv_index(int src_format, int dst_format)
+{
+ int src_endian, dst_endian, sign, src_width, dst_width;
+
+ sign = (snd_pcm_format_signed(src_format) !=
+ snd_pcm_format_signed(dst_format));
+#ifdef SNDRV_LITTLE_ENDIAN
+ src_endian = snd_pcm_format_big_endian(src_format);
+ dst_endian = snd_pcm_format_big_endian(dst_format);
+#else
+ src_endian = snd_pcm_format_little_endian(src_format);
+ dst_endian = snd_pcm_format_little_endian(dst_format);
+#endif
+
+ if (src_endian < 0)
+ src_endian = 0;
+ if (dst_endian < 0)
+ dst_endian = 0;
+
+ src_width = snd_pcm_format_width(src_format) / 8 - 1;
+ dst_width = snd_pcm_format_width(dst_format) / 8 - 1;
+
+ return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian;
+}
+
+int snd_pcm_plugin_build_linear(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin)
+{
+ int err;
+ struct linear_private_data *data;
+ snd_pcm_plugin_t *plugin;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+
+ snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+ snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+ snd_assert(snd_pcm_format_linear(src_format->format) &&
+ snd_pcm_format_linear(dst_format->format), return -ENXIO);
+
+ err = snd_pcm_plugin_build(plug, "linear format conversion",
+ src_format, dst_format,
+ sizeof(linear_t), &plugin);
+ if (err < 0)
+ return err;
+ data = (linear_t *)plugin->extra_data;
+ data->conv = conv_index(src_format->format, dst_format->format);
+ plugin->transfer = linear_transfer;
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c
new file mode 100644
index 00000000000..98ed9a9f0da
--- /dev/null
+++ b/sound/core/oss/mixer_oss.c
@@ -0,0 +1,1340 @@
+/*
+ * OSS emulation layer for the mixer interface
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/mixer_oss.h>
+#include <linux/soundcard.h>
+
+#define OSS_ALSAEMULVER _SIOR ('M', 249, int)
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Mixer OSS emulation for ALSA.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER);
+
+static int snd_mixer_oss_open(struct inode *inode, struct file *file)
+{
+ int cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+ snd_card_t *card;
+ snd_mixer_oss_file_t *fmixer;
+ int err;
+
+ if ((card = snd_cards[cardnum]) == NULL)
+ return -ENODEV;
+ if (card->mixer_oss == NULL)
+ return -ENODEV;
+ err = snd_card_file_add(card, file);
+ if (err < 0)
+ return err;
+ fmixer = kcalloc(1, sizeof(*fmixer), GFP_KERNEL);
+ if (fmixer == NULL) {
+ snd_card_file_remove(card, file);
+ return -ENOMEM;
+ }
+ fmixer->card = card;
+ fmixer->mixer = card->mixer_oss;
+ file->private_data = fmixer;
+ if (!try_module_get(card->module)) {
+ kfree(fmixer);
+ snd_card_file_remove(card, file);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int snd_mixer_oss_release(struct inode *inode, struct file *file)
+{
+ snd_mixer_oss_file_t *fmixer;
+
+ if (file->private_data) {
+ fmixer = (snd_mixer_oss_file_t *) file->private_data;
+ module_put(fmixer->card->module);
+ snd_card_file_remove(fmixer->card, file);
+ kfree(fmixer);
+ }
+ return 0;
+}
+
+static int snd_mixer_oss_info(snd_mixer_oss_file_t *fmixer,
+ mixer_info __user *_info)
+{
+ snd_card_t *card = fmixer->card;
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ struct mixer_info info;
+
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id));
+ strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name));
+ info.modify_counter = card->mixer_oss_change_count;
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_mixer_oss_info_obsolete(snd_mixer_oss_file_t *fmixer,
+ _old_mixer_info __user *_info)
+{
+ snd_card_t *card = fmixer->card;
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ _old_mixer_info info;
+
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id));
+ strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name));
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_mixer_oss_caps(snd_mixer_oss_file_t *fmixer)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ int result = 0;
+
+ if (mixer == NULL)
+ return -EIO;
+ if (mixer->get_recsrc && mixer->put_recsrc)
+ result |= SOUND_CAP_EXCL_INPUT;
+ return result;
+}
+
+static int snd_mixer_oss_devmask(snd_mixer_oss_file_t *fmixer)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_mixer_oss_slot_t *pslot;
+ int result = 0, chn;
+
+ if (mixer == NULL)
+ return -EIO;
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->put_volume || pslot->put_recsrc)
+ result |= 1 << chn;
+ }
+ return result;
+}
+
+static int snd_mixer_oss_stereodevs(snd_mixer_oss_file_t *fmixer)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_mixer_oss_slot_t *pslot;
+ int result = 0, chn;
+
+ if (mixer == NULL)
+ return -EIO;
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->put_volume && pslot->stereo)
+ result |= 1 << chn;
+ }
+ return result;
+}
+
+static int snd_mixer_oss_recmask(snd_mixer_oss_file_t *fmixer)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ int result = 0;
+
+ if (mixer == NULL)
+ return -EIO;
+ if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */
+ result = mixer->mask_recsrc;
+ } else {
+ snd_mixer_oss_slot_t *pslot;
+ int chn;
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->put_recsrc)
+ result |= 1 << chn;
+ }
+ }
+ return result;
+}
+
+static int snd_mixer_oss_get_recsrc(snd_mixer_oss_file_t *fmixer)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ int result = 0;
+
+ if (mixer == NULL)
+ return -EIO;
+ if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */
+ int err;
+ if ((err = mixer->get_recsrc(fmixer, &result)) < 0)
+ return err;
+ result = 1 << result;
+ } else {
+ snd_mixer_oss_slot_t *pslot;
+ int chn;
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->get_recsrc) {
+ int active = 0;
+ pslot->get_recsrc(fmixer, pslot, &active);
+ if (active)
+ result |= 1 << chn;
+ }
+ }
+ }
+ return mixer->oss_recsrc = result;
+}
+
+static int snd_mixer_oss_set_recsrc(snd_mixer_oss_file_t *fmixer, int recsrc)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_mixer_oss_slot_t *pslot;
+ int chn, active;
+ int result = 0;
+
+ if (mixer == NULL)
+ return -EIO;
+ if (mixer->get_recsrc && mixer->put_recsrc) { /* exclusive input */
+ if (recsrc & ~mixer->oss_recsrc)
+ recsrc &= ~mixer->oss_recsrc;
+ mixer->put_recsrc(fmixer, ffz(~recsrc));
+ mixer->get_recsrc(fmixer, &result);
+ result = 1 << result;
+ }
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->put_recsrc) {
+ active = (recsrc & (1 << chn)) ? 1 : 0;
+ pslot->put_recsrc(fmixer, pslot, active);
+ }
+ }
+ if (! result) {
+ for (chn = 0; chn < 31; chn++) {
+ pslot = &mixer->slots[chn];
+ if (pslot->get_recsrc) {
+ active = 0;
+ pslot->get_recsrc(fmixer, pslot, &active);
+ if (active)
+ result |= 1 << chn;
+ }
+ }
+ }
+ return result;
+}
+
+static int snd_mixer_oss_get_volume(snd_mixer_oss_file_t *fmixer, int slot)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_mixer_oss_slot_t *pslot;
+ int result = 0, left, right;
+
+ if (mixer == NULL || slot > 30)
+ return -EIO;
+ pslot = &mixer->slots[slot];
+ left = pslot->volume[0];
+ right = pslot->volume[1];
+ if (pslot->get_volume)
+ result = pslot->get_volume(fmixer, pslot, &left, &right);
+ if (!pslot->stereo)
+ right = left;
+ snd_assert(left >= 0 && left <= 100, return -EIO);
+ snd_assert(right >= 0 && right <= 100, return -EIO);
+ if (result >= 0) {
+ pslot->volume[0] = left;
+ pslot->volume[1] = right;
+ result = (left & 0xff) | ((right & 0xff) << 8);
+ }
+ return result;
+}
+
+static int snd_mixer_oss_set_volume(snd_mixer_oss_file_t *fmixer,
+ int slot, int volume)
+{
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_mixer_oss_slot_t *pslot;
+ int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff;
+
+ if (mixer == NULL || slot > 30)
+ return -EIO;
+ pslot = &mixer->slots[slot];
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+ if (!pslot->stereo)
+ right = left;
+ if (pslot->put_volume)
+ result = pslot->put_volume(fmixer, pslot, left, right);
+ if (result < 0)
+ return result;
+ pslot->volume[0] = left;
+ pslot->volume[1] = right;
+ return (left & 0xff) | ((right & 0xff) << 8);
+}
+
+static int snd_mixer_oss_ioctl1(snd_mixer_oss_file_t *fmixer, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int tmp;
+
+ snd_assert(fmixer != NULL, return -ENXIO);
+ if (((cmd >> 8) & 0xff) == 'M') {
+ switch (cmd) {
+ case SOUND_MIXER_INFO:
+ return snd_mixer_oss_info(fmixer, argp);
+ case SOUND_OLD_MIXER_INFO:
+ return snd_mixer_oss_info_obsolete(fmixer, argp);
+ case SOUND_MIXER_WRITE_RECSRC:
+ if (get_user(tmp, p))
+ return -EFAULT;
+ tmp = snd_mixer_oss_set_recsrc(fmixer, tmp);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ case OSS_GETVERSION:
+ return put_user(SNDRV_OSS_VERSION, p);
+ case OSS_ALSAEMULVER:
+ return put_user(1, p);
+ case SOUND_MIXER_READ_DEVMASK:
+ tmp = snd_mixer_oss_devmask(fmixer);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ case SOUND_MIXER_READ_STEREODEVS:
+ tmp = snd_mixer_oss_stereodevs(fmixer);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ case SOUND_MIXER_READ_RECMASK:
+ tmp = snd_mixer_oss_recmask(fmixer);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ case SOUND_MIXER_READ_CAPS:
+ tmp = snd_mixer_oss_caps(fmixer);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ case SOUND_MIXER_READ_RECSRC:
+ tmp = snd_mixer_oss_get_recsrc(fmixer);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ }
+ }
+ if (cmd & SIOC_IN) {
+ if (get_user(tmp, p))
+ return -EFAULT;
+ tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ } else if (cmd & SIOC_OUT) {
+ tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff);
+ if (tmp < 0)
+ return tmp;
+ return put_user(tmp, p);
+ }
+ return -ENXIO;
+}
+
+static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ return snd_mixer_oss_ioctl1((snd_mixer_oss_file_t *) file->private_data, cmd, arg);
+}
+
+int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg)
+{
+ snd_mixer_oss_file_t fmixer;
+
+ snd_assert(card != NULL, return -ENXIO);
+ if (card->mixer_oss == NULL)
+ return -ENXIO;
+ memset(&fmixer, 0, sizeof(fmixer));
+ fmixer.card = card;
+ fmixer.mixer = card->mixer_oss;
+ return snd_mixer_oss_ioctl1(&fmixer, cmd, arg);
+}
+
+#ifdef CONFIG_COMPAT
+/* all compatible */
+#define snd_mixer_oss_ioctl_compat snd_mixer_oss_ioctl
+#else
+#define snd_mixer_oss_ioctl_compat NULL
+#endif
+
+/*
+ * REGISTRATION PART
+ */
+
+static struct file_operations snd_mixer_oss_f_ops =
+{
+ .owner = THIS_MODULE,
+ .open = snd_mixer_oss_open,
+ .release = snd_mixer_oss_release,
+ .unlocked_ioctl = snd_mixer_oss_ioctl,
+ .compat_ioctl = snd_mixer_oss_ioctl_compat,
+};
+
+static snd_minor_t snd_mixer_oss_reg =
+{
+ .comment = "mixer",
+ .f_ops = &snd_mixer_oss_f_ops,
+};
+
+/*
+ * utilities
+ */
+
+static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax)
+{
+ long orange = omax - omin, nrange = nmax - nmin;
+
+ if (orange == 0)
+ return 0;
+ return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin;
+}
+
+/* convert from alsa native to oss values (0-100) */
+static long snd_mixer_oss_conv1(long val, long min, long max, int *old)
+{
+ if (val == snd_mixer_oss_conv(*old, 0, 100, min, max))
+ return *old;
+ return snd_mixer_oss_conv(val, min, max, 0, 100);
+}
+
+/* convert from oss to alsa native values */
+static long snd_mixer_oss_conv2(long val, long min, long max)
+{
+ return snd_mixer_oss_conv(val, 0, 100, min, max);
+}
+
+#if 0
+static void snd_mixer_oss_recsrce_set(snd_card_t *card, int slot)
+{
+ snd_mixer_oss_t *mixer = card->mixer_oss;
+ if (mixer)
+ mixer->mask_recsrc |= 1 << slot;
+}
+
+static int snd_mixer_oss_recsrce_get(snd_card_t *card, int slot)
+{
+ snd_mixer_oss_t *mixer = card->mixer_oss;
+ if (mixer && (mixer->mask_recsrc & (1 << slot)))
+ return 1;
+ return 0;
+}
+#endif
+
+#define SNDRV_MIXER_OSS_SIGNATURE 0x65999250
+
+#define SNDRV_MIXER_OSS_ITEM_GLOBAL 0
+#define SNDRV_MIXER_OSS_ITEM_GSWITCH 1
+#define SNDRV_MIXER_OSS_ITEM_GROUTE 2
+#define SNDRV_MIXER_OSS_ITEM_GVOLUME 3
+#define SNDRV_MIXER_OSS_ITEM_PSWITCH 4
+#define SNDRV_MIXER_OSS_ITEM_PROUTE 5
+#define SNDRV_MIXER_OSS_ITEM_PVOLUME 6
+#define SNDRV_MIXER_OSS_ITEM_CSWITCH 7
+#define SNDRV_MIXER_OSS_ITEM_CROUTE 8
+#define SNDRV_MIXER_OSS_ITEM_CVOLUME 9
+#define SNDRV_MIXER_OSS_ITEM_CAPTURE 10
+
+#define SNDRV_MIXER_OSS_ITEM_COUNT 11
+
+#define SNDRV_MIXER_OSS_PRESENT_GLOBAL (1<<0)
+#define SNDRV_MIXER_OSS_PRESENT_GSWITCH (1<<1)
+#define SNDRV_MIXER_OSS_PRESENT_GROUTE (1<<2)
+#define SNDRV_MIXER_OSS_PRESENT_GVOLUME (1<<3)
+#define SNDRV_MIXER_OSS_PRESENT_PSWITCH (1<<4)
+#define SNDRV_MIXER_OSS_PRESENT_PROUTE (1<<5)
+#define SNDRV_MIXER_OSS_PRESENT_PVOLUME (1<<6)
+#define SNDRV_MIXER_OSS_PRESENT_CSWITCH (1<<7)
+#define SNDRV_MIXER_OSS_PRESENT_CROUTE (1<<8)
+#define SNDRV_MIXER_OSS_PRESENT_CVOLUME (1<<9)
+#define SNDRV_MIXER_OSS_PRESENT_CAPTURE (1<<10)
+
+struct slot {
+ unsigned int signature;
+ unsigned int present;
+ unsigned int channels;
+ unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT];
+ unsigned int capture_item;
+ struct snd_mixer_oss_assign_table *assigned;
+ unsigned int allocated: 1;
+};
+
+#define ID_UNKNOWN ((unsigned int)-1)
+
+static snd_kcontrol_t *snd_mixer_oss_test_id(snd_mixer_oss_t *mixer, const char *name, int index)
+{
+ snd_card_t * card = mixer->card;
+ snd_ctl_elem_id_t id;
+
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ strcpy(id.name, name);
+ id.index = index;
+ return snd_ctl_find_id(card, &id);
+}
+
+static void snd_mixer_oss_get_volume1_vol(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ unsigned int numid,
+ int *left, int *right)
+{
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = fmixer->card;
+
+ if (numid == ID_UNKNOWN)
+ return;
+ down_read(&card->controls_rwsem);
+ if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+ up_read(&card->controls_rwsem);
+ return;
+ }
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL)
+ goto __unalloc;
+ snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+ snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc);
+ snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc);
+ *left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]);
+ if (uinfo->count > 1)
+ *right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]);
+ __unalloc:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+}
+
+static void snd_mixer_oss_get_volume1_sw(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ unsigned int numid,
+ int *left, int *right,
+ int route)
+{
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = fmixer->card;
+
+ if (numid == ID_UNKNOWN)
+ return;
+ down_read(&card->controls_rwsem);
+ if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+ up_read(&card->controls_rwsem);
+ return;
+ }
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL)
+ goto __unalloc;
+ snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+ snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc);
+ if (!uctl->value.integer.value[0]) {
+ *left = 0;
+ if (uinfo->count == 1)
+ *right = 0;
+ }
+ if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1])
+ *right = 0;
+ __unalloc:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+}
+
+static int snd_mixer_oss_get_volume1(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int *left, int *right)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+
+ *left = *right = 100;
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) {
+ snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) {
+ snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) {
+ snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right);
+ }
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) {
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) {
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) {
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) {
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+ }
+ return 0;
+}
+
+static void snd_mixer_oss_put_volume1_vol(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ unsigned int numid,
+ int left, int right)
+{
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = fmixer->card;
+ int res;
+
+ if (numid == ID_UNKNOWN)
+ return;
+ down_read(&card->controls_rwsem);
+ if ((kctl = snd_ctl_find_numid(card, numid)) == NULL)
+ return;
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL)
+ goto __unalloc;
+ snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+ snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc);
+ uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max);
+ if (uinfo->count > 1)
+ uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max);
+ snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc);
+ if (res > 0)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+ __unalloc:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+}
+
+static void snd_mixer_oss_put_volume1_sw(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ unsigned int numid,
+ int left, int right,
+ int route)
+{
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ snd_kcontrol_t *kctl;
+ snd_card_t *card = fmixer->card;
+ int res;
+
+ if (numid == ID_UNKNOWN)
+ return;
+ down_read(&card->controls_rwsem);
+ if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) {
+ up_read(&fmixer->card->controls_rwsem);
+ return;
+ }
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL)
+ goto __unalloc;
+ snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc);
+ if (uinfo->count > 1) {
+ uctl->value.integer.value[0] = left > 0 ? 1 : 0;
+ uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0;
+ if (route) {
+ uctl->value.integer.value[1] =
+ uctl->value.integer.value[2] = 0;
+ }
+ } else {
+ uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0;
+ }
+ snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc);
+ if (res > 0)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+ __unalloc:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+}
+
+static int snd_mixer_oss_put_volume1(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int left, int right)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) {
+ snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right);
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME)
+ snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) {
+ snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) {
+ snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right);
+ }
+ if (left || right) {
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH)
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH)
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE)
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE)
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+ } else {
+ if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) {
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) {
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) {
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1);
+ } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) {
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1);
+ }
+ }
+ return 0;
+}
+
+static int snd_mixer_oss_get_recsrc1_sw(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int *active)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+ int left, right;
+
+ left = right = 1;
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0);
+ *active = (left || right) ? 1 : 0;
+ return 0;
+}
+
+static int snd_mixer_oss_get_recsrc1_route(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int *active)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+ int left, right;
+
+ left = right = 1;
+ snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1);
+ *active = (left || right) ? 1 : 0;
+ return 0;
+}
+
+static int snd_mixer_oss_put_recsrc1_sw(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int active)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0);
+ return 0;
+}
+
+static int snd_mixer_oss_put_recsrc1_route(snd_mixer_oss_file_t *fmixer,
+ snd_mixer_oss_slot_t *pslot,
+ int active)
+{
+ struct slot *slot = (struct slot *)pslot->private_data;
+
+ snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1);
+ return 0;
+}
+
+static int snd_mixer_oss_get_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int *active_index)
+{
+ snd_card_t *card = fmixer->card;
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_kcontrol_t *kctl;
+ snd_mixer_oss_slot_t *pslot;
+ struct slot *slot;
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ int err, idx;
+
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL) {
+ err = -ENOMEM;
+ goto __unlock;
+ }
+ down_read(&card->controls_rwsem);
+ kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+ snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock);
+ snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock);
+ snd_runtime_check(!(err = kctl->get(kctl, uctl)), goto __unlock);
+ for (idx = 0; idx < 32; idx++) {
+ if (!(mixer->mask_recsrc & (1 << idx)))
+ continue;
+ pslot = &mixer->slots[idx];
+ slot = (struct slot *)pslot->private_data;
+ if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE)
+ continue;
+ if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE))
+ continue;
+ if (slot->capture_item == uctl->value.enumerated.item[0]) {
+ *active_index = idx;
+ break;
+ }
+ }
+ err = 0;
+ __unlock:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+ return err;
+}
+
+static int snd_mixer_oss_put_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int active_index)
+{
+ snd_card_t *card = fmixer->card;
+ snd_mixer_oss_t *mixer = fmixer->mixer;
+ snd_kcontrol_t *kctl;
+ snd_mixer_oss_slot_t *pslot;
+ struct slot *slot = NULL;
+ snd_ctl_elem_info_t *uinfo;
+ snd_ctl_elem_value_t *uctl;
+ int err;
+ unsigned int idx;
+
+ uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL);
+ uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL);
+ if (uinfo == NULL || uctl == NULL) {
+ err = -ENOMEM;
+ goto __unlock;
+ }
+ down_read(&card->controls_rwsem);
+ kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+ snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock);
+ snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock);
+ for (idx = 0; idx < 32; idx++) {
+ if (!(mixer->mask_recsrc & (1 << idx)))
+ continue;
+ pslot = &mixer->slots[idx];
+ slot = (struct slot *)pslot->private_data;
+ if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE)
+ continue;
+ if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE))
+ continue;
+ if (idx == active_index)
+ break;
+ slot = NULL;
+ }
+ snd_runtime_check(slot != NULL, goto __unlock);
+ for (idx = 0; idx < uinfo->count; idx++)
+ uctl->value.enumerated.item[idx] = slot->capture_item;
+ snd_runtime_check((err = kctl->put(kctl, uctl)) >= 0, );
+ if (err > 0)
+ snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+ err = 0;
+ __unlock:
+ up_read(&card->controls_rwsem);
+ kfree(uctl);
+ kfree(uinfo);
+ return err;
+}
+
+struct snd_mixer_oss_assign_table {
+ int oss_id;
+ const char *name;
+ int index;
+};
+
+static int snd_mixer_oss_build_test(snd_mixer_oss_t *mixer, struct slot *slot, const char *name, int index, int item)
+{
+ snd_ctl_elem_info_t *info;
+ snd_kcontrol_t *kcontrol;
+ snd_card_t *card = mixer->card;
+ int err;
+
+ down_read(&card->controls_rwsem);
+ kcontrol = snd_mixer_oss_test_id(mixer, name, index);
+ if (kcontrol == NULL) {
+ up_read(&card->controls_rwsem);
+ return 0;
+ }
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (! info) {
+ up_read(&card->controls_rwsem);
+ return -ENOMEM;
+ }
+ if ((err = kcontrol->info(kcontrol, info)) < 0) {
+ up_read(&card->controls_rwsem);
+ kfree(info);
+ return err;
+ }
+ slot->numid[item] = kcontrol->id.numid;
+ up_read(&card->controls_rwsem);
+ if (info->count > slot->channels)
+ slot->channels = info->count;
+ slot->present |= 1 << item;
+ kfree(info);
+ return 0;
+}
+
+static void snd_mixer_oss_slot_free(snd_mixer_oss_slot_t *chn)
+{
+ struct slot *p = (struct slot *)chn->private_data;
+ if (p) {
+ if (p->allocated && p->assigned) {
+ kfree(p->assigned->name);
+ kfree(p->assigned);
+ }
+ kfree(p);
+ }
+}
+
+static void mixer_slot_clear(snd_mixer_oss_slot_t *rslot)
+{
+ int idx = rslot->number; /* remember this */
+ if (rslot->private_free)
+ rslot->private_free(rslot);
+ memset(rslot, 0, sizeof(*rslot));
+ rslot->number = idx;
+}
+
+/*
+ * build an OSS mixer element.
+ * ptr_allocated means the entry is dynamically allocated (change via proc file).
+ * when replace_old = 1, the old entry is replaced with the new one.
+ */
+static int snd_mixer_oss_build_input(snd_mixer_oss_t *mixer, struct snd_mixer_oss_assign_table *ptr, int ptr_allocated, int replace_old)
+{
+ struct slot slot;
+ struct slot *pslot;
+ snd_kcontrol_t *kctl;
+ snd_mixer_oss_slot_t *rslot;
+ char str[64];
+
+ /* check if already assigned */
+ if (mixer->slots[ptr->oss_id].get_volume && ! replace_old)
+ return 0;
+
+ memset(&slot, 0, sizeof(slot));
+ memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */
+ if (snd_mixer_oss_build_test(mixer, &slot, ptr->name, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_GLOBAL))
+ return 0;
+ sprintf(str, "%s Switch", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_GSWITCH))
+ return 0;
+ sprintf(str, "%s Route", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_GROUTE))
+ return 0;
+ sprintf(str, "%s Volume", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_GVOLUME))
+ return 0;
+ sprintf(str, "%s Playback Switch", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_PSWITCH))
+ return 0;
+ sprintf(str, "%s Playback Route", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_PROUTE))
+ return 0;
+ sprintf(str, "%s Playback Volume", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_PVOLUME))
+ return 0;
+ sprintf(str, "%s Capture Switch", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_CSWITCH))
+ return 0;
+ sprintf(str, "%s Capture Route", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_CROUTE))
+ return 0;
+ sprintf(str, "%s Capture Volume", ptr->name);
+ if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index,
+ SNDRV_MIXER_OSS_ITEM_CVOLUME))
+ return 0;
+ down_read(&mixer->card->controls_rwsem);
+ if (ptr->index == 0 && (kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0)) != NULL) {
+ snd_ctl_elem_info_t *uinfo;
+
+ uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
+ if (! uinfo) {
+ up_read(&mixer->card->controls_rwsem);
+ return -ENOMEM;
+ }
+
+ memset(uinfo, 0, sizeof(*uinfo));
+ if (kctl->info(kctl, uinfo)) {
+ up_read(&mixer->card->controls_rwsem);
+ return 0;
+ }
+ strcpy(str, ptr->name);
+ if (!strcmp(str, "Master"))
+ strcpy(str, "Mix");
+ if (!strcmp(str, "Master Mono"))
+ strcpy(str, "Mix Mono");
+ slot.capture_item = 0;
+ if (!strcmp(uinfo->value.enumerated.name, str)) {
+ slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE;
+ } else {
+ for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) {
+ uinfo->value.enumerated.item = slot.capture_item;
+ if (kctl->info(kctl, uinfo)) {
+ up_read(&mixer->card->controls_rwsem);
+ return 0;
+ }
+ if (!strcmp(uinfo->value.enumerated.name, str)) {
+ slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE;
+ break;
+ }
+ }
+ }
+ kfree(uinfo);
+ }
+ up_read(&mixer->card->controls_rwsem);
+ if (slot.present != 0) {
+ pslot = (struct slot *)kmalloc(sizeof(slot), GFP_KERNEL);
+ snd_runtime_check(pslot != NULL, return -ENOMEM);
+ *pslot = slot;
+ pslot->signature = SNDRV_MIXER_OSS_SIGNATURE;
+ pslot->assigned = ptr;
+ pslot->allocated = ptr_allocated;
+ rslot = &mixer->slots[ptr->oss_id];
+ mixer_slot_clear(rslot);
+ rslot->stereo = slot.channels > 1 ? 1 : 0;
+ rslot->get_volume = snd_mixer_oss_get_volume1;
+ rslot->put_volume = snd_mixer_oss_put_volume1;
+ /* note: ES18xx have both Capture Source and XX Capture Volume !!! */
+ if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) {
+ rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw;
+ rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw;
+ } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) {
+ rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route;
+ rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route;
+ } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) {
+ mixer->mask_recsrc |= 1 << ptr->oss_id;
+ }
+ rslot->private_data = pslot;
+ rslot->private_free = snd_mixer_oss_slot_free;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ */
+#define MIXER_VOL(name) [SOUND_MIXER_##name] = #name
+static char *oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = {
+ MIXER_VOL(VOLUME),
+ MIXER_VOL(BASS),
+ MIXER_VOL(TREBLE),
+ MIXER_VOL(SYNTH),
+ MIXER_VOL(PCM),
+ MIXER_VOL(SPEAKER),
+ MIXER_VOL(LINE),
+ MIXER_VOL(MIC),
+ MIXER_VOL(CD),
+ MIXER_VOL(IMIX),
+ MIXER_VOL(ALTPCM),
+ MIXER_VOL(RECLEV),
+ MIXER_VOL(IGAIN),
+ MIXER_VOL(OGAIN),
+ MIXER_VOL(LINE1),
+ MIXER_VOL(LINE2),
+ MIXER_VOL(LINE3),
+ MIXER_VOL(DIGITAL1),
+ MIXER_VOL(DIGITAL2),
+ MIXER_VOL(DIGITAL3),
+ MIXER_VOL(PHONEIN),
+ MIXER_VOL(PHONEOUT),
+ MIXER_VOL(VIDEO),
+ MIXER_VOL(RADIO),
+ MIXER_VOL(MONITOR),
+};
+
+/*
+ * /proc interface
+ */
+
+static void snd_mixer_oss_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_mixer_oss_t *mixer = entry->private_data;
+ int i;
+
+ down(&mixer->reg_mutex);
+ for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) {
+ struct slot *p;
+
+ if (! oss_mixer_names[i])
+ continue;
+ p = (struct slot *)mixer->slots[i].private_data;
+ snd_iprintf(buffer, "%s ", oss_mixer_names[i]);
+ if (p && p->assigned)
+ snd_iprintf(buffer, "\"%s\" %d\n",
+ p->assigned->name,
+ p->assigned->index);
+ else
+ snd_iprintf(buffer, "\"\" 0\n");
+ }
+ up(&mixer->reg_mutex);
+}
+
+static void snd_mixer_oss_proc_write(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_mixer_oss_t *mixer = entry->private_data;
+ char line[128], str[32], idxstr[16], *cptr;
+ int ch, idx;
+ struct snd_mixer_oss_assign_table *tbl;
+ struct slot *slot;
+
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ cptr = snd_info_get_str(str, line, sizeof(str));
+ for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++)
+ if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0)
+ break;
+ if (ch >= SNDRV_OSS_MAX_MIXERS) {
+ snd_printk(KERN_ERR "mixer_oss: invalid OSS volume '%s'\n", str);
+ continue;
+ }
+ cptr = snd_info_get_str(str, cptr, sizeof(str));
+ if (! *str) {
+ /* remove the entry */
+ down(&mixer->reg_mutex);
+ mixer_slot_clear(&mixer->slots[ch]);
+ up(&mixer->reg_mutex);
+ continue;
+ }
+ snd_info_get_str(idxstr, cptr, sizeof(idxstr));
+ idx = simple_strtoul(idxstr, NULL, 10);
+ if (idx >= 0x4000) { /* too big */
+ snd_printk(KERN_ERR "mixer_oss: invalid index %d\n", idx);
+ continue;
+ }
+ down(&mixer->reg_mutex);
+ slot = (struct slot *)mixer->slots[ch].private_data;
+ if (slot && slot->assigned &&
+ slot->assigned->index == idx && ! strcmp(slot->assigned->name, str))
+ /* not changed */
+ goto __unlock;
+ tbl = kmalloc(sizeof(*tbl), GFP_KERNEL);
+ if (! tbl) {
+ snd_printk(KERN_ERR "mixer_oss: no memory\n");
+ goto __unlock;
+ }
+ tbl->oss_id = ch;
+ tbl->name = snd_kmalloc_strdup(str, GFP_KERNEL);
+ if (! tbl->name) {
+ kfree(tbl);
+ goto __unlock;
+ }
+ tbl->index = idx;
+ if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) {
+ kfree(tbl->name);
+ kfree(tbl);
+ }
+ __unlock:
+ up(&mixer->reg_mutex);
+ }
+}
+
+static void snd_mixer_oss_proc_init(snd_mixer_oss_t *mixer)
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_card_entry(mixer->card, "oss_mixer",
+ mixer->card->proc_root);
+ if (! entry)
+ return;
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->c.text.read_size = 8192;
+ entry->c.text.read = snd_mixer_oss_proc_read;
+ entry->c.text.write_size = 8192;
+ entry->c.text.write = snd_mixer_oss_proc_write;
+ entry->private_data = mixer;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ mixer->proc_entry = entry;
+}
+
+static void snd_mixer_oss_proc_done(snd_mixer_oss_t *mixer)
+{
+ if (mixer->proc_entry) {
+ snd_info_unregister(mixer->proc_entry);
+ mixer->proc_entry = NULL;
+ }
+}
+
+static void snd_mixer_oss_build(snd_mixer_oss_t *mixer)
+{
+ static struct snd_mixer_oss_assign_table table[] = {
+ { SOUND_MIXER_VOLUME, "Master", 0 },
+ { SOUND_MIXER_VOLUME, "Front", 0 }, /* fallback */
+ { SOUND_MIXER_BASS, "Tone Control - Bass", 0 },
+ { SOUND_MIXER_TREBLE, "Tone Control - Treble", 0 },
+ { SOUND_MIXER_SYNTH, "Synth", 0 },
+ { SOUND_MIXER_SYNTH, "FM", 0 }, /* fallback */
+ { SOUND_MIXER_SYNTH, "Music", 0 }, /* fallback */
+ { SOUND_MIXER_PCM, "PCM", 0 },
+ { SOUND_MIXER_SPEAKER, "PC Speaker", 0 },
+ { SOUND_MIXER_LINE, "Line", 0 },
+ { SOUND_MIXER_MIC, "Mic", 0 },
+ { SOUND_MIXER_CD, "CD", 0 },
+ { SOUND_MIXER_IMIX, "Monitor Mix", 0 },
+ { SOUND_MIXER_ALTPCM, "PCM", 1 },
+ { SOUND_MIXER_ALTPCM, "Headphone", 0 }, /* fallback */
+ { SOUND_MIXER_ALTPCM, "Wave", 0 }, /* fallback */
+ { SOUND_MIXER_RECLEV, "-- nothing --", 0 },
+ { SOUND_MIXER_IGAIN, "Capture", 0 },
+ { SOUND_MIXER_OGAIN, "Playback", 0 },
+ { SOUND_MIXER_LINE1, "Aux", 0 },
+ { SOUND_MIXER_LINE2, "Aux", 1 },
+ { SOUND_MIXER_LINE3, "Aux", 2 },
+ { SOUND_MIXER_DIGITAL1, "Digital", 0 },
+ { SOUND_MIXER_DIGITAL1, "IEC958", 0 }, /* fallback */
+ { SOUND_MIXER_DIGITAL1, "IEC958 Optical", 0 }, /* fallback */
+ { SOUND_MIXER_DIGITAL1, "IEC958 Coaxial", 0 }, /* fallback */
+ { SOUND_MIXER_DIGITAL2, "Digital", 1 },
+ { SOUND_MIXER_DIGITAL3, "Digital", 2 },
+ { SOUND_MIXER_PHONEIN, "Phone", 0 },
+ { SOUND_MIXER_PHONEOUT, "Master Mono", 0 },
+ { SOUND_MIXER_PHONEOUT, "Phone", 0 }, /* fallback */
+ { SOUND_MIXER_VIDEO, "Video", 0 },
+ { SOUND_MIXER_RADIO, "Radio", 0 },
+ { SOUND_MIXER_MONITOR, "Monitor", 0 }
+ };
+ unsigned int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(table); idx++)
+ snd_mixer_oss_build_input(mixer, &table[idx], 0, 0);
+ if (mixer->mask_recsrc) {
+ mixer->get_recsrc = snd_mixer_oss_get_recsrc2;
+ mixer->put_recsrc = snd_mixer_oss_put_recsrc2;
+ }
+}
+
+/*
+ *
+ */
+
+static int snd_mixer_oss_free1(void *private)
+{
+ snd_mixer_oss_t *mixer = private;
+ snd_card_t * card;
+ int idx;
+
+ snd_assert(mixer != NULL, return -ENXIO);
+ card = mixer->card;
+ snd_assert(mixer == card->mixer_oss, return -ENXIO);
+ card->mixer_oss = NULL;
+ for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) {
+ snd_mixer_oss_slot_t *chn = &mixer->slots[idx];
+ if (chn->private_free)
+ chn->private_free(chn);
+ }
+ kfree(mixer);
+ return 0;
+}
+
+static int snd_mixer_oss_notify_handler(snd_card_t * card, int cmd)
+{
+ snd_mixer_oss_t *mixer;
+
+ if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) {
+ char name[128];
+ int idx, err;
+
+ mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL);
+ if (mixer == NULL)
+ return -ENOMEM;
+ init_MUTEX(&mixer->reg_mutex);
+ sprintf(name, "mixer%i%i", card->number, 0);
+ if ((err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER,
+ card, 0,
+ &snd_mixer_oss_reg,
+ name)) < 0) {
+ snd_printk("unable to register OSS mixer device %i:%i\n", card->number, 0);
+ kfree(mixer);
+ return err;
+ }
+ mixer->oss_dev_alloc = 1;
+ mixer->card = card;
+ if (*card->mixername)
+ strlcpy(mixer->name, card->mixername, sizeof(mixer->name));
+ else
+ strlcpy(mixer->name, name, sizeof(mixer->name));
+#ifdef SNDRV_OSS_INFO_DEV_MIXERS
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS,
+ card->number,
+ mixer->name);
+#endif
+ for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++)
+ mixer->slots[idx].number = idx;
+ card->mixer_oss = mixer;
+ snd_mixer_oss_build(mixer);
+ snd_mixer_oss_proc_init(mixer);
+ } else if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) {
+ mixer = card->mixer_oss;
+ if (mixer == NULL || !mixer->oss_dev_alloc)
+ return 0;
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0);
+ mixer->oss_dev_alloc = 0;
+ } else { /* free */
+ mixer = card->mixer_oss;
+ if (mixer == NULL)
+ return 0;
+#ifdef SNDRV_OSS_INFO_DEV_MIXERS
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number);
+#endif
+ if (mixer->oss_dev_alloc)
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0);
+ snd_mixer_oss_proc_done(mixer);
+ return snd_mixer_oss_free1(mixer);
+ }
+ return 0;
+}
+
+static int __init alsa_mixer_oss_init(void)
+{
+ int idx;
+
+ snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler;
+ for (idx = 0; idx < SNDRV_CARDS; idx++) {
+ if (snd_cards[idx])
+ snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_REGISTER);
+ }
+ return 0;
+}
+
+static void __exit alsa_mixer_oss_exit(void)
+{
+ int idx;
+
+ snd_mixer_oss_notify_callback = NULL;
+ for (idx = 0; idx < SNDRV_CARDS; idx++) {
+ if (snd_cards[idx])
+ snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_FREE);
+ }
+}
+
+module_init(alsa_mixer_oss_init)
+module_exit(alsa_mixer_oss_exit)
+
+EXPORT_SYMBOL(snd_mixer_oss_ioctl_card);
diff --git a/sound/core/oss/mulaw.c b/sound/core/oss/mulaw.c
new file mode 100644
index 00000000000..44ec4c66eb1
--- /dev/null
+++ b/sound/core/oss/mulaw.c
@@ -0,0 +1,308 @@
+/*
+ * Mu-Law conversion Plug-In Interface
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ * Uros Bizjak <uros@kss-loka.si>
+ *
+ * Based on reference implementation by Sun Microsystems, Inc.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */
+#define QUANT_MASK (0xf) /* Quantization field mask. */
+#define NSEGS (8) /* Number of u-law segments. */
+#define SEG_SHIFT (4) /* Left shift for segment number. */
+#define SEG_MASK (0x70) /* Segment field mask. */
+
+static inline int val_seg(int val)
+{
+ int r = 0;
+ val >>= 7;
+ if (val & 0xf0) {
+ val >>= 4;
+ r += 4;
+ }
+ if (val & 0x0c) {
+ val >>= 2;
+ r += 2;
+ }
+ if (val & 0x02)
+ r += 1;
+ return r;
+}
+
+#define BIAS (0x84) /* Bias for linear code. */
+
+/*
+ * linear2ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ * Biased Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 00000001wxyza 000wxyz
+ * 0000001wxyzab 001wxyz
+ * 000001wxyzabc 010wxyz
+ * 00001wxyzabcd 011wxyz
+ * 0001wxyzabcde 100wxyz
+ * 001wxyzabcdef 101wxyz
+ * 01wxyzabcdefg 110wxyz
+ * 1wxyzabcdefgh 111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz. * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */
+{
+ int mask;
+ int seg;
+ unsigned char uval;
+
+ /* Get the sign and the magnitude of the value. */
+ if (pcm_val < 0) {
+ pcm_val = BIAS - pcm_val;
+ mask = 0x7F;
+ } else {
+ pcm_val += BIAS;
+ mask = 0xFF;
+ }
+ if (pcm_val > 0x7FFF)
+ pcm_val = 0x7FFF;
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = val_seg(pcm_val);
+
+ /*
+ * Combine the sign, segment, quantization bits;
+ * and complement the code word.
+ */
+ uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
+ return uval ^ mask;
+}
+
+/*
+ * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static int ulaw2linear(unsigned char u_val)
+{
+ int t;
+
+ /* Complement to obtain normal u-law value. */
+ u_val = ~u_val;
+
+ /*
+ * Extract and bias the quantization bits. Then
+ * shift up by the segment number and subtract out the bias.
+ */
+ t = ((u_val & QUANT_MASK) << 3) + BIAS;
+ t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
+
+ return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
+}
+
+/*
+ * Basic Mu-Law plugin
+ */
+
+typedef void (*mulaw_f)(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames);
+
+typedef struct mulaw_private_data {
+ mulaw_f func;
+ int conv;
+} mulaw_t;
+
+static void mulaw_decode(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef PUT_S16_LABELS
+ mulaw_t *data = (mulaw_t *)plugin->extra_data;
+ void *put = put_s16_labels[data->conv];
+ int channel;
+ int nchannels = plugin->src_format.channels;
+ for (channel = 0; channel < nchannels; ++channel) {
+ char *src;
+ char *dst;
+ int src_step, dst_step;
+ snd_pcm_uframes_t frames1;
+ if (!src_channels[channel].enabled) {
+ if (dst_channels[channel].wanted)
+ snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+ dst_channels[channel].enabled = 0;
+ continue;
+ }
+ dst_channels[channel].enabled = 1;
+ src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+ dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+ src_step = src_channels[channel].area.step / 8;
+ dst_step = dst_channels[channel].area.step / 8;
+ frames1 = frames;
+ while (frames1-- > 0) {
+ signed short sample = ulaw2linear(*src);
+ goto *put;
+#define PUT_S16_END after
+#include "plugin_ops.h"
+#undef PUT_S16_END
+ after:
+ src += src_step;
+ dst += dst_step;
+ }
+ }
+}
+
+static void mulaw_encode(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+#define GET_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+ mulaw_t *data = (mulaw_t *)plugin->extra_data;
+ void *get = get_s16_labels[data->conv];
+ int channel;
+ int nchannels = plugin->src_format.channels;
+ signed short sample = 0;
+ for (channel = 0; channel < nchannels; ++channel) {
+ char *src;
+ char *dst;
+ int src_step, dst_step;
+ snd_pcm_uframes_t frames1;
+ if (!src_channels[channel].enabled) {
+ if (dst_channels[channel].wanted)
+ snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
+ dst_channels[channel].enabled = 0;
+ continue;
+ }
+ dst_channels[channel].enabled = 1;
+ src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+ dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+ src_step = src_channels[channel].area.step / 8;
+ dst_step = dst_channels[channel].area.step / 8;
+ frames1 = frames;
+ while (frames1-- > 0) {
+ goto *get;
+#define GET_S16_END after
+#include "plugin_ops.h"
+#undef GET_S16_END
+ after:
+ *dst = linear2ulaw(sample);
+ src += src_step;
+ dst += dst_step;
+ }
+ }
+}
+
+static snd_pcm_sframes_t mulaw_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ mulaw_t *data;
+
+ snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+#ifdef CONFIG_SND_DEBUG
+ {
+ unsigned int channel;
+ for (channel = 0; channel < plugin->src_format.channels; channel++) {
+ snd_assert(src_channels[channel].area.first % 8 == 0 &&
+ src_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+ dst_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ }
+ }
+#endif
+ data = (mulaw_t *)plugin->extra_data;
+ data->func(plugin, src_channels, dst_channels, frames);
+ return frames;
+}
+
+int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin)
+{
+ int err;
+ mulaw_t *data;
+ snd_pcm_plugin_t *plugin;
+ snd_pcm_plugin_format_t *format;
+ mulaw_f func;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+
+ snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+ snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+
+ if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
+ format = src_format;
+ func = mulaw_encode;
+ }
+ else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
+ format = dst_format;
+ func = mulaw_decode;
+ }
+ else {
+ snd_BUG();
+ return -EINVAL;
+ }
+ snd_assert(snd_pcm_format_linear(format->format) != 0, return -ENXIO);
+
+ err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
+ src_format, dst_format,
+ sizeof(mulaw_t), &plugin);
+ if (err < 0)
+ return err;
+ data = (mulaw_t*)plugin->extra_data;
+ data->func = func;
+ data->conv = getput_index(format->format);
+ snd_assert(data->conv >= 0 && data->conv < 4*2*2, return -EINVAL);
+ plugin->transfer = mulaw_transfer;
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
new file mode 100644
index 00000000000..1a805020f57
--- /dev/null
+++ b/sound/core/oss/pcm_oss.c
@@ -0,0 +1,2530 @@
+/*
+ * Digital Audio (PCM) abstract layer / OSS compatible
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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
+ *
+ */
+
+#if 0
+#define PLUGIN_DEBUG
+#endif
+#if 0
+#define OSS_DEBUG
+#endif
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+#include <sound/info.h>
+#include <linux/soundcard.h>
+#include <sound/initval.h>
+
+#define OSS_ALSAEMULVER _SIOR ('M', 249, int)
+
+static int dsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+static int nonblock_open = 1;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("PCM OSS emulation for ALSA.");
+MODULE_LICENSE("GPL");
+module_param_array(dsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device.");
+module_param_array(adsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device.");
+module_param(nonblock_open, bool, 0644);
+MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices.");
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1);
+
+extern int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg);
+static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file);
+
+static inline mm_segment_t snd_enter_user(void)
+{
+ mm_segment_t fs = get_fs();
+ set_fs(get_ds());
+ return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+ set_fs(fs);
+}
+
+static int snd_pcm_oss_plugin_clear(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_plugin_t *plugin, *next;
+
+ plugin = runtime->oss.plugin_first;
+ while (plugin) {
+ next = plugin->next;
+ snd_pcm_plugin_free(plugin);
+ plugin = next;
+ }
+ runtime->oss.plugin_first = runtime->oss.plugin_last = NULL;
+ return 0;
+}
+
+static int snd_pcm_plugin_insert(snd_pcm_plugin_t *plugin)
+{
+ snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+ plugin->next = runtime->oss.plugin_first;
+ plugin->prev = NULL;
+ if (runtime->oss.plugin_first) {
+ runtime->oss.plugin_first->prev = plugin;
+ runtime->oss.plugin_first = plugin;
+ } else {
+ runtime->oss.plugin_last =
+ runtime->oss.plugin_first = plugin;
+ }
+ return 0;
+}
+
+int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin)
+{
+ snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+ plugin->next = NULL;
+ plugin->prev = runtime->oss.plugin_last;
+ if (runtime->oss.plugin_last) {
+ runtime->oss.plugin_last->next = plugin;
+ runtime->oss.plugin_last = plugin;
+ } else {
+ runtime->oss.plugin_last =
+ runtime->oss.plugin_first = plugin;
+ }
+ return 0;
+}
+
+static long snd_pcm_oss_bytes(snd_pcm_substream_t *substream, long frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ frames = frames_to_bytes(runtime, frames);
+ if (buffer_size == runtime->oss.buffer_bytes)
+ return frames;
+ return (runtime->oss.buffer_bytes * frames) / buffer_size;
+}
+
+static long snd_pcm_alsa_frames(snd_pcm_substream_t *substream, long bytes)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ if (buffer_size == runtime->oss.buffer_bytes)
+ return bytes_to_frames(runtime, bytes);
+ return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes);
+}
+
+static int snd_pcm_oss_format_from(int format)
+{
+ switch (format) {
+ case AFMT_MU_LAW: return SNDRV_PCM_FORMAT_MU_LAW;
+ case AFMT_A_LAW: return SNDRV_PCM_FORMAT_A_LAW;
+ case AFMT_IMA_ADPCM: return SNDRV_PCM_FORMAT_IMA_ADPCM;
+ case AFMT_U8: return SNDRV_PCM_FORMAT_U8;
+ case AFMT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE;
+ case AFMT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE;
+ case AFMT_S8: return SNDRV_PCM_FORMAT_S8;
+ case AFMT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE;
+ case AFMT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE;
+ case AFMT_MPEG: return SNDRV_PCM_FORMAT_MPEG;
+ default: return SNDRV_PCM_FORMAT_U8;
+ }
+}
+
+static int snd_pcm_oss_format_to(int format)
+{
+ switch (format) {
+ case SNDRV_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW;
+ case SNDRV_PCM_FORMAT_A_LAW: return AFMT_A_LAW;
+ case SNDRV_PCM_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM;
+ case SNDRV_PCM_FORMAT_U8: return AFMT_U8;
+ case SNDRV_PCM_FORMAT_S16_LE: return AFMT_S16_LE;
+ case SNDRV_PCM_FORMAT_S16_BE: return AFMT_S16_BE;
+ case SNDRV_PCM_FORMAT_S8: return AFMT_S8;
+ case SNDRV_PCM_FORMAT_U16_LE: return AFMT_U16_LE;
+ case SNDRV_PCM_FORMAT_U16_BE: return AFMT_U16_BE;
+ case SNDRV_PCM_FORMAT_MPEG: return AFMT_MPEG;
+ default: return -EINVAL;
+ }
+}
+
+static int snd_pcm_oss_period_size(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *oss_params,
+ snd_pcm_hw_params_t *slave_params)
+{
+ size_t s;
+ size_t oss_buffer_size, oss_period_size, oss_periods;
+ size_t min_period_size, max_period_size;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ size_t oss_frame_size;
+
+ oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) *
+ params_channels(oss_params) / 8;
+
+ oss_buffer_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size;
+ oss_buffer_size = 1 << ld2(oss_buffer_size);
+ if (atomic_read(&runtime->mmap_count)) {
+ if (oss_buffer_size > runtime->oss.mmap_bytes)
+ oss_buffer_size = runtime->oss.mmap_bytes;
+ }
+
+ if (substream->oss.setup &&
+ substream->oss.setup->period_size > 16)
+ oss_period_size = substream->oss.setup->period_size;
+ else if (runtime->oss.fragshift) {
+ oss_period_size = 1 << runtime->oss.fragshift;
+ if (oss_period_size > oss_buffer_size / 2)
+ oss_period_size = oss_buffer_size / 2;
+ } else {
+ int sd;
+ size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8;
+
+ oss_period_size = oss_buffer_size;
+ do {
+ oss_period_size /= 2;
+ } while (oss_period_size > bytes_per_sec);
+ if (runtime->oss.subdivision == 0) {
+ sd = 4;
+ if (oss_period_size / sd > 4096)
+ sd *= 2;
+ if (oss_period_size / sd < 4096)
+ sd = 1;
+ } else
+ sd = runtime->oss.subdivision;
+ oss_period_size /= sd;
+ if (oss_period_size < 16)
+ oss_period_size = 16;
+ }
+
+ min_period_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+ min_period_size *= oss_frame_size;
+ min_period_size = 1 << (ld2(min_period_size - 1) + 1);
+ if (oss_period_size < min_period_size)
+ oss_period_size = min_period_size;
+
+ max_period_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+ max_period_size *= oss_frame_size;
+ max_period_size = 1 << ld2(max_period_size);
+ if (oss_period_size > max_period_size)
+ oss_period_size = max_period_size;
+
+ oss_periods = oss_buffer_size / oss_period_size;
+
+ if (substream->oss.setup) {
+ if (substream->oss.setup->periods > 1)
+ oss_periods = substream->oss.setup->periods;
+ }
+
+ s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+ if (runtime->oss.maxfrags && s > runtime->oss.maxfrags)
+ s = runtime->oss.maxfrags;
+ if (oss_periods > s)
+ oss_periods = s;
+
+ s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+ if (s < 2)
+ s = 2;
+ if (oss_periods < s)
+ oss_periods = s;
+
+ while (oss_period_size * oss_periods > oss_buffer_size)
+ oss_period_size /= 2;
+
+ snd_assert(oss_period_size >= 16, return -EINVAL);
+ runtime->oss.period_bytes = oss_period_size;
+ runtime->oss.period_frames = 1;
+ runtime->oss.periods = oss_periods;
+ return 0;
+}
+
+static int choose_rate(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params, unsigned int best_rate)
+{
+ snd_interval_t *it;
+ snd_pcm_hw_params_t *save;
+ unsigned int rate, prev;
+
+ save = kmalloc(sizeof(*save), GFP_KERNEL);
+ if (save == NULL)
+ return -ENOMEM;
+ *save = *params;
+ it = hw_param_interval(save, SNDRV_PCM_HW_PARAM_RATE);
+
+ /* try multiples of the best rate */
+ rate = best_rate;
+ for (;;) {
+ if (it->max < rate || (it->max == rate && it->openmax))
+ break;
+ if (it->min < rate || (it->min == rate && !it->openmin)) {
+ int ret;
+ ret = snd_pcm_hw_param_set(substream, params,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rate, 0);
+ if (ret == (int)rate) {
+ kfree(save);
+ return rate;
+ }
+ *params = *save;
+ }
+ prev = rate;
+ rate += best_rate;
+ if (rate <= prev)
+ break;
+ }
+
+ /* not found, use the nearest rate */
+ kfree(save);
+ return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL);
+}
+
+static int snd_pcm_oss_change_params(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_hw_params_t *params, *sparams;
+ snd_pcm_sw_params_t *sw_params;
+ ssize_t oss_buffer_size, oss_period_size;
+ size_t oss_frame_size;
+ int err;
+ int direct;
+ int format, sformat, n;
+ snd_mask_t sformat_mask;
+ snd_mask_t mask;
+
+ sw_params = kmalloc(sizeof(*sw_params), GFP_KERNEL);
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ sparams = kmalloc(sizeof(*sparams), GFP_KERNEL);
+ if (!sw_params || !params || !sparams) {
+ snd_printd("No memory\n");
+ err = -ENOMEM;
+ goto failure;
+ }
+
+ if (atomic_read(&runtime->mmap_count)) {
+ direct = 1;
+ } else {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ direct = (setup != NULL && setup->direct);
+ }
+
+ _snd_pcm_hw_params_any(sparams);
+ _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS);
+ _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0);
+ snd_mask_none(&mask);
+ if (atomic_read(&runtime->mmap_count))
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+ else {
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+ if (!direct)
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+ }
+ err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask);
+ if (err < 0) {
+ snd_printd("No usable accesses\n");
+ err = -EINVAL;
+ goto failure;
+ }
+ choose_rate(substream, sparams, runtime->oss.rate);
+ snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, NULL);
+
+ format = snd_pcm_oss_format_from(runtime->oss.format);
+
+ sformat_mask = *hw_param_mask(sparams, SNDRV_PCM_HW_PARAM_FORMAT);
+ if (direct)
+ sformat = format;
+ else
+ sformat = snd_pcm_plug_slave_format(format, &sformat_mask);
+
+ if (sformat < 0 || !snd_mask_test(&sformat_mask, sformat)) {
+ for (sformat = 0; sformat <= SNDRV_PCM_FORMAT_LAST; sformat++) {
+ if (snd_mask_test(&sformat_mask, sformat) &&
+ snd_pcm_oss_format_to(sformat) >= 0)
+ break;
+ }
+ if (sformat > SNDRV_PCM_FORMAT_LAST) {
+ snd_printd("Cannot find a format!!!\n");
+ err = -EINVAL;
+ goto failure;
+ }
+ }
+ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0);
+ snd_assert(err >= 0, goto failure);
+
+ if (direct) {
+ memcpy(params, sparams, sizeof(*params));
+ } else {
+ _snd_pcm_hw_params_any(params);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+ snd_pcm_oss_format_from(runtime->oss.format), 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ runtime->oss.channels, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+ runtime->oss.rate, 0);
+ pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n",
+ params_access(params), params_format(params),
+ params_channels(params), params_rate(params));
+ }
+ pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n",
+ params_access(sparams), params_format(sparams),
+ params_channels(sparams), params_rate(sparams));
+
+ oss_frame_size = snd_pcm_format_physical_width(params_format(params)) *
+ params_channels(params) / 8;
+
+ snd_pcm_oss_plugin_clear(substream);
+ if (!direct) {
+ /* add necessary plugins */
+ snd_pcm_oss_plugin_clear(substream);
+ if ((err = snd_pcm_plug_format_plugins(substream,
+ params,
+ sparams)) < 0) {
+ snd_printd("snd_pcm_plug_format_plugins failed: %i\n", err);
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_t *plugin;
+ if ((err = snd_pcm_plugin_build_io(substream, sparams, &plugin)) < 0) {
+ snd_printd("snd_pcm_plugin_build_io failed: %i\n", err);
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ err = snd_pcm_plugin_append(plugin);
+ } else {
+ err = snd_pcm_plugin_insert(plugin);
+ }
+ if (err < 0) {
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ }
+ }
+
+ err = snd_pcm_oss_period_size(substream, params, sparams);
+ if (err < 0)
+ goto failure;
+
+ n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size);
+ err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL);
+ snd_assert(err >= 0, goto failure);
+
+ err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS,
+ runtime->oss.periods, NULL);
+ snd_assert(err >= 0, goto failure);
+
+ snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+
+ if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams)) < 0) {
+ snd_printd("HW_PARAMS failed: %i\n", err);
+ goto failure;
+ }
+
+ memset(sw_params, 0, sizeof(*sw_params));
+ if (runtime->oss.trigger) {
+ sw_params->start_threshold = 1;
+ } else {
+ sw_params->start_threshold = runtime->boundary;
+ }
+ if (atomic_read(&runtime->mmap_count) || substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ sw_params->stop_threshold = runtime->boundary;
+ else
+ sw_params->stop_threshold = runtime->buffer_size;
+ sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+ sw_params->period_step = 1;
+ sw_params->sleep_min = 0;
+ sw_params->avail_min = 1;
+ sw_params->xfer_align = 1;
+ if (atomic_read(&runtime->mmap_count) ||
+ (substream->oss.setup && substream->oss.setup->nosilence)) {
+ sw_params->silence_threshold = 0;
+ sw_params->silence_size = 0;
+ } else {
+ snd_pcm_uframes_t frames;
+ frames = runtime->period_size + 16;
+ if (frames > runtime->buffer_size)
+ frames = runtime->buffer_size;
+ sw_params->silence_threshold = frames;
+ sw_params->silence_size = frames;
+ }
+
+ if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params)) < 0) {
+ snd_printd("SW_PARAMS failed: %i\n", err);
+ goto failure;
+ }
+
+ runtime->oss.periods = params_periods(sparams);
+ oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams));
+ snd_assert(oss_period_size >= 0, err = -EINVAL; goto failure);
+ if (runtime->oss.plugin_first) {
+ err = snd_pcm_plug_alloc(substream, oss_period_size);
+ if (err < 0)
+ goto failure;
+ }
+ oss_period_size *= oss_frame_size;
+
+ oss_buffer_size = oss_period_size * runtime->oss.periods;
+ snd_assert(oss_buffer_size >= 0, err = -EINVAL; goto failure);
+
+ runtime->oss.period_bytes = oss_period_size;
+ runtime->oss.buffer_bytes = oss_buffer_size;
+
+ pdprintf("oss: period bytes = %i, buffer bytes = %i\n",
+ runtime->oss.period_bytes,
+ runtime->oss.buffer_bytes);
+ pdprintf("slave: period_size = %i, buffer_size = %i\n",
+ params_period_size(sparams),
+ params_buffer_size(sparams));
+
+ runtime->oss.format = snd_pcm_oss_format_to(params_format(params));
+ runtime->oss.channels = params_channels(params);
+ runtime->oss.rate = params_rate(params);
+
+ runtime->oss.params = 0;
+ runtime->oss.prepare = 1;
+ vfree(runtime->oss.buffer);
+ runtime->oss.buffer = vmalloc(runtime->oss.period_bytes);
+ runtime->oss.buffer_used = 0;
+ if (runtime->dma_area)
+ snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes));
+
+ runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size);
+
+ err = 0;
+failure:
+ kfree(sw_params);
+ kfree(params);
+ kfree(sparams);
+ return err;
+}
+
+static int snd_pcm_oss_get_active_substream(snd_pcm_oss_file_t *pcm_oss_file, snd_pcm_substream_t **r_substream)
+{
+ int idx, err;
+ snd_pcm_substream_t *asubstream = NULL, *substream;
+
+ for (idx = 0; idx < 2; idx++) {
+ substream = pcm_oss_file->streams[idx];
+ if (substream == NULL)
+ continue;
+ if (asubstream == NULL)
+ asubstream = substream;
+ if (substream->runtime->oss.params) {
+ err = snd_pcm_oss_change_params(substream);
+ if (err < 0)
+ return err;
+ }
+ }
+ snd_assert(asubstream != NULL, return -EIO);
+ if (r_substream)
+ *r_substream = asubstream;
+ return 0;
+}
+
+static int snd_pcm_oss_prepare(snd_pcm_substream_t *substream)
+{
+ int err;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+ if (err < 0) {
+ snd_printd("snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n");
+ return err;
+ }
+ runtime->oss.prepare = 0;
+ runtime->oss.prev_hw_ptr_interrupt = 0;
+ runtime->oss.period_ptr = 0;
+ runtime->oss.buffer_used = 0;
+
+ return 0;
+}
+
+static int snd_pcm_oss_make_ready(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ int err;
+
+ if (substream == NULL)
+ return 0;
+ runtime = substream->runtime;
+ if (runtime->oss.params) {
+ err = snd_pcm_oss_change_params(substream);
+ if (err < 0)
+ return err;
+ }
+ if (runtime->oss.prepare) {
+ err = snd_pcm_oss_prepare(substream);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_capture_position_fixup(snd_pcm_substream_t *substream, snd_pcm_sframes_t *delay)
+{
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_uframes_t frames;
+ int err = 0;
+
+ while (1) {
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay);
+ if (err < 0)
+ break;
+ runtime = substream->runtime;
+ if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size)
+ break;
+ /* in case of overrun, skip whole periods like OSS/Linux driver does */
+ /* until avail(delay) <= buffer_size */
+ frames = (*delay - runtime->buffer_size) + runtime->period_size - 1;
+ frames /= runtime->period_size;
+ frames *= runtime->period_size;
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames);
+ if (err < 0)
+ break;
+ }
+ return err;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: write: recovering from XRUN\n");
+ else
+ printk("pcm_oss: write: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+ /* test, if we can't store new data, because the stream */
+ /* has not been started */
+ if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+ return -EAGAIN;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t delay;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: read: recovering from XRUN\n");
+ else
+ printk("pcm_oss: read: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+ if (ret < 0)
+ break;
+ } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ ret = snd_pcm_oss_capture_position_fixup(substream, &delay);
+ if (ret < 0)
+ break;
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+ }
+ if (ret == -EPIPE) {
+ if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ if (ret < 0)
+ break;
+ }
+ continue;
+ }
+ if (ret != -ESTRPIPE)
+ break;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: writev: recovering from XRUN\n");
+ else
+ printk("pcm_oss: writev: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+
+ /* test, if we can't store new data, because the stream */
+ /* has not been started */
+ if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+ return -EAGAIN;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: readv: recovering from XRUN\n");
+ else
+ printk("pcm_oss: readv: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+ if (ret < 0)
+ break;
+ } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+ }
+ return ret;
+}
+
+static ssize_t snd_pcm_oss_write2(snd_pcm_substream_t *substream, const char *buf, size_t bytes, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t frames, frames1;
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_channel_t *channels;
+ size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8;
+ if (!in_kernel) {
+ if (copy_from_user(runtime->oss.buffer, (const char __user *)buf, bytes))
+ return -EFAULT;
+ buf = runtime->oss.buffer;
+ }
+ frames = bytes / oss_frame_bytes;
+ frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels);
+ if (frames1 < 0)
+ return frames1;
+ frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames1 * oss_frame_bytes;
+ } else {
+ frames = bytes_to_frames(runtime, bytes);
+ frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames_to_bytes(runtime, frames1);
+ }
+ return bytes;
+}
+
+static ssize_t snd_pcm_oss_write1(snd_pcm_substream_t *substream, const char __user *buf, size_t bytes)
+{
+ size_t xfer = 0;
+ ssize_t tmp;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ if (atomic_read(&runtime->mmap_count))
+ return -ENXIO;
+
+ if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+ return tmp;
+ while (bytes > 0) {
+ if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+ tmp = bytes;
+ if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes)
+ tmp = runtime->oss.period_bytes - runtime->oss.buffer_used;
+ if (tmp > 0) {
+ if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp))
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+ }
+ runtime->oss.buffer_used += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ if ((substream->oss.setup != NULL && substream->oss.setup->partialfrag) ||
+ runtime->oss.buffer_used == runtime->oss.period_bytes) {
+ tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr,
+ runtime->oss.buffer_used - runtime->oss.period_ptr, 1);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ runtime->oss.period_ptr += tmp;
+ runtime->oss.period_ptr %= runtime->oss.period_bytes;
+ if (runtime->oss.period_ptr == 0 ||
+ runtime->oss.period_ptr == runtime->oss.buffer_used)
+ runtime->oss.buffer_used = 0;
+ else if ((substream->ffile->f_flags & O_NONBLOCK) != 0)
+ return xfer > 0 ? xfer : -EAGAIN;
+ }
+ } else {
+ tmp = snd_pcm_oss_write2(substream, (const char *)buf, runtime->oss.period_bytes, 0);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ if ((substream->ffile->f_flags & O_NONBLOCK) != 0 &&
+ tmp != runtime->oss.period_bytes)
+ break;
+ }
+ }
+ return xfer;
+}
+
+static ssize_t snd_pcm_oss_read2(snd_pcm_substream_t *substream, char *buf, size_t bytes, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t frames, frames1;
+ char __user *final_dst = (char __user *)buf;
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_channel_t *channels;
+ size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8;
+ if (!in_kernel)
+ buf = runtime->oss.buffer;
+ frames = bytes / oss_frame_bytes;
+ frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels);
+ if (frames1 < 0)
+ return frames1;
+ frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames1 * oss_frame_bytes;
+ if (!in_kernel && copy_to_user(final_dst, buf, bytes))
+ return -EFAULT;
+ } else {
+ frames = bytes_to_frames(runtime, bytes);
+ frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames_to_bytes(runtime, frames1);
+ }
+ return bytes;
+}
+
+static ssize_t snd_pcm_oss_read1(snd_pcm_substream_t *substream, char __user *buf, size_t bytes)
+{
+ size_t xfer = 0;
+ ssize_t tmp;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ if (atomic_read(&runtime->mmap_count))
+ return -ENXIO;
+
+ if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+ return tmp;
+ while (bytes > 0) {
+ if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+ if (runtime->oss.buffer_used == 0) {
+ tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ runtime->oss.period_ptr = tmp;
+ runtime->oss.buffer_used = tmp;
+ }
+ tmp = bytes;
+ if ((size_t) tmp > runtime->oss.buffer_used)
+ tmp = runtime->oss.buffer_used;
+ if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp))
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ runtime->oss.buffer_used -= tmp;
+ } else {
+ tmp = snd_pcm_oss_read2(substream, (char *)buf, runtime->oss.period_bytes, 0);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ }
+ }
+ return xfer;
+}
+
+static int snd_pcm_oss_reset(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ substream->runtime->oss.prepare = 1;
+ }
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ if (substream != NULL) {
+ snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ substream->runtime->oss.prepare = 1;
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_post(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL);
+ }
+ /* note: all errors from the start action are ignored */
+ /* OSS apps do not know, how to handle them */
+ return 0;
+}
+
+static int snd_pcm_oss_sync1(snd_pcm_substream_t *substream, size_t size)
+{
+ snd_pcm_runtime_t *runtime;
+ ssize_t result = 0;
+ long res;
+ wait_queue_t wait;
+
+ runtime = substream->runtime;
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+#ifdef OSS_DEBUG
+ printk("sync1: size = %li\n", size);
+#endif
+ while (1) {
+ result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1);
+ if (result > 0) {
+ runtime->oss.buffer_used = 0;
+ result = 0;
+ break;
+ }
+ if (result != 0 && result != -EAGAIN)
+ break;
+ result = 0;
+ set_current_state(TASK_INTERRUPTIBLE);
+ snd_pcm_stream_lock_irq(substream);
+ res = runtime->status->state;
+ snd_pcm_stream_unlock_irq(substream);
+ if (res != SNDRV_PCM_STATE_RUNNING) {
+ set_current_state(TASK_RUNNING);
+ break;
+ }
+ res = schedule_timeout(10 * HZ);
+ if (signal_pending(current)) {
+ result = -ERESTARTSYS;
+ break;
+ }
+ if (res == 0) {
+ snd_printk(KERN_ERR "OSS sync error - DMA timeout\n");
+ result = -EIO;
+ break;
+ }
+ }
+ remove_wait_queue(&runtime->sleep, &wait);
+ return result;
+}
+
+static int snd_pcm_oss_sync(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ int err = 0;
+ unsigned int saved_f_flags;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_format_t format;
+ unsigned long width;
+ size_t size;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ runtime = substream->runtime;
+ if (atomic_read(&runtime->mmap_count))
+ goto __direct;
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ format = snd_pcm_oss_format_from(runtime->oss.format);
+ width = snd_pcm_format_physical_width(format);
+ if (runtime->oss.buffer_used > 0) {
+#ifdef OSS_DEBUG
+ printk("sync: buffer_used\n");
+#endif
+ size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width;
+ snd_pcm_format_set_silence(format,
+ runtime->oss.buffer + runtime->oss.buffer_used,
+ size);
+ err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes);
+ if (err < 0)
+ return err;
+ } else if (runtime->oss.period_ptr > 0) {
+#ifdef OSS_DEBUG
+ printk("sync: period_ptr\n");
+#endif
+ size = runtime->oss.period_bytes - runtime->oss.period_ptr;
+ snd_pcm_format_set_silence(format,
+ runtime->oss.buffer,
+ size * 8 / width);
+ err = snd_pcm_oss_sync1(substream, size);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * The ALSA's period might be a bit large than OSS one.
+ * Fill the remain portion of ALSA period with zeros.
+ */
+ size = runtime->control->appl_ptr % runtime->period_size;
+ if (size > 0) {
+ size = runtime->period_size - size;
+ if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ size = (runtime->frame_bits * size) / 8;
+ while (size > 0) {
+ mm_segment_t fs;
+ size_t size1 = size < runtime->oss.period_bytes ? size : runtime->oss.period_bytes;
+ size -= size1;
+ size1 *= 8;
+ size1 /= runtime->sample_bits;
+ snd_pcm_format_set_silence(runtime->format,
+ runtime->oss.buffer,
+ size1);
+ fs = snd_enter_user();
+ snd_pcm_lib_write(substream, (void __user *)runtime->oss.buffer, size1);
+ snd_leave_user(fs);
+ }
+ } else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
+ void __user *buffers[runtime->channels];
+ memset(buffers, 0, runtime->channels * sizeof(void *));
+ snd_pcm_lib_writev(substream, buffers, size);
+ }
+ }
+ /*
+ * finish sync: drain the buffer
+ */
+ __direct:
+ saved_f_flags = substream->ffile->f_flags;
+ substream->ffile->f_flags &= ~O_NONBLOCK;
+ err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+ substream->ffile->f_flags = saved_f_flags;
+ if (err < 0)
+ return err;
+ runtime->oss.prepare = 1;
+ }
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ if (substream != NULL) {
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ runtime = substream->runtime;
+ err = snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ if (err < 0)
+ return err;
+ runtime->oss.buffer_used = 0;
+ runtime->oss.prepare = 1;
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_set_rate(snd_pcm_oss_file_t *pcm_oss_file, int rate)
+{
+ int idx;
+
+ for (idx = 1; idx >= 0; --idx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ snd_pcm_runtime_t *runtime;
+ if (substream == NULL)
+ continue;
+ runtime = substream->runtime;
+ if (rate < 1000)
+ rate = 1000;
+ else if (rate > 192000)
+ rate = 192000;
+ if (runtime->oss.rate != rate) {
+ runtime->oss.params = 1;
+ runtime->oss.rate = rate;
+ }
+ }
+ return snd_pcm_oss_get_rate(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+ return err;
+ return substream->runtime->oss.rate;
+}
+
+static int snd_pcm_oss_set_channels(snd_pcm_oss_file_t *pcm_oss_file, unsigned int channels)
+{
+ int idx;
+ if (channels < 1)
+ channels = 1;
+ if (channels > 128)
+ return -EINVAL;
+ for (idx = 1; idx >= 0; --idx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ snd_pcm_runtime_t *runtime;
+ if (substream == NULL)
+ continue;
+ runtime = substream->runtime;
+ if (runtime->oss.channels != channels) {
+ runtime->oss.params = 1;
+ runtime->oss.channels = channels;
+ }
+ }
+ return snd_pcm_oss_get_channels(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+ return err;
+ return substream->runtime->oss.channels;
+}
+
+static int snd_pcm_oss_get_block_size(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+ return err;
+ return substream->runtime->oss.period_bytes;
+}
+
+static int snd_pcm_oss_get_formats(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+ int direct;
+ snd_pcm_hw_params_t *params;
+ unsigned int formats = 0;
+ snd_mask_t format_mask;
+ int fmt;
+
+ if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+ return err;
+ if (atomic_read(&substream->runtime->mmap_count)) {
+ direct = 1;
+ } else {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ direct = (setup != NULL && setup->direct);
+ }
+ if (!direct)
+ return AFMT_MU_LAW | AFMT_U8 |
+ AFMT_S16_LE | AFMT_S16_BE |
+ AFMT_S8 | AFMT_U16_LE |
+ AFMT_U16_BE;
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+ _snd_pcm_hw_params_any(params);
+ err = snd_pcm_hw_refine(substream, params);
+ format_mask = *hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ kfree(params);
+ snd_assert(err >= 0, return err);
+ for (fmt = 0; fmt < 32; ++fmt) {
+ if (snd_mask_test(&format_mask, fmt)) {
+ int f = snd_pcm_oss_format_to(fmt);
+ if (f >= 0)
+ formats |= f;
+ }
+ }
+ return formats;
+}
+
+static int snd_pcm_oss_set_format(snd_pcm_oss_file_t *pcm_oss_file, int format)
+{
+ int formats, idx;
+
+ if (format != AFMT_QUERY) {
+ formats = snd_pcm_oss_get_formats(pcm_oss_file);
+ if (!(formats & format))
+ format = AFMT_U8;
+ for (idx = 1; idx >= 0; --idx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ snd_pcm_runtime_t *runtime;
+ if (substream == NULL)
+ continue;
+ runtime = substream->runtime;
+ if (runtime->oss.format != format) {
+ runtime->oss.params = 1;
+ runtime->oss.format = format;
+ }
+ }
+ }
+ return snd_pcm_oss_get_format(pcm_oss_file);
+}
+
+static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+ return err;
+ return substream->runtime->oss.format;
+}
+
+static int snd_pcm_oss_set_subdivide1(snd_pcm_substream_t *substream, int subdivide)
+{
+ snd_pcm_runtime_t *runtime;
+
+ if (substream == NULL)
+ return 0;
+ runtime = substream->runtime;
+ if (subdivide == 0) {
+ subdivide = runtime->oss.subdivision;
+ if (subdivide == 0)
+ subdivide = 1;
+ return subdivide;
+ }
+ if (runtime->oss.subdivision || runtime->oss.fragshift)
+ return -EINVAL;
+ if (subdivide != 1 && subdivide != 2 && subdivide != 4 &&
+ subdivide != 8 && subdivide != 16)
+ return -EINVAL;
+ runtime->oss.subdivision = subdivide;
+ runtime->oss.params = 1;
+ return subdivide;
+}
+
+static int snd_pcm_oss_set_subdivide(snd_pcm_oss_file_t *pcm_oss_file, int subdivide)
+{
+ int err = -EINVAL, idx;
+
+ for (idx = 1; idx >= 0; --idx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ if (substream == NULL)
+ continue;
+ if ((err = snd_pcm_oss_set_subdivide1(substream, subdivide)) < 0)
+ return err;
+ }
+ return err;
+}
+
+static int snd_pcm_oss_set_fragment1(snd_pcm_substream_t *substream, unsigned int val)
+{
+ snd_pcm_runtime_t *runtime;
+
+ if (substream == NULL)
+ return 0;
+ runtime = substream->runtime;
+ if (runtime->oss.subdivision || runtime->oss.fragshift)
+ return -EINVAL;
+ runtime->oss.fragshift = val & 0xffff;
+ runtime->oss.maxfrags = (val >> 16) & 0xffff;
+ if (runtime->oss.fragshift < 4) /* < 16 */
+ runtime->oss.fragshift = 4;
+ if (runtime->oss.maxfrags < 2)
+ runtime->oss.maxfrags = 2;
+ runtime->oss.params = 1;
+ return 0;
+}
+
+static int snd_pcm_oss_set_fragment(snd_pcm_oss_file_t *pcm_oss_file, unsigned int val)
+{
+ int err = -EINVAL, idx;
+
+ for (idx = 1; idx >= 0; --idx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ if (substream == NULL)
+ continue;
+ if ((err = snd_pcm_oss_set_fragment1(substream, val)) < 0)
+ return err;
+ }
+ return err;
+}
+
+static int snd_pcm_oss_nonblock(struct file * file)
+{
+ file->f_flags |= O_NONBLOCK;
+ return 0;
+}
+
+static int snd_pcm_oss_get_caps1(snd_pcm_substream_t *substream, int res)
+{
+
+ if (substream == NULL) {
+ res &= ~DSP_CAP_DUPLEX;
+ return res;
+ }
+#ifdef DSP_CAP_MULTI
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ if (substream->pstr->substream_count > 1)
+ res |= DSP_CAP_MULTI;
+#endif
+ /* DSP_CAP_REALTIME is set all times: */
+ /* all ALSA drivers can return actual pointer in ring buffer */
+#if defined(DSP_CAP_REALTIME) && 0
+ {
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH))
+ res &= ~DSP_CAP_REALTIME;
+ }
+#endif
+ return res;
+}
+
+static int snd_pcm_oss_get_caps(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ int result, idx;
+
+ result = DSP_CAP_TRIGGER | DSP_CAP_MMAP | DSP_CAP_DUPLEX | DSP_CAP_REALTIME;
+ for (idx = 0; idx < 2; idx++) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[idx];
+ result = snd_pcm_oss_get_caps1(substream, result);
+ }
+ result |= 0x0001; /* revision - same as SB AWE 64 */
+ return result;
+}
+
+static void snd_pcm_oss_simulate_fill(snd_pcm_substream_t *substream, snd_pcm_uframes_t hw_ptr)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t appl_ptr;
+ appl_ptr = hw_ptr + runtime->buffer_size;
+ appl_ptr %= runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+}
+
+static int snd_pcm_oss_set_trigger(snd_pcm_oss_file_t *pcm_oss_file, int trigger)
+{
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+ int err, cmd;
+
+#ifdef OSS_DEBUG
+ printk("pcm_oss: trigger = 0x%x\n", trigger);
+#endif
+
+ psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+ if (psubstream) {
+ if ((err = snd_pcm_oss_make_ready(psubstream)) < 0)
+ return err;
+ }
+ if (csubstream) {
+ if ((err = snd_pcm_oss_make_ready(csubstream)) < 0)
+ return err;
+ }
+ if (psubstream) {
+ runtime = psubstream->runtime;
+ if (trigger & PCM_ENABLE_OUTPUT) {
+ if (runtime->oss.trigger)
+ goto _skip1;
+ if (atomic_read(&psubstream->runtime->mmap_count))
+ snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt);
+ runtime->oss.trigger = 1;
+ runtime->start_threshold = 1;
+ cmd = SNDRV_PCM_IOCTL_START;
+ } else {
+ if (!runtime->oss.trigger)
+ goto _skip1;
+ runtime->oss.trigger = 0;
+ runtime->start_threshold = runtime->boundary;
+ cmd = SNDRV_PCM_IOCTL_DROP;
+ runtime->oss.prepare = 1;
+ }
+ err = snd_pcm_kernel_playback_ioctl(psubstream, cmd, NULL);
+ if (err < 0)
+ return err;
+ }
+ _skip1:
+ if (csubstream) {
+ runtime = csubstream->runtime;
+ if (trigger & PCM_ENABLE_INPUT) {
+ if (runtime->oss.trigger)
+ goto _skip2;
+ runtime->oss.trigger = 1;
+ runtime->start_threshold = 1;
+ cmd = SNDRV_PCM_IOCTL_START;
+ } else {
+ if (!runtime->oss.trigger)
+ goto _skip2;
+ runtime->oss.trigger = 0;
+ runtime->start_threshold = runtime->boundary;
+ cmd = SNDRV_PCM_IOCTL_DROP;
+ runtime->oss.prepare = 1;
+ }
+ err = snd_pcm_kernel_capture_ioctl(csubstream, cmd, NULL);
+ if (err < 0)
+ return err;
+ }
+ _skip2:
+ return 0;
+}
+
+static int snd_pcm_oss_get_trigger(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+ int result = 0;
+
+ psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger)
+ result |= PCM_ENABLE_OUTPUT;
+ if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger)
+ result |= PCM_ENABLE_INPUT;
+ return result;
+}
+
+static int snd_pcm_oss_get_odelay(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t delay;
+ int err;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream == NULL)
+ return -EINVAL;
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ runtime = substream->runtime;
+ if (runtime->oss.params || runtime->oss.prepare)
+ return 0;
+ err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay);
+ if (err == -EPIPE)
+ delay = 0; /* hack for broken OSS applications */
+ else if (err < 0)
+ return err;
+ return snd_pcm_oss_bytes(substream, delay);
+}
+
+static int snd_pcm_oss_get_ptr(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct count_info __user * _info)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t delay;
+ int fixup;
+ struct count_info info;
+ int err;
+
+ if (_info == NULL)
+ return -EFAULT;
+ substream = pcm_oss_file->streams[stream];
+ if (substream == NULL)
+ return -EINVAL;
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ runtime = substream->runtime;
+ if (runtime->oss.params || runtime->oss.prepare) {
+ memset(&info, 0, sizeof(info));
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay);
+ if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) {
+ err = 0;
+ delay = 0;
+ fixup = 0;
+ } else {
+ fixup = runtime->oss.buffer_used;
+ }
+ } else {
+ err = snd_pcm_oss_capture_position_fixup(substream, &delay);
+ fixup = -runtime->oss.buffer_used;
+ }
+ if (err < 0)
+ return err;
+ info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size);
+ if (atomic_read(&runtime->mmap_count)) {
+ snd_pcm_sframes_t n;
+ n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt;
+ if (n < 0)
+ n += runtime->boundary;
+ info.blocks = n / runtime->period_size;
+ runtime->oss.prev_hw_ptr_interrupt = delay;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_pcm_oss_simulate_fill(substream, delay);
+ info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX;
+ } else {
+ delay = snd_pcm_oss_bytes(substream, delay) + fixup;
+ info.blocks = delay / runtime->oss.period_bytes;
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ info.bytes = (runtime->oss.bytes - delay) & INT_MAX;
+ else
+ info.bytes = (runtime->oss.bytes + delay) & INT_MAX;
+ }
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_pcm_oss_get_space(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct audio_buf_info __user *_info)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t avail;
+ int fixup;
+ struct audio_buf_info info;
+ int err;
+
+ if (_info == NULL)
+ return -EFAULT;
+ substream = pcm_oss_file->streams[stream];
+ if (substream == NULL)
+ return -EINVAL;
+ runtime = substream->runtime;
+
+ if (runtime->oss.params &&
+ (err = snd_pcm_oss_change_params(substream)) < 0)
+ return err;
+
+ info.fragsize = runtime->oss.period_bytes;
+ info.fragstotal = runtime->periods;
+ if (runtime->oss.prepare) {
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ info.bytes = runtime->oss.period_bytes * runtime->oss.periods;
+ info.fragments = runtime->oss.periods;
+ } else {
+ info.bytes = 0;
+ info.fragments = 0;
+ }
+ } else {
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail);
+ if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) {
+ avail = runtime->buffer_size;
+ err = 0;
+ fixup = 0;
+ } else {
+ avail = runtime->buffer_size - avail;
+ fixup = -runtime->oss.buffer_used;
+ }
+ } else {
+ err = snd_pcm_oss_capture_position_fixup(substream, &avail);
+ fixup = runtime->oss.buffer_used;
+ }
+ if (err < 0)
+ return err;
+ info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup;
+ info.fragments = info.bytes / runtime->oss.period_bytes;
+ }
+
+#ifdef OSS_DEBUG
+ printk("pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", info.bytes, info.fragments, info.fragstotal, info.fragsize);
+#endif
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_pcm_oss_get_mapbuf(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct buffmem_desc __user * _info)
+{
+ // it won't be probably implemented
+ // snd_printd("TODO: snd_pcm_oss_get_mapbuf\n");
+ return -EINVAL;
+}
+
+static snd_pcm_oss_setup_t *snd_pcm_oss_look_for_setup(snd_pcm_t *pcm, int stream, const char *task_name)
+{
+ const char *ptr, *ptrl;
+ snd_pcm_oss_setup_t *setup;
+
+ down(&pcm->streams[stream].oss.setup_mutex);
+ for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) {
+ if (!strcmp(setup->task_name, task_name)) {
+ up(&pcm->streams[stream].oss.setup_mutex);
+ return setup;
+ }
+ }
+ ptr = ptrl = task_name;
+ while (*ptr) {
+ if (*ptr == '/')
+ ptrl = ptr + 1;
+ ptr++;
+ }
+ if (ptrl == task_name) {
+ goto __not_found;
+ return NULL;
+ }
+ for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) {
+ if (!strcmp(setup->task_name, ptrl)) {
+ up(&pcm->streams[stream].oss.setup_mutex);
+ return setup;
+ }
+ }
+ __not_found:
+ up(&pcm->streams[stream].oss.setup_mutex);
+ return NULL;
+}
+
+static void snd_pcm_oss_init_substream(snd_pcm_substream_t *substream,
+ snd_pcm_oss_setup_t *setup,
+ int minor)
+{
+ snd_pcm_runtime_t *runtime;
+
+ substream->oss.oss = 1;
+ substream->oss.setup = setup;
+ runtime = substream->runtime;
+ runtime->oss.params = 1;
+ runtime->oss.trigger = 1;
+ runtime->oss.rate = 8000;
+ switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
+ case SNDRV_MINOR_OSS_PCM_8:
+ runtime->oss.format = AFMT_U8;
+ break;
+ case SNDRV_MINOR_OSS_PCM_16:
+ runtime->oss.format = AFMT_S16_LE;
+ break;
+ default:
+ runtime->oss.format = AFMT_MU_LAW;
+ }
+ runtime->oss.channels = 1;
+ runtime->oss.fragshift = 0;
+ runtime->oss.maxfrags = 0;
+ runtime->oss.subdivision = 0;
+}
+
+static void snd_pcm_oss_release_substream(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ runtime = substream->runtime;
+ vfree(runtime->oss.buffer);
+ snd_pcm_oss_plugin_clear(substream);
+ substream->oss.file = NULL;
+ substream->oss.oss = 0;
+}
+
+static int snd_pcm_oss_release_file(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ int cidx;
+ snd_assert(pcm_oss_file != NULL, return -ENXIO);
+ for (cidx = 0; cidx < 2; ++cidx) {
+ snd_pcm_substream_t *substream = pcm_oss_file->streams[cidx];
+ snd_pcm_runtime_t *runtime;
+ if (substream == NULL)
+ continue;
+ runtime = substream->runtime;
+
+ snd_pcm_stream_lock_irq(substream);
+ if (snd_pcm_running(substream))
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ snd_pcm_stream_unlock_irq(substream);
+ if (substream->open_flag) {
+ if (substream->ops->hw_free != NULL)
+ substream->ops->hw_free(substream);
+ substream->ops->close(substream);
+ substream->open_flag = 0;
+ }
+ substream->ffile = NULL;
+ snd_pcm_oss_release_substream(substream);
+ snd_pcm_release_substream(substream);
+ }
+ kfree(pcm_oss_file);
+ return 0;
+}
+
+static int snd_pcm_oss_open_file(struct file *file,
+ snd_pcm_t *pcm,
+ snd_pcm_oss_file_t **rpcm_oss_file,
+ int minor,
+ snd_pcm_oss_setup_t *psetup,
+ snd_pcm_oss_setup_t *csetup)
+{
+ int err = 0;
+ snd_pcm_oss_file_t *pcm_oss_file;
+ snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+ unsigned int f_mode = file->f_mode;
+
+ snd_assert(rpcm_oss_file != NULL, return -EINVAL);
+ *rpcm_oss_file = NULL;
+
+ pcm_oss_file = kcalloc(1, sizeof(*pcm_oss_file), GFP_KERNEL);
+ if (pcm_oss_file == NULL)
+ return -ENOMEM;
+
+ if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) &&
+ (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX))
+ f_mode = FMODE_WRITE;
+ if ((f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) {
+ if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &psubstream)) < 0) {
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK] = psubstream;
+ }
+ if ((f_mode & FMODE_READ) && !(csetup && csetup->disable)) {
+ if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &csubstream)) < 0) {
+ if (!(f_mode & FMODE_WRITE) || err != -ENODEV) {
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ } else {
+ csubstream = NULL;
+ }
+ }
+ pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE] = csubstream;
+ }
+
+ if (psubstream == NULL && csubstream == NULL) {
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return -EINVAL;
+ }
+ if (psubstream != NULL) {
+ psubstream->oss.file = pcm_oss_file;
+ err = snd_pcm_hw_constraints_init(psubstream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraint_init failed\n");
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ if ((err = psubstream->ops->open(psubstream)) < 0) {
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ psubstream->open_flag = 1;
+ err = snd_pcm_hw_constraints_complete(psubstream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraint_complete failed\n");
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ psubstream->ffile = file;
+ snd_pcm_oss_init_substream(psubstream, psetup, minor);
+ }
+ if (csubstream != NULL) {
+ csubstream->oss.file = pcm_oss_file;
+ err = snd_pcm_hw_constraints_init(csubstream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraint_init failed\n");
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ if ((err = csubstream->ops->open(csubstream)) < 0) {
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ csubstream->open_flag = 1;
+ err = snd_pcm_hw_constraints_complete(csubstream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraint_complete failed\n");
+ snd_pcm_oss_release_file(pcm_oss_file);
+ return err;
+ }
+ csubstream->ffile = file;
+ snd_pcm_oss_init_substream(csubstream, csetup, minor);
+ }
+
+ file->private_data = pcm_oss_file;
+ *rpcm_oss_file = pcm_oss_file;
+ return 0;
+}
+
+
+static int snd_pcm_oss_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ int cardnum = SNDRV_MINOR_OSS_CARD(minor);
+ int device;
+ int err;
+ char task_name[32];
+ snd_pcm_t *pcm;
+ snd_pcm_oss_file_t *pcm_oss_file;
+ snd_pcm_oss_setup_t *psetup = NULL, *csetup = NULL;
+ int nonblock;
+ wait_queue_t wait;
+
+ snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
+ device = SNDRV_MINOR_OSS_DEVICE(minor) == SNDRV_MINOR_OSS_PCM1 ?
+ adsp_map[cardnum] : dsp_map[cardnum];
+
+ pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + device];
+ if (pcm == NULL) {
+ err = -ENODEV;
+ goto __error1;
+ }
+ err = snd_card_file_add(pcm->card, file);
+ if (err < 0)
+ goto __error1;
+ if (!try_module_get(pcm->card->module)) {
+ err = -EFAULT;
+ goto __error2;
+ }
+ if (snd_task_name(current, task_name, sizeof(task_name)) < 0) {
+ err = -EFAULT;
+ goto __error;
+ }
+ if (file->f_mode & FMODE_WRITE)
+ psetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name);
+ if (file->f_mode & FMODE_READ)
+ csetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name);
+
+ nonblock = !!(file->f_flags & O_NONBLOCK);
+ if (psetup && !psetup->disable) {
+ if (psetup->nonblock)
+ nonblock = 1;
+ else if (psetup->block)
+ nonblock = 0;
+ } else if (csetup && !csetup->disable) {
+ if (csetup->nonblock)
+ nonblock = 1;
+ else if (csetup->block)
+ nonblock = 0;
+ }
+ if (!nonblock)
+ nonblock = nonblock_open;
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pcm->open_wait, &wait);
+ down(&pcm->open_mutex);
+ while (1) {
+ err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file,
+ minor, psetup, csetup);
+ if (err >= 0)
+ break;
+ if (err == -EAGAIN) {
+ if (nonblock) {
+ err = -EBUSY;
+ break;
+ }
+ } else
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ up(&pcm->open_mutex);
+ schedule();
+ down(&pcm->open_mutex);
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ }
+ remove_wait_queue(&pcm->open_wait, &wait);
+ up(&pcm->open_mutex);
+ if (err < 0)
+ goto __error;
+ return err;
+
+ __error:
+ module_put(pcm->card->module);
+ __error2:
+ snd_card_file_remove(pcm->card, file);
+ __error1:
+ return err;
+}
+
+static int snd_pcm_oss_release(struct inode *inode, struct file *file)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_substream_t *substream;
+ snd_pcm_oss_file_t *pcm_oss_file;
+
+ pcm_oss_file = file->private_data;
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream == NULL)
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ snd_assert(substream != NULL, return -ENXIO);
+ pcm = substream->pcm;
+ snd_pcm_oss_sync(pcm_oss_file);
+ down(&pcm->open_mutex);
+ snd_pcm_oss_release_file(pcm_oss_file);
+ up(&pcm->open_mutex);
+ wake_up(&pcm->open_wait);
+ module_put(pcm->card->module);
+ snd_card_file_remove(pcm->card, file);
+ return 0;
+}
+
+static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_pcm_oss_file_t *pcm_oss_file;
+ int __user *p = (int __user *)arg;
+ int res;
+
+ pcm_oss_file = file->private_data;
+ if (cmd == OSS_GETVERSION)
+ return put_user(SNDRV_OSS_VERSION, p);
+ if (cmd == OSS_ALSAEMULVER)
+ return put_user(1, p);
+#if defined(CONFIG_SND_MIXER_OSS) || (defined(MODULE) && defined(CONFIG_SND_MIXER_OSS_MODULE))
+ if (((cmd >> 8) & 0xff) == 'M') { /* mixer ioctl - for OSS compatibility */
+ snd_pcm_substream_t *substream;
+ int idx;
+ for (idx = 0; idx < 2; ++idx) {
+ substream = pcm_oss_file->streams[idx];
+ if (substream != NULL)
+ break;
+ }
+ snd_assert(substream != NULL, return -ENXIO);
+ return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg);
+ }
+#endif
+ if (((cmd >> 8) & 0xff) != 'P')
+ return -EINVAL;
+#ifdef OSS_DEBUG
+ printk("pcm_oss: ioctl = 0x%x\n", cmd);
+#endif
+ switch (cmd) {
+ case SNDCTL_DSP_RESET:
+ return snd_pcm_oss_reset(pcm_oss_file);
+ case SNDCTL_DSP_SYNC:
+ return snd_pcm_oss_sync(pcm_oss_file);
+ case SNDCTL_DSP_SPEED:
+ if (get_user(res, p))
+ return -EFAULT;
+ if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0)
+ return res;
+ return put_user(res, p);
+ case SOUND_PCM_READ_RATE:
+ res = snd_pcm_oss_get_rate(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_STEREO:
+ if (get_user(res, p))
+ return -EFAULT;
+ res = res > 0 ? 2 : 1;
+ if ((res = snd_pcm_oss_set_channels(pcm_oss_file, res)) < 0)
+ return res;
+ return put_user(--res, p);
+ case SNDCTL_DSP_GETBLKSIZE:
+ res = snd_pcm_oss_get_block_size(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(res, p))
+ return -EFAULT;
+ res = snd_pcm_oss_set_format(pcm_oss_file, res);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SOUND_PCM_READ_BITS:
+ res = snd_pcm_oss_get_format(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_CHANNELS:
+ if (get_user(res, p))
+ return -EFAULT;
+ res = snd_pcm_oss_set_channels(pcm_oss_file, res);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SOUND_PCM_READ_CHANNELS:
+ res = snd_pcm_oss_get_channels(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SOUND_PCM_WRITE_FILTER:
+ case SOUND_PCM_READ_FILTER:
+ return -EIO;
+ case SNDCTL_DSP_POST:
+ return snd_pcm_oss_post(pcm_oss_file);
+ case SNDCTL_DSP_SUBDIVIDE:
+ if (get_user(res, p))
+ return -EFAULT;
+ res = snd_pcm_oss_set_subdivide(pcm_oss_file, res);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_SETFRAGMENT:
+ if (get_user(res, p))
+ return -EFAULT;
+ return snd_pcm_oss_set_fragment(pcm_oss_file, res);
+ case SNDCTL_DSP_GETFMTS:
+ res = snd_pcm_oss_get_formats(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_GETOSPACE:
+ case SNDCTL_DSP_GETISPACE:
+ return snd_pcm_oss_get_space(pcm_oss_file,
+ cmd == SNDCTL_DSP_GETISPACE ?
+ SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+ (struct audio_buf_info __user *) arg);
+ case SNDCTL_DSP_NONBLOCK:
+ return snd_pcm_oss_nonblock(file);
+ case SNDCTL_DSP_GETCAPS:
+ res = snd_pcm_oss_get_caps(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_GETTRIGGER:
+ res = snd_pcm_oss_get_trigger(pcm_oss_file);
+ if (res < 0)
+ return res;
+ return put_user(res, p);
+ case SNDCTL_DSP_SETTRIGGER:
+ if (get_user(res, p))
+ return -EFAULT;
+ return snd_pcm_oss_set_trigger(pcm_oss_file, res);
+ case SNDCTL_DSP_GETIPTR:
+ case SNDCTL_DSP_GETOPTR:
+ return snd_pcm_oss_get_ptr(pcm_oss_file,
+ cmd == SNDCTL_DSP_GETIPTR ?
+ SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+ (struct count_info __user *) arg);
+ case SNDCTL_DSP_MAPINBUF:
+ case SNDCTL_DSP_MAPOUTBUF:
+ return snd_pcm_oss_get_mapbuf(pcm_oss_file,
+ cmd == SNDCTL_DSP_MAPINBUF ?
+ SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK,
+ (struct buffmem_desc __user *) arg);
+ case SNDCTL_DSP_SETSYNCRO:
+ /* stop DMA now.. */
+ return 0;
+ case SNDCTL_DSP_SETDUPLEX:
+ if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX)
+ return 0;
+ return -EIO;
+ case SNDCTL_DSP_GETODELAY:
+ res = snd_pcm_oss_get_odelay(pcm_oss_file);
+ if (res < 0) {
+ /* it's for sure, some broken apps don't check for error codes */
+ put_user(0, p);
+ return res;
+ }
+ return put_user(res, p);
+ case SNDCTL_DSP_PROFILE:
+ return 0; /* silently ignore */
+ default:
+ snd_printd("pcm_oss: unknown command = 0x%x\n", cmd);
+ }
+ return -EINVAL;
+}
+
+#ifdef CONFIG_COMPAT
+/* all compatible */
+#define snd_pcm_oss_ioctl_compat snd_pcm_oss_ioctl
+#else
+#define snd_pcm_oss_ioctl_compat NULL
+#endif
+
+static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ snd_pcm_oss_file_t *pcm_oss_file;
+ snd_pcm_substream_t *substream;
+
+ pcm_oss_file = file->private_data;
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ if (substream == NULL)
+ return -ENXIO;
+#ifndef OSS_DEBUG
+ return snd_pcm_oss_read1(substream, buf, count);
+#else
+ {
+ ssize_t res = snd_pcm_oss_read1(substream, buf, count);
+ printk("pcm_oss: read %li bytes (returned %li bytes)\n", (long)count, (long)res);
+ return res;
+ }
+#endif
+}
+
+static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+ snd_pcm_oss_file_t *pcm_oss_file;
+ snd_pcm_substream_t *substream;
+ long result;
+
+ pcm_oss_file = file->private_data;
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream == NULL)
+ return -ENXIO;
+ up(&file->f_dentry->d_inode->i_sem);
+ result = snd_pcm_oss_write1(substream, buf, count);
+ down(&file->f_dentry->d_inode->i_sem);
+#ifdef OSS_DEBUG
+ printk("pcm_oss: write %li bytes (wrote %li bytes)\n", (long)count, (long)result);
+#endif
+ return result;
+}
+
+static int snd_pcm_oss_playback_ready(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (atomic_read(&runtime->mmap_count))
+ return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+ else
+ return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames;
+}
+
+static int snd_pcm_oss_capture_ready(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (atomic_read(&runtime->mmap_count))
+ return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+ else
+ return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames;
+}
+
+static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)
+{
+ snd_pcm_oss_file_t *pcm_oss_file;
+ unsigned int mask;
+ snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL;
+
+ pcm_oss_file = file->private_data;
+
+ psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+ mask = 0;
+ if (psubstream != NULL) {
+ snd_pcm_runtime_t *runtime = psubstream->runtime;
+ poll_wait(file, &runtime->sleep, wait);
+ snd_pcm_stream_lock_irq(psubstream);
+ if (runtime->status->state != SNDRV_PCM_STATE_DRAINING &&
+ (runtime->status->state != SNDRV_PCM_STATE_RUNNING ||
+ snd_pcm_oss_playback_ready(psubstream)))
+ mask |= POLLOUT | POLLWRNORM;
+ snd_pcm_stream_unlock_irq(psubstream);
+ }
+ if (csubstream != NULL) {
+ snd_pcm_runtime_t *runtime = csubstream->runtime;
+ enum sndrv_pcm_state ostate;
+ poll_wait(file, &runtime->sleep, wait);
+ snd_pcm_stream_lock_irq(csubstream);
+ if ((ostate = runtime->status->state) != SNDRV_PCM_STATE_RUNNING ||
+ snd_pcm_oss_capture_ready(csubstream))
+ mask |= POLLIN | POLLRDNORM;
+ snd_pcm_stream_unlock_irq(csubstream);
+ if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) {
+ snd_pcm_oss_file_t ofile;
+ memset(&ofile, 0, sizeof(ofile));
+ ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ runtime->oss.trigger = 0;
+ snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT);
+ }
+ }
+
+ return mask;
+}
+
+static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area)
+{
+ snd_pcm_oss_file_t *pcm_oss_file;
+ snd_pcm_substream_t *substream = NULL;
+ snd_pcm_runtime_t *runtime;
+ int err;
+
+#ifdef OSS_DEBUG
+ printk("pcm_oss: mmap begin\n");
+#endif
+ pcm_oss_file = file->private_data;
+ switch ((area->vm_flags & (VM_READ | VM_WRITE))) {
+ case VM_READ | VM_WRITE:
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream)
+ break;
+ /* Fall through */
+ case VM_READ:
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ break;
+ case VM_WRITE:
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ break;
+ default:
+ return -EINVAL;
+ }
+ /* set VM_READ access as well to fix memset() routines that do
+ reads before writes (to improve performance) */
+ area->vm_flags |= VM_READ;
+ if (substream == NULL)
+ return -ENXIO;
+ runtime = substream->runtime;
+ if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID))
+ return -EIO;
+ if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED)
+ runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED;
+ else
+ return -EIO;
+
+ if (runtime->oss.params) {
+ if ((err = snd_pcm_oss_change_params(substream)) < 0)
+ return err;
+ }
+ if (runtime->oss.plugin_first != NULL)
+ return -EIO;
+
+ if (area->vm_pgoff != 0)
+ return -EINVAL;
+
+ err = snd_pcm_mmap_data(substream, file, area);
+ if (err < 0)
+ return err;
+ runtime->oss.mmap_bytes = area->vm_end - area->vm_start;
+ runtime->silence_threshold = 0;
+ runtime->silence_size = 0;
+#ifdef OSS_DEBUG
+ printk("pcm_oss: mmap ok, bytes = 0x%x\n", runtime->oss.mmap_bytes);
+#endif
+ /* In mmap mode we never stop */
+ runtime->stop_threshold = runtime->boundary;
+
+ return 0;
+}
+
+/*
+ * /proc interface
+ */
+
+static void snd_pcm_oss_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+ snd_pcm_oss_setup_t *setup = pstr->oss.setup_list;
+ down(&pstr->oss.setup_mutex);
+ while (setup) {
+ snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n",
+ setup->task_name,
+ setup->periods,
+ setup->period_size,
+ setup->disable ? " disable" : "",
+ setup->direct ? " direct" : "",
+ setup->block ? " block" : "",
+ setup->nonblock ? " non-block" : "",
+ setup->partialfrag ? " partial-frag" : "",
+ setup->nosilence ? " no-silence" : "");
+ setup = setup->next;
+ }
+ up(&pstr->oss.setup_mutex);
+}
+
+static void snd_pcm_oss_proc_free_setup_list(snd_pcm_str_t * pstr)
+{
+ unsigned int idx;
+ snd_pcm_substream_t *substream;
+ snd_pcm_oss_setup_t *setup, *setupn;
+
+ for (idx = 0, substream = pstr->substream;
+ idx < pstr->substream_count; idx++, substream = substream->next)
+ substream->oss.setup = NULL;
+ for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL;
+ setup; setup = setupn) {
+ setupn = setup->next;
+ kfree(setup->task_name);
+ kfree(setup);
+ }
+ pstr->oss.setup_list = NULL;
+}
+
+static void snd_pcm_oss_proc_write(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+ char line[128], str[32], task_name[32], *ptr;
+ int idx1;
+ snd_pcm_oss_setup_t *setup, *setup1, template;
+
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ down(&pstr->oss.setup_mutex);
+ memset(&template, 0, sizeof(template));
+ ptr = snd_info_get_str(task_name, line, sizeof(task_name));
+ if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) {
+ snd_pcm_oss_proc_free_setup_list(pstr);
+ up(&pstr->oss.setup_mutex);
+ continue;
+ }
+ for (setup = pstr->oss.setup_list; setup; setup = setup->next) {
+ if (!strcmp(setup->task_name, task_name)) {
+ template = *setup;
+ break;
+ }
+ }
+ ptr = snd_info_get_str(str, ptr, sizeof(str));
+ template.periods = simple_strtoul(str, NULL, 10);
+ ptr = snd_info_get_str(str, ptr, sizeof(str));
+ template.period_size = simple_strtoul(str, NULL, 10);
+ for (idx1 = 31; idx1 >= 0; idx1--)
+ if (template.period_size & (1 << idx1))
+ break;
+ for (idx1--; idx1 >= 0; idx1--)
+ template.period_size &= ~(1 << idx1);
+ do {
+ ptr = snd_info_get_str(str, ptr, sizeof(str));
+ if (!strcmp(str, "disable")) {
+ template.disable = 1;
+ } else if (!strcmp(str, "direct")) {
+ template.direct = 1;
+ } else if (!strcmp(str, "block")) {
+ template.block = 1;
+ } else if (!strcmp(str, "non-block")) {
+ template.nonblock = 1;
+ } else if (!strcmp(str, "partial-frag")) {
+ template.partialfrag = 1;
+ } else if (!strcmp(str, "no-silence")) {
+ template.nosilence = 1;
+ }
+ } while (*str);
+ if (setup == NULL) {
+ setup = (snd_pcm_oss_setup_t *) kmalloc(sizeof(snd_pcm_oss_setup_t), GFP_KERNEL);
+ if (setup) {
+ if (pstr->oss.setup_list == NULL) {
+ pstr->oss.setup_list = setup;
+ } else {
+ for (setup1 = pstr->oss.setup_list; setup1->next; setup1 = setup1->next);
+ setup1->next = setup;
+ }
+ template.task_name = snd_kmalloc_strdup(task_name, GFP_KERNEL);
+ } else {
+ buffer->error = -ENOMEM;
+ }
+ }
+ if (setup)
+ *setup = template;
+ up(&pstr->oss.setup_mutex);
+ }
+}
+
+static void snd_pcm_oss_proc_init(snd_pcm_t *pcm)
+{
+ int stream;
+ for (stream = 0; stream < 2; ++stream) {
+ snd_info_entry_t *entry;
+ snd_pcm_str_t *pstr = &pcm->streams[stream];
+ if (pstr->substream_count == 0)
+ continue;
+ if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) {
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->c.text.read_size = 8192;
+ entry->c.text.read = snd_pcm_oss_proc_read;
+ entry->c.text.write_size = 8192;
+ entry->c.text.write = snd_pcm_oss_proc_write;
+ entry->private_data = pstr;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ pstr->oss.proc_entry = entry;
+ }
+}
+
+static void snd_pcm_oss_proc_done(snd_pcm_t *pcm)
+{
+ int stream;
+ for (stream = 0; stream < 2; ++stream) {
+ snd_pcm_str_t *pstr = &pcm->streams[stream];
+ if (pstr->oss.proc_entry) {
+ snd_info_unregister(pstr->oss.proc_entry);
+ pstr->oss.proc_entry = NULL;
+ snd_pcm_oss_proc_free_setup_list(pstr);
+ }
+ }
+}
+
+/*
+ * ENTRY functions
+ */
+
+static struct file_operations snd_pcm_oss_f_reg =
+{
+ .owner = THIS_MODULE,
+ .read = snd_pcm_oss_read,
+ .write = snd_pcm_oss_write,
+ .open = snd_pcm_oss_open,
+ .release = snd_pcm_oss_release,
+ .poll = snd_pcm_oss_poll,
+ .unlocked_ioctl = snd_pcm_oss_ioctl,
+ .compat_ioctl = snd_pcm_oss_ioctl_compat,
+ .mmap = snd_pcm_oss_mmap,
+};
+
+static snd_minor_t snd_pcm_oss_reg =
+{
+ .comment = "digital audio",
+ .f_ops = &snd_pcm_oss_f_reg,
+};
+
+static void register_oss_dsp(snd_pcm_t *pcm, int index)
+{
+ char name[128];
+ sprintf(name, "dsp%i%i", pcm->card->number, pcm->device);
+ if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+ pcm->card, index, &snd_pcm_oss_reg,
+ name) < 0) {
+ snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device);
+ }
+}
+
+static int snd_pcm_oss_register_minor(snd_pcm_t * pcm)
+{
+ pcm->oss.reg = 0;
+ if (dsp_map[pcm->card->number] == (int)pcm->device) {
+ char name[128];
+ int duplex;
+ register_oss_dsp(pcm, 0);
+ duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 &&
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count &&
+ !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX));
+ sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : "");
+#ifdef SNDRV_OSS_INFO_DEV_AUDIO
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO,
+ pcm->card->number,
+ name);
+#endif
+ pcm->oss.reg++;
+ pcm->oss.reg_mask |= 1;
+ }
+ if (adsp_map[pcm->card->number] == (int)pcm->device) {
+ register_oss_dsp(pcm, 1);
+ pcm->oss.reg++;
+ pcm->oss.reg_mask |= 2;
+ }
+
+ if (pcm->oss.reg)
+ snd_pcm_oss_proc_init(pcm);
+
+ return 0;
+}
+
+static int snd_pcm_oss_disconnect_minor(snd_pcm_t * pcm)
+{
+ if (pcm->oss.reg) {
+ if (pcm->oss.reg_mask & 1) {
+ pcm->oss.reg_mask &= ~1;
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+ pcm->card, 0);
+ }
+ if (pcm->oss.reg_mask & 2) {
+ pcm->oss.reg_mask &= ~2;
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
+ pcm->card, 1);
+ }
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_unregister_minor(snd_pcm_t * pcm)
+{
+ snd_pcm_oss_disconnect_minor(pcm);
+ if (pcm->oss.reg) {
+ if (dsp_map[pcm->card->number] == (int)pcm->device) {
+#ifdef SNDRV_OSS_INFO_DEV_AUDIO
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number);
+#endif
+ }
+ pcm->oss.reg = 0;
+ snd_pcm_oss_proc_done(pcm);
+ }
+ return 0;
+}
+
+static snd_pcm_notify_t snd_pcm_oss_notify =
+{
+ .n_register = snd_pcm_oss_register_minor,
+ .n_disconnect = snd_pcm_oss_disconnect_minor,
+ .n_unregister = snd_pcm_oss_unregister_minor,
+};
+
+static int __init alsa_pcm_oss_init(void)
+{
+ int i;
+ int err;
+
+ /* check device map table */
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) {
+ snd_printk("invalid dsp_map[%d] = %d\n", i, dsp_map[i]);
+ dsp_map[i] = 0;
+ }
+ if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) {
+ snd_printk("invalid adsp_map[%d] = %d\n", i, adsp_map[i]);
+ adsp_map[i] = 1;
+ }
+ }
+ if ((err = snd_pcm_notify(&snd_pcm_oss_notify, 0)) < 0)
+ return err;
+ return 0;
+}
+
+static void __exit alsa_pcm_oss_exit(void)
+{
+ snd_pcm_notify(&snd_pcm_oss_notify, 1);
+}
+
+module_init(alsa_pcm_oss_init)
+module_exit(alsa_pcm_oss_exit)
diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c
new file mode 100644
index 00000000000..6bb31009f0b
--- /dev/null
+++ b/sound/core/oss/pcm_plugin.c
@@ -0,0 +1,921 @@
+/*
+ * PCM Plug-In shared (kernel/library) code
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#if 0
+#define PLUGIN_DEBUG
+#endif
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+
+#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
+#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
+
+static int snd_pcm_plugin_src_channels_mask(snd_pcm_plugin_t *plugin,
+ bitset_t *dst_vmask,
+ bitset_t **src_vmask)
+{
+ bitset_t *vmask = plugin->src_vmask;
+ bitset_copy(vmask, dst_vmask, plugin->src_format.channels);
+ *src_vmask = vmask;
+ return 0;
+}
+
+static int snd_pcm_plugin_dst_channels_mask(snd_pcm_plugin_t *plugin,
+ bitset_t *src_vmask,
+ bitset_t **dst_vmask)
+{
+ bitset_t *vmask = plugin->dst_vmask;
+ bitset_copy(vmask, src_vmask, plugin->dst_format.channels);
+ *dst_vmask = vmask;
+ return 0;
+}
+
+/*
+ * because some cards might have rates "very close", we ignore
+ * all "resampling" requests within +-5%
+ */
+static int rate_match(unsigned int src_rate, unsigned int dst_rate)
+{
+ unsigned int low = (src_rate * 95) / 100;
+ unsigned int high = (src_rate * 105) / 100;
+ return dst_rate >= low && dst_rate <= high;
+}
+
+static int snd_pcm_plugin_alloc(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+ snd_pcm_plugin_format_t *format;
+ ssize_t width;
+ size_t size;
+ unsigned int channel;
+ snd_pcm_plugin_channel_t *c;
+
+ if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ format = &plugin->src_format;
+ } else {
+ format = &plugin->dst_format;
+ }
+ if ((width = snd_pcm_format_physical_width(format->format)) < 0)
+ return width;
+ size = frames * format->channels * width;
+ snd_assert((size % 8) == 0, return -ENXIO);
+ size /= 8;
+ if (plugin->buf_frames < frames) {
+ vfree(plugin->buf);
+ plugin->buf = vmalloc(size);
+ plugin->buf_frames = frames;
+ }
+ if (!plugin->buf) {
+ plugin->buf_frames = 0;
+ return -ENOMEM;
+ }
+ c = plugin->buf_channels;
+ if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ for (channel = 0; channel < format->channels; channel++, c++) {
+ c->frames = frames;
+ c->enabled = 1;
+ c->wanted = 0;
+ c->area.addr = plugin->buf;
+ c->area.first = channel * width;
+ c->area.step = format->channels * width;
+ }
+ } else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
+ snd_assert((size % format->channels) == 0,);
+ size /= format->channels;
+ for (channel = 0; channel < format->channels; channel++, c++) {
+ c->frames = frames;
+ c->enabled = 1;
+ c->wanted = 0;
+ c->area.addr = plugin->buf + (channel * size);
+ c->area.first = 0;
+ c->area.step = width;
+ }
+ } else
+ return -EINVAL;
+ return 0;
+}
+
+int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames)
+{
+ int err;
+ snd_assert(snd_pcm_plug_first(plug) != NULL, return -ENXIO);
+ if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
+ snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
+ while (plugin->next) {
+ if (plugin->dst_frames)
+ frames = plugin->dst_frames(plugin, frames);
+ snd_assert(frames > 0, return -ENXIO);
+ plugin = plugin->next;
+ err = snd_pcm_plugin_alloc(plugin, frames);
+ if (err < 0)
+ return err;
+ }
+ } else {
+ snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+ while (plugin->prev) {
+ if (plugin->src_frames)
+ frames = plugin->src_frames(plugin, frames);
+ snd_assert(frames > 0, return -ENXIO);
+ plugin = plugin->prev;
+ err = snd_pcm_plugin_alloc(plugin, frames);
+ if (err < 0)
+ return err;
+ }
+ }
+ return 0;
+}
+
+
+snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
+ snd_pcm_uframes_t frames,
+ snd_pcm_plugin_channel_t **channels)
+{
+ *channels = plugin->buf_channels;
+ return frames;
+}
+
+int snd_pcm_plugin_build(snd_pcm_plug_t *plug,
+ const char *name,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ size_t extra,
+ snd_pcm_plugin_t **ret)
+{
+ snd_pcm_plugin_t *plugin;
+ unsigned int channels;
+
+ snd_assert(plug != NULL, return -ENXIO);
+ snd_assert(src_format != NULL && dst_format != NULL, return -ENXIO);
+ plugin = kcalloc(1, sizeof(*plugin) + extra, GFP_KERNEL);
+ if (plugin == NULL)
+ return -ENOMEM;
+ plugin->name = name;
+ plugin->plug = plug;
+ plugin->stream = snd_pcm_plug_stream(plug);
+ plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+ plugin->src_format = *src_format;
+ plugin->src_width = snd_pcm_format_physical_width(src_format->format);
+ snd_assert(plugin->src_width > 0, );
+ plugin->dst_format = *dst_format;
+ plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
+ snd_assert(plugin->dst_width > 0, );
+ if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ channels = src_format->channels;
+ else
+ channels = dst_format->channels;
+ plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
+ if (plugin->buf_channels == NULL) {
+ snd_pcm_plugin_free(plugin);
+ return -ENOMEM;
+ }
+ plugin->src_vmask = bitset_alloc(src_format->channels);
+ if (plugin->src_vmask == NULL) {
+ snd_pcm_plugin_free(plugin);
+ return -ENOMEM;
+ }
+ plugin->dst_vmask = bitset_alloc(dst_format->channels);
+ if (plugin->dst_vmask == NULL) {
+ snd_pcm_plugin_free(plugin);
+ return -ENOMEM;
+ }
+ plugin->client_channels = snd_pcm_plugin_client_channels;
+ plugin->src_channels_mask = snd_pcm_plugin_src_channels_mask;
+ plugin->dst_channels_mask = snd_pcm_plugin_dst_channels_mask;
+ *ret = plugin;
+ return 0;
+}
+
+int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin)
+{
+ if (! plugin)
+ return 0;
+ if (plugin->private_free)
+ plugin->private_free(plugin);
+ kfree(plugin->buf_channels);
+ vfree(plugin->buf);
+ kfree(plugin->src_vmask);
+ kfree(plugin->dst_vmask);
+ kfree(plugin);
+ return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t drv_frames)
+{
+ snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
+ int stream = snd_pcm_plug_stream(plug);
+
+ snd_assert(plug != NULL, return -ENXIO);
+ if (drv_frames == 0)
+ return 0;
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ plugin = snd_pcm_plug_last(plug);
+ while (plugin && drv_frames > 0) {
+ plugin_prev = plugin->prev;
+ if (plugin->src_frames)
+ drv_frames = plugin->src_frames(plugin, drv_frames);
+ plugin = plugin_prev;
+ }
+ } else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+ plugin = snd_pcm_plug_first(plug);
+ while (plugin && drv_frames > 0) {
+ plugin_next = plugin->next;
+ if (plugin->dst_frames)
+ drv_frames = plugin->dst_frames(plugin, drv_frames);
+ plugin = plugin_next;
+ }
+ } else
+ snd_BUG();
+ return drv_frames;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t clt_frames)
+{
+ snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next;
+ snd_pcm_sframes_t frames;
+ int stream = snd_pcm_plug_stream(plug);
+
+ snd_assert(plug != NULL, return -ENXIO);
+ if (clt_frames == 0)
+ return 0;
+ frames = clt_frames;
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ plugin = snd_pcm_plug_first(plug);
+ while (plugin && frames > 0) {
+ plugin_next = plugin->next;
+ if (plugin->dst_frames) {
+ frames = plugin->dst_frames(plugin, frames);
+ if (frames < 0)
+ return frames;
+ }
+ plugin = plugin_next;
+ }
+ } else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+ plugin = snd_pcm_plug_last(plug);
+ while (plugin) {
+ plugin_prev = plugin->prev;
+ if (plugin->src_frames) {
+ frames = plugin->src_frames(plugin, frames);
+ if (frames < 0)
+ return frames;
+ }
+ plugin = plugin_prev;
+ }
+ } else
+ snd_BUG();
+ return frames;
+}
+
+static int snd_pcm_plug_formats(snd_mask_t *mask, int format)
+{
+ snd_mask_t formats = *mask;
+ u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
+ SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
+ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW);
+
+ if (formats.bits[0] & (u32)linfmts)
+ formats.bits[0] |= (u32)linfmts;
+ if (formats.bits[1] & (u32)(linfmts >> 32))
+ formats.bits[1] |= (u32)(linfmts >> 32);
+ return snd_mask_test(&formats, format);
+}
+
+static int preferred_formats[] = {
+ SNDRV_PCM_FORMAT_S16_LE,
+ SNDRV_PCM_FORMAT_S16_BE,
+ SNDRV_PCM_FORMAT_U16_LE,
+ SNDRV_PCM_FORMAT_U16_BE,
+ SNDRV_PCM_FORMAT_S24_LE,
+ SNDRV_PCM_FORMAT_S24_BE,
+ SNDRV_PCM_FORMAT_U24_LE,
+ SNDRV_PCM_FORMAT_U24_BE,
+ SNDRV_PCM_FORMAT_S32_LE,
+ SNDRV_PCM_FORMAT_S32_BE,
+ SNDRV_PCM_FORMAT_U32_LE,
+ SNDRV_PCM_FORMAT_U32_BE,
+ SNDRV_PCM_FORMAT_S8,
+ SNDRV_PCM_FORMAT_U8
+};
+
+int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask)
+{
+ if (snd_mask_test(format_mask, format))
+ return format;
+ if (! snd_pcm_plug_formats(format_mask, format))
+ return -EINVAL;
+ if (snd_pcm_format_linear(format)) {
+ int width = snd_pcm_format_width(format);
+ int unsignd = snd_pcm_format_unsigned(format);
+ int big = snd_pcm_format_big_endian(format);
+ int format1;
+ int wid, width1=width;
+ int dwidth1 = 8;
+ for (wid = 0; wid < 4; ++wid) {
+ int end, big1 = big;
+ for (end = 0; end < 2; ++end) {
+ int sgn, unsignd1 = unsignd;
+ for (sgn = 0; sgn < 2; ++sgn) {
+ format1 = snd_pcm_build_linear_format(width1, unsignd1, big1);
+ if (format1 >= 0 &&
+ snd_mask_test(format_mask, format1))
+ goto _found;
+ unsignd1 = !unsignd1;
+ }
+ big1 = !big1;
+ }
+ if (width1 == 32) {
+ dwidth1 = -dwidth1;
+ width1 = width;
+ }
+ width1 += dwidth1;
+ }
+ return -EINVAL;
+ _found:
+ return format1;
+ } else {
+ unsigned int i;
+ switch (format) {
+ case SNDRV_PCM_FORMAT_MU_LAW:
+ for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
+ int format1 = preferred_formats[i];
+ if (snd_mask_test(format_mask, format1))
+ return format1;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+}
+
+int snd_pcm_plug_format_plugins(snd_pcm_plug_t *plug,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *slave_params)
+{
+ snd_pcm_plugin_format_t tmpformat;
+ snd_pcm_plugin_format_t dstformat;
+ snd_pcm_plugin_format_t srcformat;
+ int src_access, dst_access;
+ snd_pcm_plugin_t *plugin = NULL;
+ int err;
+ int stream = snd_pcm_plug_stream(plug);
+ int slave_interleaved = (params_channels(slave_params) == 1 ||
+ params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+
+ switch (stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ dstformat.format = params_format(slave_params);
+ dstformat.rate = params_rate(slave_params);
+ dstformat.channels = params_channels(slave_params);
+ srcformat.format = params_format(params);
+ srcformat.rate = params_rate(params);
+ srcformat.channels = params_channels(params);
+ src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+ dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
+ SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ dstformat.format = params_format(params);
+ dstformat.rate = params_rate(params);
+ dstformat.channels = params_channels(params);
+ srcformat.format = params_format(slave_params);
+ srcformat.rate = params_rate(slave_params);
+ srcformat.channels = params_channels(slave_params);
+ src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
+ SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+ dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+ break;
+ default:
+ snd_BUG();
+ return -EINVAL;
+ }
+ tmpformat = srcformat;
+
+ pdprintf("srcformat: format=%i, rate=%i, channels=%i\n",
+ srcformat.format,
+ srcformat.rate,
+ srcformat.channels);
+ pdprintf("dstformat: format=%i, rate=%i, channels=%i\n",
+ dstformat.format,
+ dstformat.rate,
+ dstformat.channels);
+
+ /* Format change (linearization) */
+ if ((srcformat.format != dstformat.format ||
+ !rate_match(srcformat.rate, dstformat.rate) ||
+ srcformat.channels != dstformat.channels) &&
+ !snd_pcm_format_linear(srcformat.format)) {
+ if (snd_pcm_format_linear(dstformat.format))
+ tmpformat.format = dstformat.format;
+ else
+ tmpformat.format = SNDRV_PCM_FORMAT_S16;
+ switch (srcformat.format) {
+ case SNDRV_PCM_FORMAT_MU_LAW:
+ err = snd_pcm_plugin_build_mulaw(plug,
+ &srcformat, &tmpformat,
+ &plugin);
+ break;
+ default:
+ return -EINVAL;
+ }
+ pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
+ if (err < 0)
+ return err;
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ srcformat = tmpformat;
+ src_access = dst_access;
+ }
+
+ /* channels reduction */
+ if (srcformat.channels > dstformat.channels) {
+ int sv = srcformat.channels;
+ int dv = dstformat.channels;
+ route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
+ if (ttable == NULL)
+ return -ENOMEM;
+#if 1
+ if (sv == 2 && dv == 1) {
+ ttable[0] = HALF;
+ ttable[1] = HALF;
+ } else
+#endif
+ {
+ int v;
+ for (v = 0; v < dv; ++v)
+ ttable[v * sv + v] = FULL;
+ }
+ tmpformat.channels = dstformat.channels;
+ if (rate_match(srcformat.rate, dstformat.rate) &&
+ snd_pcm_format_linear(dstformat.format))
+ tmpformat.format = dstformat.format;
+ err = snd_pcm_plugin_build_route(plug,
+ &srcformat, &tmpformat,
+ ttable, &plugin);
+ kfree(ttable);
+ pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ srcformat = tmpformat;
+ src_access = dst_access;
+ }
+
+ /* rate resampling */
+ if (!rate_match(srcformat.rate, dstformat.rate)) {
+ tmpformat.rate = dstformat.rate;
+ if (srcformat.channels == dstformat.channels &&
+ snd_pcm_format_linear(dstformat.format))
+ tmpformat.format = dstformat.format;
+ err = snd_pcm_plugin_build_rate(plug,
+ &srcformat, &tmpformat,
+ &plugin);
+ pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ srcformat = tmpformat;
+ src_access = dst_access;
+ }
+
+ /* channels extension */
+ if (srcformat.channels < dstformat.channels) {
+ int sv = srcformat.channels;
+ int dv = dstformat.channels;
+ route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL);
+ if (ttable == NULL)
+ return -ENOMEM;
+#if 0
+ {
+ int v;
+ for (v = 0; v < sv; ++v)
+ ttable[v * sv + v] = FULL;
+ }
+#else
+ {
+ /* Playback is spreaded on all channels */
+ int vd, vs;
+ for (vd = 0, vs = 0; vd < dv; ++vd) {
+ ttable[vd * sv + vs] = FULL;
+ vs++;
+ if (vs == sv)
+ vs = 0;
+ }
+ }
+#endif
+ tmpformat.channels = dstformat.channels;
+ if (snd_pcm_format_linear(dstformat.format))
+ tmpformat.format = dstformat.format;
+ err = snd_pcm_plugin_build_route(plug,
+ &srcformat, &tmpformat,
+ ttable, &plugin);
+ kfree(ttable);
+ pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ srcformat = tmpformat;
+ src_access = dst_access;
+ }
+
+ /* format change */
+ if (srcformat.format != dstformat.format) {
+ tmpformat.format = dstformat.format;
+ if (tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
+ err = snd_pcm_plugin_build_mulaw(plug,
+ &srcformat, &tmpformat,
+ &plugin);
+ }
+ else if (snd_pcm_format_linear(srcformat.format) &&
+ snd_pcm_format_linear(tmpformat.format)) {
+ err = snd_pcm_plugin_build_linear(plug,
+ &srcformat, &tmpformat,
+ &plugin);
+ }
+ else
+ return -EINVAL;
+ pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
+ if (err < 0)
+ return err;
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ srcformat = tmpformat;
+ src_access = dst_access;
+ }
+
+ /* de-interleave */
+ if (src_access != dst_access) {
+ err = snd_pcm_plugin_build_copy(plug,
+ &srcformat,
+ &tmpformat,
+ &plugin);
+ pdprintf("interleave change (copy: returns %i)\n", err);
+ if (err < 0)
+ return err;
+ err = snd_pcm_plugin_append(plugin);
+ if (err < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *plug,
+ char *buf,
+ snd_pcm_uframes_t count,
+ snd_pcm_plugin_channel_t **channels)
+{
+ snd_pcm_plugin_t *plugin;
+ snd_pcm_plugin_channel_t *v;
+ snd_pcm_plugin_format_t *format;
+ int width, nchannels, channel;
+ int stream = snd_pcm_plug_stream(plug);
+
+ snd_assert(buf != NULL, return -ENXIO);
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ plugin = snd_pcm_plug_first(plug);
+ format = &plugin->src_format;
+ } else {
+ plugin = snd_pcm_plug_last(plug);
+ format = &plugin->dst_format;
+ }
+ v = plugin->buf_channels;
+ *channels = v;
+ if ((width = snd_pcm_format_physical_width(format->format)) < 0)
+ return width;
+ nchannels = format->channels;
+ snd_assert(plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || format->channels <= 1, return -ENXIO);
+ for (channel = 0; channel < nchannels; channel++, v++) {
+ v->frames = count;
+ v->enabled = 1;
+ v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
+ v->area.addr = buf;
+ v->area.first = channel * width;
+ v->area.step = nchannels * width;
+ }
+ return count;
+}
+
+static int snd_pcm_plug_playback_channels_mask(snd_pcm_plug_t *plug,
+ bitset_t *client_vmask)
+{
+ snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+ if (plugin == NULL) {
+ return 0;
+ } else {
+ int schannels = plugin->dst_format.channels;
+ bitset_t bs[bitset_size(schannels)];
+ bitset_t *srcmask;
+ bitset_t *dstmask = bs;
+ int err;
+ bitset_one(dstmask, schannels);
+ if (plugin == NULL) {
+ bitset_and(client_vmask, dstmask, schannels);
+ return 0;
+ }
+ while (1) {
+ err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
+ if (err < 0)
+ return err;
+ dstmask = srcmask;
+ if (plugin->prev == NULL)
+ break;
+ plugin = plugin->prev;
+ }
+ bitset_and(client_vmask, dstmask, plugin->src_format.channels);
+ return 0;
+ }
+}
+
+static int snd_pcm_plug_playback_disable_useless_channels(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_channel_t *src_channels)
+{
+ snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug);
+ unsigned int nchannels = plugin->src_format.channels;
+ bitset_t bs[bitset_size(nchannels)];
+ bitset_t *srcmask = bs;
+ int err;
+ unsigned int channel;
+ for (channel = 0; channel < nchannels; channel++) {
+ if (src_channels[channel].enabled)
+ bitset_set(srcmask, channel);
+ else
+ bitset_reset(srcmask, channel);
+ }
+ err = snd_pcm_plug_playback_channels_mask(plug, srcmask);
+ if (err < 0)
+ return err;
+ for (channel = 0; channel < nchannels; channel++) {
+ if (!bitset_get(srcmask, channel))
+ src_channels[channel].enabled = 0;
+ }
+ return 0;
+}
+
+static int snd_pcm_plug_capture_disable_useless_channels(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *client_channels)
+{
+ snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug);
+ unsigned int nchannels = plugin->dst_format.channels;
+ bitset_t bs[bitset_size(nchannels)];
+ bitset_t *dstmask = bs;
+ bitset_t *srcmask;
+ int err;
+ unsigned int channel;
+ for (channel = 0; channel < nchannels; channel++) {
+ if (client_channels[channel].enabled)
+ bitset_set(dstmask, channel);
+ else
+ bitset_reset(dstmask, channel);
+ }
+ while (plugin) {
+ err = plugin->src_channels_mask(plugin, dstmask, &srcmask);
+ if (err < 0)
+ return err;
+ dstmask = srcmask;
+ plugin = plugin->prev;
+ }
+ plugin = snd_pcm_plug_first(plug);
+ nchannels = plugin->src_format.channels;
+ for (channel = 0; channel < nchannels; channel++) {
+ if (!bitset_get(dstmask, channel))
+ src_channels[channel].enabled = 0;
+ }
+ return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size)
+{
+ snd_pcm_plugin_t *plugin, *next;
+ snd_pcm_plugin_channel_t *dst_channels;
+ int err;
+ snd_pcm_sframes_t frames = size;
+
+ if ((err = snd_pcm_plug_playback_disable_useless_channels(plug, src_channels)) < 0)
+ return err;
+
+ plugin = snd_pcm_plug_first(plug);
+ while (plugin && frames > 0) {
+ if ((next = plugin->next) != NULL) {
+ snd_pcm_sframes_t frames1 = frames;
+ if (plugin->dst_frames)
+ frames1 = plugin->dst_frames(plugin, frames);
+ if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) {
+ return err;
+ }
+ if (err != frames1) {
+ frames = err;
+ if (plugin->src_frames)
+ frames = plugin->src_frames(plugin, frames1);
+ }
+ } else
+ dst_channels = NULL;
+ pdprintf("write plugin: %s, %li\n", plugin->name, frames);
+ if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
+ return frames;
+ src_channels = dst_channels;
+ plugin = next;
+ }
+ return snd_pcm_plug_client_size(plug, frames);
+}
+
+snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size)
+{
+ snd_pcm_plugin_t *plugin, *next;
+ snd_pcm_plugin_channel_t *src_channels, *dst_channels;
+ snd_pcm_sframes_t frames = size;
+ int err;
+
+ frames = snd_pcm_plug_slave_size(plug, frames);
+ if (frames < 0)
+ return frames;
+
+ src_channels = NULL;
+ plugin = snd_pcm_plug_first(plug);
+ while (plugin && frames > 0) {
+ if ((next = plugin->next) != NULL) {
+ if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) {
+ return err;
+ }
+ frames = err;
+ if (!plugin->prev) {
+ if ((err = snd_pcm_plug_capture_disable_useless_channels(plug, dst_channels, dst_channels_final)) < 0)
+ return err;
+ }
+ } else {
+ dst_channels = dst_channels_final;
+ }
+ pdprintf("read plugin: %s, %li\n", plugin->name, frames);
+ if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
+ return frames;
+ plugin = next;
+ src_channels = dst_channels;
+ }
+ return frames;
+}
+
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
+ size_t samples, int format)
+{
+ /* FIXME: sub byte resolution and odd dst_offset */
+ unsigned char *dst;
+ unsigned int dst_step;
+ int width;
+ const unsigned char *silence;
+ if (!dst_area->addr)
+ return 0;
+ dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
+ width = snd_pcm_format_physical_width(format);
+ if (width <= 0)
+ return -EINVAL;
+ if (dst_area->step == (unsigned int) width && width >= 8)
+ return snd_pcm_format_set_silence(format, dst, samples);
+ silence = snd_pcm_format_silence_64(format);
+ if (! silence)
+ return -EINVAL;
+ dst_step = dst_area->step / 8;
+ if (width == 4) {
+ /* Ima ADPCM */
+ int dstbit = dst_area->first % 8;
+ int dstbit_step = dst_area->step % 8;
+ while (samples-- > 0) {
+ if (dstbit)
+ *dst &= 0xf0;
+ else
+ *dst &= 0x0f;
+ dst += dst_step;
+ dstbit += dstbit_step;
+ if (dstbit == 8) {
+ dst++;
+ dstbit = 0;
+ }
+ }
+ } else {
+ width /= 8;
+ while (samples-- > 0) {
+ memcpy(dst, silence, width);
+ dst += dst_step;
+ }
+ }
+ return 0;
+}
+
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_area, size_t src_offset,
+ const snd_pcm_channel_area_t *dst_area, size_t dst_offset,
+ size_t samples, int format)
+{
+ /* FIXME: sub byte resolution and odd dst_offset */
+ char *src, *dst;
+ int width;
+ int src_step, dst_step;
+ src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
+ if (!src_area->addr)
+ return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
+ dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
+ if (!dst_area->addr)
+ return 0;
+ width = snd_pcm_format_physical_width(format);
+ if (width <= 0)
+ return -EINVAL;
+ if (src_area->step == (unsigned int) width &&
+ dst_area->step == (unsigned int) width && width >= 8) {
+ size_t bytes = samples * width / 8;
+ memcpy(dst, src, bytes);
+ return 0;
+ }
+ src_step = src_area->step / 8;
+ dst_step = dst_area->step / 8;
+ if (width == 4) {
+ /* Ima ADPCM */
+ int srcbit = src_area->first % 8;
+ int srcbit_step = src_area->step % 8;
+ int dstbit = dst_area->first % 8;
+ int dstbit_step = dst_area->step % 8;
+ while (samples-- > 0) {
+ unsigned char srcval;
+ if (srcbit)
+ srcval = *src & 0x0f;
+ else
+ srcval = (*src & 0xf0) >> 4;
+ if (dstbit)
+ *dst = (*dst & 0xf0) | srcval;
+ else
+ *dst = (*dst & 0x0f) | (srcval << 4);
+ src += src_step;
+ srcbit += srcbit_step;
+ if (srcbit == 8) {
+ src++;
+ srcbit = 0;
+ }
+ dst += dst_step;
+ dstbit += dstbit_step;
+ if (dstbit == 8) {
+ dst++;
+ dstbit = 0;
+ }
+ }
+ } else {
+ width /= 8;
+ while (samples-- > 0) {
+ memcpy(dst, src, width);
+ src += src_step;
+ dst += dst_step;
+ }
+ }
+ return 0;
+}
diff --git a/sound/core/oss/pcm_plugin.h b/sound/core/oss/pcm_plugin.h
new file mode 100644
index 00000000000..0f86ce47749
--- /dev/null
+++ b/sound/core/oss/pcm_plugin.h
@@ -0,0 +1,250 @@
+#ifndef __PCM_PLUGIN_H
+#define __PCM_PLUGIN_H
+
+/*
+ * Digital Audio (Plugin interface) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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
+ *
+ */
+
+#ifndef ATTRIBUTE_UNUSED
+#define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+typedef unsigned int bitset_t;
+
+static inline size_t bitset_size(int nbits)
+{
+ return (nbits + sizeof(bitset_t) * 8 - 1) / (sizeof(bitset_t) * 8);
+}
+
+static inline bitset_t *bitset_alloc(int nbits)
+{
+ return kcalloc(bitset_size(nbits), sizeof(bitset_t), GFP_KERNEL);
+}
+
+static inline void bitset_set(bitset_t *bitmap, unsigned int pos)
+{
+ size_t bits = sizeof(*bitmap) * 8;
+ bitmap[pos / bits] |= 1 << (pos % bits);
+}
+
+static inline void bitset_reset(bitset_t *bitmap, unsigned int pos)
+{
+ size_t bits = sizeof(*bitmap) * 8;
+ bitmap[pos / bits] &= ~(1 << (pos % bits));
+}
+
+static inline int bitset_get(bitset_t *bitmap, unsigned int pos)
+{
+ size_t bits = sizeof(*bitmap) * 8;
+ return !!(bitmap[pos / bits] & (1 << (pos % bits)));
+}
+
+static inline void bitset_copy(bitset_t *dst, bitset_t *src, unsigned int nbits)
+{
+ memcpy(dst, src, bitset_size(nbits) * sizeof(bitset_t));
+}
+
+static inline void bitset_and(bitset_t *dst, bitset_t *bs, unsigned int nbits)
+{
+ bitset_t *end = dst + bitset_size(nbits);
+ while (dst < end)
+ *dst++ &= *bs++;
+}
+
+static inline void bitset_or(bitset_t *dst, bitset_t *bs, unsigned int nbits)
+{
+ bitset_t *end = dst + bitset_size(nbits);
+ while (dst < end)
+ *dst++ |= *bs++;
+}
+
+static inline void bitset_zero(bitset_t *dst, unsigned int nbits)
+{
+ bitset_t *end = dst + bitset_size(nbits);
+ while (dst < end)
+ *dst++ = 0;
+}
+
+static inline void bitset_one(bitset_t *dst, unsigned int nbits)
+{
+ bitset_t *end = dst + bitset_size(nbits);
+ while (dst < end)
+ *dst++ = ~(bitset_t)0;
+}
+
+#define snd_pcm_plug_t snd_pcm_substream_t
+#define snd_pcm_plug_stream(plug) ((plug)->stream)
+
+typedef enum {
+ INIT = 0,
+ PREPARE = 1,
+} snd_pcm_plugin_action_t;
+
+typedef struct _snd_pcm_channel_area {
+ void *addr; /* base address of channel samples */
+ unsigned int first; /* offset to first sample in bits */
+ unsigned int step; /* samples distance in bits */
+} snd_pcm_channel_area_t;
+
+typedef struct _snd_pcm_plugin_channel {
+ void *aptr; /* pointer to the allocated area */
+ snd_pcm_channel_area_t area;
+ snd_pcm_uframes_t frames; /* allocated frames */
+ unsigned int enabled:1; /* channel need to be processed */
+ unsigned int wanted:1; /* channel is wanted */
+} snd_pcm_plugin_channel_t;
+
+typedef struct _snd_pcm_plugin_format {
+ int format;
+ unsigned int rate;
+ unsigned int channels;
+} snd_pcm_plugin_format_t;
+
+struct _snd_pcm_plugin {
+ const char *name; /* plug-in name */
+ int stream;
+ snd_pcm_plugin_format_t src_format; /* source format */
+ snd_pcm_plugin_format_t dst_format; /* destination format */
+ int src_width; /* sample width in bits */
+ int dst_width; /* sample width in bits */
+ int access;
+ snd_pcm_sframes_t (*src_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t dst_frames);
+ snd_pcm_sframes_t (*dst_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t src_frames);
+ snd_pcm_sframes_t (*client_channels)(snd_pcm_plugin_t *plugin,
+ snd_pcm_uframes_t frames,
+ snd_pcm_plugin_channel_t **channels);
+ int (*src_channels_mask)(snd_pcm_plugin_t *plugin,
+ bitset_t *dst_vmask,
+ bitset_t **src_vmask);
+ int (*dst_channels_mask)(snd_pcm_plugin_t *plugin,
+ bitset_t *src_vmask,
+ bitset_t **dst_vmask);
+ snd_pcm_sframes_t (*transfer)(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames);
+ int (*action)(snd_pcm_plugin_t *plugin,
+ snd_pcm_plugin_action_t action,
+ unsigned long data);
+ snd_pcm_plugin_t *prev;
+ snd_pcm_plugin_t *next;
+ snd_pcm_plug_t *plug;
+ void *private_data;
+ void (*private_free)(snd_pcm_plugin_t *plugin);
+ char *buf;
+ snd_pcm_uframes_t buf_frames;
+ snd_pcm_plugin_channel_t *buf_channels;
+ bitset_t *src_vmask;
+ bitset_t *dst_vmask;
+ char extra_data[0];
+};
+
+int snd_pcm_plugin_build(snd_pcm_plug_t *handle,
+ const char *name,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ size_t extra,
+ snd_pcm_plugin_t **ret);
+int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin);
+int snd_pcm_plugin_clear(snd_pcm_plugin_t **first);
+int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t drv_size);
+snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t clt_size);
+
+#define FULL ROUTE_PLUGIN_RESOLUTION
+#define HALF ROUTE_PLUGIN_RESOLUTION / 2
+typedef int route_ttable_entry_t;
+
+int snd_pcm_plugin_build_io(snd_pcm_plug_t *handle,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_linear(snd_pcm_plug_t *handle,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *handle,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_rate(snd_pcm_plug_t *handle,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_route(snd_pcm_plug_t *handle,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ route_ttable_entry_t *ttable,
+ snd_pcm_plugin_t **r_plugin);
+int snd_pcm_plugin_build_copy(snd_pcm_plug_t *handle,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin);
+
+int snd_pcm_plug_format_plugins(snd_pcm_plug_t *substream,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *slave_params);
+
+int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask);
+
+int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin);
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size);
+
+snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *handle,
+ char *buf, snd_pcm_uframes_t count,
+ snd_pcm_plugin_channel_t **channels);
+
+snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin,
+ snd_pcm_uframes_t frames,
+ snd_pcm_plugin_channel_t **channels);
+
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
+ size_t samples, int format);
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_channel, size_t src_offset,
+ const snd_pcm_channel_area_t *dst_channel, size_t dst_offset,
+ size_t samples, int format);
+
+void *snd_pcm_plug_buf_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t size);
+void snd_pcm_plug_buf_unlock(snd_pcm_plug_t *plug, void *ptr);
+snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t size, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t size, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel);
+
+
+
+#define ROUTE_PLUGIN_RESOLUTION 16
+
+int getput_index(int format);
+int copy_index(int format);
+int conv_index(int src_format, int dst_format);
+
+void zero_channel(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *dst_channel,
+ size_t samples);
+
+#ifdef PLUGIN_DEBUG
+#define pdprintf( fmt, args... ) printk( "plugin: " fmt, ##args)
+#else
+#define pdprintf( fmt, args... )
+#endif
+
+#endif /* __PCM_PLUGIN_H */
diff --git a/sound/core/oss/plugin_ops.h b/sound/core/oss/plugin_ops.h
new file mode 100644
index 00000000000..0607e956608
--- /dev/null
+++ b/sound/core/oss/plugin_ops.h
@@ -0,0 +1,536 @@
+/*
+ * Plugin sample operators with fast switch
+ * Copyright (c) 2000 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#define as_u8(ptr) (*(u_int8_t*)(ptr))
+#define as_u16(ptr) (*(u_int16_t*)(ptr))
+#define as_u32(ptr) (*(u_int32_t*)(ptr))
+#define as_u64(ptr) (*(u_int64_t*)(ptr))
+#define as_s8(ptr) (*(int8_t*)(ptr))
+#define as_s16(ptr) (*(int16_t*)(ptr))
+#define as_s32(ptr) (*(int32_t*)(ptr))
+#define as_s64(ptr) (*(int64_t*)(ptr))
+
+#ifdef COPY_LABELS
+static void *copy_labels[4] = {
+ &&copy_8,
+ &&copy_16,
+ &&copy_32,
+ &&copy_64
+};
+#endif
+
+#ifdef COPY_END
+while(0) {
+copy_8: as_s8(dst) = as_s8(src); goto COPY_END;
+copy_16: as_s16(dst) = as_s16(src); goto COPY_END;
+copy_32: as_s32(dst) = as_s32(src); goto COPY_END;
+copy_64: as_s64(dst) = as_s64(src); goto COPY_END;
+}
+#endif
+
+#ifdef CONV_LABELS
+/* src_wid src_endswap sign_toggle dst_wid dst_endswap */
+static void *conv_labels[4 * 2 * 2 * 4 * 2] = {
+ &&conv_xxx1_xxx1, /* 8h -> 8h */
+ &&conv_xxx1_xxx1, /* 8h -> 8s */
+ &&conv_xxx1_xx10, /* 8h -> 16h */
+ &&conv_xxx1_xx01, /* 8h -> 16s */
+ &&conv_xxx1_x100, /* 8h -> 24h */
+ &&conv_xxx1_001x, /* 8h -> 24s */
+ &&conv_xxx1_1000, /* 8h -> 32h */
+ &&conv_xxx1_0001, /* 8h -> 32s */
+ &&conv_xxx1_xxx9, /* 8h ^> 8h */
+ &&conv_xxx1_xxx9, /* 8h ^> 8s */
+ &&conv_xxx1_xx90, /* 8h ^> 16h */
+ &&conv_xxx1_xx09, /* 8h ^> 16s */
+ &&conv_xxx1_x900, /* 8h ^> 24h */
+ &&conv_xxx1_009x, /* 8h ^> 24s */
+ &&conv_xxx1_9000, /* 8h ^> 32h */
+ &&conv_xxx1_0009, /* 8h ^> 32s */
+ &&conv_xxx1_xxx1, /* 8s -> 8h */
+ &&conv_xxx1_xxx1, /* 8s -> 8s */
+ &&conv_xxx1_xx10, /* 8s -> 16h */
+ &&conv_xxx1_xx01, /* 8s -> 16s */
+ &&conv_xxx1_x100, /* 8s -> 24h */
+ &&conv_xxx1_001x, /* 8s -> 24s */
+ &&conv_xxx1_1000, /* 8s -> 32h */
+ &&conv_xxx1_0001, /* 8s -> 32s */
+ &&conv_xxx1_xxx9, /* 8s ^> 8h */
+ &&conv_xxx1_xxx9, /* 8s ^> 8s */
+ &&conv_xxx1_xx90, /* 8s ^> 16h */
+ &&conv_xxx1_xx09, /* 8s ^> 16s */
+ &&conv_xxx1_x900, /* 8s ^> 24h */
+ &&conv_xxx1_009x, /* 8s ^> 24s */
+ &&conv_xxx1_9000, /* 8s ^> 32h */
+ &&conv_xxx1_0009, /* 8s ^> 32s */
+ &&conv_xx12_xxx1, /* 16h -> 8h */
+ &&conv_xx12_xxx1, /* 16h -> 8s */
+ &&conv_xx12_xx12, /* 16h -> 16h */
+ &&conv_xx12_xx21, /* 16h -> 16s */
+ &&conv_xx12_x120, /* 16h -> 24h */
+ &&conv_xx12_021x, /* 16h -> 24s */
+ &&conv_xx12_1200, /* 16h -> 32h */
+ &&conv_xx12_0021, /* 16h -> 32s */
+ &&conv_xx12_xxx9, /* 16h ^> 8h */
+ &&conv_xx12_xxx9, /* 16h ^> 8s */
+ &&conv_xx12_xx92, /* 16h ^> 16h */
+ &&conv_xx12_xx29, /* 16h ^> 16s */
+ &&conv_xx12_x920, /* 16h ^> 24h */
+ &&conv_xx12_029x, /* 16h ^> 24s */
+ &&conv_xx12_9200, /* 16h ^> 32h */
+ &&conv_xx12_0029, /* 16h ^> 32s */
+ &&conv_xx12_xxx2, /* 16s -> 8h */
+ &&conv_xx12_xxx2, /* 16s -> 8s */
+ &&conv_xx12_xx21, /* 16s -> 16h */
+ &&conv_xx12_xx12, /* 16s -> 16s */
+ &&conv_xx12_x210, /* 16s -> 24h */
+ &&conv_xx12_012x, /* 16s -> 24s */
+ &&conv_xx12_2100, /* 16s -> 32h */
+ &&conv_xx12_0012, /* 16s -> 32s */
+ &&conv_xx12_xxxA, /* 16s ^> 8h */
+ &&conv_xx12_xxxA, /* 16s ^> 8s */
+ &&conv_xx12_xxA1, /* 16s ^> 16h */
+ &&conv_xx12_xx1A, /* 16s ^> 16s */
+ &&conv_xx12_xA10, /* 16s ^> 24h */
+ &&conv_xx12_01Ax, /* 16s ^> 24s */
+ &&conv_xx12_A100, /* 16s ^> 32h */
+ &&conv_xx12_001A, /* 16s ^> 32s */
+ &&conv_x123_xxx1, /* 24h -> 8h */
+ &&conv_x123_xxx1, /* 24h -> 8s */
+ &&conv_x123_xx12, /* 24h -> 16h */
+ &&conv_x123_xx21, /* 24h -> 16s */
+ &&conv_x123_x123, /* 24h -> 24h */
+ &&conv_x123_321x, /* 24h -> 24s */
+ &&conv_x123_1230, /* 24h -> 32h */
+ &&conv_x123_0321, /* 24h -> 32s */
+ &&conv_x123_xxx9, /* 24h ^> 8h */
+ &&conv_x123_xxx9, /* 24h ^> 8s */
+ &&conv_x123_xx92, /* 24h ^> 16h */
+ &&conv_x123_xx29, /* 24h ^> 16s */
+ &&conv_x123_x923, /* 24h ^> 24h */
+ &&conv_x123_329x, /* 24h ^> 24s */
+ &&conv_x123_9230, /* 24h ^> 32h */
+ &&conv_x123_0329, /* 24h ^> 32s */
+ &&conv_123x_xxx3, /* 24s -> 8h */
+ &&conv_123x_xxx3, /* 24s -> 8s */
+ &&conv_123x_xx32, /* 24s -> 16h */
+ &&conv_123x_xx23, /* 24s -> 16s */
+ &&conv_123x_x321, /* 24s -> 24h */
+ &&conv_123x_123x, /* 24s -> 24s */
+ &&conv_123x_3210, /* 24s -> 32h */
+ &&conv_123x_0123, /* 24s -> 32s */
+ &&conv_123x_xxxB, /* 24s ^> 8h */
+ &&conv_123x_xxxB, /* 24s ^> 8s */
+ &&conv_123x_xxB2, /* 24s ^> 16h */
+ &&conv_123x_xx2B, /* 24s ^> 16s */
+ &&conv_123x_xB21, /* 24s ^> 24h */
+ &&conv_123x_12Bx, /* 24s ^> 24s */
+ &&conv_123x_B210, /* 24s ^> 32h */
+ &&conv_123x_012B, /* 24s ^> 32s */
+ &&conv_1234_xxx1, /* 32h -> 8h */
+ &&conv_1234_xxx1, /* 32h -> 8s */
+ &&conv_1234_xx12, /* 32h -> 16h */
+ &&conv_1234_xx21, /* 32h -> 16s */
+ &&conv_1234_x123, /* 32h -> 24h */
+ &&conv_1234_321x, /* 32h -> 24s */
+ &&conv_1234_1234, /* 32h -> 32h */
+ &&conv_1234_4321, /* 32h -> 32s */
+ &&conv_1234_xxx9, /* 32h ^> 8h */
+ &&conv_1234_xxx9, /* 32h ^> 8s */
+ &&conv_1234_xx92, /* 32h ^> 16h */
+ &&conv_1234_xx29, /* 32h ^> 16s */
+ &&conv_1234_x923, /* 32h ^> 24h */
+ &&conv_1234_329x, /* 32h ^> 24s */
+ &&conv_1234_9234, /* 32h ^> 32h */
+ &&conv_1234_4329, /* 32h ^> 32s */
+ &&conv_1234_xxx4, /* 32s -> 8h */
+ &&conv_1234_xxx4, /* 32s -> 8s */
+ &&conv_1234_xx43, /* 32s -> 16h */
+ &&conv_1234_xx34, /* 32s -> 16s */
+ &&conv_1234_x432, /* 32s -> 24h */
+ &&conv_1234_234x, /* 32s -> 24s */
+ &&conv_1234_4321, /* 32s -> 32h */
+ &&conv_1234_1234, /* 32s -> 32s */
+ &&conv_1234_xxxC, /* 32s ^> 8h */
+ &&conv_1234_xxxC, /* 32s ^> 8s */
+ &&conv_1234_xxC3, /* 32s ^> 16h */
+ &&conv_1234_xx3C, /* 32s ^> 16s */
+ &&conv_1234_xC32, /* 32s ^> 24h */
+ &&conv_1234_23Cx, /* 32s ^> 24s */
+ &&conv_1234_C321, /* 32s ^> 32h */
+ &&conv_1234_123C, /* 32s ^> 32s */
+};
+#endif
+
+#ifdef CONV_END
+while(0) {
+conv_xxx1_xxx1: as_u8(dst) = as_u8(src); goto CONV_END;
+conv_xxx1_xx10: as_u16(dst) = (u_int16_t)as_u8(src) << 8; goto CONV_END;
+conv_xxx1_xx01: as_u16(dst) = (u_int16_t)as_u8(src); goto CONV_END;
+conv_xxx1_x100: as_u32(dst) = (u_int32_t)as_u8(src) << 16; goto CONV_END;
+conv_xxx1_001x: as_u32(dst) = (u_int32_t)as_u8(src) << 8; goto CONV_END;
+conv_xxx1_1000: as_u32(dst) = (u_int32_t)as_u8(src) << 24; goto CONV_END;
+conv_xxx1_0001: as_u32(dst) = (u_int32_t)as_u8(src); goto CONV_END;
+conv_xxx1_xxx9: as_u8(dst) = as_u8(src) ^ 0x80; goto CONV_END;
+conv_xxx1_xx90: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
+conv_xxx1_xx09: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80); goto CONV_END;
+conv_xxx1_x900: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 16; goto CONV_END;
+conv_xxx1_009x: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END;
+conv_xxx1_9000: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto CONV_END;
+conv_xxx1_0009: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80); goto CONV_END;
+conv_xx12_xxx1: as_u8(dst) = as_u16(src) >> 8; goto CONV_END;
+conv_xx12_xx12: as_u16(dst) = as_u16(src); goto CONV_END;
+conv_xx12_xx21: as_u16(dst) = swab16(as_u16(src)); goto CONV_END;
+conv_xx12_x120: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
+conv_xx12_021x: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
+conv_xx12_1200: as_u32(dst) = (u_int32_t)as_u16(src) << 16; goto CONV_END;
+conv_xx12_0021: as_u32(dst) = (u_int32_t)swab16(as_u16(src)); goto CONV_END;
+conv_xx12_xxx9: as_u8(dst) = (as_u16(src) >> 8) ^ 0x80; goto CONV_END;
+conv_xx12_xx92: as_u16(dst) = as_u16(src) ^ 0x8000; goto CONV_END;
+conv_xx12_xx29: as_u16(dst) = swab16(as_u16(src)) ^ 0x80; goto CONV_END;
+conv_xx12_x920: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 8; goto CONV_END;
+conv_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_9200: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto CONV_END;
+conv_xx12_0029: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80); goto CONV_END;
+conv_xx12_xxx2: as_u8(dst) = as_u16(src) & 0xff; goto CONV_END;
+conv_xx12_x210: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END;
+conv_xx12_012x: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END;
+conv_xx12_2100: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 16; goto CONV_END;
+conv_xx12_0012: as_u32(dst) = (u_int32_t)as_u16(src); goto CONV_END;
+conv_xx12_xxxA: as_u8(dst) = (as_u16(src) ^ 0x80) & 0xff; goto CONV_END;
+conv_xx12_xxA1: as_u16(dst) = swab16(as_u16(src) ^ 0x80); goto CONV_END;
+conv_xx12_xx1A: as_u16(dst) = as_u16(src) ^ 0x80; goto CONV_END;
+conv_xx12_xA10: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_01Ax: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80) << 8; goto CONV_END;
+conv_xx12_A100: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto CONV_END;
+conv_xx12_001A: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80); goto CONV_END;
+conv_x123_xxx1: as_u8(dst) = as_u32(src) >> 16; goto CONV_END;
+conv_x123_xx12: as_u16(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_x123_xx21: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
+conv_x123_x123: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_x123_321x: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_x123_1230: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
+conv_x123_0321: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
+conv_x123_xxx9: as_u8(dst) = (as_u32(src) >> 16) ^ 0x80; goto CONV_END;
+conv_x123_xx92: as_u16(dst) = (as_u32(src) >> 8) ^ 0x8000; goto CONV_END;
+conv_x123_xx29: as_u16(dst) = swab16(as_u32(src) >> 8) ^ 0x80; goto CONV_END;
+conv_x123_x923: as_u32(dst) = as_u32(src) ^ 0x800000; goto CONV_END;
+conv_x123_329x: as_u32(dst) = swab32(as_u32(src)) ^ 0x8000; goto CONV_END;
+conv_x123_9230: as_u32(dst) = (as_u32(src) ^ 0x800000) << 8; goto CONV_END;
+conv_x123_0329: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x80; goto CONV_END;
+conv_123x_xxx3: as_u8(dst) = (as_u32(src) >> 8) & 0xff; goto CONV_END;
+conv_123x_xx32: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END;
+conv_123x_xx23: as_u16(dst) = (as_u32(src) >> 8) & 0xffff; goto CONV_END;
+conv_123x_x321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_123x_123x: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_123x_3210: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
+conv_123x_0123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_123x_xxxB: as_u8(dst) = ((as_u32(src) >> 8) & 0xff) ^ 0x80; goto CONV_END;
+conv_123x_xxB2: as_u16(dst) = swab16((as_u32(src) >> 8) ^ 0x80); goto CONV_END;
+conv_123x_xx2B: as_u16(dst) = ((as_u32(src) >> 8) & 0xffff) ^ 0x80; goto CONV_END;
+conv_123x_xB21: as_u32(dst) = swab32(as_u32(src)) ^ 0x800000; goto CONV_END;
+conv_123x_12Bx: as_u32(dst) = as_u32(src) ^ 0x8000; goto CONV_END;
+conv_123x_B210: as_u32(dst) = swab32(as_u32(src) ^ 0x8000) << 8; goto CONV_END;
+conv_123x_012B: as_u32(dst) = (as_u32(src) >> 8) ^ 0x80; goto CONV_END;
+conv_1234_xxx1: as_u8(dst) = as_u32(src) >> 24; goto CONV_END;
+conv_1234_xx12: as_u16(dst) = as_u32(src) >> 16; goto CONV_END;
+conv_1234_xx21: as_u16(dst) = swab16(as_u32(src) >> 16); goto CONV_END;
+conv_1234_x123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END;
+conv_1234_321x: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END;
+conv_1234_1234: as_u32(dst) = as_u32(src); goto CONV_END;
+conv_1234_4321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END;
+conv_1234_xxx9: as_u8(dst) = (as_u32(src) >> 24) ^ 0x80; goto CONV_END;
+conv_1234_xx92: as_u16(dst) = (as_u32(src) >> 16) ^ 0x8000; goto CONV_END;
+conv_1234_xx29: as_u16(dst) = swab16(as_u32(src) >> 16) ^ 0x80; goto CONV_END;
+conv_1234_x923: as_u32(dst) = (as_u32(src) >> 8) ^ 0x800000; goto CONV_END;
+conv_1234_329x: as_u32(dst) = (swab32(as_u32(src)) ^ 0x80) << 8; goto CONV_END;
+conv_1234_9234: as_u32(dst) = as_u32(src) ^ 0x80000000; goto CONV_END;
+conv_1234_4329: as_u32(dst) = swab32(as_u32(src)) ^ 0x80; goto CONV_END;
+conv_1234_xxx4: as_u8(dst) = as_u32(src) & 0xff; goto CONV_END;
+conv_1234_xx43: as_u16(dst) = swab16(as_u32(src)); goto CONV_END;
+conv_1234_xx34: as_u16(dst) = as_u32(src) & 0xffff; goto CONV_END;
+conv_1234_x432: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END;
+conv_1234_234x: as_u32(dst) = as_u32(src) << 8; goto CONV_END;
+conv_1234_xxxC: as_u8(dst) = (as_u32(src) & 0xff) ^ 0x80; goto CONV_END;
+conv_1234_xxC3: as_u16(dst) = swab16(as_u32(src) ^ 0x80); goto CONV_END;
+conv_1234_xx3C: as_u16(dst) = (as_u32(src) & 0xffff) ^ 0x80; goto CONV_END;
+conv_1234_xC32: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x800000; goto CONV_END;
+conv_1234_23Cx: as_u32(dst) = (as_u32(src) ^ 0x80) << 8; goto CONV_END;
+conv_1234_C321: as_u32(dst) = swab32(as_u32(src) ^ 0x80); goto CONV_END;
+conv_1234_123C: as_u32(dst) = as_u32(src) ^ 0x80; goto CONV_END;
+}
+#endif
+
+#ifdef GET_S16_LABELS
+/* src_wid src_endswap unsigned */
+static void *get_s16_labels[4 * 2 * 2] = {
+ &&get_s16_xxx1_xx10, /* 8h -> 16h */
+ &&get_s16_xxx1_xx90, /* 8h ^> 16h */
+ &&get_s16_xxx1_xx10, /* 8s -> 16h */
+ &&get_s16_xxx1_xx90, /* 8s ^> 16h */
+ &&get_s16_xx12_xx12, /* 16h -> 16h */
+ &&get_s16_xx12_xx92, /* 16h ^> 16h */
+ &&get_s16_xx12_xx21, /* 16s -> 16h */
+ &&get_s16_xx12_xxA1, /* 16s ^> 16h */
+ &&get_s16_x123_xx12, /* 24h -> 16h */
+ &&get_s16_x123_xx92, /* 24h ^> 16h */
+ &&get_s16_123x_xx32, /* 24s -> 16h */
+ &&get_s16_123x_xxB2, /* 24s ^> 16h */
+ &&get_s16_1234_xx12, /* 32h -> 16h */
+ &&get_s16_1234_xx92, /* 32h ^> 16h */
+ &&get_s16_1234_xx43, /* 32s -> 16h */
+ &&get_s16_1234_xxC3, /* 32s ^> 16h */
+};
+#endif
+
+#ifdef GET_S16_END
+while(0) {
+get_s16_xxx1_xx10: sample = (u_int16_t)as_u8(src) << 8; goto GET_S16_END;
+get_s16_xxx1_xx90: sample = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto GET_S16_END;
+get_s16_xx12_xx12: sample = as_u16(src); goto GET_S16_END;
+get_s16_xx12_xx92: sample = as_u16(src) ^ 0x8000; goto GET_S16_END;
+get_s16_xx12_xx21: sample = swab16(as_u16(src)); goto GET_S16_END;
+get_s16_xx12_xxA1: sample = swab16(as_u16(src) ^ 0x80); goto GET_S16_END;
+get_s16_x123_xx12: sample = as_u32(src) >> 8; goto GET_S16_END;
+get_s16_x123_xx92: sample = (as_u32(src) >> 8) ^ 0x8000; goto GET_S16_END;
+get_s16_123x_xx32: sample = swab16(as_u32(src) >> 8); goto GET_S16_END;
+get_s16_123x_xxB2: sample = swab16((as_u32(src) >> 8) ^ 0x8000); goto GET_S16_END;
+get_s16_1234_xx12: sample = as_u32(src) >> 16; goto GET_S16_END;
+get_s16_1234_xx92: sample = (as_u32(src) >> 16) ^ 0x8000; goto GET_S16_END;
+get_s16_1234_xx43: sample = swab16(as_u32(src)); goto GET_S16_END;
+get_s16_1234_xxC3: sample = swab16(as_u32(src) ^ 0x80); goto GET_S16_END;
+}
+#endif
+
+#ifdef PUT_S16_LABELS
+/* dst_wid dst_endswap unsigned */
+static void *put_s16_labels[4 * 2 * 2] = {
+ &&put_s16_xx12_xxx1, /* 16h -> 8h */
+ &&put_s16_xx12_xxx9, /* 16h ^> 8h */
+ &&put_s16_xx12_xxx1, /* 16h -> 8s */
+ &&put_s16_xx12_xxx9, /* 16h ^> 8s */
+ &&put_s16_xx12_xx12, /* 16h -> 16h */
+ &&put_s16_xx12_xx92, /* 16h ^> 16h */
+ &&put_s16_xx12_xx21, /* 16h -> 16s */
+ &&put_s16_xx12_xx29, /* 16h ^> 16s */
+ &&put_s16_xx12_x120, /* 16h -> 24h */
+ &&put_s16_xx12_x920, /* 16h ^> 24h */
+ &&put_s16_xx12_021x, /* 16h -> 24s */
+ &&put_s16_xx12_029x, /* 16h ^> 24s */
+ &&put_s16_xx12_1200, /* 16h -> 32h */
+ &&put_s16_xx12_9200, /* 16h ^> 32h */
+ &&put_s16_xx12_0021, /* 16h -> 32s */
+ &&put_s16_xx12_0029, /* 16h ^> 32s */
+};
+#endif
+
+#ifdef PUT_S16_END
+while (0) {
+put_s16_xx12_xxx1: as_u8(dst) = sample >> 8; goto PUT_S16_END;
+put_s16_xx12_xxx9: as_u8(dst) = (sample >> 8) ^ 0x80; goto PUT_S16_END;
+put_s16_xx12_xx12: as_u16(dst) = sample; goto PUT_S16_END;
+put_s16_xx12_xx92: as_u16(dst) = sample ^ 0x8000; goto PUT_S16_END;
+put_s16_xx12_xx21: as_u16(dst) = swab16(sample); goto PUT_S16_END;
+put_s16_xx12_xx29: as_u16(dst) = swab16(sample) ^ 0x80; goto PUT_S16_END;
+put_s16_xx12_x120: as_u32(dst) = (u_int32_t)sample << 8; goto PUT_S16_END;
+put_s16_xx12_x920: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 8; goto PUT_S16_END;
+put_s16_xx12_021x: as_u32(dst) = (u_int32_t)swab16(sample) << 8; goto PUT_S16_END;
+put_s16_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(sample) ^ 0x80) << 8; goto PUT_S16_END;
+put_s16_xx12_1200: as_u32(dst) = (u_int32_t)sample << 16; goto PUT_S16_END;
+put_s16_xx12_9200: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 16; goto PUT_S16_END;
+put_s16_xx12_0021: as_u32(dst) = (u_int32_t)swab16(sample); goto PUT_S16_END;
+put_s16_xx12_0029: as_u32(dst) = (u_int32_t)swab16(sample) ^ 0x80; goto PUT_S16_END;
+}
+#endif
+
+#if 0
+#ifdef GET32_LABELS
+/* src_wid src_endswap unsigned */
+static void *get32_labels[4 * 2 * 2] = {
+ &&get32_xxx1_1000, /* 8h -> 32h */
+ &&get32_xxx1_9000, /* 8h ^> 32h */
+ &&get32_xxx1_1000, /* 8s -> 32h */
+ &&get32_xxx1_9000, /* 8s ^> 32h */
+ &&get32_xx12_1200, /* 16h -> 32h */
+ &&get32_xx12_9200, /* 16h ^> 32h */
+ &&get32_xx12_2100, /* 16s -> 32h */
+ &&get32_xx12_A100, /* 16s ^> 32h */
+ &&get32_x123_1230, /* 24h -> 32h */
+ &&get32_x123_9230, /* 24h ^> 32h */
+ &&get32_123x_3210, /* 24s -> 32h */
+ &&get32_123x_B210, /* 24s ^> 32h */
+ &&get32_1234_1234, /* 32h -> 32h */
+ &&get32_1234_9234, /* 32h ^> 32h */
+ &&get32_1234_4321, /* 32s -> 32h */
+ &&get32_1234_C321, /* 32s ^> 32h */
+};
+#endif
+
+#ifdef GET32_END
+while (0) {
+get32_xxx1_1000: sample = (u_int32_t)as_u8(src) << 24; goto GET32_END;
+get32_xxx1_9000: sample = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto GET32_END;
+get32_xx12_1200: sample = (u_int32_t)as_u16(src) << 16; goto GET32_END;
+get32_xx12_9200: sample = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto GET32_END;
+get32_xx12_2100: sample = (u_int32_t)swab16(as_u16(src)) << 16; goto GET32_END;
+get32_xx12_A100: sample = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto GET32_END;
+get32_x123_1230: sample = as_u32(src) << 8; goto GET32_END;
+get32_x123_9230: sample = (as_u32(src) << 8) ^ 0x80000000; goto GET32_END;
+get32_123x_3210: sample = swab32(as_u32(src) >> 8); goto GET32_END;
+get32_123x_B210: sample = swab32((as_u32(src) >> 8) ^ 0x80); goto GET32_END;
+get32_1234_1234: sample = as_u32(src); goto GET32_END;
+get32_1234_9234: sample = as_u32(src) ^ 0x80000000; goto GET32_END;
+get32_1234_4321: sample = swab32(as_u32(src)); goto GET32_END;
+get32_1234_C321: sample = swab32(as_u32(src) ^ 0x80); goto GET32_END;
+}
+#endif
+#endif
+
+#ifdef PUT_U32_LABELS
+/* dst_wid dst_endswap unsigned */
+static void *put_u32_labels[4 * 2 * 2] = {
+ &&put_u32_1234_xxx9, /* u32h -> s8h */
+ &&put_u32_1234_xxx1, /* u32h -> u8h */
+ &&put_u32_1234_xxx9, /* u32h -> s8s */
+ &&put_u32_1234_xxx1, /* u32h -> u8s */
+ &&put_u32_1234_xx92, /* u32h -> s16h */
+ &&put_u32_1234_xx12, /* u32h -> u16h */
+ &&put_u32_1234_xx29, /* u32h -> s16s */
+ &&put_u32_1234_xx21, /* u32h -> u16s */
+ &&put_u32_1234_x923, /* u32h -> s24h */
+ &&put_u32_1234_x123, /* u32h -> u24h */
+ &&put_u32_1234_329x, /* u32h -> s24s */
+ &&put_u32_1234_321x, /* u32h -> u24s */
+ &&put_u32_1234_9234, /* u32h -> s32h */
+ &&put_u32_1234_1234, /* u32h -> u32h */
+ &&put_u32_1234_4329, /* u32h -> s32s */
+ &&put_u32_1234_4321, /* u32h -> u32s */
+};
+#endif
+
+#ifdef PUT_U32_END
+while (0) {
+put_u32_1234_xxx1: as_u8(dst) = sample >> 24; goto PUT_U32_END;
+put_u32_1234_xxx9: as_u8(dst) = (sample >> 24) ^ 0x80; goto PUT_U32_END;
+put_u32_1234_xx12: as_u16(dst) = sample >> 16; goto PUT_U32_END;
+put_u32_1234_xx92: as_u16(dst) = (sample >> 16) ^ 0x8000; goto PUT_U32_END;
+put_u32_1234_xx21: as_u16(dst) = swab16(sample >> 16); goto PUT_U32_END;
+put_u32_1234_xx29: as_u16(dst) = swab16(sample >> 16) ^ 0x80; goto PUT_U32_END;
+put_u32_1234_x123: as_u32(dst) = sample >> 8; goto PUT_U32_END;
+put_u32_1234_x923: as_u32(dst) = (sample >> 8) ^ 0x800000; goto PUT_U32_END;
+put_u32_1234_321x: as_u32(dst) = swab32(sample) << 8; goto PUT_U32_END;
+put_u32_1234_329x: as_u32(dst) = (swab32(sample) ^ 0x80) << 8; goto PUT_U32_END;
+put_u32_1234_1234: as_u32(dst) = sample; goto PUT_U32_END;
+put_u32_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT_U32_END;
+put_u32_1234_4321: as_u32(dst) = swab32(sample); goto PUT_U32_END;
+put_u32_1234_4329: as_u32(dst) = swab32(sample) ^ 0x80; goto PUT_U32_END;
+}
+#endif
+
+#ifdef GET_U_LABELS
+/* width endswap unsigned*/
+static void *get_u_labels[4 * 2 * 2] = {
+ &&get_u_s8, /* s8 -> u8 */
+ &&get_u_u8, /* u8 -> u8 */
+ &&get_u_s8, /* s8 -> u8 */
+ &&get_u_u8, /* u8 -> u8 */
+ &&get_u_s16h, /* s16h -> u16h */
+ &&get_u_u16h, /* u16h -> u16h */
+ &&get_u_s16s, /* s16s -> u16h */
+ &&get_u_u16s, /* u16s -> u16h */
+ &&get_u_s24h, /* s24h -> u32h */
+ &&get_u_u24h, /* u24h -> u32h */
+ &&get_u_s24s, /* s24s -> u32h */
+ &&get_u_u24s, /* u24s -> u32h */
+ &&get_u_s32h, /* s32h -> u32h */
+ &&get_u_u32h, /* u32h -> u32h */
+ &&get_u_s32s, /* s32s -> u32h */
+ &&get_u_u32s, /* u32s -> u32h */
+};
+#endif
+
+#ifdef GET_U_END
+while (0) {
+get_u_s8: sample = as_u8(src) ^ 0x80; goto GET_U_END;
+get_u_u8: sample = as_u8(src); goto GET_U_END;
+get_u_s16h: sample = as_u16(src) ^ 0x8000; goto GET_U_END;
+get_u_u16h: sample = as_u16(src); goto GET_U_END;
+get_u_s16s: sample = swab16(as_u16(src) ^ 0x80); goto GET_U_END;
+get_u_u16s: sample = swab16(as_u16(src)); goto GET_U_END;
+get_u_s24h: sample = (as_u32(src) ^ 0x800000); goto GET_U_END;
+get_u_u24h: sample = as_u32(src); goto GET_U_END;
+get_u_s24s: sample = swab32(as_u32(src) ^ 0x800000); goto GET_U_END;
+get_u_u24s: sample = swab32(as_u32(src)); goto GET_U_END;
+get_u_s32h: sample = as_u32(src) ^ 0x80000000; goto GET_U_END;
+get_u_u32h: sample = as_u32(src); goto GET_U_END;
+get_u_s32s: sample = swab32(as_u32(src) ^ 0x80); goto GET_U_END;
+get_u_u32s: sample = swab32(as_u32(src)); goto GET_U_END;
+}
+#endif
+
+#if 0
+#ifdef PUT_LABELS
+/* width endswap unsigned */
+static void *put_labels[4 * 2 * 2] = {
+ &&put_s8, /* s8 -> s8 */
+ &&put_u8, /* u8 -> s8 */
+ &&put_s8, /* s8 -> s8 */
+ &&put_u8, /* u8 -> s8 */
+ &&put_s16h, /* s16h -> s16h */
+ &&put_u16h, /* u16h -> s16h */
+ &&put_s16s, /* s16s -> s16h */
+ &&put_u16s, /* u16s -> s16h */
+ &&put_s24h, /* s24h -> s32h */
+ &&put_u24h, /* u24h -> s32h */
+ &&put_s24s, /* s24s -> s32h */
+ &&put_u24s, /* u24s -> s32h */
+ &&put_s32h, /* s32h -> s32h */
+ &&put_u32h, /* u32h -> s32h */
+ &&put_s32s, /* s32s -> s32h */
+ &&put_u32s, /* u32s -> s32h */
+};
+#endif
+
+#ifdef PUT_END
+put_s8: as_s8(dst) = sample; goto PUT_END;
+put_u8: as_u8(dst) = sample ^ 0x80; goto PUT_END;
+put_s16h: as_s16(dst) = sample; goto PUT_END;
+put_u16h: as_u16(dst) = sample ^ 0x8000; goto PUT_END;
+put_s16s: as_s16(dst) = swab16(sample); goto PUT_END;
+put_u16s: as_u16(dst) = swab16(sample ^ 0x80); goto PUT_END;
+put_s24h: as_s24(dst) = sample & 0xffffff; goto PUT_END;
+put_u24h: as_u24(dst) = sample ^ 0x80000000; goto PUT_END;
+put_s24s: as_s24(dst) = swab32(sample & 0xffffff); goto PUT_END;
+put_u24s: as_u24(dst) = swab32(sample ^ 0x80); goto PUT_END;
+put_s32h: as_s32(dst) = sample; goto PUT_END;
+put_u32h: as_u32(dst) = sample ^ 0x80000000; goto PUT_END;
+put_s32s: as_s32(dst) = swab32(sample); goto PUT_END;
+put_u32s: as_u32(dst) = swab32(sample ^ 0x80); goto PUT_END;
+#endif
+#endif
+
+#undef as_u8
+#undef as_u16
+#undef as_u32
+#undef as_s8
+#undef as_s16
+#undef as_s32
diff --git a/sound/core/oss/rate.c b/sound/core/oss/rate.c
new file mode 100644
index 00000000000..1096ec18671
--- /dev/null
+++ b/sound/core/oss/rate.c
@@ -0,0 +1,378 @@
+/*
+ * Rate conversion Plug-In
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+#define SHIFT 11
+#define BITS (1<<SHIFT)
+#define R_MASK (BITS-1)
+
+/*
+ * Basic rate conversion plugin
+ */
+
+typedef struct {
+ signed short last_S1;
+ signed short last_S2;
+} rate_channel_t;
+
+typedef void (*rate_f)(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ int src_frames, int dst_frames);
+
+typedef struct rate_private_data {
+ unsigned int pitch;
+ unsigned int pos;
+ rate_f func;
+ int get, put;
+ snd_pcm_sframes_t old_src_frames, old_dst_frames;
+ rate_channel_t channels[0];
+} rate_t;
+
+static void rate_init(snd_pcm_plugin_t *plugin)
+{
+ unsigned int channel;
+ rate_t *data = (rate_t *)plugin->extra_data;
+ data->pos = 0;
+ for (channel = 0; channel < plugin->src_format.channels; channel++) {
+ data->channels[channel].last_S1 = 0;
+ data->channels[channel].last_S2 = 0;
+ }
+}
+
+static void resample_expand(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ int src_frames, int dst_frames)
+{
+ unsigned int pos = 0;
+ signed int val;
+ signed short S1, S2;
+ char *src, *dst;
+ unsigned int channel;
+ int src_step, dst_step;
+ int src_frames1, dst_frames1;
+ rate_t *data = (rate_t *)plugin->extra_data;
+ rate_channel_t *rchannels = data->channels;
+
+#define GET_S16_LABELS
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+#undef PUT_S16_LABELS
+ void *get = get_s16_labels[data->get];
+ void *put = put_s16_labels[data->put];
+ signed short sample = 0;
+
+ for (channel = 0; channel < plugin->src_format.channels; channel++) {
+ pos = data->pos;
+ S1 = rchannels->last_S1;
+ S2 = rchannels->last_S2;
+ if (!src_channels[channel].enabled) {
+ if (dst_channels[channel].wanted)
+ snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
+ dst_channels[channel].enabled = 0;
+ continue;
+ }
+ dst_channels[channel].enabled = 1;
+ src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+ dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+ src_step = src_channels[channel].area.step / 8;
+ dst_step = dst_channels[channel].area.step / 8;
+ src_frames1 = src_frames;
+ dst_frames1 = dst_frames;
+ while (dst_frames1-- > 0) {
+ if (pos & ~R_MASK) {
+ pos &= R_MASK;
+ S1 = S2;
+ if (src_frames1-- > 0) {
+ goto *get;
+#define GET_S16_END after_get
+#include "plugin_ops.h"
+#undef GET_S16_END
+ after_get:
+ S2 = sample;
+ src += src_step;
+ }
+ }
+ val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
+ if (val < -32768)
+ val = -32768;
+ else if (val > 32767)
+ val = 32767;
+ sample = val;
+ goto *put;
+#define PUT_S16_END after_put
+#include "plugin_ops.h"
+#undef PUT_S16_END
+ after_put:
+ dst += dst_step;
+ pos += data->pitch;
+ }
+ rchannels->last_S1 = S1;
+ rchannels->last_S2 = S2;
+ rchannels++;
+ }
+ data->pos = pos;
+}
+
+static void resample_shrink(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ int src_frames, int dst_frames)
+{
+ unsigned int pos = 0;
+ signed int val;
+ signed short S1, S2;
+ char *src, *dst;
+ unsigned int channel;
+ int src_step, dst_step;
+ int src_frames1, dst_frames1;
+ rate_t *data = (rate_t *)plugin->extra_data;
+ rate_channel_t *rchannels = data->channels;
+
+#define GET_S16_LABELS
+#define PUT_S16_LABELS
+#include "plugin_ops.h"
+#undef GET_S16_LABELS
+#undef PUT_S16_LABELS
+ void *get = get_s16_labels[data->get];
+ void *put = put_s16_labels[data->put];
+ signed short sample = 0;
+
+ for (channel = 0; channel < plugin->src_format.channels; ++channel) {
+ pos = data->pos;
+ S1 = rchannels->last_S1;
+ S2 = rchannels->last_S2;
+ if (!src_channels[channel].enabled) {
+ if (dst_channels[channel].wanted)
+ snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
+ dst_channels[channel].enabled = 0;
+ continue;
+ }
+ dst_channels[channel].enabled = 1;
+ src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8;
+ dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
+ src_step = src_channels[channel].area.step / 8;
+ dst_step = dst_channels[channel].area.step / 8;
+ src_frames1 = src_frames;
+ dst_frames1 = dst_frames;
+ while (dst_frames1 > 0) {
+ S1 = S2;
+ if (src_frames1-- > 0) {
+ goto *get;
+#define GET_S16_END after_get
+#include "plugin_ops.h"
+#undef GET_S16_END
+ after_get:
+ S2 = sample;
+ src += src_step;
+ }
+ if (pos & ~R_MASK) {
+ pos &= R_MASK;
+ val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
+ if (val < -32768)
+ val = -32768;
+ else if (val > 32767)
+ val = 32767;
+ sample = val;
+ goto *put;
+#define PUT_S16_END after_put
+#include "plugin_ops.h"
+#undef PUT_S16_END
+ after_put:
+ dst += dst_step;
+ dst_frames1--;
+ }
+ pos += data->pitch;
+ }
+ rchannels->last_S1 = S1;
+ rchannels->last_S2 = S2;
+ rchannels++;
+ }
+ data->pos = pos;
+}
+
+static snd_pcm_sframes_t rate_src_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+ rate_t *data;
+ snd_pcm_sframes_t res;
+
+ snd_assert(plugin != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+ data = (rate_t *)plugin->extra_data;
+ if (plugin->src_format.rate < plugin->dst_format.rate) {
+ res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
+ } else {
+ res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
+ }
+ if (data->old_src_frames > 0) {
+ snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames;
+ while (data->old_src_frames < frames1) {
+ frames1 >>= 1;
+ res1 <<= 1;
+ }
+ while (data->old_src_frames > frames1) {
+ frames1 <<= 1;
+ res1 >>= 1;
+ }
+ if (data->old_src_frames == frames1)
+ return res1;
+ }
+ data->old_src_frames = frames;
+ data->old_dst_frames = res;
+ return res;
+}
+
+static snd_pcm_sframes_t rate_dst_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames)
+{
+ rate_t *data;
+ snd_pcm_sframes_t res;
+
+ snd_assert(plugin != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+ data = (rate_t *)plugin->extra_data;
+ if (plugin->src_format.rate < plugin->dst_format.rate) {
+ res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
+ } else {
+ res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
+ }
+ if (data->old_dst_frames > 0) {
+ snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames;
+ while (data->old_dst_frames < frames1) {
+ frames1 >>= 1;
+ res1 <<= 1;
+ }
+ while (data->old_dst_frames > frames1) {
+ frames1 <<= 1;
+ res1 >>= 1;
+ }
+ if (data->old_dst_frames == frames1)
+ return res1;
+ }
+ data->old_dst_frames = frames;
+ data->old_src_frames = res;
+ return res;
+}
+
+static snd_pcm_sframes_t rate_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_uframes_t dst_frames;
+ rate_t *data;
+
+ snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+#ifdef CONFIG_SND_DEBUG
+ {
+ unsigned int channel;
+ for (channel = 0; channel < plugin->src_format.channels; channel++) {
+ snd_assert(src_channels[channel].area.first % 8 == 0 &&
+ src_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ snd_assert(dst_channels[channel].area.first % 8 == 0 &&
+ dst_channels[channel].area.step % 8 == 0,
+ return -ENXIO);
+ }
+ }
+#endif
+
+ dst_frames = rate_dst_frames(plugin, frames);
+ if (dst_frames > dst_channels[0].frames)
+ dst_frames = dst_channels[0].frames;
+ data = (rate_t *)plugin->extra_data;
+ data->func(plugin, src_channels, dst_channels, frames, dst_frames);
+ return dst_frames;
+}
+
+static int rate_action(snd_pcm_plugin_t *plugin,
+ snd_pcm_plugin_action_t action,
+ unsigned long udata ATTRIBUTE_UNUSED)
+{
+ snd_assert(plugin != NULL, return -ENXIO);
+ switch (action) {
+ case INIT:
+ case PREPARE:
+ rate_init(plugin);
+ break;
+ default:
+ break;
+ }
+ return 0; /* silenty ignore other actions */
+}
+
+int snd_pcm_plugin_build_rate(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ snd_pcm_plugin_t **r_plugin)
+{
+ int err;
+ rate_t *data;
+ snd_pcm_plugin_t *plugin;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+
+ snd_assert(src_format->channels == dst_format->channels, return -ENXIO);
+ snd_assert(src_format->channels > 0, return -ENXIO);
+ snd_assert(snd_pcm_format_linear(src_format->format) != 0, return -ENXIO);
+ snd_assert(snd_pcm_format_linear(dst_format->format) != 0, return -ENXIO);
+ snd_assert(src_format->rate != dst_format->rate, return -ENXIO);
+
+ err = snd_pcm_plugin_build(plug, "rate conversion",
+ src_format, dst_format,
+ sizeof(rate_t) + src_format->channels * sizeof(rate_channel_t),
+ &plugin);
+ if (err < 0)
+ return err;
+ data = (rate_t *)plugin->extra_data;
+ data->get = getput_index(src_format->format);
+ snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+ data->put = getput_index(dst_format->format);
+ snd_assert(data->put >= 0 && data->put < 4*2*2, return -EINVAL);
+
+ if (src_format->rate < dst_format->rate) {
+ data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate;
+ data->func = resample_expand;
+ } else {
+ data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate;
+ data->func = resample_shrink;
+ }
+ data->pos = 0;
+ rate_init(plugin);
+ data->old_src_frames = data->old_dst_frames = 0;
+ plugin->transfer = rate_transfer;
+ plugin->src_frames = rate_src_frames;
+ plugin->dst_frames = rate_dst_frames;
+ plugin->action = rate_action;
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/oss/route.c b/sound/core/oss/route.c
new file mode 100644
index 00000000000..c955b7dfdb3
--- /dev/null
+++ b/sound/core/oss/route.c
@@ -0,0 +1,519 @@
+/*
+ * Attenuated route Plug-In
+ * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+/* The best possible hack to support missing optimization in gcc 2.7.2.3 */
+#if ROUTE_PLUGIN_RESOLUTION & (ROUTE_PLUGIN_RESOLUTION - 1) != 0
+#define div(a) a /= ROUTE_PLUGIN_RESOLUTION
+#elif ROUTE_PLUGIN_RESOLUTION == 16
+#define div(a) a >>= 4
+#else
+#error "Add some code here"
+#endif
+
+typedef struct ttable_dst ttable_dst_t;
+typedef struct route_private_data route_t;
+
+typedef void (*route_channel_f)(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channel,
+ ttable_dst_t* ttable, snd_pcm_uframes_t frames);
+
+typedef struct {
+ int channel;
+ int as_int;
+} ttable_src_t;
+
+struct ttable_dst {
+ int att; /* Attenuated */
+ unsigned int nsrcs;
+ ttable_src_t* srcs;
+ route_channel_f func;
+};
+
+struct route_private_data {
+ enum {R_UINT32=0, R_UINT64=1} sum_type;
+ int get, put;
+ int conv;
+ int src_sample_size;
+ ttable_dst_t ttable[0];
+};
+
+typedef union {
+ u_int32_t as_uint32;
+ u_int64_t as_uint64;
+} sum_t;
+
+
+static void route_to_channel_from_zero(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED,
+ snd_pcm_plugin_channel_t *dst_channel,
+ ttable_dst_t* ttable ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames)
+{
+ if (dst_channel->wanted)
+ snd_pcm_area_silence(&dst_channel->area, 0, frames, plugin->dst_format.format);
+ dst_channel->enabled = 0;
+}
+
+static void route_to_channel_from_one(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channel,
+ ttable_dst_t* ttable, snd_pcm_uframes_t frames)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+ route_t *data = (route_t *)plugin->extra_data;
+ void *conv;
+ const snd_pcm_plugin_channel_t *src_channel = NULL;
+ unsigned int srcidx;
+ char *src, *dst;
+ int src_step, dst_step;
+ for (srcidx = 0; srcidx < ttable->nsrcs; ++srcidx) {
+ src_channel = &src_channels[ttable->srcs[srcidx].channel];
+ if (src_channel->area.addr != NULL)
+ break;
+ }
+ if (srcidx == ttable->nsrcs) {
+ route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
+ return;
+ }
+
+ dst_channel->enabled = 1;
+ conv = conv_labels[data->conv];
+ src = src_channel->area.addr + src_channel->area.first / 8;
+ src_step = src_channel->area.step / 8;
+ dst = dst_channel->area.addr + dst_channel->area.first / 8;
+ dst_step = dst_channel->area.step / 8;
+ while (frames-- > 0) {
+ goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+ after:
+ src += src_step;
+ dst += dst_step;
+ }
+}
+
+static void route_to_channel(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channel,
+ ttable_dst_t* ttable, snd_pcm_uframes_t frames)
+{
+#define GET_U_LABELS
+#define PUT_U32_LABELS
+#include "plugin_ops.h"
+#undef GET_U_LABELS
+#undef PUT_U32_LABELS
+ static void *zero_labels[2] = { &&zero_int32, &&zero_int64 };
+ /* sum_type att */
+ static void *add_labels[2 * 2] = { &&add_int32_noatt, &&add_int32_att,
+ &&add_int64_noatt, &&add_int64_att,
+ };
+ /* sum_type att shift */
+ static void *norm_labels[2 * 2 * 4] = { NULL,
+ &&norm_int32_8_noatt,
+ &&norm_int32_16_noatt,
+ &&norm_int32_24_noatt,
+ NULL,
+ &&norm_int32_8_att,
+ &&norm_int32_16_att,
+ &&norm_int32_24_att,
+ &&norm_int64_0_noatt,
+ &&norm_int64_8_noatt,
+ &&norm_int64_16_noatt,
+ &&norm_int64_24_noatt,
+ &&norm_int64_0_att,
+ &&norm_int64_8_att,
+ &&norm_int64_16_att,
+ &&norm_int64_24_att,
+ };
+ route_t *data = (route_t *)plugin->extra_data;
+ void *zero, *get, *add, *norm, *put_u32;
+ int nsrcs = ttable->nsrcs;
+ char *dst;
+ int dst_step;
+ char *srcs[nsrcs];
+ int src_steps[nsrcs];
+ ttable_src_t src_tt[nsrcs];
+ u_int32_t sample = 0;
+ int srcidx, srcidx1 = 0;
+ for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
+ const snd_pcm_plugin_channel_t *src_channel = &src_channels[ttable->srcs[srcidx].channel];
+ if (!src_channel->enabled)
+ continue;
+ srcs[srcidx1] = src_channel->area.addr + src_channel->area.first / 8;
+ src_steps[srcidx1] = src_channel->area.step / 8;
+ src_tt[srcidx1] = ttable->srcs[srcidx];
+ srcidx1++;
+ }
+ nsrcs = srcidx1;
+ if (nsrcs == 0) {
+ route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames);
+ return;
+ } else if (nsrcs == 1 && src_tt[0].as_int == ROUTE_PLUGIN_RESOLUTION) {
+ route_to_channel_from_one(plugin, src_channels, dst_channel, ttable, frames);
+ return;
+ }
+
+ dst_channel->enabled = 1;
+ zero = zero_labels[data->sum_type];
+ get = get_u_labels[data->get];
+ add = add_labels[data->sum_type * 2 + ttable->att];
+ norm = norm_labels[data->sum_type * 8 + ttable->att * 4 + 4 - data->src_sample_size];
+ put_u32 = put_u32_labels[data->put];
+ dst = dst_channel->area.addr + dst_channel->area.first / 8;
+ dst_step = dst_channel->area.step / 8;
+
+ while (frames-- > 0) {
+ ttable_src_t *ttp = src_tt;
+ sum_t sum;
+
+ /* Zero sum */
+ goto *zero;
+ zero_int32:
+ sum.as_uint32 = 0;
+ goto zero_end;
+ zero_int64:
+ sum.as_uint64 = 0;
+ goto zero_end;
+ zero_end:
+ for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
+ char *src = srcs[srcidx];
+
+ /* Get sample */
+ goto *get;
+#define GET_U_END after_get
+#include "plugin_ops.h"
+#undef GET_U_END
+ after_get:
+
+ /* Sum */
+ goto *add;
+ add_int32_att:
+ sum.as_uint32 += sample * ttp->as_int;
+ goto after_sum;
+ add_int32_noatt:
+ if (ttp->as_int)
+ sum.as_uint32 += sample;
+ goto after_sum;
+ add_int64_att:
+ sum.as_uint64 += (u_int64_t) sample * ttp->as_int;
+ goto after_sum;
+ add_int64_noatt:
+ if (ttp->as_int)
+ sum.as_uint64 += sample;
+ goto after_sum;
+ after_sum:
+ srcs[srcidx] += src_steps[srcidx];
+ ttp++;
+ }
+
+ /* Normalization */
+ goto *norm;
+ norm_int32_8_att:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_8_att:
+ sum.as_uint64 <<= 8;
+ norm_int64_0_att:
+ div(sum.as_uint64);
+ goto norm_int;
+
+ norm_int32_16_att:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_16_att:
+ sum.as_uint64 <<= 16;
+ div(sum.as_uint64);
+ goto norm_int;
+
+ norm_int32_24_att:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_24_att:
+ sum.as_uint64 <<= 24;
+ div(sum.as_uint64);
+ goto norm_int;
+
+ norm_int32_8_noatt:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_8_noatt:
+ sum.as_uint64 <<= 8;
+ goto norm_int;
+
+ norm_int32_16_noatt:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_16_noatt:
+ sum.as_uint64 <<= 16;
+ goto norm_int;
+
+ norm_int32_24_noatt:
+ sum.as_uint64 = sum.as_uint32;
+ norm_int64_24_noatt:
+ sum.as_uint64 <<= 24;
+ goto norm_int;
+
+ norm_int64_0_noatt:
+ norm_int:
+ if (sum.as_uint64 > (u_int32_t)0xffffffff)
+ sample = (u_int32_t)0xffffffff;
+ else
+ sample = sum.as_uint64;
+ goto after_norm;
+
+ after_norm:
+
+ /* Put sample */
+ goto *put_u32;
+#define PUT_U32_END after_put_u32
+#include "plugin_ops.h"
+#undef PUT_U32_END
+ after_put_u32:
+
+ dst += dst_step;
+ }
+}
+
+static int route_src_channels_mask(snd_pcm_plugin_t *plugin,
+ bitset_t *dst_vmask,
+ bitset_t **src_vmask)
+{
+ route_t *data = (route_t *)plugin->extra_data;
+ int schannels = plugin->src_format.channels;
+ int dchannels = plugin->dst_format.channels;
+ bitset_t *vmask = plugin->src_vmask;
+ int channel;
+ ttable_dst_t *dp = data->ttable;
+ bitset_zero(vmask, schannels);
+ for (channel = 0; channel < dchannels; channel++, dp++) {
+ unsigned int src;
+ ttable_src_t *sp;
+ if (!bitset_get(dst_vmask, channel))
+ continue;
+ sp = dp->srcs;
+ for (src = 0; src < dp->nsrcs; src++, sp++)
+ bitset_set(vmask, sp->channel);
+ }
+ *src_vmask = vmask;
+ return 0;
+}
+
+static int route_dst_channels_mask(snd_pcm_plugin_t *plugin,
+ bitset_t *src_vmask,
+ bitset_t **dst_vmask)
+{
+ route_t *data = (route_t *)plugin->extra_data;
+ int dchannels = plugin->dst_format.channels;
+ bitset_t *vmask = plugin->dst_vmask;
+ int channel;
+ ttable_dst_t *dp = data->ttable;
+ bitset_zero(vmask, dchannels);
+ for (channel = 0; channel < dchannels; channel++, dp++) {
+ unsigned int src;
+ ttable_src_t *sp;
+ sp = dp->srcs;
+ for (src = 0; src < dp->nsrcs; src++, sp++) {
+ if (bitset_get(src_vmask, sp->channel)) {
+ bitset_set(vmask, channel);
+ break;
+ }
+ }
+ }
+ *dst_vmask = vmask;
+ return 0;
+}
+
+static void route_free(snd_pcm_plugin_t *plugin)
+{
+ route_t *data = (route_t *)plugin->extra_data;
+ unsigned int dst_channel;
+ for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
+ kfree(data->ttable[dst_channel].srcs);
+ }
+}
+
+static int route_load_ttable(snd_pcm_plugin_t *plugin,
+ const route_ttable_entry_t* src_ttable)
+{
+ route_t *data;
+ unsigned int src_channel, dst_channel;
+ const route_ttable_entry_t *sptr;
+ ttable_dst_t *dptr;
+ if (src_ttable == NULL)
+ return 0;
+ data = (route_t *)plugin->extra_data;
+ dptr = data->ttable;
+ sptr = src_ttable;
+ plugin->private_free = route_free;
+ for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) {
+ route_ttable_entry_t t = 0;
+ int att = 0;
+ int nsrcs = 0;
+ ttable_src_t srcs[plugin->src_format.channels];
+ for (src_channel = 0; src_channel < plugin->src_format.channels; ++src_channel) {
+ snd_assert(*sptr >= 0 || *sptr <= FULL, return -ENXIO);
+ if (*sptr != 0) {
+ srcs[nsrcs].channel = src_channel;
+ srcs[nsrcs].as_int = *sptr;
+ if (*sptr != FULL)
+ att = 1;
+ t += *sptr;
+ nsrcs++;
+ }
+ sptr++;
+ }
+ dptr->att = att;
+ dptr->nsrcs = nsrcs;
+ if (nsrcs == 0)
+ dptr->func = route_to_channel_from_zero;
+ else if (nsrcs == 1 && !att)
+ dptr->func = route_to_channel_from_one;
+ else
+ dptr->func = route_to_channel;
+ if (nsrcs > 0) {
+ int srcidx;
+ dptr->srcs = kcalloc(nsrcs, sizeof(*srcs), GFP_KERNEL);
+ for(srcidx = 0; srcidx < nsrcs; srcidx++)
+ dptr->srcs[srcidx] = srcs[srcidx];
+ } else
+ dptr->srcs = NULL;
+ dptr++;
+ }
+ return 0;
+}
+
+static snd_pcm_sframes_t route_transfer(snd_pcm_plugin_t *plugin,
+ const snd_pcm_plugin_channel_t *src_channels,
+ snd_pcm_plugin_channel_t *dst_channels,
+ snd_pcm_uframes_t frames)
+{
+ route_t *data;
+ int src_nchannels, dst_nchannels;
+ int dst_channel;
+ ttable_dst_t *ttp;
+ snd_pcm_plugin_channel_t *dvp;
+
+ snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO);
+ if (frames == 0)
+ return 0;
+ data = (route_t *)plugin->extra_data;
+
+ src_nchannels = plugin->src_format.channels;
+ dst_nchannels = plugin->dst_format.channels;
+
+#ifdef CONFIG_SND_DEBUG
+ {
+ int src_channel;
+ for (src_channel = 0; src_channel < src_nchannels; ++src_channel) {
+ snd_assert(src_channels[src_channel].area.first % 8 == 0 ||
+ src_channels[src_channel].area.step % 8 == 0,
+ return -ENXIO);
+ }
+ for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
+ snd_assert(dst_channels[dst_channel].area.first % 8 == 0 ||
+ dst_channels[dst_channel].area.step % 8 == 0,
+ return -ENXIO);
+ }
+ }
+#endif
+
+ ttp = data->ttable;
+ dvp = dst_channels;
+ for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) {
+ ttp->func(plugin, src_channels, dvp, ttp, frames);
+ dvp++;
+ ttp++;
+ }
+ return frames;
+}
+
+int getput_index(int format)
+{
+ int sign, width, endian;
+ sign = !snd_pcm_format_signed(format);
+ width = snd_pcm_format_width(format) / 8 - 1;
+ if (width < 0 || width > 3) {
+ snd_printk(KERN_ERR "snd-pcm-oss: invalid format %d\n", format);
+ width = 0;
+ }
+#ifdef SNDRV_LITTLE_ENDIAN
+ endian = snd_pcm_format_big_endian(format);
+#else
+ endian = snd_pcm_format_little_endian(format);
+#endif
+ if (endian < 0)
+ endian = 0;
+ return width * 4 + endian * 2 + sign;
+}
+
+int snd_pcm_plugin_build_route(snd_pcm_plug_t *plug,
+ snd_pcm_plugin_format_t *src_format,
+ snd_pcm_plugin_format_t *dst_format,
+ route_ttable_entry_t *ttable,
+ snd_pcm_plugin_t **r_plugin)
+{
+ route_t *data;
+ snd_pcm_plugin_t *plugin;
+ int err;
+
+ snd_assert(r_plugin != NULL, return -ENXIO);
+ *r_plugin = NULL;
+ snd_assert(src_format->rate == dst_format->rate, return -ENXIO);
+ snd_assert(snd_pcm_format_linear(src_format->format) != 0 &&
+ snd_pcm_format_linear(dst_format->format) != 0,
+ return -ENXIO);
+
+ err = snd_pcm_plugin_build(plug, "attenuated route conversion",
+ src_format, dst_format,
+ sizeof(route_t) + sizeof(data->ttable[0]) * dst_format->channels,
+ &plugin);
+ if (err < 0)
+ return err;
+
+ data = (route_t *) plugin->extra_data;
+
+ data->get = getput_index(src_format->format);
+ snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+ data->put = getput_index(dst_format->format);
+ snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL);
+ data->conv = conv_index(src_format->format, dst_format->format);
+
+ if (snd_pcm_format_width(src_format->format) == 32)
+ data->sum_type = R_UINT64;
+ else
+ data->sum_type = R_UINT32;
+ data->src_sample_size = snd_pcm_format_width(src_format->format) / 8;
+
+ if ((err = route_load_ttable(plugin, ttable)) < 0) {
+ snd_pcm_plugin_free(plugin);
+ return err;
+ }
+ plugin->transfer = route_transfer;
+ plugin->src_channels_mask = route_src_channels_mask;
+ plugin->dst_channels_mask = route_dst_channels_mask;
+ *r_plugin = plugin;
+ return 0;
+}
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
new file mode 100644
index 00000000000..8d94325529a
--- /dev/null
+++ b/sound/core/pcm.c
@@ -0,0 +1,1074 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("Midlevel PCM code for ALSA.");
+MODULE_LICENSE("GPL");
+
+snd_pcm_t *snd_pcm_devices[SNDRV_CARDS * SNDRV_PCM_DEVICES];
+static LIST_HEAD(snd_pcm_notify_list);
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_pcm_free(snd_pcm_t *pcm);
+static int snd_pcm_dev_free(snd_device_t *device);
+static int snd_pcm_dev_register(snd_device_t *device);
+static int snd_pcm_dev_disconnect(snd_device_t *device);
+static int snd_pcm_dev_unregister(snd_device_t *device);
+
+static int snd_pcm_control_ioctl(snd_card_t * card,
+ snd_ctl_file_t * control,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int tmp;
+
+ tmp = card->number * SNDRV_PCM_DEVICES;
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
+ {
+ int device;
+
+ if (get_user(device, (int __user *)arg))
+ return -EFAULT;
+ device = device < 0 ? 0 : device + 1;
+ while (device < SNDRV_PCM_DEVICES) {
+ if (snd_pcm_devices[tmp + device])
+ break;
+ device++;
+ }
+ if (device == SNDRV_PCM_DEVICES)
+ device = -1;
+ if (put_user(device, (int __user *)arg))
+ return -EFAULT;
+ return 0;
+ }
+ case SNDRV_CTL_IOCTL_PCM_INFO:
+ {
+ snd_pcm_info_t __user *info;
+ unsigned int device, subdevice;
+ snd_pcm_stream_t stream;
+ snd_pcm_t *pcm;
+ snd_pcm_str_t *pstr;
+ snd_pcm_substream_t *substream;
+ info = (snd_pcm_info_t __user *)arg;
+ if (get_user(device, &info->device))
+ return -EFAULT;
+ if (device >= SNDRV_PCM_DEVICES)
+ return -ENXIO;
+ pcm = snd_pcm_devices[tmp + device];
+ if (pcm == NULL)
+ return -ENXIO;
+ if (get_user(stream, &info->stream))
+ return -EFAULT;
+ if (stream < 0 || stream > 1)
+ return -EINVAL;
+ pstr = &pcm->streams[stream];
+ if (pstr->substream_count == 0)
+ return -ENOENT;
+ if (get_user(subdevice, &info->subdevice))
+ return -EFAULT;
+ if (subdevice >= pstr->substream_count)
+ return -ENXIO;
+ for (substream = pstr->substream; substream; substream = substream->next)
+ if (substream->number == (int)subdevice)
+ break;
+ if (substream == NULL)
+ return -ENXIO;
+ return snd_pcm_info_user(substream, info);
+ }
+ case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE:
+ {
+ int val;
+
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ control->prefer_pcm_subdevice = val;
+ return 0;
+ }
+ }
+ return -ENOIOCTLCMD;
+}
+#define STATE(v) [SNDRV_PCM_STATE_##v] = #v
+#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v
+#define READY(v) [SNDRV_PCM_READY_##v] = #v
+#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v
+#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v
+#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v
+#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v
+#define START(v) [SNDRV_PCM_START_##v] = #v
+#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v
+#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v
+
+static char *snd_pcm_stream_names[] = {
+ STREAM(PLAYBACK),
+ STREAM(CAPTURE),
+};
+
+static char *snd_pcm_state_names[] = {
+ STATE(OPEN),
+ STATE(SETUP),
+ STATE(PREPARED),
+ STATE(RUNNING),
+ STATE(XRUN),
+ STATE(DRAINING),
+ STATE(PAUSED),
+ STATE(SUSPENDED),
+};
+
+static char *snd_pcm_access_names[] = {
+ ACCESS(MMAP_INTERLEAVED),
+ ACCESS(MMAP_NONINTERLEAVED),
+ ACCESS(MMAP_COMPLEX),
+ ACCESS(RW_INTERLEAVED),
+ ACCESS(RW_NONINTERLEAVED),
+};
+
+static char *snd_pcm_format_names[] = {
+ FORMAT(S8),
+ FORMAT(U8),
+ FORMAT(S16_LE),
+ FORMAT(S16_BE),
+ FORMAT(U16_LE),
+ FORMAT(U16_BE),
+ FORMAT(S24_LE),
+ FORMAT(S24_BE),
+ FORMAT(U24_LE),
+ FORMAT(U24_BE),
+ FORMAT(S32_LE),
+ FORMAT(S32_BE),
+ FORMAT(U32_LE),
+ FORMAT(U32_BE),
+ FORMAT(FLOAT_LE),
+ FORMAT(FLOAT_BE),
+ FORMAT(FLOAT64_LE),
+ FORMAT(FLOAT64_BE),
+ FORMAT(IEC958_SUBFRAME_LE),
+ FORMAT(IEC958_SUBFRAME_BE),
+ FORMAT(MU_LAW),
+ FORMAT(A_LAW),
+ FORMAT(IMA_ADPCM),
+ FORMAT(MPEG),
+ FORMAT(GSM),
+ FORMAT(SPECIAL),
+ FORMAT(S24_3LE),
+ FORMAT(S24_3BE),
+ FORMAT(U24_3LE),
+ FORMAT(U24_3BE),
+ FORMAT(S20_3LE),
+ FORMAT(S20_3BE),
+ FORMAT(U20_3LE),
+ FORMAT(U20_3BE),
+ FORMAT(S18_3LE),
+ FORMAT(S18_3BE),
+ FORMAT(U18_3LE),
+ FORMAT(U18_3BE),
+};
+
+static char *snd_pcm_subformat_names[] = {
+ SUBFORMAT(STD),
+};
+
+static char *snd_pcm_tstamp_mode_names[] = {
+ TSTAMP(NONE),
+ TSTAMP(MMAP),
+};
+
+static const char *snd_pcm_stream_name(snd_pcm_stream_t stream)
+{
+ snd_assert(stream <= SNDRV_PCM_STREAM_LAST, return NULL);
+ return snd_pcm_stream_names[stream];
+}
+
+static const char *snd_pcm_access_name(snd_pcm_access_t access)
+{
+ snd_assert(access <= SNDRV_PCM_ACCESS_LAST, return NULL);
+ return snd_pcm_access_names[access];
+}
+
+const char *snd_pcm_format_name(snd_pcm_format_t format)
+{
+ snd_assert(format <= SNDRV_PCM_FORMAT_LAST, return NULL);
+ return snd_pcm_format_names[format];
+}
+
+static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat)
+{
+ snd_assert(subformat <= SNDRV_PCM_SUBFORMAT_LAST, return NULL);
+ return snd_pcm_subformat_names[subformat];
+}
+
+static const char *snd_pcm_tstamp_mode_name(snd_pcm_tstamp_t mode)
+{
+ snd_assert(mode <= SNDRV_PCM_TSTAMP_LAST, return NULL);
+ return snd_pcm_tstamp_mode_names[mode];
+}
+
+static const char *snd_pcm_state_name(snd_pcm_state_t state)
+{
+ snd_assert(state <= SNDRV_PCM_STATE_LAST, return NULL);
+ return snd_pcm_state_names[state];
+}
+
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+#include <linux/soundcard.h>
+static const char *snd_pcm_oss_format_name(int format)
+{
+ switch (format) {
+ case AFMT_MU_LAW:
+ return "MU_LAW";
+ case AFMT_A_LAW:
+ return "A_LAW";
+ case AFMT_IMA_ADPCM:
+ return "IMA_ADPCM";
+ case AFMT_U8:
+ return "U8";
+ case AFMT_S16_LE:
+ return "S16_LE";
+ case AFMT_S16_BE:
+ return "S16_BE";
+ case AFMT_S8:
+ return "S8";
+ case AFMT_U16_LE:
+ return "U16_LE";
+ case AFMT_U16_BE:
+ return "U16_BE";
+ case AFMT_MPEG:
+ return "MPEG";
+ default:
+ return "unknown";
+ }
+}
+#endif
+
+#ifdef CONFIG_PROC_FS
+static void snd_pcm_proc_info_read(snd_pcm_substream_t *substream, snd_info_buffer_t *buffer)
+{
+ snd_pcm_info_t *info;
+ int err;
+
+ snd_runtime_check(substream, return);
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (! info) {
+ printk(KERN_DEBUG "snd_pcm_proc_info_read: cannot malloc\n");
+ return;
+ }
+
+ err = snd_pcm_info(substream, info);
+ if (err < 0) {
+ snd_iprintf(buffer, "error %d\n", err);
+ kfree(info);
+ return;
+ }
+ snd_iprintf(buffer, "card: %d\n", info->card);
+ snd_iprintf(buffer, "device: %d\n", info->device);
+ snd_iprintf(buffer, "subdevice: %d\n", info->subdevice);
+ snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream));
+ snd_iprintf(buffer, "id: %s\n", info->id);
+ snd_iprintf(buffer, "name: %s\n", info->name);
+ snd_iprintf(buffer, "subname: %s\n", info->subname);
+ snd_iprintf(buffer, "class: %d\n", info->dev_class);
+ snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass);
+ snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count);
+ snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail);
+ kfree(info);
+}
+
+static void snd_pcm_stream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_proc_info_read(((snd_pcm_str_t *)entry->private_data)->substream, buffer);
+}
+
+static void snd_pcm_substream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_proc_info_read((snd_pcm_substream_t *)entry->private_data, buffer);
+}
+
+static void snd_pcm_substream_proc_hw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!runtime) {
+ snd_iprintf(buffer, "closed\n");
+ return;
+ }
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_iprintf(buffer, "no setup\n");
+ snd_pcm_stream_unlock_irq(substream);
+ return;
+ }
+ snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access));
+ snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format));
+ snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat));
+ snd_iprintf(buffer, "channels: %u\n", runtime->channels);
+ snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den);
+ snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size);
+ snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size);
+ snd_iprintf(buffer, "tick_time: %u\n", runtime->tick_time);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (substream->oss.oss) {
+ snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format));
+ snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels);
+ snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate);
+ snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes);
+ snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods);
+ snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames);
+ }
+#endif
+ snd_pcm_stream_unlock_irq(substream);
+}
+
+static void snd_pcm_substream_proc_sw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!runtime) {
+ snd_iprintf(buffer, "closed\n");
+ return;
+ }
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_iprintf(buffer, "no setup\n");
+ snd_pcm_stream_unlock_irq(substream);
+ return;
+ }
+ snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode));
+ snd_iprintf(buffer, "period_step: %u\n", runtime->period_step);
+ snd_iprintf(buffer, "sleep_min: %u\n", runtime->sleep_min);
+ snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min);
+ snd_iprintf(buffer, "xfer_align: %lu\n", runtime->xfer_align);
+ snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold);
+ snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold);
+ snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold);
+ snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size);
+ snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary);
+ snd_pcm_stream_unlock_irq(substream);
+}
+
+static void snd_pcm_substream_proc_status_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_status_t status;
+ int err;
+ if (!runtime) {
+ snd_iprintf(buffer, "closed\n");
+ return;
+ }
+ memset(&status, 0, sizeof(status));
+ err = snd_pcm_status(substream, &status);
+ if (err < 0) {
+ snd_iprintf(buffer, "error %d\n", err);
+ return;
+ }
+ snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state));
+ snd_iprintf(buffer, "trigger_time: %ld.%09ld\n",
+ status.trigger_tstamp.tv_sec, status.trigger_tstamp.tv_nsec);
+ snd_iprintf(buffer, "tstamp : %ld.%09ld\n",
+ status.tstamp.tv_sec, status.tstamp.tv_nsec);
+ snd_iprintf(buffer, "delay : %ld\n", status.delay);
+ snd_iprintf(buffer, "avail : %ld\n", status.avail);
+ snd_iprintf(buffer, "avail_max : %ld\n", status.avail_max);
+ snd_iprintf(buffer, "-----\n");
+ snd_iprintf(buffer, "hw_ptr : %ld\n", runtime->status->hw_ptr);
+ snd_iprintf(buffer, "appl_ptr : %ld\n", runtime->control->appl_ptr);
+}
+#endif
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_pcm_xrun_debug_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+ snd_iprintf(buffer, "%d\n", pstr->xrun_debug);
+}
+
+static void snd_pcm_xrun_debug_write(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data;
+ char line[64];
+ if (!snd_info_get_line(buffer, line, sizeof(line)))
+ pstr->xrun_debug = simple_strtoul(line, NULL, 10);
+}
+#endif
+
+static int snd_pcm_stream_proc_init(snd_pcm_str_t *pstr)
+{
+ snd_pcm_t *pcm = pstr->pcm;
+ snd_info_entry_t *entry;
+ char name[16];
+
+ sprintf(name, "pcm%i%c", pcm->device,
+ pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
+ if ((entry = snd_info_create_card_entry(pcm->card, name, pcm->card->proc_root)) == NULL)
+ return -ENOMEM;
+ entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ pstr->proc_root = entry;
+
+ if ((entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, pstr, 256, snd_pcm_stream_proc_info_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ pstr->proc_info_entry = entry;
+
+#ifdef CONFIG_SND_DEBUG
+ if ((entry = snd_info_create_card_entry(pcm->card, "xrun_debug", pstr->proc_root)) != NULL) {
+ entry->c.text.read_size = 64;
+ entry->c.text.read = snd_pcm_xrun_debug_read;
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_pcm_xrun_debug_write;
+ entry->private_data = pstr;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ pstr->proc_xrun_debug_entry = entry;
+#endif
+ return 0;
+}
+
+static int snd_pcm_stream_proc_done(snd_pcm_str_t *pstr)
+{
+#ifdef CONFIG_SND_DEBUG
+ if (pstr->proc_xrun_debug_entry) {
+ snd_info_unregister(pstr->proc_xrun_debug_entry);
+ pstr->proc_xrun_debug_entry = NULL;
+ }
+#endif
+ if (pstr->proc_info_entry) {
+ snd_info_unregister(pstr->proc_info_entry);
+ pstr->proc_info_entry = NULL;
+ }
+ if (pstr->proc_root) {
+ snd_info_unregister(pstr->proc_root);
+ pstr->proc_root = NULL;
+ }
+ return 0;
+}
+
+static int snd_pcm_substream_proc_init(snd_pcm_substream_t *substream)
+{
+ snd_info_entry_t *entry;
+ snd_card_t *card;
+ char name[16];
+
+ card = substream->pcm->card;
+
+ sprintf(name, "sub%i", substream->number);
+ if ((entry = snd_info_create_card_entry(card, name, substream->pstr->proc_root)) == NULL)
+ return -ENOMEM;
+ entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ substream->proc_root = entry;
+
+ if ((entry = snd_info_create_card_entry(card, "info", substream->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_info_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ substream->proc_info_entry = entry;
+
+ if ((entry = snd_info_create_card_entry(card, "hw_params", substream->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_hw_params_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ substream->proc_hw_params_entry = entry;
+
+ if ((entry = snd_info_create_card_entry(card, "sw_params", substream->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_sw_params_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ substream->proc_sw_params_entry = entry;
+
+ if ((entry = snd_info_create_card_entry(card, "status", substream->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_status_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ substream->proc_status_entry = entry;
+
+ return 0;
+}
+
+static int snd_pcm_substream_proc_done(snd_pcm_substream_t *substream)
+{
+ if (substream->proc_info_entry) {
+ snd_info_unregister(substream->proc_info_entry);
+ substream->proc_info_entry = NULL;
+ }
+ if (substream->proc_hw_params_entry) {
+ snd_info_unregister(substream->proc_hw_params_entry);
+ substream->proc_hw_params_entry = NULL;
+ }
+ if (substream->proc_sw_params_entry) {
+ snd_info_unregister(substream->proc_sw_params_entry);
+ substream->proc_sw_params_entry = NULL;
+ }
+ if (substream->proc_status_entry) {
+ snd_info_unregister(substream->proc_status_entry);
+ substream->proc_status_entry = NULL;
+ }
+ if (substream->proc_root) {
+ snd_info_unregister(substream->proc_root);
+ substream->proc_root = NULL;
+ }
+ return 0;
+}
+
+/**
+ * snd_pcm_new_stream - create a new PCM stream
+ * @pcm: the pcm instance
+ * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
+ * @substream_count: the number of substreams
+ *
+ * Creates a new stream for the pcm.
+ * The corresponding stream on the pcm must have been empty before
+ * calling this, i.e. zero must be given to the argument of
+ * snd_pcm_new().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count)
+{
+ int idx, err;
+ snd_pcm_str_t *pstr = &pcm->streams[stream];
+ snd_pcm_substream_t *substream, *prev;
+
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ init_MUTEX(&pstr->oss.setup_mutex);
+#endif
+ pstr->stream = stream;
+ pstr->pcm = pcm;
+ pstr->substream_count = substream_count;
+ pstr->reg = &snd_pcm_reg[stream];
+ if (substream_count > 0) {
+ err = snd_pcm_stream_proc_init(pstr);
+ if (err < 0)
+ return err;
+ }
+ prev = NULL;
+ for (idx = 0, prev = NULL; idx < substream_count; idx++) {
+ substream = kcalloc(1, sizeof(*substream), GFP_KERNEL);
+ if (substream == NULL)
+ return -ENOMEM;
+ substream->pcm = pcm;
+ substream->pstr = pstr;
+ substream->number = idx;
+ substream->stream = stream;
+ sprintf(substream->name, "subdevice #%i", idx);
+ substream->buffer_bytes_max = UINT_MAX;
+ if (prev == NULL)
+ pstr->substream = substream;
+ else
+ prev->next = substream;
+ err = snd_pcm_substream_proc_init(substream);
+ if (err < 0) {
+ kfree(substream);
+ return err;
+ }
+ substream->group = &substream->self_group;
+ spin_lock_init(&substream->self_group.lock);
+ INIT_LIST_HEAD(&substream->self_group.substreams);
+ list_add_tail(&substream->link_list, &substream->self_group.substreams);
+ spin_lock_init(&substream->timer_lock);
+ prev = substream;
+ }
+ return 0;
+}
+
+/**
+ * snd_pcm_new - create a new PCM instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero based)
+ * @playback_count: the number of substreams for playback
+ * @capture_count: the number of substreams for capture
+ * @rpcm: the pointer to store the new pcm instance
+ *
+ * Creates a new PCM instance.
+ *
+ * The pcm operators have to be set afterwards to the new instance
+ * via snd_pcm_set_ops().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_new(snd_card_t * card, char *id, int device,
+ int playback_count, int capture_count,
+ snd_pcm_t ** rpcm)
+{
+ snd_pcm_t *pcm;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_pcm_dev_free,
+ .dev_register = snd_pcm_dev_register,
+ .dev_disconnect = snd_pcm_dev_disconnect,
+ .dev_unregister = snd_pcm_dev_unregister
+ };
+
+ snd_assert(rpcm != NULL, return -EINVAL);
+ *rpcm = NULL;
+ snd_assert(card != NULL, return -ENXIO);
+ pcm = kcalloc(1, sizeof(*pcm), GFP_KERNEL);
+ if (pcm == NULL)
+ return -ENOMEM;
+ pcm->card = card;
+ pcm->device = device;
+ if (id) {
+ strlcpy(pcm->id, id, sizeof(pcm->id));
+ }
+ if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
+ snd_pcm_free(pcm);
+ return err;
+ }
+ if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
+ snd_pcm_free(pcm);
+ return err;
+ }
+ init_MUTEX(&pcm->open_mutex);
+ init_waitqueue_head(&pcm->open_wait);
+ if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
+ snd_pcm_free(pcm);
+ return err;
+ }
+ *rpcm = pcm;
+ return 0;
+}
+
+static void snd_pcm_free_stream(snd_pcm_str_t * pstr)
+{
+ snd_pcm_substream_t *substream, *substream_next;
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ snd_pcm_oss_setup_t *setup, *setupn;
+#endif
+ substream = pstr->substream;
+ while (substream) {
+ substream_next = substream->next;
+ snd_pcm_substream_proc_done(substream);
+ kfree(substream);
+ substream = substream_next;
+ }
+ snd_pcm_stream_proc_done(pstr);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ for (setup = pstr->oss.setup_list; setup; setup = setupn) {
+ setupn = setup->next;
+ kfree(setup->task_name);
+ kfree(setup);
+ }
+#endif
+}
+
+static int snd_pcm_free(snd_pcm_t *pcm)
+{
+ snd_assert(pcm != NULL, return -ENXIO);
+ if (pcm->private_free)
+ pcm->private_free(pcm);
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+ snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]);
+ snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]);
+ kfree(pcm);
+ return 0;
+}
+
+static int snd_pcm_dev_free(snd_device_t *device)
+{
+ snd_pcm_t *pcm = device->device_data;
+ return snd_pcm_free(pcm);
+}
+
+static void snd_pcm_tick_timer_func(unsigned long data)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t*) data;
+ snd_pcm_tick_elapsed(substream);
+}
+
+int snd_pcm_open_substream(snd_pcm_t *pcm, int stream,
+ snd_pcm_substream_t **rsubstream)
+{
+ snd_pcm_str_t * pstr;
+ snd_pcm_substream_t * substream;
+ snd_pcm_runtime_t * runtime;
+ snd_ctl_file_t *kctl;
+ snd_card_t *card;
+ struct list_head *list;
+ int prefer_subdevice = -1;
+ size_t size;
+
+ snd_assert(rsubstream != NULL, return -EINVAL);
+ *rsubstream = NULL;
+ snd_assert(pcm != NULL, return -ENXIO);
+ pstr = &pcm->streams[stream];
+ if (pstr->substream == NULL)
+ return -ENODEV;
+
+ card = pcm->card;
+ down_read(&card->controls_rwsem);
+ list_for_each(list, &card->ctl_files) {
+ kctl = snd_ctl_file(list);
+ if (kctl->pid == current->pid) {
+ prefer_subdevice = kctl->prefer_pcm_subdevice;
+ break;
+ }
+ }
+ up_read(&card->controls_rwsem);
+
+ if (pstr->substream_count == 0)
+ return -ENODEV;
+ switch (stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) {
+ if (SUBSTREAM_BUSY(substream))
+ return -EAGAIN;
+ }
+ }
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) {
+ if (SUBSTREAM_BUSY(substream))
+ return -EAGAIN;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (prefer_subdevice >= 0) {
+ for (substream = pstr->substream; substream; substream = substream->next)
+ if (!SUBSTREAM_BUSY(substream) && substream->number == prefer_subdevice)
+ goto __ok;
+ }
+ for (substream = pstr->substream; substream; substream = substream->next)
+ if (!SUBSTREAM_BUSY(substream))
+ break;
+ __ok:
+ if (substream == NULL)
+ return -EAGAIN;
+
+ runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL);
+ if (runtime == NULL)
+ return -ENOMEM;
+
+ size = PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t));
+ runtime->status = snd_malloc_pages(size, GFP_KERNEL);
+ if (runtime->status == NULL) {
+ kfree(runtime);
+ return -ENOMEM;
+ }
+ memset((void*)runtime->status, 0, size);
+
+ size = PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t));
+ runtime->control = snd_malloc_pages(size, GFP_KERNEL);
+ if (runtime->control == NULL) {
+ snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)));
+ kfree(runtime);
+ return -ENOMEM;
+ }
+ memset((void*)runtime->control, 0, size);
+
+ init_waitqueue_head(&runtime->sleep);
+ atomic_set(&runtime->mmap_count, 0);
+ init_timer(&runtime->tick_timer);
+ runtime->tick_timer.function = snd_pcm_tick_timer_func;
+ runtime->tick_timer.data = (unsigned long) substream;
+
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+
+ substream->runtime = runtime;
+ substream->private_data = pcm->private_data;
+ pstr->substream_opened++;
+ *rsubstream = substream;
+ return 0;
+}
+
+void snd_pcm_release_substream(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t * runtime;
+ substream->file = NULL;
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return);
+ if (runtime->private_free != NULL)
+ runtime->private_free(runtime);
+ snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)));
+ snd_free_pages((void*)runtime->control, PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t)));
+ kfree(runtime->hw_constraints.rules);
+ kfree(runtime);
+ substream->runtime = NULL;
+ substream->pstr->substream_opened--;
+}
+
+static int snd_pcm_dev_register(snd_device_t *device)
+{
+ int idx, cidx, err;
+ unsigned short minor;
+ snd_pcm_substream_t *substream;
+ struct list_head *list;
+ char str[16];
+ snd_pcm_t *pcm = device->device_data;
+
+ snd_assert(pcm != NULL && device != NULL, return -ENXIO);
+ down(&register_mutex);
+ idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+ if (snd_pcm_devices[idx]) {
+ up(&register_mutex);
+ return -EBUSY;
+ }
+ snd_pcm_devices[idx] = pcm;
+ for (cidx = 0; cidx < 2; cidx++) {
+ int devtype = -1;
+ if (pcm->streams[cidx].substream == NULL)
+ continue;
+ switch (cidx) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
+ minor = SNDRV_MINOR_PCM_PLAYBACK + idx;
+ devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
+ minor = SNDRV_MINOR_PCM_CAPTURE + idx;
+ devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
+ break;
+ }
+ if ((err = snd_register_device(devtype, pcm->card, pcm->device, pcm->streams[cidx].reg, str)) < 0) {
+ snd_pcm_devices[idx] = NULL;
+ up(&register_mutex);
+ return err;
+ }
+ for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+ snd_pcm_timer_init(substream);
+ }
+ list_for_each(list, &snd_pcm_notify_list) {
+ snd_pcm_notify_t *notify;
+ notify = list_entry(list, snd_pcm_notify_t, list);
+ notify->n_register(pcm);
+ }
+ up(&register_mutex);
+ return 0;
+}
+
+static int snd_pcm_dev_disconnect(snd_device_t *device)
+{
+ snd_pcm_t *pcm = device->device_data;
+ struct list_head *list;
+ snd_pcm_substream_t *substream;
+ int idx, cidx;
+
+ down(&register_mutex);
+ idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+ snd_pcm_devices[idx] = NULL;
+ for (cidx = 0; cidx < 2; cidx++)
+ for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+ if (substream->runtime)
+ substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED;
+ list_for_each(list, &snd_pcm_notify_list) {
+ snd_pcm_notify_t *notify;
+ notify = list_entry(list, snd_pcm_notify_t, list);
+ notify->n_disconnect(pcm);
+ }
+ up(&register_mutex);
+ return 0;
+}
+
+static int snd_pcm_dev_unregister(snd_device_t *device)
+{
+ int idx, cidx, devtype;
+ snd_pcm_substream_t *substream;
+ struct list_head *list;
+ snd_pcm_t *pcm = device->device_data;
+
+ snd_assert(pcm != NULL, return -ENXIO);
+ down(&register_mutex);
+ idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device;
+ snd_pcm_devices[idx] = NULL;
+ for (cidx = 0; cidx < 2; cidx++) {
+ devtype = -1;
+ switch (cidx) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
+ break;
+ }
+ snd_unregister_device(devtype, pcm->card, pcm->device);
+ for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+ snd_pcm_timer_done(substream);
+ }
+ list_for_each(list, &snd_pcm_notify_list) {
+ snd_pcm_notify_t *notify;
+ notify = list_entry(list, snd_pcm_notify_t, list);
+ notify->n_unregister(pcm);
+ }
+ up(&register_mutex);
+ return snd_pcm_free(pcm);
+}
+
+int snd_pcm_notify(snd_pcm_notify_t *notify, int nfree)
+{
+ int idx;
+
+ snd_assert(notify != NULL && notify->n_register != NULL && notify->n_unregister != NULL, return -EINVAL);
+ down(&register_mutex);
+ if (nfree) {
+ list_del(&notify->list);
+ for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+ if (snd_pcm_devices[idx] == NULL)
+ continue;
+ notify->n_unregister(snd_pcm_devices[idx]);
+ }
+ } else {
+ list_add_tail(&notify->list, &snd_pcm_notify_list);
+ for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+ if (snd_pcm_devices[idx] == NULL)
+ continue;
+ notify->n_register(snd_pcm_devices[idx]);
+ }
+ }
+ up(&register_mutex);
+ return 0;
+}
+
+/*
+ * Info interface
+ */
+
+static void snd_pcm_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int idx;
+ snd_pcm_t *pcm;
+
+ down(&register_mutex);
+ for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) {
+ pcm = snd_pcm_devices[idx];
+ if (pcm == NULL)
+ continue;
+ snd_iprintf(buffer, "%02i-%02i: %s : %s", idx / SNDRV_PCM_DEVICES,
+ idx % SNDRV_PCM_DEVICES, pcm->id, pcm->name);
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+ snd_iprintf(buffer, " : playback %i", pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count);
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+ snd_iprintf(buffer, " : capture %i", pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count);
+ snd_iprintf(buffer, "\n");
+ }
+ up(&register_mutex);
+}
+
+/*
+ * ENTRY functions
+ */
+
+static snd_info_entry_t *snd_pcm_proc_entry = NULL;
+
+static int __init alsa_pcm_init(void)
+{
+ snd_info_entry_t *entry;
+
+ snd_ctl_register_ioctl(snd_pcm_control_ioctl);
+ snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl);
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) {
+ snd_info_set_text_ops(entry, NULL, SNDRV_CARDS * SNDRV_PCM_DEVICES * 128, snd_pcm_proc_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_pcm_proc_entry = entry;
+ return 0;
+}
+
+static void __exit alsa_pcm_exit(void)
+{
+ snd_ctl_unregister_ioctl(snd_pcm_control_ioctl);
+ snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl);
+ if (snd_pcm_proc_entry) {
+ snd_info_unregister(snd_pcm_proc_entry);
+ snd_pcm_proc_entry = NULL;
+ }
+}
+
+module_init(alsa_pcm_init)
+module_exit(alsa_pcm_exit)
+
+EXPORT_SYMBOL(snd_pcm_devices);
+EXPORT_SYMBOL(snd_pcm_new);
+EXPORT_SYMBOL(snd_pcm_new_stream);
+EXPORT_SYMBOL(snd_pcm_notify);
+EXPORT_SYMBOL(snd_pcm_open_substream);
+EXPORT_SYMBOL(snd_pcm_release_substream);
+EXPORT_SYMBOL(snd_pcm_format_name);
+ /* pcm_native.c */
+EXPORT_SYMBOL(snd_pcm_link_rwlock);
+EXPORT_SYMBOL(snd_pcm_start);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_pcm_suspend);
+EXPORT_SYMBOL(snd_pcm_suspend_all);
+#endif
+EXPORT_SYMBOL(snd_pcm_kernel_playback_ioctl);
+EXPORT_SYMBOL(snd_pcm_kernel_capture_ioctl);
+EXPORT_SYMBOL(snd_pcm_kernel_ioctl);
+EXPORT_SYMBOL(snd_pcm_mmap_data);
+#if SNDRV_PCM_INFO_MMAP_IOMEM
+EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem);
+#endif
+ /* pcm_misc.c */
+EXPORT_SYMBOL(snd_pcm_format_signed);
+EXPORT_SYMBOL(snd_pcm_format_unsigned);
+EXPORT_SYMBOL(snd_pcm_format_linear);
+EXPORT_SYMBOL(snd_pcm_format_little_endian);
+EXPORT_SYMBOL(snd_pcm_format_big_endian);
+EXPORT_SYMBOL(snd_pcm_format_width);
+EXPORT_SYMBOL(snd_pcm_format_physical_width);
+EXPORT_SYMBOL(snd_pcm_format_silence_64);
+EXPORT_SYMBOL(snd_pcm_format_set_silence);
+EXPORT_SYMBOL(snd_pcm_build_linear_format);
+EXPORT_SYMBOL(snd_pcm_limit_hw_rates);
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
new file mode 100644
index 00000000000..3920bf0eebb
--- /dev/null
+++ b/sound/core/pcm_compat.c
@@ -0,0 +1,513 @@
+/*
+ * 32bit -> 64bit ioctl wrapper for PCM API
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ */
+
+/* This file included from pcm_native.c */
+
+#include <linux/compat.h>
+
+static int snd_pcm_ioctl_delay_compat(snd_pcm_substream_t *substream,
+ s32 __user *src)
+{
+ snd_pcm_sframes_t delay;
+ mm_segment_t fs;
+ int err;
+
+ fs = snd_enter_user();
+ err = snd_pcm_delay(substream, &delay);
+ snd_leave_user(fs);
+ if (err < 0)
+ return err;
+ if (put_user(delay, src))
+ return -EFAULT;
+ return err;
+}
+
+static int snd_pcm_ioctl_rewind_compat(snd_pcm_substream_t *substream,
+ u32 __user *src)
+{
+ snd_pcm_uframes_t frames;
+ int err;
+
+ if (get_user(frames, src))
+ return -EFAULT;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ err = snd_pcm_playback_rewind(substream, frames);
+ else
+ err = snd_pcm_capture_rewind(substream, frames);
+ if (put_user(err, src))
+ return -EFAULT;
+ return err < 0 ? err : 0;
+}
+
+static int snd_pcm_ioctl_forward_compat(snd_pcm_substream_t *substream,
+ u32 __user *src)
+{
+ snd_pcm_uframes_t frames;
+ int err;
+
+ if (get_user(frames, src))
+ return -EFAULT;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ err = snd_pcm_playback_forward(substream, frames);
+ else
+ err = snd_pcm_capture_forward(substream, frames);
+ if (put_user(err, src))
+ return -EFAULT;
+ return err < 0 ? err : 0;
+}
+
+struct sndrv_pcm_hw_params32 {
+ u32 flags;
+ struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
+ struct sndrv_mask mres[5]; /* reserved masks */
+ struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
+ struct sndrv_interval ires[9]; /* reserved intervals */
+ u32 rmask;
+ u32 cmask;
+ u32 info;
+ u32 msbits;
+ u32 rate_num;
+ u32 rate_den;
+ u32 fifo_size;
+ unsigned char reserved[64];
+};
+
+struct sndrv_pcm_sw_params32 {
+ s32 tstamp_mode;
+ u32 period_step;
+ u32 sleep_min;
+ u32 avail_min;
+ u32 xfer_align;
+ u32 start_threshold;
+ u32 stop_threshold;
+ u32 silence_threshold;
+ u32 silence_size;
+ u32 boundary;
+ unsigned char reserved[64];
+};
+
+static int snd_pcm_ioctl_sw_params_compat(snd_pcm_substream_t *substream,
+ struct sndrv_pcm_sw_params32 __user *src)
+{
+ snd_pcm_sw_params_t params;
+ int err;
+
+ memset(&params, 0, sizeof(params));
+ if (get_user(params.tstamp_mode, &src->tstamp_mode) ||
+ get_user(params.period_step, &src->period_step) ||
+ get_user(params.sleep_min, &src->sleep_min) ||
+ get_user(params.avail_min, &src->avail_min) ||
+ get_user(params.xfer_align, &src->xfer_align) ||
+ get_user(params.start_threshold, &src->start_threshold) ||
+ get_user(params.stop_threshold, &src->stop_threshold) ||
+ get_user(params.silence_threshold, &src->silence_threshold) ||
+ get_user(params.silence_size, &src->silence_size))
+ return -EFAULT;
+ err = snd_pcm_sw_params(substream, &params);
+ if (err < 0)
+ return err;
+ if (put_user(params.boundary, &src->boundary))
+ return -EFAULT;
+ return err;
+}
+
+struct sndrv_pcm_channel_info32 {
+ u32 channel;
+ u32 offset;
+ u32 first;
+ u32 step;
+};
+
+static int snd_pcm_ioctl_channel_info_compat(snd_pcm_substream_t *substream,
+ struct sndrv_pcm_channel_info32 __user *src)
+{
+ snd_pcm_channel_info_t info;
+ int err;
+
+ if (get_user(info.channel, &src->channel) ||
+ get_user(info.offset, &src->offset) ||
+ get_user(info.first, &src->first) ||
+ get_user(info.step, &src->step))
+ return -EFAULT;
+ err = snd_pcm_channel_info(substream, &info);
+ if (err < 0)
+ return err;
+ if (put_user(info.channel, &src->channel) ||
+ put_user(info.offset, &src->offset) ||
+ put_user(info.first, &src->first) ||
+ put_user(info.step, &src->step))
+ return -EFAULT;
+ return err;
+}
+
+struct sndrv_pcm_status32 {
+ s32 state;
+ struct compat_timespec trigger_tstamp;
+ struct compat_timespec tstamp;
+ u32 appl_ptr;
+ u32 hw_ptr;
+ s32 delay;
+ u32 avail;
+ u32 avail_max;
+ u32 overrange;
+ s32 suspended_state;
+ unsigned char reserved[60];
+} __attribute__((packed));
+
+
+static int snd_pcm_status_user_compat(snd_pcm_substream_t *substream,
+ struct sndrv_pcm_status32 __user *src)
+{
+ snd_pcm_status_t status;
+ int err;
+
+ err = snd_pcm_status(substream, &status);
+ if (err < 0)
+ return err;
+
+ if (put_user(status.state, &src->state) ||
+ put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) ||
+ put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) ||
+ put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+ put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
+ put_user(status.appl_ptr, &src->appl_ptr) ||
+ put_user(status.hw_ptr, &src->hw_ptr) ||
+ put_user(status.delay, &src->delay) ||
+ put_user(status.avail, &src->avail) ||
+ put_user(status.avail_max, &src->avail_max) ||
+ put_user(status.overrange, &src->overrange) ||
+ put_user(status.suspended_state, &src->suspended_state))
+ return -EFAULT;
+
+ return err;
+}
+
+/* recalcuate the boundary within 32bit */
+static void recalculate_boundary(snd_pcm_runtime_t *runtime)
+{
+ if (! runtime->buffer_size)
+ return;
+ runtime->boundary = runtime->buffer_size;
+ while (runtime->boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
+ runtime->boundary *= 2;
+}
+
+/* both for HW_PARAMS and HW_REFINE */
+static int snd_pcm_ioctl_hw_params_compat(snd_pcm_substream_t *substream,
+ int refine,
+ struct sndrv_pcm_hw_params32 __user *data32)
+{
+ struct sndrv_pcm_hw_params *data;
+ snd_pcm_runtime_t *runtime;
+ int err;
+
+ if (! (runtime = substream->runtime))
+ return -ENOTTY;
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ /* only fifo_size is different, so just copy all */
+ if (copy_from_user(data, data32, sizeof(*data32))) {
+ err = -EFAULT;
+ goto error;
+ }
+ if (refine)
+ err = snd_pcm_hw_refine(substream, data);
+ else
+ err = snd_pcm_hw_params(substream, data);
+ if (err < 0)
+ goto error;
+ if (copy_to_user(data32, data, sizeof(*data32)) ||
+ put_user(data->fifo_size, &data32->fifo_size)) {
+ err = -EFAULT;
+ goto error;
+ }
+
+ if (! refine)
+ recalculate_boundary(runtime);
+ error:
+ kfree(data);
+ return err;
+}
+
+
+/*
+ */
+struct sndrv_xferi32 {
+ s32 result;
+ u32 buf;
+ u32 frames;
+};
+
+static int snd_pcm_ioctl_xferi_compat(snd_pcm_substream_t *substream,
+ int dir, struct sndrv_xferi32 __user *data32)
+{
+ compat_caddr_t buf;
+ u32 frames;
+ int err;
+
+ if (! substream->runtime)
+ return -ENOTTY;
+ if (substream->stream != dir)
+ return -EINVAL;
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ if (get_user(buf, &data32->buf) ||
+ get_user(frames, &data32->frames))
+ return -EFAULT;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ err = snd_pcm_lib_write(substream, compat_ptr(buf), frames);
+ else
+ err = snd_pcm_lib_read(substream, compat_ptr(buf), frames);
+ if (err < 0)
+ return err;
+ /* copy the result */
+ if (put_user(err, &data32->result))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* snd_xfern needs remapping of bufs */
+struct sndrv_xfern32 {
+ s32 result;
+ u32 bufs; /* this is void **; */
+ u32 frames;
+};
+
+/*
+ * xfern ioctl nees to copy (up to) 128 pointers on stack.
+ * although we may pass the copied pointers through f_op->ioctl, but the ioctl
+ * handler there expands again the same 128 pointers on stack, so it is better
+ * to handle the function (calling pcm_readv/writev) directly in this handler.
+ */
+static int snd_pcm_ioctl_xfern_compat(snd_pcm_substream_t *substream,
+ int dir, struct sndrv_xfern32 __user *data32)
+{
+ compat_caddr_t buf;
+ compat_caddr_t __user *bufptr;
+ u32 frames;
+ void __user **bufs;
+ int err, ch, i;
+
+ if (! substream->runtime)
+ return -ENOTTY;
+ if (substream->stream != dir)
+ return -EINVAL;
+
+ if ((ch = substream->runtime->channels) > 128)
+ return -EINVAL;
+ if (get_user(buf, &data32->bufs) ||
+ get_user(frames, &data32->frames))
+ return -EFAULT;
+ bufptr = compat_ptr(buf);
+ bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
+ if (bufs == NULL)
+ return -ENOMEM;
+ for (i = 0; i < ch; i++) {
+ u32 ptr;
+ if (get_user(ptr, bufptr)) {
+ kfree(bufs);
+ return -EFAULT;
+ }
+ bufs[ch] = compat_ptr(ptr);
+ bufptr++;
+ }
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ err = snd_pcm_lib_writev(substream, bufs, frames);
+ else
+ err = snd_pcm_lib_readv(substream, bufs, frames);
+ if (err >= 0) {
+ if (put_user(err, &data32->result))
+ err = -EFAULT;
+ }
+ kfree(bufs);
+ return err;
+}
+
+
+struct sndrv_pcm_mmap_status32 {
+ s32 state;
+ s32 pad1;
+ u32 hw_ptr;
+ struct compat_timespec tstamp;
+ s32 suspended_state;
+} __attribute__((packed));
+
+struct sndrv_pcm_mmap_control32 {
+ u32 appl_ptr;
+ u32 avail_min;
+};
+
+struct sndrv_pcm_sync_ptr32 {
+ u32 flags;
+ union {
+ struct sndrv_pcm_mmap_status32 status;
+ unsigned char reserved[64];
+ } s;
+ union {
+ struct sndrv_pcm_mmap_control32 control;
+ unsigned char reserved[64];
+ } c;
+} __attribute__((packed));
+
+static int snd_pcm_ioctl_sync_ptr_compat(snd_pcm_substream_t *substream,
+ struct sndrv_pcm_sync_ptr32 __user *src)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ volatile struct sndrv_pcm_mmap_status *status;
+ volatile struct sndrv_pcm_mmap_control *control;
+ u32 sflags;
+ struct sndrv_pcm_mmap_control scontrol;
+ struct sndrv_pcm_mmap_status sstatus;
+ int err;
+
+ snd_assert(runtime, return -EINVAL);
+
+ if (get_user(sflags, &src->flags) ||
+ get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
+ get_user(scontrol.avail_min, &src->c.control.avail_min))
+ return -EFAULT;
+ if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+ err = snd_pcm_hwsync(substream);
+ if (err < 0)
+ return err;
+ }
+ status = runtime->status;
+ control = runtime->control;
+ snd_pcm_stream_lock_irq(substream);
+ if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
+ control->appl_ptr = scontrol.appl_ptr;
+ else
+ scontrol.appl_ptr = control->appl_ptr;
+ if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
+ control->avail_min = scontrol.avail_min;
+ else
+ scontrol.avail_min = control->avail_min;
+ sstatus.state = status->state;
+ sstatus.hw_ptr = status->hw_ptr;
+ sstatus.tstamp = status->tstamp;
+ sstatus.suspended_state = status->suspended_state;
+ snd_pcm_stream_unlock_irq(substream);
+ if (put_user(sstatus.state, &src->s.status.state) ||
+ put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
+ put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
+ put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
+ put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
+ put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
+ put_user(scontrol.avail_min, &src->c.control.avail_min))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+/*
+ */
+enum {
+ SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct sndrv_pcm_hw_params32),
+ SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct sndrv_pcm_hw_params32),
+ SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct sndrv_pcm_sw_params32),
+ SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct sndrv_pcm_status32),
+ SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
+ SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct sndrv_pcm_channel_info32),
+ SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
+ SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
+ SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct sndrv_xferi32),
+ SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct sndrv_xferi32),
+ SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct sndrv_xfern32),
+ SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct sndrv_xfern32),
+ SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr32),
+
+};
+
+static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ void __user *argp = compat_ptr(arg);
+
+ pcm_file = file->private_data;
+ if (! pcm_file)
+ return -ENOTTY;
+ substream = pcm_file->substream;
+ if (! substream)
+ return -ENOTTY;
+
+ /*
+ * When PCM is used on 32bit mode, we need to disable
+ * mmap of PCM status/control records because of the size
+ * incompatibility.
+ */
+ substream->no_mmap_ctrl = 1;
+
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_PVERSION:
+ case SNDRV_PCM_IOCTL_INFO:
+ case SNDRV_PCM_IOCTL_TSTAMP:
+ case SNDRV_PCM_IOCTL_HWSYNC:
+ case SNDRV_PCM_IOCTL_PREPARE:
+ case SNDRV_PCM_IOCTL_RESET:
+ case SNDRV_PCM_IOCTL_START:
+ case SNDRV_PCM_IOCTL_DROP:
+ case SNDRV_PCM_IOCTL_DRAIN:
+ case SNDRV_PCM_IOCTL_PAUSE:
+ case SNDRV_PCM_IOCTL_HW_FREE:
+ case SNDRV_PCM_IOCTL_RESUME:
+ case SNDRV_PCM_IOCTL_XRUN:
+ case SNDRV_PCM_IOCTL_LINK:
+ case SNDRV_PCM_IOCTL_UNLINK:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return snd_pcm_playback_ioctl1(substream, cmd, argp);
+ else
+ return snd_pcm_capture_ioctl1(substream, cmd, argp);
+ case SNDRV_PCM_IOCTL_HW_REFINE32:
+ return snd_pcm_ioctl_hw_params_compat(substream, 1, argp);
+ case SNDRV_PCM_IOCTL_HW_PARAMS32:
+ return snd_pcm_ioctl_hw_params_compat(substream, 0, argp);
+ case SNDRV_PCM_IOCTL_SW_PARAMS32:
+ return snd_pcm_ioctl_sw_params_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_STATUS32:
+ return snd_pcm_status_user_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_SYNC_PTR32:
+ return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
+ return snd_pcm_ioctl_channel_info_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_WRITEI_FRAMES32:
+ return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
+ case SNDRV_PCM_IOCTL_READI_FRAMES32:
+ return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
+ case SNDRV_PCM_IOCTL_WRITEN_FRAMES32:
+ return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
+ case SNDRV_PCM_IOCTL_READN_FRAMES32:
+ return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
+ case SNDRV_PCM_IOCTL_DELAY32:
+ return snd_pcm_ioctl_delay_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_REWIND32:
+ return snd_pcm_ioctl_rewind_compat(substream, argp);
+ case SNDRV_PCM_IOCTL_FORWARD32:
+ return snd_pcm_ioctl_forward_compat(substream, argp);
+ }
+
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
new file mode 100644
index 00000000000..151fd99ca2c
--- /dev/null
+++ b/sound/core/pcm_lib.c
@@ -0,0 +1,2612 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/timer.h>
+
+/*
+ * fill ring buffer with silence
+ * runtime->silence_start: starting pointer to silence area
+ * runtime->silence_filled: size filled with silence
+ * runtime->silence_threshold: threshold from application
+ * runtime->silence_size: maximal size from application
+ *
+ * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
+ */
+void snd_pcm_playback_silence(snd_pcm_substream_t *substream, snd_pcm_uframes_t new_hw_ptr)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t frames, ofs, transfer;
+
+ if (runtime->silence_size < runtime->boundary) {
+ snd_pcm_sframes_t noise_dist, n;
+ if (runtime->silence_start != runtime->control->appl_ptr) {
+ n = runtime->control->appl_ptr - runtime->silence_start;
+ if (n < 0)
+ n += runtime->boundary;
+ if ((snd_pcm_uframes_t)n < runtime->silence_filled)
+ runtime->silence_filled -= n;
+ else
+ runtime->silence_filled = 0;
+ runtime->silence_start = runtime->control->appl_ptr;
+ }
+ if (runtime->silence_filled == runtime->buffer_size)
+ return;
+ snd_assert(runtime->silence_filled <= runtime->buffer_size, return);
+ noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
+ if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold)
+ return;
+ frames = runtime->silence_threshold - noise_dist;
+ if (frames > runtime->silence_size)
+ frames = runtime->silence_size;
+ } else {
+ if (new_hw_ptr == ULONG_MAX) { /* initialization */
+ snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime);
+ runtime->silence_filled = avail > 0 ? avail : 0;
+ runtime->silence_start = (runtime->status->hw_ptr +
+ runtime->silence_filled) %
+ runtime->boundary;
+ } else {
+ ofs = runtime->status->hw_ptr;
+ frames = new_hw_ptr - ofs;
+ if ((snd_pcm_sframes_t)frames < 0)
+ frames += runtime->boundary;
+ runtime->silence_filled -= frames;
+ if ((snd_pcm_sframes_t)runtime->silence_filled < 0) {
+ runtime->silence_filled = 0;
+ runtime->silence_start = (ofs + frames) - runtime->buffer_size;
+ } else {
+ runtime->silence_start = ofs - runtime->silence_filled;
+ }
+ if ((snd_pcm_sframes_t)runtime->silence_start < 0)
+ runtime->silence_start += runtime->boundary;
+ }
+ frames = runtime->buffer_size - runtime->silence_filled;
+ }
+ snd_assert(frames <= runtime->buffer_size, return);
+ if (frames == 0)
+ return;
+ ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size;
+ while (frames > 0) {
+ transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
+ if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ if (substream->ops->silence) {
+ int err;
+ err = substream->ops->silence(substream, -1, ofs, transfer);
+ snd_assert(err >= 0, );
+ } else {
+ char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, ofs);
+ snd_pcm_format_set_silence(runtime->format, hwbuf, transfer * runtime->channels);
+ }
+ } else {
+ unsigned int c;
+ unsigned int channels = runtime->channels;
+ if (substream->ops->silence) {
+ for (c = 0; c < channels; ++c) {
+ int err;
+ err = substream->ops->silence(substream, c, ofs, transfer);
+ snd_assert(err >= 0, );
+ }
+ } else {
+ size_t dma_csize = runtime->dma_bytes / channels;
+ for (c = 0; c < channels; ++c) {
+ char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, ofs);
+ snd_pcm_format_set_silence(runtime->format, hwbuf, transfer);
+ }
+ }
+ }
+ runtime->silence_filled += transfer;
+ frames -= transfer;
+ ofs = 0;
+ }
+}
+
+static void xrun(snd_pcm_substream_t *substream)
+{
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+#ifdef CONFIG_SND_DEBUG
+ if (substream->pstr->xrun_debug) {
+ snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n",
+ substream->pcm->card->number,
+ substream->pcm->device,
+ substream->stream ? 'c' : 'p');
+ if (substream->pstr->xrun_debug > 1)
+ dump_stack();
+ }
+#endif
+}
+
+static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(snd_pcm_substream_t *substream,
+ snd_pcm_runtime_t *runtime)
+{
+ snd_pcm_uframes_t pos;
+
+ pos = substream->ops->pointer(substream);
+ if (pos == SNDRV_PCM_POS_XRUN)
+ return pos; /* XRUN */
+ if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP)
+ snd_timestamp_now((snd_timestamp_t*)&runtime->status->tstamp, runtime->tstamp_timespec);
+#ifdef CONFIG_SND_DEBUG
+ if (pos >= runtime->buffer_size) {
+ snd_printk(KERN_ERR "BUG: stream = %i, pos = 0x%lx, buffer size = 0x%lx, period size = 0x%lx\n", substream->stream, pos, runtime->buffer_size, runtime->period_size);
+ } else
+#endif
+ snd_runtime_check(pos < runtime->buffer_size, return 0);
+ pos -= pos % runtime->min_align;
+ return pos;
+}
+
+static inline int snd_pcm_update_hw_ptr_post(snd_pcm_substream_t *substream,
+ snd_pcm_runtime_t *runtime)
+{
+ snd_pcm_uframes_t avail;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ avail = snd_pcm_playback_avail(runtime);
+ else
+ avail = snd_pcm_capture_avail(runtime);
+ if (avail > runtime->avail_max)
+ runtime->avail_max = avail;
+ if (avail >= runtime->stop_threshold) {
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+ snd_pcm_drain_done(substream);
+ else
+ xrun(substream);
+ return -EPIPE;
+ }
+ if (avail >= runtime->control->avail_min)
+ wake_up(&runtime->sleep);
+ return 0;
+}
+
+static inline int snd_pcm_update_hw_ptr_interrupt(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t pos;
+ snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt;
+ snd_pcm_sframes_t delta;
+
+ pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
+ if (pos == SNDRV_PCM_POS_XRUN) {
+ xrun(substream);
+ return -EPIPE;
+ }
+ if (runtime->period_size == runtime->buffer_size)
+ goto __next_buf;
+ new_hw_ptr = runtime->hw_ptr_base + pos;
+ hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size;
+
+ delta = hw_ptr_interrupt - new_hw_ptr;
+ if (delta > 0) {
+ if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
+#ifdef CONFIG_SND_DEBUG
+ if (runtime->periods > 1 && substream->pstr->xrun_debug) {
+ snd_printd(KERN_ERR "Unexpected hw_pointer value [1] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
+ if (substream->pstr->xrun_debug > 1)
+ dump_stack();
+ }
+#endif
+ return 0;
+ }
+ __next_buf:
+ runtime->hw_ptr_base += runtime->buffer_size;
+ if (runtime->hw_ptr_base == runtime->boundary)
+ runtime->hw_ptr_base = 0;
+ new_hw_ptr = runtime->hw_ptr_base + pos;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, new_hw_ptr);
+
+ runtime->status->hw_ptr = new_hw_ptr;
+ runtime->hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size;
+
+ return snd_pcm_update_hw_ptr_post(substream, runtime);
+}
+
+/* CAUTION: call it with irq disabled */
+int snd_pcm_update_hw_ptr(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t pos;
+ snd_pcm_uframes_t old_hw_ptr, new_hw_ptr;
+ snd_pcm_sframes_t delta;
+
+ old_hw_ptr = runtime->status->hw_ptr;
+ pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
+ if (pos == SNDRV_PCM_POS_XRUN) {
+ xrun(substream);
+ return -EPIPE;
+ }
+ new_hw_ptr = runtime->hw_ptr_base + pos;
+
+ delta = old_hw_ptr - new_hw_ptr;
+ if (delta > 0) {
+ if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) {
+#ifdef CONFIG_SND_DEBUG
+ if (runtime->periods > 2 && substream->pstr->xrun_debug) {
+ snd_printd(KERN_ERR "Unexpected hw_pointer value [2] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2);
+ if (substream->pstr->xrun_debug > 1)
+ dump_stack();
+ }
+#endif
+ return 0;
+ }
+ runtime->hw_ptr_base += runtime->buffer_size;
+ if (runtime->hw_ptr_base == runtime->boundary)
+ runtime->hw_ptr_base = 0;
+ new_hw_ptr = runtime->hw_ptr_base + pos;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, new_hw_ptr);
+
+ runtime->status->hw_ptr = new_hw_ptr;
+
+ return snd_pcm_update_hw_ptr_post(substream, runtime);
+}
+
+/**
+ * snd_pcm_set_ops - set the PCM operators
+ * @pcm: the pcm instance
+ * @direction: stream direction, SNDRV_PCM_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the given PCM operators to the pcm instance.
+ */
+void snd_pcm_set_ops(snd_pcm_t *pcm, int direction, snd_pcm_ops_t *ops)
+{
+ snd_pcm_str_t *stream = &pcm->streams[direction];
+ snd_pcm_substream_t *substream;
+
+ for (substream = stream->substream; substream != NULL; substream = substream->next)
+ substream->ops = ops;
+}
+
+
+/**
+ * snd_pcm_sync - set the PCM sync id
+ * @substream: the pcm substream
+ *
+ * Sets the PCM sync identifier for the card.
+ */
+void snd_pcm_set_sync(snd_pcm_substream_t * substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ runtime->sync.id32[0] = substream->pcm->card->number;
+ runtime->sync.id32[1] = -1;
+ runtime->sync.id32[2] = -1;
+ runtime->sync.id32[3] = -1;
+}
+
+/*
+ * Standard ioctl routine
+ */
+
+/* Code taken from alsa-lib */
+#define assert(a) snd_assert((a), return -EINVAL)
+
+static inline unsigned int div32(unsigned int a, unsigned int b,
+ unsigned int *r)
+{
+ if (b == 0) {
+ *r = 0;
+ return UINT_MAX;
+ }
+ *r = a % b;
+ return a / b;
+}
+
+static inline unsigned int div_down(unsigned int a, unsigned int b)
+{
+ if (b == 0)
+ return UINT_MAX;
+ return a / b;
+}
+
+static inline unsigned int div_up(unsigned int a, unsigned int b)
+{
+ unsigned int r;
+ unsigned int q;
+ if (b == 0)
+ return UINT_MAX;
+ q = div32(a, b, &r);
+ if (r)
+ ++q;
+ return q;
+}
+
+static inline unsigned int mul(unsigned int a, unsigned int b)
+{
+ if (a == 0)
+ return 0;
+ if (div_down(UINT_MAX, a) < b)
+ return UINT_MAX;
+ return a * b;
+}
+
+static inline unsigned int muldiv32(unsigned int a, unsigned int b,
+ unsigned int c, unsigned int *r)
+{
+ u_int64_t n = (u_int64_t) a * b;
+ if (c == 0) {
+ snd_assert(n > 0, );
+ *r = 0;
+ return UINT_MAX;
+ }
+ div64_32(&n, c, r);
+ if (n >= UINT_MAX) {
+ *r = 0;
+ return UINT_MAX;
+ }
+ return n;
+}
+
+static int snd_interval_refine_min(snd_interval_t *i, unsigned int min, int openmin)
+{
+ int changed = 0;
+ assert(!snd_interval_empty(i));
+ if (i->min < min) {
+ i->min = min;
+ i->openmin = openmin;
+ changed = 1;
+ } else if (i->min == min && !i->openmin && openmin) {
+ i->openmin = 1;
+ changed = 1;
+ }
+ if (i->integer) {
+ if (i->openmin) {
+ i->min++;
+ i->openmin = 0;
+ }
+ }
+ if (snd_interval_checkempty(i)) {
+ snd_interval_none(i);
+ return -EINVAL;
+ }
+ return changed;
+}
+
+static int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax)
+{
+ int changed = 0;
+ assert(!snd_interval_empty(i));
+ if (i->max > max) {
+ i->max = max;
+ i->openmax = openmax;
+ changed = 1;
+ } else if (i->max == max && !i->openmax && openmax) {
+ i->openmax = 1;
+ changed = 1;
+ }
+ if (i->integer) {
+ if (i->openmax) {
+ i->max--;
+ i->openmax = 0;
+ }
+ }
+ if (snd_interval_checkempty(i)) {
+ snd_interval_none(i);
+ return -EINVAL;
+ }
+ return changed;
+}
+
+/**
+ * snd_interval_refine - refine the interval value of configurator
+ * @i: the interval value to refine
+ * @v: the interval value to refer to
+ *
+ * Refines the interval value with the reference value.
+ * The interval is changed to the range satisfying both intervals.
+ * The interval status (min, max, integer, etc.) are evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v)
+{
+ int changed = 0;
+ assert(!snd_interval_empty(i));
+ if (i->min < v->min) {
+ i->min = v->min;
+ i->openmin = v->openmin;
+ changed = 1;
+ } else if (i->min == v->min && !i->openmin && v->openmin) {
+ i->openmin = 1;
+ changed = 1;
+ }
+ if (i->max > v->max) {
+ i->max = v->max;
+ i->openmax = v->openmax;
+ changed = 1;
+ } else if (i->max == v->max && !i->openmax && v->openmax) {
+ i->openmax = 1;
+ changed = 1;
+ }
+ if (!i->integer && v->integer) {
+ i->integer = 1;
+ changed = 1;
+ }
+ if (i->integer) {
+ if (i->openmin) {
+ i->min++;
+ i->openmin = 0;
+ }
+ if (i->openmax) {
+ i->max--;
+ i->openmax = 0;
+ }
+ } else if (!i->openmin && !i->openmax && i->min == i->max)
+ i->integer = 1;
+ if (snd_interval_checkempty(i)) {
+ snd_interval_none(i);
+ return -EINVAL;
+ }
+ return changed;
+}
+
+static int snd_interval_refine_first(snd_interval_t *i)
+{
+ assert(!snd_interval_empty(i));
+ if (snd_interval_single(i))
+ return 0;
+ i->max = i->min;
+ i->openmax = i->openmin;
+ if (i->openmax)
+ i->max++;
+ return 1;
+}
+
+static int snd_interval_refine_last(snd_interval_t *i)
+{
+ assert(!snd_interval_empty(i));
+ if (snd_interval_single(i))
+ return 0;
+ i->min = i->max;
+ i->openmin = i->openmax;
+ if (i->openmin)
+ i->min--;
+ return 1;
+}
+
+static int snd_interval_refine_set(snd_interval_t *i, unsigned int val)
+{
+ snd_interval_t t;
+ t.empty = 0;
+ t.min = t.max = val;
+ t.openmin = t.openmax = 0;
+ t.integer = 1;
+ return snd_interval_refine(i, &t);
+}
+
+void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+ if (a->empty || b->empty) {
+ snd_interval_none(c);
+ return;
+ }
+ c->empty = 0;
+ c->min = mul(a->min, b->min);
+ c->openmin = (a->openmin || b->openmin);
+ c->max = mul(a->max, b->max);
+ c->openmax = (a->openmax || b->openmax);
+ c->integer = (a->integer && b->integer);
+}
+
+/**
+ * snd_interval_div - refine the interval value with division
+ *
+ * c = a / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+ unsigned int r;
+ if (a->empty || b->empty) {
+ snd_interval_none(c);
+ return;
+ }
+ c->empty = 0;
+ c->min = div32(a->min, b->max, &r);
+ c->openmin = (r || a->openmin || b->openmax);
+ if (b->min > 0) {
+ c->max = div32(a->max, b->min, &r);
+ if (r) {
+ c->max++;
+ c->openmax = 1;
+ } else
+ c->openmax = (a->openmax || b->openmin);
+ } else {
+ c->max = UINT_MAX;
+ c->openmax = 0;
+ }
+ c->integer = 0;
+}
+
+/**
+ * snd_interval_muldivk - refine the interval value
+ *
+ * c = a * b / k
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b,
+ unsigned int k, snd_interval_t *c)
+{
+ unsigned int r;
+ if (a->empty || b->empty) {
+ snd_interval_none(c);
+ return;
+ }
+ c->empty = 0;
+ c->min = muldiv32(a->min, b->min, k, &r);
+ c->openmin = (r || a->openmin || b->openmin);
+ c->max = muldiv32(a->max, b->max, k, &r);
+ if (r) {
+ c->max++;
+ c->openmax = 1;
+ } else
+ c->openmax = (a->openmax || b->openmax);
+ c->integer = 0;
+}
+
+/**
+ * snd_interval_mulkdiv - refine the interval value
+ *
+ * c = a * k / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
+ const snd_interval_t *b, snd_interval_t *c)
+{
+ unsigned int r;
+ if (a->empty || b->empty) {
+ snd_interval_none(c);
+ return;
+ }
+ c->empty = 0;
+ c->min = muldiv32(a->min, k, b->max, &r);
+ c->openmin = (r || a->openmin || b->openmax);
+ if (b->min > 0) {
+ c->max = muldiv32(a->max, k, b->min, &r);
+ if (r) {
+ c->max++;
+ c->openmax = 1;
+ } else
+ c->openmax = (a->openmax || b->openmin);
+ } else {
+ c->max = UINT_MAX;
+ c->openmax = 0;
+ }
+ c->integer = 0;
+}
+
+#undef assert
+/* ---- */
+
+
+/**
+ * snd_interval_ratnum - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_ratnum(snd_interval_t *i,
+ unsigned int rats_count, ratnum_t *rats,
+ unsigned int *nump, unsigned int *denp)
+{
+ unsigned int best_num, best_diff, best_den;
+ unsigned int k;
+ snd_interval_t t;
+ int err;
+
+ best_num = best_den = best_diff = 0;
+ for (k = 0; k < rats_count; ++k) {
+ unsigned int num = rats[k].num;
+ unsigned int den;
+ unsigned int q = i->min;
+ int diff;
+ if (q == 0)
+ q = 1;
+ den = div_down(num, q);
+ if (den < rats[k].den_min)
+ continue;
+ if (den > rats[k].den_max)
+ den = rats[k].den_max;
+ else {
+ unsigned int r;
+ r = (den - rats[k].den_min) % rats[k].den_step;
+ if (r != 0)
+ den -= r;
+ }
+ diff = num - q * den;
+ if (best_num == 0 ||
+ diff * best_den < best_diff * den) {
+ best_diff = diff;
+ best_den = den;
+ best_num = num;
+ }
+ }
+ if (best_den == 0) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ t.min = div_down(best_num, best_den);
+ t.openmin = !!(best_num % best_den);
+
+ best_num = best_den = best_diff = 0;
+ for (k = 0; k < rats_count; ++k) {
+ unsigned int num = rats[k].num;
+ unsigned int den;
+ unsigned int q = i->max;
+ int diff;
+ if (q == 0) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ den = div_up(num, q);
+ if (den > rats[k].den_max)
+ continue;
+ if (den < rats[k].den_min)
+ den = rats[k].den_min;
+ else {
+ unsigned int r;
+ r = (den - rats[k].den_min) % rats[k].den_step;
+ if (r != 0)
+ den += rats[k].den_step - r;
+ }
+ diff = q * den - num;
+ if (best_num == 0 ||
+ diff * best_den < best_diff * den) {
+ best_diff = diff;
+ best_den = den;
+ best_num = num;
+ }
+ }
+ if (best_den == 0) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ t.max = div_up(best_num, best_den);
+ t.openmax = !!(best_num % best_den);
+ t.integer = 0;
+ err = snd_interval_refine(i, &t);
+ if (err < 0)
+ return err;
+
+ if (snd_interval_single(i)) {
+ if (nump)
+ *nump = best_num;
+ if (denp)
+ *denp = best_den;
+ }
+ return err;
+}
+
+/**
+ * snd_interval_ratden - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+static int snd_interval_ratden(snd_interval_t *i,
+ unsigned int rats_count, ratden_t *rats,
+ unsigned int *nump, unsigned int *denp)
+{
+ unsigned int best_num, best_diff, best_den;
+ unsigned int k;
+ snd_interval_t t;
+ int err;
+
+ best_num = best_den = best_diff = 0;
+ for (k = 0; k < rats_count; ++k) {
+ unsigned int num;
+ unsigned int den = rats[k].den;
+ unsigned int q = i->min;
+ int diff;
+ num = mul(q, den);
+ if (num > rats[k].num_max)
+ continue;
+ if (num < rats[k].num_min)
+ num = rats[k].num_max;
+ else {
+ unsigned int r;
+ r = (num - rats[k].num_min) % rats[k].num_step;
+ if (r != 0)
+ num += rats[k].num_step - r;
+ }
+ diff = num - q * den;
+ if (best_num == 0 ||
+ diff * best_den < best_diff * den) {
+ best_diff = diff;
+ best_den = den;
+ best_num = num;
+ }
+ }
+ if (best_den == 0) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ t.min = div_down(best_num, best_den);
+ t.openmin = !!(best_num % best_den);
+
+ best_num = best_den = best_diff = 0;
+ for (k = 0; k < rats_count; ++k) {
+ unsigned int num;
+ unsigned int den = rats[k].den;
+ unsigned int q = i->max;
+ int diff;
+ num = mul(q, den);
+ if (num < rats[k].num_min)
+ continue;
+ if (num > rats[k].num_max)
+ num = rats[k].num_max;
+ else {
+ unsigned int r;
+ r = (num - rats[k].num_min) % rats[k].num_step;
+ if (r != 0)
+ num -= r;
+ }
+ diff = q * den - num;
+ if (best_num == 0 ||
+ diff * best_den < best_diff * den) {
+ best_diff = diff;
+ best_den = den;
+ best_num = num;
+ }
+ }
+ if (best_den == 0) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ t.max = div_up(best_num, best_den);
+ t.openmax = !!(best_num % best_den);
+ t.integer = 0;
+ err = snd_interval_refine(i, &t);
+ if (err < 0)
+ return err;
+
+ if (snd_interval_single(i)) {
+ if (nump)
+ *nump = best_num;
+ if (denp)
+ *denp = best_den;
+ }
+ return err;
+}
+
+/**
+ * snd_interval_list - refine the interval value from the list
+ * @i: the interval value to refine
+ * @count: the number of elements in the list
+ * @list: the value list
+ * @mask: the bit-mask to evaluate
+ *
+ * Refines the interval value from the list.
+ * When mask is non-zero, only the elements corresponding to bit 1 are
+ * evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_list(snd_interval_t *i, unsigned int count, unsigned int *list, unsigned int mask)
+{
+ unsigned int k;
+ int changed = 0;
+ for (k = 0; k < count; k++) {
+ if (mask && !(mask & (1 << k)))
+ continue;
+ if (i->min == list[k] && !i->openmin)
+ goto _l1;
+ if (i->min < list[k]) {
+ i->min = list[k];
+ i->openmin = 0;
+ changed = 1;
+ goto _l1;
+ }
+ }
+ i->empty = 1;
+ return -EINVAL;
+ _l1:
+ for (k = count; k-- > 0;) {
+ if (mask && !(mask & (1 << k)))
+ continue;
+ if (i->max == list[k] && !i->openmax)
+ goto _l2;
+ if (i->max > list[k]) {
+ i->max = list[k];
+ i->openmax = 0;
+ changed = 1;
+ goto _l2;
+ }
+ }
+ i->empty = 1;
+ return -EINVAL;
+ _l2:
+ if (snd_interval_checkempty(i)) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ return changed;
+}
+
+static int snd_interval_step(snd_interval_t *i, unsigned int min, unsigned int step)
+{
+ unsigned int n;
+ int changed = 0;
+ n = (i->min - min) % step;
+ if (n != 0 || i->openmin) {
+ i->min += step - n;
+ changed = 1;
+ }
+ n = (i->max - min) % step;
+ if (n != 0 || i->openmax) {
+ i->max -= n;
+ changed = 1;
+ }
+ if (snd_interval_checkempty(i)) {
+ i->empty = 1;
+ return -EINVAL;
+ }
+ return changed;
+}
+
+/* Info constraints helpers */
+
+/**
+ * snd_pcm_hw_rule_add - add the hw-constraint rule
+ * @runtime: the pcm runtime instance
+ * @cond: condition bits
+ * @var: the variable to evaluate
+ * @func: the evaluation function
+ * @private: the private data pointer passed to function
+ * @dep: the dependent variables
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_hw_rule_add(snd_pcm_runtime_t *runtime, unsigned int cond,
+ int var,
+ snd_pcm_hw_rule_func_t func, void *private,
+ int dep, ...)
+{
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ snd_pcm_hw_rule_t *c;
+ unsigned int k;
+ va_list args;
+ va_start(args, dep);
+ if (constrs->rules_num >= constrs->rules_all) {
+ snd_pcm_hw_rule_t *new;
+ unsigned int new_rules = constrs->rules_all + 16;
+ new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+ if (constrs->rules) {
+ memcpy(new, constrs->rules,
+ constrs->rules_num * sizeof(*c));
+ kfree(constrs->rules);
+ }
+ constrs->rules = new;
+ constrs->rules_all = new_rules;
+ }
+ c = &constrs->rules[constrs->rules_num];
+ c->cond = cond;
+ c->func = func;
+ c->var = var;
+ c->private = private;
+ k = 0;
+ while (1) {
+ snd_assert(k < ARRAY_SIZE(c->deps), return -EINVAL);
+ c->deps[k++] = dep;
+ if (dep < 0)
+ break;
+ dep = va_arg(args, int);
+ }
+ constrs->rules_num++;
+ va_end(args);
+ return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_mask
+ */
+int snd_pcm_hw_constraint_mask(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+ u_int32_t mask)
+{
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ snd_mask_t *maskp = constrs_mask(constrs, var);
+ *maskp->bits &= mask;
+ memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */
+ if (*maskp->bits == 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_mask64
+ */
+int snd_pcm_hw_constraint_mask64(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+ u_int64_t mask)
+{
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ snd_mask_t *maskp = constrs_mask(constrs, var);
+ maskp->bits[0] &= (u_int32_t)mask;
+ maskp->bits[1] &= (u_int32_t)(mask >> 32);
+ memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
+ if (! maskp->bits[0] && ! maskp->bits[1])
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_integer
+ */
+int snd_pcm_hw_constraint_integer(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var)
+{
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ return snd_interval_setinteger(constrs_interval(constrs, var));
+}
+
+/**
+ * snd_pcm_hw_constraint_minmax
+ */
+int snd_pcm_hw_constraint_minmax(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
+ unsigned int min, unsigned int max)
+{
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ snd_interval_t t;
+ t.min = min;
+ t.max = max;
+ t.openmin = t.openmax = 0;
+ t.integer = 0;
+ return snd_interval_refine(constrs_interval(constrs, var), &t);
+}
+
+static int snd_pcm_hw_rule_list(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_pcm_hw_constraint_list_t *list = rule->private;
+ return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask);
+}
+
+
+/**
+ * snd_pcm_hw_constraint_list
+ */
+int snd_pcm_hw_constraint_list(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ snd_pcm_hw_param_t var,
+ snd_pcm_hw_constraint_list_t *l)
+{
+ return snd_pcm_hw_rule_add(runtime, cond, var,
+ snd_pcm_hw_rule_list, l,
+ var, -1);
+}
+
+static int snd_pcm_hw_rule_ratnums(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_pcm_hw_constraint_ratnums_t *r = rule->private;
+ unsigned int num = 0, den = 0;
+ int err;
+ err = snd_interval_ratnum(hw_param_interval(params, rule->var),
+ r->nrats, r->rats, &num, &den);
+ if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
+ params->rate_num = num;
+ params->rate_den = den;
+ }
+ return err;
+}
+
+/**
+ * snd_pcm_hw_constraint_ratnums
+ */
+int snd_pcm_hw_constraint_ratnums(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ snd_pcm_hw_param_t var,
+ snd_pcm_hw_constraint_ratnums_t *r)
+{
+ return snd_pcm_hw_rule_add(runtime, cond, var,
+ snd_pcm_hw_rule_ratnums, r,
+ var, -1);
+}
+
+static int snd_pcm_hw_rule_ratdens(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_pcm_hw_constraint_ratdens_t *r = rule->private;
+ unsigned int num = 0, den = 0;
+ int err = snd_interval_ratden(hw_param_interval(params, rule->var),
+ r->nrats, r->rats, &num, &den);
+ if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) {
+ params->rate_num = num;
+ params->rate_den = den;
+ }
+ return err;
+}
+
+/**
+ * snd_pcm_hw_constraint_ratdens
+ */
+int snd_pcm_hw_constraint_ratdens(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ snd_pcm_hw_param_t var,
+ snd_pcm_hw_constraint_ratdens_t *r)
+{
+ return snd_pcm_hw_rule_add(runtime, cond, var,
+ snd_pcm_hw_rule_ratdens, r,
+ var, -1);
+}
+
+static int snd_pcm_hw_rule_msbits(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ unsigned int l = (unsigned long) rule->private;
+ int width = l & 0xffff;
+ unsigned int msbits = l >> 16;
+ snd_interval_t *i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+ if (snd_interval_single(i) && snd_interval_value(i) == width)
+ params->msbits = msbits;
+ return 0;
+}
+
+/**
+ * snd_pcm_hw_constraint_msbits
+ */
+int snd_pcm_hw_constraint_msbits(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ unsigned int width,
+ unsigned int msbits)
+{
+ unsigned long l = (msbits << 16) | width;
+ return snd_pcm_hw_rule_add(runtime, cond, -1,
+ snd_pcm_hw_rule_msbits,
+ (void*) l,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+}
+
+static int snd_pcm_hw_rule_step(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ unsigned long step = (unsigned long) rule->private;
+ return snd_interval_step(hw_param_interval(params, rule->var), 0, step);
+}
+
+/**
+ * snd_pcm_hw_constraint_step
+ */
+int snd_pcm_hw_constraint_step(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ snd_pcm_hw_param_t var,
+ unsigned long step)
+{
+ return snd_pcm_hw_rule_add(runtime, cond, var,
+ snd_pcm_hw_rule_step, (void *) step,
+ var, -1);
+}
+
+static int snd_pcm_hw_rule_pow2(snd_pcm_hw_params_t *params, snd_pcm_hw_rule_t *rule)
+{
+ static int pow2_sizes[] = {
+ 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7,
+ 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
+ 1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
+ 1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30
+ };
+ return snd_interval_list(hw_param_interval(params, rule->var),
+ ARRAY_SIZE(pow2_sizes), pow2_sizes, 0);
+}
+
+/**
+ * snd_pcm_hw_constraint_pow2
+ */
+int snd_pcm_hw_constraint_pow2(snd_pcm_runtime_t *runtime,
+ unsigned int cond,
+ snd_pcm_hw_param_t var)
+{
+ return snd_pcm_hw_rule_add(runtime, cond, var,
+ snd_pcm_hw_rule_pow2, NULL,
+ var, -1);
+}
+
+/* To use the same code we have in alsa-lib */
+#define snd_pcm_t snd_pcm_substream_t
+#define assert(i) snd_assert((i), return -EINVAL)
+#ifndef INT_MIN
+#define INT_MIN ((int)((unsigned int)INT_MAX+1))
+#endif
+
+void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var)
+{
+ if (hw_is_mask(var)) {
+ snd_mask_any(hw_param_mask(params, var));
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ return;
+ }
+ if (hw_is_interval(var)) {
+ snd_interval_any(hw_param_interval(params, var));
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ return;
+ }
+ snd_BUG();
+}
+
+/**
+ * snd_pcm_hw_param_any
+ */
+int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ _snd_pcm_hw_param_any(params, var);
+ return snd_pcm_hw_refine(pcm, params);
+}
+
+void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params)
+{
+ unsigned int k;
+ memset(params, 0, sizeof(*params));
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++)
+ _snd_pcm_hw_param_any(params, k);
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
+ _snd_pcm_hw_param_any(params, k);
+ params->info = ~0U;
+}
+
+/**
+ * snd_pcm_hw_params_any
+ *
+ * Fill PARAMS with full configuration space boundaries
+ */
+int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ _snd_pcm_hw_params_any(params);
+ return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * snd_pcm_hw_param_value
+ *
+ * Return the value for field PAR if it's fixed in configuration space
+ * defined by PARAMS. Return -EINVAL otherwise
+ */
+int snd_pcm_hw_param_value(const snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, int *dir)
+{
+ if (hw_is_mask(var)) {
+ const snd_mask_t *mask = hw_param_mask_c(params, var);
+ if (!snd_mask_single(mask))
+ return -EINVAL;
+ if (dir)
+ *dir = 0;
+ return snd_mask_value(mask);
+ }
+ if (hw_is_interval(var)) {
+ const snd_interval_t *i = hw_param_interval_c(params, var);
+ if (!snd_interval_single(i))
+ return -EINVAL;
+ if (dir)
+ *dir = i->openmin;
+ return snd_interval_value(i);
+ }
+ assert(0);
+ return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_min
+ *
+ * Return the minimum value for field PAR.
+ */
+unsigned int snd_pcm_hw_param_value_min(const snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, int *dir)
+{
+ if (hw_is_mask(var)) {
+ if (dir)
+ *dir = 0;
+ return snd_mask_min(hw_param_mask_c(params, var));
+ }
+ if (hw_is_interval(var)) {
+ const snd_interval_t *i = hw_param_interval_c(params, var);
+ if (dir)
+ *dir = i->openmin;
+ return snd_interval_min(i);
+ }
+ assert(0);
+ return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_max
+ *
+ * Return the maximum value for field PAR.
+ */
+unsigned int snd_pcm_hw_param_value_max(const snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, int *dir)
+{
+ if (hw_is_mask(var)) {
+ if (dir)
+ *dir = 0;
+ return snd_mask_max(hw_param_mask_c(params, var));
+ }
+ if (hw_is_interval(var)) {
+ const snd_interval_t *i = hw_param_interval_c(params, var);
+ if (dir)
+ *dir = - (int) i->openmax;
+ return snd_interval_max(i);
+ }
+ assert(0);
+ return -EINVAL;
+}
+
+void _snd_pcm_hw_param_setempty(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ if (hw_is_mask(var)) {
+ snd_mask_none(hw_param_mask(params, var));
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ } else if (hw_is_interval(var)) {
+ snd_interval_none(hw_param_interval(params, var));
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ } else {
+ snd_BUG();
+ }
+}
+
+int _snd_pcm_hw_param_setinteger(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ int changed;
+ assert(hw_is_interval(var));
+ changed = snd_interval_setinteger(hw_param_interval(params, var));
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+/**
+ * snd_pcm_hw_param_setinteger
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * non integer values. Reduce configuration space accordingly.
+ * Return -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_setinteger(snd_pcm_t *pcm,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ int changed = _snd_pcm_hw_param_setinteger(params, var);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+int _snd_pcm_hw_param_first(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ int changed;
+ if (hw_is_mask(var))
+ changed = snd_mask_refine_first(hw_param_mask(params, var));
+ else if (hw_is_interval(var))
+ changed = snd_interval_refine_first(hw_param_interval(params, var));
+ else {
+ assert(0);
+ return -EINVAL;
+ }
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_first
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values > minimum. Reduce configuration space accordingly.
+ * Return the minimum.
+ */
+int snd_pcm_hw_param_first(snd_pcm_t *pcm,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, int *dir)
+{
+ int changed = _snd_pcm_hw_param_first(params, var);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ assert(err >= 0);
+ }
+ return snd_pcm_hw_param_value(params, var, dir);
+}
+
+int _snd_pcm_hw_param_last(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var)
+{
+ int changed;
+ if (hw_is_mask(var))
+ changed = snd_mask_refine_last(hw_param_mask(params, var));
+ else if (hw_is_interval(var))
+ changed = snd_interval_refine_last(hw_param_interval(params, var));
+ else {
+ assert(0);
+ return -EINVAL;
+ }
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_last
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values < maximum. Reduce configuration space accordingly.
+ * Return the maximum.
+ */
+int snd_pcm_hw_param_last(snd_pcm_t *pcm,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, int *dir)
+{
+ int changed = _snd_pcm_hw_param_last(params, var);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ assert(err >= 0);
+ }
+ return snd_pcm_hw_param_value(params, var, dir);
+}
+
+int _snd_pcm_hw_param_min(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+ int changed;
+ int open = 0;
+ if (dir) {
+ if (dir > 0) {
+ open = 1;
+ } else if (dir < 0) {
+ if (val > 0) {
+ open = 1;
+ val--;
+ }
+ }
+ }
+ if (hw_is_mask(var))
+ changed = snd_mask_refine_min(hw_param_mask(params, var), val + !!open);
+ else if (hw_is_interval(var))
+ changed = snd_interval_refine_min(hw_param_interval(params, var), val, open);
+ else {
+ assert(0);
+ return -EINVAL;
+ }
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+/**
+ * snd_pcm_hw_param_min
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values < VAL. Reduce configuration space accordingly.
+ * Return new minimum or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int *dir)
+{
+ int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ if (err < 0)
+ return err;
+ }
+ return snd_pcm_hw_param_value_min(params, var, dir);
+}
+
+int _snd_pcm_hw_param_max(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+ int changed;
+ int open = 0;
+ if (dir) {
+ if (dir < 0) {
+ open = 1;
+ } else if (dir > 0) {
+ open = 1;
+ val++;
+ }
+ }
+ if (hw_is_mask(var)) {
+ if (val == 0 && open) {
+ snd_mask_none(hw_param_mask(params, var));
+ changed = -EINVAL;
+ } else
+ changed = snd_mask_refine_max(hw_param_mask(params, var), val - !!open);
+ } else if (hw_is_interval(var))
+ changed = snd_interval_refine_max(hw_param_interval(params, var), val, open);
+ else {
+ assert(0);
+ return -EINVAL;
+ }
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+/**
+ * snd_pcm_hw_param_max
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values >= VAL + 1. Reduce configuration space accordingly.
+ * Return new maximum or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int *dir)
+{
+ int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ if (err < 0)
+ return err;
+ }
+ return snd_pcm_hw_param_value_max(params, var, dir);
+}
+
+int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+ int changed;
+ if (hw_is_mask(var)) {
+ snd_mask_t *m = hw_param_mask(params, var);
+ if (val == 0 && dir < 0) {
+ changed = -EINVAL;
+ snd_mask_none(m);
+ } else {
+ if (dir > 0)
+ val++;
+ else if (dir < 0)
+ val--;
+ changed = snd_mask_refine_set(hw_param_mask(params, var), val);
+ }
+ } else if (hw_is_interval(var)) {
+ snd_interval_t *i = hw_param_interval(params, var);
+ if (val == 0 && dir < 0) {
+ changed = -EINVAL;
+ snd_interval_none(i);
+ } else if (dir == 0)
+ changed = snd_interval_refine_set(i, val);
+ else {
+ snd_interval_t t;
+ t.openmin = 1;
+ t.openmax = 1;
+ t.empty = 0;
+ t.integer = 0;
+ if (dir < 0) {
+ t.min = val - 1;
+ t.max = val;
+ } else {
+ t.min = val;
+ t.max = val+1;
+ }
+ changed = snd_interval_refine(i, &t);
+ }
+ } else {
+ assert(0);
+ return -EINVAL;
+ }
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+/**
+ * snd_pcm_hw_param_set
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values != VAL. Reduce configuration space accordingly.
+ * Return VAL or -EINVAL if the configuration space is empty
+ */
+int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+ int changed = _snd_pcm_hw_param_set(params, var, val, dir);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ if (err < 0)
+ return err;
+ }
+ return snd_pcm_hw_param_value(params, var, NULL);
+}
+
+int _snd_pcm_hw_param_mask(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+ int changed;
+ assert(hw_is_mask(var));
+ changed = snd_mask_refine(hw_param_mask(params, var), val);
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+
+/**
+ * snd_pcm_hw_param_mask
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all values
+ * not contained in MASK. Reduce configuration space accordingly.
+ * This function can be called only for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return 0 on success or -EINVAL
+ * if the configuration space is empty
+ */
+int snd_pcm_hw_param_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+ int changed = _snd_pcm_hw_param_mask(params, var, val);
+ if (changed < 0)
+ return changed;
+ if (params->rmask) {
+ int err = snd_pcm_hw_refine(pcm, params);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int boundary_sub(int a, int adir,
+ int b, int bdir,
+ int *c, int *cdir)
+{
+ adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0);
+ bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0);
+ *c = a - b;
+ *cdir = adir - bdir;
+ if (*cdir == -2) {
+ assert(*c > INT_MIN);
+ (*c)--;
+ } else if (*cdir == 2) {
+ assert(*c < INT_MAX);
+ (*c)++;
+ }
+ return 0;
+}
+
+static int boundary_lt(unsigned int a, int adir,
+ unsigned int b, int bdir)
+{
+ assert(a > 0 || adir >= 0);
+ assert(b > 0 || bdir >= 0);
+ if (adir < 0) {
+ a--;
+ adir = 1;
+ } else if (adir > 0)
+ adir = 1;
+ if (bdir < 0) {
+ b--;
+ bdir = 1;
+ } else if (bdir > 0)
+ bdir = 1;
+ return a < b || (a == b && adir < bdir);
+}
+
+/* Return 1 if min is nearer to best than max */
+static int boundary_nearer(int min, int mindir,
+ int best, int bestdir,
+ int max, int maxdir)
+{
+ int dmin, dmindir;
+ int dmax, dmaxdir;
+ boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir);
+ boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir);
+ return boundary_lt(dmin, dmindir, dmax, dmaxdir);
+}
+
+/**
+ * snd_pcm_hw_param_near
+ *
+ * Inside configuration space defined by PARAMS set PAR to the available value
+ * nearest to VAL. Reduce configuration space accordingly.
+ * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return the value found.
+ */
+int snd_pcm_hw_param_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_param_t var, unsigned int best, int *dir)
+{
+ snd_pcm_hw_params_t *save = NULL;
+ int v;
+ unsigned int saved_min;
+ int last = 0;
+ int min, max;
+ int mindir, maxdir;
+ int valdir = dir ? *dir : 0;
+ /* FIXME */
+ if (best > INT_MAX)
+ best = INT_MAX;
+ min = max = best;
+ mindir = maxdir = valdir;
+ if (maxdir > 0)
+ maxdir = 0;
+ else if (maxdir == 0)
+ maxdir = -1;
+ else {
+ maxdir = 1;
+ max--;
+ }
+ save = kmalloc(sizeof(*save), GFP_KERNEL);
+ if (save == NULL)
+ return -ENOMEM;
+ *save = *params;
+ saved_min = min;
+ min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir);
+ if (min >= 0) {
+ snd_pcm_hw_params_t *params1;
+ if (max < 0)
+ goto _end;
+ if ((unsigned int)min == saved_min && mindir == valdir)
+ goto _end;
+ params1 = kmalloc(sizeof(*params1), GFP_KERNEL);
+ if (params1 == NULL) {
+ kfree(save);
+ return -ENOMEM;
+ }
+ *params1 = *save;
+ max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir);
+ if (max < 0) {
+ kfree(params1);
+ goto _end;
+ }
+ if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) {
+ *params = *params1;
+ last = 1;
+ }
+ kfree(params1);
+ } else {
+ *params = *save;
+ max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir);
+ assert(max >= 0);
+ last = 1;
+ }
+ _end:
+ kfree(save);
+ if (last)
+ v = snd_pcm_hw_param_last(pcm, params, var, dir);
+ else
+ v = snd_pcm_hw_param_first(pcm, params, var, dir);
+ assert(v >= 0);
+ return v;
+}
+
+/**
+ * snd_pcm_hw_param_choose
+ *
+ * Choose one configuration from configuration space defined by PARAMS
+ * The configuration chosen is that obtained fixing in this order:
+ * first access, first format, first subformat, min channels,
+ * min rate, min period time, max buffer size, min tick time
+ */
+int snd_pcm_hw_params_choose(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ int err;
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_ACCESS, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_FORMAT, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_SUBFORMAT, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_CHANNELS, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_RATE, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_last(pcm, params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL);
+ assert(err >= 0);
+
+ err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_TICK_TIME, NULL);
+ assert(err >= 0);
+
+ return 0;
+}
+
+#undef snd_pcm_t
+#undef assert
+
+static int snd_pcm_lib_ioctl_reset(snd_pcm_substream_t *substream,
+ void *arg)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ unsigned long flags;
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ if (snd_pcm_running(substream) &&
+ snd_pcm_update_hw_ptr(substream) >= 0)
+ runtime->status->hw_ptr %= runtime->buffer_size;
+ else
+ runtime->status->hw_ptr = 0;
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+ return 0;
+}
+
+static int snd_pcm_lib_ioctl_channel_info(snd_pcm_substream_t *substream,
+ void *arg)
+{
+ snd_pcm_channel_info_t *info = arg;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int width;
+ if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) {
+ info->offset = -1;
+ return 0;
+ }
+ width = snd_pcm_format_physical_width(runtime->format);
+ if (width < 0)
+ return width;
+ info->offset = 0;
+ switch (runtime->access) {
+ case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED:
+ case SNDRV_PCM_ACCESS_RW_INTERLEAVED:
+ info->first = info->channel * width;
+ info->step = runtime->channels * width;
+ break;
+ case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED:
+ case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED:
+ {
+ size_t size = runtime->dma_bytes / runtime->channels;
+ info->first = info->channel * size * 8;
+ info->step = width;
+ break;
+ }
+ default:
+ snd_BUG();
+ break;
+ }
+ return 0;
+}
+
+/**
+ * snd_pcm_lib_ioctl - a generic PCM ioctl callback
+ * @substream: the pcm substream instance
+ * @cmd: ioctl command
+ * @arg: ioctl argument
+ *
+ * Processes the generic ioctl commands for PCM.
+ * Can be passed as the ioctl callback for PCM ops.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_ioctl(snd_pcm_substream_t *substream,
+ unsigned int cmd, void *arg)
+{
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL1_INFO:
+ return 0;
+ case SNDRV_PCM_IOCTL1_RESET:
+ return snd_pcm_lib_ioctl_reset(substream, arg);
+ case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
+ return snd_pcm_lib_ioctl_channel_info(substream, arg);
+ }
+ return -ENXIO;
+}
+
+/*
+ * Conditions
+ */
+
+static void snd_pcm_system_tick_set(snd_pcm_substream_t *substream,
+ unsigned long ticks)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (ticks == 0)
+ del_timer(&runtime->tick_timer);
+ else {
+ ticks += (1000000 / HZ) - 1;
+ ticks /= (1000000 / HZ);
+ mod_timer(&runtime->tick_timer, jiffies + ticks);
+ }
+}
+
+/* Temporary alias */
+void snd_pcm_tick_set(snd_pcm_substream_t *substream, unsigned long ticks)
+{
+ snd_pcm_system_tick_set(substream, ticks);
+}
+
+void snd_pcm_tick_prepare(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t frames = ULONG_MAX;
+ snd_pcm_uframes_t avail, dist;
+ unsigned int ticks;
+ u_int64_t n;
+ u_int32_t r;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (runtime->silence_size >= runtime->boundary) {
+ frames = 1;
+ } else if (runtime->silence_size > 0 &&
+ runtime->silence_filled < runtime->buffer_size) {
+ snd_pcm_sframes_t noise_dist;
+ noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
+ snd_assert(noise_dist <= (snd_pcm_sframes_t)runtime->silence_threshold, );
+ frames = noise_dist - runtime->silence_threshold;
+ }
+ avail = snd_pcm_playback_avail(runtime);
+ } else {
+ avail = snd_pcm_capture_avail(runtime);
+ }
+ if (avail < runtime->control->avail_min) {
+ snd_pcm_sframes_t n = runtime->control->avail_min - avail;
+ if (n > 0 && frames > (snd_pcm_uframes_t)n)
+ frames = n;
+ }
+ if (avail < runtime->buffer_size) {
+ snd_pcm_sframes_t n = runtime->buffer_size - avail;
+ if (n > 0 && frames > (snd_pcm_uframes_t)n)
+ frames = n;
+ }
+ if (frames == ULONG_MAX) {
+ snd_pcm_tick_set(substream, 0);
+ return;
+ }
+ dist = runtime->status->hw_ptr - runtime->hw_ptr_base;
+ /* Distance to next interrupt */
+ dist = runtime->period_size - dist % runtime->period_size;
+ if (dist <= frames) {
+ snd_pcm_tick_set(substream, 0);
+ return;
+ }
+ /* the base time is us */
+ n = frames;
+ n *= 1000000;
+ div64_32(&n, runtime->tick_time * runtime->rate, &r);
+ ticks = n + (r > 0 ? 1 : 0);
+ if (ticks < runtime->sleep_min)
+ ticks = runtime->sleep_min;
+ snd_pcm_tick_set(substream, (unsigned long) ticks);
+}
+
+void snd_pcm_tick_elapsed(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ unsigned long flags;
+
+ snd_assert(substream != NULL, return);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return);
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ if (!snd_pcm_running(substream) ||
+ snd_pcm_update_hw_ptr(substream) < 0)
+ goto _end;
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ _end:
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * snd_pcm_period_elapsed - update the pcm status for the next period
+ * @substream: the pcm substream instance
+ *
+ * This function is called from the interrupt handler when the
+ * PCM has processed the period size. It will update the current
+ * pointer, set up the tick, wake up sleepers, etc.
+ *
+ * Even if more than one periods have elapsed since the last call, you
+ * have to call this only once.
+ */
+void snd_pcm_period_elapsed(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ unsigned long flags;
+
+ snd_assert(substream != NULL, return);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return);
+
+ if (runtime->transfer_ack_begin)
+ runtime->transfer_ack_begin(substream);
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ if (!snd_pcm_running(substream) ||
+ snd_pcm_update_hw_ptr_interrupt(substream) < 0)
+ goto _end;
+
+ if (substream->timer_running)
+ snd_timer_interrupt(substream->timer, 1);
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ _end:
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+ if (runtime->transfer_ack_end)
+ runtime->transfer_ack_end(substream);
+ kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
+}
+
+static int snd_pcm_lib_write_transfer(snd_pcm_substream_t *substream,
+ unsigned int hwoff,
+ unsigned long data, unsigned int off,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+ char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+ if (substream->ops->copy) {
+ if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
+ return err;
+ } else {
+ char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+ snd_assert(runtime->dma_area, return -EFAULT);
+ if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+typedef int (*transfer_f)(snd_pcm_substream_t *substream, unsigned int hwoff,
+ unsigned long data, unsigned int off,
+ snd_pcm_uframes_t size);
+
+static snd_pcm_sframes_t snd_pcm_lib_write1(snd_pcm_substream_t *substream,
+ unsigned long data,
+ snd_pcm_uframes_t size,
+ int nonblock,
+ transfer_f transfer)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t xfer = 0;
+ snd_pcm_uframes_t offset = 0;
+ int err = 0;
+
+ if (size == 0)
+ return 0;
+ if (size > runtime->xfer_align)
+ size -= size % runtime->xfer_align;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_RUNNING:
+ case SNDRV_PCM_STATE_PAUSED:
+ break;
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ default:
+ err = -EBADFD;
+ goto _end_unlock;
+ }
+
+ while (size > 0) {
+ snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+ snd_pcm_uframes_t avail;
+ snd_pcm_uframes_t cont;
+ if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ snd_pcm_update_hw_ptr(substream);
+ avail = snd_pcm_playback_avail(runtime);
+ if (((avail < runtime->control->avail_min && size > avail) ||
+ (size >= runtime->xfer_align && avail < runtime->xfer_align))) {
+ wait_queue_t wait;
+ enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state;
+ long tout;
+
+ if (nonblock) {
+ err = -EAGAIN;
+ goto _end_unlock;
+ }
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+ while (1) {
+ if (signal_pending(current)) {
+ state = SIGNALED;
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ snd_pcm_stream_unlock_irq(substream);
+ tout = schedule_timeout(10 * HZ);
+ snd_pcm_stream_lock_irq(substream);
+ if (tout == 0) {
+ if (runtime->status->state != SNDRV_PCM_STATE_PREPARED &&
+ runtime->status->state != SNDRV_PCM_STATE_PAUSED) {
+ state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+ break;
+ }
+ }
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_XRUN:
+ case SNDRV_PCM_STATE_DRAINING:
+ state = ERROR;
+ goto _end_loop;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ state = SUSPENDED;
+ goto _end_loop;
+ default:
+ break;
+ }
+ avail = snd_pcm_playback_avail(runtime);
+ if (avail >= runtime->control->avail_min) {
+ state = READY;
+ break;
+ }
+ }
+ _end_loop:
+ remove_wait_queue(&runtime->sleep, &wait);
+
+ switch (state) {
+ case ERROR:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ case SIGNALED:
+ err = -ERESTARTSYS;
+ goto _end_unlock;
+ case EXPIRED:
+ snd_printd("playback write error (DMA or IRQ trouble?)\n");
+ err = -EIO;
+ goto _end_unlock;
+ default:
+ break;
+ }
+ }
+ if (avail > runtime->xfer_align)
+ avail -= avail % runtime->xfer_align;
+ frames = size > avail ? avail : size;
+ cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+ if (frames > cont)
+ frames = cont;
+ snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL);
+ appl_ptr = runtime->control->appl_ptr;
+ appl_ofs = appl_ptr % runtime->buffer_size;
+ snd_pcm_stream_unlock_irq(substream);
+ if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
+ goto _end;
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ default:
+ break;
+ }
+ appl_ptr += frames;
+ if (appl_ptr >= runtime->boundary)
+ appl_ptr -= runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (substream->ops->ack)
+ substream->ops->ack(substream);
+
+ offset += frames;
+ size -= frames;
+ xfer += frames;
+ if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
+ snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
+ err = snd_pcm_start(substream);
+ if (err < 0)
+ goto _end_unlock;
+ }
+ if (runtime->sleep_min &&
+ runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ snd_pcm_tick_prepare(substream);
+ }
+ _end_unlock:
+ snd_pcm_stream_unlock_irq(substream);
+ _end:
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_write(snd_pcm_substream_t *substream, const void __user *buf, snd_pcm_uframes_t size)
+{
+ snd_pcm_runtime_t *runtime;
+ int nonblock;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ snd_assert(substream->ffile != NULL, return -ENXIO);
+ nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (substream->oss.oss) {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ if (setup != NULL) {
+ if (setup->nonblock)
+ nonblock = 1;
+ else if (setup->block)
+ nonblock = 0;
+ }
+ }
+#endif
+
+ if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
+ runtime->channels > 1)
+ return -EINVAL;
+ return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,
+ snd_pcm_lib_write_transfer);
+}
+
+static int snd_pcm_lib_writev_transfer(snd_pcm_substream_t *substream,
+ unsigned int hwoff,
+ unsigned long data, unsigned int off,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+ void __user **bufs = (void __user **)data;
+ int channels = runtime->channels;
+ int c;
+ if (substream->ops->copy) {
+ snd_assert(substream->ops->silence != NULL, return -EINVAL);
+ for (c = 0; c < channels; ++c, ++bufs) {
+ if (*bufs == NULL) {
+ if ((err = substream->ops->silence(substream, c, hwoff, frames)) < 0)
+ return err;
+ } else {
+ char __user *buf = *bufs + samples_to_bytes(runtime, off);
+ if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0)
+ return err;
+ }
+ }
+ } else {
+ /* default transfer behaviour */
+ size_t dma_csize = runtime->dma_bytes / channels;
+ snd_assert(runtime->dma_area, return -EFAULT);
+ for (c = 0; c < channels; ++c, ++bufs) {
+ char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
+ if (*bufs == NULL) {
+ snd_pcm_format_set_silence(runtime->format, hwbuf, frames);
+ } else {
+ char __user *buf = *bufs + samples_to_bytes(runtime, off);
+ if (copy_from_user(hwbuf, buf, samples_to_bytes(runtime, frames)))
+ return -EFAULT;
+ }
+ }
+ }
+ return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_writev(snd_pcm_substream_t *substream,
+ void __user **bufs,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime;
+ int nonblock;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ snd_assert(substream->ffile != NULL, return -ENXIO);
+ nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (substream->oss.oss) {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ if (setup != NULL) {
+ if (setup->nonblock)
+ nonblock = 1;
+ else if (setup->block)
+ nonblock = 0;
+ }
+ }
+#endif
+
+ if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+ return -EINVAL;
+ return snd_pcm_lib_write1(substream, (unsigned long)bufs, frames,
+ nonblock, snd_pcm_lib_writev_transfer);
+}
+
+static int snd_pcm_lib_read_transfer(snd_pcm_substream_t *substream,
+ unsigned int hwoff,
+ unsigned long data, unsigned int off,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+ char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+ if (substream->ops->copy) {
+ if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
+ return err;
+ } else {
+ char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+ snd_assert(runtime->dma_area, return -EFAULT);
+ if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_lib_read1(snd_pcm_substream_t *substream,
+ unsigned long data,
+ snd_pcm_uframes_t size,
+ int nonblock,
+ transfer_f transfer)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t xfer = 0;
+ snd_pcm_uframes_t offset = 0;
+ int err = 0;
+
+ if (size == 0)
+ return 0;
+ if (size > runtime->xfer_align)
+ size -= size % runtime->xfer_align;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ if (size >= runtime->start_threshold) {
+ err = snd_pcm_start(substream);
+ if (err < 0)
+ goto _end_unlock;
+ }
+ break;
+ case SNDRV_PCM_STATE_DRAINING:
+ case SNDRV_PCM_STATE_RUNNING:
+ case SNDRV_PCM_STATE_PAUSED:
+ break;
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ default:
+ err = -EBADFD;
+ goto _end_unlock;
+ }
+
+ while (size > 0) {
+ snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+ snd_pcm_uframes_t avail;
+ snd_pcm_uframes_t cont;
+ if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ snd_pcm_update_hw_ptr(substream);
+ __draining:
+ avail = snd_pcm_capture_avail(runtime);
+ if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+ if (avail < runtime->xfer_align) {
+ err = -EPIPE;
+ goto _end_unlock;
+ }
+ } else if ((avail < runtime->control->avail_min && size > avail) ||
+ (size >= runtime->xfer_align && avail < runtime->xfer_align)) {
+ wait_queue_t wait;
+ enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state;
+ long tout;
+
+ if (nonblock) {
+ err = -EAGAIN;
+ goto _end_unlock;
+ }
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+ while (1) {
+ if (signal_pending(current)) {
+ state = SIGNALED;
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ snd_pcm_stream_unlock_irq(substream);
+ tout = schedule_timeout(10 * HZ);
+ snd_pcm_stream_lock_irq(substream);
+ if (tout == 0) {
+ if (runtime->status->state != SNDRV_PCM_STATE_PREPARED &&
+ runtime->status->state != SNDRV_PCM_STATE_PAUSED) {
+ state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+ break;
+ }
+ }
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_XRUN:
+ state = ERROR;
+ goto _end_loop;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ state = SUSPENDED;
+ goto _end_loop;
+ case SNDRV_PCM_STATE_DRAINING:
+ goto __draining;
+ default:
+ break;
+ }
+ avail = snd_pcm_capture_avail(runtime);
+ if (avail >= runtime->control->avail_min) {
+ state = READY;
+ break;
+ }
+ }
+ _end_loop:
+ remove_wait_queue(&runtime->sleep, &wait);
+
+ switch (state) {
+ case ERROR:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ case SIGNALED:
+ err = -ERESTARTSYS;
+ goto _end_unlock;
+ case EXPIRED:
+ snd_printd("capture read error (DMA or IRQ trouble?)\n");
+ err = -EIO;
+ goto _end_unlock;
+ default:
+ break;
+ }
+ }
+ if (avail > runtime->xfer_align)
+ avail -= avail % runtime->xfer_align;
+ frames = size > avail ? avail : size;
+ cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+ if (frames > cont)
+ frames = cont;
+ snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL);
+ appl_ptr = runtime->control->appl_ptr;
+ appl_ofs = appl_ptr % runtime->buffer_size;
+ snd_pcm_stream_unlock_irq(substream);
+ if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
+ goto _end;
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ goto _end_unlock;
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = -ESTRPIPE;
+ goto _end_unlock;
+ default:
+ break;
+ }
+ appl_ptr += frames;
+ if (appl_ptr >= runtime->boundary)
+ appl_ptr -= runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (substream->ops->ack)
+ substream->ops->ack(substream);
+
+ offset += frames;
+ size -= frames;
+ xfer += frames;
+ if (runtime->sleep_min &&
+ runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ snd_pcm_tick_prepare(substream);
+ }
+ _end_unlock:
+ snd_pcm_stream_unlock_irq(substream);
+ _end:
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_read(snd_pcm_substream_t *substream, void __user *buf, snd_pcm_uframes_t size)
+{
+ snd_pcm_runtime_t *runtime;
+ int nonblock;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ snd_assert(substream->ffile != NULL, return -ENXIO);
+ nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (substream->oss.oss) {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ if (setup != NULL) {
+ if (setup->nonblock)
+ nonblock = 1;
+ else if (setup->block)
+ nonblock = 0;
+ }
+ }
+#endif
+ if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED)
+ return -EINVAL;
+ return snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer);
+}
+
+static int snd_pcm_lib_readv_transfer(snd_pcm_substream_t *substream,
+ unsigned int hwoff,
+ unsigned long data, unsigned int off,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+ void __user **bufs = (void __user **)data;
+ int channels = runtime->channels;
+ int c;
+ if (substream->ops->copy) {
+ for (c = 0; c < channels; ++c, ++bufs) {
+ char __user *buf;
+ if (*bufs == NULL)
+ continue;
+ buf = *bufs + samples_to_bytes(runtime, off);
+ if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0)
+ return err;
+ }
+ } else {
+ snd_pcm_uframes_t dma_csize = runtime->dma_bytes / channels;
+ snd_assert(runtime->dma_area, return -EFAULT);
+ for (c = 0; c < channels; ++c, ++bufs) {
+ char *hwbuf;
+ char __user *buf;
+ if (*bufs == NULL)
+ continue;
+
+ hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
+ buf = *bufs + samples_to_bytes(runtime, off);
+ if (copy_to_user(buf, hwbuf, samples_to_bytes(runtime, frames)))
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_readv(snd_pcm_substream_t *substream,
+ void __user **bufs,
+ snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime;
+ int nonblock;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ snd_assert(substream->ffile != NULL, return -ENXIO);
+ nonblock = !!(substream->ffile->f_flags & O_NONBLOCK);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (substream->oss.oss) {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ if (setup != NULL) {
+ if (setup->nonblock)
+ nonblock = 1;
+ else if (setup->block)
+ nonblock = 0;
+ }
+ }
+#endif
+
+ if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+ return -EINVAL;
+ return snd_pcm_lib_read1(substream, (unsigned long)bufs, frames, nonblock, snd_pcm_lib_readv_transfer);
+}
+
+/*
+ * Exported symbols
+ */
+
+EXPORT_SYMBOL(snd_interval_refine);
+EXPORT_SYMBOL(snd_interval_list);
+EXPORT_SYMBOL(snd_interval_ratnum);
+EXPORT_SYMBOL(snd_interval_muldivk);
+EXPORT_SYMBOL(snd_interval_mulkdiv);
+EXPORT_SYMBOL(snd_interval_div);
+EXPORT_SYMBOL(_snd_pcm_hw_params_any);
+EXPORT_SYMBOL(_snd_pcm_hw_param_min);
+EXPORT_SYMBOL(_snd_pcm_hw_param_set);
+EXPORT_SYMBOL(_snd_pcm_hw_param_setempty);
+EXPORT_SYMBOL(_snd_pcm_hw_param_setinteger);
+EXPORT_SYMBOL(snd_pcm_hw_param_value_min);
+EXPORT_SYMBOL(snd_pcm_hw_param_value_max);
+EXPORT_SYMBOL(snd_pcm_hw_param_mask);
+EXPORT_SYMBOL(snd_pcm_hw_param_first);
+EXPORT_SYMBOL(snd_pcm_hw_param_last);
+EXPORT_SYMBOL(snd_pcm_hw_param_near);
+EXPORT_SYMBOL(snd_pcm_hw_param_set);
+EXPORT_SYMBOL(snd_pcm_hw_refine);
+EXPORT_SYMBOL(snd_pcm_hw_params);
+EXPORT_SYMBOL(snd_pcm_hw_constraints_init);
+EXPORT_SYMBOL(snd_pcm_hw_constraints_complete);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_list);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_step);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_integer);
+EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2);
+EXPORT_SYMBOL(snd_pcm_hw_rule_add);
+EXPORT_SYMBOL(snd_pcm_set_ops);
+EXPORT_SYMBOL(snd_pcm_set_sync);
+EXPORT_SYMBOL(snd_pcm_lib_ioctl);
+EXPORT_SYMBOL(snd_pcm_stop);
+EXPORT_SYMBOL(snd_pcm_period_elapsed);
+EXPORT_SYMBOL(snd_pcm_lib_write);
+EXPORT_SYMBOL(snd_pcm_lib_read);
+EXPORT_SYMBOL(snd_pcm_lib_writev);
+EXPORT_SYMBOL(snd_pcm_lib_readv);
+EXPORT_SYMBOL(snd_pcm_lib_buffer_bytes);
+EXPORT_SYMBOL(snd_pcm_lib_period_bytes);
+/* pcm_memory.c */
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
+EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
+EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
+EXPORT_SYMBOL(snd_pcm_lib_free_pages);
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
new file mode 100644
index 00000000000..f1d5f7a6ee0
--- /dev/null
+++ b/sound/core/pcm_memory.c
@@ -0,0 +1,363 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <asm/io.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+static int preallocate_dma = 1;
+module_param(preallocate_dma, int, 0444);
+MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
+
+static int maximum_substreams = 4;
+module_param(maximum_substreams, int, 0444);
+MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
+
+static const size_t snd_minimum_buffer = 16384;
+
+
+/*
+ * try to allocate as the large pages as possible.
+ * stores the resultant memory size in *res_size.
+ *
+ * the minimum size is snd_minimum_buffer. it should be power of 2.
+ */
+static int preallocate_pcm_pages(snd_pcm_substream_t *substream, size_t size)
+{
+ struct snd_dma_buffer *dmab = &substream->dma_buffer;
+ int err;
+
+ snd_assert(size > 0, return -EINVAL);
+
+ /* already reserved? */
+ if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) {
+ if (dmab->bytes >= size)
+ return 0; /* yes */
+ /* no, free the reserved block */
+ snd_dma_free_pages(dmab);
+ dmab->bytes = 0;
+ }
+
+ do {
+ if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
+ size, dmab)) < 0) {
+ if (err != -ENOMEM)
+ return err; /* fatal error */
+ } else
+ return 0;
+ size >>= 1;
+ } while (size >= snd_minimum_buffer);
+ dmab->bytes = 0; /* tell error */
+ return 0;
+}
+
+/*
+ * release the preallocated buffer if not yet done.
+ */
+static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
+{
+ if (substream->dma_buffer.area == NULL)
+ return;
+ if (substream->dma_buf_id)
+ snd_dma_reserve_buf(&substream->dma_buffer, substream->dma_buf_id);
+ else
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+}
+
+/**
+ * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
+ * @substream: the pcm substream instance
+ *
+ * Releases the pre-allocated buffer of the given substream.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
+{
+ snd_pcm_lib_preallocate_dma_free(substream);
+ if (substream->proc_prealloc_entry) {
+ snd_info_unregister(substream->proc_prealloc_entry);
+ substream->proc_prealloc_entry = NULL;
+ }
+ return 0;
+}
+
+/**
+ * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
+ * @pcm: the pcm instance
+ *
+ * Releases all the pre-allocated buffers on the given pcm.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
+{
+ snd_pcm_substream_t *substream;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++)
+ for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
+ snd_pcm_lib_preallocate_free(substream);
+ return 0;
+}
+
+/*
+ * read callback for prealloc proc file
+ *
+ * prints the current allocated size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t *buffer)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+ snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
+}
+
+/*
+ * write callback for prealloc proc file
+ *
+ * accepts the preallocation size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
+ snd_info_buffer_t *buffer)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
+ char line[64], str[64];
+ size_t size;
+ struct snd_dma_buffer new_dmab;
+
+ if (substream->runtime) {
+ buffer->error = -EBUSY;
+ return;
+ }
+ if (!snd_info_get_line(buffer, line, sizeof(line))) {
+ snd_info_get_str(str, line, sizeof(str));
+ size = simple_strtoul(str, NULL, 10) * 1024;
+ if ((size != 0 && size < 8192) || size > substream->dma_max) {
+ buffer->error = -EINVAL;
+ return;
+ }
+ if (substream->dma_buffer.bytes == size)
+ return;
+ memset(&new_dmab, 0, sizeof(new_dmab));
+ new_dmab.dev = substream->dma_buffer.dev;
+ if (size > 0) {
+ if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
+ substream->dma_buffer.dev.dev,
+ size, &new_dmab) < 0) {
+ buffer->error = -ENOMEM;
+ return;
+ }
+ substream->buffer_bytes_max = size;
+ } else {
+ substream->buffer_bytes_max = UINT_MAX;
+ }
+ if (substream->dma_buffer.area)
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer = new_dmab;
+ } else {
+ buffer->error = -EINVAL;
+ }
+}
+
+/*
+ * pre-allocate the buffer and create a proc file for the substream
+ */
+static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
+ size_t size, size_t max)
+{
+ snd_info_entry_t *entry;
+
+ if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
+ preallocate_pcm_pages(substream, size);
+
+ if (substream->dma_buffer.bytes > 0)
+ substream->buffer_bytes_max = substream->dma_buffer.bytes;
+ substream->dma_max = max;
+ if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
+ entry->c.text.read_size = 64;
+ entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
+ entry->c.text.write_size = 64;
+ entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
+ entry->private_data = substream;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ substream->proc_prealloc_entry = entry;
+ return 0;
+}
+
+
+/**
+ * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
+ * @substream: the pcm substream instance
+ * @type: DMA type (SNDRV_DMA_TYPE_*)
+ * @data: DMA type dependant data
+ * @size: the requested pre-allocation size in bytes
+ * @max: the max. allowed pre-allocation size
+ *
+ * Do pre-allocation for the given DMA buffer type.
+ *
+ * When substream->dma_buf_id is set, the function tries to look for
+ * the reserved buffer, and the buffer is not freed but reserved at
+ * destruction time. The dma_buf_id must be unique for all systems
+ * (in the same DMA buffer type) e.g. using snd_dma_pci_buf_id().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
+ int type, struct device *data,
+ size_t size, size_t max)
+{
+ substream->dma_buffer.dev.type = type;
+ substream->dma_buffer.dev.dev = data;
+ return snd_pcm_lib_preallocate_pages1(substream, size, max);
+}
+
+/**
+ * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
+ * @substream: the pcm substream instance
+ * @type: DMA type (SNDRV_DMA_TYPE_*)
+ * @data: DMA type dependant data
+ * @size: the requested pre-allocation size in bytes
+ * @max: the max. allowed pre-allocation size
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * specified DMA type.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
+ int type, void *data,
+ size_t size, size_t max)
+{
+ snd_pcm_substream_t *substream;
+ int stream, err;
+
+ for (stream = 0; stream < 2; stream++)
+ for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
+ if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
+ return err;
+ return 0;
+}
+
+/**
+ * snd_pcm_sgbuf_ops_page - get the page struct at the given offset
+ * @substream: the pcm substream instance
+ * @offset: the buffer offset
+ *
+ * Returns the page struct at the given buffer offset.
+ * Used as the page callback of PCM ops.
+ */
+struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset)
+{
+ struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+
+ unsigned int idx = offset >> PAGE_SHIFT;
+ if (idx >= (unsigned int)sgbuf->pages)
+ return NULL;
+ return sgbuf->page_table[idx];
+}
+
+/**
+ * snd_pcm_lib_malloc_pages - allocate the DMA buffer
+ * @substream: the substream to allocate the DMA buffer to
+ * @size: the requested buffer size in bytes
+ *
+ * Allocates the DMA buffer on the BUS type given earlier to
+ * snd_pcm_lib_preallocate_xxx_pages().
+ *
+ * Returns 1 if the buffer is changed, 0 if not changed, or a negative
+ * code on failure.
+ */
+int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
+{
+ snd_pcm_runtime_t *runtime;
+ struct snd_dma_buffer *dmab = NULL;
+
+ snd_assert(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_UNKNOWN, return -EINVAL);
+ snd_assert(substream != NULL, return -EINVAL);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -EINVAL);
+
+ if (runtime->dma_buffer_p) {
+ /* perphaps, we might free the large DMA memory region
+ to save some space here, but the actual solution
+ costs us less time */
+ if (runtime->dma_buffer_p->bytes >= size) {
+ runtime->dma_bytes = size;
+ return 0; /* ok, do not change */
+ }
+ snd_pcm_lib_free_pages(substream);
+ }
+ if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) {
+ dmab = &substream->dma_buffer; /* use the pre-allocated buffer */
+ } else {
+ dmab = kcalloc(1, sizeof(*dmab), GFP_KERNEL);
+ if (! dmab)
+ return -ENOMEM;
+ dmab->dev = substream->dma_buffer.dev;
+ if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
+ substream->dma_buffer.dev.dev,
+ size, dmab) < 0) {
+ kfree(dmab);
+ return -ENOMEM;
+ }
+ }
+ snd_pcm_set_runtime_buffer(substream, dmab);
+ runtime->dma_bytes = size;
+ return 1; /* area was changed */
+}
+
+/**
+ * snd_pcm_lib_free_pages - release the allocated DMA buffer.
+ * @substream: the substream to release the DMA buffer
+ *
+ * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+
+ snd_assert(substream != NULL, return -EINVAL);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -EINVAL);
+ if (runtime->dma_area == NULL)
+ return 0;
+ if (runtime->dma_buffer_p != &substream->dma_buffer) {
+ /* it's a newly allocated buffer. release it now. */
+ snd_dma_free_pages(runtime->dma_buffer_p);
+ kfree(runtime->dma_buffer_p);
+ }
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c
new file mode 100644
index 00000000000..422b8db1415
--- /dev/null
+++ b/sound/core/pcm_misc.c
@@ -0,0 +1,481 @@
+/*
+ * PCM Interface - misc routines
+ * Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#define SND_PCM_FORMAT_UNKNOWN (-1)
+
+/* NOTE: "signed" prefix must be given below since the default char is
+ * unsigned on some architectures!
+ */
+struct pcm_format_data {
+ unsigned char width; /* bit width */
+ unsigned char phys; /* physical bit width */
+ signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */
+ signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */
+ unsigned char silence[8]; /* silence data to fill */
+};
+
+static struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = {
+ [SNDRV_PCM_FORMAT_S8] = {
+ .width = 8, .phys = 8, .le = -1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U8] = {
+ .width = 8, .phys = 8, .le = -1, .signd = 0,
+ .silence = { 0x80 },
+ },
+ [SNDRV_PCM_FORMAT_S16_LE] = {
+ .width = 16, .phys = 16, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S16_BE] = {
+ .width = 16, .phys = 16, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U16_LE] = {
+ .width = 16, .phys = 16, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x80 },
+ },
+ [SNDRV_PCM_FORMAT_U16_BE] = {
+ .width = 16, .phys = 16, .le = 0, .signd = 0,
+ .silence = { 0x80, 0x00 },
+ },
+ [SNDRV_PCM_FORMAT_S24_LE] = {
+ .width = 24, .phys = 32, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S24_BE] = {
+ .width = 24, .phys = 32, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U24_LE] = {
+ .width = 24, .phys = 32, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x00, 0x80 },
+ },
+ [SNDRV_PCM_FORMAT_U24_BE] = {
+ .width = 24, .phys = 32, .le = 0, .signd = 0,
+ .silence = { 0x80, 0x00, 0x00 },
+ },
+ [SNDRV_PCM_FORMAT_S32_LE] = {
+ .width = 32, .phys = 32, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S32_BE] = {
+ .width = 32, .phys = 32, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U32_LE] = {
+ .width = 32, .phys = 32, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x00, 0x00, 0x80 },
+ },
+ [SNDRV_PCM_FORMAT_U32_BE] = {
+ .width = 32, .phys = 32, .le = 0, .signd = 0,
+ .silence = { 0x80, 0x00, 0x00, 0x00 },
+ },
+ [SNDRV_PCM_FORMAT_FLOAT_LE] = {
+ .width = 32, .phys = 32, .le = 1, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_FLOAT_BE] = {
+ .width = 32, .phys = 32, .le = 0, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_FLOAT64_LE] = {
+ .width = 64, .phys = 64, .le = 1, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_FLOAT64_BE] = {
+ .width = 64, .phys = 64, .le = 0, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = {
+ .width = 32, .phys = 32, .le = 1, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = {
+ .width = 32, .phys = 32, .le = 0, .signd = -1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_MU_LAW] = {
+ .width = 8, .phys = 8, .le = -1, .signd = -1,
+ .silence = { 0x7f },
+ },
+ [SNDRV_PCM_FORMAT_A_LAW] = {
+ .width = 8, .phys = 8, .le = -1, .signd = -1,
+ .silence = { 0x55 },
+ },
+ [SNDRV_PCM_FORMAT_IMA_ADPCM] = {
+ .width = 4, .phys = 4, .le = -1, .signd = -1,
+ .silence = {},
+ },
+ /* FIXME: the following three formats are not defined properly yet */
+ [SNDRV_PCM_FORMAT_MPEG] = {
+ .le = -1, .signd = -1,
+ },
+ [SNDRV_PCM_FORMAT_GSM] = {
+ .le = -1, .signd = -1,
+ },
+ [SNDRV_PCM_FORMAT_SPECIAL] = {
+ .le = -1, .signd = -1,
+ },
+ [SNDRV_PCM_FORMAT_S24_3LE] = {
+ .width = 24, .phys = 24, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S24_3BE] = {
+ .width = 24, .phys = 24, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U24_3LE] = {
+ .width = 24, .phys = 24, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x00, 0x80 },
+ },
+ [SNDRV_PCM_FORMAT_U24_3BE] = {
+ .width = 24, .phys = 24, .le = 0, .signd = 0,
+ .silence = { 0x80, 0x00, 0x00 },
+ },
+ [SNDRV_PCM_FORMAT_S20_3LE] = {
+ .width = 20, .phys = 24, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S20_3BE] = {
+ .width = 20, .phys = 24, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U20_3LE] = {
+ .width = 20, .phys = 24, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x00, 0x08 },
+ },
+ [SNDRV_PCM_FORMAT_U20_3BE] = {
+ .width = 20, .phys = 24, .le = 0, .signd = 0,
+ .silence = { 0x08, 0x00, 0x00 },
+ },
+ [SNDRV_PCM_FORMAT_S18_3LE] = {
+ .width = 18, .phys = 24, .le = 1, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_S18_3BE] = {
+ .width = 18, .phys = 24, .le = 0, .signd = 1,
+ .silence = {},
+ },
+ [SNDRV_PCM_FORMAT_U18_3LE] = {
+ .width = 18, .phys = 24, .le = 1, .signd = 0,
+ .silence = { 0x00, 0x00, 0x02 },
+ },
+ [SNDRV_PCM_FORMAT_U18_3BE] = {
+ .width = 18, .phys = 24, .le = 0, .signd = 0,
+ .silence = { 0x02, 0x00, 0x00 },
+ },
+};
+
+
+/**
+ * snd_pcm_format_signed - Check the PCM format is signed linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is signed linear, 0 if unsigned
+ * linear, and a negative error code for non-linear formats.
+ */
+int snd_pcm_format_signed(snd_pcm_format_t format)
+{
+ int val;
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return -EINVAL;
+ if ((val = pcm_formats[format].signd) < 0)
+ return -EINVAL;
+ return val;
+}
+
+/**
+ * snd_pcm_format_unsigned - Check the PCM format is unsigned linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is unsigned linear, 0 if signed
+ * linear, and a negative error code for non-linear formats.
+ */
+int snd_pcm_format_unsigned(snd_pcm_format_t format)
+{
+ int val;
+
+ val = snd_pcm_format_signed(format);
+ if (val < 0)
+ return val;
+ return !val;
+}
+
+/**
+ * snd_pcm_format_linear - Check the PCM format is linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is linear, 0 if not.
+ */
+int snd_pcm_format_linear(snd_pcm_format_t format)
+{
+ return snd_pcm_format_signed(format) >= 0;
+}
+
+/**
+ * snd_pcm_format_little_endian - Check the PCM format is little-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is little-endian, 0 if
+ * big-endian, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_little_endian(snd_pcm_format_t format)
+{
+ int val;
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return -EINVAL;
+ if ((val = pcm_formats[format].le) < 0)
+ return -EINVAL;
+ return val;
+}
+
+/**
+ * snd_pcm_format_big_endian - Check the PCM format is big-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is big-endian, 0 if
+ * little-endian, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_big_endian(snd_pcm_format_t format)
+{
+ int val;
+
+ val = snd_pcm_format_little_endian(format);
+ if (val < 0)
+ return val;
+ return !val;
+}
+
+/**
+ * snd_pcm_format_cpu_endian - Check the PCM format is CPU-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is CPU-endian, 0 if
+ * opposite, or a negative error code if endian not specified.
+ */
+int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
+{
+#ifdef SNDRV_LITTLE_ENDIAN
+ return snd_pcm_format_little_endian(format);
+#else
+ return snd_pcm_format_big_endian(format);
+#endif
+}
+
+/**
+ * snd_pcm_format_width - return the bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the bit-width of the format, or a negative error code
+ * if unknown format.
+ */
+int snd_pcm_format_width(snd_pcm_format_t format)
+{
+ int val;
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return -EINVAL;
+ if ((val = pcm_formats[format].width) == 0)
+ return -EINVAL;
+ return val;
+}
+
+/**
+ * snd_pcm_format_physical_width - return the physical bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the physical bit-width of the format, or a negative error code
+ * if unknown format.
+ */
+int snd_pcm_format_physical_width(snd_pcm_format_t format)
+{
+ int val;
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return -EINVAL;
+ if ((val = pcm_formats[format].phys) == 0)
+ return -EINVAL;
+ return val;
+}
+
+/**
+ * snd_pcm_format_size - return the byte size of samples on the given format
+ * @format: the format to check
+ *
+ * Returns the byte size of the given samples for the format, or a
+ * negative error code if unknown format.
+ */
+ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
+{
+ int phys_width = snd_pcm_format_physical_width(format);
+ if (phys_width < 0)
+ return -EINVAL;
+ return samples * phys_width / 8;
+}
+
+/**
+ * snd_pcm_format_silence_64 - return the silent data in 8 bytes array
+ * @format: the format to check
+ *
+ * Returns the format pattern to fill or NULL if error.
+ */
+const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format)
+{
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return NULL;
+ if (! pcm_formats[format].phys)
+ return NULL;
+ return pcm_formats[format].silence;
+}
+
+/**
+ * snd_pcm_format_set_silence - set the silence data on the buffer
+ * @format: the PCM format
+ * @data: the buffer pointer
+ * @samples: the number of samples to set silence
+ *
+ * Sets the silence data on the buffer for the given samples.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
+{
+ int width;
+ unsigned char *dst, *pat;
+
+ if (format < 0 || format > SNDRV_PCM_FORMAT_LAST)
+ return -EINVAL;
+ if (samples == 0)
+ return 0;
+ width = pcm_formats[format].phys; /* physical width */
+ pat = pcm_formats[format].silence;
+ if (! width)
+ return -EINVAL;
+ /* signed or 1 byte data */
+ if (pcm_formats[format].signd == 1 || width <= 8) {
+ unsigned int bytes = samples * width / 8;
+ memset(data, *pat, bytes);
+ return 0;
+ }
+ /* non-zero samples, fill using a loop */
+ width /= 8;
+ dst = data;
+#if 0
+ while (samples--) {
+ memcpy(dst, pat, width);
+ dst += width;
+ }
+#else
+ /* a bit optimization for constant width */
+ switch (width) {
+ case 2:
+ while (samples--) {
+ memcpy(dst, pat, 2);
+ dst += 2;
+ }
+ break;
+ case 3:
+ while (samples--) {
+ memcpy(dst, pat, 3);
+ dst += 3;
+ }
+ break;
+ case 4:
+ while (samples--) {
+ memcpy(dst, pat, 4);
+ dst += 4;
+ }
+ break;
+ case 8:
+ while (samples--) {
+ memcpy(dst, pat, 8);
+ dst += 8;
+ }
+ break;
+ }
+#endif
+ return 0;
+}
+
+/* [width][unsigned][bigendian] */
+static int linear_formats[4][2][2] = {
+ {{ SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8},
+ { SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8}},
+ {{SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE},
+ {SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE}},
+ {{SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE},
+ {SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE}},
+ {{SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE},
+ {SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE}}
+};
+
+/**
+ * snd_pcm_build_linear_format - return the suitable linear format for the given condition
+ * @width: the bit-width
+ * @unsignd: 1 if unsigned, 0 if signed.
+ * @big_endian: 1 if big-endian, 0 if little-endian
+ *
+ * Returns the suitable linear format for the given condition.
+ */
+snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian)
+{
+ if (width & 7)
+ return SND_PCM_FORMAT_UNKNOWN;
+ width = (width / 8) - 1;
+ if (width < 0 || width >= 4)
+ return SND_PCM_FORMAT_UNKNOWN;
+ return linear_formats[width][!!unsignd][!!big_endian];
+}
+
+/**
+ * snd_pcm_limit_hw_rates - determine rate_min/rate_max fields
+ * @runtime: the runtime instance
+ *
+ * Determines the rate_min and rate_max fields from the rates bits of
+ * the given runtime->hw.
+ *
+ * Returns zero if successful.
+ */
+int snd_pcm_limit_hw_rates(snd_pcm_runtime_t *runtime)
+{
+ static unsigned rates[] = {
+ /* ATTENTION: these values depend on the definition in pcm.h! */
+ 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
+ 64000, 88200, 96000, 176400, 192000
+ };
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(rates); i++) {
+ if (runtime->hw.rates & (1 << i)) {
+ runtime->hw.rate_min = rates[i];
+ break;
+ }
+ }
+ for (i = (int)ARRAY_SIZE(rates) - 1; i >= 0; i--) {
+ if (runtime->hw.rates & (1 << i)) {
+ runtime->hw.rate_max = rates[i];
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
new file mode 100644
index 00000000000..cad9bbde998
--- /dev/null
+++ b/sound/core/pcm_native.c
@@ -0,0 +1,3364 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/uio.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/timer.h>
+#include <sound/minors.h>
+#include <asm/io.h>
+
+/*
+ * Compatibility
+ */
+
+struct sndrv_pcm_hw_params_old {
+ unsigned int flags;
+ unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
+ SNDRV_PCM_HW_PARAM_ACCESS + 1];
+ struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
+ unsigned int rmask;
+ unsigned int cmask;
+ unsigned int info;
+ unsigned int msbits;
+ unsigned int rate_num;
+ unsigned int rate_den;
+ sndrv_pcm_uframes_t fifo_size;
+ unsigned char reserved[64];
+};
+
+#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old)
+#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old)
+
+static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams);
+
+/*
+ *
+ */
+
+DEFINE_RWLOCK(snd_pcm_link_rwlock);
+static DECLARE_RWSEM(snd_pcm_link_rwsem);
+
+
+static inline mm_segment_t snd_enter_user(void)
+{
+ mm_segment_t fs = get_fs();
+ set_fs(get_ds());
+ return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+ set_fs(fs);
+}
+
+
+
+int snd_pcm_info(snd_pcm_substream_t * substream, snd_pcm_info_t *info)
+{
+ snd_pcm_runtime_t * runtime;
+ snd_pcm_t *pcm = substream->pcm;
+ snd_pcm_str_t *pstr = substream->pstr;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ memset(info, 0, sizeof(*info));
+ info->card = pcm->card->number;
+ info->device = pcm->device;
+ info->stream = substream->stream;
+ info->subdevice = substream->number;
+ strlcpy(info->id, pcm->id, sizeof(info->id));
+ strlcpy(info->name, pcm->name, sizeof(info->name));
+ info->dev_class = pcm->dev_class;
+ info->dev_subclass = pcm->dev_subclass;
+ info->subdevices_count = pstr->substream_count;
+ info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
+ strlcpy(info->subname, substream->name, sizeof(info->subname));
+ runtime = substream->runtime;
+ /* AB: FIXME!!! This is definitely nonsense */
+ if (runtime) {
+ info->sync = runtime->sync;
+ substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
+ }
+ return 0;
+}
+
+int snd_pcm_info_user(snd_pcm_substream_t * substream, snd_pcm_info_t __user * _info)
+{
+ snd_pcm_info_t *info;
+ int err;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (! info)
+ return -ENOMEM;
+ err = snd_pcm_info(substream, info);
+ if (err >= 0) {
+ if (copy_to_user(_info, info, sizeof(*info)))
+ err = -EFAULT;
+ }
+ kfree(info);
+ return err;
+}
+
+#undef RULES_DEBUG
+
+#ifdef RULES_DEBUG
+#define HW_PARAM(v) [SNDRV_PCM_HW_PARAM_##v] = #v
+char *snd_pcm_hw_param_names[] = {
+ HW_PARAM(ACCESS),
+ HW_PARAM(FORMAT),
+ HW_PARAM(SUBFORMAT),
+ HW_PARAM(SAMPLE_BITS),
+ HW_PARAM(FRAME_BITS),
+ HW_PARAM(CHANNELS),
+ HW_PARAM(RATE),
+ HW_PARAM(PERIOD_TIME),
+ HW_PARAM(PERIOD_SIZE),
+ HW_PARAM(PERIOD_BYTES),
+ HW_PARAM(PERIODS),
+ HW_PARAM(BUFFER_TIME),
+ HW_PARAM(BUFFER_SIZE),
+ HW_PARAM(BUFFER_BYTES),
+ HW_PARAM(TICK_TIME),
+};
+#endif
+
+int snd_pcm_hw_refine(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params)
+{
+ unsigned int k;
+ snd_pcm_hardware_t *hw;
+ snd_interval_t *i = NULL;
+ snd_mask_t *m = NULL;
+ snd_pcm_hw_constraints_t *constrs = &substream->runtime->hw_constraints;
+ unsigned int rstamps[constrs->rules_num];
+ unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
+ unsigned int stamp = 2;
+ int changed, again;
+
+ params->info = 0;
+ params->fifo_size = 0;
+ if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
+ params->msbits = 0;
+ if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) {
+ params->rate_num = 0;
+ params->rate_den = 0;
+ }
+
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
+ m = hw_param_mask(params, k);
+ if (snd_mask_empty(m))
+ return -EINVAL;
+ if (!(params->rmask & (1 << k)))
+ continue;
+#ifdef RULES_DEBUG
+ printk("%s = ", snd_pcm_hw_param_names[k]);
+ printk("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
+#endif
+ changed = snd_mask_refine(m, constrs_mask(constrs, k));
+#ifdef RULES_DEBUG
+ printk("%04x%04x%04x%04x\n", m->bits[3], m->bits[2], m->bits[1], m->bits[0]);
+#endif
+ if (changed)
+ params->cmask |= 1 << k;
+ if (changed < 0)
+ return changed;
+ }
+
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
+ i = hw_param_interval(params, k);
+ if (snd_interval_empty(i))
+ return -EINVAL;
+ if (!(params->rmask & (1 << k)))
+ continue;
+#ifdef RULES_DEBUG
+ printk("%s = ", snd_pcm_hw_param_names[k]);
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%c%u %u%c",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+ printk(" -> ");
+#endif
+ changed = snd_interval_refine(i, constrs_interval(constrs, k));
+#ifdef RULES_DEBUG
+ if (i->empty)
+ printk("empty\n");
+ else
+ printk("%c%u %u%c\n",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+#endif
+ if (changed)
+ params->cmask |= 1 << k;
+ if (changed < 0)
+ return changed;
+ }
+
+ for (k = 0; k < constrs->rules_num; k++)
+ rstamps[k] = 0;
+ for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
+ vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0;
+ do {
+ again = 0;
+ for (k = 0; k < constrs->rules_num; k++) {
+ snd_pcm_hw_rule_t *r = &constrs->rules[k];
+ unsigned int d;
+ int doit = 0;
+ if (r->cond && !(r->cond & params->flags))
+ continue;
+ for (d = 0; r->deps[d] >= 0; d++) {
+ if (vstamps[r->deps[d]] > rstamps[k]) {
+ doit = 1;
+ break;
+ }
+ }
+ if (!doit)
+ continue;
+#ifdef RULES_DEBUG
+ printk("Rule %d [%p]: ", k, r->func);
+ if (r->var >= 0) {
+ printk("%s = ", snd_pcm_hw_param_names[r->var]);
+ if (hw_is_mask(r->var)) {
+ m = hw_param_mask(params, r->var);
+ printk("%x", *m->bits);
+ } else {
+ i = hw_param_interval(params, r->var);
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%c%u %u%c",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+ }
+ }
+#endif
+ changed = r->func(params, r);
+#ifdef RULES_DEBUG
+ if (r->var >= 0) {
+ printk(" -> ");
+ if (hw_is_mask(r->var))
+ printk("%x", *m->bits);
+ else {
+ if (i->empty)
+ printk("empty");
+ else
+ printk("%c%u %u%c",
+ i->openmin ? '(' : '[', i->min,
+ i->max, i->openmax ? ')' : ']');
+ }
+ }
+ printk("\n");
+#endif
+ rstamps[k] = stamp;
+ if (changed && r->var >= 0) {
+ params->cmask |= (1 << r->var);
+ vstamps[r->var] = stamp;
+ again = 1;
+ }
+ if (changed < 0)
+ return changed;
+ stamp++;
+ }
+ } while (again);
+ if (!params->msbits) {
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+ if (snd_interval_single(i))
+ params->msbits = snd_interval_value(i);
+ }
+
+ if (!params->rate_den) {
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ if (snd_interval_single(i)) {
+ params->rate_num = snd_interval_value(i);
+ params->rate_den = 1;
+ }
+ }
+
+ hw = &substream->runtime->hw;
+ if (!params->info)
+ params->info = hw->info;
+ if (!params->fifo_size)
+ params->fifo_size = hw->fifo_size;
+ params->rmask = 0;
+ return 0;
+}
+
+static int snd_pcm_hw_refine_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+ snd_pcm_hw_params_t *params;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(params, _params, sizeof(*params))) {
+ err = -EFAULT;
+ goto out;
+ }
+ err = snd_pcm_hw_refine(substream, params);
+ if (copy_to_user(_params, params, sizeof(*params))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ return err;
+}
+
+int snd_pcm_hw_params(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params)
+{
+ snd_pcm_runtime_t *runtime;
+ int err;
+ unsigned int bits;
+ snd_pcm_uframes_t frames;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_OPEN:
+ case SNDRV_PCM_STATE_SETUP:
+ case SNDRV_PCM_STATE_PREPARED:
+ break;
+ default:
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+ if (!substream->oss.oss)
+#endif
+ if (atomic_read(&runtime->mmap_count))
+ return -EBADFD;
+
+ params->rmask = ~0U;
+ err = snd_pcm_hw_refine(substream, params);
+ if (err < 0)
+ goto _error;
+
+ err = snd_pcm_hw_params_choose(substream, params);
+ if (err < 0)
+ goto _error;
+
+ if (substream->ops->hw_params != NULL) {
+ err = substream->ops->hw_params(substream, params);
+ if (err < 0)
+ goto _error;
+ }
+
+ runtime->access = params_access(params);
+ runtime->format = params_format(params);
+ runtime->subformat = params_subformat(params);
+ runtime->channels = params_channels(params);
+ runtime->rate = params_rate(params);
+ runtime->period_size = params_period_size(params);
+ runtime->periods = params_periods(params);
+ runtime->buffer_size = params_buffer_size(params);
+ runtime->tick_time = params_tick_time(params);
+ runtime->info = params->info;
+ runtime->rate_num = params->rate_num;
+ runtime->rate_den = params->rate_den;
+
+ bits = snd_pcm_format_physical_width(runtime->format);
+ runtime->sample_bits = bits;
+ bits *= runtime->channels;
+ runtime->frame_bits = bits;
+ frames = 1;
+ while (bits % 8 != 0) {
+ bits *= 2;
+ frames *= 2;
+ }
+ runtime->byte_align = bits / 8;
+ runtime->min_align = frames;
+
+ /* Default sw params */
+ runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+ runtime->period_step = 1;
+ runtime->sleep_min = 0;
+ runtime->control->avail_min = runtime->period_size;
+ runtime->xfer_align = runtime->period_size;
+ runtime->start_threshold = 1;
+ runtime->stop_threshold = runtime->buffer_size;
+ runtime->silence_threshold = 0;
+ runtime->silence_size = 0;
+ runtime->boundary = runtime->buffer_size;
+ while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
+ runtime->boundary *= 2;
+
+ snd_pcm_timer_resolution_change(substream);
+ runtime->status->state = SNDRV_PCM_STATE_SETUP;
+ return 0;
+ _error:
+ /* hardware might be unuseable from this time,
+ so we force application to retry to set
+ the correct hardware parameter settings */
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+ if (substream->ops->hw_free != NULL)
+ substream->ops->hw_free(substream);
+ return err;
+}
+
+static int snd_pcm_hw_params_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params)
+{
+ snd_pcm_hw_params_t *params;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(params, _params, sizeof(*params))) {
+ err = -EFAULT;
+ goto out;
+ }
+ err = snd_pcm_hw_params(substream, params);
+ if (copy_to_user(_params, params, sizeof(*params))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ return err;
+}
+
+static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
+{
+ snd_pcm_runtime_t *runtime;
+ int result = 0;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_SETUP:
+ case SNDRV_PCM_STATE_PREPARED:
+ break;
+ default:
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ if (atomic_read(&runtime->mmap_count))
+ return -EBADFD;
+ if (substream->ops->hw_free)
+ result = substream->ops->hw_free(substream);
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+ return result;
+}
+
+static int snd_pcm_sw_params(snd_pcm_substream_t * substream, snd_pcm_sw_params_t *params)
+{
+ snd_pcm_runtime_t *runtime;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -ENXIO);
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+
+ if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
+ return -EINVAL;
+ if (params->avail_min == 0)
+ return -EINVAL;
+ if (params->xfer_align == 0 ||
+ params->xfer_align % runtime->min_align != 0)
+ return -EINVAL;
+ if (params->silence_size >= runtime->boundary) {
+ if (params->silence_threshold != 0)
+ return -EINVAL;
+ } else {
+ if (params->silence_size > params->silence_threshold)
+ return -EINVAL;
+ if (params->silence_threshold > runtime->buffer_size)
+ return -EINVAL;
+ }
+ snd_pcm_stream_lock_irq(substream);
+ runtime->tstamp_mode = params->tstamp_mode;
+ runtime->sleep_min = params->sleep_min;
+ runtime->period_step = params->period_step;
+ runtime->control->avail_min = params->avail_min;
+ runtime->start_threshold = params->start_threshold;
+ runtime->stop_threshold = params->stop_threshold;
+ runtime->silence_threshold = params->silence_threshold;
+ runtime->silence_size = params->silence_size;
+ runtime->xfer_align = params->xfer_align;
+ params->boundary = runtime->boundary;
+ if (snd_pcm_running(substream)) {
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ else
+ snd_pcm_tick_set(substream, 0);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, ULONG_MAX);
+ wake_up(&runtime->sleep);
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ return 0;
+}
+
+static int snd_pcm_sw_params_user(snd_pcm_substream_t * substream, snd_pcm_sw_params_t __user * _params)
+{
+ snd_pcm_sw_params_t params;
+ int err;
+ if (copy_from_user(&params, _params, sizeof(params)))
+ return -EFAULT;
+ err = snd_pcm_sw_params(substream, &params);
+ if (copy_to_user(_params, &params, sizeof(params)))
+ return -EFAULT;
+ return err;
+}
+
+int snd_pcm_status(snd_pcm_substream_t *substream,
+ snd_pcm_status_t *status)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ snd_pcm_stream_lock_irq(substream);
+ status->state = runtime->status->state;
+ status->suspended_state = runtime->status->suspended_state;
+ if (status->state == SNDRV_PCM_STATE_OPEN)
+ goto _end;
+ status->trigger_tstamp = runtime->trigger_tstamp;
+ if (snd_pcm_running(substream)) {
+ snd_pcm_update_hw_ptr(substream);
+ if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP)
+ status->tstamp = runtime->status->tstamp;
+ else
+ snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+ } else
+ snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec);
+ status->appl_ptr = runtime->control->appl_ptr;
+ status->hw_ptr = runtime->status->hw_ptr;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ status->avail = snd_pcm_playback_avail(runtime);
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING ||
+ runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+ status->delay = runtime->buffer_size - status->avail;
+ else
+ status->delay = 0;
+ } else {
+ status->avail = snd_pcm_capture_avail(runtime);
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ status->delay = status->avail;
+ else
+ status->delay = 0;
+ }
+ status->avail_max = runtime->avail_max;
+ status->overrange = runtime->overrange;
+ runtime->avail_max = 0;
+ runtime->overrange = 0;
+ _end:
+ snd_pcm_stream_unlock_irq(substream);
+ return 0;
+}
+
+static int snd_pcm_status_user(snd_pcm_substream_t * substream, snd_pcm_status_t __user * _status)
+{
+ snd_pcm_status_t status;
+ snd_pcm_runtime_t *runtime;
+ int res;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ memset(&status, 0, sizeof(status));
+ res = snd_pcm_status(substream, &status);
+ if (res < 0)
+ return res;
+ if (copy_to_user(_status, &status, sizeof(status)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_pcm_channel_info(snd_pcm_substream_t * substream, snd_pcm_channel_info_t * info)
+{
+ snd_pcm_runtime_t *runtime;
+ unsigned int channel;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ channel = info->channel;
+ runtime = substream->runtime;
+ snd_pcm_stream_lock_irq(substream);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ snd_pcm_stream_unlock_irq(substream);
+ return -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ if (channel >= runtime->channels)
+ return -EINVAL;
+ memset(info, 0, sizeof(*info));
+ info->channel = channel;
+ return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
+}
+
+static int snd_pcm_channel_info_user(snd_pcm_substream_t * substream, snd_pcm_channel_info_t __user * _info)
+{
+ snd_pcm_channel_info_t info;
+ int res;
+
+ if (copy_from_user(&info, _info, sizeof(info)))
+ return -EFAULT;
+ res = snd_pcm_channel_info(substream, &info);
+ if (res < 0)
+ return res;
+ if (copy_to_user(_info, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master == NULL)
+ return;
+ if (runtime->trigger_master == substream) {
+ snd_timestamp_now(&runtime->trigger_tstamp, runtime->tstamp_timespec);
+ } else {
+ snd_pcm_trigger_tstamp(runtime->trigger_master);
+ runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp;
+ }
+ runtime->trigger_master = NULL;
+}
+
+struct action_ops {
+ int (*pre_action)(snd_pcm_substream_t *substream, int state);
+ int (*do_action)(snd_pcm_substream_t *substream, int state);
+ void (*undo_action)(snd_pcm_substream_t *substream, int state);
+ void (*post_action)(snd_pcm_substream_t *substream, int state);
+};
+
+/*
+ * this functions is core for handling of linked stream
+ * Note: the stream state might be changed also on failure
+ * Note2: call with calling stream lock + link lock
+ */
+static int snd_pcm_action_group(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state, int do_lock)
+{
+ struct list_head *pos;
+ snd_pcm_substream_t *s = NULL;
+ snd_pcm_substream_t *s1;
+ int res = 0;
+
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ if (do_lock && s != substream)
+ spin_lock(&s->self_group.lock);
+ res = ops->pre_action(s, state);
+ if (res < 0)
+ goto _unlock;
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ res = ops->do_action(s, state);
+ if (res < 0) {
+ if (ops->undo_action) {
+ snd_pcm_group_for_each(pos, substream) {
+ s1 = snd_pcm_group_substream_entry(pos);
+ if (s1 == s) /* failed stream */
+ break;
+ ops->undo_action(s1, state);
+ }
+ }
+ s = NULL; /* unlock all */
+ goto _unlock;
+ }
+ }
+ snd_pcm_group_for_each(pos, substream) {
+ s = snd_pcm_group_substream_entry(pos);
+ ops->post_action(s, state);
+ }
+ _unlock:
+ if (do_lock) {
+ /* unlock streams */
+ snd_pcm_group_for_each(pos, substream) {
+ s1 = snd_pcm_group_substream_entry(pos);
+ if (s1 != substream)
+ spin_unlock(&s1->self_group.lock);
+ if (s1 == s) /* end */
+ break;
+ }
+ }
+ return res;
+}
+
+/*
+ * Note: call with stream lock
+ */
+static int snd_pcm_action_single(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ res = ops->pre_action(substream, state);
+ if (res < 0)
+ return res;
+ res = ops->do_action(substream, state);
+ if (res == 0)
+ ops->post_action(substream, state);
+ else if (ops->undo_action)
+ ops->undo_action(substream, state);
+ return res;
+}
+
+/*
+ * Note: call with stream lock
+ */
+static int snd_pcm_action(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ if (snd_pcm_stream_linked(substream)) {
+ if (!spin_trylock(&substream->group->lock)) {
+ spin_unlock(&substream->self_group.lock);
+ spin_lock(&substream->group->lock);
+ spin_lock(&substream->self_group.lock);
+ }
+ res = snd_pcm_action_group(ops, substream, state, 1);
+ spin_unlock(&substream->group->lock);
+ } else {
+ res = snd_pcm_action_single(ops, substream, state);
+ }
+ return res;
+}
+
+/*
+ * Note: don't use any locks before
+ */
+static int snd_pcm_action_lock_irq(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ read_lock_irq(&snd_pcm_link_rwlock);
+ if (snd_pcm_stream_linked(substream)) {
+ spin_lock(&substream->group->lock);
+ spin_lock(&substream->self_group.lock);
+ res = snd_pcm_action_group(ops, substream, state, 1);
+ spin_unlock(&substream->self_group.lock);
+ spin_unlock(&substream->group->lock);
+ } else {
+ spin_lock(&substream->self_group.lock);
+ res = snd_pcm_action_single(ops, substream, state);
+ spin_unlock(&substream->self_group.lock);
+ }
+ read_unlock_irq(&snd_pcm_link_rwlock);
+ return res;
+}
+
+/*
+ */
+static int snd_pcm_action_nonatomic(struct action_ops *ops,
+ snd_pcm_substream_t *substream,
+ int state)
+{
+ int res;
+
+ down_read(&snd_pcm_link_rwsem);
+ if (snd_pcm_stream_linked(substream))
+ res = snd_pcm_action_group(ops, substream, state, 0);
+ else
+ res = snd_pcm_action_single(ops, substream, state);
+ up_read(&snd_pcm_link_rwsem);
+ return res;
+}
+
+/*
+ * start callbacks
+ */
+static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state != SNDRV_PCM_STATE_PREPARED)
+ return -EBADFD;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ !snd_pcm_playback_data(substream))
+ return -EPIPE;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master != substream)
+ return 0;
+ return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+}
+
+static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+}
+
+static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ runtime->status->state = state;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, ULONG_MAX);
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp);
+}
+
+static struct action_ops snd_pcm_action_start = {
+ .pre_action = snd_pcm_pre_start,
+ .do_action = snd_pcm_do_start,
+ .undo_action = snd_pcm_undo_start,
+ .post_action = snd_pcm_post_start
+};
+
+/**
+ * snd_pcm_start
+ *
+ * Start all linked streams.
+ */
+int snd_pcm_start(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+}
+
+/*
+ * stop callbacks
+ */
+static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+ return 0; /* unconditonally stop all substreams */
+}
+
+static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state != state) {
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
+ runtime->status->state = state;
+ snd_pcm_tick_set(substream, 0);
+ }
+ wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_stop = {
+ .pre_action = snd_pcm_pre_stop,
+ .do_action = snd_pcm_do_stop,
+ .post_action = snd_pcm_post_stop
+};
+
+/**
+ * snd_pcm_stop
+ *
+ * Try to stop all running streams in the substream group.
+ * The state of each stream is changed to the given value after that unconditionally.
+ */
+int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
+{
+ return snd_pcm_action(&snd_pcm_action_stop, substream, state);
+}
+
+/**
+ * snd_pcm_drain_done
+ *
+ * Stop the DMA only when the given stream is playback.
+ * The state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
+static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!(runtime->info & SNDRV_PCM_INFO_PAUSE))
+ return -ENOSYS;
+ if (push) {
+ if (runtime->status->state != SNDRV_PCM_STATE_RUNNING)
+ return -EBADFD;
+ } else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED)
+ return -EBADFD;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push)
+{
+ if (substream->runtime->trigger_master != substream)
+ return 0;
+ return substream->ops->trigger(substream,
+ push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH :
+ SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
+}
+
+static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push)
+{
+ if (substream->runtime->trigger_master == substream)
+ substream->ops->trigger(substream,
+ push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
+ SNDRV_PCM_TRIGGER_PAUSE_PUSH);
+}
+
+static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (push) {
+ runtime->status->state = SNDRV_PCM_STATE_PAUSED;
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+ snd_pcm_tick_set(substream, 0);
+ wake_up(&runtime->sleep);
+ } else {
+ runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);
+ }
+}
+
+static struct action_ops snd_pcm_action_pause = {
+ .pre_action = snd_pcm_pre_pause,
+ .do_action = snd_pcm_do_pause,
+ .undo_action = snd_pcm_undo_pause,
+ .post_action = snd_pcm_post_pause
+};
+
+/*
+ * Push/release the pause for all linked streams.
+ */
+static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
+{
+ return snd_pcm_action(&snd_pcm_action_pause, substream, push);
+}
+
+#ifdef CONFIG_PM
+/* suspend */
+
+static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+ return -EBUSY;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master != substream)
+ return 0;
+ if (! snd_pcm_running(substream))
+ return 0;
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+ return 0; /* suspend unconditionally */
+}
+
+static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+ runtime->status->suspended_state = runtime->status->state;
+ runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
+ snd_pcm_tick_set(substream, 0);
+ wake_up(&runtime->sleep);
+}
+
+static struct action_ops snd_pcm_action_suspend = {
+ .pre_action = snd_pcm_pre_suspend,
+ .do_action = snd_pcm_do_suspend,
+ .post_action = snd_pcm_post_suspend
+};
+
+/**
+ * snd_pcm_suspend
+ *
+ * Trigger SUSPEND to all linked streams.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend(snd_pcm_substream_t *substream)
+{
+ int err;
+ unsigned long flags;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+ err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0);
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+ return err;
+}
+
+/**
+ * snd_pcm_suspend_all
+ *
+ * Trigger SUSPEND to all substreams in the given pcm.
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend_all(snd_pcm_t *pcm)
+{
+ snd_pcm_substream_t *substream;
+ int stream, err = 0;
+
+ for (stream = 0; stream < 2; stream++) {
+ for (substream = pcm->streams[stream].substream; substream; substream = substream->next) {
+ /* FIXME: the open/close code should lock this as well */
+ if (substream->runtime == NULL)
+ continue;
+ err = snd_pcm_suspend(substream);
+ if (err < 0 && err != -EBUSY)
+ return err;
+ }
+ }
+ return 0;
+}
+
+/* resume */
+
+static int snd_pcm_pre_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
+ return -ENOSYS;
+ runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->trigger_master != substream)
+ return 0;
+ /* DMA not running previously? */
+ if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
+ (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
+ substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
+ return 0;
+ return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
+}
+
+static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state)
+{
+ if (substream->runtime->trigger_master == substream &&
+ snd_pcm_running(substream))
+ substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+}
+
+static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_trigger_tstamp(substream);
+ if (substream->timer)
+ snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp);
+ runtime->status->state = runtime->status->suspended_state;
+ if (runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+}
+
+static struct action_ops snd_pcm_action_resume = {
+ .pre_action = snd_pcm_pre_resume,
+ .do_action = snd_pcm_do_resume,
+ .undo_action = snd_pcm_undo_resume,
+ .post_action = snd_pcm_post_resume
+};
+
+static int snd_pcm_resume(snd_pcm_substream_t *substream)
+{
+ snd_card_t *card = substream->pcm->card;
+ int res;
+
+ snd_power_lock(card);
+ if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0)
+ res = snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0);
+ snd_power_unlock(card);
+ return res;
+}
+
+#else
+
+static int snd_pcm_resume(snd_pcm_substream_t *substream)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_PM */
+
+/*
+ * xrun ioctl
+ *
+ * Change the RUNNING stream(s) to XRUN state.
+ */
+static int snd_pcm_xrun(snd_pcm_substream_t *substream)
+{
+ snd_card_t *card = substream->pcm->card;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int result;
+
+ snd_power_lock(card);
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
+ }
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_XRUN:
+ result = 0; /* already there */
+ break;
+ case SNDRV_PCM_STATE_RUNNING:
+ result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+ break;
+ default:
+ result = -EBADFD;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ _unlock:
+ snd_power_unlock(card);
+ return result;
+}
+
+/*
+ * reset ioctl
+ */
+static int snd_pcm_pre_reset(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_RUNNING:
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_PAUSED:
+ case SNDRV_PCM_STATE_SUSPENDED:
+ return 0;
+ default:
+ return -EBADFD;
+ }
+}
+
+static int snd_pcm_do_reset(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
+ if (err < 0)
+ return err;
+ // snd_assert(runtime->status->hw_ptr < runtime->buffer_size, );
+ runtime->hw_ptr_base = 0;
+ runtime->hw_ptr_interrupt = runtime->status->hw_ptr - runtime->status->hw_ptr % runtime->period_size;
+ runtime->silence_start = runtime->status->hw_ptr;
+ runtime->silence_filled = 0;
+ return 0;
+}
+
+static void snd_pcm_post_reset(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ runtime->control->appl_ptr = runtime->status->hw_ptr;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream, ULONG_MAX);
+}
+
+static struct action_ops snd_pcm_action_reset = {
+ .pre_action = snd_pcm_pre_reset,
+ .do_action = snd_pcm_do_reset,
+ .post_action = snd_pcm_post_reset
+};
+
+static int snd_pcm_reset(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0);
+}
+
+/*
+ * prepare ioctl
+ */
+static int snd_pcm_pre_prepare(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (snd_pcm_running(substream))
+ return -EBUSY;
+ return 0;
+}
+
+static int snd_pcm_do_prepare(snd_pcm_substream_t * substream, int state)
+{
+ int err;
+ err = substream->ops->prepare(substream);
+ if (err < 0)
+ return err;
+ return snd_pcm_do_reset(substream, 0);
+}
+
+static void snd_pcm_post_prepare(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ runtime->control->appl_ptr = runtime->status->hw_ptr;
+ runtime->status->state = SNDRV_PCM_STATE_PREPARED;
+}
+
+static struct action_ops snd_pcm_action_prepare = {
+ .pre_action = snd_pcm_pre_prepare,
+ .do_action = snd_pcm_do_prepare,
+ .post_action = snd_pcm_post_prepare
+};
+
+/**
+ * snd_pcm_prepare
+ */
+int snd_pcm_prepare(snd_pcm_substream_t *substream)
+{
+ int res;
+ snd_card_t *card = substream->pcm->card;
+
+ snd_power_lock(card);
+ if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0)
+ res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, 0);
+ snd_power_unlock(card);
+ return res;
+}
+
+/*
+ * drain ioctl
+ */
+
+static int snd_pcm_pre_drain_init(snd_pcm_substream_t * substream, int state)
+{
+ if (substream->ffile->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ substream->runtime->trigger_master = substream;
+ return 0;
+}
+
+static int snd_pcm_do_drain_init(snd_pcm_substream_t * substream, int state)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ /* start playback stream if possible */
+ if (! snd_pcm_playback_empty(substream)) {
+ snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
+ }
+ break;
+ case SNDRV_PCM_STATE_RUNNING:
+ runtime->status->state = SNDRV_PCM_STATE_DRAINING;
+ break;
+ default:
+ break;
+ }
+ } else {
+ /* stop running stream */
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
+ int state = snd_pcm_capture_avail(runtime) > 0 ?
+ SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
+ snd_pcm_do_stop(substream, state);
+ snd_pcm_post_stop(substream, state);
+ }
+ }
+ return 0;
+}
+
+static void snd_pcm_post_drain_init(snd_pcm_substream_t * substream, int state)
+{
+}
+
+static struct action_ops snd_pcm_action_drain_init = {
+ .pre_action = snd_pcm_pre_drain_init,
+ .do_action = snd_pcm_do_drain_init,
+ .post_action = snd_pcm_post_drain_init
+};
+
+struct drain_rec {
+ snd_pcm_substream_t *substream;
+ wait_queue_t wait;
+ snd_pcm_uframes_t stop_threshold;
+};
+
+static int snd_pcm_drop(snd_pcm_substream_t *substream);
+
+/*
+ * Drain the stream(s).
+ * When the substream is linked, sync until the draining of all playback streams
+ * is finished.
+ * After this call, all streams are supposed to be either SETUP or DRAINING
+ * (capture only) state.
+ */
+static int snd_pcm_drain(snd_pcm_substream_t *substream)
+{
+ snd_card_t *card;
+ snd_pcm_runtime_t *runtime;
+ struct list_head *pos;
+ int result = 0;
+ int i, num_drecs;
+ struct drain_rec *drec, drec_tmp, *d;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ card = substream->pcm->card;
+ runtime = substream->runtime;
+
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ down_read(&snd_pcm_link_rwsem);
+ snd_power_lock(card);
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
+ }
+
+ /* allocate temporary record for drain sync */
+ if (snd_pcm_stream_linked(substream)) {
+ drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL);
+ if (! drec) {
+ result = -ENOMEM;
+ goto _unlock;
+ }
+ } else
+ drec = &drec_tmp;
+
+ snd_pcm_stream_lock_irq(substream);
+ /* resume pause */
+ if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
+ snd_pcm_pause(substream, 0);
+
+ /* pre-start/stop - all running streams are changed to DRAINING state */
+ result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0);
+ if (result < 0)
+ goto _end;
+
+ /* check streams with PLAYBACK & DRAINING */
+ num_drecs = 0;
+ snd_pcm_group_for_each(pos, substream) {
+ snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos);
+ runtime = s->runtime;
+ if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
+ runtime->status->state = SNDRV_PCM_STATE_SETUP;
+ continue;
+ }
+ if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ d = &drec[num_drecs++];
+ d->substream = s;
+ init_waitqueue_entry(&d->wait, current);
+ add_wait_queue(&runtime->sleep, &d->wait);
+ /* stop_threshold fixup to avoid endless loop when
+ * stop_threshold > buffer_size
+ */
+ d->stop_threshold = runtime->stop_threshold;
+ if (runtime->stop_threshold > runtime->buffer_size)
+ runtime->stop_threshold = runtime->buffer_size;
+ }
+ }
+
+ if (! num_drecs)
+ goto _end;
+
+ for (;;) {
+ long tout;
+ if (signal_pending(current)) {
+ result = -ERESTARTSYS;
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ snd_pcm_stream_unlock_irq(substream);
+ snd_power_unlock(card);
+ tout = schedule_timeout(10 * HZ);
+ snd_power_lock(card);
+ snd_pcm_stream_lock_irq(substream);
+ if (tout == 0) {
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+ result = -ESTRPIPE;
+ else {
+ snd_printd("playback drain error (DMA or IRQ trouble?)\n");
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ result = -EIO;
+ }
+ break;
+ }
+ /* all finished? */
+ for (i = 0; i < num_drecs; i++) {
+ runtime = drec[i].substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+ break;
+ }
+ if (i == num_drecs)
+ break;
+ }
+ for (i = 0; i < num_drecs; i++) {
+ d = &drec[i];
+ runtime = d->substream->runtime;
+ remove_wait_queue(&runtime->sleep, &d->wait);
+ runtime->stop_threshold = d->stop_threshold;
+ }
+
+ _end:
+ snd_pcm_stream_unlock_irq(substream);
+ if (drec != &drec_tmp)
+ kfree(drec);
+ _unlock:
+ snd_power_unlock(card);
+ up_read(&snd_pcm_link_rwsem);
+
+ return result;
+}
+
+/*
+ * drop ioctl
+ *
+ * Immediately put all linked substreams into SETUP state.
+ */
+static int snd_pcm_drop(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ snd_card_t *card;
+ int result = 0;
+
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ card = substream->pcm->card;
+
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+
+ snd_power_lock(card);
+ if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+ if (result < 0)
+ goto _unlock;
+ }
+
+ snd_pcm_stream_lock_irq(substream);
+ /* resume pause */
+ if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
+ snd_pcm_pause(substream, 0);
+
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ /* runtime->control->appl_ptr = runtime->status->hw_ptr; */
+ snd_pcm_stream_unlock_irq(substream);
+ _unlock:
+ snd_power_unlock(card);
+ return result;
+}
+
+
+/* WARNING: Don't forget to fput back the file */
+extern int snd_major;
+static struct file *snd_pcm_file_fd(int fd)
+{
+ struct file *file;
+ struct inode *inode;
+ unsigned short minor;
+ file = fget(fd);
+ if (!file)
+ return NULL;
+ inode = file->f_dentry->d_inode;
+ if (!S_ISCHR(inode->i_mode) ||
+ imajor(inode) != snd_major) {
+ fput(file);
+ return NULL;
+ }
+ minor = iminor(inode);
+ if (minor >= 256 ||
+ minor % SNDRV_MINOR_DEVICES < SNDRV_MINOR_PCM_PLAYBACK) {
+ fput(file);
+ return NULL;
+ }
+ return file;
+}
+
+/*
+ * PCM link handling
+ */
+static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
+{
+ int res = 0;
+ struct file *file;
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream1;
+
+ file = snd_pcm_file_fd(fd);
+ if (!file)
+ return -EBADFD;
+ pcm_file = file->private_data;
+ substream1 = pcm_file->substream;
+ down_write(&snd_pcm_link_rwsem);
+ write_lock_irq(&snd_pcm_link_rwlock);
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+ substream->runtime->status->state != substream1->runtime->status->state) {
+ res = -EBADFD;
+ goto _end;
+ }
+ if (snd_pcm_stream_linked(substream1)) {
+ res = -EALREADY;
+ goto _end;
+ }
+ if (!snd_pcm_stream_linked(substream)) {
+ substream->group = kmalloc(sizeof(snd_pcm_group_t), GFP_ATOMIC);
+ if (substream->group == NULL) {
+ res = -ENOMEM;
+ goto _end;
+ }
+ spin_lock_init(&substream->group->lock);
+ INIT_LIST_HEAD(&substream->group->substreams);
+ list_add_tail(&substream->link_list, &substream->group->substreams);
+ substream->group->count = 1;
+ }
+ list_add_tail(&substream1->link_list, &substream->group->substreams);
+ substream->group->count++;
+ substream1->group = substream->group;
+ _end:
+ write_unlock_irq(&snd_pcm_link_rwlock);
+ up_write(&snd_pcm_link_rwsem);
+ fput(file);
+ return res;
+}
+
+static void relink_to_local(snd_pcm_substream_t *substream)
+{
+ substream->group = &substream->self_group;
+ INIT_LIST_HEAD(&substream->self_group.substreams);
+ list_add_tail(&substream->link_list, &substream->self_group.substreams);
+}
+
+static int snd_pcm_unlink(snd_pcm_substream_t *substream)
+{
+ struct list_head *pos;
+ int res = 0;
+
+ down_write(&snd_pcm_link_rwsem);
+ write_lock_irq(&snd_pcm_link_rwlock);
+ if (!snd_pcm_stream_linked(substream)) {
+ res = -EALREADY;
+ goto _end;
+ }
+ list_del(&substream->link_list);
+ substream->group->count--;
+ if (substream->group->count == 1) { /* detach the last stream, too */
+ snd_pcm_group_for_each(pos, substream) {
+ relink_to_local(snd_pcm_group_substream_entry(pos));
+ break;
+ }
+ kfree(substream->group);
+ }
+ relink_to_local(substream);
+ _end:
+ write_unlock_irq(&snd_pcm_link_rwlock);
+ up_write(&snd_pcm_link_rwsem);
+ return res;
+}
+
+/*
+ * hw configurator
+ */
+static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ snd_interval_mul(hw_param_interval_c(params, rule->deps[0]),
+ hw_param_interval_c(params, rule->deps[1]), &t);
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_div(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ snd_interval_div(hw_param_interval_c(params, rule->deps[0]),
+ hw_param_interval_c(params, rule->deps[1]), &t);
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_muldivk(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]),
+ hw_param_interval_c(params, rule->deps[1]),
+ (unsigned long) rule->private, &t);
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_mulkdiv(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]),
+ (unsigned long) rule->private,
+ hw_param_interval_c(params, rule->deps[1]), &t);
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_format(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ unsigned int k;
+ snd_interval_t *i = hw_param_interval(params, rule->deps[0]);
+ snd_mask_t m;
+ snd_mask_t *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_any(&m);
+ for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) {
+ int bits;
+ if (! snd_mask_test(mask, k))
+ continue;
+ bits = snd_pcm_format_physical_width(k);
+ if (bits <= 0)
+ continue; /* ignore invalid formats */
+ if ((unsigned)bits < i->min || (unsigned)bits > i->max)
+ snd_mask_reset(&m, k);
+ }
+ return snd_mask_refine(mask, &m);
+}
+
+static int snd_pcm_hw_rule_sample_bits(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ unsigned int k;
+ t.min = UINT_MAX;
+ t.max = 0;
+ t.openmin = 0;
+ t.openmax = 0;
+ for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) {
+ int bits;
+ if (! snd_mask_test(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k))
+ continue;
+ bits = snd_pcm_format_physical_width(k);
+ if (bits <= 0)
+ continue; /* ignore invalid formats */
+ if (t.min > (unsigned)bits)
+ t.min = bits;
+ if (t.max < (unsigned)bits)
+ t.max = bits;
+ }
+ t.integer = 1;
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
+#error "Change this table"
+#endif
+
+static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
+ 48000, 64000, 88200, 96000, 176400, 192000 };
+
+static int snd_pcm_hw_rule_rate(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_pcm_hardware_t *hw = rule->private;
+ return snd_interval_list(hw_param_interval(params, rule->var),
+ ARRAY_SIZE(rates), rates, hw->rates);
+}
+
+static int snd_pcm_hw_rule_buffer_bytes_max(snd_pcm_hw_params_t *params,
+ snd_pcm_hw_rule_t *rule)
+{
+ snd_interval_t t;
+ snd_pcm_substream_t *substream = rule->private;
+ t.min = 0;
+ t.max = substream->buffer_bytes_max;
+ t.openmin = 0;
+ t.openmax = 0;
+ t.integer = 1;
+ return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+int snd_pcm_hw_constraints_init(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
+ int k, err;
+
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
+ snd_mask_any(constrs_mask(constrs, k));
+ }
+
+ for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
+ snd_interval_any(constrs_interval(constrs, k));
+ }
+
+ snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
+ snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
+ snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
+ snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
+ snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+ snd_pcm_hw_rule_format, NULL,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ snd_pcm_hw_rule_sample_bits, NULL,
+ SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ snd_pcm_hw_rule_div, NULL,
+ SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ snd_pcm_hw_rule_mul, NULL,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ snd_pcm_hw_rule_mulkdiv, (void*) 8,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ snd_pcm_hw_rule_mulkdiv, (void*) 8,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ snd_pcm_hw_rule_div, NULL,
+ SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS,
+ snd_pcm_hw_rule_div, NULL,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ snd_pcm_hw_rule_div, NULL,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ snd_pcm_hw_rule_mulkdiv, (void*) 8,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ snd_pcm_hw_rule_muldivk, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ snd_pcm_hw_rule_mul, NULL,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ snd_pcm_hw_rule_mulkdiv, (void*) 8,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ snd_pcm_hw_rule_muldivk, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ snd_pcm_hw_rule_muldivk, (void*) 8,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ snd_pcm_hw_rule_muldivk, (void*) 8,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+ snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+int snd_pcm_hw_constraints_complete(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_hardware_t *hw = &runtime->hw;
+ int err;
+ unsigned int mask = 0;
+
+ if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
+ mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+ if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
+ mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED;
+ if (hw->info & SNDRV_PCM_INFO_MMAP) {
+ if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
+ mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED;
+ if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
+ mask |= 1 << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED;
+ if (hw->info & SNDRV_PCM_INFO_COMPLEX)
+ mask |= 1 << SNDRV_PCM_ACCESS_MMAP_COMPLEX;
+ }
+ err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+ hw->channels_min, hw->channels_max);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
+ hw->rate_min, hw->rate_max);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ hw->period_bytes_min, hw->period_bytes_max);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
+ hw->periods_min, hw->periods_max);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ hw->period_bytes_min, hw->buffer_bytes_max);
+ snd_assert(err >= 0, return -EINVAL);
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ snd_pcm_hw_rule_buffer_bytes_max, substream,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1);
+ if (err < 0)
+ return err;
+
+ /* FIXME: remove */
+ if (runtime->dma_bytes) {
+ err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes);
+ snd_assert(err >= 0, return -EINVAL);
+ }
+
+ if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) {
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ snd_pcm_hw_rule_rate, hw,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+ }
+
+ /* FIXME: this belong to lowlevel */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_TICK_TIME,
+ 1000000 / HZ, 1000000 / HZ);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+
+ return 0;
+}
+
+static void snd_pcm_add_file(snd_pcm_str_t *str,
+ snd_pcm_file_t *pcm_file)
+{
+ pcm_file->next = str->files;
+ str->files = pcm_file;
+}
+
+static void snd_pcm_remove_file(snd_pcm_str_t *str,
+ snd_pcm_file_t *pcm_file)
+{
+ snd_pcm_file_t * pcm_file1;
+ if (str->files == pcm_file) {
+ str->files = pcm_file->next;
+ } else {
+ pcm_file1 = str->files;
+ while (pcm_file1 && pcm_file1->next != pcm_file)
+ pcm_file1 = pcm_file1->next;
+ if (pcm_file1 != NULL)
+ pcm_file1->next = pcm_file->next;
+ }
+}
+
+static int snd_pcm_release_file(snd_pcm_file_t * pcm_file)
+{
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_str_t * str;
+
+ snd_assert(pcm_file != NULL, return -ENXIO);
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ str = substream->pstr;
+ snd_pcm_unlink(substream);
+ if (substream->open_flag) {
+ if (substream->ops->hw_free != NULL)
+ substream->ops->hw_free(substream);
+ substream->ops->close(substream);
+ substream->open_flag = 0;
+ }
+ substream->ffile = NULL;
+ snd_pcm_remove_file(str, pcm_file);
+ snd_pcm_release_substream(substream);
+ kfree(pcm_file);
+ return 0;
+}
+
+static int snd_pcm_open_file(struct file *file,
+ snd_pcm_t *pcm,
+ int stream,
+ snd_pcm_file_t **rpcm_file)
+{
+ int err = 0;
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_str_t *str;
+
+ snd_assert(rpcm_file != NULL, return -EINVAL);
+ *rpcm_file = NULL;
+
+ pcm_file = kcalloc(1, sizeof(*pcm_file), GFP_KERNEL);
+ if (pcm_file == NULL) {
+ return -ENOMEM;
+ }
+
+ if ((err = snd_pcm_open_substream(pcm, stream, &substream)) < 0) {
+ kfree(pcm_file);
+ return err;
+ }
+
+ str = substream->pstr;
+ substream->file = pcm_file;
+ substream->no_mmap_ctrl = 0;
+
+ pcm_file->substream = substream;
+
+ snd_pcm_add_file(str, pcm_file);
+
+ err = snd_pcm_hw_constraints_init(substream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraints_init failed\n");
+ snd_pcm_release_file(pcm_file);
+ return err;
+ }
+
+ if ((err = substream->ops->open(substream)) < 0) {
+ snd_pcm_release_file(pcm_file);
+ return err;
+ }
+ substream->open_flag = 1;
+
+ err = snd_pcm_hw_constraints_complete(substream);
+ if (err < 0) {
+ snd_printd("snd_pcm_hw_constraints_complete failed\n");
+ substream->ops->close(substream);
+ snd_pcm_release_file(pcm_file);
+ return err;
+ }
+
+ substream->ffile = file;
+
+ file->private_data = pcm_file;
+ *rpcm_file = pcm_file;
+ return 0;
+}
+
+static int snd_pcm_open(struct inode *inode, struct file *file)
+{
+ int cardnum = SNDRV_MINOR_CARD(iminor(inode));
+ int device = SNDRV_MINOR_DEVICE(iminor(inode));
+ int err;
+ snd_pcm_t *pcm;
+ snd_pcm_file_t *pcm_file;
+ wait_queue_t wait;
+
+ snd_runtime_check(device >= SNDRV_MINOR_PCM_PLAYBACK && device < SNDRV_MINOR_DEVICES, return -ENXIO);
+ pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + (device % SNDRV_MINOR_PCMS)];
+ if (pcm == NULL) {
+ err = -ENODEV;
+ goto __error1;
+ }
+ err = snd_card_file_add(pcm->card, file);
+ if (err < 0)
+ goto __error1;
+ if (!try_module_get(pcm->card->module)) {
+ err = -EFAULT;
+ goto __error2;
+ }
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pcm->open_wait, &wait);
+ down(&pcm->open_mutex);
+ while (1) {
+ err = snd_pcm_open_file(file, pcm, device >= SNDRV_MINOR_PCM_CAPTURE ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, &pcm_file);
+ if (err >= 0)
+ break;
+ if (err == -EAGAIN) {
+ if (file->f_flags & O_NONBLOCK) {
+ err = -EBUSY;
+ break;
+ }
+ } else
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ up(&pcm->open_mutex);
+ schedule();
+ down(&pcm->open_mutex);
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ }
+ remove_wait_queue(&pcm->open_wait, &wait);
+ up(&pcm->open_mutex);
+ if (err < 0)
+ goto __error;
+ return err;
+
+ __error:
+ module_put(pcm->card->module);
+ __error2:
+ snd_card_file_remove(pcm->card, file);
+ __error1:
+ return err;
+}
+
+static int snd_pcm_release(struct inode *inode, struct file *file)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_substream_t *substream;
+ snd_pcm_file_t *pcm_file;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ snd_assert(!atomic_read(&substream->runtime->mmap_count), );
+ pcm = substream->pcm;
+ snd_pcm_drop(substream);
+ fasync_helper(-1, file, 0, &substream->runtime->fasync);
+ down(&pcm->open_mutex);
+ snd_pcm_release_file(pcm_file);
+ up(&pcm->open_mutex);
+ wake_up(&pcm->open_wait);
+ module_put(pcm->card->module);
+ snd_card_file_remove(pcm->card, file);
+ return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t appl_ptr;
+ snd_pcm_sframes_t ret;
+ snd_pcm_sframes_t hw_avail;
+
+ if (frames == 0)
+ return 0;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ break;
+ case SNDRV_PCM_STATE_DRAINING:
+ case SNDRV_PCM_STATE_RUNNING:
+ if (snd_pcm_update_hw_ptr(substream) >= 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_XRUN:
+ ret = -EPIPE;
+ goto __end;
+ default:
+ ret = -EBADFD;
+ goto __end;
+ }
+
+ hw_avail = snd_pcm_playback_hw_avail(runtime);
+ if (hw_avail <= 0) {
+ ret = 0;
+ goto __end;
+ }
+ if (frames > (snd_pcm_uframes_t)hw_avail)
+ frames = hw_avail;
+ else
+ frames -= frames % runtime->xfer_align;
+ appl_ptr = runtime->control->appl_ptr - frames;
+ if (appl_ptr < 0)
+ appl_ptr += runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+ runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ ret = frames;
+ __end:
+ snd_pcm_stream_unlock_irq(substream);
+ return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t appl_ptr;
+ snd_pcm_sframes_t ret;
+ snd_pcm_sframes_t hw_avail;
+
+ if (frames == 0)
+ return 0;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_DRAINING:
+ break;
+ case SNDRV_PCM_STATE_RUNNING:
+ if (snd_pcm_update_hw_ptr(substream) >= 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_XRUN:
+ ret = -EPIPE;
+ goto __end;
+ default:
+ ret = -EBADFD;
+ goto __end;
+ }
+
+ hw_avail = snd_pcm_capture_hw_avail(runtime);
+ if (hw_avail <= 0) {
+ ret = 0;
+ goto __end;
+ }
+ if (frames > (snd_pcm_uframes_t)hw_avail)
+ frames = hw_avail;
+ else
+ frames -= frames % runtime->xfer_align;
+ appl_ptr = runtime->control->appl_ptr - frames;
+ if (appl_ptr < 0)
+ appl_ptr += runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+ runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ ret = frames;
+ __end:
+ snd_pcm_stream_unlock_irq(substream);
+ return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t appl_ptr;
+ snd_pcm_sframes_t ret;
+ snd_pcm_sframes_t avail;
+
+ if (frames == 0)
+ return 0;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_PAUSED:
+ break;
+ case SNDRV_PCM_STATE_DRAINING:
+ case SNDRV_PCM_STATE_RUNNING:
+ if (snd_pcm_update_hw_ptr(substream) >= 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_XRUN:
+ ret = -EPIPE;
+ goto __end;
+ default:
+ ret = -EBADFD;
+ goto __end;
+ }
+
+ avail = snd_pcm_playback_avail(runtime);
+ if (avail <= 0) {
+ ret = 0;
+ goto __end;
+ }
+ if (frames > (snd_pcm_uframes_t)avail)
+ frames = avail;
+ else
+ frames -= frames % runtime->xfer_align;
+ appl_ptr = runtime->control->appl_ptr + frames;
+ if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
+ appl_ptr -= runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+ runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ ret = frames;
+ __end:
+ snd_pcm_stream_unlock_irq(substream);
+ return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t appl_ptr;
+ snd_pcm_sframes_t ret;
+ snd_pcm_sframes_t avail;
+
+ if (frames == 0)
+ return 0;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_DRAINING:
+ case SNDRV_PCM_STATE_PAUSED:
+ break;
+ case SNDRV_PCM_STATE_RUNNING:
+ if (snd_pcm_update_hw_ptr(substream) >= 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_XRUN:
+ ret = -EPIPE;
+ goto __end;
+ default:
+ ret = -EBADFD;
+ goto __end;
+ }
+
+ avail = snd_pcm_capture_avail(runtime);
+ if (avail <= 0) {
+ ret = 0;
+ goto __end;
+ }
+ if (frames > (snd_pcm_uframes_t)avail)
+ frames = avail;
+ else
+ frames -= frames % runtime->xfer_align;
+ appl_ptr = runtime->control->appl_ptr + frames;
+ if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
+ appl_ptr -= runtime->boundary;
+ runtime->control->appl_ptr = appl_ptr;
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING &&
+ runtime->sleep_min)
+ snd_pcm_tick_prepare(substream);
+ ret = frames;
+ __end:
+ snd_pcm_stream_unlock_irq(substream);
+ return ret;
+}
+
+static int snd_pcm_hwsync(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_DRAINING:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ goto __badfd;
+ case SNDRV_PCM_STATE_RUNNING:
+ if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = 0;
+ break;
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ break;
+ default:
+ __badfd:
+ err = -EBADFD;
+ break;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ return err;
+}
+
+static int snd_pcm_delay(snd_pcm_substream_t *substream, snd_pcm_sframes_t __user *res)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
+ snd_pcm_sframes_t n = 0;
+
+ snd_pcm_stream_lock_irq(substream);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_DRAINING:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ goto __badfd;
+ case SNDRV_PCM_STATE_RUNNING:
+ if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
+ break;
+ /* Fall through */
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_SUSPENDED:
+ err = 0;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ n = snd_pcm_playback_hw_avail(runtime);
+ else
+ n = snd_pcm_capture_avail(runtime);
+ break;
+ case SNDRV_PCM_STATE_XRUN:
+ err = -EPIPE;
+ break;
+ default:
+ __badfd:
+ err = -EBADFD;
+ break;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ if (!err)
+ if (put_user(n, res))
+ err = -EFAULT;
+ return err;
+}
+
+static int snd_pcm_sync_ptr(snd_pcm_substream_t *substream, struct sndrv_pcm_sync_ptr __user *_sync_ptr)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ struct sndrv_pcm_sync_ptr sync_ptr;
+ volatile struct sndrv_pcm_mmap_status *status;
+ volatile struct sndrv_pcm_mmap_control *control;
+ int err;
+
+ memset(&sync_ptr, 0, sizeof(sync_ptr));
+ if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags)))
+ return -EFAULT;
+ if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct sndrv_pcm_mmap_control)))
+ return -EFAULT;
+ status = runtime->status;
+ control = runtime->control;
+ if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+ err = snd_pcm_hwsync(substream);
+ if (err < 0)
+ return err;
+ }
+ snd_pcm_stream_lock_irq(substream);
+ if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL))
+ control->appl_ptr = sync_ptr.c.control.appl_ptr;
+ else
+ sync_ptr.c.control.appl_ptr = control->appl_ptr;
+ if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
+ control->avail_min = sync_ptr.c.control.avail_min;
+ else
+ sync_ptr.c.control.avail_min = control->avail_min;
+ sync_ptr.s.status.state = status->state;
+ sync_ptr.s.status.hw_ptr = status->hw_ptr;
+ sync_ptr.s.status.tstamp = status->tstamp;
+ sync_ptr.s.status.suspended_state = status->suspended_state;
+ snd_pcm_stream_unlock_irq(substream);
+ if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
+ unsigned int cmd, void __user *arg);
+static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
+ unsigned int cmd, void __user *arg);
+
+static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
+ unsigned int cmd, void __user *arg)
+{
+ snd_assert(substream != NULL, return -ENXIO);
+
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_PVERSION:
+ return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
+ case SNDRV_PCM_IOCTL_INFO:
+ return snd_pcm_info_user(substream, arg);
+ case SNDRV_PCM_IOCTL_TSTAMP:
+ {
+ int xarg;
+ if (get_user(xarg, (int __user *)arg))
+ return -EFAULT;
+ substream->runtime->tstamp_timespec = xarg ? 1 : 0;
+ return 0;
+ }
+ case SNDRV_PCM_IOCTL_HW_REFINE:
+ return snd_pcm_hw_refine_user(substream, arg);
+ case SNDRV_PCM_IOCTL_HW_PARAMS:
+ return snd_pcm_hw_params_user(substream, arg);
+ case SNDRV_PCM_IOCTL_HW_FREE:
+ return snd_pcm_hw_free(substream);
+ case SNDRV_PCM_IOCTL_SW_PARAMS:
+ return snd_pcm_sw_params_user(substream, arg);
+ case SNDRV_PCM_IOCTL_STATUS:
+ return snd_pcm_status_user(substream, arg);
+ case SNDRV_PCM_IOCTL_CHANNEL_INFO:
+ return snd_pcm_channel_info_user(substream, arg);
+ case SNDRV_PCM_IOCTL_PREPARE:
+ return snd_pcm_prepare(substream);
+ case SNDRV_PCM_IOCTL_RESET:
+ return snd_pcm_reset(substream);
+ case SNDRV_PCM_IOCTL_START:
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+ case SNDRV_PCM_IOCTL_LINK:
+ return snd_pcm_link(substream, (int)(unsigned long) arg);
+ case SNDRV_PCM_IOCTL_UNLINK:
+ return snd_pcm_unlink(substream);
+ case SNDRV_PCM_IOCTL_RESUME:
+ return snd_pcm_resume(substream);
+ case SNDRV_PCM_IOCTL_XRUN:
+ return snd_pcm_xrun(substream);
+ case SNDRV_PCM_IOCTL_HWSYNC:
+ return snd_pcm_hwsync(substream);
+ case SNDRV_PCM_IOCTL_DELAY:
+ return snd_pcm_delay(substream, arg);
+ case SNDRV_PCM_IOCTL_SYNC_PTR:
+ return snd_pcm_sync_ptr(substream, arg);
+ case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
+ return snd_pcm_hw_refine_old_user(substream, arg);
+ case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
+ return snd_pcm_hw_params_old_user(substream, arg);
+ case SNDRV_PCM_IOCTL_DRAIN:
+ return snd_pcm_drain(substream);
+ case SNDRV_PCM_IOCTL_DROP:
+ return snd_pcm_drop(substream);
+ }
+ snd_printd("unknown ioctl = 0x%x\n", cmd);
+ return -ENOTTY;
+}
+
+static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
+ unsigned int cmd, void __user *arg)
+{
+ snd_assert(substream != NULL, return -ENXIO);
+ snd_assert(substream->stream == SNDRV_PCM_STREAM_PLAYBACK, return -EINVAL);
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
+ {
+ snd_xferi_t xferi;
+ snd_xferi_t __user *_xferi = arg;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t result;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (put_user(0, &_xferi->result))
+ return -EFAULT;
+ if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
+ return -EFAULT;
+ result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
+ __put_user(result, &_xferi->result);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
+ {
+ snd_xfern_t xfern;
+ snd_xfern_t __user *_xfern = arg;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ void __user **bufs;
+ snd_pcm_sframes_t result;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (runtime->channels > 128)
+ return -EINVAL;
+ if (put_user(0, &_xfern->result))
+ return -EFAULT;
+ if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
+ return -EFAULT;
+ bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL);
+ if (bufs == NULL)
+ return -ENOMEM;
+ if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) {
+ kfree(bufs);
+ return -EFAULT;
+ }
+ result = snd_pcm_lib_writev(substream, bufs, xfern.frames);
+ kfree(bufs);
+ __put_user(result, &_xfern->result);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_REWIND:
+ {
+ snd_pcm_uframes_t frames;
+ snd_pcm_uframes_t __user *_frames = arg;
+ snd_pcm_sframes_t result;
+ if (get_user(frames, _frames))
+ return -EFAULT;
+ if (put_user(0, _frames))
+ return -EFAULT;
+ result = snd_pcm_playback_rewind(substream, frames);
+ __put_user(result, _frames);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_FORWARD:
+ {
+ snd_pcm_uframes_t frames;
+ snd_pcm_uframes_t __user *_frames = arg;
+ snd_pcm_sframes_t result;
+ if (get_user(frames, _frames))
+ return -EFAULT;
+ if (put_user(0, _frames))
+ return -EFAULT;
+ result = snd_pcm_playback_forward(substream, frames);
+ __put_user(result, _frames);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_PAUSE:
+ {
+ int res;
+ snd_pcm_stream_lock_irq(substream);
+ res = snd_pcm_pause(substream, (int)(unsigned long)arg);
+ snd_pcm_stream_unlock_irq(substream);
+ return res;
+ }
+ }
+ return snd_pcm_common_ioctl1(substream, cmd, arg);
+}
+
+static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
+ unsigned int cmd, void __user *arg)
+{
+ snd_assert(substream != NULL, return -ENXIO);
+ snd_assert(substream->stream == SNDRV_PCM_STREAM_CAPTURE, return -EINVAL);
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_READI_FRAMES:
+ {
+ snd_xferi_t xferi;
+ snd_xferi_t __user *_xferi = arg;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t result;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (put_user(0, &_xferi->result))
+ return -EFAULT;
+ if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
+ return -EFAULT;
+ result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
+ __put_user(result, &_xferi->result);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_READN_FRAMES:
+ {
+ snd_xfern_t xfern;
+ snd_xfern_t __user *_xfern = arg;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ void *bufs;
+ snd_pcm_sframes_t result;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (runtime->channels > 128)
+ return -EINVAL;
+ if (put_user(0, &_xfern->result))
+ return -EFAULT;
+ if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
+ return -EFAULT;
+ bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL);
+ if (bufs == NULL)
+ return -ENOMEM;
+ if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) {
+ kfree(bufs);
+ return -EFAULT;
+ }
+ result = snd_pcm_lib_readv(substream, bufs, xfern.frames);
+ kfree(bufs);
+ __put_user(result, &_xfern->result);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_REWIND:
+ {
+ snd_pcm_uframes_t frames;
+ snd_pcm_uframes_t __user *_frames = arg;
+ snd_pcm_sframes_t result;
+ if (get_user(frames, _frames))
+ return -EFAULT;
+ if (put_user(0, _frames))
+ return -EFAULT;
+ result = snd_pcm_capture_rewind(substream, frames);
+ __put_user(result, _frames);
+ return result < 0 ? result : 0;
+ }
+ case SNDRV_PCM_IOCTL_FORWARD:
+ {
+ snd_pcm_uframes_t frames;
+ snd_pcm_uframes_t __user *_frames = arg;
+ snd_pcm_sframes_t result;
+ if (get_user(frames, _frames))
+ return -EFAULT;
+ if (put_user(0, _frames))
+ return -EFAULT;
+ result = snd_pcm_capture_forward(substream, frames);
+ __put_user(result, _frames);
+ return result < 0 ? result : 0;
+ }
+ }
+ return snd_pcm_common_ioctl1(substream, cmd, arg);
+}
+
+static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_pcm_file_t *pcm_file;
+
+ pcm_file = file->private_data;
+
+ if (((cmd >> 8) & 0xff) != 'A')
+ return -ENOTTY;
+
+ return snd_pcm_playback_ioctl1(pcm_file->substream, cmd, (void __user *)arg);
+}
+
+static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_pcm_file_t *pcm_file;
+
+ pcm_file = file->private_data;
+
+ if (((cmd >> 8) & 0xff) != 'A')
+ return -ENOTTY;
+
+ return snd_pcm_capture_ioctl1(pcm_file->substream, cmd, (void __user *)arg);
+}
+
+int snd_pcm_kernel_playback_ioctl(snd_pcm_substream_t *substream,
+ unsigned int cmd, void *arg)
+{
+ mm_segment_t fs;
+ int result;
+
+ fs = snd_enter_user();
+ result = snd_pcm_playback_ioctl1(substream, cmd, (void __user *)arg);
+ snd_leave_user(fs);
+ return result;
+}
+
+int snd_pcm_kernel_capture_ioctl(snd_pcm_substream_t *substream,
+ unsigned int cmd, void *arg)
+{
+ mm_segment_t fs;
+ int result;
+
+ fs = snd_enter_user();
+ result = snd_pcm_capture_ioctl1(substream, cmd, (void __user *)arg);
+ snd_leave_user(fs);
+ return result;
+}
+
+int snd_pcm_kernel_ioctl(snd_pcm_substream_t *substream,
+ unsigned int cmd, void *arg)
+{
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ return snd_pcm_kernel_playback_ioctl(substream, cmd, arg);
+ case SNDRV_PCM_STREAM_CAPTURE:
+ return snd_pcm_kernel_capture_ioctl(substream, cmd, arg);
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t result;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (!frame_aligned(runtime, count))
+ return -EINVAL;
+ count = bytes_to_frames(runtime, count);
+ result = snd_pcm_lib_read(substream, buf, count);
+ if (result > 0)
+ result = frames_to_bytes(runtime, result);
+ return result;
+}
+
+static ssize_t snd_pcm_write(struct file *file, const char __user *buf, size_t count, loff_t * offset)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t result;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, result = -ENXIO; goto end);
+ runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ result = -EBADFD;
+ goto end;
+ }
+ if (!frame_aligned(runtime, count)) {
+ result = -EINVAL;
+ goto end;
+ }
+ count = bytes_to_frames(runtime, count);
+ result = snd_pcm_lib_write(substream, buf, count);
+ if (result > 0)
+ result = frames_to_bytes(runtime, result);
+ end:
+ return result;
+}
+
+static ssize_t snd_pcm_readv(struct file *file, const struct iovec *_vector,
+ unsigned long count, loff_t * offset)
+
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t result;
+ unsigned long i;
+ void __user **bufs;
+ snd_pcm_uframes_t frames;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (count > 1024 || count != runtime->channels)
+ return -EINVAL;
+ if (!frame_aligned(runtime, _vector->iov_len))
+ return -EINVAL;
+ frames = bytes_to_samples(runtime, _vector->iov_len);
+ bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL);
+ if (bufs == NULL)
+ return -ENOMEM;
+ for (i = 0; i < count; ++i)
+ bufs[i] = _vector[i].iov_base;
+ result = snd_pcm_lib_readv(substream, bufs, frames);
+ if (result > 0)
+ result = frames_to_bytes(runtime, result);
+ kfree(bufs);
+ return result;
+}
+
+static ssize_t snd_pcm_writev(struct file *file, const struct iovec *_vector,
+ unsigned long count, loff_t * offset)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_sframes_t result;
+ unsigned long i;
+ void __user **bufs;
+ snd_pcm_uframes_t frames;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, result = -ENXIO; goto end);
+ runtime = substream->runtime;
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ result = -EBADFD;
+ goto end;
+ }
+ if (count > 128 || count != runtime->channels ||
+ !frame_aligned(runtime, _vector->iov_len)) {
+ result = -EINVAL;
+ goto end;
+ }
+ frames = bytes_to_samples(runtime, _vector->iov_len);
+ bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL);
+ if (bufs == NULL)
+ return -ENOMEM;
+ for (i = 0; i < count; ++i)
+ bufs[i] = _vector[i].iov_base;
+ result = snd_pcm_lib_writev(substream, bufs, frames);
+ if (result > 0)
+ result = frames_to_bytes(runtime, result);
+ kfree(bufs);
+ end:
+ return result;
+}
+
+static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ unsigned int mask;
+ snd_pcm_uframes_t avail;
+
+ pcm_file = file->private_data;
+
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+
+ poll_wait(file, &runtime->sleep, wait);
+
+ snd_pcm_stream_lock_irq(substream);
+ avail = snd_pcm_playback_avail(runtime);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_RUNNING:
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_PAUSED:
+ if (avail >= runtime->control->avail_min) {
+ mask = POLLOUT | POLLWRNORM;
+ break;
+ }
+ /* Fall through */
+ case SNDRV_PCM_STATE_DRAINING:
+ mask = 0;
+ break;
+ default:
+ mask = POLLOUT | POLLWRNORM | POLLERR;
+ break;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ return mask;
+}
+
+static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait)
+{
+ snd_pcm_file_t *pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ unsigned int mask;
+ snd_pcm_uframes_t avail;
+
+ pcm_file = file->private_data;
+
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+
+ poll_wait(file, &runtime->sleep, wait);
+
+ snd_pcm_stream_lock_irq(substream);
+ avail = snd_pcm_capture_avail(runtime);
+ switch (runtime->status->state) {
+ case SNDRV_PCM_STATE_RUNNING:
+ case SNDRV_PCM_STATE_PREPARED:
+ case SNDRV_PCM_STATE_PAUSED:
+ if (avail >= runtime->control->avail_min) {
+ mask = POLLIN | POLLRDNORM;
+ break;
+ }
+ mask = 0;
+ break;
+ case SNDRV_PCM_STATE_DRAINING:
+ if (avail > 0) {
+ mask = POLLIN | POLLRDNORM;
+ break;
+ }
+ /* Fall through */
+ default:
+ mask = POLLIN | POLLRDNORM | POLLERR;
+ break;
+ }
+ snd_pcm_stream_unlock_irq(substream);
+ return mask;
+}
+
+/*
+ * mmap support
+ */
+
+/*
+ * Only on coherent architectures, we can mmap the status and the control records
+ * for effcient data transfer. On others, we have to use HWSYNC ioctl...
+ */
+#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA)
+/*
+ * mmap status record
+ */
+static struct page * snd_pcm_mmap_status_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+ snd_pcm_runtime_t *runtime;
+ struct page * page;
+
+ if (substream == NULL)
+ return NOPAGE_OOM;
+ runtime = substream->runtime;
+ page = virt_to_page(runtime->status);
+ if (!PageReserved(page))
+ get_page(page);
+ if (type)
+ *type = VM_FAULT_MINOR;
+ return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_status =
+{
+ .nopage = snd_pcm_mmap_status_nopage,
+};
+
+static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file,
+ struct vm_area_struct *area)
+{
+ snd_pcm_runtime_t *runtime;
+ long size;
+ if (!(area->vm_flags & VM_READ))
+ return -EINVAL;
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -EAGAIN);
+ size = area->vm_end - area->vm_start;
+ if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)))
+ return -EINVAL;
+ area->vm_ops = &snd_pcm_vm_ops_status;
+ area->vm_private_data = substream;
+ area->vm_flags |= VM_RESERVED;
+ return 0;
+}
+
+/*
+ * mmap control record
+ */
+static struct page * snd_pcm_mmap_control_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+ snd_pcm_runtime_t *runtime;
+ struct page * page;
+
+ if (substream == NULL)
+ return NOPAGE_OOM;
+ runtime = substream->runtime;
+ page = virt_to_page(runtime->control);
+ if (!PageReserved(page))
+ get_page(page);
+ if (type)
+ *type = VM_FAULT_MINOR;
+ return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_control =
+{
+ .nopage = snd_pcm_mmap_control_nopage,
+};
+
+static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file,
+ struct vm_area_struct *area)
+{
+ snd_pcm_runtime_t *runtime;
+ long size;
+ if (!(area->vm_flags & VM_READ))
+ return -EINVAL;
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -EAGAIN);
+ size = area->vm_end - area->vm_start;
+ if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t)))
+ return -EINVAL;
+ area->vm_ops = &snd_pcm_vm_ops_control;
+ area->vm_private_data = substream;
+ area->vm_flags |= VM_RESERVED;
+ return 0;
+}
+#else /* ! coherent mmap */
+/*
+ * don't support mmap for status and control records.
+ */
+static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file,
+ struct vm_area_struct *area)
+{
+ return -ENXIO;
+}
+static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file,
+ struct vm_area_struct *area)
+{
+ return -ENXIO;
+}
+#endif /* coherent mmap */
+
+/*
+ * nopage callback for mmapping a RAM page
+ */
+static struct page *snd_pcm_mmap_data_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+ snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data;
+ snd_pcm_runtime_t *runtime;
+ unsigned long offset;
+ struct page * page;
+ void *vaddr;
+ size_t dma_bytes;
+
+ if (substream == NULL)
+ return NOPAGE_OOM;
+ runtime = substream->runtime;
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ offset += address - area->vm_start;
+ snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
+ dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+ if (offset > dma_bytes - PAGE_SIZE)
+ return NOPAGE_SIGBUS;
+ if (substream->ops->page) {
+ page = substream->ops->page(substream, offset);
+ if (! page)
+ return NOPAGE_OOM;
+ } else {
+ vaddr = runtime->dma_area + offset;
+ page = virt_to_page(vaddr);
+ }
+ if (!PageReserved(page))
+ get_page(page);
+ if (type)
+ *type = VM_FAULT_MINOR;
+ return page;
+}
+
+static struct vm_operations_struct snd_pcm_vm_ops_data =
+{
+ .open = snd_pcm_mmap_data_open,
+ .close = snd_pcm_mmap_data_close,
+ .nopage = snd_pcm_mmap_data_nopage,
+};
+
+/*
+ * mmap the DMA buffer on RAM
+ */
+static int snd_pcm_default_mmap(snd_pcm_substream_t *substream, struct vm_area_struct *area)
+{
+ area->vm_ops = &snd_pcm_vm_ops_data;
+ area->vm_private_data = substream;
+ area->vm_flags |= VM_RESERVED;
+ atomic_inc(&substream->runtime->mmap_count);
+ return 0;
+}
+
+/*
+ * mmap the DMA buffer on I/O memory area
+ */
+#if SNDRV_PCM_INFO_MMAP_IOMEM
+static struct vm_operations_struct snd_pcm_vm_ops_data_mmio =
+{
+ .open = snd_pcm_mmap_data_open,
+ .close = snd_pcm_mmap_data_close,
+};
+
+int snd_pcm_lib_mmap_iomem(snd_pcm_substream_t *substream, struct vm_area_struct *area)
+{
+ long size;
+ unsigned long offset;
+
+#ifdef pgprot_noncached
+ area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
+#endif
+ area->vm_ops = &snd_pcm_vm_ops_data_mmio;
+ area->vm_private_data = substream;
+ area->vm_flags |= VM_IO;
+ size = area->vm_end - area->vm_start;
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ if (io_remap_pfn_range(area, area->vm_start,
+ (substream->runtime->dma_addr + offset) >> PAGE_SHIFT,
+ size, area->vm_page_prot))
+ return -EAGAIN;
+ atomic_inc(&substream->runtime->mmap_count);
+ return 0;
+}
+#endif /* SNDRV_PCM_INFO_MMAP */
+
+/*
+ * mmap DMA buffer
+ */
+int snd_pcm_mmap_data(snd_pcm_substream_t *substream, struct file *file,
+ struct vm_area_struct *area)
+{
+ snd_pcm_runtime_t *runtime;
+ long size;
+ unsigned long offset;
+ size_t dma_bytes;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (!(area->vm_flags & (VM_WRITE|VM_READ)))
+ return -EINVAL;
+ } else {
+ if (!(area->vm_flags & VM_READ))
+ return -EINVAL;
+ }
+ runtime = substream->runtime;
+ snd_assert(runtime != NULL, return -EAGAIN);
+ if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+ return -EBADFD;
+ if (!(runtime->info & SNDRV_PCM_INFO_MMAP))
+ return -ENXIO;
+ if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
+ return -EINVAL;
+ size = area->vm_end - area->vm_start;
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+ if ((size_t)size > dma_bytes)
+ return -EINVAL;
+ if (offset > dma_bytes - size)
+ return -EINVAL;
+
+ if (substream->ops->mmap)
+ return substream->ops->mmap(substream, area);
+ else
+ return snd_pcm_default_mmap(substream, area);
+}
+
+static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
+{
+ snd_pcm_file_t * pcm_file;
+ snd_pcm_substream_t *substream;
+ unsigned long offset;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ switch (offset) {
+ case SNDRV_PCM_MMAP_OFFSET_STATUS:
+ if (substream->no_mmap_ctrl)
+ return -ENXIO;
+ return snd_pcm_mmap_status(substream, file, area);
+ case SNDRV_PCM_MMAP_OFFSET_CONTROL:
+ if (substream->no_mmap_ctrl)
+ return -ENXIO;
+ return snd_pcm_mmap_control(substream, file, area);
+ default:
+ return snd_pcm_mmap_data(substream, file, area);
+ }
+ return 0;
+}
+
+static int snd_pcm_fasync(int fd, struct file * file, int on)
+{
+ snd_pcm_file_t * pcm_file;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ int err;
+
+ pcm_file = file->private_data;
+ substream = pcm_file->substream;
+ snd_assert(substream != NULL, return -ENXIO);
+ runtime = substream->runtime;
+
+ err = fasync_helper(fd, file, on, &runtime->fasync);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/*
+ * ioctl32 compat
+ */
+#ifdef CONFIG_COMPAT
+#include "pcm_compat.c"
+#else
+#define snd_pcm_ioctl_compat NULL
+#endif
+
+/*
+ * To be removed helpers to keep binary compatibility
+ */
+
+#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5))
+#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5))
+
+static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, struct sndrv_pcm_hw_params_old *oparams)
+{
+ unsigned int i;
+
+ memset(params, 0, sizeof(*params));
+ params->flags = oparams->flags;
+ for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
+ params->masks[i].bits[0] = oparams->masks[i];
+ memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals));
+ params->rmask = __OLD_TO_NEW_MASK(oparams->rmask);
+ params->cmask = __OLD_TO_NEW_MASK(oparams->cmask);
+ params->info = oparams->info;
+ params->msbits = oparams->msbits;
+ params->rate_num = oparams->rate_num;
+ params->rate_den = oparams->rate_den;
+ params->fifo_size = oparams->fifo_size;
+}
+
+static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams, snd_pcm_hw_params_t *params)
+{
+ unsigned int i;
+
+ memset(oparams, 0, sizeof(*oparams));
+ oparams->flags = params->flags;
+ for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
+ oparams->masks[i] = params->masks[i].bits[0];
+ memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals));
+ oparams->rmask = __NEW_TO_OLD_MASK(params->rmask);
+ oparams->cmask = __NEW_TO_OLD_MASK(params->cmask);
+ oparams->info = params->info;
+ oparams->msbits = params->msbits;
+ oparams->rate_num = params->rate_num;
+ oparams->rate_den = params->rate_den;
+ oparams->fifo_size = params->fifo_size;
+}
+
+static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams)
+{
+ snd_pcm_hw_params_t *params;
+ struct sndrv_pcm_hw_params_old *oparams = NULL;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ oparams = kmalloc(sizeof(*oparams), GFP_KERNEL);
+ if (!oparams) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (copy_from_user(oparams, _oparams, sizeof(*oparams))) {
+ err = -EFAULT;
+ goto out;
+ }
+ snd_pcm_hw_convert_from_old_params(params, oparams);
+ err = snd_pcm_hw_refine(substream, params);
+ snd_pcm_hw_convert_to_old_params(oparams, params);
+ if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ kfree(oparams);
+ return err;
+}
+
+static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams)
+{
+ snd_pcm_hw_params_t *params;
+ struct sndrv_pcm_hw_params_old *oparams = NULL;
+ int err;
+
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ oparams = kmalloc(sizeof(*oparams), GFP_KERNEL);
+ if (!oparams) {
+ err = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user(oparams, _oparams, sizeof(*oparams))) {
+ err = -EFAULT;
+ goto out;
+ }
+ snd_pcm_hw_convert_from_old_params(params, oparams);
+ err = snd_pcm_hw_params(substream, params);
+ snd_pcm_hw_convert_to_old_params(oparams, params);
+ if (copy_to_user(_oparams, oparams, sizeof(*oparams))) {
+ if (!err)
+ err = -EFAULT;
+ }
+out:
+ kfree(params);
+ kfree(oparams);
+ return err;
+}
+
+/*
+ * Register section
+ */
+
+static struct file_operations snd_pcm_f_ops_playback = {
+ .owner = THIS_MODULE,
+ .write = snd_pcm_write,
+ .writev = snd_pcm_writev,
+ .open = snd_pcm_open,
+ .release = snd_pcm_release,
+ .poll = snd_pcm_playback_poll,
+ .unlocked_ioctl = snd_pcm_playback_ioctl,
+ .compat_ioctl = snd_pcm_ioctl_compat,
+ .mmap = snd_pcm_mmap,
+ .fasync = snd_pcm_fasync,
+};
+
+static struct file_operations snd_pcm_f_ops_capture = {
+ .owner = THIS_MODULE,
+ .read = snd_pcm_read,
+ .readv = snd_pcm_readv,
+ .open = snd_pcm_open,
+ .release = snd_pcm_release,
+ .poll = snd_pcm_capture_poll,
+ .unlocked_ioctl = snd_pcm_capture_ioctl,
+ .compat_ioctl = snd_pcm_ioctl_compat,
+ .mmap = snd_pcm_mmap,
+ .fasync = snd_pcm_fasync,
+};
+
+snd_minor_t snd_pcm_reg[2] =
+{
+ {
+ .comment = "digital audio playback",
+ .f_ops = &snd_pcm_f_ops_playback,
+ },
+ {
+ .comment = "digital audio capture",
+ .f_ops = &snd_pcm_f_ops_capture,
+ }
+};
diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c
new file mode 100644
index 00000000000..884eaea31fe
--- /dev/null
+++ b/sound/core/pcm_timer.c
@@ -0,0 +1,161 @@
+/*
+ * Digital Audio (PCM) abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/timer.h>
+
+/*
+ * Timer functions
+ */
+
+/* Greatest common divisor */
+static unsigned long gcd(unsigned long a, unsigned long b)
+{
+ unsigned long r;
+ if (a < b) {
+ r = a;
+ a = b;
+ b = r;
+ }
+ while ((r = a % b) != 0) {
+ a = b;
+ b = r;
+ }
+ return b;
+}
+
+void snd_pcm_timer_resolution_change(snd_pcm_substream_t *substream)
+{
+ unsigned long rate, mult, fsize, l, post;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ mult = 1000000000;
+ rate = runtime->rate;
+ snd_assert(rate != 0, return);
+ l = gcd(mult, rate);
+ mult /= l;
+ rate /= l;
+ fsize = runtime->period_size;
+ snd_assert(fsize != 0, return);
+ l = gcd(rate, fsize);
+ rate /= l;
+ fsize /= l;
+ post = 1;
+ while ((mult * fsize) / fsize != mult) {
+ mult /= 2;
+ post *= 2;
+ }
+ if (rate == 0) {
+ snd_printk(KERN_ERR "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", runtime->rate, runtime->period_size);
+ runtime->timer_resolution = -1;
+ return;
+ }
+ runtime->timer_resolution = (mult * fsize / rate) * post;
+}
+
+static unsigned long snd_pcm_timer_resolution(snd_timer_t * timer)
+{
+ snd_pcm_substream_t * substream;
+
+ substream = timer->private_data;
+ return substream->runtime ? substream->runtime->timer_resolution : 0;
+}
+
+static int snd_pcm_timer_start(snd_timer_t * timer)
+{
+ unsigned long flags;
+ snd_pcm_substream_t * substream;
+
+ substream = snd_timer_chip(timer);
+ spin_lock_irqsave(&substream->timer_lock, flags);
+ substream->timer_running = 1;
+ spin_unlock_irqrestore(&substream->timer_lock, flags);
+ return 0;
+}
+
+static int snd_pcm_timer_stop(snd_timer_t * timer)
+{
+ unsigned long flags;
+ snd_pcm_substream_t * substream;
+
+ substream = snd_timer_chip(timer);
+ spin_lock_irqsave(&substream->timer_lock, flags);
+ substream->timer_running = 0;
+ spin_unlock_irqrestore(&substream->timer_lock, flags);
+ return 0;
+}
+
+static struct _snd_timer_hardware snd_pcm_timer =
+{
+ .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
+ .resolution = 0,
+ .ticks = 1,
+ .c_resolution = snd_pcm_timer_resolution,
+ .start = snd_pcm_timer_start,
+ .stop = snd_pcm_timer_stop,
+};
+
+/*
+ * Init functions
+ */
+
+static void snd_pcm_timer_free(snd_timer_t *timer)
+{
+ snd_pcm_substream_t *substream = timer->private_data;
+ substream->timer = NULL;
+}
+
+void snd_pcm_timer_init(snd_pcm_substream_t *substream)
+{
+ snd_timer_id_t tid;
+ snd_timer_t *timer;
+
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.dev_class = SNDRV_TIMER_CLASS_PCM;
+ tid.card = substream->pcm->card->number;
+ tid.device = substream->pcm->device;
+ tid.subdevice = (substream->number << 1) | (substream->stream & 1);
+ if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
+ return;
+ sprintf(timer->name, "PCM %s %i-%i-%i",
+ substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
+ "capture" : "playback",
+ tid.card, tid.device, tid.subdevice);
+ timer->hw = snd_pcm_timer;
+ if (snd_device_register(timer->card, timer) < 0) {
+ snd_device_free(timer->card, timer);
+ return;
+ }
+ timer->private_data = substream;
+ timer->private_free = snd_pcm_timer_free;
+ substream->timer = timer;
+}
+
+void snd_pcm_timer_done(snd_pcm_substream_t *substream)
+{
+ if (substream->timer) {
+ snd_device_free(substream->pcm->card, substream->timer);
+ substream->timer = NULL;
+ }
+}
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
new file mode 100644
index 00000000000..edba4118271
--- /dev/null
+++ b/sound/core/rawmidi.c
@@ -0,0 +1,1680 @@
+/*
+ * Abstract layer for MIDI v1.0 stream
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <sound/core.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <sound/rawmidi.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA.");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_OSSEMUL
+static int midi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int amidi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+module_param_array(midi_map, int, NULL, 0444);
+MODULE_PARM_DESC(midi_map, "Raw MIDI device number assigned to 1st OSS device.");
+module_param_array(amidi_map, int, NULL, 0444);
+MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device.");
+#endif /* CONFIG_SND_OSSEMUL */
+
+static int snd_rawmidi_free(snd_rawmidi_t *rawmidi);
+static int snd_rawmidi_dev_free(snd_device_t *device);
+static int snd_rawmidi_dev_register(snd_device_t *device);
+static int snd_rawmidi_dev_disconnect(snd_device_t *device);
+static int snd_rawmidi_dev_unregister(snd_device_t *device);
+
+static snd_rawmidi_t *snd_rawmidi_devices[SNDRV_CARDS * SNDRV_RAWMIDI_DEVICES];
+
+static DECLARE_MUTEX(register_mutex);
+
+static inline unsigned short snd_rawmidi_file_flags(struct file *file)
+{
+ switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
+ case FMODE_WRITE:
+ return SNDRV_RAWMIDI_LFLG_OUTPUT;
+ case FMODE_READ:
+ return SNDRV_RAWMIDI_LFLG_INPUT;
+ default:
+ return SNDRV_RAWMIDI_LFLG_OPEN;
+ }
+}
+
+static inline int snd_rawmidi_ready(snd_rawmidi_substream_t * substream)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+ return runtime->avail >= runtime->avail_min;
+}
+
+static inline int snd_rawmidi_ready_append(snd_rawmidi_substream_t * substream, size_t count)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+ return runtime->avail >= runtime->avail_min &&
+ (!substream->append || runtime->avail >= count);
+}
+
+static void snd_rawmidi_input_event_tasklet(unsigned long data)
+{
+ snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data;
+ substream->runtime->event(substream);
+}
+
+static void snd_rawmidi_output_trigger_tasklet(unsigned long data)
+{
+ snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data;
+ substream->ops->trigger(substream, 1);
+}
+
+static int snd_rawmidi_runtime_create(snd_rawmidi_substream_t * substream)
+{
+ snd_rawmidi_runtime_t *runtime;
+
+ if ((runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ spin_lock_init(&runtime->lock);
+ init_waitqueue_head(&runtime->sleep);
+ if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+ tasklet_init(&runtime->tasklet,
+ snd_rawmidi_input_event_tasklet,
+ (unsigned long)substream);
+ else
+ tasklet_init(&runtime->tasklet,
+ snd_rawmidi_output_trigger_tasklet,
+ (unsigned long)substream);
+ runtime->event = NULL;
+ runtime->buffer_size = PAGE_SIZE;
+ runtime->avail_min = 1;
+ if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+ runtime->avail = 0;
+ else
+ runtime->avail = runtime->buffer_size;
+ if ((runtime->buffer = kmalloc(runtime->buffer_size, GFP_KERNEL)) == NULL) {
+ kfree(runtime);
+ return -ENOMEM;
+ }
+ runtime->appl_ptr = runtime->hw_ptr = 0;
+ substream->runtime = runtime;
+ return 0;
+}
+
+static int snd_rawmidi_runtime_free(snd_rawmidi_substream_t * substream)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ kfree(runtime->buffer);
+ kfree(runtime);
+ substream->runtime = NULL;
+ return 0;
+}
+
+static inline void snd_rawmidi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ if (up) {
+ tasklet_hi_schedule(&substream->runtime->tasklet);
+ } else {
+ tasklet_kill(&substream->runtime->tasklet);
+ substream->ops->trigger(substream, 0);
+ }
+}
+
+static void snd_rawmidi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ substream->ops->trigger(substream, up);
+ if (!up && substream->runtime->event)
+ tasklet_kill(&substream->runtime->tasklet);
+}
+
+int snd_rawmidi_drop_output(snd_rawmidi_substream_t * substream)
+{
+ unsigned long flags;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ snd_rawmidi_output_trigger(substream, 0);
+ runtime->drain = 0;
+ spin_lock_irqsave(&runtime->lock, flags);
+ runtime->appl_ptr = runtime->hw_ptr = 0;
+ runtime->avail = runtime->buffer_size;
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return 0;
+}
+
+int snd_rawmidi_drain_output(snd_rawmidi_substream_t * substream)
+{
+ int err;
+ long timeout;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ err = 0;
+ runtime->drain = 1;
+ timeout = wait_event_interruptible_timeout(runtime->sleep,
+ (runtime->avail >= runtime->buffer_size),
+ 10*HZ);
+ if (signal_pending(current))
+ err = -ERESTARTSYS;
+ if (runtime->avail < runtime->buffer_size && !timeout) {
+ snd_printk(KERN_WARNING "rawmidi drain error (avail = %li, buffer_size = %li)\n", (long)runtime->avail, (long)runtime->buffer_size);
+ err = -EIO;
+ }
+ runtime->drain = 0;
+ if (err != -ERESTARTSYS) {
+ /* we need wait a while to make sure that Tx FIFOs are empty */
+ if (substream->ops->drain)
+ substream->ops->drain(substream);
+ else
+ msleep(50);
+ snd_rawmidi_drop_output(substream);
+ }
+ return err;
+}
+
+int snd_rawmidi_drain_input(snd_rawmidi_substream_t * substream)
+{
+ unsigned long flags;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ snd_rawmidi_input_trigger(substream, 0);
+ runtime->drain = 0;
+ spin_lock_irqsave(&runtime->lock, flags);
+ runtime->appl_ptr = runtime->hw_ptr = 0;
+ runtime->avail = 0;
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return 0;
+}
+
+int snd_rawmidi_kernel_open(int cardnum, int device, int subdevice,
+ int mode, snd_rawmidi_file_t * rfile)
+{
+ snd_rawmidi_t *rmidi;
+ struct list_head *list1, *list2;
+ snd_rawmidi_substream_t *sinput = NULL, *soutput = NULL;
+ snd_rawmidi_runtime_t *input = NULL, *output = NULL;
+ int err;
+
+ if (rfile)
+ rfile->input = rfile->output = NULL;
+ rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device];
+ if (rmidi == NULL) {
+ err = -ENODEV;
+ goto __error1;
+ }
+ if (!try_module_get(rmidi->card->module)) {
+ err = -EFAULT;
+ goto __error1;
+ }
+ if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+ down(&rmidi->open_mutex);
+ if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+ if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT)) {
+ err = -ENXIO;
+ goto __error;
+ }
+ if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) {
+ err = -ENODEV;
+ goto __error;
+ }
+ if (rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened >=
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) {
+ err = -EAGAIN;
+ goto __error;
+ }
+ }
+ if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+ if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT)) {
+ err = -ENXIO;
+ goto __error;
+ }
+ if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) {
+ err = -ENODEV;
+ goto __error;
+ }
+ if (rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened >=
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) {
+ err = -EAGAIN;
+ goto __error;
+ }
+ }
+ list1 = rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams.next;
+ while (1) {
+ if (list1 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+ sinput = NULL;
+ if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+ err = -EAGAIN;
+ goto __error;
+ }
+ break;
+ }
+ sinput = list_entry(list1, snd_rawmidi_substream_t, list);
+ if ((mode & SNDRV_RAWMIDI_LFLG_INPUT) && sinput->opened)
+ goto __nexti;
+ if (subdevice < 0 || (subdevice >= 0 && subdevice == sinput->number))
+ break;
+ __nexti:
+ list1 = list1->next;
+ }
+ list2 = rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams.next;
+ while (1) {
+ if (list2 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+ soutput = NULL;
+ if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+ err = -EAGAIN;
+ goto __error;
+ }
+ break;
+ }
+ soutput = list_entry(list2, snd_rawmidi_substream_t, list);
+ if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+ if (mode & SNDRV_RAWMIDI_LFLG_APPEND) {
+ if (soutput->opened && !soutput->append)
+ goto __nexto;
+ } else {
+ if (soutput->opened)
+ goto __nexto;
+ }
+ }
+ if (subdevice < 0 || (subdevice >= 0 && subdevice == soutput->number))
+ break;
+ __nexto:
+ list2 = list2->next;
+ }
+ if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+ if ((err = snd_rawmidi_runtime_create(sinput)) < 0)
+ goto __error;
+ input = sinput->runtime;
+ if ((err = sinput->ops->open(sinput)) < 0)
+ goto __error;
+ sinput->opened = 1;
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened++;
+ } else {
+ sinput = NULL;
+ }
+ if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+ if (soutput->opened)
+ goto __skip_output;
+ if ((err = snd_rawmidi_runtime_create(soutput)) < 0) {
+ if (mode & SNDRV_RAWMIDI_LFLG_INPUT)
+ sinput->ops->close(sinput);
+ goto __error;
+ }
+ output = soutput->runtime;
+ if ((err = soutput->ops->open(soutput)) < 0) {
+ if (mode & SNDRV_RAWMIDI_LFLG_INPUT)
+ sinput->ops->close(sinput);
+ goto __error;
+ }
+ __skip_output:
+ soutput->opened = 1;
+ if (mode & SNDRV_RAWMIDI_LFLG_APPEND)
+ soutput->append = 1;
+ if (soutput->use_count++ == 0)
+ soutput->active_sensing = 1;
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened++;
+ } else {
+ soutput = NULL;
+ }
+ if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+ up(&rmidi->open_mutex);
+ if (rfile) {
+ rfile->rmidi = rmidi;
+ rfile->input = sinput;
+ rfile->output = soutput;
+ }
+ return 0;
+
+ __error:
+ if (input != NULL)
+ snd_rawmidi_runtime_free(sinput);
+ if (output != NULL)
+ snd_rawmidi_runtime_free(soutput);
+ module_put(rmidi->card->module);
+ if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK))
+ up(&rmidi->open_mutex);
+ __error1:
+ return err;
+}
+
+static int snd_rawmidi_open(struct inode *inode, struct file *file)
+{
+ int maj = imajor(inode);
+ int cardnum;
+ snd_card_t *card;
+ int device, subdevice;
+ unsigned short fflags;
+ int err;
+ snd_rawmidi_t *rmidi;
+ snd_rawmidi_file_t *rawmidi_file;
+ wait_queue_t wait;
+ struct list_head *list;
+ snd_ctl_file_t *kctl;
+
+ switch (maj) {
+ case CONFIG_SND_MAJOR:
+ cardnum = SNDRV_MINOR_CARD(iminor(inode));
+ cardnum %= SNDRV_CARDS;
+ device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_RAWMIDI;
+ device %= SNDRV_MINOR_RAWMIDIS;
+ break;
+#ifdef CONFIG_SND_OSSEMUL
+ case SOUND_MAJOR:
+ cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode));
+ cardnum %= SNDRV_CARDS;
+ device = SNDRV_MINOR_OSS_DEVICE(iminor(inode)) == SNDRV_MINOR_OSS_MIDI ?
+ midi_map[cardnum] : amidi_map[cardnum];
+ break;
+#endif
+ default:
+ return -ENXIO;
+ }
+
+ rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device];
+ if (rmidi == NULL)
+ return -ENODEV;
+#ifdef CONFIG_SND_OSSEMUL
+ if (maj == SOUND_MAJOR && !rmidi->ossreg)
+ return -ENXIO;
+#endif
+ if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK))
+ return -EINVAL; /* invalid combination */
+ card = rmidi->card;
+ err = snd_card_file_add(card, file);
+ if (err < 0)
+ return -ENODEV;
+ fflags = snd_rawmidi_file_flags(file);
+ if ((file->f_flags & O_APPEND) || maj != CONFIG_SND_MAJOR) /* OSS emul? */
+ fflags |= SNDRV_RAWMIDI_LFLG_APPEND;
+ fflags |= SNDRV_RAWMIDI_LFLG_NOOPENLOCK;
+ rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL);
+ if (rawmidi_file == NULL) {
+ snd_card_file_remove(card, file);
+ return -ENOMEM;
+ }
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&rmidi->open_wait, &wait);
+ down(&rmidi->open_mutex);
+ while (1) {
+ subdevice = -1;
+ down_read(&card->controls_rwsem);
+ list_for_each(list, &card->ctl_files) {
+ kctl = snd_ctl_file(list);
+ if (kctl->pid == current->pid) {
+ subdevice = kctl->prefer_rawmidi_subdevice;
+ break;
+ }
+ }
+ up_read(&card->controls_rwsem);
+ err = snd_rawmidi_kernel_open(cardnum, device, subdevice, fflags, rawmidi_file);
+ if (err >= 0)
+ break;
+ if (err == -EAGAIN) {
+ if (file->f_flags & O_NONBLOCK) {
+ err = -EBUSY;
+ break;
+ }
+ } else
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ up(&rmidi->open_mutex);
+ schedule();
+ down(&rmidi->open_mutex);
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ if (rawmidi_file->input && rawmidi_file->input->runtime)
+ rawmidi_file->input->runtime->oss = (maj == SOUND_MAJOR);
+ if (rawmidi_file->output && rawmidi_file->output->runtime)
+ rawmidi_file->output->runtime->oss = (maj == SOUND_MAJOR);
+#endif
+ remove_wait_queue(&rmidi->open_wait, &wait);
+ if (err >= 0) {
+ file->private_data = rawmidi_file;
+ } else {
+ snd_card_file_remove(card, file);
+ kfree(rawmidi_file);
+ }
+ up(&rmidi->open_mutex);
+ return err;
+}
+
+int snd_rawmidi_kernel_release(snd_rawmidi_file_t * rfile)
+{
+ snd_rawmidi_t *rmidi;
+ snd_rawmidi_substream_t *substream;
+ snd_rawmidi_runtime_t *runtime;
+
+ snd_assert(rfile != NULL, return -ENXIO);
+ snd_assert(rfile->input != NULL || rfile->output != NULL, return -ENXIO);
+ rmidi = rfile->rmidi;
+ down(&rmidi->open_mutex);
+ if (rfile->input != NULL) {
+ substream = rfile->input;
+ rfile->input = NULL;
+ runtime = substream->runtime;
+ snd_rawmidi_input_trigger(substream, 0);
+ substream->ops->close(substream);
+ if (runtime->private_free != NULL)
+ runtime->private_free(substream);
+ snd_rawmidi_runtime_free(substream);
+ substream->opened = 0;
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened--;
+ }
+ if (rfile->output != NULL) {
+ substream = rfile->output;
+ rfile->output = NULL;
+ if (--substream->use_count == 0) {
+ runtime = substream->runtime;
+ if (substream->active_sensing) {
+ unsigned char buf = 0xfe;
+ /* sending single active sensing message to shut the device up */
+ snd_rawmidi_kernel_write(substream, &buf, 1);
+ }
+ if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS)
+ snd_rawmidi_output_trigger(substream, 0);
+ substream->ops->close(substream);
+ if (runtime->private_free != NULL)
+ runtime->private_free(substream);
+ snd_rawmidi_runtime_free(substream);
+ substream->opened = 0;
+ substream->append = 0;
+ }
+ rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened--;
+ }
+ up(&rmidi->open_mutex);
+ module_put(rmidi->card->module);
+ return 0;
+}
+
+static int snd_rawmidi_release(struct inode *inode, struct file *file)
+{
+ snd_rawmidi_file_t *rfile;
+ snd_rawmidi_t *rmidi;
+ int err;
+
+ rfile = file->private_data;
+ err = snd_rawmidi_kernel_release(rfile);
+ rmidi = rfile->rmidi;
+ wake_up(&rmidi->open_wait);
+ kfree(rfile);
+ snd_card_file_remove(rmidi->card, file);
+ return err;
+}
+
+int snd_rawmidi_info(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t *info)
+{
+ snd_rawmidi_t *rmidi;
+
+ if (substream == NULL)
+ return -ENODEV;
+ rmidi = substream->rmidi;
+ memset(info, 0, sizeof(*info));
+ info->card = rmidi->card->number;
+ info->device = rmidi->device;
+ info->subdevice = substream->number;
+ info->stream = substream->stream;
+ info->flags = rmidi->info_flags;
+ strcpy(info->id, rmidi->id);
+ strcpy(info->name, rmidi->name);
+ strcpy(info->subname, substream->name);
+ info->subdevices_count = substream->pstr->substream_count;
+ info->subdevices_avail = (substream->pstr->substream_count -
+ substream->pstr->substream_opened);
+ return 0;
+}
+
+static int snd_rawmidi_info_user(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t __user * _info)
+{
+ snd_rawmidi_info_t info;
+ int err;
+ if ((err = snd_rawmidi_info(substream, &info)) < 0)
+ return err;
+ if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t)))
+ return -EFAULT;
+ return 0;
+}
+
+int snd_rawmidi_info_select(snd_card_t *card, snd_rawmidi_info_t *info)
+{
+ snd_rawmidi_t *rmidi;
+ snd_rawmidi_str_t *pstr;
+ snd_rawmidi_substream_t *substream;
+ struct list_head *list;
+ if (info->device >= SNDRV_RAWMIDI_DEVICES)
+ return -ENXIO;
+ rmidi = snd_rawmidi_devices[card->number * SNDRV_RAWMIDI_DEVICES + info->device];
+ if (info->stream < 0 || info->stream > 1)
+ return -EINVAL;
+ pstr = &rmidi->streams[info->stream];
+ if (pstr->substream_count == 0)
+ return -ENOENT;
+ if (info->subdevice >= pstr->substream_count)
+ return -ENXIO;
+ list_for_each(list, &pstr->substreams) {
+ substream = list_entry(list, snd_rawmidi_substream_t, list);
+ if ((unsigned int)substream->number == info->subdevice)
+ return snd_rawmidi_info(substream, info);
+ }
+ return -ENXIO;
+}
+
+static int snd_rawmidi_info_select_user(snd_card_t *card,
+ snd_rawmidi_info_t __user *_info)
+{
+ int err;
+ snd_rawmidi_info_t info;
+ if (get_user(info.device, &_info->device))
+ return -EFAULT;
+ if (get_user(info.stream, &_info->stream))
+ return -EFAULT;
+ if (get_user(info.subdevice, &_info->subdevice))
+ return -EFAULT;
+ if ((err = snd_rawmidi_info_select(card, &info)) < 0)
+ return err;
+ if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t)))
+ return -EFAULT;
+ return 0;
+}
+
+int snd_rawmidi_output_params(snd_rawmidi_substream_t * substream,
+ snd_rawmidi_params_t * params)
+{
+ char *newbuf;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ if (substream->append && substream->use_count > 1)
+ return -EBUSY;
+ snd_rawmidi_drain_output(substream);
+ if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
+ return -EINVAL;
+ }
+ if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
+ return -EINVAL;
+ }
+ if (params->buffer_size != runtime->buffer_size) {
+ if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ kfree(runtime->buffer);
+ runtime->buffer = newbuf;
+ runtime->buffer_size = params->buffer_size;
+ }
+ runtime->avail_min = params->avail_min;
+ substream->active_sensing = !params->no_active_sensing;
+ return 0;
+}
+
+int snd_rawmidi_input_params(snd_rawmidi_substream_t * substream,
+ snd_rawmidi_params_t * params)
+{
+ char *newbuf;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ snd_rawmidi_drain_input(substream);
+ if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
+ return -EINVAL;
+ }
+ if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
+ return -EINVAL;
+ }
+ if (params->buffer_size != runtime->buffer_size) {
+ if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ kfree(runtime->buffer);
+ runtime->buffer = newbuf;
+ runtime->buffer_size = params->buffer_size;
+ }
+ runtime->avail_min = params->avail_min;
+ return 0;
+}
+
+static int snd_rawmidi_output_status(snd_rawmidi_substream_t * substream,
+ snd_rawmidi_status_t * status)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ memset(status, 0, sizeof(*status));
+ status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+ spin_lock_irq(&runtime->lock);
+ status->avail = runtime->avail;
+ spin_unlock_irq(&runtime->lock);
+ return 0;
+}
+
+static int snd_rawmidi_input_status(snd_rawmidi_substream_t * substream,
+ snd_rawmidi_status_t * status)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ memset(status, 0, sizeof(*status));
+ status->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+ spin_lock_irq(&runtime->lock);
+ status->avail = runtime->avail;
+ status->xruns = runtime->xruns;
+ runtime->xruns = 0;
+ spin_unlock_irq(&runtime->lock);
+ return 0;
+}
+
+static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_rawmidi_file_t *rfile;
+ void __user *argp = (void __user *)arg;
+
+ rfile = file->private_data;
+ if (((cmd >> 8) & 0xff) != 'W')
+ return -ENOTTY;
+ switch (cmd) {
+ case SNDRV_RAWMIDI_IOCTL_PVERSION:
+ return put_user(SNDRV_RAWMIDI_VERSION, (int __user *)argp) ? -EFAULT : 0;
+ case SNDRV_RAWMIDI_IOCTL_INFO:
+ {
+ snd_rawmidi_stream_t stream;
+ snd_rawmidi_info_t __user *info = argp;
+ if (get_user(stream, &info->stream))
+ return -EFAULT;
+ switch (stream) {
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ return snd_rawmidi_info_user(rfile->input, info);
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ return snd_rawmidi_info_user(rfile->output, info);
+ default:
+ return -EINVAL;
+ }
+ }
+ case SNDRV_RAWMIDI_IOCTL_PARAMS:
+ {
+ snd_rawmidi_params_t params;
+ if (copy_from_user(&params, argp, sizeof(snd_rawmidi_params_t)))
+ return -EFAULT;
+ switch (params.stream) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ if (rfile->output == NULL)
+ return -EINVAL;
+ return snd_rawmidi_output_params(rfile->output, &params);
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ if (rfile->input == NULL)
+ return -EINVAL;
+ return snd_rawmidi_input_params(rfile->input, &params);
+ default:
+ return -EINVAL;
+ }
+ }
+ case SNDRV_RAWMIDI_IOCTL_STATUS:
+ {
+ int err = 0;
+ snd_rawmidi_status_t status;
+ if (copy_from_user(&status, argp, sizeof(snd_rawmidi_status_t)))
+ return -EFAULT;
+ switch (status.stream) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ if (rfile->output == NULL)
+ return -EINVAL;
+ err = snd_rawmidi_output_status(rfile->output, &status);
+ break;
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ if (rfile->input == NULL)
+ return -EINVAL;
+ err = snd_rawmidi_input_status(rfile->input, &status);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+ if (copy_to_user(argp, &status, sizeof(snd_rawmidi_status_t)))
+ return -EFAULT;
+ return 0;
+ }
+ case SNDRV_RAWMIDI_IOCTL_DROP:
+ {
+ int val;
+ if (get_user(val, (int __user *) argp))
+ return -EFAULT;
+ switch (val) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ if (rfile->output == NULL)
+ return -EINVAL;
+ return snd_rawmidi_drop_output(rfile->output);
+ default:
+ return -EINVAL;
+ }
+ }
+ case SNDRV_RAWMIDI_IOCTL_DRAIN:
+ {
+ int val;
+ if (get_user(val, (int __user *) argp))
+ return -EFAULT;
+ switch (val) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ if (rfile->output == NULL)
+ return -EINVAL;
+ return snd_rawmidi_drain_output(rfile->output);
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ if (rfile->input == NULL)
+ return -EINVAL;
+ return snd_rawmidi_drain_input(rfile->input);
+ default:
+ return -EINVAL;
+ }
+ }
+#ifdef CONFIG_SND_DEBUG
+ default:
+ snd_printk(KERN_WARNING "rawmidi: unknown command = 0x%x\n", cmd);
+#endif
+ }
+ return -ENOTTY;
+}
+
+static int snd_rawmidi_control_ioctl(snd_card_t * card,
+ snd_ctl_file_t * control,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ unsigned int tmp;
+
+ tmp = card->number * SNDRV_RAWMIDI_DEVICES;
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE:
+ {
+ int device;
+
+ if (get_user(device, (int __user *)argp))
+ return -EFAULT;
+ device = device < 0 ? 0 : device + 1;
+ while (device < SNDRV_RAWMIDI_DEVICES) {
+ if (snd_rawmidi_devices[tmp + device])
+ break;
+ device++;
+ }
+ if (device == SNDRV_RAWMIDI_DEVICES)
+ device = -1;
+ if (put_user(device, (int __user *)argp))
+ return -EFAULT;
+ return 0;
+ }
+ case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE:
+ {
+ int val;
+
+ if (get_user(val, (int __user *)argp))
+ return -EFAULT;
+ control->prefer_rawmidi_subdevice = val;
+ return 0;
+ }
+ case SNDRV_CTL_IOCTL_RAWMIDI_INFO:
+ return snd_rawmidi_info_select_user(card, argp);
+ }
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * snd_rawmidi_receive - receive the input data from the device
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: the data size to read
+ *
+ * Reads the data from the internal buffer.
+ *
+ * Returns the size of read data, or a negative error code on failure.
+ */
+int snd_rawmidi_receive(snd_rawmidi_substream_t * substream, const unsigned char *buffer, int count)
+{
+ unsigned long flags;
+ int result = 0, count1;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ if (runtime->buffer == NULL) {
+ snd_printd("snd_rawmidi_receive: input is not active!!!\n");
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&runtime->lock, flags);
+ if (count == 1) { /* special case, faster code */
+ substream->bytes++;
+ if (runtime->avail < runtime->buffer_size) {
+ runtime->buffer[runtime->hw_ptr++] = buffer[0];
+ runtime->hw_ptr %= runtime->buffer_size;
+ runtime->avail++;
+ result++;
+ } else {
+ runtime->xruns++;
+ }
+ } else {
+ substream->bytes += count;
+ count1 = runtime->buffer_size - runtime->hw_ptr;
+ if (count1 > count)
+ count1 = count;
+ if (count1 > (int)(runtime->buffer_size - runtime->avail))
+ count1 = runtime->buffer_size - runtime->avail;
+ memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1);
+ runtime->hw_ptr += count1;
+ runtime->hw_ptr %= runtime->buffer_size;
+ runtime->avail += count1;
+ count -= count1;
+ result += count1;
+ if (count > 0) {
+ buffer += count1;
+ count1 = count;
+ if (count1 > (int)(runtime->buffer_size - runtime->avail)) {
+ count1 = runtime->buffer_size - runtime->avail;
+ runtime->xruns += count - count1;
+ }
+ if (count1 > 0) {
+ memcpy(runtime->buffer, buffer, count1);
+ runtime->hw_ptr = count1;
+ runtime->avail += count1;
+ result += count1;
+ }
+ }
+ }
+ if (result > 0) {
+ if (runtime->event)
+ tasklet_hi_schedule(&runtime->tasklet);
+ else if (snd_rawmidi_ready(substream))
+ wake_up(&runtime->sleep);
+ }
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return result;
+}
+
+static long snd_rawmidi_kernel_read1(snd_rawmidi_substream_t *substream,
+ unsigned char *buf, long count, int kernel)
+{
+ unsigned long flags;
+ long result = 0, count1;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ while (count > 0 && runtime->avail) {
+ count1 = runtime->buffer_size - runtime->appl_ptr;
+ if (count1 > count)
+ count1 = count;
+ spin_lock_irqsave(&runtime->lock, flags);
+ if (count1 > (int)runtime->avail)
+ count1 = runtime->avail;
+ if (kernel) {
+ memcpy(buf + result, runtime->buffer + runtime->appl_ptr, count1);
+ } else {
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ if (copy_to_user((char __user *)buf + result,
+ runtime->buffer + runtime->appl_ptr, count1)) {
+ return result > 0 ? result : -EFAULT;
+ }
+ spin_lock_irqsave(&runtime->lock, flags);
+ }
+ runtime->appl_ptr += count1;
+ runtime->appl_ptr %= runtime->buffer_size;
+ runtime->avail -= count1;
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ result += count1;
+ count -= count1;
+ }
+ return result;
+}
+
+long snd_rawmidi_kernel_read(snd_rawmidi_substream_t *substream, unsigned char *buf, long count)
+{
+ snd_rawmidi_input_trigger(substream, 1);
+ return snd_rawmidi_kernel_read1(substream, buf, count, 1);
+}
+
+static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ long result;
+ int count1;
+ snd_rawmidi_file_t *rfile;
+ snd_rawmidi_substream_t *substream;
+ snd_rawmidi_runtime_t *runtime;
+
+ rfile = file->private_data;
+ substream = rfile->input;
+ if (substream == NULL)
+ return -EIO;
+ runtime = substream->runtime;
+ snd_rawmidi_input_trigger(substream, 1);
+ result = 0;
+ while (count > 0) {
+ spin_lock_irq(&runtime->lock);
+ while (!snd_rawmidi_ready(substream)) {
+ wait_queue_t wait;
+ if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+ spin_unlock_irq(&runtime->lock);
+ return result > 0 ? result : -EAGAIN;
+ }
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&runtime->lock);
+ schedule();
+ remove_wait_queue(&runtime->sleep, &wait);
+ if (signal_pending(current))
+ return result > 0 ? result : -ERESTARTSYS;
+ if (!runtime->avail)
+ return result > 0 ? result : -EIO;
+ spin_lock_irq(&runtime->lock);
+ }
+ spin_unlock_irq(&runtime->lock);
+ count1 = snd_rawmidi_kernel_read1(substream, (unsigned char *)buf, count, 0);
+ if (count1 < 0)
+ return result > 0 ? result : count1;
+ result += count1;
+ buf += count1;
+ count -= count1;
+ }
+ return result;
+}
+
+/**
+ * snd_rawmidi_transmit_empty - check whether the output buffer is empty
+ * @substream: the rawmidi substream
+ *
+ * Returns 1 if the internal output buffer is empty, 0 if not.
+ */
+int snd_rawmidi_transmit_empty(snd_rawmidi_substream_t * substream)
+{
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+ int result;
+ unsigned long flags;
+
+ if (runtime->buffer == NULL) {
+ snd_printd("snd_rawmidi_transmit_empty: output is not active!!!\n");
+ return 1;
+ }
+ spin_lock_irqsave(&runtime->lock, flags);
+ result = runtime->avail >= runtime->buffer_size;
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return result;
+}
+
+/**
+ * snd_rawmidi_transmit_peek - copy data from the internal buffer
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: data size to transfer
+ *
+ * Copies data from the internal output buffer to the given buffer.
+ *
+ * Call this in the interrupt handler when the midi output is ready,
+ * and call snd_rawmidi_transmit_ack() after the transmission is
+ * finished.
+ *
+ * Returns the size of copied data, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit_peek(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
+{
+ unsigned long flags;
+ int result, count1;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ if (runtime->buffer == NULL) {
+ snd_printd("snd_rawmidi_transmit_peek: output is not active!!!\n");
+ return -EINVAL;
+ }
+ result = 0;
+ spin_lock_irqsave(&runtime->lock, flags);
+ if (runtime->avail >= runtime->buffer_size) {
+ /* warning: lowlevel layer MUST trigger down the hardware */
+ goto __skip;
+ }
+ if (count == 1) { /* special case, faster code */
+ *buffer = runtime->buffer[runtime->hw_ptr];
+ result++;
+ } else {
+ count1 = runtime->buffer_size - runtime->hw_ptr;
+ if (count1 > count)
+ count1 = count;
+ if (count1 > (int)(runtime->buffer_size - runtime->avail))
+ count1 = runtime->buffer_size - runtime->avail;
+ memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1);
+ count -= count1;
+ result += count1;
+ if (count > 0) {
+ if (count > (int)(runtime->buffer_size - runtime->avail - count1))
+ count = runtime->buffer_size - runtime->avail - count1;
+ memcpy(buffer + count1, runtime->buffer, count);
+ result += count;
+ }
+ }
+ __skip:
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return result;
+}
+
+/**
+ * snd_rawmidi_transmit_ack - acknowledge the transmission
+ * @substream: the rawmidi substream
+ * @count: the tranferred count
+ *
+ * Advances the hardware pointer for the internal output buffer with
+ * the given size and updates the condition.
+ * Call after the transmission is finished.
+ *
+ * Returns the advanced size if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit_ack(snd_rawmidi_substream_t * substream, int count)
+{
+ unsigned long flags;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ if (runtime->buffer == NULL) {
+ snd_printd("snd_rawmidi_transmit_ack: output is not active!!!\n");
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&runtime->lock, flags);
+ snd_assert(runtime->avail + count <= runtime->buffer_size, );
+ runtime->hw_ptr += count;
+ runtime->hw_ptr %= runtime->buffer_size;
+ runtime->avail += count;
+ substream->bytes += count;
+ if (count > 0) {
+ if (runtime->drain || snd_rawmidi_ready(substream))
+ wake_up(&runtime->sleep);
+ }
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return count;
+}
+
+/**
+ * snd_rawmidi_transmit - copy from the buffer to the device
+ * @substream: the rawmidi substream
+ * @buf: the buffer pointer
+ * @count: the data size to transfer
+ *
+ * Copies data from the buffer to the device and advances the pointer.
+ *
+ * Returns the copied size if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_transmit(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
+{
+ count = snd_rawmidi_transmit_peek(substream, buffer, count);
+ if (count < 0)
+ return count;
+ return snd_rawmidi_transmit_ack(substream, count);
+}
+
+static long snd_rawmidi_kernel_write1(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count, int kernel)
+{
+ unsigned long flags;
+ long count1, result;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+
+ snd_assert(buf != NULL, return -EINVAL);
+ snd_assert(runtime->buffer != NULL, return -EINVAL);
+
+ result = 0;
+ spin_lock_irqsave(&runtime->lock, flags);
+ if (substream->append) {
+ if ((long)runtime->avail < count) {
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ return -EAGAIN;
+ }
+ }
+ while (count > 0 && runtime->avail > 0) {
+ count1 = runtime->buffer_size - runtime->appl_ptr;
+ if (count1 > count)
+ count1 = count;
+ if (count1 > (long)runtime->avail)
+ count1 = runtime->avail;
+ if (kernel) {
+ memcpy(runtime->buffer + runtime->appl_ptr, buf, count1);
+ } else {
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ if (copy_from_user(runtime->buffer + runtime->appl_ptr,
+ (char __user *)buf, count1)) {
+ spin_lock_irqsave(&runtime->lock, flags);
+ result = result > 0 ? result : -EFAULT;
+ goto __end;
+ }
+ spin_lock_irqsave(&runtime->lock, flags);
+ }
+ runtime->appl_ptr += count1;
+ runtime->appl_ptr %= runtime->buffer_size;
+ runtime->avail -= count1;
+ result += count1;
+ buf += count1;
+ count -= count1;
+ }
+ __end:
+ count1 = runtime->avail < runtime->buffer_size;
+ spin_unlock_irqrestore(&runtime->lock, flags);
+ if (count1)
+ snd_rawmidi_output_trigger(substream, 1);
+ return result;
+}
+
+long snd_rawmidi_kernel_write(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count)
+{
+ return snd_rawmidi_kernel_write1(substream, buf, count, 1);
+}
+
+static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+ long result, timeout;
+ int count1;
+ snd_rawmidi_file_t *rfile;
+ snd_rawmidi_runtime_t *runtime;
+ snd_rawmidi_substream_t *substream;
+
+ rfile = file->private_data;
+ substream = rfile->output;
+ runtime = substream->runtime;
+ /* we cannot put an atomic message to our buffer */
+ if (substream->append && count > runtime->buffer_size)
+ return -EIO;
+ result = 0;
+ while (count > 0) {
+ spin_lock_irq(&runtime->lock);
+ while (!snd_rawmidi_ready_append(substream, count)) {
+ wait_queue_t wait;
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock_irq(&runtime->lock);
+ return result > 0 ? result : -EAGAIN;
+ }
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&runtime->lock);
+ timeout = schedule_timeout(30 * HZ);
+ remove_wait_queue(&runtime->sleep, &wait);
+ if (signal_pending(current))
+ return result > 0 ? result : -ERESTARTSYS;
+ if (!runtime->avail && !timeout)
+ return result > 0 ? result : -EIO;
+ spin_lock_irq(&runtime->lock);
+ }
+ spin_unlock_irq(&runtime->lock);
+ count1 = snd_rawmidi_kernel_write1(substream, (unsigned char *)buf, count, 0);
+ if (count1 < 0)
+ return result > 0 ? result : count1;
+ result += count1;
+ buf += count1;
+ if ((size_t)count1 < count && (file->f_flags & O_NONBLOCK))
+ break;
+ count -= count1;
+ }
+ if (file->f_flags & O_SYNC) {
+ spin_lock_irq(&runtime->lock);
+ while (runtime->avail != runtime->buffer_size) {
+ wait_queue_t wait;
+ unsigned int last_avail = runtime->avail;
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&runtime->lock);
+ timeout = schedule_timeout(30 * HZ);
+ remove_wait_queue(&runtime->sleep, &wait);
+ if (signal_pending(current))
+ return result > 0 ? result : -ERESTARTSYS;
+ if (runtime->avail == last_avail && !timeout)
+ return result > 0 ? result : -EIO;
+ spin_lock_irq(&runtime->lock);
+ }
+ spin_unlock_irq(&runtime->lock);
+ }
+ return result;
+}
+
+static unsigned int snd_rawmidi_poll(struct file *file, poll_table * wait)
+{
+ snd_rawmidi_file_t *rfile;
+ snd_rawmidi_runtime_t *runtime;
+ unsigned int mask;
+
+ rfile = file->private_data;
+ if (rfile->input != NULL) {
+ runtime = rfile->input->runtime;
+ snd_rawmidi_input_trigger(rfile->input, 1);
+ poll_wait(file, &runtime->sleep, wait);
+ }
+ if (rfile->output != NULL) {
+ runtime = rfile->output->runtime;
+ poll_wait(file, &runtime->sleep, wait);
+ }
+ mask = 0;
+ if (rfile->input != NULL) {
+ if (snd_rawmidi_ready(rfile->input))
+ mask |= POLLIN | POLLRDNORM;
+ }
+ if (rfile->output != NULL) {
+ if (snd_rawmidi_ready(rfile->output))
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ return mask;
+}
+
+/*
+ */
+#ifdef CONFIG_COMPAT
+#include "rawmidi_compat.c"
+#else
+#define snd_rawmidi_ioctl_compat NULL
+#endif
+
+/*
+
+ */
+
+static void snd_rawmidi_proc_info_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ snd_rawmidi_t *rmidi;
+ snd_rawmidi_substream_t *substream;
+ snd_rawmidi_runtime_t *runtime;
+ struct list_head *list;
+
+ rmidi = entry->private_data;
+ snd_iprintf(buffer, "%s\n\n", rmidi->name);
+ down(&rmidi->open_mutex);
+ if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) {
+ list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+ substream = list_entry(list, snd_rawmidi_substream_t, list);
+ snd_iprintf(buffer,
+ "Output %d\n"
+ " Tx bytes : %lu\n",
+ substream->number,
+ (unsigned long) substream->bytes);
+ if (substream->opened) {
+ runtime = substream->runtime;
+ snd_iprintf(buffer,
+ " Mode : %s\n"
+ " Buffer size : %lu\n"
+ " Avail : %lu\n",
+ runtime->oss ? "OSS compatible" : "native",
+ (unsigned long) runtime->buffer_size,
+ (unsigned long) runtime->avail);
+ }
+ }
+ }
+ if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT) {
+ list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+ substream = list_entry(list, snd_rawmidi_substream_t, list);
+ snd_iprintf(buffer,
+ "Input %d\n"
+ " Rx bytes : %lu\n",
+ substream->number,
+ (unsigned long) substream->bytes);
+ if (substream->opened) {
+ runtime = substream->runtime;
+ snd_iprintf(buffer,
+ " Buffer size : %lu\n"
+ " Avail : %lu\n"
+ " Overruns : %lu\n",
+ (unsigned long) runtime->buffer_size,
+ (unsigned long) runtime->avail,
+ (unsigned long) runtime->xruns);
+ }
+ }
+ }
+ up(&rmidi->open_mutex);
+}
+
+/*
+ * Register functions
+ */
+
+static struct file_operations snd_rawmidi_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = snd_rawmidi_read,
+ .write = snd_rawmidi_write,
+ .open = snd_rawmidi_open,
+ .release = snd_rawmidi_release,
+ .poll = snd_rawmidi_poll,
+ .unlocked_ioctl = snd_rawmidi_ioctl,
+ .compat_ioctl = snd_rawmidi_ioctl_compat,
+};
+
+static snd_minor_t snd_rawmidi_reg =
+{
+ .comment = "raw midi",
+ .f_ops = &snd_rawmidi_f_ops,
+};
+
+static int snd_rawmidi_alloc_substreams(snd_rawmidi_t *rmidi,
+ snd_rawmidi_str_t *stream,
+ int direction,
+ int count)
+{
+ snd_rawmidi_substream_t *substream;
+ int idx;
+
+ INIT_LIST_HEAD(&stream->substreams);
+ for (idx = 0; idx < count; idx++) {
+ substream = kcalloc(1, sizeof(*substream), GFP_KERNEL);
+ if (substream == NULL)
+ return -ENOMEM;
+ substream->stream = direction;
+ substream->number = idx;
+ substream->rmidi = rmidi;
+ substream->pstr = stream;
+ list_add_tail(&substream->list, &stream->substreams);
+ stream->substream_count++;
+ }
+ return 0;
+}
+
+/**
+ * snd_rawmidi_new - create a rawmidi instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index
+ * @output_count: the number of output streams
+ * @input_count: the number of input streams
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new rawmidi instance.
+ * Use snd_rawmidi_set_ops() to set the operators to the new instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_new(snd_card_t * card, char *id, int device,
+ int output_count, int input_count,
+ snd_rawmidi_t ** rrawmidi)
+{
+ snd_rawmidi_t *rmidi;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_rawmidi_dev_free,
+ .dev_register = snd_rawmidi_dev_register,
+ .dev_disconnect = snd_rawmidi_dev_disconnect,
+ .dev_unregister = snd_rawmidi_dev_unregister
+ };
+
+ snd_assert(rrawmidi != NULL, return -EINVAL);
+ *rrawmidi = NULL;
+ snd_assert(card != NULL, return -ENXIO);
+ rmidi = kcalloc(1, sizeof(*rmidi), GFP_KERNEL);
+ if (rmidi == NULL)
+ return -ENOMEM;
+ rmidi->card = card;
+ rmidi->device = device;
+ init_MUTEX(&rmidi->open_mutex);
+ init_waitqueue_head(&rmidi->open_wait);
+ if (id != NULL)
+ strlcpy(rmidi->id, id, sizeof(rmidi->id));
+ if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], SNDRV_RAWMIDI_STREAM_INPUT, input_count)) < 0) {
+ snd_rawmidi_free(rmidi);
+ return err;
+ }
+ if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], SNDRV_RAWMIDI_STREAM_OUTPUT, output_count)) < 0) {
+ snd_rawmidi_free(rmidi);
+ return err;
+ }
+ if ((err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops)) < 0) {
+ snd_rawmidi_free(rmidi);
+ return err;
+ }
+ *rrawmidi = rmidi;
+ return 0;
+}
+
+static void snd_rawmidi_free_substreams(snd_rawmidi_str_t *stream)
+{
+ snd_rawmidi_substream_t *substream;
+
+ while (!list_empty(&stream->substreams)) {
+ substream = list_entry(stream->substreams.next, snd_rawmidi_substream_t, list);
+ list_del(&substream->list);
+ kfree(substream);
+ }
+}
+
+static int snd_rawmidi_free(snd_rawmidi_t *rmidi)
+{
+ snd_assert(rmidi != NULL, return -ENXIO);
+ snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]);
+ snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]);
+ if (rmidi->private_free)
+ rmidi->private_free(rmidi);
+ kfree(rmidi);
+ return 0;
+}
+
+static int snd_rawmidi_dev_free(snd_device_t *device)
+{
+ snd_rawmidi_t *rmidi = device->device_data;
+ return snd_rawmidi_free(rmidi);
+}
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+static void snd_rawmidi_dev_seq_free(snd_seq_device_t *device)
+{
+ snd_rawmidi_t *rmidi = device->private_data;
+ rmidi->seq_dev = NULL;
+}
+#endif
+
+static int snd_rawmidi_dev_register(snd_device_t *device)
+{
+ int idx, err;
+ snd_info_entry_t *entry;
+ char name[16];
+ snd_rawmidi_t *rmidi = device->device_data;
+
+ if (rmidi->device >= SNDRV_RAWMIDI_DEVICES)
+ return -ENOMEM;
+ down(&register_mutex);
+ idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+ if (snd_rawmidi_devices[idx] != NULL) {
+ up(&register_mutex);
+ return -EBUSY;
+ }
+ snd_rawmidi_devices[idx] = rmidi;
+ sprintf(name, "midiC%iD%i", rmidi->card->number, rmidi->device);
+ if ((err = snd_register_device(SNDRV_DEVICE_TYPE_RAWMIDI,
+ rmidi->card, rmidi->device,
+ &snd_rawmidi_reg, name)) < 0) {
+ snd_printk(KERN_ERR "unable to register rawmidi device %i:%i\n", rmidi->card->number, rmidi->device);
+ snd_rawmidi_devices[idx] = NULL;
+ up(&register_mutex);
+ return err;
+ }
+ if (rmidi->ops && rmidi->ops->dev_register &&
+ (err = rmidi->ops->dev_register(rmidi)) < 0) {
+ snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device);
+ snd_rawmidi_devices[idx] = NULL;
+ up(&register_mutex);
+ return err;
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ rmidi->ossreg = 0;
+ if ((int)rmidi->device == midi_map[rmidi->card->number]) {
+ if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
+ rmidi->card, 0, &snd_rawmidi_reg, name) < 0) {
+ snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 0);
+ } else {
+ rmidi->ossreg++;
+#ifdef SNDRV_OSS_INFO_DEV_MIDI
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number, rmidi->name);
+#endif
+ }
+ }
+ if ((int)rmidi->device == amidi_map[rmidi->card->number]) {
+ if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
+ rmidi->card, 1, &snd_rawmidi_reg, name) < 0) {
+ snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 1);
+ } else {
+ rmidi->ossreg++;
+ }
+ }
+#endif /* CONFIG_SND_OSSEMUL */
+ up(&register_mutex);
+ sprintf(name, "midi%d", rmidi->device);
+ entry = snd_info_create_card_entry(rmidi->card, name, rmidi->card->proc_root);
+ if (entry) {
+ entry->private_data = rmidi;
+ entry->c.text.read_size = 1024;
+ entry->c.text.read = snd_rawmidi_proc_info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ rmidi->proc_entry = entry;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+ if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */
+ if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) {
+ rmidi->seq_dev->private_data = rmidi;
+ rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free;
+ sprintf(rmidi->seq_dev->name, "MIDI %d-%d", rmidi->card->number, rmidi->device);
+ snd_device_register(rmidi->card, rmidi->seq_dev);
+ }
+ }
+#endif
+ return 0;
+}
+
+static int snd_rawmidi_dev_disconnect(snd_device_t *device)
+{
+ snd_rawmidi_t *rmidi = device->device_data;
+ int idx;
+
+ down(&register_mutex);
+ idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+ snd_rawmidi_devices[idx] = NULL;
+ up(&register_mutex);
+ return 0;
+}
+
+static int snd_rawmidi_dev_unregister(snd_device_t *device)
+{
+ int idx;
+ snd_rawmidi_t *rmidi = device->device_data;
+
+ snd_assert(rmidi != NULL, return -ENXIO);
+ down(&register_mutex);
+ idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device;
+ snd_rawmidi_devices[idx] = NULL;
+ if (rmidi->proc_entry) {
+ snd_info_unregister(rmidi->proc_entry);
+ rmidi->proc_entry = NULL;
+ }
+#ifdef CONFIG_SND_OSSEMUL
+ if (rmidi->ossreg) {
+ if ((int)rmidi->device == midi_map[rmidi->card->number]) {
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0);
+#ifdef SNDRV_OSS_INFO_DEV_MIDI
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number);
+#endif
+ }
+ if ((int)rmidi->device == amidi_map[rmidi->card->number])
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1);
+ rmidi->ossreg = 0;
+ }
+#endif /* CONFIG_SND_OSSEMUL */
+ if (rmidi->ops && rmidi->ops->dev_unregister)
+ rmidi->ops->dev_unregister(rmidi);
+ snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device);
+ up(&register_mutex);
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+ if (rmidi->seq_dev) {
+ snd_device_free(rmidi->card, rmidi->seq_dev);
+ rmidi->seq_dev = NULL;
+ }
+#endif
+ return snd_rawmidi_free(rmidi);
+}
+
+/**
+ * snd_rawmidi_set_ops - set the rawmidi operators
+ * @rmidi: the rawmidi instance
+ * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the rawmidi operators for the given stream direction.
+ */
+void snd_rawmidi_set_ops(snd_rawmidi_t *rmidi, int stream, snd_rawmidi_ops_t *ops)
+{
+ struct list_head *list;
+ snd_rawmidi_substream_t *substream;
+
+ list_for_each(list, &rmidi->streams[stream].substreams) {
+ substream = list_entry(list, snd_rawmidi_substream_t, list);
+ substream->ops = ops;
+ }
+}
+
+/*
+ * ENTRY functions
+ */
+
+static int __init alsa_rawmidi_init(void)
+{
+
+ snd_ctl_register_ioctl(snd_rawmidi_control_ioctl);
+ snd_ctl_register_ioctl_compat(snd_rawmidi_control_ioctl);
+#ifdef CONFIG_SND_OSSEMUL
+ { int i;
+ /* check device map table */
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (midi_map[i] < 0 || midi_map[i] >= SNDRV_RAWMIDI_DEVICES) {
+ snd_printk(KERN_ERR "invalid midi_map[%d] = %d\n", i, midi_map[i]);
+ midi_map[i] = 0;
+ }
+ if (amidi_map[i] < 0 || amidi_map[i] >= SNDRV_RAWMIDI_DEVICES) {
+ snd_printk(KERN_ERR "invalid amidi_map[%d] = %d\n", i, amidi_map[i]);
+ amidi_map[i] = 1;
+ }
+ }
+ }
+#endif /* CONFIG_SND_OSSEMUL */
+ return 0;
+}
+
+static void __exit alsa_rawmidi_exit(void)
+{
+ snd_ctl_unregister_ioctl(snd_rawmidi_control_ioctl);
+ snd_ctl_unregister_ioctl_compat(snd_rawmidi_control_ioctl);
+}
+
+module_init(alsa_rawmidi_init)
+module_exit(alsa_rawmidi_exit)
+
+EXPORT_SYMBOL(snd_rawmidi_output_params);
+EXPORT_SYMBOL(snd_rawmidi_input_params);
+EXPORT_SYMBOL(snd_rawmidi_drop_output);
+EXPORT_SYMBOL(snd_rawmidi_drain_output);
+EXPORT_SYMBOL(snd_rawmidi_drain_input);
+EXPORT_SYMBOL(snd_rawmidi_receive);
+EXPORT_SYMBOL(snd_rawmidi_transmit_empty);
+EXPORT_SYMBOL(snd_rawmidi_transmit_peek);
+EXPORT_SYMBOL(snd_rawmidi_transmit_ack);
+EXPORT_SYMBOL(snd_rawmidi_transmit);
+EXPORT_SYMBOL(snd_rawmidi_new);
+EXPORT_SYMBOL(snd_rawmidi_set_ops);
+EXPORT_SYMBOL(snd_rawmidi_info);
+EXPORT_SYMBOL(snd_rawmidi_info_select);
+EXPORT_SYMBOL(snd_rawmidi_kernel_open);
+EXPORT_SYMBOL(snd_rawmidi_kernel_release);
+EXPORT_SYMBOL(snd_rawmidi_kernel_read);
+EXPORT_SYMBOL(snd_rawmidi_kernel_write);
diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c
new file mode 100644
index 00000000000..d97631c3f3a
--- /dev/null
+++ b/sound/core/rawmidi_compat.c
@@ -0,0 +1,120 @@
+/*
+ * 32bit -> 64bit ioctl wrapper for raw MIDI API
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ */
+
+/* This file included from rawmidi.c */
+
+#include <linux/compat.h>
+
+struct sndrv_rawmidi_params32 {
+ s32 stream;
+ u32 buffer_size;
+ u32 avail_min;
+ unsigned int no_active_sensing; /* avoid bit-field */
+ unsigned char reserved[16];
+} __attribute__((packed));
+
+static int snd_rawmidi_ioctl_params_compat(snd_rawmidi_file_t *rfile,
+ struct sndrv_rawmidi_params32 __user *src)
+{
+ snd_rawmidi_params_t params;
+ unsigned int val;
+
+ if (rfile->output == NULL)
+ return -EINVAL;
+ if (get_user(params.stream, &src->stream) ||
+ get_user(params.buffer_size, &src->buffer_size) ||
+ get_user(params.avail_min, &src->avail_min) ||
+ get_user(val, &src->no_active_sensing))
+ return -EFAULT;
+ params.no_active_sensing = val;
+ switch (params.stream) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ return snd_rawmidi_output_params(rfile->output, &params);
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ return snd_rawmidi_input_params(rfile->input, &params);
+ }
+ return -EINVAL;
+}
+
+struct sndrv_rawmidi_status32 {
+ s32 stream;
+ struct compat_timespec tstamp;
+ u32 avail;
+ u32 xruns;
+ unsigned char reserved[16];
+} __attribute__((packed));
+
+static int snd_rawmidi_ioctl_status_compat(snd_rawmidi_file_t *rfile,
+ struct sndrv_rawmidi_status32 __user *src)
+{
+ int err;
+ snd_rawmidi_status_t status;
+
+ if (rfile->output == NULL)
+ return -EINVAL;
+ if (get_user(status.stream, &src->stream))
+ return -EFAULT;
+
+ switch (status.stream) {
+ case SNDRV_RAWMIDI_STREAM_OUTPUT:
+ err = snd_rawmidi_output_status(rfile->output, &status);
+ break;
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ err = snd_rawmidi_input_status(rfile->input, &status);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+ put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
+ put_user(status.avail, &src->avail) ||
+ put_user(status.xruns, &src->xruns))
+ return -EFAULT;
+
+ return 0;
+}
+
+enum {
+ SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct sndrv_rawmidi_params32),
+ SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct sndrv_rawmidi_status32),
+};
+
+static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_rawmidi_file_t *rfile;
+ void __user *argp = compat_ptr(arg);
+
+ rfile = file->private_data;
+ switch (cmd) {
+ case SNDRV_RAWMIDI_IOCTL_PVERSION:
+ case SNDRV_RAWMIDI_IOCTL_INFO:
+ case SNDRV_RAWMIDI_IOCTL_DROP:
+ case SNDRV_RAWMIDI_IOCTL_DRAIN:
+ return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
+ case SNDRV_RAWMIDI_IOCTL_PARAMS32:
+ return snd_rawmidi_ioctl_params_compat(rfile, argp);
+ case SNDRV_RAWMIDI_IOCTL_STATUS32:
+ return snd_rawmidi_ioctl_status_compat(rfile, argp);
+ }
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/rtctimer.c b/sound/core/rtctimer.c
new file mode 100644
index 00000000000..bd5d584d284
--- /dev/null
+++ b/sound/core/rtctimer.c
@@ -0,0 +1,188 @@
+/*
+ * RTC based high-frequency timer
+ *
+ * Copyright (C) 2000 Takashi Iwai
+ * based on rtctimer.c by Steve Ratcliffe
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/threads.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/timer.h>
+#include <sound/info.h>
+
+#if defined(CONFIG_RTC) || defined(CONFIG_RTC_MODULE)
+
+#include <linux/mc146818rtc.h>
+
+#define RTC_FREQ 1024 /* default frequency */
+#define NANO_SEC 1000000000L /* 10^9 in sec */
+
+/*
+ * prototypes
+ */
+static int rtctimer_open(snd_timer_t *t);
+static int rtctimer_close(snd_timer_t *t);
+static int rtctimer_start(snd_timer_t *t);
+static int rtctimer_stop(snd_timer_t *t);
+
+
+/*
+ * The hardware dependent description for this timer.
+ */
+static struct _snd_timer_hardware rtc_hw = {
+ .flags = SNDRV_TIMER_HW_FIRST|SNDRV_TIMER_HW_AUTO,
+ .ticks = 100000000L, /* FIXME: XXX */
+ .open = rtctimer_open,
+ .close = rtctimer_close,
+ .start = rtctimer_start,
+ .stop = rtctimer_stop,
+};
+
+static int rtctimer_freq = RTC_FREQ; /* frequency */
+static snd_timer_t *rtctimer;
+static atomic_t rtc_inc = ATOMIC_INIT(0);
+static rtc_task_t rtc_task;
+
+
+static int
+rtctimer_open(snd_timer_t *t)
+{
+ int err;
+
+ err = rtc_register(&rtc_task);
+ if (err < 0)
+ return err;
+ t->private_data = &rtc_task;
+ return 0;
+}
+
+static int
+rtctimer_close(snd_timer_t *t)
+{
+ rtc_task_t *rtc = t->private_data;
+ if (rtc) {
+ rtc_unregister(rtc);
+ t->private_data = NULL;
+ }
+ return 0;
+}
+
+static int
+rtctimer_start(snd_timer_t *timer)
+{
+ rtc_task_t *rtc = timer->private_data;
+ snd_assert(rtc != NULL, return -EINVAL);
+ rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
+ rtc_control(rtc, RTC_PIE_ON, 0);
+ atomic_set(&rtc_inc, 0);
+ return 0;
+}
+
+static int
+rtctimer_stop(snd_timer_t *timer)
+{
+ rtc_task_t *rtc = timer->private_data;
+ snd_assert(rtc != NULL, return -EINVAL);
+ rtc_control(rtc, RTC_PIE_OFF, 0);
+ return 0;
+}
+
+/*
+ * interrupt
+ */
+static void rtctimer_interrupt(void *private_data)
+{
+ int ticks;
+
+ atomic_inc(&rtc_inc);
+ ticks = atomic_read(&rtc_inc);
+ snd_timer_interrupt((snd_timer_t*)private_data, ticks);
+ atomic_sub(ticks, &rtc_inc);
+}
+
+
+/*
+ * ENTRY functions
+ */
+static int __init rtctimer_init(void)
+{
+ int order, err;
+ snd_timer_t *timer;
+
+ if (rtctimer_freq < 2 || rtctimer_freq > 8192) {
+ snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
+ return -EINVAL;
+ }
+ for (order = 1; rtctimer_freq > order; order <<= 1)
+ ;
+ if (rtctimer_freq != order) {
+ snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq);
+ return -EINVAL;
+ }
+
+ /* Create a new timer and set up the fields */
+ err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
+ if (err < 0)
+ return err;
+
+ strcpy(timer->name, "RTC timer");
+ timer->hw = rtc_hw;
+ timer->hw.resolution = NANO_SEC / rtctimer_freq;
+
+ /* set up RTC callback */
+ rtc_task.func = rtctimer_interrupt;
+ rtc_task.private_data = timer;
+
+ err = snd_timer_global_register(timer);
+ if (err < 0) {
+ snd_timer_global_free(timer);
+ return err;
+ }
+ rtctimer = timer; /* remember this */
+
+ return 0;
+}
+
+static void __exit rtctimer_exit(void)
+{
+ if (rtctimer) {
+ snd_timer_global_unregister(rtctimer);
+ rtctimer = NULL;
+ }
+}
+
+
+/*
+ * exported stuff
+ */
+module_init(rtctimer_init)
+module_exit(rtctimer_exit)
+
+module_param(rtctimer_freq, int, 0444);
+MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
+
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
+
+#endif /* CONFIG_RTC || CONFIG_RTC_MODULE */
diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile
new file mode 100644
index 00000000000..64cb50d7b58
--- /dev/null
+++ b/sound/core/seq/Makefile
@@ -0,0 +1,44 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+obj-$(CONFIG_SND) += instr/
+ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
+ obj-$(CONFIG_SND_SEQUENCER) += oss/
+endif
+
+snd-seq-device-objs := seq_device.o
+snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
+ seq_fifo.o seq_prioq.o seq_timer.o \
+ seq_system.o seq_ports.o seq_info.o
+snd-seq-midi-objs := seq_midi.o
+snd-seq-midi-emul-objs := seq_midi_emul.o
+snd-seq-midi-event-objs := seq_midi_event.o
+snd-seq-instr-objs := seq_instr.o
+snd-seq-dummy-objs := seq_dummy.o
+snd-seq-virmidi-objs := seq_virmidi.o
+
+#
+# this function returns:
+# "m" - CONFIG_SND_SEQUENCER is m
+# <empty string> - CONFIG_SND_SEQUENCER is undefined
+# otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o snd-seq-device.o
+ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
+obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o
+endif
+obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o
+obj-$(call sequencer,$(CONFIG_SND_RAWMIDI)) += snd-seq-midi.o snd-seq-midi-event.o
+obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-seq-instr.o
+obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-seq-midi-emul.o snd-seq-virmidi.o
+obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-seq-midi-emul.o snd-seq-virmidi.o
+obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-seq-midi-emul.o snd-seq-instr.o
diff --git a/sound/core/seq/instr/Makefile b/sound/core/seq/instr/Makefile
new file mode 100644
index 00000000000..69138f30a29
--- /dev/null
+++ b/sound/core/seq/instr/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-ainstr-fm-objs := ainstr_fm.o
+snd-ainstr-simple-objs := ainstr_simple.o
+snd-ainstr-gf1-objs := ainstr_gf1.o
+snd-ainstr-iw-objs := ainstr_iw.o
+
+#
+# this function returns:
+# "m" - CONFIG_SND_SEQUENCER is m
+# <empty string> - CONFIG_SND_SEQUENCER is undefined
+# otherwise parameter #1 value
+#
+sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
+
+# Toplevel Module Dependency
+obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-ainstr-fm.o
+obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-ainstr-fm.o
+obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-ainstr-gf1.o snd-ainstr-simple.o snd-ainstr-iw.o
+obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-ainstr-simple.o
diff --git a/sound/core/seq/instr/ainstr_fm.c b/sound/core/seq/instr/ainstr_fm.c
new file mode 100644
index 00000000000..5c671e69884
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_fm.c
@@ -0,0 +1,156 @@
+/*
+ * FM (OPL2/3) Instrument routines
+ * Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/ainstr_fm.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture FM Instrument support.");
+MODULE_LICENSE("GPL");
+
+static int snd_seq_fm_put(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic, int cmd)
+{
+ fm_instrument_t *ip;
+ fm_xinstrument_t ix;
+ int idx;
+
+ if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+ return -EINVAL;
+ /* copy instrument data */
+ if (len < (long)sizeof(ix))
+ return -EINVAL;
+ if (copy_from_user(&ix, instr_data, sizeof(ix)))
+ return -EFAULT;
+ if (ix.stype != FM_STRU_INSTR)
+ return -EINVAL;
+ ip = (fm_instrument_t *)KINSTR_DATA(instr);
+ ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
+ ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
+ ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
+ ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
+ ip->type = ix.type;
+ for (idx = 0; idx < 4; idx++) {
+ ip->op[idx].am_vib = ix.op[idx].am_vib;
+ ip->op[idx].ksl_level = ix.op[idx].ksl_level;
+ ip->op[idx].attack_decay = ix.op[idx].attack_decay;
+ ip->op[idx].sustain_release = ix.op[idx].sustain_release;
+ ip->op[idx].wave_select = ix.op[idx].wave_select;
+ }
+ for (idx = 0; idx < 2; idx++) {
+ ip->feedback_connection[idx] = ix.feedback_connection[idx];
+ }
+ ip->echo_delay = ix.echo_delay;
+ ip->echo_atten = ix.echo_atten;
+ ip->chorus_spread = ix.chorus_spread;
+ ip->trnsps = ix.trnsps;
+ ip->fix_dur = ix.fix_dur;
+ ip->modes = ix.modes;
+ ip->fix_key = ix.fix_key;
+ return 0;
+}
+
+static int snd_seq_fm_get(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic,
+ int cmd)
+{
+ fm_instrument_t *ip;
+ fm_xinstrument_t ix;
+ int idx;
+
+ if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+ return -EINVAL;
+ if (len < (long)sizeof(ix))
+ return -ENOMEM;
+ memset(&ix, 0, sizeof(ix));
+ ip = (fm_instrument_t *)KINSTR_DATA(instr);
+ ix.stype = FM_STRU_INSTR;
+ ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
+ ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
+ ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
+ ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
+ ix.type = ip->type;
+ for (idx = 0; idx < 4; idx++) {
+ ix.op[idx].am_vib = ip->op[idx].am_vib;
+ ix.op[idx].ksl_level = ip->op[idx].ksl_level;
+ ix.op[idx].attack_decay = ip->op[idx].attack_decay;
+ ix.op[idx].sustain_release = ip->op[idx].sustain_release;
+ ix.op[idx].wave_select = ip->op[idx].wave_select;
+ }
+ for (idx = 0; idx < 2; idx++) {
+ ix.feedback_connection[idx] = ip->feedback_connection[idx];
+ }
+ if (copy_to_user(instr_data, &ix, sizeof(ix)))
+ return -EFAULT;
+ ix.echo_delay = ip->echo_delay;
+ ix.echo_atten = ip->echo_atten;
+ ix.chorus_spread = ip->chorus_spread;
+ ix.trnsps = ip->trnsps;
+ ix.fix_dur = ip->fix_dur;
+ ix.modes = ip->modes;
+ ix.fix_key = ip->fix_key;
+ return 0;
+}
+
+static int snd_seq_fm_get_size(void *private_data, snd_seq_kinstr_t *instr,
+ long *size)
+{
+ *size = sizeof(fm_xinstrument_t);
+ return 0;
+}
+
+int snd_seq_fm_init(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_ops_t *next)
+{
+ memset(ops, 0, sizeof(*ops));
+ // ops->private_data = private_data;
+ ops->add_len = sizeof(fm_instrument_t);
+ ops->instr_type = SNDRV_SEQ_INSTR_ID_OPL2_3;
+ ops->put = snd_seq_fm_put;
+ ops->get = snd_seq_fm_get;
+ ops->get_size = snd_seq_fm_get_size;
+ // ops->remove = snd_seq_fm_remove;
+ // ops->notify = snd_seq_fm_notify;
+ ops->next = next;
+ return 0;
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_ainstr_fm_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_ainstr_fm_exit(void)
+{
+}
+
+module_init(alsa_ainstr_fm_init)
+module_exit(alsa_ainstr_fm_exit)
+
+EXPORT_SYMBOL(snd_seq_fm_init);
diff --git a/sound/core/seq/instr/ainstr_gf1.c b/sound/core/seq/instr/ainstr_gf1.c
new file mode 100644
index 00000000000..0779c41ca03
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_gf1.c
@@ -0,0 +1,358 @@
+/*
+ * GF1 (GUS) Patch - Instrument routines
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_gf1.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture GF1 (GUS) Patch support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_gf1_size(unsigned int size, unsigned int format)
+{
+ unsigned int result = size;
+
+ if (format & GF1_WAVE_16BIT)
+ result <<= 1;
+ if (format & GF1_WAVE_STEREO)
+ result <<= 1;
+ return format;
+}
+
+static int snd_seq_gf1_copy_wave_from_stream(snd_gf1_ops_t *ops,
+ gf1_instrument_t *ip,
+ char __user **data,
+ long *len,
+ int atomic)
+{
+ gf1_wave_t *wp, *prev;
+ gf1_xwave_t xp;
+ int err, gfp_mask;
+ unsigned int real_size;
+
+ gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+ if (*len < (long)sizeof(xp))
+ return -EINVAL;
+ if (copy_from_user(&xp, *data, sizeof(xp)))
+ return -EFAULT;
+ *data += sizeof(xp);
+ *len -= sizeof(xp);
+ wp = kcalloc(1, sizeof(*wp), gfp_mask);
+ if (wp == NULL)
+ return -ENOMEM;
+ wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
+ wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
+ wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
+ wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
+ wp->format = le32_to_cpu(xp.format);
+ wp->size = le32_to_cpu(xp.size);
+ wp->start = le32_to_cpu(xp.start);
+ wp->loop_start = le32_to_cpu(xp.loop_start);
+ wp->loop_end = le32_to_cpu(xp.loop_end);
+ wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
+ wp->flags = xp.flags;
+ wp->sample_rate = le32_to_cpu(xp.sample_rate);
+ wp->low_frequency = le32_to_cpu(xp.low_frequency);
+ wp->high_frequency = le32_to_cpu(xp.high_frequency);
+ wp->root_frequency = le32_to_cpu(xp.root_frequency);
+ wp->tune = le16_to_cpu(xp.tune);
+ wp->balance = xp.balance;
+ memcpy(wp->envelope_rate, xp.envelope_rate, 6);
+ memcpy(wp->envelope_offset, xp.envelope_offset, 6);
+ wp->tremolo_sweep = xp.tremolo_sweep;
+ wp->tremolo_rate = xp.tremolo_rate;
+ wp->tremolo_depth = xp.tremolo_depth;
+ wp->vibrato_sweep = xp.vibrato_sweep;
+ wp->vibrato_rate = xp.vibrato_rate;
+ wp->vibrato_depth = xp.vibrato_depth;
+ wp->scale_frequency = le16_to_cpu(xp.scale_frequency);
+ wp->scale_factor = le16_to_cpu(xp.scale_factor);
+ real_size = snd_seq_gf1_size(wp->size, wp->format);
+ if ((long)real_size > *len) {
+ kfree(wp);
+ return -ENOMEM;
+ }
+ if (ops->put_sample) {
+ err = ops->put_sample(ops->private_data, wp,
+ *data, real_size, atomic);
+ if (err < 0) {
+ kfree(wp);
+ return err;
+ }
+ }
+ *data += real_size;
+ *len -= real_size;
+ prev = ip->wave;
+ if (prev) {
+ while (prev->next) prev = prev->next;
+ prev->next = wp;
+ } else {
+ ip->wave = wp;
+ }
+ return 0;
+}
+
+static void snd_seq_gf1_wave_free(snd_gf1_ops_t *ops,
+ gf1_wave_t *wave,
+ int atomic)
+{
+ if (ops->remove_sample)
+ ops->remove_sample(ops->private_data, wave, atomic);
+ kfree(wave);
+}
+
+static void snd_seq_gf1_instr_free(snd_gf1_ops_t *ops,
+ gf1_instrument_t *ip,
+ int atomic)
+{
+ gf1_wave_t *wave;
+
+ while ((wave = ip->wave) != NULL) {
+ ip->wave = wave->next;
+ snd_seq_gf1_wave_free(ops, wave, atomic);
+ }
+}
+
+static int snd_seq_gf1_put(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic,
+ int cmd)
+{
+ snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+ gf1_instrument_t *ip;
+ gf1_xinstrument_t ix;
+ int err, gfp_mask;
+
+ if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+ return -EINVAL;
+ gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+ /* copy instrument data */
+ if (len < (long)sizeof(ix))
+ return -EINVAL;
+ if (copy_from_user(&ix, instr_data, sizeof(ix)))
+ return -EFAULT;
+ if (ix.stype != GF1_STRU_INSTR)
+ return -EINVAL;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+ ip->exclusion = le16_to_cpu(ix.exclusion);
+ ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
+ ip->effect1 = ix.effect1;
+ ip->effect1_depth = ix.effect1_depth;
+ ip->effect2 = ix.effect2;
+ ip->effect2_depth = ix.effect2_depth;
+ /* copy layers */
+ while (len > (long)sizeof(__u32)) {
+ __u32 stype;
+
+ if (copy_from_user(&stype, instr_data, sizeof(stype)))
+ return -EFAULT;
+ if (stype != GF1_STRU_WAVE) {
+ snd_seq_gf1_instr_free(ops, ip, atomic);
+ return -EINVAL;
+ }
+ err = snd_seq_gf1_copy_wave_from_stream(ops,
+ ip,
+ &instr_data,
+ &len,
+ atomic);
+ if (err < 0) {
+ snd_seq_gf1_instr_free(ops, ip, atomic);
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int snd_seq_gf1_copy_wave_to_stream(snd_gf1_ops_t *ops,
+ gf1_instrument_t *ip,
+ char __user **data,
+ long *len,
+ int atomic)
+{
+ gf1_wave_t *wp;
+ gf1_xwave_t xp;
+ int err;
+ unsigned int real_size;
+
+ for (wp = ip->wave; wp; wp = wp->next) {
+ if (*len < (long)sizeof(xp))
+ return -ENOMEM;
+ memset(&xp, 0, sizeof(xp));
+ xp.stype = GF1_STRU_WAVE;
+ xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
+ xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
+ xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
+ xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
+ xp.format = cpu_to_le32(wp->format);
+ xp.size = cpu_to_le32(wp->size);
+ xp.start = cpu_to_le32(wp->start);
+ xp.loop_start = cpu_to_le32(wp->loop_start);
+ xp.loop_end = cpu_to_le32(wp->loop_end);
+ xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
+ xp.flags = wp->flags;
+ xp.sample_rate = cpu_to_le32(wp->sample_rate);
+ xp.low_frequency = cpu_to_le32(wp->low_frequency);
+ xp.high_frequency = cpu_to_le32(wp->high_frequency);
+ xp.root_frequency = cpu_to_le32(wp->root_frequency);
+ xp.tune = cpu_to_le16(wp->tune);
+ xp.balance = wp->balance;
+ memcpy(xp.envelope_rate, wp->envelope_rate, 6);
+ memcpy(xp.envelope_offset, wp->envelope_offset, 6);
+ xp.tremolo_sweep = wp->tremolo_sweep;
+ xp.tremolo_rate = wp->tremolo_rate;
+ xp.tremolo_depth = wp->tremolo_depth;
+ xp.vibrato_sweep = wp->vibrato_sweep;
+ xp.vibrato_rate = wp->vibrato_rate;
+ xp.vibrato_depth = wp->vibrato_depth;
+ xp.scale_frequency = cpu_to_le16(wp->scale_frequency);
+ xp.scale_factor = cpu_to_le16(wp->scale_factor);
+ if (copy_to_user(*data, &xp, sizeof(xp)))
+ return -EFAULT;
+ *data += sizeof(xp);
+ *len -= sizeof(xp);
+ real_size = snd_seq_gf1_size(wp->size, wp->format);
+ if (*len < (long)real_size)
+ return -ENOMEM;
+ if (ops->get_sample) {
+ err = ops->get_sample(ops->private_data, wp,
+ *data, real_size, atomic);
+ if (err < 0)
+ return err;
+ }
+ *data += wp->size;
+ *len -= wp->size;
+ }
+ return 0;
+}
+
+static int snd_seq_gf1_get(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic,
+ int cmd)
+{
+ snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+ gf1_instrument_t *ip;
+ gf1_xinstrument_t ix;
+
+ if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+ return -EINVAL;
+ if (len < (long)sizeof(ix))
+ return -ENOMEM;
+ memset(&ix, 0, sizeof(ix));
+ ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+ ix.stype = GF1_STRU_INSTR;
+ ix.exclusion = cpu_to_le16(ip->exclusion);
+ ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
+ ix.effect1 = cpu_to_le16(ip->effect1);
+ ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+ ix.effect2 = ip->effect2;
+ ix.effect2_depth = ip->effect2_depth;
+ if (copy_to_user(instr_data, &ix, sizeof(ix)))
+ return -EFAULT;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ return snd_seq_gf1_copy_wave_to_stream(ops,
+ ip,
+ &instr_data,
+ &len,
+ atomic);
+}
+
+static int snd_seq_gf1_get_size(void *private_data, snd_seq_kinstr_t *instr,
+ long *size)
+{
+ long result;
+ gf1_instrument_t *ip;
+ gf1_wave_t *wp;
+
+ *size = 0;
+ ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+ result = sizeof(gf1_xinstrument_t);
+ for (wp = ip->wave; wp; wp = wp->next) {
+ result += sizeof(gf1_xwave_t);
+ result += wp->size;
+ }
+ *size = result;
+ return 0;
+}
+
+static int snd_seq_gf1_remove(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int atomic)
+{
+ snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+ gf1_instrument_t *ip;
+
+ ip = (gf1_instrument_t *)KINSTR_DATA(instr);
+ snd_seq_gf1_instr_free(ops, ip, atomic);
+ return 0;
+}
+
+static void snd_seq_gf1_notify(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int what)
+{
+ snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data;
+
+ if (ops->notify)
+ ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_gf1_init(snd_gf1_ops_t *ops,
+ void *private_data,
+ snd_seq_kinstr_ops_t *next)
+{
+ memset(ops, 0, sizeof(*ops));
+ ops->private_data = private_data;
+ ops->kops.private_data = ops;
+ ops->kops.add_len = sizeof(gf1_instrument_t);
+ ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_GUS_PATCH;
+ ops->kops.put = snd_seq_gf1_put;
+ ops->kops.get = snd_seq_gf1_get;
+ ops->kops.get_size = snd_seq_gf1_get_size;
+ ops->kops.remove = snd_seq_gf1_remove;
+ ops->kops.notify = snd_seq_gf1_notify;
+ ops->kops.next = next;
+ return 0;
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_ainstr_gf1_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_ainstr_gf1_exit(void)
+{
+}
+
+module_init(alsa_ainstr_gf1_init)
+module_exit(alsa_ainstr_gf1_exit)
+
+EXPORT_SYMBOL(snd_seq_gf1_init);
diff --git a/sound/core/seq/instr/ainstr_iw.c b/sound/core/seq/instr/ainstr_iw.c
new file mode 100644
index 00000000000..39ff72b2aab
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_iw.c
@@ -0,0 +1,622 @@
+/*
+ * IWFFFF - AMD InterWave (tm) - Instrument routines
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_iw.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture IWFFFF support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_iwffff_size(unsigned int size, unsigned int format)
+{
+ unsigned int result = size;
+
+ if (format & IWFFFF_WAVE_16BIT)
+ result <<= 1;
+ if (format & IWFFFF_WAVE_STEREO)
+ result <<= 1;
+ return result;
+}
+
+static void snd_seq_iwffff_copy_lfo_from_stream(iwffff_lfo_t *fp,
+ iwffff_xlfo_t *fx)
+{
+ fp->freq = le16_to_cpu(fx->freq);
+ fp->depth = le16_to_cpu(fx->depth);
+ fp->sweep = le16_to_cpu(fx->sweep);
+ fp->shape = fx->shape;
+ fp->delay = fx->delay;
+}
+
+static int snd_seq_iwffff_copy_env_from_stream(__u32 req_stype,
+ iwffff_layer_t *lp,
+ iwffff_env_t *ep,
+ iwffff_xenv_t *ex,
+ char __user **data,
+ long *len,
+ int gfp_mask)
+{
+ __u32 stype;
+ iwffff_env_record_t *rp, *rp_last;
+ iwffff_xenv_record_t rx;
+ iwffff_env_point_t *pp;
+ iwffff_xenv_point_t px;
+ int points_size, idx;
+
+ ep->flags = ex->flags;
+ ep->mode = ex->mode;
+ ep->index = ex->index;
+ rp_last = NULL;
+ while (1) {
+ if (*len < (long)sizeof(__u32))
+ return -EINVAL;
+ if (copy_from_user(&stype, *data, sizeof(stype)))
+ return -EFAULT;
+ if (stype == IWFFFF_STRU_WAVE)
+ return 0;
+ if (req_stype != stype) {
+ if (stype == IWFFFF_STRU_ENV_RECP ||
+ stype == IWFFFF_STRU_ENV_RECV)
+ return 0;
+ }
+ if (*len < (long)sizeof(rx))
+ return -EINVAL;
+ if (copy_from_user(&rx, *data, sizeof(rx)))
+ return -EFAULT;
+ *data += sizeof(rx);
+ *len -= sizeof(rx);
+ points_size = (le16_to_cpu(rx.nattack) + le16_to_cpu(rx.nrelease)) * 2 * sizeof(__u16);
+ if (points_size > *len)
+ return -EINVAL;
+ rp = kcalloc(1, sizeof(*rp) + points_size, gfp_mask);
+ if (rp == NULL)
+ return -ENOMEM;
+ rp->nattack = le16_to_cpu(rx.nattack);
+ rp->nrelease = le16_to_cpu(rx.nrelease);
+ rp->sustain_offset = le16_to_cpu(rx.sustain_offset);
+ rp->sustain_rate = le16_to_cpu(rx.sustain_rate);
+ rp->release_rate = le16_to_cpu(rx.release_rate);
+ rp->hirange = rx.hirange;
+ pp = (iwffff_env_point_t *)(rp + 1);
+ for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
+ if (copy_from_user(&px, *data, sizeof(px)))
+ return -EFAULT;
+ *data += sizeof(px);
+ *len -= sizeof(px);
+ pp->offset = le16_to_cpu(px.offset);
+ pp->rate = le16_to_cpu(px.rate);
+ }
+ if (ep->record == NULL) {
+ ep->record = rp;
+ } else {
+ rp_last = rp;
+ }
+ rp_last = rp;
+ }
+ return 0;
+}
+
+static int snd_seq_iwffff_copy_wave_from_stream(snd_iwffff_ops_t *ops,
+ iwffff_layer_t *lp,
+ char __user **data,
+ long *len,
+ int atomic)
+{
+ iwffff_wave_t *wp, *prev;
+ iwffff_xwave_t xp;
+ int err, gfp_mask;
+ unsigned int real_size;
+
+ gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+ if (*len < (long)sizeof(xp))
+ return -EINVAL;
+ if (copy_from_user(&xp, *data, sizeof(xp)))
+ return -EFAULT;
+ *data += sizeof(xp);
+ *len -= sizeof(xp);
+ wp = kcalloc(1, sizeof(*wp), gfp_mask);
+ if (wp == NULL)
+ return -ENOMEM;
+ wp->share_id[0] = le32_to_cpu(xp.share_id[0]);
+ wp->share_id[1] = le32_to_cpu(xp.share_id[1]);
+ wp->share_id[2] = le32_to_cpu(xp.share_id[2]);
+ wp->share_id[3] = le32_to_cpu(xp.share_id[3]);
+ wp->format = le32_to_cpu(xp.format);
+ wp->address.memory = le32_to_cpu(xp.offset);
+ wp->size = le32_to_cpu(xp.size);
+ wp->start = le32_to_cpu(xp.start);
+ wp->loop_start = le32_to_cpu(xp.loop_start);
+ wp->loop_end = le32_to_cpu(xp.loop_end);
+ wp->loop_repeat = le16_to_cpu(xp.loop_repeat);
+ wp->sample_ratio = le32_to_cpu(xp.sample_ratio);
+ wp->attenuation = xp.attenuation;
+ wp->low_note = xp.low_note;
+ wp->high_note = xp.high_note;
+ real_size = snd_seq_iwffff_size(wp->size, wp->format);
+ if (!(wp->format & IWFFFF_WAVE_ROM)) {
+ if ((long)real_size > *len) {
+ kfree(wp);
+ return -ENOMEM;
+ }
+ }
+ if (ops->put_sample) {
+ err = ops->put_sample(ops->private_data, wp,
+ *data, real_size, atomic);
+ if (err < 0) {
+ kfree(wp);
+ return err;
+ }
+ }
+ if (!(wp->format & IWFFFF_WAVE_ROM)) {
+ *data += real_size;
+ *len -= real_size;
+ }
+ prev = lp->wave;
+ if (prev) {
+ while (prev->next) prev = prev->next;
+ prev->next = wp;
+ } else {
+ lp->wave = wp;
+ }
+ return 0;
+}
+
+static void snd_seq_iwffff_env_free(snd_iwffff_ops_t *ops,
+ iwffff_env_t *env,
+ int atomic)
+{
+ iwffff_env_record_t *rec;
+
+ while ((rec = env->record) != NULL) {
+ env->record = rec->next;
+ kfree(rec);
+ }
+}
+
+static void snd_seq_iwffff_wave_free(snd_iwffff_ops_t *ops,
+ iwffff_wave_t *wave,
+ int atomic)
+{
+ if (ops->remove_sample)
+ ops->remove_sample(ops->private_data, wave, atomic);
+ kfree(wave);
+}
+
+static void snd_seq_iwffff_instr_free(snd_iwffff_ops_t *ops,
+ iwffff_instrument_t *ip,
+ int atomic)
+{
+ iwffff_layer_t *layer;
+ iwffff_wave_t *wave;
+
+ while ((layer = ip->layer) != NULL) {
+ ip->layer = layer->next;
+ snd_seq_iwffff_env_free(ops, &layer->penv, atomic);
+ snd_seq_iwffff_env_free(ops, &layer->venv, atomic);
+ while ((wave = layer->wave) != NULL) {
+ layer->wave = wave->next;
+ snd_seq_iwffff_wave_free(ops, wave, atomic);
+ }
+ kfree(layer);
+ }
+}
+
+static int snd_seq_iwffff_put(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic,
+ int cmd)
+{
+ snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+ iwffff_instrument_t *ip;
+ iwffff_xinstrument_t ix;
+ iwffff_layer_t *lp, *prev_lp;
+ iwffff_xlayer_t lx;
+ int err, gfp_mask;
+
+ if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+ return -EINVAL;
+ gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+ /* copy instrument data */
+ if (len < (long)sizeof(ix))
+ return -EINVAL;
+ if (copy_from_user(&ix, instr_data, sizeof(ix)))
+ return -EFAULT;
+ if (ix.stype != IWFFFF_STRU_INSTR)
+ return -EINVAL;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+ ip->exclusion = le16_to_cpu(ix.exclusion);
+ ip->layer_type = le16_to_cpu(ix.layer_type);
+ ip->exclusion_group = le16_to_cpu(ix.exclusion_group);
+ ip->effect1 = ix.effect1;
+ ip->effect1_depth = ix.effect1_depth;
+ ip->effect2 = ix.effect2;
+ ip->effect2_depth = ix.effect2_depth;
+ /* copy layers */
+ prev_lp = NULL;
+ while (len > 0) {
+ if (len < (long)sizeof(iwffff_xlayer_t)) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return -EINVAL;
+ }
+ if (copy_from_user(&lx, instr_data, sizeof(lx)))
+ return -EFAULT;
+ instr_data += sizeof(lx);
+ len -= sizeof(lx);
+ if (lx.stype != IWFFFF_STRU_LAYER) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return -EINVAL;
+ }
+ lp = kcalloc(1, sizeof(*lp), gfp_mask);
+ if (lp == NULL) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return -ENOMEM;
+ }
+ if (prev_lp) {
+ prev_lp->next = lp;
+ } else {
+ ip->layer = lp;
+ }
+ prev_lp = lp;
+ lp->flags = lx.flags;
+ lp->velocity_mode = lx.velocity_mode;
+ lp->layer_event = lx.layer_event;
+ lp->low_range = lx.low_range;
+ lp->high_range = lx.high_range;
+ lp->pan = lx.pan;
+ lp->pan_freq_scale = lx.pan_freq_scale;
+ lp->attenuation = lx.attenuation;
+ snd_seq_iwffff_copy_lfo_from_stream(&lp->tremolo, &lx.tremolo);
+ snd_seq_iwffff_copy_lfo_from_stream(&lp->vibrato, &lx.vibrato);
+ lp->freq_scale = le16_to_cpu(lx.freq_scale);
+ lp->freq_center = lx.freq_center;
+ err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECP,
+ lp,
+ &lp->penv, &lx.penv,
+ &instr_data, &len,
+ gfp_mask);
+ if (err < 0) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return err;
+ }
+ err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECV,
+ lp,
+ &lp->venv, &lx.venv,
+ &instr_data, &len,
+ gfp_mask);
+ if (err < 0) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return err;
+ }
+ while (len > (long)sizeof(__u32)) {
+ __u32 stype;
+
+ if (copy_from_user(&stype, instr_data, sizeof(stype)))
+ return -EFAULT;
+ if (stype != IWFFFF_STRU_WAVE)
+ break;
+ err = snd_seq_iwffff_copy_wave_from_stream(ops,
+ lp,
+ &instr_data,
+ &len,
+ atomic);
+ if (err < 0) {
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+static void snd_seq_iwffff_copy_lfo_to_stream(iwffff_xlfo_t *fx,
+ iwffff_lfo_t *fp)
+{
+ fx->freq = cpu_to_le16(fp->freq);
+ fx->depth = cpu_to_le16(fp->depth);
+ fx->sweep = cpu_to_le16(fp->sweep);
+ fp->shape = fx->shape;
+ fp->delay = fx->delay;
+}
+
+static int snd_seq_iwffff_copy_env_to_stream(__u32 req_stype,
+ iwffff_layer_t *lp,
+ iwffff_xenv_t *ex,
+ iwffff_env_t *ep,
+ char __user **data,
+ long *len)
+{
+ iwffff_env_record_t *rp;
+ iwffff_xenv_record_t rx;
+ iwffff_env_point_t *pp;
+ iwffff_xenv_point_t px;
+ int points_size, idx;
+
+ ex->flags = ep->flags;
+ ex->mode = ep->mode;
+ ex->index = ep->index;
+ for (rp = ep->record; rp; rp = rp->next) {
+ if (*len < (long)sizeof(rx))
+ return -ENOMEM;
+ memset(&rx, 0, sizeof(rx));
+ rx.stype = req_stype;
+ rx.nattack = cpu_to_le16(rp->nattack);
+ rx.nrelease = cpu_to_le16(rp->nrelease);
+ rx.sustain_offset = cpu_to_le16(rp->sustain_offset);
+ rx.sustain_rate = cpu_to_le16(rp->sustain_rate);
+ rx.release_rate = cpu_to_le16(rp->release_rate);
+ rx.hirange = cpu_to_le16(rp->hirange);
+ if (copy_to_user(*data, &rx, sizeof(rx)))
+ return -EFAULT;
+ *data += sizeof(rx);
+ *len -= sizeof(rx);
+ points_size = (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
+ if (*len < points_size)
+ return -ENOMEM;
+ pp = (iwffff_env_point_t *)(rp + 1);
+ for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) {
+ px.offset = cpu_to_le16(pp->offset);
+ px.rate = cpu_to_le16(pp->rate);
+ if (copy_to_user(*data, &px, sizeof(px)))
+ return -EFAULT;
+ *data += sizeof(px);
+ *len -= sizeof(px);
+ }
+ }
+ return 0;
+}
+
+static int snd_seq_iwffff_copy_wave_to_stream(snd_iwffff_ops_t *ops,
+ iwffff_layer_t *lp,
+ char __user **data,
+ long *len,
+ int atomic)
+{
+ iwffff_wave_t *wp;
+ iwffff_xwave_t xp;
+ int err;
+ unsigned int real_size;
+
+ for (wp = lp->wave; wp; wp = wp->next) {
+ if (*len < (long)sizeof(xp))
+ return -ENOMEM;
+ memset(&xp, 0, sizeof(xp));
+ xp.stype = IWFFFF_STRU_WAVE;
+ xp.share_id[0] = cpu_to_le32(wp->share_id[0]);
+ xp.share_id[1] = cpu_to_le32(wp->share_id[1]);
+ xp.share_id[2] = cpu_to_le32(wp->share_id[2]);
+ xp.share_id[3] = cpu_to_le32(wp->share_id[3]);
+ xp.format = cpu_to_le32(wp->format);
+ if (wp->format & IWFFFF_WAVE_ROM)
+ xp.offset = cpu_to_le32(wp->address.memory);
+ xp.size = cpu_to_le32(wp->size);
+ xp.start = cpu_to_le32(wp->start);
+ xp.loop_start = cpu_to_le32(wp->loop_start);
+ xp.loop_end = cpu_to_le32(wp->loop_end);
+ xp.loop_repeat = cpu_to_le32(wp->loop_repeat);
+ xp.sample_ratio = cpu_to_le32(wp->sample_ratio);
+ xp.attenuation = wp->attenuation;
+ xp.low_note = wp->low_note;
+ xp.high_note = wp->high_note;
+ if (copy_to_user(*data, &xp, sizeof(xp)))
+ return -EFAULT;
+ *data += sizeof(xp);
+ *len -= sizeof(xp);
+ real_size = snd_seq_iwffff_size(wp->size, wp->format);
+ if (!(wp->format & IWFFFF_WAVE_ROM)) {
+ if (*len < (long)real_size)
+ return -ENOMEM;
+ }
+ if (ops->get_sample) {
+ err = ops->get_sample(ops->private_data, wp,
+ *data, real_size, atomic);
+ if (err < 0)
+ return err;
+ }
+ if (!(wp->format & IWFFFF_WAVE_ROM)) {
+ *data += real_size;
+ *len -= real_size;
+ }
+ }
+ return 0;
+}
+
+static int snd_seq_iwffff_get(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len, int atomic, int cmd)
+{
+ snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+ iwffff_instrument_t *ip;
+ iwffff_xinstrument_t ix;
+ iwffff_layer_t *lp;
+ iwffff_xlayer_t lx;
+ char __user *layer_instr_data;
+ int err;
+
+ if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+ return -EINVAL;
+ if (len < (long)sizeof(ix))
+ return -ENOMEM;
+ memset(&ix, 0, sizeof(ix));
+ ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+ ix.stype = IWFFFF_STRU_INSTR;
+ ix.exclusion = cpu_to_le16(ip->exclusion);
+ ix.layer_type = cpu_to_le16(ip->layer_type);
+ ix.exclusion_group = cpu_to_le16(ip->exclusion_group);
+ ix.effect1 = cpu_to_le16(ip->effect1);
+ ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+ ix.effect2 = ip->effect2;
+ ix.effect2_depth = ip->effect2_depth;
+ if (copy_to_user(instr_data, &ix, sizeof(ix)))
+ return -EFAULT;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ for (lp = ip->layer; lp; lp = lp->next) {
+ if (len < (long)sizeof(lx))
+ return -ENOMEM;
+ memset(&lx, 0, sizeof(lx));
+ lx.stype = IWFFFF_STRU_LAYER;
+ lx.flags = lp->flags;
+ lx.velocity_mode = lp->velocity_mode;
+ lx.layer_event = lp->layer_event;
+ lx.low_range = lp->low_range;
+ lx.high_range = lp->high_range;
+ lx.pan = lp->pan;
+ lx.pan_freq_scale = lp->pan_freq_scale;
+ lx.attenuation = lp->attenuation;
+ snd_seq_iwffff_copy_lfo_to_stream(&lx.tremolo, &lp->tremolo);
+ snd_seq_iwffff_copy_lfo_to_stream(&lx.vibrato, &lp->vibrato);
+ layer_instr_data = instr_data;
+ instr_data += sizeof(lx);
+ len -= sizeof(lx);
+ err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECP,
+ lp,
+ &lx.penv, &lp->penv,
+ &instr_data, &len);
+ if (err < 0)
+ return err;
+ err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECV,
+ lp,
+ &lx.venv, &lp->venv,
+ &instr_data, &len);
+ if (err < 0)
+ return err;
+ /* layer structure updating is now finished */
+ if (copy_to_user(layer_instr_data, &lx, sizeof(lx)))
+ return -EFAULT;
+ err = snd_seq_iwffff_copy_wave_to_stream(ops,
+ lp,
+ &instr_data,
+ &len,
+ atomic);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static long snd_seq_iwffff_env_size_in_stream(iwffff_env_t *ep)
+{
+ long result = 0;
+ iwffff_env_record_t *rp;
+
+ for (rp = ep->record; rp; rp = rp->next) {
+ result += sizeof(iwffff_xenv_record_t);
+ result += (rp->nattack + rp->nrelease) * 2 * sizeof(__u16);
+ }
+ return 0;
+}
+
+static long snd_seq_iwffff_wave_size_in_stream(iwffff_layer_t *lp)
+{
+ long result = 0;
+ iwffff_wave_t *wp;
+
+ for (wp = lp->wave; wp; wp = wp->next) {
+ result += sizeof(iwffff_xwave_t);
+ if (!(wp->format & IWFFFF_WAVE_ROM))
+ result += wp->size;
+ }
+ return result;
+}
+
+static int snd_seq_iwffff_get_size(void *private_data, snd_seq_kinstr_t *instr,
+ long *size)
+{
+ long result;
+ iwffff_instrument_t *ip;
+ iwffff_layer_t *lp;
+
+ *size = 0;
+ ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+ result = sizeof(iwffff_xinstrument_t);
+ for (lp = ip->layer; lp; lp = lp->next) {
+ result += sizeof(iwffff_xlayer_t);
+ result += snd_seq_iwffff_env_size_in_stream(&lp->penv);
+ result += snd_seq_iwffff_env_size_in_stream(&lp->venv);
+ result += snd_seq_iwffff_wave_size_in_stream(lp);
+ }
+ *size = result;
+ return 0;
+}
+
+static int snd_seq_iwffff_remove(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int atomic)
+{
+ snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+ iwffff_instrument_t *ip;
+
+ ip = (iwffff_instrument_t *)KINSTR_DATA(instr);
+ snd_seq_iwffff_instr_free(ops, ip, atomic);
+ return 0;
+}
+
+static void snd_seq_iwffff_notify(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int what)
+{
+ snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data;
+
+ if (ops->notify)
+ ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_iwffff_init(snd_iwffff_ops_t *ops,
+ void *private_data,
+ snd_seq_kinstr_ops_t *next)
+{
+ memset(ops, 0, sizeof(*ops));
+ ops->private_data = private_data;
+ ops->kops.private_data = ops;
+ ops->kops.add_len = sizeof(iwffff_instrument_t);
+ ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_INTERWAVE;
+ ops->kops.put = snd_seq_iwffff_put;
+ ops->kops.get = snd_seq_iwffff_get;
+ ops->kops.get_size = snd_seq_iwffff_get_size;
+ ops->kops.remove = snd_seq_iwffff_remove;
+ ops->kops.notify = snd_seq_iwffff_notify;
+ ops->kops.next = next;
+ return 0;
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_ainstr_iw_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_ainstr_iw_exit(void)
+{
+}
+
+module_init(alsa_ainstr_iw_init)
+module_exit(alsa_ainstr_iw_exit)
+
+EXPORT_SYMBOL(snd_seq_iwffff_init);
diff --git a/sound/core/seq/instr/ainstr_simple.c b/sound/core/seq/instr/ainstr_simple.c
new file mode 100644
index 00000000000..6183d215103
--- /dev/null
+++ b/sound/core/seq/instr/ainstr_simple.c
@@ -0,0 +1,215 @@
+/*
+ * Simple (MOD player) - Instrument routines
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/ainstr_simple.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture Simple Instrument support.");
+MODULE_LICENSE("GPL");
+
+static unsigned int snd_seq_simple_size(unsigned int size, unsigned int format)
+{
+ unsigned int result = size;
+
+ if (format & SIMPLE_WAVE_16BIT)
+ result <<= 1;
+ if (format & SIMPLE_WAVE_STEREO)
+ result <<= 1;
+ return result;
+}
+
+static void snd_seq_simple_instr_free(snd_simple_ops_t *ops,
+ simple_instrument_t *ip,
+ int atomic)
+{
+ if (ops->remove_sample)
+ ops->remove_sample(ops->private_data, ip, atomic);
+}
+
+static int snd_seq_simple_put(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len,
+ int atomic, int cmd)
+{
+ snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+ simple_instrument_t *ip;
+ simple_xinstrument_t ix;
+ int err, gfp_mask;
+ unsigned int real_size;
+
+ if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE)
+ return -EINVAL;
+ gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+ /* copy instrument data */
+ if (len < (long)sizeof(ix))
+ return -EINVAL;
+ if (copy_from_user(&ix, instr_data, sizeof(ix)))
+ return -EFAULT;
+ if (ix.stype != SIMPLE_STRU_INSTR)
+ return -EINVAL;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ ip = (simple_instrument_t *)KINSTR_DATA(instr);
+ ip->share_id[0] = le32_to_cpu(ix.share_id[0]);
+ ip->share_id[1] = le32_to_cpu(ix.share_id[1]);
+ ip->share_id[2] = le32_to_cpu(ix.share_id[2]);
+ ip->share_id[3] = le32_to_cpu(ix.share_id[3]);
+ ip->format = le32_to_cpu(ix.format);
+ ip->size = le32_to_cpu(ix.size);
+ ip->start = le32_to_cpu(ix.start);
+ ip->loop_start = le32_to_cpu(ix.loop_start);
+ ip->loop_end = le32_to_cpu(ix.loop_end);
+ ip->loop_repeat = le16_to_cpu(ix.loop_repeat);
+ ip->effect1 = ix.effect1;
+ ip->effect1_depth = ix.effect1_depth;
+ ip->effect2 = ix.effect2;
+ ip->effect2_depth = ix.effect2_depth;
+ real_size = snd_seq_simple_size(ip->size, ip->format);
+ if (len < (long)real_size)
+ return -EINVAL;
+ if (ops->put_sample) {
+ err = ops->put_sample(ops->private_data, ip,
+ instr_data, real_size, atomic);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int snd_seq_simple_get(void *private_data, snd_seq_kinstr_t *instr,
+ char __user *instr_data, long len,
+ int atomic, int cmd)
+{
+ snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+ simple_instrument_t *ip;
+ simple_xinstrument_t ix;
+ int err;
+ unsigned int real_size;
+
+ if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL)
+ return -EINVAL;
+ if (len < (long)sizeof(ix))
+ return -ENOMEM;
+ memset(&ix, 0, sizeof(ix));
+ ip = (simple_instrument_t *)KINSTR_DATA(instr);
+ ix.stype = SIMPLE_STRU_INSTR;
+ ix.share_id[0] = cpu_to_le32(ip->share_id[0]);
+ ix.share_id[1] = cpu_to_le32(ip->share_id[1]);
+ ix.share_id[2] = cpu_to_le32(ip->share_id[2]);
+ ix.share_id[3] = cpu_to_le32(ip->share_id[3]);
+ ix.format = cpu_to_le32(ip->format);
+ ix.size = cpu_to_le32(ip->size);
+ ix.start = cpu_to_le32(ip->start);
+ ix.loop_start = cpu_to_le32(ip->loop_start);
+ ix.loop_end = cpu_to_le32(ip->loop_end);
+ ix.loop_repeat = cpu_to_le32(ip->loop_repeat);
+ ix.effect1 = cpu_to_le16(ip->effect1);
+ ix.effect1_depth = cpu_to_le16(ip->effect1_depth);
+ ix.effect2 = ip->effect2;
+ ix.effect2_depth = ip->effect2_depth;
+ if (copy_to_user(instr_data, &ix, sizeof(ix)))
+ return -EFAULT;
+ instr_data += sizeof(ix);
+ len -= sizeof(ix);
+ real_size = snd_seq_simple_size(ip->size, ip->format);
+ if (len < (long)real_size)
+ return -ENOMEM;
+ if (ops->get_sample) {
+ err = ops->get_sample(ops->private_data, ip,
+ instr_data, real_size, atomic);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int snd_seq_simple_get_size(void *private_data, snd_seq_kinstr_t *instr,
+ long *size)
+{
+ simple_instrument_t *ip;
+
+ ip = (simple_instrument_t *)KINSTR_DATA(instr);
+ *size = sizeof(simple_xinstrument_t) + snd_seq_simple_size(ip->size, ip->format);
+ return 0;
+}
+
+static int snd_seq_simple_remove(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int atomic)
+{
+ snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+ simple_instrument_t *ip;
+
+ ip = (simple_instrument_t *)KINSTR_DATA(instr);
+ snd_seq_simple_instr_free(ops, ip, atomic);
+ return 0;
+}
+
+static void snd_seq_simple_notify(void *private_data,
+ snd_seq_kinstr_t *instr,
+ int what)
+{
+ snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data;
+
+ if (ops->notify)
+ ops->notify(ops->private_data, instr, what);
+}
+
+int snd_seq_simple_init(snd_simple_ops_t *ops,
+ void *private_data,
+ snd_seq_kinstr_ops_t *next)
+{
+ memset(ops, 0, sizeof(*ops));
+ ops->private_data = private_data;
+ ops->kops.private_data = ops;
+ ops->kops.add_len = sizeof(simple_instrument_t);
+ ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_SIMPLE;
+ ops->kops.put = snd_seq_simple_put;
+ ops->kops.get = snd_seq_simple_get;
+ ops->kops.get_size = snd_seq_simple_get_size;
+ ops->kops.remove = snd_seq_simple_remove;
+ ops->kops.notify = snd_seq_simple_notify;
+ ops->kops.next = next;
+ return 0;
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_ainstr_simple_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_ainstr_simple_exit(void)
+{
+}
+
+module_init(alsa_ainstr_simple_init)
+module_exit(alsa_ainstr_simple_exit)
+
+EXPORT_SYMBOL(snd_seq_simple_init);
diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile
new file mode 100644
index 00000000000..a37ddedf710
--- /dev/null
+++ b/sound/core/seq/oss/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \
+ seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \
+ seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o
+
+obj-$(CONFIG_SND_SEQUENCER) += snd-seq-oss.o
diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c
new file mode 100644
index 00000000000..4c0558c0a8b
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss.c
@@ -0,0 +1,317 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * registration of device and proc
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+#include "seq_oss_device.h"
+#include "seq_oss_synth.h"
+
+/*
+ * module option
+ */
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("OSS-compatible sequencer module");
+MODULE_LICENSE("GPL");
+/* Takashi says this is really only for sound-service-0-, but this is OK. */
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC);
+
+#ifdef SNDRV_SEQ_OSS_DEBUG
+module_param(seq_oss_debug, int, 0644);
+MODULE_PARM_DESC(seq_oss_debug, "debug option");
+int seq_oss_debug = 0;
+#endif
+
+
+/*
+ * prototypes
+ */
+static int register_device(void);
+static void unregister_device(void);
+static int register_proc(void);
+static void unregister_proc(void);
+
+static int odev_open(struct inode *inode, struct file *file);
+static int odev_release(struct inode *inode, struct file *file);
+static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
+static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
+static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static unsigned int odev_poll(struct file *file, poll_table * wait);
+#ifdef CONFIG_PROC_FS
+static void info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf);
+#endif
+
+
+/*
+ * module interface
+ */
+
+static int __init alsa_seq_oss_init(void)
+{
+ int rc;
+ static snd_seq_dev_ops_t ops = {
+ snd_seq_oss_synth_register,
+ snd_seq_oss_synth_unregister,
+ };
+
+ snd_seq_autoload_lock();
+ if ((rc = register_device()) < 0)
+ goto error;
+ if ((rc = register_proc()) < 0) {
+ unregister_device();
+ goto error;
+ }
+ if ((rc = snd_seq_oss_create_client()) < 0) {
+ unregister_proc();
+ unregister_device();
+ goto error;
+ }
+
+ if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops,
+ sizeof(snd_seq_oss_reg_t))) < 0) {
+ snd_seq_oss_delete_client();
+ unregister_proc();
+ unregister_device();
+ goto error;
+ }
+
+ /* success */
+ snd_seq_oss_synth_init();
+
+ error:
+ snd_seq_autoload_unlock();
+ return rc;
+}
+
+static void __exit alsa_seq_oss_exit(void)
+{
+ snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS);
+ snd_seq_oss_delete_client();
+ unregister_proc();
+ unregister_device();
+}
+
+module_init(alsa_seq_oss_init)
+module_exit(alsa_seq_oss_exit)
+
+/*
+ * ALSA minor device interface
+ */
+
+static DECLARE_MUTEX(register_mutex);
+
+static int
+odev_open(struct inode *inode, struct file *file)
+{
+ int level, rc;
+
+ if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
+ level = SNDRV_SEQ_OSS_MODE_MUSIC;
+ else
+ level = SNDRV_SEQ_OSS_MODE_SYNTH;
+
+ down(&register_mutex);
+ rc = snd_seq_oss_open(file, level);
+ up(&register_mutex);
+
+ return rc;
+}
+
+static int
+odev_release(struct inode *inode, struct file *file)
+{
+ seq_oss_devinfo_t *dp;
+
+ if ((dp = file->private_data) == NULL)
+ return 0;
+
+ snd_seq_oss_drain_write(dp);
+
+ down(&register_mutex);
+ snd_seq_oss_release(dp);
+ up(&register_mutex);
+
+ return 0;
+}
+
+static ssize_t
+odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ seq_oss_devinfo_t *dp;
+ dp = file->private_data;
+ snd_assert(dp != NULL, return -EIO);
+ return snd_seq_oss_read(dp, buf, count);
+}
+
+
+static ssize_t
+odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+ seq_oss_devinfo_t *dp;
+ dp = file->private_data;
+ snd_assert(dp != NULL, return -EIO);
+ return snd_seq_oss_write(dp, buf, count, file);
+}
+
+static long
+odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ seq_oss_devinfo_t *dp;
+ dp = file->private_data;
+ snd_assert(dp != NULL, return -EIO);
+ return snd_seq_oss_ioctl(dp, cmd, arg);
+}
+
+#ifdef CONFIG_COMPAT
+#define odev_ioctl_compat odev_ioctl
+#else
+#define odev_ioctl_compat NULL
+#endif
+
+static unsigned int
+odev_poll(struct file *file, poll_table * wait)
+{
+ seq_oss_devinfo_t *dp;
+ dp = file->private_data;
+ snd_assert(dp != NULL, return 0);
+ return snd_seq_oss_poll(dp, file, wait);
+}
+
+/*
+ * registration of sequencer minor device
+ */
+
+static struct file_operations seq_oss_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = odev_read,
+ .write = odev_write,
+ .open = odev_open,
+ .release = odev_release,
+ .poll = odev_poll,
+ .unlocked_ioctl = odev_ioctl,
+ .compat_ioctl = odev_ioctl_compat,
+};
+
+static snd_minor_t seq_oss_reg = {
+ .comment = "sequencer",
+ .f_ops = &seq_oss_f_ops,
+};
+
+static int __init
+register_device(void)
+{
+ int rc;
+
+ down(&register_mutex);
+ if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
+ NULL, 0,
+ &seq_oss_reg,
+ SNDRV_SEQ_OSS_DEVNAME)) < 0) {
+ snd_printk(KERN_ERR "can't register device seq\n");
+ up(&register_mutex);
+ return rc;
+ }
+ if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
+ NULL, 0,
+ &seq_oss_reg,
+ SNDRV_SEQ_OSS_DEVNAME)) < 0) {
+ snd_printk(KERN_ERR "can't register device music\n");
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
+ up(&register_mutex);
+ return rc;
+ }
+ debug_printk(("device registered\n"));
+ up(&register_mutex);
+ return 0;
+}
+
+static void
+unregister_device(void)
+{
+ down(&register_mutex);
+ debug_printk(("device unregistered\n"));
+ if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
+ snd_printk(KERN_ERR "error unregister device music\n");
+ if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
+ snd_printk(KERN_ERR "error unregister device seq\n");
+ up(&register_mutex);
+}
+
+/*
+ * /proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static snd_info_entry_t *info_entry;
+
+static void
+info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf)
+{
+ down(&register_mutex);
+ snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
+ snd_seq_oss_system_info_read(buf);
+ snd_seq_oss_synth_info_read(buf);
+ snd_seq_oss_midi_info_read(buf);
+ up(&register_mutex);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+static int __init
+register_proc(void)
+{
+#ifdef CONFIG_PROC_FS
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root);
+ if (entry == NULL)
+ return -ENOMEM;
+
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->private_data = NULL;
+ entry->c.text.read_size = 1024;
+ entry->c.text.read = info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ info_entry = entry;
+#endif
+ return 0;
+}
+
+static void
+unregister_proc(void)
+{
+#ifdef CONFIG_PROC_FS
+ if (info_entry)
+ snd_info_unregister(info_entry);
+ info_entry = NULL;
+#endif
+}
diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h
new file mode 100644
index 00000000000..da23c4db8dd
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_device.h
@@ -0,0 +1,198 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_DEVICE_H
+#define __SEQ_OSS_DEVICE_H
+
+#include <sound/driver.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/seq_oss.h>
+#include <sound/rawmidi.h>
+#include <sound/seq_kernel.h>
+#include <sound/info.h>
+
+/* enable debug print */
+#define SNDRV_SEQ_OSS_DEBUG
+
+/* max. applications */
+#define SNDRV_SEQ_OSS_MAX_CLIENTS 16
+#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16
+#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32
+
+/* version */
+#define SNDRV_SEQ_OSS_MAJOR_VERSION 0
+#define SNDRV_SEQ_OSS_MINOR_VERSION 1
+#define SNDRV_SEQ_OSS_TINY_VERSION 8
+#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8"
+
+/* device and proc interface name */
+#define SNDRV_SEQ_OSS_DEVNAME "seq_oss"
+#define SNDRV_SEQ_OSS_PROCNAME "oss"
+
+
+/*
+ * type definitions
+ */
+
+typedef struct seq_oss_devinfo_t seq_oss_devinfo_t;
+typedef struct seq_oss_writeq_t seq_oss_writeq_t;
+typedef struct seq_oss_readq_t seq_oss_readq_t;
+typedef struct seq_oss_timer_t seq_oss_timer_t;
+typedef struct seq_oss_synthinfo_t seq_oss_synthinfo_t;
+typedef struct seq_oss_synth_sysex_t seq_oss_synth_sysex_t;
+typedef struct seq_oss_chinfo_t seq_oss_chinfo_t;
+typedef unsigned int reltime_t;
+typedef unsigned int abstime_t;
+typedef union evrec_t evrec_t;
+
+
+/*
+ * synthesizer channel information
+ */
+struct seq_oss_chinfo_t {
+ int note, vel;
+};
+
+/*
+ * synthesizer information
+ */
+struct seq_oss_synthinfo_t {
+ snd_seq_oss_arg_t arg;
+ seq_oss_chinfo_t *ch;
+ seq_oss_synth_sysex_t *sysex;
+ int nr_voices;
+ int opened;
+ int is_midi;
+ int midi_mapped;
+};
+
+
+/*
+ * sequencer client information
+ */
+
+struct seq_oss_devinfo_t {
+
+ int index; /* application index */
+ int cseq; /* sequencer client number */
+ int port; /* sequencer port number */
+ int queue; /* sequencer queue number */
+
+ snd_seq_addr_t addr; /* address of this device */
+
+ int seq_mode; /* sequencer mode */
+ int file_mode; /* file access */
+
+ /* midi device table */
+ int max_mididev;
+
+ /* synth device table */
+ int max_synthdev;
+ seq_oss_synthinfo_t synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+ int synth_opened;
+
+ /* output queue */
+ seq_oss_writeq_t *writeq;
+
+ /* midi input queue */
+ seq_oss_readq_t *readq;
+
+ /* timer */
+ seq_oss_timer_t *timer;
+};
+
+
+/*
+ * function prototypes
+ */
+
+/* create/delete OSS sequencer client */
+int snd_seq_oss_create_client(void);
+int snd_seq_oss_delete_client(void);
+
+/* device file interface */
+int snd_seq_oss_open(struct file *file, int level);
+void snd_seq_oss_release(seq_oss_devinfo_t *dp);
+int snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long arg);
+int snd_seq_oss_read(seq_oss_devinfo_t *dev, char __user *buf, int count);
+int snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt);
+unsigned int snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait);
+
+void snd_seq_oss_reset(seq_oss_devinfo_t *dp);
+void snd_seq_oss_drain_write(seq_oss_devinfo_t *dp);
+
+/* */
+void snd_seq_oss_process_queue(seq_oss_devinfo_t *dp, abstime_t time);
+
+
+/* proc interface */
+void snd_seq_oss_system_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_midi_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_synth_info_read(snd_info_buffer_t *buf);
+void snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf);
+
+/* file mode macros */
+#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ)
+#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE)
+#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK)
+
+/* dispatch event */
+inline static int
+snd_seq_oss_dispatch(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int atomic, int hop)
+{
+ return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
+}
+
+/* ioctl */
+inline static int
+snd_seq_oss_control(seq_oss_devinfo_t *dp, unsigned int type, void *arg)
+{
+ return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
+}
+
+/* fill the addresses in header */
+inline static void
+snd_seq_oss_fill_addr(seq_oss_devinfo_t *dp, snd_seq_event_t *ev,
+ int dest_client, int dest_port)
+{
+ ev->queue = dp->queue;
+ ev->source = dp->addr;
+ ev->dest.client = dest_client;
+ ev->dest.port = dest_port;
+}
+
+
+/* misc. functions for proc interface */
+char *enabled_str(int bool);
+
+
+/* for debug */
+#ifdef SNDRV_SEQ_OSS_DEBUG
+extern int seq_oss_debug;
+#define debug_printk(x) do { if (seq_oss_debug > 0) snd_printk x; } while (0)
+#else
+#define debug_printk(x) /**/
+#endif
+
+#endif /* __SEQ_OSS_DEVICE_H */
diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c
new file mode 100644
index 00000000000..58e52ddd292
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.c
@@ -0,0 +1,447 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+
+
+/*
+ * prototypes
+ */
+static int extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+static int chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int chn_common_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int timing_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int local_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev);
+static int old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+static int note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
+static int note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev);
+static int set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev);
+static int set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev);
+static int set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev);
+
+
+/*
+ * convert an OSS event to ALSA event
+ * return 0 : enqueued
+ * non-zero : invalid - ignored
+ */
+
+int
+snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ switch (q->s.code) {
+ case SEQ_EXTENDED:
+ return extended_event(dp, q, ev);
+
+ case EV_CHN_VOICE:
+ return chn_voice_event(dp, q, ev);
+
+ case EV_CHN_COMMON:
+ return chn_common_event(dp, q, ev);
+
+ case EV_TIMING:
+ return timing_event(dp, q, ev);
+
+ case EV_SEQ_LOCAL:
+ return local_event(dp, q, ev);
+
+ case EV_SYSEX:
+ return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
+
+ case SEQ_MIDIPUTC:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ /* put a midi byte */
+ if (! is_write_mode(dp->file_mode))
+ break;
+ if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
+ break;
+ if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
+ return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
+ break;
+
+ case SEQ_ECHO:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return set_echo_event(dp, q, ev);
+
+ case SEQ_PRIVATE:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
+
+ default:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return old_event(dp, q, ev);
+ }
+ return -EINVAL;
+}
+
+/* old type events: mode1 only */
+static int
+old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ switch (q->s.code) {
+ case SEQ_NOTEOFF:
+ return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+ case SEQ_NOTEON:
+ return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+ case SEQ_WAIT:
+ /* skip */
+ break;
+
+ case SEQ_PGMCHANGE:
+ return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->n.chn, 0, q->n.note, ev);
+
+ case SEQ_SYNCTIMER:
+ return snd_seq_oss_timer_reset(dp->timer);
+ }
+
+ return -EINVAL;
+}
+
+/* 8bytes extended event: mode1 only */
+static int
+extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ int val;
+
+ switch (q->e.cmd) {
+ case SEQ_NOTEOFF:
+ return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+ case SEQ_NOTEON:
+ return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+ case SEQ_PGMCHANGE:
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->e.chn, 0, q->e.p1, ev);
+
+ case SEQ_AFTERTOUCH:
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+ q->e.chn, 0, q->e.p1, ev);
+
+ case SEQ_BALANCE:
+ /* convert -128:127 to 0:127 */
+ val = (char)q->e.p1;
+ val = (val + 128) / 2;
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+ q->e.chn, CTL_PAN, val, ev);
+
+ case SEQ_CONTROLLER:
+ val = ((short)q->e.p3 << 8) | (short)q->e.p2;
+ switch (q->e.p1) {
+ case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
+ /* -0x2000:0x1fff */
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_PITCHBEND,
+ q->e.chn, 0, val, ev);
+ case CTRL_PITCH_BENDER_RANGE:
+ /* conversion: 100/semitone -> 128/semitone */
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_REGPARAM,
+ q->e.chn, 0, val*128/100, ev);
+ default:
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_CONTROL14,
+ q->e.chn, q->e.p1, val, ev);
+ }
+
+ case SEQ_VOLMODE:
+ return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
+
+ }
+ return -EINVAL;
+}
+
+/* channel voice events: mode1 and 2 */
+static int
+chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ if (q->v.chn >= 32)
+ return -EINVAL;
+ switch (q->v.cmd) {
+ case MIDI_NOTEON:
+ return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+ case MIDI_NOTEOFF:
+ return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+ case MIDI_KEY_PRESSURE:
+ return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
+ q->v.chn, q->v.note, q->v.parm, ev);
+
+ }
+ return -EINVAL;
+}
+
+/* channel common events: mode1 and 2 */
+static int
+chn_common_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ if (q->l.chn >= 32)
+ return -EINVAL;
+ switch (q->l.cmd) {
+ case MIDI_PGM_CHANGE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->l.chn, 0, q->l.p1, ev);
+
+ case MIDI_CTL_CHANGE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+ q->l.chn, q->l.p1, q->l.val, ev);
+
+ case MIDI_PITCH_BEND:
+ /* conversion: 0:0x3fff -> -0x2000:0x1fff */
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
+ q->l.chn, 0, q->l.val - 8192, ev);
+
+ case MIDI_CHN_PRESSURE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+ q->l.chn, 0, q->l.val, ev);
+ }
+ return -EINVAL;
+}
+
+/* timer events: mode1 and mode2 */
+static int
+timing_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ switch (q->t.cmd) {
+ case TMR_ECHO:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return set_echo_event(dp, q, ev);
+ else {
+ evrec_t tmp;
+ memset(&tmp, 0, sizeof(tmp));
+ /* XXX: only for little-endian! */
+ tmp.echo = (q->t.time << 8) | SEQ_ECHO;
+ return set_echo_event(dp, &tmp, ev);
+ }
+
+ case TMR_STOP:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_stop(dp->timer);
+ return 0;
+
+ case TMR_CONTINUE:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_continue(dp->timer);
+ return 0;
+
+ case TMR_TEMPO:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* local events: mode1 and 2 */
+static int
+local_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev)
+{
+ return -EINVAL;
+}
+
+/*
+ * process note-on event for OSS synth
+ * three different modes are available:
+ * - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode)
+ * Accept note 255 as volume change.
+ * - SNDRV_SEQ_OSS_PASS_EVENTS
+ * Pass all events to lowlevel driver anyway
+ * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000)
+ * Use key-pressure if note >= 128
+ */
+static int
+note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+ seq_oss_synthinfo_t *info = &dp->synths[dev];
+ switch (info->arg.event_passing) {
+ case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+ if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+ /* pass directly */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+
+ if (note == 255 && info->ch[ch].note >= 0) {
+ /* volume control */
+ int type;
+ //if (! vel)
+ /* set volume to zero -- note off */
+ // type = SNDRV_SEQ_EVENT_NOTEOFF;
+ //else
+ if (info->ch[ch].vel)
+ /* sample already started -- volume change */
+ type = SNDRV_SEQ_EVENT_KEYPRESS;
+ else
+ /* sample not started -- start now */
+ type = SNDRV_SEQ_EVENT_NOTEON;
+ info->ch[ch].vel = vel;
+ return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
+ } else if (note >= 128)
+ return -EINVAL; /* invalid */
+
+ if (note != info->ch[ch].note && info->ch[ch].note >= 0)
+ /* note changed - note off at beginning */
+ set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
+ /* set current status */
+ info->ch[ch].note = note;
+ info->ch[ch].vel = vel;
+ if (vel) /* non-zero velocity - start the note now */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ return -EINVAL;
+
+ case SNDRV_SEQ_OSS_PASS_EVENTS:
+ /* pass the event anyway */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+
+ case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+ if (note >= 128) /* key pressure: shifted by 128 */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
+ else /* normal note-on event */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+ return -EINVAL;
+}
+
+/*
+ * process note-off event for OSS synth
+ */
+static int
+note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+ seq_oss_synthinfo_t *info = &dp->synths[dev];
+ switch (info->arg.event_passing) {
+ case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+ if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+ /* pass directly */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+
+ if (info->ch[ch].note >= 0) {
+ note = info->ch[ch].note;
+ info->ch[ch].vel = 0;
+ info->ch[ch].note = -1;
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+ }
+ return -EINVAL; /* invalid */
+
+ case SNDRV_SEQ_OSS_PASS_EVENTS:
+ case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+ /* pass the event anyway */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+
+ }
+ return -EINVAL;
+}
+
+/*
+ * create a note event
+ */
+static int
+set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev)
+{
+ if (! snd_seq_oss_synth_is_valid(dp, dev))
+ return -ENXIO;
+
+ ev->type = type;
+ snd_seq_oss_synth_addr(dp, dev, ev);
+ ev->data.note.channel = ch;
+ ev->data.note.note = note;
+ ev->data.note.velocity = vel;
+
+ return 0;
+}
+
+/*
+ * create a control event
+ */
+static int
+set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev)
+{
+ if (! snd_seq_oss_synth_is_valid(dp, dev))
+ return -ENXIO;
+
+ ev->type = type;
+ snd_seq_oss_synth_addr(dp, dev, ev);
+ ev->data.control.channel = ch;
+ ev->data.control.param = param;
+ ev->data.control.value = val;
+
+ return 0;
+}
+
+/*
+ * create an echo event
+ */
+static int
+set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev)
+{
+ ev->type = SNDRV_SEQ_EVENT_ECHO;
+ /* echo back to itself */
+ snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
+ memcpy(&ev->data, rec, LONG_EVENT_SIZE);
+ return 0;
+}
+
+/*
+ * event input callback from ALSA sequencer:
+ * the echo event is processed here.
+ */
+int
+snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data,
+ int atomic, int hop)
+{
+ seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
+ evrec_t *rec;
+
+ if (ev->type != SNDRV_SEQ_EVENT_ECHO)
+ return snd_seq_oss_midi_input(ev, direct, private_data);
+
+ if (ev->source.client != dp->cseq)
+ return 0; /* ignored */
+
+ rec = (evrec_t*)&ev->data;
+ if (rec->s.code == SEQ_SYNCTIMER) {
+ /* sync echo back */
+ snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
+
+ } else {
+ /* echo back event */
+ if (dp->readq == NULL)
+ return 0;
+ snd_seq_oss_readq_put_event(dp->readq, rec);
+ }
+ return 0;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h
new file mode 100644
index 00000000000..bf1d4d3f53c
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.h
@@ -0,0 +1,112 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_event.h - OSS event queue record
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_EVENT_H
+#define __SEQ_OSS_EVENT_H
+
+#include "seq_oss_device.h"
+
+#define SHORT_EVENT_SIZE 4
+#define LONG_EVENT_SIZE 8
+
+/* short event (4bytes) */
+typedef struct evrec_short_t {
+ unsigned char code;
+ unsigned char parm1;
+ unsigned char dev;
+ unsigned char parm2;
+} evrec_short_t;
+
+/* short note events (4bytes) */
+typedef struct evrec_note_t {
+ unsigned char code;
+ unsigned char chn;
+ unsigned char note;
+ unsigned char vel;
+} evrec_note_t;
+
+/* long timer events (8bytes) */
+typedef struct evrec_timer_t {
+ unsigned char code;
+ unsigned char cmd;
+ unsigned char dummy1, dummy2;
+ unsigned int time;
+} evrec_timer_t;
+
+/* long extended events (8bytes) */
+typedef struct evrec_extended_t {
+ unsigned char code;
+ unsigned char cmd;
+ unsigned char dev;
+ unsigned char chn;
+ unsigned char p1, p2, p3, p4;
+} evrec_extended_t;
+
+/* long channel events (8bytes) */
+typedef struct evrec_long_t {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char cmd;
+ unsigned char chn;
+ unsigned char p1, p2;
+ unsigned short val;
+} evrec_long_t;
+
+/* channel voice events (8bytes) */
+typedef struct evrec_voice_t {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char cmd;
+ unsigned char chn;
+ unsigned char note, parm;
+ unsigned short dummy;
+} evrec_voice_t;
+
+/* sysex events (8bytes) */
+typedef struct evrec_sysex_t {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char buf[6];
+} evrec_sysex_t;
+
+/* event record */
+union evrec_t {
+ evrec_short_t s;
+ evrec_note_t n;
+ evrec_long_t l;
+ evrec_voice_t v;
+ evrec_timer_t t;
+ evrec_extended_t e;
+ evrec_sysex_t x;
+ unsigned int echo;
+ unsigned char c[LONG_EVENT_SIZE];
+};
+
+#define ev_is_long(ev) ((ev)->s.code >= 128)
+#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE)
+
+int snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev);
+int snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *q);
+int snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop);
+
+
+#endif /* __SEQ_OSS_EVENT_H */
diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c
new file mode 100644
index 00000000000..bac4b4f1a94
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_init.c
@@ -0,0 +1,555 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * open/close and reset interface
+ *
+ * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+
+/*
+ * common variables
+ */
+static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;
+module_param(maxqlen, int, 0444);
+MODULE_PARM_DESC(maxqlen, "maximum queue length");
+
+static int system_client = -1; /* ALSA sequencer client number */
+static int system_port = -1;
+
+static int num_clients;
+static seq_oss_devinfo_t *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
+
+
+/*
+ * prototypes
+ */
+static int receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop);
+static int translate_mode(struct file *file);
+static int create_port(seq_oss_devinfo_t *dp);
+static int delete_port(seq_oss_devinfo_t *dp);
+static int alloc_seq_queue(seq_oss_devinfo_t *dp);
+static int delete_seq_queue(int queue);
+static void free_devinfo(void *private);
+
+#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)
+
+
+/*
+ * create sequencer client for OSS sequencer
+ */
+int __init
+snd_seq_oss_create_client(void)
+{
+ int rc;
+ snd_seq_client_callback_t callback;
+ snd_seq_client_info_t *info;
+ snd_seq_port_info_t *port;
+ snd_seq_port_callback_t port_callback;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ port = kmalloc(sizeof(*port), GFP_KERNEL);
+ if (!info || !port) {
+ rc = -ENOMEM;
+ goto __error;
+ }
+
+ /* create ALSA client */
+ memset(&callback, 0, sizeof(callback));
+
+ callback.private_data = NULL;
+ callback.allow_input = 1;
+ callback.allow_output = 1;
+
+ rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, &callback);
+ if (rc < 0)
+ goto __error;
+
+ system_client = rc;
+ debug_printk(("new client = %d\n", rc));
+
+ /* set client information */
+ memset(info, 0, sizeof(*info));
+ info->client = system_client;
+ info->type = KERNEL_CLIENT;
+ strcpy(info->name, "OSS sequencer");
+
+ rc = call_ctl(SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info);
+
+ /* look up midi devices */
+ snd_seq_oss_midi_lookup_ports(system_client);
+
+ /* create annoucement receiver port */
+ memset(port, 0, sizeof(*port));
+ strcpy(port->name, "Receiver");
+ port->addr.client = system_client;
+ port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */
+ port->type = 0;
+
+ memset(&port_callback, 0, sizeof(port_callback));
+ /* don't set port_callback.owner here. otherwise the module counter
+ * is incremented and we can no longer release the module..
+ */
+ port_callback.event_input = receive_announce;
+ port->kernel = &port_callback;
+
+ call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+ if ((system_port = port->addr.port) >= 0) {
+ snd_seq_port_subscribe_t subs;
+
+ memset(&subs, 0, sizeof(subs));
+ subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+ subs.dest.client = system_client;
+ subs.dest.port = system_port;
+ call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);
+ }
+ rc = 0;
+
+ __error:
+ kfree(port);
+ kfree(info);
+ return rc;
+}
+
+
+/*
+ * receive annoucement from system port, and check the midi device
+ */
+static int
+receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop)
+{
+ snd_seq_port_info_t pinfo;
+
+ if (atomic)
+ return 0; /* it must not happen */
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_PORT_START:
+ case SNDRV_SEQ_EVENT_PORT_CHANGE:
+ if (ev->data.addr.client == system_client)
+ break; /* ignore myself */
+ memset(&pinfo, 0, sizeof(pinfo));
+ pinfo.addr = ev->data.addr;
+ if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)
+ snd_seq_oss_midi_check_new_port(&pinfo);
+ break;
+
+ case SNDRV_SEQ_EVENT_PORT_EXIT:
+ if (ev->data.addr.client == system_client)
+ break; /* ignore myself */
+ snd_seq_oss_midi_check_exit_port(ev->data.addr.client,
+ ev->data.addr.port);
+ break;
+ }
+ return 0;
+}
+
+
+/*
+ * delete OSS sequencer client
+ */
+int
+snd_seq_oss_delete_client(void)
+{
+ if (system_client >= 0)
+ snd_seq_delete_kernel_client(system_client);
+
+ snd_seq_oss_midi_clear_all();
+
+ return 0;
+}
+
+
+/*
+ * open sequencer device
+ */
+int
+snd_seq_oss_open(struct file *file, int level)
+{
+ int i, rc;
+ seq_oss_devinfo_t *dp;
+
+ if ((dp = kcalloc(1, sizeof(*dp), GFP_KERNEL)) == NULL) {
+ snd_printk(KERN_ERR "can't malloc device info\n");
+ return -ENOMEM;
+ }
+ debug_printk(("oss_open: dp = %p\n", dp));
+
+ for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
+ if (client_table[i] == NULL)
+ break;
+ }
+ if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
+ snd_printk(KERN_ERR "too many applications\n");
+ kfree(dp);
+ return -ENOMEM;
+ }
+
+ dp->index = i;
+ dp->cseq = system_client;
+ dp->port = -1;
+ dp->queue = -1;
+ dp->readq = NULL;
+ dp->writeq = NULL;
+
+ /* look up synth and midi devices */
+ snd_seq_oss_synth_setup(dp);
+ snd_seq_oss_midi_setup(dp);
+
+ if (dp->synth_opened == 0 && dp->max_mididev == 0) {
+ /* snd_printk(KERN_ERR "no device found\n"); */
+ rc = -ENODEV;
+ goto _error;
+ }
+
+ /* create port */
+ debug_printk(("create new port\n"));
+ if ((rc = create_port(dp)) < 0) {
+ snd_printk(KERN_ERR "can't create port\n");
+ goto _error;
+ }
+
+ /* allocate queue */
+ debug_printk(("allocate queue\n"));
+ if ((rc = alloc_seq_queue(dp)) < 0)
+ goto _error;
+
+ /* set address */
+ dp->addr.client = dp->cseq;
+ dp->addr.port = dp->port;
+ /*dp->addr.queue = dp->queue;*/
+ /*dp->addr.channel = 0;*/
+
+ dp->seq_mode = level;
+
+ /* set up file mode */
+ dp->file_mode = translate_mode(file);
+
+ /* initialize read queue */
+ debug_printk(("initialize read queue\n"));
+ if (is_read_mode(dp->file_mode)) {
+ if ((dp->readq = snd_seq_oss_readq_new(dp, maxqlen)) == NULL) {
+ rc = -ENOMEM;
+ goto _error;
+ }
+ }
+
+ /* initialize write queue */
+ debug_printk(("initialize write queue\n"));
+ if (is_write_mode(dp->file_mode)) {
+ dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
+ if (dp->writeq == NULL) {
+ rc = -ENOMEM;
+ goto _error;
+ }
+ }
+
+ /* initialize timer */
+ debug_printk(("initialize timer\n"));
+ if ((dp->timer = snd_seq_oss_timer_new(dp)) == NULL) {
+ snd_printk(KERN_ERR "can't alloc timer\n");
+ rc = -ENOMEM;
+ goto _error;
+ }
+ debug_printk(("timer initialized\n"));
+
+ /* set private data pointer */
+ file->private_data = dp;
+
+ /* set up for mode2 */
+ if (level == SNDRV_SEQ_OSS_MODE_MUSIC)
+ snd_seq_oss_synth_setup_midi(dp);
+ else if (is_read_mode(dp->file_mode))
+ snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);
+
+ client_table[dp->index] = dp;
+ num_clients++;
+
+ debug_printk(("open done\n"));
+ return 0;
+
+ _error:
+ snd_seq_oss_synth_cleanup(dp);
+ snd_seq_oss_midi_cleanup(dp);
+ i = dp->queue;
+ delete_port(dp);
+ delete_seq_queue(i);
+
+ return rc;
+}
+
+/*
+ * translate file flags to private mode
+ */
+static int
+translate_mode(struct file *file)
+{
+ int file_mode = 0;
+ if ((file->f_flags & O_ACCMODE) != O_RDONLY)
+ file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+ if ((file->f_flags & O_ACCMODE) != O_WRONLY)
+ file_mode |= SNDRV_SEQ_OSS_FILE_READ;
+ if (file->f_flags & O_NONBLOCK)
+ file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;
+ return file_mode;
+}
+
+
+/*
+ * create sequencer port
+ */
+static int
+create_port(seq_oss_devinfo_t *dp)
+{
+ int rc;
+ snd_seq_port_info_t port;
+ snd_seq_port_callback_t callback;
+
+ memset(&port, 0, sizeof(port));
+ port.addr.client = dp->cseq;
+ sprintf(port.name, "Sequencer-%d", dp->index);
+ port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */
+ port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
+ port.midi_channels = 128;
+ port.synth_voices = 128;
+
+ memset(&callback, 0, sizeof(callback));
+ callback.owner = THIS_MODULE;
+ callback.private_data = dp;
+ callback.event_input = snd_seq_oss_event_input;
+ callback.private_free = free_devinfo;
+ port.kernel = &callback;
+
+ rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);
+ if (rc < 0)
+ return rc;
+
+ dp->port = port.addr.port;
+ debug_printk(("new port = %d\n", port.addr.port));
+
+ return 0;
+}
+
+/*
+ * delete ALSA port
+ */
+static int
+delete_port(seq_oss_devinfo_t *dp)
+{
+ if (dp->port < 0)
+ return 0;
+
+ debug_printk(("delete_port %i\n", dp->port));
+ return snd_seq_event_port_detach(dp->cseq, dp->port);
+}
+
+/*
+ * allocate a queue
+ */
+static int
+alloc_seq_queue(seq_oss_devinfo_t *dp)
+{
+ snd_seq_queue_info_t qinfo;
+ int rc;
+
+ memset(&qinfo, 0, sizeof(qinfo));
+ qinfo.owner = system_client;
+ qinfo.locked = 1;
+ strcpy(qinfo.name, "OSS Sequencer Emulation");
+ if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0)
+ return rc;
+ dp->queue = qinfo.queue;
+ return 0;
+}
+
+/*
+ * release queue
+ */
+static int
+delete_seq_queue(int queue)
+{
+ snd_seq_queue_info_t qinfo;
+ int rc;
+
+ if (queue < 0)
+ return 0;
+ memset(&qinfo, 0, sizeof(qinfo));
+ qinfo.queue = queue;
+ rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);
+ if (rc < 0)
+ printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc);
+ return rc;
+}
+
+
+/*
+ * free device informations - private_free callback of port
+ */
+static void
+free_devinfo(void *private)
+{
+ seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private;
+
+ if (dp->timer)
+ snd_seq_oss_timer_delete(dp->timer);
+
+ if (dp->writeq)
+ snd_seq_oss_writeq_delete(dp->writeq);
+
+ if (dp->readq)
+ snd_seq_oss_readq_delete(dp->readq);
+
+ kfree(dp);
+}
+
+
+/*
+ * close sequencer device
+ */
+void
+snd_seq_oss_release(seq_oss_devinfo_t *dp)
+{
+ int queue;
+
+ client_table[dp->index] = NULL;
+ num_clients--;
+
+ debug_printk(("resetting..\n"));
+ snd_seq_oss_reset(dp);
+
+ debug_printk(("cleaning up..\n"));
+ snd_seq_oss_synth_cleanup(dp);
+ snd_seq_oss_midi_cleanup(dp);
+
+ /* clear slot */
+ debug_printk(("releasing resource..\n"));
+ queue = dp->queue;
+ if (dp->port >= 0)
+ delete_port(dp);
+ delete_seq_queue(queue);
+
+ debug_printk(("release done\n"));
+}
+
+
+/*
+ * Wait until the queue is empty (if we don't have nonblock)
+ */
+void
+snd_seq_oss_drain_write(seq_oss_devinfo_t *dp)
+{
+ if (! dp->timer->running)
+ return;
+ if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) &&
+ dp->writeq) {
+ debug_printk(("syncing..\n"));
+ while (snd_seq_oss_writeq_sync(dp->writeq))
+ ;
+ }
+}
+
+
+/*
+ * reset sequencer devices
+ */
+void
+snd_seq_oss_reset(seq_oss_devinfo_t *dp)
+{
+ int i;
+
+ /* reset all synth devices */
+ for (i = 0; i < dp->max_synthdev; i++)
+ snd_seq_oss_synth_reset(dp, i);
+
+ /* reset all midi devices */
+ if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_reset(dp, i);
+ }
+
+ /* remove queues */
+ if (dp->readq)
+ snd_seq_oss_readq_clear(dp->readq);
+ if (dp->writeq)
+ snd_seq_oss_writeq_clear(dp->writeq);
+
+ /* reset timer */
+ snd_seq_oss_timer_stop(dp->timer);
+}
+
+
+/*
+ * misc. functions for proc interface
+ */
+char *
+enabled_str(int bool)
+{
+ return bool ? "enabled" : "disabled";
+}
+
+static char *
+filemode_str(int val)
+{
+ static char *str[] = {
+ "none", "read", "write", "read/write",
+ };
+ return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_system_info_read(snd_info_buffer_t *buf)
+{
+ int i;
+ seq_oss_devinfo_t *dp;
+
+ snd_iprintf(buf, "ALSA client number %d\n", system_client);
+ snd_iprintf(buf, "ALSA receiver port %d\n", system_port);
+
+ snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);
+ for (i = 0; i < num_clients; i++) {
+ snd_iprintf(buf, "\nApplication %d: ", i);
+ if ((dp = client_table[i]) == NULL) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);
+ snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n",
+ (dp->seq_mode ? "music" : "synth"),
+ filemode_str(dp->file_mode));
+ if (dp->seq_mode)
+ snd_iprintf(buf, " timer tempo = %d, timebase = %d\n",
+ dp->timer->oss_tempo, dp->timer->oss_timebase);
+ snd_iprintf(buf, " max queue length %d\n", maxqlen);
+ if (is_read_mode(dp->file_mode) && dp->readq)
+ snd_seq_oss_readq_info_read(dp->readq, buf);
+ }
+}
+
diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c
new file mode 100644
index 00000000000..e86f18d00f3
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_ioctl.c
@@ -0,0 +1,209 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * OSS compatible i/o control
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_device.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_event.h"
+
+static int snd_seq_oss_synth_info_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+ struct synth_info info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+ if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0)
+ return -EINVAL;
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_seq_oss_midi_info_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+ struct midi_info info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+ if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0)
+ return -EINVAL;
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_seq_oss_oob_user(seq_oss_devinfo_t *dp, void __user *arg)
+{
+ unsigned char ev[8];
+ snd_seq_event_t tmpev;
+
+ if (copy_from_user(ev, arg, 8))
+ return -EFAULT;
+ memset(&tmpev, 0, sizeof(tmpev));
+ snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.port, dp->addr.client);
+ tmpev.time.tick = 0;
+ if (! snd_seq_oss_process_event(dp, (evrec_t*)ev, &tmpev)) {
+ snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
+ }
+ return 0;
+}
+
+int
+snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long carg)
+{
+ int dev, val;
+ void __user *arg = (void __user *)carg;
+ int __user *p = arg;
+
+ switch (cmd) {
+ case SNDCTL_TMR_TIMEBASE:
+ case SNDCTL_TMR_TEMPO:
+ case SNDCTL_TMR_START:
+ case SNDCTL_TMR_STOP:
+ case SNDCTL_TMR_CONTINUE:
+ case SNDCTL_TMR_METRONOME:
+ case SNDCTL_TMR_SOURCE:
+ case SNDCTL_TMR_SELECT:
+ case SNDCTL_SEQ_CTRLRATE:
+ return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg);
+
+ case SNDCTL_SEQ_PANIC:
+ debug_printk(("panic\n"));
+ snd_seq_oss_reset(dp);
+ return -EINVAL;
+
+ case SNDCTL_SEQ_SYNC:
+ debug_printk(("sync\n"));
+ if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+ return 0;
+ while (snd_seq_oss_writeq_sync(dp->writeq))
+ ;
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ return 0;
+
+ case SNDCTL_SEQ_RESET:
+ debug_printk(("reset\n"));
+ snd_seq_oss_reset(dp);
+ return 0;
+
+ case SNDCTL_SEQ_TESTMIDI:
+ debug_printk(("test midi\n"));
+ if (get_user(dev, p))
+ return -EFAULT;
+ return snd_seq_oss_midi_open(dp, dev, dp->file_mode);
+
+ case SNDCTL_SEQ_GETINCOUNT:
+ debug_printk(("get in count\n"));
+ if (dp->readq == NULL || ! is_read_mode(dp->file_mode))
+ return 0;
+ return put_user(dp->readq->qlen, p) ? -EFAULT : 0;
+
+ case SNDCTL_SEQ_GETOUTCOUNT:
+ debug_printk(("get out count\n"));
+ if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+ return 0;
+ return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0;
+
+ case SNDCTL_SEQ_GETTIME:
+ debug_printk(("get time\n"));
+ return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;
+
+ case SNDCTL_SEQ_RESETSAMPLES:
+ debug_printk(("reset samples\n"));
+ if (get_user(dev, p))
+ return -EFAULT;
+ return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+
+ case SNDCTL_SEQ_NRSYNTHS:
+ debug_printk(("nr synths\n"));
+ return put_user(dp->max_synthdev, p) ? -EFAULT : 0;
+
+ case SNDCTL_SEQ_NRMIDIS:
+ debug_printk(("nr midis\n"));
+ return put_user(dp->max_mididev, p) ? -EFAULT : 0;
+
+ case SNDCTL_SYNTH_MEMAVL:
+ debug_printk(("mem avail\n"));
+ if (get_user(dev, p))
+ return -EFAULT;
+ val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+ return put_user(val, p) ? -EFAULT : 0;
+
+ case SNDCTL_FM_4OP_ENABLE:
+ debug_printk(("4op\n"));
+ if (get_user(dev, p))
+ return -EFAULT;
+ snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
+ return 0;
+
+ case SNDCTL_SYNTH_INFO:
+ case SNDCTL_SYNTH_ID:
+ debug_printk(("synth info\n"));
+ return snd_seq_oss_synth_info_user(dp, arg);
+
+ case SNDCTL_SEQ_OUTOFBAND:
+ debug_printk(("out of band\n"));
+ return snd_seq_oss_oob_user(dp, arg);
+
+ case SNDCTL_MIDI_INFO:
+ debug_printk(("midi info\n"));
+ return snd_seq_oss_midi_info_user(dp, arg);
+
+ case SNDCTL_SEQ_THRESHOLD:
+ debug_printk(("threshold\n"));
+ if (! is_write_mode(dp->file_mode))
+ return 0;
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val < 1)
+ val = 1;
+ if (val >= dp->writeq->maxlen)
+ val = dp->writeq->maxlen - 1;
+ snd_seq_oss_writeq_set_output(dp->writeq, val);
+ return 0;
+
+ case SNDCTL_MIDI_PRETIME:
+ debug_printk(("pretime\n"));
+ if (dp->readq == NULL || !is_read_mode(dp->file_mode))
+ return 0;
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val <= 0)
+ val = -1;
+ else
+ val = (HZ * val) / 10;
+ dp->readq->pre_event_timeout = val;
+ return put_user(val, p) ? -EFAULT : 0;
+
+ default:
+ debug_printk(("others\n"));
+ if (! is_write_mode(dp->file_mode))
+ return -EIO;
+ return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
+ }
+ return 0;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c
new file mode 100644
index 00000000000..9aece6c65db
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.c
@@ -0,0 +1,710 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * MIDI device handlers
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_midi.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_midi_event.h>
+#include "../seq_lock.h"
+#include <linux/init.h>
+
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30
+
+/*
+ * definition of midi device record
+ */
+struct seq_oss_midi_t {
+ int seq_device; /* device number */
+ int client; /* sequencer client number */
+ int port; /* sequencer port number */
+ unsigned int flags; /* port capability */
+ int opened; /* flag for opening */
+ unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];
+ snd_midi_event_t *coder; /* MIDI event coder */
+ seq_oss_devinfo_t *devinfo; /* assigned OSSseq device */
+ snd_use_lock_t use_lock;
+};
+
+
+/*
+ * midi device table
+ */
+static int max_midi_devs;
+static seq_oss_midi_t *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static seq_oss_midi_t *get_mdev(int dev);
+static seq_oss_midi_t *get_mididev(seq_oss_devinfo_t *dp, int dev);
+static int send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev);
+static int send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev);
+
+/*
+ * look up the existing ports
+ * this looks a very exhausting job.
+ */
+int __init
+snd_seq_oss_midi_lookup_ports(int client)
+{
+ snd_seq_client_info_t *clinfo;
+ snd_seq_port_info_t *pinfo;
+
+ clinfo = kcalloc(1, sizeof(*clinfo), GFP_KERNEL);
+ pinfo = kcalloc(1, sizeof(*pinfo), GFP_KERNEL);
+ if (! clinfo || ! pinfo) {
+ kfree(clinfo);
+ kfree(pinfo);
+ return -ENOMEM;
+ }
+ clinfo->client = -1;
+ while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {
+ if (clinfo->client == client)
+ continue; /* ignore myself */
+ pinfo->addr.client = clinfo->client;
+ pinfo->addr.port = -1;
+ while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)
+ snd_seq_oss_midi_check_new_port(pinfo);
+ }
+ kfree(clinfo);
+ kfree(pinfo);
+ return 0;
+}
+
+
+/*
+ */
+static seq_oss_midi_t *
+get_mdev(int dev)
+{
+ seq_oss_midi_t *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ mdev = midi_devs[dev];
+ if (mdev)
+ snd_use_lock_use(&mdev->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return mdev;
+}
+
+/*
+ * look for the identical slot
+ */
+static seq_oss_midi_t *
+find_slot(int client, int port)
+{
+ int i;
+ seq_oss_midi_t *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ mdev = midi_devs[i];
+ if (mdev && mdev->client == client && mdev->port == port) {
+ /* found! */
+ snd_use_lock_use(&mdev->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return mdev;
+ }
+ }
+ spin_unlock_irqrestore(&register_lock, flags);
+ return NULL;
+}
+
+
+#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
+#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
+/*
+ * register a new port if it doesn't exist yet
+ */
+int
+snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo)
+{
+ int i;
+ seq_oss_midi_t *mdev;
+ unsigned long flags;
+
+ debug_printk(("check for MIDI client %d port %d\n", pinfo->addr.client, pinfo->addr.port));
+ /* the port must include generic midi */
+ if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))
+ return 0;
+ /* either read or write subscribable */
+ if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&
+ (pinfo->capability & PERM_READ) != PERM_READ)
+ return 0;
+
+ /*
+ * look for the identical slot
+ */
+ if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) {
+ /* already exists */
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ /*
+ * allocate midi info record
+ */
+ if ((mdev = kcalloc(1, sizeof(*mdev), GFP_KERNEL)) == NULL) {
+ snd_printk(KERN_ERR "can't malloc midi info\n");
+ return -ENOMEM;
+ }
+
+ /* copy the port information */
+ mdev->client = pinfo->addr.client;
+ mdev->port = pinfo->addr.port;
+ mdev->flags = pinfo->capability;
+ mdev->opened = 0;
+ snd_use_lock_init(&mdev->use_lock);
+
+ /* copy and truncate the name of synth device */
+ strlcpy(mdev->name, pinfo->name, sizeof(mdev->name));
+
+ /* create MIDI coder */
+ if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {
+ snd_printk(KERN_ERR "can't malloc midi coder\n");
+ kfree(mdev);
+ return -ENOMEM;
+ }
+ /* OSS sequencer adds running status to all sequences */
+ snd_midi_event_no_status(mdev->coder, 1);
+
+ /*
+ * look for en empty slot
+ */
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ if (midi_devs[i] == NULL)
+ break;
+ }
+ if (i >= max_midi_devs) {
+ if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ return -ENOMEM;
+ }
+ max_midi_devs++;
+ }
+ mdev->seq_device = i;
+ midi_devs[mdev->seq_device] = mdev;
+ spin_unlock_irqrestore(&register_lock, flags);
+
+ return 0;
+}
+
+/*
+ * release the midi device if it was registered
+ */
+int
+snd_seq_oss_midi_check_exit_port(int client, int port)
+{
+ seq_oss_midi_t *mdev;
+ unsigned long flags;
+ int index;
+
+ if ((mdev = find_slot(client, port)) != NULL) {
+ spin_lock_irqsave(&register_lock, flags);
+ midi_devs[mdev->seq_device] = NULL;
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_use_lock_free(&mdev->use_lock);
+ snd_use_lock_sync(&mdev->use_lock);
+ if (mdev->coder)
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ }
+ spin_lock_irqsave(&register_lock, flags);
+ for (index = max_midi_devs - 1; index >= 0; index--) {
+ if (midi_devs[index])
+ break;
+ }
+ max_midi_devs = index + 1;
+ spin_unlock_irqrestore(&register_lock, flags);
+ return 0;
+}
+
+
+/*
+ * release the midi device if it was registered
+ */
+void
+snd_seq_oss_midi_clear_all(void)
+{
+ int i;
+ seq_oss_midi_t *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ if ((mdev = midi_devs[i]) != NULL) {
+ if (mdev->coder)
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ midi_devs[i] = NULL;
+ }
+ }
+ max_midi_devs = 0;
+ spin_unlock_irqrestore(&register_lock, flags);
+}
+
+
+/*
+ * set up midi tables
+ */
+void
+snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp)
+{
+ dp->max_mididev = max_midi_devs;
+}
+
+/*
+ * clean up midi tables
+ */
+void
+snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp)
+{
+ int i;
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_close(dp, i);
+ dp->max_mididev = 0;
+}
+
+
+/*
+ * open all midi devices. ignore errors.
+ */
+void
+snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode)
+{
+ int i;
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_open(dp, i, file_mode);
+}
+
+
+/*
+ * get the midi device information
+ */
+static seq_oss_midi_t *
+get_mididev(seq_oss_devinfo_t *dp, int dev)
+{
+ if (dev < 0 || dev >= dp->max_mididev)
+ return NULL;
+ return get_mdev(dev);
+}
+
+
+/*
+ * open the midi device if not opened yet
+ */
+int
+snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int fmode)
+{
+ int perm;
+ seq_oss_midi_t *mdev;
+ snd_seq_port_subscribe_t subs;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return -ENODEV;
+
+ /* already used? */
+ if (mdev->opened && mdev->devinfo != dp) {
+ snd_use_lock_free(&mdev->use_lock);
+ return -EBUSY;
+ }
+
+ perm = 0;
+ if (is_write_mode(fmode))
+ perm |= PERM_WRITE;
+ if (is_read_mode(fmode))
+ perm |= PERM_READ;
+ perm &= mdev->flags;
+ if (perm == 0) {
+ snd_use_lock_free(&mdev->use_lock);
+ return -ENXIO;
+ }
+
+ /* already opened? */
+ if ((mdev->opened & perm) == perm) {
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ perm &= ~mdev->opened;
+
+ memset(&subs, 0, sizeof(subs));
+
+ if (perm & PERM_WRITE) {
+ subs.sender = dp->addr;
+ subs.dest.client = mdev->client;
+ subs.dest.port = mdev->port;
+ if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+ mdev->opened |= PERM_WRITE;
+ }
+ if (perm & PERM_READ) {
+ subs.sender.client = mdev->client;
+ subs.sender.port = mdev->port;
+ subs.dest = dp->addr;
+ subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
+ subs.queue = dp->queue; /* queue for timestamps */
+ if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+ mdev->opened |= PERM_READ;
+ }
+
+ if (! mdev->opened) {
+ snd_use_lock_free(&mdev->use_lock);
+ return -ENXIO;
+ }
+
+ mdev->devinfo = dp;
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+}
+
+/*
+ * close the midi device if already opened
+ */
+int
+snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_midi_t *mdev;
+ snd_seq_port_subscribe_t subs;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return -ENODEV;
+ if (! mdev->opened || mdev->devinfo != dp) {
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ debug_printk(("closing client %d port %d mode %d\n", mdev->client, mdev->port, mdev->opened));
+ memset(&subs, 0, sizeof(subs));
+ if (mdev->opened & PERM_WRITE) {
+ subs.sender = dp->addr;
+ subs.dest.client = mdev->client;
+ subs.dest.port = mdev->port;
+ snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+ }
+ if (mdev->opened & PERM_READ) {
+ subs.sender.client = mdev->client;
+ subs.sender.port = mdev->port;
+ subs.dest = dp->addr;
+ snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+ }
+
+ mdev->opened = 0;
+ mdev->devinfo = NULL;
+
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+}
+
+/*
+ * change seq capability flags to file mode flags
+ */
+int
+snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_midi_t *mdev;
+ int mode;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return 0;
+
+ mode = 0;
+ if (mdev->opened & PERM_WRITE)
+ mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+ if (mdev->opened & PERM_READ)
+ mode |= SNDRV_SEQ_OSS_FILE_READ;
+
+ snd_use_lock_free(&mdev->use_lock);
+ return mode;
+}
+
+/*
+ * reset the midi device and close it:
+ * so far, only close the device.
+ */
+void
+snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_midi_t *mdev;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return;
+ if (! mdev->opened) {
+ snd_use_lock_free(&mdev->use_lock);
+ return;
+ }
+
+ if (mdev->opened & PERM_WRITE) {
+ snd_seq_event_t ev;
+ int c;
+
+ debug_printk(("resetting client %d port %d\n", mdev->client, mdev->port));
+ memset(&ev, 0, sizeof(ev));
+ ev.dest.client = mdev->client;
+ ev.dest.port = mdev->port;
+ ev.queue = dp->queue;
+ ev.source.port = dp->port;
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {
+ ev.type = SNDRV_SEQ_EVENT_SENSING;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0); /* active sensing */
+ }
+ for (c = 0; c < 16; c++) {
+ ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+ ev.data.control.channel = c;
+ ev.data.control.param = 123;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0); /* all notes off */
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ ev.data.control.param = 121;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0); /* reset all controllers */
+ ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
+ ev.data.control.value = 0;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0); /* bender off */
+ }
+ }
+ }
+ // snd_seq_oss_midi_close(dp, dev);
+ snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * get client/port of the specified MIDI device
+ */
+void
+snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr)
+{
+ seq_oss_midi_t *mdev;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return;
+ addr->client = mdev->client;
+ addr->port = mdev->port;
+ snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * input callback - this can be atomic
+ */
+int
+snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private_data)
+{
+ seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data;
+ seq_oss_midi_t *mdev;
+ int rc;
+
+ if (dp->readq == NULL)
+ return 0;
+ if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL)
+ return 0;
+ if (! (mdev->opened & PERM_READ)) {
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ rc = send_synth_event(dp, ev, mdev->seq_device);
+ else
+ rc = send_midi_event(dp, ev, mdev);
+
+ snd_use_lock_free(&mdev->use_lock);
+ return rc;
+}
+
+/*
+ * convert ALSA sequencer event to OSS synth event
+ */
+static int
+send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev)
+{
+ evrec_t ossev;
+
+ memset(&ossev, 0, sizeof(ossev));
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEON:
+ ossev.v.cmd = MIDI_NOTEON; break;
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ ossev.v.cmd = MIDI_NOTEOFF; break;
+ case SNDRV_SEQ_EVENT_KEYPRESS:
+ ossev.v.cmd = MIDI_KEY_PRESSURE; break;
+ case SNDRV_SEQ_EVENT_CONTROLLER:
+ ossev.l.cmd = MIDI_CTL_CHANGE; break;
+ case SNDRV_SEQ_EVENT_PGMCHANGE:
+ ossev.l.cmd = MIDI_PGM_CHANGE; break;
+ case SNDRV_SEQ_EVENT_CHANPRESS:
+ ossev.l.cmd = MIDI_CHN_PRESSURE; break;
+ case SNDRV_SEQ_EVENT_PITCHBEND:
+ ossev.l.cmd = MIDI_PITCH_BEND; break;
+ default:
+ return 0; /* not supported */
+ }
+
+ ossev.v.dev = dev;
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEON:
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ case SNDRV_SEQ_EVENT_KEYPRESS:
+ ossev.v.code = EV_CHN_VOICE;
+ ossev.v.note = ev->data.note.note;
+ ossev.v.parm = ev->data.note.velocity;
+ ossev.v.chn = ev->data.note.channel;
+ break;
+ case SNDRV_SEQ_EVENT_CONTROLLER:
+ case SNDRV_SEQ_EVENT_PGMCHANGE:
+ case SNDRV_SEQ_EVENT_CHANPRESS:
+ ossev.l.code = EV_CHN_COMMON;
+ ossev.l.p1 = ev->data.control.param;
+ ossev.l.val = ev->data.control.value;
+ ossev.l.chn = ev->data.control.channel;
+ break;
+ case SNDRV_SEQ_EVENT_PITCHBEND:
+ ossev.l.code = EV_CHN_COMMON;
+ ossev.l.val = ev->data.control.value + 8192;
+ ossev.l.chn = ev->data.control.channel;
+ break;
+ }
+
+ snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+ snd_seq_oss_readq_put_event(dp->readq, &ossev);
+
+ return 0;
+}
+
+/*
+ * decode event and send MIDI bytes to read queue
+ */
+static int
+send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev)
+{
+ char msg[32];
+ int len;
+
+ snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+ if (!dp->timer->running)
+ len = snd_seq_oss_timer_start(dp->timer);
+ if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+ snd_seq_oss_readq_puts(dp->readq, mdev->seq_device,
+ ev->data.ext.ptr, ev->data.ext.len);
+ } else {
+ len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);
+ if (len > 0)
+ snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);
+ }
+
+ return 0;
+}
+
+
+/*
+ * dump midi data
+ * return 0 : enqueued
+ * non-zero : invalid - ignored
+ */
+int
+snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev)
+{
+ seq_oss_midi_t *mdev;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return -ENODEV;
+ if (snd_midi_event_encode_byte(mdev->coder, c, ev) > 0) {
+ snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+ snd_use_lock_free(&mdev->use_lock);
+ return -EINVAL;
+}
+
+/*
+ * create OSS compatible midi_info record
+ */
+int
+snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf)
+{
+ seq_oss_midi_t *mdev;
+
+ if ((mdev = get_mididev(dp, dev)) == NULL)
+ return -ENXIO;
+ inf->device = dev;
+ inf->dev_type = 0; /* FIXME: ?? */
+ inf->capabilities = 0; /* FIXME: ?? */
+ strlcpy(inf->name, mdev->name, sizeof(inf->name));
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+}
+
+
+/*
+ * proc interface
+ */
+static char *
+capmode_str(int val)
+{
+ val &= PERM_READ|PERM_WRITE;
+ if (val == (PERM_READ|PERM_WRITE))
+ return "read/write";
+ else if (val == PERM_READ)
+ return "read";
+ else if (val == PERM_WRITE)
+ return "write";
+ else
+ return "none";
+}
+
+void
+snd_seq_oss_midi_info_read(snd_info_buffer_t *buf)
+{
+ int i;
+ seq_oss_midi_t *mdev;
+
+ snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);
+ for (i = 0; i < max_midi_devs; i++) {
+ snd_iprintf(buf, "\nmidi %d: ", i);
+ mdev = get_mdev(i);
+ if (mdev == NULL) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,
+ mdev->client, mdev->port);
+ snd_iprintf(buf, " capability %s / opened %s\n",
+ capmode_str(mdev->flags),
+ capmode_str(mdev->opened));
+ snd_use_lock_free(&mdev->use_lock);
+ }
+}
+
diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h
new file mode 100644
index 00000000000..462484b2b6f
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.h
@@ -0,0 +1,49 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * midi device information
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_MIDI_H
+#define __SEQ_OSS_MIDI_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+
+typedef struct seq_oss_midi_t seq_oss_midi_t;
+
+int snd_seq_oss_midi_lookup_ports(int client);
+int snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo);
+int snd_seq_oss_midi_check_exit_port(int client, int port);
+void snd_seq_oss_midi_clear_all(void);
+
+void snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp);
+void snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp);
+
+int snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int file_mode);
+void snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode);
+int snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev);
+void snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev);
+int snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private);
+int snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf);
+void snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c
new file mode 100644
index 00000000000..0a6f2a64f69
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.c
@@ -0,0 +1,234 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_readq.c - MIDI input queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_readq.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include <linux/wait.h>
+
+/*
+ * constants
+ */
+//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1)
+#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600)
+
+
+/*
+ * prototypes
+ */
+
+
+/*
+ * create a read queue
+ */
+seq_oss_readq_t *
+snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen)
+{
+ seq_oss_readq_t *q;
+
+ if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL) {
+ snd_printk(KERN_ERR "can't malloc read queue\n");
+ return NULL;
+ }
+
+ if ((q->q = kcalloc(maxlen, sizeof(evrec_t), GFP_KERNEL)) == NULL) {
+ snd_printk(KERN_ERR "can't malloc read queue buffer\n");
+ kfree(q);
+ return NULL;
+ }
+
+ q->maxlen = maxlen;
+ q->qlen = 0;
+ q->head = q->tail = 0;
+ init_waitqueue_head(&q->midi_sleep);
+ spin_lock_init(&q->lock);
+ q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT;
+ q->input_time = (unsigned long)-1;
+
+ return q;
+}
+
+/*
+ * delete the read queue
+ */
+void
+snd_seq_oss_readq_delete(seq_oss_readq_t *q)
+{
+ if (q) {
+ kfree(q->q);
+ kfree(q);
+ }
+}
+
+/*
+ * reset the read queue
+ */
+void
+snd_seq_oss_readq_clear(seq_oss_readq_t *q)
+{
+ if (q->qlen) {
+ q->qlen = 0;
+ q->head = q->tail = 0;
+ }
+ /* if someone sleeping, wake'em up */
+ if (waitqueue_active(&q->midi_sleep))
+ wake_up(&q->midi_sleep);
+ q->input_time = (unsigned long)-1;
+}
+
+/*
+ * put a midi byte
+ */
+int
+snd_seq_oss_readq_puts(seq_oss_readq_t *q, int dev, unsigned char *data, int len)
+{
+ evrec_t rec;
+ int result;
+
+ memset(&rec, 0, sizeof(rec));
+ rec.c[0] = SEQ_MIDIPUTC;
+ rec.c[2] = dev;
+
+ while (len-- > 0) {
+ rec.c[1] = *data++;
+ result = snd_seq_oss_readq_put_event(q, &rec);
+ if (result < 0)
+ return result;
+ }
+ return 0;
+}
+
+/*
+ * copy an event to input queue:
+ * return zero if enqueued
+ */
+int
+snd_seq_oss_readq_put_event(seq_oss_readq_t *q, evrec_t *ev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->lock, flags);
+ if (q->qlen >= q->maxlen - 1) {
+ spin_unlock_irqrestore(&q->lock, flags);
+ return -ENOMEM;
+ }
+
+ memcpy(&q->q[q->tail], ev, sizeof(*ev));
+ q->tail = (q->tail + 1) % q->maxlen;
+ q->qlen++;
+
+ /* wake up sleeper */
+ if (waitqueue_active(&q->midi_sleep))
+ wake_up(&q->midi_sleep);
+
+ spin_unlock_irqrestore(&q->lock, flags);
+
+ return 0;
+}
+
+
+/*
+ * pop queue
+ * caller must hold lock
+ */
+int
+snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec)
+{
+ if (q->qlen == 0)
+ return -EAGAIN;
+ memcpy(rec, &q->q[q->head], sizeof(*rec));
+ return 0;
+}
+
+/*
+ * sleep until ready
+ */
+void
+snd_seq_oss_readq_wait(seq_oss_readq_t *q)
+{
+ wait_event_interruptible_timeout(q->midi_sleep,
+ (q->qlen > 0 || q->head == q->tail),
+ q->pre_event_timeout);
+}
+
+/*
+ * drain one record
+ * caller must hold lock
+ */
+void
+snd_seq_oss_readq_free(seq_oss_readq_t *q)
+{
+ if (q->qlen > 0) {
+ q->head = (q->head + 1) % q->maxlen;
+ q->qlen--;
+ }
+}
+
+/*
+ * polling/select:
+ * return non-zero if readq is not empty.
+ */
+unsigned int
+snd_seq_oss_readq_poll(seq_oss_readq_t *q, struct file *file, poll_table *wait)
+{
+ poll_wait(file, &q->midi_sleep, wait);
+ return q->qlen;
+}
+
+/*
+ * put a timestamp
+ */
+int
+snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *q, unsigned long curt, int seq_mode)
+{
+ if (curt != q->input_time) {
+ evrec_t rec;
+ memset(&rec, 0, sizeof(rec));
+ switch (seq_mode) {
+ case SNDRV_SEQ_OSS_MODE_SYNTH:
+ rec.echo = (curt << 8) | SEQ_WAIT;
+ snd_seq_oss_readq_put_event(q, &rec);
+ break;
+ case SNDRV_SEQ_OSS_MODE_MUSIC:
+ rec.t.code = EV_TIMING;
+ rec.t.cmd = TMR_WAIT_ABS;
+ rec.t.time = curt;
+ snd_seq_oss_readq_put_event(q, &rec);
+ break;
+ }
+ q->input_time = curt;
+ }
+ return 0;
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf)
+{
+ snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n",
+ (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
+ q->qlen, q->input_time);
+}
diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h
new file mode 100644
index 00000000000..303b9298f20
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.h
@@ -0,0 +1,56 @@
+/*
+ * OSS compatible sequencer driver
+ * read fifo queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_READQ_H
+#define __SEQ_OSS_READQ_H
+
+#include "seq_oss_device.h"
+
+
+/*
+ * definition of read queue
+ */
+struct seq_oss_readq_t {
+ evrec_t *q;
+ int qlen;
+ int maxlen;
+ int head, tail;
+ unsigned long pre_event_timeout;
+ unsigned long input_time;
+ wait_queue_head_t midi_sleep;
+ spinlock_t lock;
+};
+
+seq_oss_readq_t *snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen);
+void snd_seq_oss_readq_delete(seq_oss_readq_t *q);
+void snd_seq_oss_readq_clear(seq_oss_readq_t *readq);
+unsigned int snd_seq_oss_readq_poll(seq_oss_readq_t *readq, struct file *file, poll_table *wait);
+int snd_seq_oss_readq_puts(seq_oss_readq_t *readq, int dev, unsigned char *data, int len);
+int snd_seq_oss_readq_put_event(seq_oss_readq_t *readq, evrec_t *ev);
+int snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *readq, unsigned long curt, int seq_mode);
+int snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec);
+void snd_seq_oss_readq_wait(seq_oss_readq_t *q);
+void snd_seq_oss_readq_free(seq_oss_readq_t *q);
+
+#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags)
+#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags)
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c
new file mode 100644
index 00000000000..1d8fbd22e3e
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_rw.c
@@ -0,0 +1,216 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * read/write/select interface to device file
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_device.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_synth.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include "../seq_clientmgr.h"
+
+
+/*
+ * protoypes
+ */
+static int insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt);
+
+
+/*
+ * read interface
+ */
+
+int
+snd_seq_oss_read(seq_oss_devinfo_t *dp, char __user *buf, int count)
+{
+ seq_oss_readq_t *readq = dp->readq;
+ int result = 0, err = 0;
+ int ev_len;
+ evrec_t rec;
+ unsigned long flags;
+
+ if (readq == NULL || ! is_read_mode(dp->file_mode))
+ return -ENXIO;
+
+ while (count >= SHORT_EVENT_SIZE) {
+ snd_seq_oss_readq_lock(readq, flags);
+ err = snd_seq_oss_readq_pick(readq, &rec);
+ if (err == -EAGAIN &&
+ !is_nonblock_mode(dp->file_mode) && result == 0) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ snd_seq_oss_readq_wait(readq);
+ snd_seq_oss_readq_lock(readq, flags);
+ if (signal_pending(current))
+ err = -ERESTARTSYS;
+ else
+ err = snd_seq_oss_readq_pick(readq, &rec);
+ }
+ if (err < 0) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ break;
+ }
+ ev_len = ev_length(&rec);
+ if (ev_len < count) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ break;
+ }
+ snd_seq_oss_readq_free(readq);
+ snd_seq_oss_readq_unlock(readq, flags);
+ if (copy_to_user(buf, &rec, ev_len)) {
+ err = -EFAULT;
+ break;
+ }
+ result += ev_len;
+ buf += ev_len;
+ count -= ev_len;
+ }
+ return result > 0 ? result : err;
+}
+
+
+/*
+ * write interface
+ */
+
+int
+snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt)
+{
+ int result = 0, err = 0;
+ int ev_size, fmt;
+ evrec_t rec;
+
+ if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+ return -ENXIO;
+
+ while (count >= SHORT_EVENT_SIZE) {
+ if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) {
+ err = -EFAULT;
+ break;
+ }
+ if (rec.s.code == SEQ_FULLSIZE) {
+ /* load patch */
+ if (result > 0) {
+ err = -EINVAL;
+ break;
+ }
+ fmt = (*(unsigned short *)rec.c) & 0xffff;
+ /* FIXME the return value isn't correct */
+ return snd_seq_oss_synth_load_patch(dp, rec.s.dev,
+ fmt, buf, 0, count);
+ }
+ if (ev_is_long(&rec)) {
+ /* extended code */
+ if (rec.s.code == SEQ_EXTENDED &&
+ dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ err = -EINVAL;
+ break;
+ }
+ ev_size = LONG_EVENT_SIZE;
+ if (count < ev_size)
+ break;
+ /* copy the reset 4 bytes */
+ if (copy_from_user(rec.c + SHORT_EVENT_SIZE,
+ buf + SHORT_EVENT_SIZE,
+ LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) {
+ err = -EFAULT;
+ break;
+ }
+ } else {
+ /* old-type code */
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ err = -EINVAL;
+ break;
+ }
+ ev_size = SHORT_EVENT_SIZE;
+ }
+
+ /* insert queue */
+ if ((err = insert_queue(dp, &rec, opt)) < 0)
+ break;
+
+ result += ev_size;
+ buf += ev_size;
+ count -= ev_size;
+ }
+ return result > 0 ? result : err;
+}
+
+
+/*
+ * insert event record to write queue
+ * return: 0 = OK, non-zero = NG
+ */
+static int
+insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt)
+{
+ int rc = 0;
+ snd_seq_event_t event;
+
+ /* if this is a timing event, process the current time */
+ if (snd_seq_oss_process_timer_event(dp->timer, rec))
+ return 0; /* no need to insert queue */
+
+ /* parse this event */
+ memset(&event, 0, sizeof(event));
+ /* set dummy -- to be sure */
+ event.type = SNDRV_SEQ_EVENT_NOTEOFF;
+ snd_seq_oss_fill_addr(dp, &event, dp->addr.port, dp->addr.client);
+
+ if (snd_seq_oss_process_event(dp, rec, &event))
+ return 0; /* invalid event - no need to insert queue */
+
+ event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer);
+ if (dp->timer->realtime || !dp->timer->running) {
+ snd_seq_oss_dispatch(dp, &event, 0, 0);
+ } else {
+ if (is_nonblock_mode(dp->file_mode))
+ rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, 0, 0);
+ else
+ rc = snd_seq_kernel_client_enqueue_blocking(dp->cseq, &event, opt, 0, 0);
+ }
+ return rc;
+}
+
+
+/*
+ * select / poll
+ */
+
+unsigned int
+snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait)
+{
+ unsigned int mask = 0;
+
+ /* input */
+ if (dp->readq && is_read_mode(dp->file_mode)) {
+ if (snd_seq_oss_readq_poll(dp->readq, file, wait))
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ /* output */
+ if (dp->writeq && is_write_mode(dp->file_mode)) {
+ if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait))
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ return mask;
+}
diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c
new file mode 100644
index 00000000000..638cc148706
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.c
@@ -0,0 +1,659 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device handlers
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "../seq_lock.h"
+#include <linux/init.h>
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30
+#define MAX_SYSEX_BUFLEN 128
+
+
+/*
+ * definition of synth info records
+ */
+
+/* sysex buffer */
+struct seq_oss_synth_sysex_t {
+ int len;
+ int skip;
+ unsigned char buf[MAX_SYSEX_BUFLEN];
+};
+
+/* synth info */
+struct seq_oss_synth_t {
+ int seq_device;
+
+ /* for synth_info */
+ int synth_type;
+ int synth_subtype;
+ int nr_voices;
+
+ char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
+ snd_seq_oss_callback_t oper;
+
+ int opened;
+
+ void *private_data;
+ snd_use_lock_t use_lock;
+};
+
+
+/*
+ * device table
+ */
+static int max_synth_devs;
+static seq_oss_synth_t *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+static seq_oss_synth_t midi_synth_dev = {
+ -1, /* seq_device */
+ SYNTH_TYPE_MIDI, /* synth_type */
+ 0, /* synth_subtype */
+ 16, /* nr_voices */
+ "MIDI", /* name */
+};
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static seq_oss_synth_t *get_synthdev(seq_oss_devinfo_t *dp, int dev);
+static void reset_channels(seq_oss_synthinfo_t *info);
+
+/*
+ * global initialization
+ */
+void __init
+snd_seq_oss_synth_init(void)
+{
+ snd_use_lock_init(&midi_synth_dev.use_lock);
+}
+
+/*
+ * registration of the synth device
+ */
+int
+snd_seq_oss_synth_register(snd_seq_device_t *dev)
+{
+ int i;
+ seq_oss_synth_t *rec;
+ snd_seq_oss_reg_t *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+ unsigned long flags;
+
+ if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL) {
+ snd_printk(KERN_ERR "can't malloc synth info\n");
+ return -ENOMEM;
+ }
+ rec->seq_device = -1;
+ rec->synth_type = reg->type;
+ rec->synth_subtype = reg->subtype;
+ rec->nr_voices = reg->nvoices;
+ rec->oper = reg->oper;
+ rec->private_data = reg->private_data;
+ rec->opened = 0;
+ snd_use_lock_init(&rec->use_lock);
+
+ /* copy and truncate the name of synth device */
+ strlcpy(rec->name, dev->name, sizeof(rec->name));
+
+ /* registration */
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_synth_devs; i++) {
+ if (synth_devs[i] == NULL)
+ break;
+ }
+ if (i >= max_synth_devs) {
+ if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_printk(KERN_ERR "no more synth slot\n");
+ kfree(rec);
+ return -ENOMEM;
+ }
+ max_synth_devs++;
+ }
+ rec->seq_device = i;
+ synth_devs[i] = rec;
+ debug_printk(("synth %s registered %d\n", rec->name, i));
+ spin_unlock_irqrestore(&register_lock, flags);
+ dev->driver_data = rec;
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+ if (i < SNDRV_CARDS)
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name);
+#endif
+ return 0;
+}
+
+
+int
+snd_seq_oss_synth_unregister(snd_seq_device_t *dev)
+{
+ int index;
+ seq_oss_synth_t *rec = dev->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (index = 0; index < max_synth_devs; index++) {
+ if (synth_devs[index] == rec)
+ break;
+ }
+ if (index >= max_synth_devs) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_printk(KERN_ERR "can't unregister synth\n");
+ return -EINVAL;
+ }
+ synth_devs[index] = NULL;
+ if (index == max_synth_devs - 1) {
+ for (index--; index >= 0; index--) {
+ if (synth_devs[index])
+ break;
+ }
+ max_synth_devs = index + 1;
+ }
+ spin_unlock_irqrestore(&register_lock, flags);
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+ if (rec->seq_device < SNDRV_CARDS)
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device);
+#endif
+
+ snd_use_lock_sync(&rec->use_lock);
+ kfree(rec);
+
+ return 0;
+}
+
+
+/*
+ */
+static seq_oss_synth_t *
+get_sdev(int dev)
+{
+ seq_oss_synth_t *rec;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ rec = synth_devs[dev];
+ if (rec)
+ snd_use_lock_use(&rec->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return rec;
+}
+
+
+/*
+ * set up synth tables
+ */
+
+void
+snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp)
+{
+ int i;
+ seq_oss_synth_t *rec;
+ seq_oss_synthinfo_t *info;
+
+ dp->max_synthdev = max_synth_devs;
+ dp->synth_opened = 0;
+ memset(dp->synths, 0, sizeof(dp->synths));
+ for (i = 0; i < dp->max_synthdev; i++) {
+ rec = get_sdev(i);
+ if (rec == NULL)
+ continue;
+ if (rec->oper.open == NULL || rec->oper.close == NULL) {
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ info = &dp->synths[i];
+ info->arg.app_index = dp->port;
+ info->arg.file_mode = dp->file_mode;
+ info->arg.seq_mode = dp->seq_mode;
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+ info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
+ else
+ info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+ info->opened = 0;
+ if (!try_module_get(rec->oper.owner)) {
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ if (rec->oper.open(&info->arg, rec->private_data) < 0) {
+ module_put(rec->oper.owner);
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ info->nr_voices = rec->nr_voices;
+ if (info->nr_voices > 0) {
+ info->ch = kcalloc(info->nr_voices, sizeof(seq_oss_chinfo_t), GFP_KERNEL);
+ if (!info->ch)
+ BUG();
+ reset_channels(info);
+ }
+ debug_printk(("synth %d assigned\n", i));
+ info->opened++;
+ rec->opened++;
+ dp->synth_opened++;
+ snd_use_lock_free(&rec->use_lock);
+ }
+}
+
+
+/*
+ * set up synth tables for MIDI emulation - /dev/music mode only
+ */
+
+void
+snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp)
+{
+ int i;
+
+ if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+ return;
+
+ for (i = 0; i < dp->max_mididev; i++) {
+ seq_oss_synthinfo_t *info;
+ info = &dp->synths[dp->max_synthdev];
+ if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0)
+ continue;
+ info->arg.app_index = dp->port;
+ info->arg.file_mode = dp->file_mode;
+ info->arg.seq_mode = dp->seq_mode;
+ info->arg.private_data = info;
+ info->is_midi = 1;
+ info->midi_mapped = i;
+ info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+ snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr);
+ info->opened = 1;
+ midi_synth_dev.opened++;
+ dp->max_synthdev++;
+ if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+ break;
+ }
+}
+
+
+/*
+ * clean up synth tables
+ */
+
+void
+snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp)
+{
+ int i;
+ seq_oss_synth_t *rec;
+ seq_oss_synthinfo_t *info;
+
+ snd_assert(dp->max_synthdev <= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS, return);
+ for (i = 0; i < dp->max_synthdev; i++) {
+ info = &dp->synths[i];
+ if (! info->opened)
+ continue;
+ if (info->is_midi) {
+ if (midi_synth_dev.opened > 0) {
+ snd_seq_oss_midi_close(dp, info->midi_mapped);
+ midi_synth_dev.opened--;
+ }
+ } else {
+ rec = get_sdev(i);
+ if (rec == NULL)
+ continue;
+ if (rec->opened > 0) {
+ debug_printk(("synth %d closed\n", i));
+ rec->oper.close(&info->arg);
+ module_put(rec->oper.owner);
+ rec->opened = 0;
+ }
+ snd_use_lock_free(&rec->use_lock);
+ }
+ if (info->sysex) {
+ kfree(info->sysex);
+ info->sysex = NULL;
+ }
+ if (info->ch) {
+ kfree(info->ch);
+ info->ch = NULL;
+ }
+ }
+ dp->synth_opened = 0;
+ dp->max_synthdev = 0;
+}
+
+/*
+ * check if the specified device is MIDI mapped device
+ */
+static int
+is_midi_dev(seq_oss_devinfo_t *dp, int dev)
+{
+ if (dev < 0 || dev >= dp->max_synthdev)
+ return 0;
+ if (dp->synths[dev].is_midi)
+ return 1;
+ return 0;
+}
+
+/*
+ * return synth device information pointer
+ */
+static seq_oss_synth_t *
+get_synthdev(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_synth_t *rec;
+ if (dev < 0 || dev >= dp->max_synthdev)
+ return NULL;
+ if (! dp->synths[dev].opened)
+ return NULL;
+ if (dp->synths[dev].is_midi)
+ return &midi_synth_dev;
+ if ((rec = get_sdev(dev)) == NULL)
+ return NULL;
+ if (! rec->opened) {
+ snd_use_lock_free(&rec->use_lock);
+ return NULL;
+ }
+ return rec;
+}
+
+
+/*
+ * reset note and velocity on each channel.
+ */
+static void
+reset_channels(seq_oss_synthinfo_t *info)
+{
+ int i;
+ if (info->ch == NULL || ! info->nr_voices)
+ return;
+ for (i = 0; i < info->nr_voices; i++) {
+ info->ch[i].note = -1;
+ info->ch[i].vel = 0;
+ }
+}
+
+
+/*
+ * reset synth device:
+ * call reset callback. if no callback is defined, send a heartbeat
+ * event to the corresponding port.
+ */
+void
+snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_synth_t *rec;
+ seq_oss_synthinfo_t *info;
+
+ snd_assert(dev >= 0 && dev < dp->max_synthdev, return);
+ info = &dp->synths[dev];
+ if (! info->opened)
+ return;
+ if (info->sysex)
+ info->sysex->len = 0; /* reset sysex */
+ reset_channels(info);
+ if (info->is_midi) {
+ if (midi_synth_dev.opened <= 0)
+ return;
+ snd_seq_oss_midi_reset(dp, info->midi_mapped);
+ /* reopen the device */
+ snd_seq_oss_midi_close(dp, dev);
+ if (snd_seq_oss_midi_open(dp, info->midi_mapped,
+ dp->file_mode) < 0) {
+ midi_synth_dev.opened--;
+ info->opened = 0;
+ if (info->sysex) {
+ kfree(info->sysex);
+ info->sysex = NULL;
+ }
+ if (info->ch) {
+ kfree(info->ch);
+ info->ch = NULL;
+ }
+ }
+ return;
+ }
+
+ rec = get_sdev(dev);
+ if (rec == NULL)
+ return;
+ if (rec->oper.reset) {
+ rec->oper.reset(&info->arg);
+ } else {
+ snd_seq_event_t ev;
+ memset(&ev, 0, sizeof(ev));
+ snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client,
+ info->arg.addr.port);
+ ev.type = SNDRV_SEQ_EVENT_RESET;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ }
+ snd_use_lock_free(&rec->use_lock);
+}
+
+
+/*
+ * load a patch record:
+ * call load_patch callback function
+ */
+int
+snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt,
+ const char __user *buf, int p, int c)
+{
+ seq_oss_synth_t *rec;
+ int rc;
+
+ if (dev < 0 || dev >= dp->max_synthdev)
+ return -ENXIO;
+
+ if (is_midi_dev(dp, dev))
+ return 0;
+ if ((rec = get_synthdev(dp, dev)) == NULL)
+ return -ENXIO;
+
+ if (rec->oper.load_patch == NULL)
+ rc = -ENXIO;
+ else
+ rc = rec->oper.load_patch(&dp->synths[dev].arg, fmt, buf, p, c);
+ snd_use_lock_free(&rec->use_lock);
+ return rc;
+}
+
+/*
+ * check if the device is valid synth device
+ */
+int
+snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev)
+{
+ seq_oss_synth_t *rec;
+ rec = get_synthdev(dp, dev);
+ if (rec) {
+ snd_use_lock_free(&rec->use_lock);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * receive OSS 6 byte sysex packet:
+ * the full sysex message will be sent if it reaches to the end of data
+ * (0xff).
+ */
+int
+snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev)
+{
+ int i, send;
+ unsigned char *dest;
+ seq_oss_synth_sysex_t *sysex;
+
+ if (! snd_seq_oss_synth_is_valid(dp, dev))
+ return -ENXIO;
+
+ sysex = dp->synths[dev].sysex;
+ if (sysex == NULL) {
+ sysex = kcalloc(1, sizeof(*sysex), GFP_KERNEL);
+ if (sysex == NULL)
+ return -ENOMEM;
+ dp->synths[dev].sysex = sysex;
+ }
+
+ send = 0;
+ dest = sysex->buf + sysex->len;
+ /* copy 6 byte packet to the buffer */
+ for (i = 0; i < 6; i++) {
+ if (buf[i] == 0xff) {
+ send = 1;
+ break;
+ }
+ dest[i] = buf[i];
+ sysex->len++;
+ if (sysex->len >= MAX_SYSEX_BUFLEN) {
+ sysex->len = 0;
+ sysex->skip = 1;
+ break;
+ }
+ }
+
+ if (sysex->len && send) {
+ if (sysex->skip) {
+ sysex->skip = 0;
+ sysex->len = 0;
+ return -EINVAL; /* skip */
+ }
+ /* copy the data to event record and send it */
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+ if (snd_seq_oss_synth_addr(dp, dev, ev))
+ return -EINVAL;
+ ev->data.ext.len = sysex->len;
+ ev->data.ext.ptr = sysex->buf;
+ sysex->len = 0;
+ return 0;
+ }
+
+ return -EINVAL; /* skip */
+}
+
+/*
+ * fill the event source/destination addresses
+ */
+int
+snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev)
+{
+ if (! snd_seq_oss_synth_is_valid(dp, dev))
+ return -EINVAL;
+ snd_seq_oss_fill_addr(dp, ev, dp->synths[dev].arg.addr.client,
+ dp->synths[dev].arg.addr.port);
+ return 0;
+}
+
+
+/*
+ * OSS compatible ioctl
+ */
+int
+snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr)
+{
+ seq_oss_synth_t *rec;
+ int rc;
+
+ if (is_midi_dev(dp, dev))
+ return -ENXIO;
+ if ((rec = get_synthdev(dp, dev)) == NULL)
+ return -ENXIO;
+ if (rec->oper.ioctl == NULL)
+ rc = -ENXIO;
+ else
+ rc = rec->oper.ioctl(&dp->synths[dev].arg, cmd, addr);
+ snd_use_lock_free(&rec->use_lock);
+ return rc;
+}
+
+
+/*
+ * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME
+ */
+int
+snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev)
+{
+ if (! snd_seq_oss_synth_is_valid(dp, dev) || is_midi_dev(dp, dev))
+ return -ENXIO;
+ ev->type = SNDRV_SEQ_EVENT_OSS;
+ memcpy(ev->data.raw8.d, data, 8);
+ return snd_seq_oss_synth_addr(dp, dev, ev);
+}
+
+
+/*
+ * create OSS compatible synth_info record
+ */
+int
+snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf)
+{
+ seq_oss_synth_t *rec;
+
+ if (dp->synths[dev].is_midi) {
+ struct midi_info minf;
+ snd_seq_oss_midi_make_info(dp, dp->synths[dev].midi_mapped, &minf);
+ inf->synth_type = SYNTH_TYPE_MIDI;
+ inf->synth_subtype = 0;
+ inf->nr_voices = 16;
+ inf->device = dev;
+ strlcpy(inf->name, minf.name, sizeof(inf->name));
+ } else {
+ if ((rec = get_synthdev(dp, dev)) == NULL)
+ return -ENXIO;
+ inf->synth_type = rec->synth_type;
+ inf->synth_subtype = rec->synth_subtype;
+ inf->nr_voices = rec->nr_voices;
+ inf->device = dev;
+ strlcpy(inf->name, rec->name, sizeof(inf->name));
+ snd_use_lock_free(&rec->use_lock);
+ }
+ return 0;
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_synth_info_read(snd_info_buffer_t *buf)
+{
+ int i;
+ seq_oss_synth_t *rec;
+
+ snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs);
+ for (i = 0; i < max_synth_devs; i++) {
+ snd_iprintf(buf, "\nsynth %d: ", i);
+ rec = get_sdev(i);
+ if (rec == NULL) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "[%s]\n", rec->name);
+ snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n",
+ rec->synth_type, rec->synth_subtype,
+ rec->nr_voices);
+ snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n",
+ enabled_str((long)rec->oper.ioctl),
+ enabled_str((long)rec->oper.load_patch));
+ snd_use_lock_free(&rec->use_lock);
+ }
+}
+
diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h
new file mode 100644
index 00000000000..07bc0e2cfb8
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.h
@@ -0,0 +1,49 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device information
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_SYNTH_H
+#define __SEQ_OSS_SYNTH_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+#include <sound/seq_device.h>
+
+typedef struct seq_oss_synth_t seq_oss_synth_t;
+
+void snd_seq_oss_synth_init(void);
+int snd_seq_oss_synth_register(snd_seq_device_t *dev);
+int snd_seq_oss_synth_unregister(snd_seq_device_t *dev);
+void snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp);
+void snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp);
+void snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp);
+
+void snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt, const char __user *buf, int p, int c);
+int snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev);
+int snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev);
+int snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev);
+int snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr);
+int snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev);
+
+int snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c
new file mode 100644
index 00000000000..42ca9493fa6
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.c
@@ -0,0 +1,283 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * Timer control routines
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+
+/*
+ */
+#define MIN_OSS_TEMPO 8
+#define MAX_OSS_TEMPO 360
+#define MIN_OSS_TIMEBASE 1
+#define MAX_OSS_TIMEBASE 1000
+
+/*
+ */
+static void calc_alsa_tempo(seq_oss_timer_t *timer);
+static int send_timer_event(seq_oss_devinfo_t *dp, int type, int value);
+
+
+/*
+ * create and register a new timer.
+ * if queue is not started yet, start it.
+ */
+seq_oss_timer_t *
+snd_seq_oss_timer_new(seq_oss_devinfo_t *dp)
+{
+ seq_oss_timer_t *rec;
+
+ rec = kcalloc(1, sizeof(*rec), GFP_KERNEL);
+ if (rec == NULL)
+ return NULL;
+
+ rec->dp = dp;
+ rec->cur_tick = 0;
+ rec->realtime = 0;
+ rec->running = 0;
+ rec->oss_tempo = 60;
+ rec->oss_timebase = 100;
+ calc_alsa_tempo(rec);
+
+ return rec;
+}
+
+
+/*
+ * delete timer.
+ * if no more timer exists, stop the queue.
+ */
+void
+snd_seq_oss_timer_delete(seq_oss_timer_t *rec)
+{
+ if (rec) {
+ snd_seq_oss_timer_stop(rec);
+ kfree(rec);
+ }
+}
+
+
+/*
+ * process one timing event
+ * return 1 : event proceseed -- skip this event
+ * 0 : not a timer event -- enqueue this event
+ */
+int
+snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *ev)
+{
+ abstime_t parm = ev->t.time;
+
+ if (ev->t.code == EV_TIMING) {
+ switch (ev->t.cmd) {
+ case TMR_WAIT_REL:
+ parm += rec->cur_tick;
+ rec->realtime = 0;
+ /* continue to next */
+ case TMR_WAIT_ABS:
+ if (parm == 0) {
+ rec->realtime = 1;
+ } else if (parm >= rec->cur_tick) {
+ rec->realtime = 0;
+ rec->cur_tick = parm;
+ }
+ return 1; /* skip this event */
+
+ case TMR_START:
+ snd_seq_oss_timer_start(rec);
+ return 1;
+
+ }
+ } else if (ev->s.code == SEQ_WAIT) {
+ /* time = from 1 to 3 bytes */
+ parm = (ev->echo >> 8) & 0xffffff;
+ if (parm > rec->cur_tick) {
+ /* set next event time */
+ rec->cur_tick = parm;
+ rec->realtime = 0;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * convert tempo units
+ */
+static void
+calc_alsa_tempo(seq_oss_timer_t *timer)
+{
+ timer->tempo = (60 * 1000000) / timer->oss_tempo;
+ timer->ppq = timer->oss_timebase;
+}
+
+
+/*
+ * dispatch a timer event
+ */
+static int
+send_timer_event(seq_oss_devinfo_t *dp, int type, int value)
+{
+ snd_seq_event_t ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = type;
+ ev.source.client = dp->cseq;
+ ev.source.port = 0;
+ ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+ ev.queue = dp->queue;
+ ev.data.queue.queue = dp->queue;
+ ev.data.queue.param.value = value;
+ return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0);
+}
+
+/*
+ * set queue tempo and start queue
+ */
+int
+snd_seq_oss_timer_start(seq_oss_timer_t *timer)
+{
+ seq_oss_devinfo_t *dp = timer->dp;
+ snd_seq_queue_tempo_t tmprec;
+
+ if (timer->running)
+ snd_seq_oss_timer_stop(timer);
+
+ memset(&tmprec, 0, sizeof(tmprec));
+ tmprec.queue = dp->queue;
+ tmprec.ppq = timer->ppq;
+ tmprec.tempo = timer->tempo;
+ snd_seq_set_queue_tempo(dp->cseq, &tmprec);
+
+ send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0);
+ timer->running = 1;
+ timer->cur_tick = 0;
+ return 0;
+}
+
+
+/*
+ * stop queue
+ */
+int
+snd_seq_oss_timer_stop(seq_oss_timer_t *timer)
+{
+ if (! timer->running)
+ return 0;
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0);
+ timer->running = 0;
+ return 0;
+}
+
+
+/*
+ * continue queue
+ */
+int
+snd_seq_oss_timer_continue(seq_oss_timer_t *timer)
+{
+ if (timer->running)
+ return 0;
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0);
+ timer->running = 1;
+ return 0;
+}
+
+
+/*
+ * change queue tempo
+ */
+int
+snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value)
+{
+ if (value < MIN_OSS_TEMPO)
+ value = MIN_OSS_TEMPO;
+ else if (value > MAX_OSS_TEMPO)
+ value = MAX_OSS_TEMPO;
+ timer->oss_tempo = value;
+ calc_alsa_tempo(timer);
+ if (timer->running)
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo);
+ return 0;
+}
+
+
+/*
+ * ioctls
+ */
+int
+snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg)
+{
+ int value;
+
+ if (cmd == SNDCTL_SEQ_CTRLRATE) {
+ debug_printk(("ctrl rate\n"));
+ /* if *arg == 0, just return the current rate */
+ if (get_user(value, arg))
+ return -EFAULT;
+ if (value)
+ return -EINVAL;
+ value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60;
+ return put_user(value, arg) ? -EFAULT : 0;
+ }
+
+ if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+ return 0;
+
+ switch (cmd) {
+ case SNDCTL_TMR_START:
+ debug_printk(("timer start\n"));
+ return snd_seq_oss_timer_start(timer);
+ case SNDCTL_TMR_STOP:
+ debug_printk(("timer stop\n"));
+ return snd_seq_oss_timer_stop(timer);
+ case SNDCTL_TMR_CONTINUE:
+ debug_printk(("timer continue\n"));
+ return snd_seq_oss_timer_continue(timer);
+ case SNDCTL_TMR_TEMPO:
+ debug_printk(("timer tempo\n"));
+ if (get_user(value, arg))
+ return -EFAULT;
+ return snd_seq_oss_timer_tempo(timer, value);
+ case SNDCTL_TMR_TIMEBASE:
+ debug_printk(("timer timebase\n"));
+ if (get_user(value, arg))
+ return -EFAULT;
+ if (value < MIN_OSS_TIMEBASE)
+ value = MIN_OSS_TIMEBASE;
+ else if (value > MAX_OSS_TIMEBASE)
+ value = MAX_OSS_TIMEBASE;
+ timer->oss_timebase = value;
+ calc_alsa_tempo(timer);
+ return 0;
+
+ case SNDCTL_TMR_METRONOME:
+ case SNDCTL_TMR_SELECT:
+ case SNDCTL_TMR_SOURCE:
+ debug_printk(("timer XXX\n"));
+ /* not supported */
+ return 0;
+ }
+ return 0;
+}
diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h
new file mode 100644
index 00000000000..6e4dbd8504c
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.h
@@ -0,0 +1,70 @@
+/*
+ * OSS compatible sequencer driver
+ * timer handling routines
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_TIMER_H
+#define __SEQ_OSS_TIMER_H
+
+#include "seq_oss_device.h"
+
+/*
+ * timer information definition
+ */
+struct seq_oss_timer_t {
+ seq_oss_devinfo_t *dp;
+ reltime_t cur_tick;
+ int realtime;
+ int running;
+ int tempo, ppq; /* ALSA queue */
+ int oss_tempo, oss_timebase;
+};
+
+
+seq_oss_timer_t *snd_seq_oss_timer_new(seq_oss_devinfo_t *dp);
+void snd_seq_oss_timer_delete(seq_oss_timer_t *dp);
+
+int snd_seq_oss_timer_start(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_stop(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_continue(seq_oss_timer_t *timer);
+int snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value);
+#define snd_seq_oss_timer_reset snd_seq_oss_timer_start
+
+int snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg);
+
+/*
+ * get current processed time
+ */
+static inline abstime_t
+snd_seq_oss_timer_cur_tick(seq_oss_timer_t *timer)
+{
+ return timer->cur_tick;
+}
+
+
+/*
+ * is realtime event?
+ */
+static inline int
+snd_seq_oss_timer_is_realtime(seq_oss_timer_t *timer)
+{
+ return timer->realtime;
+}
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c
new file mode 100644
index 00000000000..87f85f7ee81
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.c
@@ -0,0 +1,170 @@
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_writeq.c - write queue and sync
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 "seq_oss_writeq.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include "../seq_clientmgr.h"
+#include <linux/wait.h>
+
+
+/*
+ * create a write queue record
+ */
+seq_oss_writeq_t *
+snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen)
+{
+ seq_oss_writeq_t *q;
+ snd_seq_client_pool_t pool;
+
+ if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL)
+ return NULL;
+ q->dp = dp;
+ q->maxlen = maxlen;
+ spin_lock_init(&q->sync_lock);
+ q->sync_event_put = 0;
+ q->sync_time = 0;
+ init_waitqueue_head(&q->sync_sleep);
+
+ memset(&pool, 0, sizeof(pool));
+ pool.client = dp->cseq;
+ pool.output_pool = maxlen;
+ pool.output_room = maxlen / 2;
+
+ snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+
+ return q;
+}
+
+/*
+ * delete the write queue
+ */
+void
+snd_seq_oss_writeq_delete(seq_oss_writeq_t *q)
+{
+ snd_seq_oss_writeq_clear(q); /* to be sure */
+ kfree(q);
+}
+
+
+/*
+ * reset the write queue
+ */
+void
+snd_seq_oss_writeq_clear(seq_oss_writeq_t *q)
+{
+ snd_seq_remove_events_t reset;
+
+ memset(&reset, 0, sizeof(reset));
+ reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset);
+
+ /* wake up sleepers if any */
+ snd_seq_oss_writeq_wakeup(q, 0);
+}
+
+/*
+ * wait until the write buffer has enough room
+ */
+int
+snd_seq_oss_writeq_sync(seq_oss_writeq_t *q)
+{
+ seq_oss_devinfo_t *dp = q->dp;
+ abstime_t time;
+
+ time = snd_seq_oss_timer_cur_tick(dp->timer);
+ if (q->sync_time >= time)
+ return 0; /* already finished */
+
+ if (! q->sync_event_put) {
+ snd_seq_event_t ev;
+ evrec_t *rec;
+
+ /* put echoback event */
+ memset(&ev, 0, sizeof(ev));
+ ev.flags = 0;
+ ev.type = SNDRV_SEQ_EVENT_ECHO;
+ ev.time.tick = time;
+ /* echo back to itself */
+ snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port);
+ rec = (evrec_t*)&ev.data;
+ rec->t.code = SEQ_SYNCTIMER;
+ rec->t.time = time;
+ q->sync_event_put = 1;
+ snd_seq_kernel_client_enqueue_blocking(dp->cseq, &ev, NULL, 0, 0);
+ }
+
+ wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ);
+ if (signal_pending(current))
+ /* interrupted - return 0 to finish sync */
+ q->sync_event_put = 0;
+ if (! q->sync_event_put || q->sync_time >= time)
+ return 0;
+ return 1;
+}
+
+/*
+ * wake up sync - echo event was catched
+ */
+void
+snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->sync_lock, flags);
+ q->sync_time = time;
+ q->sync_event_put = 0;
+ if (waitqueue_active(&q->sync_sleep)) {
+ wake_up(&q->sync_sleep);
+ }
+ spin_unlock_irqrestore(&q->sync_lock, flags);
+}
+
+
+/*
+ * return the unused pool size
+ */
+int
+snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q)
+{
+ snd_seq_client_pool_t pool;
+ pool.client = q->dp->cseq;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+ return pool.output_free;
+}
+
+
+/*
+ * set output threshold size from ioctl
+ */
+void
+snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int val)
+{
+ snd_seq_client_pool_t pool;
+ pool.client = q->dp->cseq;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+ pool.output_room = val;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+}
+
diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h
new file mode 100644
index 00000000000..6a13c85e239
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.h
@@ -0,0 +1,50 @@
+/*
+ * OSS compatible sequencer driver
+ * write priority queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ */
+
+#ifndef __SEQ_OSS_WRITEQ_H
+#define __SEQ_OSS_WRITEQ_H
+
+#include "seq_oss_device.h"
+
+
+struct seq_oss_writeq_t {
+ seq_oss_devinfo_t *dp;
+ int maxlen;
+ abstime_t sync_time;
+ int sync_event_put;
+ wait_queue_head_t sync_sleep;
+ spinlock_t sync_lock;
+};
+
+
+/*
+ * seq_oss_writeq.c
+ */
+seq_oss_writeq_t *snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen);
+void snd_seq_oss_writeq_delete(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_clear(seq_oss_writeq_t *q);
+int snd_seq_oss_writeq_sync(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time);
+int snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q);
+void snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int size);
+
+
+#endif
diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c
new file mode 100644
index 00000000000..7449d2a6262
--- /dev/null
+++ b/sound/core/seq/seq.c
@@ -0,0 +1,147 @@
+/*
+ * ALSA sequencer main module
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_clientmgr.h"
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_lock.h"
+#include "seq_timer.h"
+#include "seq_system.h"
+#include "seq_info.h"
+#include <sound/seq_device.h>
+
+#if defined(CONFIG_SND_SEQ_DUMMY_MODULE)
+int seq_client_load[64] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 63] = -1};
+#else
+int seq_client_load[64] = {[0 ... 63] = -1};
+#endif
+int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL;
+int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE;
+int seq_default_timer_card = -1;
+int seq_default_timer_device = SNDRV_TIMER_GLOBAL_SYSTEM;
+int seq_default_timer_subdevice = 0;
+int seq_default_timer_resolution = 0; /* Hz */
+
+MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer.");
+MODULE_LICENSE("GPL");
+
+module_param_array(seq_client_load, int, NULL, 0444);
+MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod.");
+module_param(seq_default_timer_class, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_class, "The default timer class.");
+module_param(seq_default_timer_sclass, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class.");
+module_param(seq_default_timer_card, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number.");
+module_param(seq_default_timer_device, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number.");
+module_param(seq_default_timer_subdevice, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number.");
+module_param(seq_default_timer_resolution, int, 0644);
+MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz.");
+
+/*
+ * INIT PART
+ */
+
+static int __init alsa_seq_init(void)
+{
+ int err;
+
+ snd_seq_autoload_lock();
+ if ((err = client_init_data()) < 0)
+ goto error;
+
+ /* init memory, room for selected events */
+ if ((err = snd_sequencer_memory_init()) < 0)
+ goto error;
+
+ /* init event queues */
+ if ((err = snd_seq_queues_init()) < 0)
+ goto error;
+
+ /* register sequencer device */
+ if ((err = snd_sequencer_device_init()) < 0)
+ goto error;
+
+ /* register proc interface */
+ if ((err = snd_seq_info_init()) < 0)
+ goto error;
+
+ /* register our internal client */
+ if ((err = snd_seq_system_client_init()) < 0)
+ goto error;
+
+ error:
+ snd_seq_autoload_unlock();
+ return err;
+}
+
+static void __exit alsa_seq_exit(void)
+{
+ /* unregister our internal client */
+ snd_seq_system_client_done();
+
+ /* unregister proc interface */
+ snd_seq_info_done();
+
+ /* delete timing queues */
+ snd_seq_queues_delete();
+
+ /* unregister sequencer device */
+ snd_sequencer_device_done();
+
+ /* release event memory */
+ snd_sequencer_memory_done();
+}
+
+module_init(alsa_seq_init)
+module_exit(alsa_seq_exit)
+
+ /* seq_clientmgr.c */
+EXPORT_SYMBOL(snd_seq_create_kernel_client);
+EXPORT_SYMBOL(snd_seq_delete_kernel_client);
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue);
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking);
+EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
+EXPORT_SYMBOL(snd_seq_kernel_client_ctl);
+EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
+EXPORT_SYMBOL(snd_seq_set_queue_tempo);
+ /* seq_memory.c */
+EXPORT_SYMBOL(snd_seq_expand_var_event);
+EXPORT_SYMBOL(snd_seq_dump_var_event);
+ /* seq_ports.c */
+EXPORT_SYMBOL(snd_seq_event_port_attach);
+EXPORT_SYMBOL(snd_seq_event_port_detach);
+ /* seq_lock.c */
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+/*EXPORT_SYMBOL(snd_seq_sleep_in_lock);*/
+/*EXPORT_SYMBOL(snd_seq_sleep_timeout_in_lock);*/
+EXPORT_SYMBOL(snd_use_lock_sync_helper);
+#endif
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
new file mode 100644
index 00000000000..d8f76afd284
--- /dev/null
+++ b/sound/core/seq/seq_clientmgr.c
@@ -0,0 +1,2503 @@
+/*
+ * ALSA sequencer Client Manager
+ * Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl>
+ * Jaroslav Kysela <perex@suse.cz>
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <linux/kmod.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_clientmgr.h"
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_timer.h"
+#include "seq_info.h"
+#include "seq_system.h"
+#include <sound/seq_device.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+
+/* Client Manager
+
+ * this module handles the connections of userland and kernel clients
+ *
+ */
+
+#define SNDRV_SEQ_LFLG_INPUT 0x0001
+#define SNDRV_SEQ_LFLG_OUTPUT 0x0002
+#define SNDRV_SEQ_LFLG_OPEN (SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT)
+
+static DEFINE_SPINLOCK(clients_lock);
+static DECLARE_MUTEX(register_mutex);
+
+/*
+ * client table
+ */
+static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];
+static client_t *clienttab[SNDRV_SEQ_MAX_CLIENTS];
+static usage_t client_usage;
+
+/*
+ * prototypes
+ */
+static int bounce_error_event(client_t *client, snd_seq_event_t *event, int err, int atomic, int hop);
+static int snd_seq_deliver_single_event(client_t *client, snd_seq_event_t *event, int filter, int atomic, int hop);
+
+/*
+ */
+
+static inline mm_segment_t snd_enter_user(void)
+{
+ mm_segment_t fs = get_fs();
+ set_fs(get_ds());
+ return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+ set_fs(fs);
+}
+
+/*
+ */
+static inline unsigned short snd_seq_file_flags(struct file *file)
+{
+ switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
+ case FMODE_WRITE:
+ return SNDRV_SEQ_LFLG_OUTPUT;
+ case FMODE_READ:
+ return SNDRV_SEQ_LFLG_INPUT;
+ default:
+ return SNDRV_SEQ_LFLG_OPEN;
+ }
+}
+
+static inline int snd_seq_write_pool_allocated(client_t *client)
+{
+ return snd_seq_total_cells(client->pool) > 0;
+}
+
+/* return pointer to client structure for specified id */
+static client_t *clientptr(int clientid)
+{
+ if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
+ snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid);
+ return NULL;
+ }
+ return clienttab[clientid];
+}
+
+extern int seq_client_load[];
+
+client_t *snd_seq_client_use_ptr(int clientid)
+{
+ unsigned long flags;
+ client_t *client;
+
+ if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
+ snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid);
+ return NULL;
+ }
+ spin_lock_irqsave(&clients_lock, flags);
+ client = clientptr(clientid);
+ if (client)
+ goto __lock;
+ if (clienttablock[clientid]) {
+ spin_unlock_irqrestore(&clients_lock, flags);
+ return NULL;
+ }
+ spin_unlock_irqrestore(&clients_lock, flags);
+#ifdef CONFIG_KMOD
+ if (!in_interrupt() && current->fs->root) {
+ static char client_requested[64];
+ static char card_requested[SNDRV_CARDS];
+ if (clientid < 64) {
+ int idx;
+
+ if (! client_requested[clientid] && current->fs->root) {
+ client_requested[clientid] = 1;
+ for (idx = 0; idx < 64; idx++) {
+ if (seq_client_load[idx] < 0)
+ break;
+ if (seq_client_load[idx] == clientid) {
+ request_module("snd-seq-client-%i", clientid);
+ break;
+ }
+ }
+ }
+ } else if (clientid >= 64 && clientid < 128) {
+ int card = (clientid - 64) / 8;
+ if (card < snd_ecards_limit) {
+ if (! card_requested[card]) {
+ card_requested[card] = 1;
+ snd_request_card(card);
+ }
+ snd_seq_device_load_drivers();
+ }
+ }
+ spin_lock_irqsave(&clients_lock, flags);
+ client = clientptr(clientid);
+ if (client)
+ goto __lock;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ }
+#endif
+ return NULL;
+
+ __lock:
+ snd_use_lock_use(&client->use_lock);
+ spin_unlock_irqrestore(&clients_lock, flags);
+ return client;
+}
+
+static void usage_alloc(usage_t * res, int num)
+{
+ res->cur += num;
+ if (res->cur > res->peak)
+ res->peak = res->cur;
+}
+
+static void usage_free(usage_t * res, int num)
+{
+ res->cur -= num;
+}
+
+/* initialise data structures */
+int __init client_init_data(void)
+{
+ /* zap out the client table */
+ memset(&clienttablock, 0, sizeof(clienttablock));
+ memset(&clienttab, 0, sizeof(clienttab));
+ return 0;
+}
+
+
+static client_t *seq_create_client1(int client_index, int poolsize)
+{
+ unsigned long flags;
+ int c;
+ client_t *client;
+
+ /* init client data */
+ client = kcalloc(1, sizeof(*client), GFP_KERNEL);
+ if (client == NULL)
+ return NULL;
+ client->pool = snd_seq_pool_new(poolsize);
+ if (client->pool == NULL) {
+ kfree(client);
+ return NULL;
+ }
+ client->type = NO_CLIENT;
+ snd_use_lock_init(&client->use_lock);
+ rwlock_init(&client->ports_lock);
+ init_MUTEX(&client->ports_mutex);
+ INIT_LIST_HEAD(&client->ports_list_head);
+
+ /* find free slot in the client table */
+ spin_lock_irqsave(&clients_lock, flags);
+ if (client_index < 0) {
+ for (c = 128; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
+ if (clienttab[c] || clienttablock[c])
+ continue;
+ clienttab[client->number = c] = client;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ return client;
+ }
+ } else {
+ if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
+ clienttab[client->number = client_index] = client;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ return client;
+ }
+ }
+ spin_unlock_irqrestore(&clients_lock, flags);
+ snd_seq_pool_delete(&client->pool);
+ kfree(client);
+ return NULL; /* no free slot found or busy, return failure code */
+}
+
+
+static int seq_free_client1(client_t *client)
+{
+ unsigned long flags;
+
+ snd_assert(client != NULL, return -EINVAL);
+ snd_seq_delete_all_ports(client);
+ snd_seq_queue_client_leave(client->number);
+ spin_lock_irqsave(&clients_lock, flags);
+ clienttablock[client->number] = 1;
+ clienttab[client->number] = NULL;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ snd_use_lock_sync(&client->use_lock);
+ snd_seq_queue_client_termination(client->number);
+ if (client->pool)
+ snd_seq_pool_delete(&client->pool);
+ spin_lock_irqsave(&clients_lock, flags);
+ clienttablock[client->number] = 0;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ return 0;
+}
+
+
+static void seq_free_client(client_t * client)
+{
+ down(&register_mutex);
+ switch (client->type) {
+ case NO_CLIENT:
+ snd_printk(KERN_WARNING "Seq: Trying to free unused client %d\n", client->number);
+ break;
+ case USER_CLIENT:
+ case KERNEL_CLIENT:
+ seq_free_client1(client);
+ usage_free(&client_usage, 1);
+ break;
+
+ default:
+ snd_printk(KERN_ERR "Seq: Trying to free client %d with undefined type = %d\n", client->number, client->type);
+ }
+ up(&register_mutex);
+
+ snd_seq_system_client_ev_client_exit(client->number);
+}
+
+
+
+/* -------------------------------------------------------- */
+
+/* create a user client */
+static int snd_seq_open(struct inode *inode, struct file *file)
+{
+ int c, mode; /* client id */
+ client_t *client;
+ user_client_t *user;
+
+ if (down_interruptible(&register_mutex))
+ return -ERESTARTSYS;
+ client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
+ if (client == NULL) {
+ up(&register_mutex);
+ return -ENOMEM; /* failure code */
+ }
+
+ mode = snd_seq_file_flags(file);
+ if (mode & SNDRV_SEQ_LFLG_INPUT)
+ client->accept_input = 1;
+ if (mode & SNDRV_SEQ_LFLG_OUTPUT)
+ client->accept_output = 1;
+
+ user = &client->data.user;
+ user->fifo = NULL;
+ user->fifo_pool_size = 0;
+
+ if (mode & SNDRV_SEQ_LFLG_INPUT) {
+ user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS;
+ user->fifo = snd_seq_fifo_new(user->fifo_pool_size);
+ if (user->fifo == NULL) {
+ seq_free_client1(client);
+ kfree(client);
+ up(&register_mutex);
+ return -ENOMEM;
+ }
+ }
+
+ usage_alloc(&client_usage, 1);
+ client->type = USER_CLIENT;
+ up(&register_mutex);
+
+ c = client->number;
+ file->private_data = client;
+
+ /* fill client data */
+ user->file = file;
+ sprintf(client->name, "Client-%d", c);
+
+ /* make others aware this new client */
+ snd_seq_system_client_ev_client_start(c);
+
+ return 0;
+}
+
+/* delete a user client */
+static int snd_seq_release(struct inode *inode, struct file *file)
+{
+ client_t *client = (client_t *) file->private_data;
+
+ if (client) {
+ seq_free_client(client);
+ if (client->data.user.fifo)
+ snd_seq_fifo_delete(&client->data.user.fifo);
+ kfree(client);
+ }
+
+ return 0;
+}
+
+
+/* handle client read() */
+/* possible error values:
+ * -ENXIO invalid client or file open mode
+ * -ENOSPC FIFO overflow (the flag is cleared after this error report)
+ * -EINVAL no enough user-space buffer to write the whole event
+ * -EFAULT seg. fault during copy to user space
+ */
+static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ client_t *client = (client_t *) file->private_data;
+ fifo_t *fifo;
+ int err;
+ long result = 0;
+ snd_seq_event_cell_t *cell;
+
+ if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT))
+ return -ENXIO;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+
+ /* check client structures are in place */
+ snd_assert(client != NULL, return -ENXIO);
+
+ if (!client->accept_input || (fifo = client->data.user.fifo) == NULL)
+ return -ENXIO;
+
+ if (atomic_read(&fifo->overflow) > 0) {
+ /* buffer overflow is detected */
+ snd_seq_fifo_clear(fifo);
+ /* return error code */
+ return -ENOSPC;
+ }
+
+ cell = NULL;
+ err = 0;
+ snd_seq_fifo_lock(fifo);
+
+ /* while data available in queue */
+ while (count >= sizeof(snd_seq_event_t)) {
+ int nonblock;
+
+ nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
+ if ((err = snd_seq_fifo_cell_out(fifo, &cell, nonblock)) < 0) {
+ break;
+ }
+ if (snd_seq_ev_is_variable(&cell->event)) {
+ snd_seq_event_t tmpev;
+ tmpev = cell->event;
+ tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
+ if (copy_to_user(buf, &tmpev, sizeof(snd_seq_event_t))) {
+ err = -EFAULT;
+ break;
+ }
+ count -= sizeof(snd_seq_event_t);
+ buf += sizeof(snd_seq_event_t);
+ err = snd_seq_expand_var_event(&cell->event, count, (char *)buf, 0, sizeof(snd_seq_event_t));
+ if (err < 0)
+ break;
+ result += err;
+ count -= err;
+ buf += err;
+ } else {
+ if (copy_to_user(buf, &cell->event, sizeof(snd_seq_event_t))) {
+ err = -EFAULT;
+ break;
+ }
+ count -= sizeof(snd_seq_event_t);
+ buf += sizeof(snd_seq_event_t);
+ }
+ snd_seq_cell_free(cell);
+ cell = NULL; /* to be sure */
+ result += sizeof(snd_seq_event_t);
+ }
+
+ if (err < 0) {
+ if (cell)
+ snd_seq_fifo_cell_putback(fifo, cell);
+ if (err == -EAGAIN && result > 0)
+ err = 0;
+ }
+ snd_seq_fifo_unlock(fifo);
+
+ return (err < 0) ? err : result;
+}
+
+
+/*
+ * check access permission to the port
+ */
+static int check_port_perm(client_port_t *port, unsigned int flags)
+{
+ if ((port->capability & flags) != flags)
+ return 0;
+ return flags;
+}
+
+/*
+ * check if the destination client is available, and return the pointer
+ * if filter is non-zero, client filter bitmap is tested.
+ */
+static client_t *get_event_dest_client(snd_seq_event_t *event, int filter)
+{
+ client_t *dest;
+
+ dest = snd_seq_client_use_ptr(event->dest.client);
+ if (dest == NULL)
+ return NULL;
+ if (! dest->accept_input)
+ goto __not_avail;
+ if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) &&
+ ! test_bit(event->type, dest->event_filter))
+ goto __not_avail;
+ if (filter && !(dest->filter & filter))
+ goto __not_avail;
+
+ return dest; /* ok - accessible */
+__not_avail:
+ snd_seq_client_unlock(dest);
+ return NULL;
+}
+
+
+/*
+ * Return the error event.
+ *
+ * If the receiver client is a user client, the original event is
+ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If
+ * the original event is also variable length, the external data is
+ * copied after the event record.
+ * If the receiver client is a kernel client, the original event is
+ * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra
+ * kmalloc.
+ */
+static int bounce_error_event(client_t *client, snd_seq_event_t *event,
+ int err, int atomic, int hop)
+{
+ snd_seq_event_t bounce_ev;
+ int result;
+
+ if (client == NULL ||
+ ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) ||
+ ! client->accept_input)
+ return 0; /* ignored */
+
+ /* set up quoted error */
+ memset(&bounce_ev, 0, sizeof(bounce_ev));
+ bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR;
+ bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
+ bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+ bounce_ev.dest.client = client->number;
+ bounce_ev.dest.port = event->source.port;
+ bounce_ev.data.quote.origin = event->dest;
+ bounce_ev.data.quote.event = event;
+ bounce_ev.data.quote.value = -err; /* use positive value */
+ result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1);
+ if (result < 0) {
+ client->event_lost++;
+ return result;
+ }
+
+ return result;
+}
+
+
+/*
+ * rewrite the time-stamp of the event record with the curren time
+ * of the given queue.
+ * return non-zero if updated.
+ */
+static int update_timestamp_of_queue(snd_seq_event_t *event, int queue, int real_time)
+{
+ queue_t *q;
+
+ q = queueptr(queue);
+ if (! q)
+ return 0;
+ event->queue = queue;
+ event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK;
+ if (real_time) {
+ event->time.time = snd_seq_timer_get_cur_time(q->timer);
+ event->flags |= SNDRV_SEQ_TIME_STAMP_REAL;
+ } else {
+ event->time.tick = snd_seq_timer_get_cur_tick(q->timer);
+ event->flags |= SNDRV_SEQ_TIME_STAMP_TICK;
+ }
+ queuefree(q);
+ return 1;
+}
+
+
+/*
+ * deliver an event to the specified destination.
+ * if filter is non-zero, client filter bitmap is tested.
+ *
+ * RETURN VALUE: 0 : if succeeded
+ * <0 : error
+ */
+static int snd_seq_deliver_single_event(client_t *client,
+ snd_seq_event_t *event,
+ int filter, int atomic, int hop)
+{
+ client_t *dest = NULL;
+ client_port_t *dest_port = NULL;
+ int result = -ENOENT;
+ int direct;
+
+ direct = snd_seq_ev_is_direct(event);
+
+ dest = get_event_dest_client(event, filter);
+ if (dest == NULL)
+ goto __skip;
+ dest_port = snd_seq_port_use_ptr(dest, event->dest.port);
+ if (dest_port == NULL)
+ goto __skip;
+
+ /* check permission */
+ if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) {
+ result = -EPERM;
+ goto __skip;
+ }
+
+ if (dest_port->timestamping)
+ update_timestamp_of_queue(event, dest_port->time_queue,
+ dest_port->time_real);
+
+ switch (dest->type) {
+ case USER_CLIENT:
+ if (dest->data.user.fifo)
+ result = snd_seq_fifo_event_in(dest->data.user.fifo, event);
+ break;
+
+ case KERNEL_CLIENT:
+ if (dest_port->event_input == NULL)
+ break;
+ result = dest_port->event_input(event, direct, dest_port->private_data, atomic, hop);
+ break;
+ default:
+ break;
+ }
+
+ __skip:
+ if (dest_port)
+ snd_seq_port_unlock(dest_port);
+ if (dest)
+ snd_seq_client_unlock(dest);
+
+ if (result < 0 && !direct) {
+ result = bounce_error_event(client, event, result, atomic, hop);
+ }
+ return result;
+}
+
+
+/*
+ * send the event to all subscribers:
+ */
+static int deliver_to_subscribers(client_t *client,
+ snd_seq_event_t *event,
+ int atomic, int hop)
+{
+ subscribers_t *subs;
+ int err = 0, num_ev = 0;
+ snd_seq_event_t event_saved;
+ client_port_t *src_port;
+ struct list_head *p;
+ port_subs_info_t *grp;
+
+ src_port = snd_seq_port_use_ptr(client, event->source.port);
+ if (src_port == NULL)
+ return -EINVAL; /* invalid source port */
+ /* save original event record */
+ event_saved = *event;
+ grp = &src_port->c_src;
+
+ /* lock list */
+ if (atomic)
+ read_lock(&grp->list_lock);
+ else
+ down_read(&grp->list_mutex);
+ list_for_each(p, &grp->list_head) {
+ subs = list_entry(p, subscribers_t, src_list);
+ event->dest = subs->info.dest;
+ if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
+ /* convert time according to flag with subscription */
+ update_timestamp_of_queue(event, subs->info.queue,
+ subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
+ err = snd_seq_deliver_single_event(client, event,
+ 0, atomic, hop);
+ if (err < 0)
+ break;
+ num_ev++;
+ /* restore original event record */
+ *event = event_saved;
+ }
+ if (atomic)
+ read_unlock(&grp->list_lock);
+ else
+ up_read(&grp->list_mutex);
+ *event = event_saved; /* restore */
+ snd_seq_port_unlock(src_port);
+ return (err < 0) ? err : num_ev;
+}
+
+
+#ifdef SUPPORT_BROADCAST
+/*
+ * broadcast to all ports:
+ */
+static int port_broadcast_event(client_t *client,
+ snd_seq_event_t *event,
+ int atomic, int hop)
+{
+ int num_ev = 0, err = 0;
+ client_t *dest_client;
+ struct list_head *p;
+
+ dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST);
+ if (dest_client == NULL)
+ return 0; /* no matching destination */
+
+ read_lock(&dest_client->ports_lock);
+ list_for_each(p, &dest_client->ports_list_head) {
+ client_port_t *port = list_entry(p, client_port_t, list);
+ event->dest.port = port->addr.port;
+ /* pass NULL as source client to avoid error bounce */
+ err = snd_seq_deliver_single_event(NULL, event,
+ SNDRV_SEQ_FILTER_BROADCAST,
+ atomic, hop);
+ if (err < 0)
+ break;
+ num_ev++;
+ }
+ read_unlock(&dest_client->ports_lock);
+ snd_seq_client_unlock(dest_client);
+ event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */
+ return (err < 0) ? err : num_ev;
+}
+
+/*
+ * send the event to all clients:
+ * if destination port is also ADDRESS_BROADCAST, deliver to all ports.
+ */
+static int broadcast_event(client_t *client,
+ snd_seq_event_t *event, int atomic, int hop)
+{
+ int err = 0, num_ev = 0;
+ int dest;
+ snd_seq_addr_t addr;
+
+ addr = event->dest; /* save */
+
+ for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) {
+ /* don't send to itself */
+ if (dest == client->number)
+ continue;
+ event->dest.client = dest;
+ event->dest.port = addr.port;
+ if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST)
+ err = port_broadcast_event(client, event, atomic, hop);
+ else
+ /* pass NULL as source client to avoid error bounce */
+ err = snd_seq_deliver_single_event(NULL, event,
+ SNDRV_SEQ_FILTER_BROADCAST,
+ atomic, hop);
+ if (err < 0)
+ break;
+ num_ev += err;
+ }
+ event->dest = addr; /* restore */
+ return (err < 0) ? err : num_ev;
+}
+
+
+/* multicast - not supported yet */
+static int multicast_event(client_t *client, snd_seq_event_t *event,
+ int atomic, int hop)
+{
+ snd_printd("seq: multicast not supported yet.\n");
+ return 0; /* ignored */
+}
+#endif /* SUPPORT_BROADCAST */
+
+
+/* deliver an event to the destination port(s).
+ * if the event is to subscribers or broadcast, the event is dispatched
+ * to multiple targets.
+ *
+ * RETURN VALUE: n > 0 : the number of delivered events.
+ * n == 0 : the event was not passed to any client.
+ * n < 0 : error - event was not processed.
+ */
+static int snd_seq_deliver_event(client_t *client, snd_seq_event_t *event,
+ int atomic, int hop)
+{
+ int result;
+
+ hop++;
+ if (hop >= SNDRV_SEQ_MAX_HOPS) {
+ snd_printd("too long delivery path (%d:%d->%d:%d)\n",
+ event->source.client, event->source.port,
+ event->dest.client, event->dest.port);
+ return -EMLINK;
+ }
+
+ if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
+ event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
+ result = deliver_to_subscribers(client, event, atomic, hop);
+#ifdef SUPPORT_BROADCAST
+ else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST ||
+ event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST)
+ result = broadcast_event(client, event, atomic, hop);
+ else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS)
+ result = multicast_event(client, event, atomic, hop);
+ else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST)
+ result = port_broadcast_event(client, event, atomic, hop);
+#endif
+ else
+ result = snd_seq_deliver_single_event(client, event, 0, atomic, hop);
+
+ return result;
+}
+
+/*
+ * dispatch an event cell:
+ * This function is called only from queue check routines in timer
+ * interrupts or after enqueued.
+ * The event cell shall be released or re-queued in this function.
+ *
+ * RETURN VALUE: n > 0 : the number of delivered events.
+ * n == 0 : the event was not passed to any client.
+ * n < 0 : error - event was not processed.
+ */
+int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop)
+{
+ client_t *client;
+ int result;
+
+ snd_assert(cell != NULL, return -EINVAL);
+
+ client = snd_seq_client_use_ptr(cell->event.source.client);
+ if (client == NULL) {
+ snd_seq_cell_free(cell); /* release this cell */
+ return -EINVAL;
+ }
+
+ if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
+ /* NOTE event:
+ * the event cell is re-used as a NOTE-OFF event and
+ * enqueued again.
+ */
+ snd_seq_event_t tmpev, *ev;
+
+ /* reserve this event to enqueue note-off later */
+ tmpev = cell->event;
+ tmpev.type = SNDRV_SEQ_EVENT_NOTEON;
+ result = snd_seq_deliver_event(client, &tmpev, atomic, hop);
+
+ /*
+ * This was originally a note event. We now re-use the
+ * cell for the note-off event.
+ */
+
+ ev = &cell->event;
+ ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
+ ev->flags |= SNDRV_SEQ_PRIORITY_HIGH;
+
+ /* add the duration time */
+ switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+ case SNDRV_SEQ_TIME_STAMP_TICK:
+ ev->time.tick += ev->data.note.duration;
+ break;
+ case SNDRV_SEQ_TIME_STAMP_REAL:
+ /* unit for duration is ms */
+ ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000);
+ ev->time.time.tv_sec += ev->data.note.duration / 1000 +
+ ev->time.time.tv_nsec / 1000000000;
+ ev->time.time.tv_nsec %= 1000000000;
+ break;
+ }
+ ev->data.note.velocity = ev->data.note.off_velocity;
+
+ /* Now queue this cell as the note off event */
+ if (snd_seq_enqueue_event(cell, atomic, hop) < 0)
+ snd_seq_cell_free(cell); /* release this cell */
+
+ } else {
+ /* Normal events:
+ * event cell is freed after processing the event
+ */
+
+ result = snd_seq_deliver_event(client, &cell->event, atomic, hop);
+ snd_seq_cell_free(cell);
+ }
+
+ snd_seq_client_unlock(client);
+ return result;
+}
+
+
+/* Allocate a cell from client pool and enqueue it to queue:
+ * if pool is empty and blocking is TRUE, sleep until a new cell is
+ * available.
+ */
+static int snd_seq_client_enqueue_event(client_t *client,
+ snd_seq_event_t *event,
+ struct file *file, int blocking,
+ int atomic, int hop)
+{
+ snd_seq_event_cell_t *cell;
+ int err;
+
+ /* special queue values - force direct passing */
+ if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
+ event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ event->queue = SNDRV_SEQ_QUEUE_DIRECT;
+ } else
+#ifdef SUPPORT_BROADCAST
+ if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) {
+ event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST;
+ event->queue = SNDRV_SEQ_QUEUE_DIRECT;
+ }
+#endif
+ if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
+ /* check presence of source port */
+ client_port_t *src_port = snd_seq_port_use_ptr(client, event->source.port);
+ if (src_port == NULL)
+ return -EINVAL;
+ snd_seq_port_unlock(src_port);
+ }
+
+ /* direct event processing without enqueued */
+ if (snd_seq_ev_is_direct(event)) {
+ if (event->type == SNDRV_SEQ_EVENT_NOTE)
+ return -EINVAL; /* this event must be enqueued! */
+ return snd_seq_deliver_event(client, event, atomic, hop);
+ }
+
+ /* Not direct, normal queuing */
+ if (snd_seq_queue_is_used(event->queue, client->number) <= 0)
+ return -EINVAL; /* invalid queue */
+ if (! snd_seq_write_pool_allocated(client))
+ return -ENXIO; /* queue is not allocated */
+
+ /* allocate an event cell */
+ err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, file);
+ if (err < 0)
+ return err;
+
+ /* we got a cell. enqueue it. */
+ if ((err = snd_seq_enqueue_event(cell, atomic, hop)) < 0) {
+ snd_seq_cell_free(cell);
+ return err;
+ }
+
+ return 0;
+}
+
+
+/*
+ * check validity of event type and data length.
+ * return non-zero if invalid.
+ */
+static int check_event_type_and_length(snd_seq_event_t *ev)
+{
+ switch (snd_seq_ev_length_type(ev)) {
+ case SNDRV_SEQ_EVENT_LENGTH_FIXED:
+ if (snd_seq_ev_is_variable_type(ev))
+ return -EINVAL;
+ break;
+ case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
+ if (! snd_seq_ev_is_variable_type(ev) ||
+ (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN)
+ return -EINVAL;
+ break;
+ case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
+ if (! snd_seq_ev_is_instr_type(ev) ||
+ ! snd_seq_ev_is_direct(ev))
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+
+/* handle write() */
+/* possible error values:
+ * -ENXIO invalid client or file open mode
+ * -ENOMEM malloc failed
+ * -EFAULT seg. fault during copy from user space
+ * -EINVAL invalid event
+ * -EAGAIN no space in output pool
+ * -EINTR interrupts while sleep
+ * -EMLINK too many hops
+ * others depends on return value from driver callback
+ */
+static ssize_t snd_seq_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+ client_t *client = (client_t *) file->private_data;
+ int written = 0, len;
+ int err = -EINVAL;
+ snd_seq_event_t event;
+
+ if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
+ return -ENXIO;
+
+ /* check client structures are in place */
+ snd_assert(client != NULL, return -ENXIO);
+
+ if (!client->accept_output || client->pool == NULL)
+ return -ENXIO;
+
+ /* allocate the pool now if the pool is not allocated yet */
+ if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
+ if (snd_seq_pool_init(client->pool) < 0)
+ return -ENOMEM;
+ }
+
+ /* only process whole events */
+ while (count >= sizeof(snd_seq_event_t)) {
+ /* Read in the event header from the user */
+ len = sizeof(event);
+ if (copy_from_user(&event, buf, len)) {
+ err = -EFAULT;
+ break;
+ }
+ event.source.client = client->number; /* fill in client number */
+ /* Check for extension data length */
+ if (check_event_type_and_length(&event)) {
+ err = -EINVAL;
+ break;
+ }
+
+ /* check for special events */
+ if (event.type == SNDRV_SEQ_EVENT_NONE)
+ goto __skip_event;
+ else if (snd_seq_ev_is_reserved(&event)) {
+ err = -EINVAL;
+ break;
+ }
+
+ if (snd_seq_ev_is_variable(&event)) {
+ int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+ if ((size_t)(extlen + len) > count) {
+ /* back out, will get an error this time or next */
+ err = -EINVAL;
+ break;
+ }
+ /* set user space pointer */
+ event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
+ event.data.ext.ptr = (char*)buf + sizeof(snd_seq_event_t);
+ len += extlen; /* increment data length */
+ } else {
+#ifdef CONFIG_COMPAT
+ if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
+ void *ptr = compat_ptr(event.data.raw32.d[1]);
+ event.data.ext.ptr = ptr;
+ }
+#endif
+ }
+
+ /* ok, enqueue it */
+ err = snd_seq_client_enqueue_event(client, &event, file,
+ !(file->f_flags & O_NONBLOCK),
+ 0, 0);
+ if (err < 0)
+ break;
+
+ __skip_event:
+ /* Update pointers and counts */
+ count -= len;
+ buf += len;
+ written += len;
+ }
+
+ return written ? written : err;
+}
+
+
+/*
+ * handle polling
+ */
+static unsigned int snd_seq_poll(struct file *file, poll_table * wait)
+{
+ client_t *client = (client_t *) file->private_data;
+ unsigned int mask = 0;
+
+ /* check client structures are in place */
+ snd_assert(client != NULL, return -ENXIO);
+
+ if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) &&
+ client->data.user.fifo) {
+
+ /* check if data is available in the outqueue */
+ if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait))
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) {
+
+ /* check if data is available in the pool */
+ if (!snd_seq_write_pool_allocated(client) ||
+ snd_seq_pool_poll_wait(client->pool, file, wait))
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+ return mask;
+}
+
+
+/*-----------------------------------------------------*/
+
+
+/* SYSTEM_INFO ioctl() */
+static int snd_seq_ioctl_system_info(client_t *client, void __user *arg)
+{
+ snd_seq_system_info_t info;
+
+ memset(&info, 0, sizeof(info));
+ /* fill the info fields */
+ info.queues = SNDRV_SEQ_MAX_QUEUES;
+ info.clients = SNDRV_SEQ_MAX_CLIENTS;
+ info.ports = 256; /* fixed limit */
+ info.channels = 256; /* fixed limit */
+ info.cur_clients = client_usage.cur;
+ info.cur_queues = snd_seq_queue_get_cur_queues();
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* RUNNING_MODE ioctl() */
+static int snd_seq_ioctl_running_mode(client_t *client, void __user *arg)
+{
+ struct sndrv_seq_running_info info;
+ client_t *cptr;
+ int err = 0;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ /* requested client number */
+ cptr = snd_seq_client_use_ptr(info.client);
+ if (cptr == NULL)
+ return -ENOENT; /* don't change !!! */
+
+#ifdef SNDRV_BIG_ENDIAN
+ if (! info.big_endian) {
+ err = -EINVAL;
+ goto __err;
+ }
+#else
+ if (info.big_endian) {
+ err = -EINVAL;
+ goto __err;
+ }
+
+#endif
+ if (info.cpu_mode > sizeof(long)) {
+ err = -EINVAL;
+ goto __err;
+ }
+ cptr->convert32 = (info.cpu_mode < sizeof(long));
+ __err:
+ snd_seq_client_unlock(cptr);
+ return err;
+}
+
+/* CLIENT_INFO ioctl() */
+static void get_client_info(client_t *cptr, snd_seq_client_info_t *info)
+{
+ info->client = cptr->number;
+
+ /* fill the info fields */
+ info->type = cptr->type;
+ strcpy(info->name, cptr->name);
+ info->filter = cptr->filter;
+ info->event_lost = cptr->event_lost;
+ memcpy(info->event_filter, cptr->event_filter, 32);
+ info->num_ports = cptr->num_ports;
+ memset(info->reserved, 0, sizeof(info->reserved));
+}
+
+static int snd_seq_ioctl_get_client_info(client_t * client, void __user *arg)
+{
+ client_t *cptr;
+ snd_seq_client_info_t client_info;
+
+ if (copy_from_user(&client_info, arg, sizeof(client_info)))
+ return -EFAULT;
+
+ /* requested client number */
+ cptr = snd_seq_client_use_ptr(client_info.client);
+ if (cptr == NULL)
+ return -ENOENT; /* don't change !!! */
+
+ get_client_info(cptr, &client_info);
+ snd_seq_client_unlock(cptr);
+
+ if (copy_to_user(arg, &client_info, sizeof(client_info)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* CLIENT_INFO ioctl() */
+static int snd_seq_ioctl_set_client_info(client_t * client, void __user *arg)
+{
+ snd_seq_client_info_t client_info;
+
+ if (copy_from_user(&client_info, arg, sizeof(client_info)))
+ return -EFAULT;
+
+ /* it is not allowed to set the info fields for an another client */
+ if (client->number != client_info.client)
+ return -EPERM;
+ /* also client type must be set now */
+ if (client->type != client_info.type)
+ return -EINVAL;
+
+ /* fill the info fields */
+ if (client_info.name[0])
+ strlcpy(client->name, client_info.name, sizeof(client->name));
+
+ client->filter = client_info.filter;
+ client->event_lost = client_info.event_lost;
+ memcpy(client->event_filter, client_info.event_filter, 32);
+
+ return 0;
+}
+
+
+/*
+ * CREATE PORT ioctl()
+ */
+static int snd_seq_ioctl_create_port(client_t * client, void __user *arg)
+{
+ client_port_t *port;
+ snd_seq_port_info_t info;
+ snd_seq_port_callback_t *callback;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ /* it is not allowed to create the port for an another client */
+ if (info.addr.client != client->number)
+ return -EPERM;
+
+ port = snd_seq_create_port(client, (info.flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info.addr.port : -1);
+ if (port == NULL)
+ return -ENOMEM;
+
+ if (client->type == USER_CLIENT && info.kernel) {
+ snd_seq_delete_port(client, port->addr.port);
+ return -EINVAL;
+ }
+ if (client->type == KERNEL_CLIENT) {
+ if ((callback = info.kernel) != NULL) {
+ if (callback->owner)
+ port->owner = callback->owner;
+ port->private_data = callback->private_data;
+ port->private_free = callback->private_free;
+ port->callback_all = callback->callback_all;
+ port->event_input = callback->event_input;
+ port->c_src.open = callback->subscribe;
+ port->c_src.close = callback->unsubscribe;
+ port->c_dest.open = callback->use;
+ port->c_dest.close = callback->unuse;
+ }
+ }
+
+ info.addr = port->addr;
+
+ snd_seq_set_port_info(port, &info);
+ snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * DELETE PORT ioctl()
+ */
+static int snd_seq_ioctl_delete_port(client_t * client, void __user *arg)
+{
+ snd_seq_port_info_t info;
+ int err;
+
+ /* set passed parameters */
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ /* it is not allowed to remove the port for an another client */
+ if (info.addr.client != client->number)
+ return -EPERM;
+
+ err = snd_seq_delete_port(client, info.addr.port);
+ if (err >= 0)
+ snd_seq_system_client_ev_port_exit(client->number, info.addr.port);
+ return err;
+}
+
+
+/*
+ * GET_PORT_INFO ioctl() (on any client)
+ */
+static int snd_seq_ioctl_get_port_info(client_t *client, void __user *arg)
+{
+ client_t *cptr;
+ client_port_t *port;
+ snd_seq_port_info_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+ cptr = snd_seq_client_use_ptr(info.addr.client);
+ if (cptr == NULL)
+ return -ENXIO;
+
+ port = snd_seq_port_use_ptr(cptr, info.addr.port);
+ if (port == NULL) {
+ snd_seq_client_unlock(cptr);
+ return -ENOENT; /* don't change */
+ }
+
+ /* get port info */
+ snd_seq_get_port_info(port, &info);
+ snd_seq_port_unlock(port);
+ snd_seq_client_unlock(cptr);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/*
+ * SET_PORT_INFO ioctl() (only ports on this/own client)
+ */
+static int snd_seq_ioctl_set_port_info(client_t * client, void __user *arg)
+{
+ client_port_t *port;
+ snd_seq_port_info_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ if (info.addr.client != client->number) /* only set our own ports ! */
+ return -EPERM;
+ port = snd_seq_port_use_ptr(client, info.addr.port);
+ if (port) {
+ snd_seq_set_port_info(port, &info);
+ snd_seq_port_unlock(port);
+ }
+ return 0;
+}
+
+
+/*
+ * port subscription (connection)
+ */
+#define PERM_RD (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
+#define PERM_WR (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
+
+static int check_subscription_permission(client_t *client, client_port_t *sport,
+ client_port_t *dport,
+ snd_seq_port_subscribe_t *subs)
+{
+ if (client->number != subs->sender.client &&
+ client->number != subs->dest.client) {
+ /* connection by third client - check export permission */
+ if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
+ return -EPERM;
+ if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
+ return -EPERM;
+ }
+
+ /* check read permission */
+ /* if sender or receiver is the subscribing client itself,
+ * no permission check is necessary
+ */
+ if (client->number != subs->sender.client) {
+ if (! check_port_perm(sport, PERM_RD))
+ return -EPERM;
+ }
+ /* check write permission */
+ if (client->number != subs->dest.client) {
+ if (! check_port_perm(dport, PERM_WR))
+ return -EPERM;
+ }
+ return 0;
+}
+
+/*
+ * send an subscription notify event to user client:
+ * client must be user client.
+ */
+int snd_seq_client_notify_subscription(int client, int port,
+ snd_seq_port_subscribe_t *info, int evtype)
+{
+ snd_seq_event_t event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = evtype;
+ event.data.connect.dest = info->dest;
+ event.data.connect.sender = info->sender;
+
+ return snd_seq_system_notify(client, port, &event); /* non-atomic */
+}
+
+
+/*
+ * add to port's subscription list IOCTL interface
+ */
+static int snd_seq_ioctl_subscribe_port(client_t * client, void __user *arg)
+{
+ int result = -EINVAL;
+ client_t *receiver = NULL, *sender = NULL;
+ client_port_t *sport = NULL, *dport = NULL;
+ snd_seq_port_subscribe_t subs;
+
+ if (copy_from_user(&subs, arg, sizeof(subs)))
+ return -EFAULT;
+
+ if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL)
+ goto __end;
+ if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+ goto __end;
+ if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+ goto __end;
+ if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL)
+ goto __end;
+
+ result = check_subscription_permission(client, sport, dport, &subs);
+ if (result < 0)
+ goto __end;
+
+ /* connect them */
+ result = snd_seq_port_connect(client, sender, sport, receiver, dport, &subs);
+ if (! result) /* broadcast announce */
+ snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
+ &subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
+ __end:
+ if (sport)
+ snd_seq_port_unlock(sport);
+ if (dport)
+ snd_seq_port_unlock(dport);
+ if (sender)
+ snd_seq_client_unlock(sender);
+ if (receiver)
+ snd_seq_client_unlock(receiver);
+ return result;
+}
+
+
+/*
+ * remove from port's subscription list
+ */
+static int snd_seq_ioctl_unsubscribe_port(client_t * client, void __user *arg)
+{
+ int result = -ENXIO;
+ client_t *receiver = NULL, *sender = NULL;
+ client_port_t *sport = NULL, *dport = NULL;
+ snd_seq_port_subscribe_t subs;
+
+ if (copy_from_user(&subs, arg, sizeof(subs)))
+ return -EFAULT;
+
+ if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL)
+ goto __end;
+ if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+ goto __end;
+ if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+ goto __end;
+ if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL)
+ goto __end;
+
+ result = check_subscription_permission(client, sport, dport, &subs);
+ if (result < 0)
+ goto __end;
+
+ result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, &subs);
+ if (! result) /* broadcast announce */
+ snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
+ &subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
+ __end:
+ if (sport)
+ snd_seq_port_unlock(sport);
+ if (dport)
+ snd_seq_port_unlock(dport);
+ if (sender)
+ snd_seq_client_unlock(sender);
+ if (receiver)
+ snd_seq_client_unlock(receiver);
+ return result;
+}
+
+
+/* CREATE_QUEUE ioctl() */
+static int snd_seq_ioctl_create_queue(client_t *client, void __user *arg)
+{
+ snd_seq_queue_info_t info;
+ int result;
+ queue_t *q;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ result = snd_seq_queue_alloc(client->number, info.locked, info.flags);
+ if (result < 0)
+ return result;
+
+ q = queueptr(result);
+ if (q == NULL)
+ return -EINVAL;
+
+ info.queue = q->queue;
+ info.locked = q->locked;
+ info.owner = q->owner;
+
+ /* set queue name */
+ if (! info.name[0])
+ snprintf(info.name, sizeof(info.name), "Queue-%d", q->queue);
+ strlcpy(q->name, info.name, sizeof(q->name));
+ queuefree(q);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* DELETE_QUEUE ioctl() */
+static int snd_seq_ioctl_delete_queue(client_t *client, void __user *arg)
+{
+ snd_seq_queue_info_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ return snd_seq_queue_delete(client->number, info.queue);
+}
+
+/* GET_QUEUE_INFO ioctl() */
+static int snd_seq_ioctl_get_queue_info(client_t *client, void __user *arg)
+{
+ snd_seq_queue_info_t info;
+ queue_t *q;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ q = queueptr(info.queue);
+ if (q == NULL)
+ return -EINVAL;
+
+ memset(&info, 0, sizeof(info));
+ info.queue = q->queue;
+ info.owner = q->owner;
+ info.locked = q->locked;
+ strlcpy(info.name, q->name, sizeof(info.name));
+ queuefree(q);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* SET_QUEUE_INFO ioctl() */
+static int snd_seq_ioctl_set_queue_info(client_t *client, void __user *arg)
+{
+ snd_seq_queue_info_t info;
+ queue_t *q;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ if (info.owner != client->number)
+ return -EINVAL;
+
+ /* change owner/locked permission */
+ if (snd_seq_queue_check_access(info.queue, client->number)) {
+ if (snd_seq_queue_set_owner(info.queue, client->number, info.locked) < 0)
+ return -EPERM;
+ if (info.locked)
+ snd_seq_queue_use(info.queue, client->number, 1);
+ } else {
+ return -EPERM;
+ }
+
+ q = queueptr(info.queue);
+ if (! q)
+ return -EINVAL;
+ if (q->owner != client->number) {
+ queuefree(q);
+ return -EPERM;
+ }
+ strlcpy(q->name, info.name, sizeof(q->name));
+ queuefree(q);
+
+ return 0;
+}
+
+/* GET_NAMED_QUEUE ioctl() */
+static int snd_seq_ioctl_get_named_queue(client_t *client, void __user *arg)
+{
+ snd_seq_queue_info_t info;
+ queue_t *q;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ q = snd_seq_queue_find_name(info.name);
+ if (q == NULL)
+ return -EINVAL;
+ info.queue = q->queue;
+ info.owner = q->owner;
+ info.locked = q->locked;
+ queuefree(q);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* GET_QUEUE_STATUS ioctl() */
+static int snd_seq_ioctl_get_queue_status(client_t * client, void __user *arg)
+{
+ snd_seq_queue_status_t status;
+ queue_t *queue;
+ seq_timer_t *tmr;
+
+ if (copy_from_user(&status, arg, sizeof(status)))
+ return -EFAULT;
+
+ queue = queueptr(status.queue);
+ if (queue == NULL)
+ return -EINVAL;
+ memset(&status, 0, sizeof(status));
+ status.queue = queue->queue;
+
+ tmr = queue->timer;
+ status.events = queue->tickq->cells + queue->timeq->cells;
+
+ status.time = snd_seq_timer_get_cur_time(tmr);
+ status.tick = snd_seq_timer_get_cur_tick(tmr);
+
+ status.running = tmr->running;
+
+ status.flags = queue->flags;
+ queuefree(queue);
+
+ if (copy_to_user(arg, &status, sizeof(status)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* GET_QUEUE_TEMPO ioctl() */
+static int snd_seq_ioctl_get_queue_tempo(client_t * client, void __user *arg)
+{
+ snd_seq_queue_tempo_t tempo;
+ queue_t *queue;
+ seq_timer_t *tmr;
+
+ if (copy_from_user(&tempo, arg, sizeof(tempo)))
+ return -EFAULT;
+
+ queue = queueptr(tempo.queue);
+ if (queue == NULL)
+ return -EINVAL;
+ memset(&tempo, 0, sizeof(tempo));
+ tempo.queue = queue->queue;
+
+ tmr = queue->timer;
+
+ tempo.tempo = tmr->tempo;
+ tempo.ppq = tmr->ppq;
+ tempo.skew_value = tmr->skew;
+ tempo.skew_base = tmr->skew_base;
+ queuefree(queue);
+
+ if (copy_to_user(arg, &tempo, sizeof(tempo)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* SET_QUEUE_TEMPO ioctl() */
+int snd_seq_set_queue_tempo(int client, snd_seq_queue_tempo_t *tempo)
+{
+ if (!snd_seq_queue_check_access(tempo->queue, client))
+ return -EPERM;
+ return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo);
+}
+
+static int snd_seq_ioctl_set_queue_tempo(client_t * client, void __user *arg)
+{
+ int result;
+ snd_seq_queue_tempo_t tempo;
+
+ if (copy_from_user(&tempo, arg, sizeof(tempo)))
+ return -EFAULT;
+
+ result = snd_seq_set_queue_tempo(client->number, &tempo);
+ return result < 0 ? result : 0;
+}
+
+
+/* GET_QUEUE_TIMER ioctl() */
+static int snd_seq_ioctl_get_queue_timer(client_t * client, void __user *arg)
+{
+ snd_seq_queue_timer_t timer;
+ queue_t *queue;
+ seq_timer_t *tmr;
+
+ if (copy_from_user(&timer, arg, sizeof(timer)))
+ return -EFAULT;
+
+ queue = queueptr(timer.queue);
+ if (queue == NULL)
+ return -EINVAL;
+
+ if (down_interruptible(&queue->timer_mutex)) {
+ queuefree(queue);
+ return -ERESTARTSYS;
+ }
+ tmr = queue->timer;
+ memset(&timer, 0, sizeof(timer));
+ timer.queue = queue->queue;
+
+ timer.type = tmr->type;
+ if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
+ timer.u.alsa.id = tmr->alsa_id;
+ timer.u.alsa.resolution = tmr->preferred_resolution;
+ }
+ up(&queue->timer_mutex);
+ queuefree(queue);
+
+ if (copy_to_user(arg, &timer, sizeof(timer)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* SET_QUEUE_TIMER ioctl() */
+static int snd_seq_ioctl_set_queue_timer(client_t * client, void __user *arg)
+{
+ int result = 0;
+ snd_seq_queue_timer_t timer;
+
+ if (copy_from_user(&timer, arg, sizeof(timer)))
+ return -EFAULT;
+
+ if (timer.type != SNDRV_SEQ_TIMER_ALSA)
+ return -EINVAL;
+
+ if (snd_seq_queue_check_access(timer.queue, client->number)) {
+ queue_t *q;
+ seq_timer_t *tmr;
+
+ q = queueptr(timer.queue);
+ if (q == NULL)
+ return -ENXIO;
+ if (down_interruptible(&q->timer_mutex)) {
+ queuefree(q);
+ return -ERESTARTSYS;
+ }
+ tmr = q->timer;
+ snd_seq_queue_timer_close(timer.queue);
+ tmr->type = timer.type;
+ if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
+ tmr->alsa_id = timer.u.alsa.id;
+ tmr->preferred_resolution = timer.u.alsa.resolution;
+ }
+ result = snd_seq_queue_timer_open(timer.queue);
+ up(&q->timer_mutex);
+ queuefree(q);
+ } else {
+ return -EPERM;
+ }
+
+ return result;
+}
+
+
+/* GET_QUEUE_CLIENT ioctl() */
+static int snd_seq_ioctl_get_queue_client(client_t * client, void __user *arg)
+{
+ snd_seq_queue_client_t info;
+ int used;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ used = snd_seq_queue_is_used(info.queue, client->number);
+ if (used < 0)
+ return -EINVAL;
+ info.used = used;
+ info.client = client->number;
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+
+/* SET_QUEUE_CLIENT ioctl() */
+static int snd_seq_ioctl_set_queue_client(client_t * client, void __user *arg)
+{
+ int err;
+ snd_seq_queue_client_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ if (info.used >= 0) {
+ err = snd_seq_queue_use(info.queue, client->number, info.used);
+ if (err < 0)
+ return err;
+ }
+
+ return snd_seq_ioctl_get_queue_client(client, arg);
+}
+
+
+/* GET_CLIENT_POOL ioctl() */
+static int snd_seq_ioctl_get_client_pool(client_t * client, void __user *arg)
+{
+ snd_seq_client_pool_t info;
+ client_t *cptr;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ cptr = snd_seq_client_use_ptr(info.client);
+ if (cptr == NULL)
+ return -ENOENT;
+ memset(&info, 0, sizeof(info));
+ info.output_pool = cptr->pool->size;
+ info.output_room = cptr->pool->room;
+ info.output_free = info.output_pool;
+ if (cptr->pool)
+ info.output_free = snd_seq_unused_cells(cptr->pool);
+ if (cptr->type == USER_CLIENT) {
+ info.input_pool = cptr->data.user.fifo_pool_size;
+ info.input_free = info.input_pool;
+ if (cptr->data.user.fifo)
+ info.input_free = snd_seq_unused_cells(cptr->data.user.fifo->pool);
+ } else {
+ info.input_pool = 0;
+ info.input_free = 0;
+ }
+ snd_seq_client_unlock(cptr);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+/* SET_CLIENT_POOL ioctl() */
+static int snd_seq_ioctl_set_client_pool(client_t * client, void __user *arg)
+{
+ snd_seq_client_pool_t info;
+ int rc;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ if (client->number != info.client)
+ return -EINVAL; /* can't change other clients */
+
+ if (info.output_pool >= 1 && info.output_pool <= SNDRV_SEQ_MAX_EVENTS &&
+ (! snd_seq_write_pool_allocated(client) ||
+ info.output_pool != client->pool->size)) {
+ if (snd_seq_write_pool_allocated(client)) {
+ /* remove all existing cells */
+ snd_seq_queue_client_leave_cells(client->number);
+ snd_seq_pool_done(client->pool);
+ }
+ client->pool->size = info.output_pool;
+ rc = snd_seq_pool_init(client->pool);
+ if (rc < 0)
+ return rc;
+ }
+ if (client->type == USER_CLIENT && client->data.user.fifo != NULL &&
+ info.input_pool >= 1 &&
+ info.input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS &&
+ info.input_pool != client->data.user.fifo_pool_size) {
+ /* change pool size */
+ rc = snd_seq_fifo_resize(client->data.user.fifo, info.input_pool);
+ if (rc < 0)
+ return rc;
+ client->data.user.fifo_pool_size = info.input_pool;
+ }
+ if (info.output_room >= 1 &&
+ info.output_room <= client->pool->size) {
+ client->pool->room = info.output_room;
+ }
+
+ return snd_seq_ioctl_get_client_pool(client, arg);
+}
+
+
+/* REMOVE_EVENTS ioctl() */
+static int snd_seq_ioctl_remove_events(client_t * client, void __user *arg)
+{
+ snd_seq_remove_events_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ /*
+ * Input mostly not implemented XXX.
+ */
+ if (info.remove_mode & SNDRV_SEQ_REMOVE_INPUT) {
+ /*
+ * No restrictions so for a user client we can clear
+ * the whole fifo
+ */
+ if (client->type == USER_CLIENT)
+ snd_seq_fifo_clear(client->data.user.fifo);
+ }
+
+ if (info.remove_mode & SNDRV_SEQ_REMOVE_OUTPUT)
+ snd_seq_queue_remove_cells(client->number, &info);
+
+ return 0;
+}
+
+
+/*
+ * get subscription info
+ */
+static int snd_seq_ioctl_get_subscription(client_t *client, void __user *arg)
+{
+ int result;
+ client_t *sender = NULL;
+ client_port_t *sport = NULL;
+ snd_seq_port_subscribe_t subs;
+ subscribers_t *p;
+
+ if (copy_from_user(&subs, arg, sizeof(subs)))
+ return -EFAULT;
+
+ result = -EINVAL;
+ if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL)
+ goto __end;
+ if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL)
+ goto __end;
+ p = snd_seq_port_get_subscription(&sport->c_src, &subs.dest);
+ if (p) {
+ result = 0;
+ subs = p->info;
+ } else
+ result = -ENOENT;
+
+ __end:
+ if (sport)
+ snd_seq_port_unlock(sport);
+ if (sender)
+ snd_seq_client_unlock(sender);
+ if (result >= 0) {
+ if (copy_to_user(arg, &subs, sizeof(subs)))
+ return -EFAULT;
+ }
+ return result;
+}
+
+
+/*
+ * get subscription info - check only its presence
+ */
+static int snd_seq_ioctl_query_subs(client_t *client, void __user *arg)
+{
+ int result = -ENXIO;
+ client_t *cptr = NULL;
+ client_port_t *port = NULL;
+ snd_seq_query_subs_t subs;
+ port_subs_info_t *group;
+ struct list_head *p;
+ int i;
+
+ if (copy_from_user(&subs, arg, sizeof(subs)))
+ return -EFAULT;
+
+ if ((cptr = snd_seq_client_use_ptr(subs.root.client)) == NULL)
+ goto __end;
+ if ((port = snd_seq_port_use_ptr(cptr, subs.root.port)) == NULL)
+ goto __end;
+
+ switch (subs.type) {
+ case SNDRV_SEQ_QUERY_SUBS_READ:
+ group = &port->c_src;
+ break;
+ case SNDRV_SEQ_QUERY_SUBS_WRITE:
+ group = &port->c_dest;
+ break;
+ default:
+ goto __end;
+ }
+
+ down_read(&group->list_mutex);
+ /* search for the subscriber */
+ subs.num_subs = group->count;
+ i = 0;
+ result = -ENOENT;
+ list_for_each(p, &group->list_head) {
+ if (i++ == subs.index) {
+ /* found! */
+ subscribers_t *s;
+ if (subs.type == SNDRV_SEQ_QUERY_SUBS_READ) {
+ s = list_entry(p, subscribers_t, src_list);
+ subs.addr = s->info.dest;
+ } else {
+ s = list_entry(p, subscribers_t, dest_list);
+ subs.addr = s->info.sender;
+ }
+ subs.flags = s->info.flags;
+ subs.queue = s->info.queue;
+ result = 0;
+ break;
+ }
+ }
+ up_read(&group->list_mutex);
+
+ __end:
+ if (port)
+ snd_seq_port_unlock(port);
+ if (cptr)
+ snd_seq_client_unlock(cptr);
+ if (result >= 0) {
+ if (copy_to_user(arg, &subs, sizeof(subs)))
+ return -EFAULT;
+ }
+ return result;
+}
+
+
+/*
+ * query next client
+ */
+static int snd_seq_ioctl_query_next_client(client_t *client, void __user *arg)
+{
+ client_t *cptr = NULL;
+ snd_seq_client_info_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ /* search for next client */
+ info.client++;
+ if (info.client < 0)
+ info.client = 0;
+ for (; info.client < SNDRV_SEQ_MAX_CLIENTS; info.client++) {
+ cptr = snd_seq_client_use_ptr(info.client);
+ if (cptr)
+ break; /* found */
+ }
+ if (cptr == NULL)
+ return -ENOENT;
+
+ get_client_info(cptr, &info);
+ snd_seq_client_unlock(cptr);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * query next port
+ */
+static int snd_seq_ioctl_query_next_port(client_t *client, void __user *arg)
+{
+ client_t *cptr;
+ client_port_t *port = NULL;
+ snd_seq_port_info_t info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+ cptr = snd_seq_client_use_ptr(info.addr.client);
+ if (cptr == NULL)
+ return -ENXIO;
+
+ /* search for next port */
+ info.addr.port++;
+ port = snd_seq_port_query_nearest(cptr, &info);
+ if (port == NULL) {
+ snd_seq_client_unlock(cptr);
+ return -ENOENT;
+ }
+
+ /* get port info */
+ info.addr = port->addr;
+ snd_seq_get_port_info(port, &info);
+ snd_seq_port_unlock(port);
+ snd_seq_client_unlock(cptr);
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+/* -------------------------------------------------------- */
+
+static struct seq_ioctl_table {
+ unsigned int cmd;
+ int (*func)(client_t *client, void __user * arg);
+} ioctl_tables[] = {
+ { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info },
+ { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode },
+ { SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info },
+ { SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info },
+ { SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port },
+ { SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port },
+ { SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info },
+ { SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info },
+ { SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port },
+ { SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port },
+ { SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue },
+ { SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue },
+ { SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info },
+ { SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info },
+ { SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue },
+ { SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status },
+ { SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo },
+ { SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo },
+ { SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer },
+ { SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer },
+ { SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client },
+ { SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client },
+ { SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool },
+ { SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool },
+ { SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription },
+ { SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client },
+ { SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port },
+ { SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events },
+ { SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs },
+ { 0, NULL },
+};
+
+static int snd_seq_do_ioctl(client_t *client, unsigned int cmd, void __user *arg)
+{
+ struct seq_ioctl_table *p;
+
+ switch (cmd) {
+ case SNDRV_SEQ_IOCTL_PVERSION:
+ /* return sequencer version number */
+ return put_user(SNDRV_SEQ_VERSION, (int __user *)arg) ? -EFAULT : 0;
+ case SNDRV_SEQ_IOCTL_CLIENT_ID:
+ /* return the id of this client */
+ return put_user(client->number, (int __user *)arg) ? -EFAULT : 0;
+ }
+
+ if (! arg)
+ return -EFAULT;
+ for (p = ioctl_tables; p->cmd; p++) {
+ if (p->cmd == cmd)
+ return p->func(client, arg);
+ }
+ snd_printd("seq unknown ioctl() 0x%x (type='%c', number=0x%2x)\n",
+ cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
+ return -ENOTTY;
+}
+
+
+static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ client_t *client = (client_t *) file->private_data;
+
+ snd_assert(client != NULL, return -ENXIO);
+
+ return snd_seq_do_ioctl(client, cmd, (void __user *) arg);
+}
+
+#ifdef CONFIG_COMPAT
+#include "seq_compat.c"
+#else
+#define snd_seq_ioctl_compat NULL
+#endif
+
+/* -------------------------------------------------------- */
+
+
+/* exported to kernel modules */
+int snd_seq_create_kernel_client(snd_card_t *card, int client_index, snd_seq_client_callback_t * callback)
+{
+ client_t *client;
+
+ snd_assert(! in_interrupt(), return -EBUSY);
+
+ if (callback == NULL)
+ return -EINVAL;
+ if (card && client_index > 7)
+ return -EINVAL;
+ if (card == NULL && client_index > 63)
+ return -EINVAL;
+ if (card)
+ client_index += 64 + (card->number << 3);
+
+ if (down_interruptible(&register_mutex))
+ return -ERESTARTSYS;
+ /* empty write queue as default */
+ client = seq_create_client1(client_index, 0);
+ if (client == NULL) {
+ up(&register_mutex);
+ return -EBUSY; /* failure code */
+ }
+ usage_alloc(&client_usage, 1);
+
+ client->accept_input = callback->allow_output;
+ client->accept_output = callback->allow_input;
+
+ /* fill client data */
+ client->data.kernel.card = card;
+ client->data.kernel.private_data = callback->private_data;
+ sprintf(client->name, "Client-%d", client->number);
+
+ client->type = KERNEL_CLIENT;
+ up(&register_mutex);
+
+ /* make others aware this new client */
+ snd_seq_system_client_ev_client_start(client->number);
+
+ /* return client number to caller */
+ return client->number;
+}
+
+/* exported to kernel modules */
+int snd_seq_delete_kernel_client(int client)
+{
+ client_t *ptr;
+
+ snd_assert(! in_interrupt(), return -EBUSY);
+
+ ptr = clientptr(client);
+ if (ptr == NULL)
+ return -EINVAL;
+
+ seq_free_client(ptr);
+ kfree(ptr);
+ return 0;
+}
+
+
+/* skeleton to enqueue event, called from snd_seq_kernel_client_enqueue
+ * and snd_seq_kernel_client_enqueue_blocking
+ */
+static int kernel_client_enqueue(int client, snd_seq_event_t *ev,
+ struct file *file, int blocking,
+ int atomic, int hop)
+{
+ client_t *cptr;
+ int result;
+
+ snd_assert(ev != NULL, return -EINVAL);
+
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ return 0; /* ignore this */
+ if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+ return -EINVAL; /* quoted events can't be enqueued */
+
+ /* fill in client number */
+ ev->source.client = client;
+
+ if (check_event_type_and_length(ev))
+ return -EINVAL;
+
+ cptr = snd_seq_client_use_ptr(client);
+ if (cptr == NULL)
+ return -EINVAL;
+
+ if (! cptr->accept_output)
+ result = -EPERM;
+ else /* send it */
+ result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, atomic, hop);
+
+ snd_seq_client_unlock(cptr);
+ return result;
+}
+
+/*
+ * exported, called by kernel clients to enqueue events (w/o blocking)
+ *
+ * RETURN VALUE: zero if succeed, negative if error
+ */
+int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t * ev,
+ int atomic, int hop)
+{
+ return kernel_client_enqueue(client, ev, NULL, 0, atomic, hop);
+}
+
+/*
+ * exported, called by kernel clients to enqueue events (with blocking)
+ *
+ * RETURN VALUE: zero if succeed, negative if error
+ */
+int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev,
+ struct file *file,
+ int atomic, int hop)
+{
+ return kernel_client_enqueue(client, ev, file, 1, atomic, hop);
+}
+
+
+/*
+ * exported, called by kernel clients to dispatch events directly to other
+ * clients, bypassing the queues. Event time-stamp will be updated.
+ *
+ * RETURN VALUE: negative = delivery failed,
+ * zero, or positive: the number of delivered events
+ */
+int snd_seq_kernel_client_dispatch(int client, snd_seq_event_t * ev,
+ int atomic, int hop)
+{
+ client_t *cptr;
+ int result;
+
+ snd_assert(ev != NULL, return -EINVAL);
+
+ /* fill in client number */
+ ev->queue = SNDRV_SEQ_QUEUE_DIRECT;
+ ev->source.client = client;
+
+ if (check_event_type_and_length(ev))
+ return -EINVAL;
+
+ cptr = snd_seq_client_use_ptr(client);
+ if (cptr == NULL)
+ return -EINVAL;
+
+ if (!cptr->accept_output)
+ result = -EPERM;
+ else
+ result = snd_seq_deliver_event(cptr, ev, atomic, hop);
+
+ snd_seq_client_unlock(cptr);
+ return result;
+}
+
+
+/*
+ * exported, called by kernel clients to perform same functions as with
+ * userland ioctl()
+ */
+int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg)
+{
+ client_t *client;
+ mm_segment_t fs;
+ int result;
+
+ client = clientptr(clientid);
+ if (client == NULL)
+ return -ENXIO;
+ fs = snd_enter_user();
+ result = snd_seq_do_ioctl(client, cmd, (void __user *)arg);
+ snd_leave_user(fs);
+ return result;
+}
+
+
+/* exported (for OSS emulator) */
+int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait)
+{
+ client_t *client;
+
+ client = clientptr(clientid);
+ if (client == NULL)
+ return -ENXIO;
+
+ if (! snd_seq_write_pool_allocated(client))
+ return 1;
+ if (snd_seq_pool_poll_wait(client->pool, file, wait))
+ return 1;
+ return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+/*
+ * /proc interface
+ */
+static void snd_seq_info_dump_subscribers(snd_info_buffer_t *buffer, port_subs_info_t *group, int is_src, char *msg)
+{
+ struct list_head *p;
+ subscribers_t *s;
+ int count = 0;
+
+ down_read(&group->list_mutex);
+ if (list_empty(&group->list_head)) {
+ up_read(&group->list_mutex);
+ return;
+ }
+ snd_iprintf(buffer, msg);
+ list_for_each(p, &group->list_head) {
+ if (is_src)
+ s = list_entry(p, subscribers_t, src_list);
+ else
+ s = list_entry(p, subscribers_t, dest_list);
+ if (count++)
+ snd_iprintf(buffer, ", ");
+ snd_iprintf(buffer, "%d:%d",
+ is_src ? s->info.dest.client : s->info.sender.client,
+ is_src ? s->info.dest.port : s->info.sender.port);
+ if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
+ snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue);
+ if (group->exclusive)
+ snd_iprintf(buffer, "[ex]");
+ }
+ up_read(&group->list_mutex);
+ snd_iprintf(buffer, "\n");
+}
+
+#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-')
+#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-')
+#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e')
+
+#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-')
+
+static void snd_seq_info_dump_ports(snd_info_buffer_t *buffer, client_t *client)
+{
+ struct list_head *l;
+
+ down(&client->ports_mutex);
+ list_for_each(l, &client->ports_list_head) {
+ client_port_t *p = list_entry(l, client_port_t, list);
+ snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n",
+ p->addr.port, p->name,
+ FLAG_PERM_RD(p->capability),
+ FLAG_PERM_WR(p->capability),
+ FLAG_PERM_EX(p->capability),
+ FLAG_PERM_DUPLEX(p->capability));
+ snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: ");
+ snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: ");
+ }
+ up(&client->ports_mutex);
+}
+
+
+/* exported to seq_info.c */
+void snd_seq_info_clients_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ extern void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t * pool, char *space);
+ int c;
+ client_t *client;
+
+ snd_iprintf(buffer, "Client info\n");
+ snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur);
+ snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak);
+ snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS);
+ snd_iprintf(buffer, "\n");
+
+ /* list the client table */
+ for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
+ client = snd_seq_client_use_ptr(c);
+ if (client == NULL)
+ continue;
+ if (client->type == NO_CLIENT) {
+ snd_seq_client_unlock(client);
+ continue;
+ }
+
+ snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n",
+ c, client->name,
+ client->type == USER_CLIENT ? "User" : "Kernel");
+ snd_seq_info_dump_ports(buffer, client);
+ if (snd_seq_write_pool_allocated(client)) {
+ snd_iprintf(buffer, " Output pool :\n");
+ snd_seq_info_pool(buffer, client->pool, " ");
+ }
+ if (client->type == USER_CLIENT && client->data.user.fifo &&
+ client->data.user.fifo->pool) {
+ snd_iprintf(buffer, " Input pool :\n");
+ snd_seq_info_pool(buffer, client->data.user.fifo->pool, " ");
+ }
+ snd_seq_client_unlock(client);
+ }
+}
+
+
+/*---------------------------------------------------------------------------*/
+
+
+/*
+ * REGISTRATION PART
+ */
+
+static struct file_operations snd_seq_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = snd_seq_read,
+ .write = snd_seq_write,
+ .open = snd_seq_open,
+ .release = snd_seq_release,
+ .poll = snd_seq_poll,
+ .unlocked_ioctl = snd_seq_ioctl,
+ .compat_ioctl = snd_seq_ioctl_compat,
+};
+
+static snd_minor_t snd_seq_reg =
+{
+ .comment = "sequencer",
+ .f_ops = &snd_seq_f_ops,
+};
+
+
+/*
+ * register sequencer device
+ */
+int __init snd_sequencer_device_init(void)
+{
+ int err;
+
+ if (down_interruptible(&register_mutex))
+ return -ERESTARTSYS;
+
+ if ((err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, &snd_seq_reg, "seq")) < 0) {
+ up(&register_mutex);
+ return err;
+ }
+
+ up(&register_mutex);
+
+ return 0;
+}
+
+
+
+/*
+ * unregister sequencer device
+ */
+void __exit snd_sequencer_device_done(void)
+{
+ snd_unregister_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0);
+}
diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h
new file mode 100644
index 00000000000..3715c36183d
--- /dev/null
+++ b/sound/core/seq/seq_clientmgr.h
@@ -0,0 +1,104 @@
+/*
+ * ALSA sequencer Client Manager
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_CLIENTMGR_H
+#define __SND_SEQ_CLIENTMGR_H
+
+#include <sound/seq_kernel.h>
+#include <linux/bitops.h>
+#include "seq_fifo.h"
+#include "seq_ports.h"
+#include "seq_lock.h"
+
+
+/* client manager */
+
+struct _snd_seq_user_client {
+ struct file *file; /* file struct of client */
+ /* ... */
+
+ /* fifo */
+ fifo_t *fifo; /* queue for incoming events */
+ int fifo_pool_size;
+};
+
+struct _snd_seq_kernel_client {
+ snd_card_t *card;
+ /* pointer to client functions */
+ void *private_data; /* private data for client */
+ /* ... */
+};
+
+
+struct _snd_seq_client {
+ snd_seq_client_type_t type;
+ unsigned int accept_input: 1,
+ accept_output: 1;
+ char name[64]; /* client name */
+ int number; /* client number */
+ unsigned int filter; /* filter flags */
+ DECLARE_BITMAP(event_filter, 256);
+ snd_use_lock_t use_lock;
+ int event_lost;
+ /* ports */
+ int num_ports; /* number of ports */
+ struct list_head ports_list_head;
+ rwlock_t ports_lock;
+ struct semaphore ports_mutex;
+ int convert32; /* convert 32->64bit */
+
+ /* output pool */
+ pool_t *pool; /* memory pool for this client */
+
+ union {
+ user_client_t user;
+ kernel_client_t kernel;
+ } data;
+};
+
+/* usage statistics */
+typedef struct {
+ int cur;
+ int peak;
+} usage_t;
+
+
+extern int client_init_data(void);
+extern int snd_sequencer_device_init(void);
+extern void snd_sequencer_device_done(void);
+
+/* get locked pointer to client */
+extern client_t *snd_seq_client_use_ptr(int clientid);
+
+/* unlock pointer to client */
+#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock)
+
+/* dispatch event to client(s) */
+extern int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop);
+
+/* exported to other modules */
+extern int snd_seq_register_kernel_client(snd_seq_client_callback_t *callback, void *private_data);
+extern int snd_seq_unregister_kernel_client(int client);
+extern int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t *ev, int atomic, int hop);
+int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev, struct file *file, int atomic, int hop);
+int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait);
+int snd_seq_client_notify_subscription(int client, int port, snd_seq_port_subscribe_t *info, int evtype);
+
+#endif
diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c
new file mode 100644
index 00000000000..902ad8b0c35
--- /dev/null
+++ b/sound/core/seq/seq_compat.c
@@ -0,0 +1,137 @@
+/*
+ * 32bit -> 64bit ioctl wrapper for sequencer API
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ */
+
+/* This file included from seq.c */
+
+#include <linux/compat.h>
+
+struct sndrv_seq_port_info32 {
+ struct sndrv_seq_addr addr; /* client/port numbers */
+ char name[64]; /* port name */
+
+ u32 capability; /* port capability bits */
+ u32 type; /* port type bits */
+ s32 midi_channels; /* channels per MIDI port */
+ s32 midi_voices; /* voices per MIDI port */
+ s32 synth_voices; /* voices per SYNTH port */
+
+ s32 read_use; /* R/O: subscribers for output (from this port) */
+ s32 write_use; /* R/O: subscribers for input (to this port) */
+
+ u32 kernel; /* reserved for kernel use (must be NULL) */
+ u32 flags; /* misc. conditioning */
+ unsigned char time_queue; /* queue # for timestamping */
+ char reserved[59]; /* for future use */
+};
+
+static int snd_seq_call_port_info_ioctl(client_t *client, unsigned int cmd,
+ struct sndrv_seq_port_info32 __user *data32)
+{
+ int err = -EFAULT;
+ snd_seq_port_info_t *data;
+ mm_segment_t fs;
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL);
+ if (! data)
+ return -ENOMEM;
+
+ if (copy_from_user(data, data32, sizeof(*data32)) ||
+ get_user(data->flags, &data32->flags) ||
+ get_user(data->time_queue, &data32->time_queue))
+ goto error;
+ data->kernel = NULL;
+
+ fs = snd_enter_user();
+ err = snd_seq_do_ioctl(client, cmd, data);
+ snd_leave_user(fs);
+ if (err < 0)
+ goto error;
+
+ if (copy_to_user(data32, data, sizeof(*data32)) ||
+ put_user(data->flags, &data32->flags) ||
+ put_user(data->time_queue, &data32->time_queue))
+ err = -EFAULT;
+
+ error:
+ kfree(data);
+ return err;
+}
+
+
+
+/*
+ */
+
+enum {
+ SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct sndrv_seq_port_info32),
+ SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct sndrv_seq_port_info32),
+ SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct sndrv_seq_port_info32),
+ SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct sndrv_seq_port_info32),
+ SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct sndrv_seq_port_info32),
+};
+
+static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ client_t *client = (client_t *) file->private_data;
+ void __user *argp = compat_ptr(arg);
+
+ snd_assert(client != NULL, return -ENXIO);
+
+ switch (cmd) {
+ case SNDRV_SEQ_IOCTL_PVERSION:
+ case SNDRV_SEQ_IOCTL_CLIENT_ID:
+ case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
+ case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
+ case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
+ case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
+ case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
+ case SNDRV_SEQ_IOCTL_CREATE_QUEUE:
+ case SNDRV_SEQ_IOCTL_DELETE_QUEUE:
+ case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO:
+ case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO:
+ case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE:
+ case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS:
+ case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO:
+ case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO:
+ case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER:
+ case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER:
+ case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT:
+ case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT:
+ case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL:
+ case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL:
+ case SNDRV_SEQ_IOCTL_REMOVE_EVENTS:
+ case SNDRV_SEQ_IOCTL_QUERY_SUBS:
+ case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION:
+ case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT:
+ case SNDRV_SEQ_IOCTL_RUNNING_MODE:
+ return snd_seq_do_ioctl(client, cmd, argp);
+ case SNDRV_SEQ_IOCTL_CREATE_PORT32:
+ return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp);
+ case SNDRV_SEQ_IOCTL_DELETE_PORT32:
+ return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp);
+ case SNDRV_SEQ_IOCTL_GET_PORT_INFO32:
+ return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp);
+ case SNDRV_SEQ_IOCTL_SET_PORT_INFO32:
+ return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp);
+ case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32:
+ return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp);
+ }
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/seq/seq_device.c b/sound/core/seq/seq_device.c
new file mode 100644
index 00000000000..4d80f39612e
--- /dev/null
+++ b/sound/core/seq/seq_device.c
@@ -0,0 +1,575 @@
+/*
+ * ALSA sequencer device management
+ * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ *
+ *----------------------------------------------------------------
+ *
+ * This device handler separates the card driver module from sequencer
+ * stuff (sequencer core, synth drivers, etc), so that user can avoid
+ * to spend unnecessary resources e.g. if he needs only listening to
+ * MP3s.
+ *
+ * The card (or lowlevel) driver creates a sequencer device entry
+ * via snd_seq_device_new(). This is an entry pointer to communicate
+ * with the sequencer device "driver", which is involved with the
+ * actual part to communicate with the sequencer core.
+ * Each sequencer device entry has an id string and the corresponding
+ * driver with the same id is loaded when required. For example,
+ * lowlevel codes to access emu8000 chip on sbawe card are included in
+ * emu8000-synth module. To activate this module, the hardware
+ * resources like i/o port are passed via snd_seq_device argument.
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/seq_device.h>
+#include <sound/seq_kernel.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer device management");
+MODULE_LICENSE("GPL");
+
+/*
+ * driver list
+ */
+typedef struct ops_list ops_list_t;
+
+/* driver state */
+#define DRIVER_EMPTY 0
+#define DRIVER_LOADED (1<<0)
+#define DRIVER_REQUESTED (1<<1)
+#define DRIVER_LOCKED (1<<2)
+
+struct ops_list {
+ char id[ID_LEN]; /* driver id */
+ int driver; /* driver state */
+ int used; /* reference counter */
+ int argsize; /* argument size */
+
+ /* operators */
+ snd_seq_dev_ops_t ops;
+
+ /* registred devices */
+ struct list_head dev_list; /* list of devices */
+ int num_devices; /* number of associated devices */
+ int num_init_devices; /* number of initialized devices */
+ struct semaphore reg_mutex;
+
+ struct list_head list; /* next driver */
+};
+
+
+static LIST_HEAD(opslist);
+static int num_ops;
+static DECLARE_MUTEX(ops_mutex);
+static snd_info_entry_t *info_entry = NULL;
+
+/*
+ * prototypes
+ */
+static int snd_seq_device_free(snd_seq_device_t *dev);
+static int snd_seq_device_dev_free(snd_device_t *device);
+static int snd_seq_device_dev_register(snd_device_t *device);
+static int snd_seq_device_dev_disconnect(snd_device_t *device);
+static int snd_seq_device_dev_unregister(snd_device_t *device);
+
+static int init_device(snd_seq_device_t *dev, ops_list_t *ops);
+static int free_device(snd_seq_device_t *dev, ops_list_t *ops);
+static ops_list_t *find_driver(char *id, int create_if_empty);
+static ops_list_t *create_driver(char *id);
+static void unlock_driver(ops_list_t *ops);
+static void remove_drivers(void);
+
+/*
+ * show all drivers and their status
+ */
+
+static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ struct list_head *head;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ snd_iprintf(buffer, "snd-%s%s%s%s,%d\n",
+ ops->id,
+ ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""),
+ ops->driver & DRIVER_REQUESTED ? ",requested" : "",
+ ops->driver & DRIVER_LOCKED ? ",locked" : "",
+ ops->num_devices);
+ }
+ up(&ops_mutex);
+}
+
+/*
+ * load all registered drivers (called from seq_clientmgr.c)
+ */
+
+#ifdef CONFIG_KMOD
+/* avoid auto-loading during module_init() */
+static int snd_seq_in_init;
+void snd_seq_autoload_lock(void)
+{
+ snd_seq_in_init++;
+}
+
+void snd_seq_autoload_unlock(void)
+{
+ snd_seq_in_init--;
+}
+#endif
+
+void snd_seq_device_load_drivers(void)
+{
+#ifdef CONFIG_KMOD
+ struct list_head *head;
+
+ /* Calling request_module during module_init()
+ * may cause blocking.
+ */
+ if (snd_seq_in_init)
+ return;
+
+ if (! current->fs->root)
+ return;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ if (! (ops->driver & DRIVER_LOADED) &&
+ ! (ops->driver & DRIVER_REQUESTED)) {
+ ops->used++;
+ up(&ops_mutex);
+ ops->driver |= DRIVER_REQUESTED;
+ request_module("snd-%s", ops->id);
+ down(&ops_mutex);
+ ops->used--;
+ }
+ }
+ up(&ops_mutex);
+#endif
+}
+
+/*
+ * register a sequencer device
+ * card = card info (NULL allowed)
+ * device = device number (if any)
+ * id = id of driver
+ * result = return pointer (NULL allowed if unnecessary)
+ */
+int snd_seq_device_new(snd_card_t *card, int device, char *id, int argsize,
+ snd_seq_device_t **result)
+{
+ snd_seq_device_t *dev;
+ ops_list_t *ops;
+ int err;
+ static snd_device_ops_t dops = {
+ .dev_free = snd_seq_device_dev_free,
+ .dev_register = snd_seq_device_dev_register,
+ .dev_disconnect = snd_seq_device_dev_disconnect,
+ .dev_unregister = snd_seq_device_dev_unregister
+ };
+
+ if (result)
+ *result = NULL;
+
+ snd_assert(id != NULL, return -EINVAL);
+
+ ops = find_driver(id, 1);
+ if (ops == NULL)
+ return -ENOMEM;
+
+ dev = kcalloc(1, sizeof(*dev)*2 + argsize, GFP_KERNEL);
+ if (dev == NULL) {
+ unlock_driver(ops);
+ return -ENOMEM;
+ }
+
+ /* set up device info */
+ dev->card = card;
+ dev->device = device;
+ strlcpy(dev->id, id, sizeof(dev->id));
+ dev->argsize = argsize;
+ dev->status = SNDRV_SEQ_DEVICE_FREE;
+
+ /* add this device to the list */
+ down(&ops->reg_mutex);
+ list_add_tail(&dev->list, &ops->dev_list);
+ ops->num_devices++;
+ up(&ops->reg_mutex);
+
+ unlock_driver(ops);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
+ snd_seq_device_free(dev);
+ return err;
+ }
+
+ if (result)
+ *result = dev;
+
+ return 0;
+}
+
+/*
+ * free the existing device
+ */
+static int snd_seq_device_free(snd_seq_device_t *dev)
+{
+ ops_list_t *ops;
+
+ snd_assert(dev != NULL, return -EINVAL);
+
+ ops = find_driver(dev->id, 0);
+ if (ops == NULL)
+ return -ENXIO;
+
+ /* remove the device from the list */
+ down(&ops->reg_mutex);
+ list_del(&dev->list);
+ ops->num_devices--;
+ up(&ops->reg_mutex);
+
+ free_device(dev, ops);
+ if (dev->private_free)
+ dev->private_free(dev);
+ kfree(dev);
+
+ unlock_driver(ops);
+
+ return 0;
+}
+
+static int snd_seq_device_dev_free(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ return snd_seq_device_free(dev);
+}
+
+/*
+ * register the device
+ */
+static int snd_seq_device_dev_register(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ ops_list_t *ops;
+
+ ops = find_driver(dev->id, 0);
+ if (ops == NULL)
+ return -ENOENT;
+
+ /* initialize this device if the corresponding driver was
+ * already loaded
+ */
+ if (ops->driver & DRIVER_LOADED)
+ init_device(dev, ops);
+
+ unlock_driver(ops);
+ return 0;
+}
+
+/*
+ * disconnect the device
+ */
+static int snd_seq_device_dev_disconnect(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ ops_list_t *ops;
+
+ ops = find_driver(dev->id, 0);
+ if (ops == NULL)
+ return -ENOENT;
+
+ free_device(dev, ops);
+
+ unlock_driver(ops);
+ return 0;
+}
+
+/*
+ * unregister the existing device
+ */
+static int snd_seq_device_dev_unregister(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ return snd_seq_device_free(dev);
+}
+
+/*
+ * register device driver
+ * id = driver id
+ * entry = driver operators - duplicated to each instance
+ */
+int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize)
+{
+ struct list_head *head;
+ ops_list_t *ops;
+
+ if (id == NULL || entry == NULL ||
+ entry->init_device == NULL || entry->free_device == NULL)
+ return -EINVAL;
+
+ snd_seq_autoload_lock();
+ ops = find_driver(id, 1);
+ if (ops == NULL) {
+ snd_seq_autoload_unlock();
+ return -ENOMEM;
+ }
+ if (ops->driver & DRIVER_LOADED) {
+ snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id);
+ unlock_driver(ops);
+ snd_seq_autoload_unlock();
+ return -EBUSY;
+ }
+
+ down(&ops->reg_mutex);
+ /* copy driver operators */
+ ops->ops = *entry;
+ ops->driver |= DRIVER_LOADED;
+ ops->argsize = argsize;
+
+ /* initialize existing devices if necessary */
+ list_for_each(head, &ops->dev_list) {
+ snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
+ init_device(dev, ops);
+ }
+ up(&ops->reg_mutex);
+
+ unlock_driver(ops);
+ snd_seq_autoload_unlock();
+
+ return 0;
+}
+
+
+/*
+ * create driver record
+ */
+static ops_list_t * create_driver(char *id)
+{
+ ops_list_t *ops;
+
+ ops = kmalloc(sizeof(*ops), GFP_KERNEL);
+ if (ops == NULL)
+ return ops;
+ memset(ops, 0, sizeof(*ops));
+
+ /* set up driver entry */
+ strlcpy(ops->id, id, sizeof(ops->id));
+ init_MUTEX(&ops->reg_mutex);
+ ops->driver = DRIVER_EMPTY;
+ INIT_LIST_HEAD(&ops->dev_list);
+ /* lock this instance */
+ ops->used = 1;
+
+ /* register driver entry */
+ down(&ops_mutex);
+ list_add_tail(&ops->list, &opslist);
+ num_ops++;
+ up(&ops_mutex);
+
+ return ops;
+}
+
+
+/*
+ * unregister the specified driver
+ */
+int snd_seq_device_unregister_driver(char *id)
+{
+ struct list_head *head;
+ ops_list_t *ops;
+
+ ops = find_driver(id, 0);
+ if (ops == NULL)
+ return -ENXIO;
+ if (! (ops->driver & DRIVER_LOADED) ||
+ (ops->driver & DRIVER_LOCKED)) {
+ snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", id, ops->driver);
+ unlock_driver(ops);
+ return -EBUSY;
+ }
+
+ /* close and release all devices associated with this driver */
+ down(&ops->reg_mutex);
+ ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
+ list_for_each(head, &ops->dev_list) {
+ snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
+ free_device(dev, ops);
+ }
+
+ ops->driver = 0;
+ if (ops->num_init_devices > 0)
+ snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", ops->num_init_devices);
+ up(&ops->reg_mutex);
+
+ unlock_driver(ops);
+
+ /* remove empty driver entries */
+ remove_drivers();
+
+ return 0;
+}
+
+
+/*
+ * remove empty driver entries
+ */
+static void remove_drivers(void)
+{
+ struct list_head *head;
+
+ down(&ops_mutex);
+ head = opslist.next;
+ while (head != &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ if (! (ops->driver & DRIVER_LOADED) &&
+ ops->used == 0 && ops->num_devices == 0) {
+ head = head->next;
+ list_del(&ops->list);
+ kfree(ops);
+ num_ops--;
+ } else
+ head = head->next;
+ }
+ up(&ops_mutex);
+}
+
+/*
+ * initialize the device - call init_device operator
+ */
+static int init_device(snd_seq_device_t *dev, ops_list_t *ops)
+{
+ if (! (ops->driver & DRIVER_LOADED))
+ return 0; /* driver is not loaded yet */
+ if (dev->status != SNDRV_SEQ_DEVICE_FREE)
+ return 0; /* already initialized */
+ if (ops->argsize != dev->argsize) {
+ snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
+ return -EINVAL;
+ }
+ if (ops->ops.init_device(dev) >= 0) {
+ dev->status = SNDRV_SEQ_DEVICE_REGISTERED;
+ ops->num_init_devices++;
+ } else {
+ snd_printk(KERN_ERR "init_device failed: %s: %s\n", dev->name, dev->id);
+ }
+
+ return 0;
+}
+
+/*
+ * release the device - call free_device operator
+ */
+static int free_device(snd_seq_device_t *dev, ops_list_t *ops)
+{
+ int result;
+
+ if (! (ops->driver & DRIVER_LOADED))
+ return 0; /* driver is not loaded yet */
+ if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED)
+ return 0; /* not registered */
+ if (ops->argsize != dev->argsize) {
+ snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize);
+ return -EINVAL;
+ }
+ if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) {
+ dev->status = SNDRV_SEQ_DEVICE_FREE;
+ dev->driver_data = NULL;
+ ops->num_init_devices--;
+ } else {
+ snd_printk(KERN_ERR "free_device failed: %s: %s\n", dev->name, dev->id);
+ }
+
+ return 0;
+}
+
+/*
+ * find the matching driver with given id
+ */
+static ops_list_t * find_driver(char *id, int create_if_empty)
+{
+ struct list_head *head;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ if (strcmp(ops->id, id) == 0) {
+ ops->used++;
+ up(&ops_mutex);
+ return ops;
+ }
+ }
+ up(&ops_mutex);
+ if (create_if_empty)
+ return create_driver(id);
+ return NULL;
+}
+
+static void unlock_driver(ops_list_t *ops)
+{
+ down(&ops_mutex);
+ ops->used--;
+ up(&ops_mutex);
+}
+
+
+/*
+ * module part
+ */
+
+static int __init alsa_seq_device_init(void)
+{
+ info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", snd_seq_root);
+ if (info_entry == NULL)
+ return -ENOMEM;
+ info_entry->content = SNDRV_INFO_CONTENT_TEXT;
+ info_entry->c.text.read_size = 2048;
+ info_entry->c.text.read = snd_seq_device_info;
+ if (snd_info_register(info_entry) < 0) {
+ snd_info_free_entry(info_entry);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void __exit alsa_seq_device_exit(void)
+{
+ remove_drivers();
+ snd_info_unregister(info_entry);
+ if (num_ops)
+ snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops);
+}
+
+module_init(alsa_seq_device_init)
+module_exit(alsa_seq_device_exit)
+
+EXPORT_SYMBOL(snd_seq_device_load_drivers);
+EXPORT_SYMBOL(snd_seq_device_new);
+EXPORT_SYMBOL(snd_seq_device_register_driver);
+EXPORT_SYMBOL(snd_seq_device_unregister_driver);
+#ifdef CONFIG_KMOD
+EXPORT_SYMBOL(snd_seq_autoload_lock);
+EXPORT_SYMBOL(snd_seq_autoload_unlock);
+#endif
diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c
new file mode 100644
index 00000000000..e88967c5b93
--- /dev/null
+++ b/sound/core/seq/seq_dummy.c
@@ -0,0 +1,273 @@
+/*
+ * ALSA sequencer MIDI-through client
+ * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include "seq_clientmgr.h"
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+/*
+
+ Sequencer MIDI-through client
+
+ This gives a simple midi-through client. All the normal input events
+ are redirected to output port immediately.
+ The routing can be done via aconnect program in alsa-utils.
+
+ Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
+ If you want to auto-load this module, you may add the following alias
+ in your /etc/conf.modules file.
+
+ alias snd-seq-client-62 snd-seq-dummy
+
+ The module is loaded on demand for client 62, or /proc/asound/seq/
+ is accessed. If you don't need this module to be loaded, alias
+ snd-seq-client-62 as "off". This will help modprobe.
+
+ The number of ports to be created can be specified via the module
+ parameter "ports". For example, to create four ports, add the
+ following option in /etc/modprobe.conf:
+
+ option snd-seq-dummy ports=4
+
+ The modle option "duplex=1" enables duplex operation to the port.
+ In duplex mode, a pair of ports are created instead of single port,
+ and events are tunneled between pair-ports. For example, input to
+ port A is sent to output port of another port B and vice versa.
+ In duplex mode, each port has DUPLEX capability.
+
+ */
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
+
+static int ports = 1;
+static int duplex = 0;
+
+module_param(ports, int, 0444);
+MODULE_PARM_DESC(ports, "number of ports to be created");
+module_param(duplex, bool, 0444);
+MODULE_PARM_DESC(duplex, "create DUPLEX ports");
+
+typedef struct snd_seq_dummy_port {
+ int client;
+ int port;
+ int duplex;
+ int connect;
+} snd_seq_dummy_port_t;
+
+static int my_client = -1;
+
+/*
+ * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
+ * to subscribers.
+ * Note: this callback is called only after all subscribers are removed.
+ */
+static int
+dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ snd_seq_dummy_port_t *p;
+ int i;
+ snd_seq_event_t ev;
+
+ p = private_data;
+ memset(&ev, 0, sizeof(ev));
+ if (p->duplex)
+ ev.source.port = p->connect;
+ else
+ ev.source.port = p->port;
+ ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+ for (i = 0; i < 16; i++) {
+ ev.data.control.channel = i;
+ ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
+ snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
+ ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
+ snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
+ }
+ return 0;
+}
+
+/*
+ * event input callback - just redirect events to subscribers
+ */
+static int
+dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
+{
+ snd_seq_dummy_port_t *p;
+ snd_seq_event_t tmpev;
+
+ p = private_data;
+ if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
+ ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+ return 0; /* ignore system messages */
+ tmpev = *ev;
+ if (p->duplex)
+ tmpev.source.port = p->connect;
+ else
+ tmpev.source.port = p->port;
+ tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
+}
+
+/*
+ * free_private callback
+ */
+static void
+dummy_free(void *private_data)
+{
+ snd_seq_dummy_port_t *p;
+
+ p = private_data;
+ kfree(p);
+}
+
+/*
+ * create a port
+ */
+static snd_seq_dummy_port_t __init *
+create_port(int idx, int type)
+{
+ snd_seq_port_info_t pinfo;
+ snd_seq_port_callback_t pcb;
+ snd_seq_dummy_port_t *rec;
+
+ if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ rec->client = my_client;
+ rec->duplex = duplex;
+ rec->connect = 0;
+ memset(&pinfo, 0, sizeof(pinfo));
+ pinfo.addr.client = my_client;
+ if (duplex)
+ sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
+ (type ? 'B' : 'A'));
+ else
+ sprintf(pinfo.name, "Midi Through Port-%d", idx);
+ pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+ pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+ if (duplex)
+ pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+ memset(&pcb, 0, sizeof(pcb));
+ pcb.owner = THIS_MODULE;
+ pcb.unuse = dummy_unuse;
+ pcb.event_input = dummy_input;
+ pcb.private_free = dummy_free;
+ pcb.private_data = rec;
+ pinfo.kernel = &pcb;
+ if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
+ kfree(rec);
+ return NULL;
+ }
+ rec->port = pinfo.addr.port;
+ return rec;
+}
+
+/*
+ * register client and create ports
+ */
+static int __init
+register_client(void)
+{
+ snd_seq_client_callback_t cb;
+ snd_seq_client_info_t cinfo;
+ snd_seq_dummy_port_t *rec1, *rec2;
+ int i;
+
+ if (ports < 1) {
+ snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
+ return -EINVAL;
+ }
+
+ /* create client */
+ memset(&cb, 0, sizeof(cb));
+ cb.allow_input = 1;
+ cb.allow_output = 1;
+ my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
+ if (my_client < 0)
+ return my_client;
+
+ /* set client name */
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.client = my_client;
+ cinfo.type = KERNEL_CLIENT;
+ strcpy(cinfo.name, "Midi Through");
+ snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+ /* create ports */
+ for (i = 0; i < ports; i++) {
+ rec1 = create_port(i, 0);
+ if (rec1 == NULL) {
+ snd_seq_delete_kernel_client(my_client);
+ return -ENOMEM;
+ }
+ if (duplex) {
+ rec2 = create_port(i, 1);
+ if (rec2 == NULL) {
+ snd_seq_delete_kernel_client(my_client);
+ return -ENOMEM;
+ }
+ rec1->connect = rec2->port;
+ rec2->connect = rec1->port;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * delete client if exists
+ */
+static void __exit
+delete_client(void)
+{
+ if (my_client >= 0)
+ snd_seq_delete_kernel_client(my_client);
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_seq_dummy_init(void)
+{
+ int err;
+ snd_seq_autoload_lock();
+ err = register_client();
+ snd_seq_autoload_unlock();
+ return err;
+}
+
+static void __exit alsa_seq_dummy_exit(void)
+{
+ delete_client();
+}
+
+module_init(alsa_seq_dummy_init)
+module_exit(alsa_seq_dummy_exit)
diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c
new file mode 100644
index 00000000000..3b7647ca7ad
--- /dev/null
+++ b/sound/core/seq/seq_fifo.c
@@ -0,0 +1,264 @@
+/*
+ * ALSA sequencer FIFO
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_fifo.h"
+#include "seq_lock.h"
+
+
+/* FIFO */
+
+/* create new fifo */
+fifo_t *snd_seq_fifo_new(int poolsize)
+{
+ fifo_t *f;
+
+ f = kcalloc(1, sizeof(*f), GFP_KERNEL);
+ if (f == NULL) {
+ snd_printd("malloc failed for snd_seq_fifo_new() \n");
+ return NULL;
+ }
+
+ f->pool = snd_seq_pool_new(poolsize);
+ if (f->pool == NULL) {
+ kfree(f);
+ return NULL;
+ }
+ if (snd_seq_pool_init(f->pool) < 0) {
+ snd_seq_pool_delete(&f->pool);
+ kfree(f);
+ return NULL;
+ }
+
+ spin_lock_init(&f->lock);
+ snd_use_lock_init(&f->use_lock);
+ init_waitqueue_head(&f->input_sleep);
+ atomic_set(&f->overflow, 0);
+
+ f->head = NULL;
+ f->tail = NULL;
+ f->cells = 0;
+
+ return f;
+}
+
+void snd_seq_fifo_delete(fifo_t **fifo)
+{
+ fifo_t *f;
+
+ snd_assert(fifo != NULL, return);
+ f = *fifo;
+ snd_assert(f != NULL, return);
+ *fifo = NULL;
+
+ snd_seq_fifo_clear(f);
+
+ /* wake up clients if any */
+ if (waitqueue_active(&f->input_sleep))
+ wake_up(&f->input_sleep);
+
+ /* release resources...*/
+ /*....................*/
+
+ if (f->pool) {
+ snd_seq_pool_done(f->pool);
+ snd_seq_pool_delete(&f->pool);
+ }
+
+ kfree(f);
+}
+
+static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f);
+
+/* clear queue */
+void snd_seq_fifo_clear(fifo_t *f)
+{
+ snd_seq_event_cell_t *cell;
+ unsigned long flags;
+
+ /* clear overflow flag */
+ atomic_set(&f->overflow, 0);
+
+ snd_use_lock_sync(&f->use_lock);
+ spin_lock_irqsave(&f->lock, flags);
+ /* drain the fifo */
+ while ((cell = fifo_cell_out(f)) != NULL) {
+ snd_seq_cell_free(cell);
+ }
+ spin_unlock_irqrestore(&f->lock, flags);
+}
+
+
+/* enqueue event to fifo */
+int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event)
+{
+ snd_seq_event_cell_t *cell;
+ unsigned long flags;
+ int err;
+
+ snd_assert(f != NULL, return -EINVAL);
+
+ snd_use_lock_use(&f->use_lock);
+ err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */
+ if (err < 0) {
+ if (err == -ENOMEM)
+ atomic_inc(&f->overflow);
+ snd_use_lock_free(&f->use_lock);
+ return err;
+ }
+
+ /* append new cells to fifo */
+ spin_lock_irqsave(&f->lock, flags);
+ if (f->tail != NULL)
+ f->tail->next = cell;
+ f->tail = cell;
+ if (f->head == NULL)
+ f->head = cell;
+ f->cells++;
+ spin_unlock_irqrestore(&f->lock, flags);
+
+ /* wakeup client */
+ if (waitqueue_active(&f->input_sleep))
+ wake_up(&f->input_sleep);
+
+ snd_use_lock_free(&f->use_lock);
+
+ return 0; /* success */
+
+}
+
+/* dequeue cell from fifo */
+static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f)
+{
+ snd_seq_event_cell_t *cell;
+
+ if ((cell = f->head) != NULL) {
+ f->head = cell->next;
+
+ /* reset tail if this was the last element */
+ if (f->tail == cell)
+ f->tail = NULL;
+
+ cell->next = NULL;
+ f->cells--;
+ }
+
+ return cell;
+}
+
+/* dequeue cell from fifo and copy on user space */
+int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock)
+{
+ snd_seq_event_cell_t *cell;
+ unsigned long flags;
+ wait_queue_t wait;
+
+ snd_assert(f != NULL, return -EINVAL);
+
+ *cellp = NULL;
+ init_waitqueue_entry(&wait, current);
+ spin_lock_irqsave(&f->lock, flags);
+ while ((cell = fifo_cell_out(f)) == NULL) {
+ if (nonblock) {
+ /* non-blocking - return immediately */
+ spin_unlock_irqrestore(&f->lock, flags);
+ return -EAGAIN;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&f->input_sleep, &wait);
+ spin_unlock_irq(&f->lock);
+ schedule();
+ spin_lock_irq(&f->lock);
+ remove_wait_queue(&f->input_sleep, &wait);
+ if (signal_pending(current)) {
+ spin_unlock_irqrestore(&f->lock, flags);
+ return -ERESTARTSYS;
+ }
+ }
+ spin_unlock_irqrestore(&f->lock, flags);
+ *cellp = cell;
+
+ return 0;
+}
+
+
+void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell)
+{
+ unsigned long flags;
+
+ if (cell) {
+ spin_lock_irqsave(&f->lock, flags);
+ cell->next = f->head;
+ f->head = cell;
+ f->cells++;
+ spin_unlock_irqrestore(&f->lock, flags);
+ }
+}
+
+
+/* polling; return non-zero if queue is available */
+int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait)
+{
+ poll_wait(file, &f->input_sleep, wait);
+ return (f->cells > 0);
+}
+
+/* change the size of pool; all old events are removed */
+int snd_seq_fifo_resize(fifo_t *f, int poolsize)
+{
+ unsigned long flags;
+ pool_t *newpool, *oldpool;
+ snd_seq_event_cell_t *cell, *next, *oldhead;
+
+ snd_assert(f != NULL && f->pool != NULL, return -EINVAL);
+
+ /* allocate new pool */
+ newpool = snd_seq_pool_new(poolsize);
+ if (newpool == NULL)
+ return -ENOMEM;
+ if (snd_seq_pool_init(newpool) < 0) {
+ snd_seq_pool_delete(&newpool);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&f->lock, flags);
+ /* remember old pool */
+ oldpool = f->pool;
+ oldhead = f->head;
+ /* exchange pools */
+ f->pool = newpool;
+ f->head = NULL;
+ f->tail = NULL;
+ f->cells = 0;
+ /* NOTE: overflow flag is not cleared */
+ spin_unlock_irqrestore(&f->lock, flags);
+
+ /* release cells in old pool */
+ for (cell = oldhead; cell; cell = next) {
+ next = cell->next;
+ snd_seq_cell_free(cell);
+ }
+ snd_seq_pool_delete(&oldpool);
+
+ return 0;
+}
diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h
new file mode 100644
index 00000000000..d677c261b0a
--- /dev/null
+++ b/sound/core/seq/seq_fifo.h
@@ -0,0 +1,72 @@
+/*
+ * ALSA sequencer FIFO
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_FIFO_H
+#define __SND_SEQ_FIFO_H
+
+#include "seq_memory.h"
+#include "seq_lock.h"
+
+
+/* === FIFO === */
+
+typedef struct {
+ pool_t *pool; /* FIFO pool */
+ snd_seq_event_cell_t* head; /* pointer to head of fifo */
+ snd_seq_event_cell_t* tail; /* pointer to tail of fifo */
+ int cells;
+ spinlock_t lock;
+ snd_use_lock_t use_lock;
+ wait_queue_head_t input_sleep;
+ atomic_t overflow;
+
+} fifo_t;
+
+/* create new fifo (constructor) */
+extern fifo_t *snd_seq_fifo_new(int poolsize);
+
+/* delete fifo (destructor) */
+extern void snd_seq_fifo_delete(fifo_t **f);
+
+
+/* enqueue event to fifo */
+extern int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event);
+
+/* lock fifo from release */
+#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock)
+#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock)
+
+/* get a cell from fifo - fifo should be locked */
+int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock);
+
+/* free dequeued cell - fifo should be locked */
+extern void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell);
+
+/* clean up queue */
+extern void snd_seq_fifo_clear(fifo_t *f);
+
+/* polling */
+extern int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait);
+
+/* resize pool in fifo */
+int snd_seq_fifo_resize(fifo_t *f, int poolsize);
+
+
+#endif
diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c
new file mode 100644
index 00000000000..b50b695c41c
--- /dev/null
+++ b/sound/core/seq/seq_info.c
@@ -0,0 +1,75 @@
+/*
+ * ALSA sequencer /proc interface
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "seq_info.h"
+#include "seq_clientmgr.h"
+#include "seq_timer.h"
+
+
+static snd_info_entry_t *queues_entry;
+static snd_info_entry_t *clients_entry;
+static snd_info_entry_t *timer_entry;
+
+
+static snd_info_entry_t * __init
+create_info_entry(char *name, int size, void (*read)(snd_info_entry_t *, snd_info_buffer_t *))
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root);
+ if (entry == NULL)
+ return NULL;
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->c.text.read_size = size;
+ entry->c.text.read = read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return NULL;
+ }
+ return entry;
+}
+
+
+/* create all our /proc entries */
+int __init snd_seq_info_init(void)
+{
+ queues_entry = create_info_entry("queues", 512 + (256 * SNDRV_SEQ_MAX_QUEUES),
+ snd_seq_info_queues_read);
+ clients_entry = create_info_entry("clients", 512 + (256 * SNDRV_SEQ_MAX_CLIENTS),
+ snd_seq_info_clients_read);
+ timer_entry = create_info_entry("timer", 1024, snd_seq_info_timer_read);
+ return 0;
+}
+
+int __exit snd_seq_info_done(void)
+{
+ if (queues_entry)
+ snd_info_unregister(queues_entry);
+ if (clients_entry)
+ snd_info_unregister(clients_entry);
+ if (timer_entry)
+ snd_info_unregister(timer_entry);
+ return 0;
+}
diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h
new file mode 100644
index 00000000000..efd099a858e
--- /dev/null
+++ b/sound/core/seq/seq_info.h
@@ -0,0 +1,36 @@
+/*
+ * ALSA sequencer /proc info
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_INFO_H
+#define __SND_SEQ_INFO_H
+
+#include <sound/info.h>
+#include <sound/seq_kernel.h>
+
+void snd_seq_info_clients_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+void snd_seq_info_queues_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
+
+
+int snd_seq_info_init( void );
+int snd_seq_info_done( void );
+
+
+#endif
diff --git a/sound/core/seq/seq_instr.c b/sound/core/seq/seq_instr.c
new file mode 100644
index 00000000000..5b40ea2ba8f
--- /dev/null
+++ b/sound/core/seq/seq_instr.c
@@ -0,0 +1,653 @@
+/*
+ * Generic Instrument routines for ALSA sequencer
+ * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "seq_clientmgr.h"
+#include <sound/seq_instr.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer instrument library.");
+MODULE_LICENSE("GPL");
+
+
+static void snd_instr_lock_ops(snd_seq_kinstr_list_t *list)
+{
+ if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
+ spin_lock_irqsave(&list->ops_lock, list->ops_flags);
+ } else {
+ down(&list->ops_mutex);
+ }
+}
+
+static void snd_instr_unlock_ops(snd_seq_kinstr_list_t *list)
+{
+ if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) {
+ spin_unlock_irqrestore(&list->ops_lock, list->ops_flags);
+ } else {
+ up(&list->ops_mutex);
+ }
+}
+
+static snd_seq_kinstr_t *snd_seq_instr_new(int add_len, int atomic)
+{
+ snd_seq_kinstr_t *instr;
+
+ instr = kcalloc(1, sizeof(snd_seq_kinstr_t) + add_len, atomic ? GFP_ATOMIC : GFP_KERNEL);
+ if (instr == NULL)
+ return NULL;
+ instr->add_len = add_len;
+ return instr;
+}
+
+static int snd_seq_instr_free(snd_seq_kinstr_t *instr, int atomic)
+{
+ int result = 0;
+
+ if (instr == NULL)
+ return -EINVAL;
+ if (instr->ops && instr->ops->remove)
+ result = instr->ops->remove(instr->ops->private_data, instr, 1);
+ if (!result)
+ kfree(instr);
+ return result;
+}
+
+snd_seq_kinstr_list_t *snd_seq_instr_list_new(void)
+{
+ snd_seq_kinstr_list_t *list;
+
+ list = kcalloc(1, sizeof(snd_seq_kinstr_list_t), GFP_KERNEL);
+ if (list == NULL)
+ return NULL;
+ spin_lock_init(&list->lock);
+ spin_lock_init(&list->ops_lock);
+ init_MUTEX(&list->ops_mutex);
+ list->owner = -1;
+ return list;
+}
+
+void snd_seq_instr_list_free(snd_seq_kinstr_list_t **list_ptr)
+{
+ snd_seq_kinstr_list_t *list;
+ snd_seq_kinstr_t *instr;
+ snd_seq_kcluster_t *cluster;
+ int idx;
+ unsigned long flags;
+
+ if (list_ptr == NULL)
+ return;
+ list = *list_ptr;
+ *list_ptr = NULL;
+ if (list == NULL)
+ return;
+
+ for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {
+ while ((instr = list->hash[idx]) != NULL) {
+ list->hash[idx] = instr->next;
+ list->count--;
+ spin_lock_irqsave(&list->lock, flags);
+ while (instr->use) {
+ spin_unlock_irqrestore(&list->lock, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ spin_lock_irqsave(&list->lock, flags);
+ }
+ spin_unlock_irqrestore(&list->lock, flags);
+ if (snd_seq_instr_free(instr, 0)<0)
+ snd_printk(KERN_WARNING "instrument free problem\n");
+ }
+ while ((cluster = list->chash[idx]) != NULL) {
+ list->chash[idx] = cluster->next;
+ list->ccount--;
+ kfree(cluster);
+ }
+ }
+ kfree(list);
+}
+
+static int instr_free_compare(snd_seq_kinstr_t *instr,
+ snd_seq_instr_header_t *ifree,
+ unsigned int client)
+{
+ switch (ifree->cmd) {
+ case SNDRV_SEQ_INSTR_FREE_CMD_ALL:
+ /* all, except private for other clients */
+ if ((instr->instr.std & 0xff000000) == 0)
+ return 0;
+ if (((instr->instr.std >> 24) & 0xff) == client)
+ return 0;
+ return 1;
+ case SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE:
+ /* all my private instruments */
+ if ((instr->instr.std & 0xff000000) == 0)
+ return 1;
+ if (((instr->instr.std >> 24) & 0xff) == client)
+ return 0;
+ return 1;
+ case SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER:
+ /* all my private instruments */
+ if ((instr->instr.std & 0xff000000) == 0) {
+ if (instr->instr.cluster == ifree->id.cluster)
+ return 0;
+ return 1;
+ }
+ if (((instr->instr.std >> 24) & 0xff) == client) {
+ if (instr->instr.cluster == ifree->id.cluster)
+ return 0;
+ }
+ return 1;
+ }
+ return 1;
+}
+
+int snd_seq_instr_list_free_cond(snd_seq_kinstr_list_t *list,
+ snd_seq_instr_header_t *ifree,
+ int client,
+ int atomic)
+{
+ snd_seq_kinstr_t *instr, *prev, *next, *flist;
+ int idx;
+ unsigned long flags;
+
+ snd_instr_lock_ops(list);
+ for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) {
+ spin_lock_irqsave(&list->lock, flags);
+ instr = list->hash[idx];
+ prev = flist = NULL;
+ while (instr) {
+ while (instr && instr_free_compare(instr, ifree, (unsigned int)client)) {
+ prev = instr;
+ instr = instr->next;
+ }
+ if (instr == NULL)
+ continue;
+ if (instr->ops && instr->ops->notify)
+ instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
+ next = instr->next;
+ if (prev == NULL) {
+ list->hash[idx] = next;
+ } else {
+ prev->next = next;
+ }
+ list->count--;
+ instr->next = flist;
+ flist = instr;
+ instr = next;
+ }
+ spin_unlock_irqrestore(&list->lock, flags);
+ while (flist) {
+ instr = flist;
+ flist = instr->next;
+ while (instr->use) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+ if (snd_seq_instr_free(instr, atomic)<0)
+ snd_printk(KERN_WARNING "instrument free problem\n");
+ instr = next;
+ }
+ }
+ snd_instr_unlock_ops(list);
+ return 0;
+}
+
+static int compute_hash_instr_key(snd_seq_instr_t *instr)
+{
+ int result;
+
+ result = instr->bank | (instr->prg << 16);
+ result += result >> 24;
+ result += result >> 16;
+ result += result >> 8;
+ return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
+}
+
+#if 0
+static int compute_hash_cluster_key(snd_seq_instr_cluster_t cluster)
+{
+ int result;
+
+ result = cluster;
+ result += result >> 24;
+ result += result >> 16;
+ result += result >> 8;
+ return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1);
+}
+#endif
+
+static int compare_instr(snd_seq_instr_t *i1, snd_seq_instr_t *i2, int exact)
+{
+ if (exact) {
+ if (i1->cluster != i2->cluster ||
+ i1->bank != i2->bank ||
+ i1->prg != i2->prg)
+ return 1;
+ if ((i1->std & 0xff000000) != (i2->std & 0xff000000))
+ return 1;
+ if (!(i1->std & i2->std))
+ return 1;
+ return 0;
+ } else {
+ unsigned int client_check;
+
+ if (i2->cluster && i1->cluster != i2->cluster)
+ return 1;
+ client_check = i2->std & 0xff000000;
+ if (client_check) {
+ if ((i1->std & 0xff000000) != client_check)
+ return 1;
+ } else {
+ if ((i1->std & i2->std) != i2->std)
+ return 1;
+ }
+ return i1->bank != i2->bank || i1->prg != i2->prg;
+ }
+}
+
+snd_seq_kinstr_t *snd_seq_instr_find(snd_seq_kinstr_list_t *list,
+ snd_seq_instr_t *instr,
+ int exact,
+ int follow_alias)
+{
+ unsigned long flags;
+ int depth = 0;
+ snd_seq_kinstr_t *result;
+
+ if (list == NULL || instr == NULL)
+ return NULL;
+ spin_lock_irqsave(&list->lock, flags);
+ __again:
+ result = list->hash[compute_hash_instr_key(instr)];
+ while (result) {
+ if (!compare_instr(&result->instr, instr, exact)) {
+ if (follow_alias && (result->type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)) {
+ instr = (snd_seq_instr_t *)KINSTR_DATA(result);
+ if (++depth > 10)
+ goto __not_found;
+ goto __again;
+ }
+ result->use++;
+ spin_unlock_irqrestore(&list->lock, flags);
+ return result;
+ }
+ result = result->next;
+ }
+ __not_found:
+ spin_unlock_irqrestore(&list->lock, flags);
+ return NULL;
+}
+
+void snd_seq_instr_free_use(snd_seq_kinstr_list_t *list,
+ snd_seq_kinstr_t *instr)
+{
+ unsigned long flags;
+
+ if (list == NULL || instr == NULL)
+ return;
+ spin_lock_irqsave(&list->lock, flags);
+ if (instr->use <= 0) {
+ snd_printk(KERN_ERR "free_use: fatal!!! use = %i, name = '%s'\n", instr->use, instr->name);
+ } else {
+ instr->use--;
+ }
+ spin_unlock_irqrestore(&list->lock, flags);
+}
+
+static snd_seq_kinstr_ops_t *instr_ops(snd_seq_kinstr_ops_t *ops, char *instr_type)
+{
+ while (ops) {
+ if (!strcmp(ops->instr_type, instr_type))
+ return ops;
+ ops = ops->next;
+ }
+ return NULL;
+}
+
+static int instr_result(snd_seq_event_t *ev,
+ int type, int result,
+ int atomic)
+{
+ snd_seq_event_t sev;
+
+ memset(&sev, 0, sizeof(sev));
+ sev.type = SNDRV_SEQ_EVENT_RESULT;
+ sev.flags = SNDRV_SEQ_TIME_STAMP_REAL | SNDRV_SEQ_EVENT_LENGTH_FIXED |
+ SNDRV_SEQ_PRIORITY_NORMAL;
+ sev.source = ev->dest;
+ sev.dest = ev->source;
+ sev.data.result.event = type;
+ sev.data.result.result = result;
+#if 0
+ printk("instr result - type = %i, result = %i, queue = %i, source.client:port = %i:%i, dest.client:port = %i:%i\n",
+ type, result,
+ sev.queue,
+ sev.source.client, sev.source.port,
+ sev.dest.client, sev.dest.port);
+#endif
+ return snd_seq_kernel_client_dispatch(sev.source.client, &sev, atomic, 0);
+}
+
+static int instr_begin(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&list->lock, flags);
+ if (list->owner >= 0 && list->owner != ev->source.client) {
+ spin_unlock_irqrestore(&list->lock, flags);
+ return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, -EBUSY, atomic);
+ }
+ list->owner = ev->source.client;
+ spin_unlock_irqrestore(&list->lock, flags);
+ return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, 0, atomic);
+}
+
+static int instr_end(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ unsigned long flags;
+
+ /* TODO: timeout handling */
+ spin_lock_irqsave(&list->lock, flags);
+ if (list->owner == ev->source.client) {
+ list->owner = -1;
+ spin_unlock_irqrestore(&list->lock, flags);
+ return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, 0, atomic);
+ }
+ spin_unlock_irqrestore(&list->lock, flags);
+ return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, -EINVAL, atomic);
+}
+
+static int instr_info(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_format_info(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_reset(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_status(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_put(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ unsigned long flags;
+ snd_seq_instr_header_t put;
+ snd_seq_kinstr_t *instr;
+ int result = -EINVAL, len, key;
+
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
+ goto __return;
+
+ if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
+ goto __return;
+ if (copy_from_user(&put, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
+ result = -EFAULT;
+ goto __return;
+ }
+ snd_instr_lock_ops(list);
+ if (put.id.instr.std & 0xff000000) { /* private instrument */
+ put.id.instr.std &= 0x00ffffff;
+ put.id.instr.std |= (unsigned int)ev->source.client << 24;
+ }
+ if ((instr = snd_seq_instr_find(list, &put.id.instr, 1, 0))) {
+ snd_seq_instr_free_use(list, instr);
+ snd_instr_unlock_ops(list);
+ result = -EBUSY;
+ goto __return;
+ }
+ ops = instr_ops(ops, put.data.data.format);
+ if (ops == NULL) {
+ snd_instr_unlock_ops(list);
+ goto __return;
+ }
+ len = ops->add_len;
+ if (put.data.type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)
+ len = sizeof(snd_seq_instr_t);
+ instr = snd_seq_instr_new(len, atomic);
+ if (instr == NULL) {
+ snd_instr_unlock_ops(list);
+ result = -ENOMEM;
+ goto __return;
+ }
+ instr->ops = ops;
+ instr->instr = put.id.instr;
+ strlcpy(instr->name, put.data.name, sizeof(instr->name));
+ instr->type = put.data.type;
+ if (instr->type == SNDRV_SEQ_INSTR_ATYPE_DATA) {
+ result = ops->put(ops->private_data,
+ instr,
+ (void __user *)ev->data.ext.ptr + sizeof(snd_seq_instr_header_t),
+ ev->data.ext.len - sizeof(snd_seq_instr_header_t),
+ atomic,
+ put.cmd);
+ if (result < 0) {
+ snd_seq_instr_free(instr, atomic);
+ snd_instr_unlock_ops(list);
+ goto __return;
+ }
+ }
+ key = compute_hash_instr_key(&instr->instr);
+ spin_lock_irqsave(&list->lock, flags);
+ instr->next = list->hash[key];
+ list->hash[key] = instr;
+ list->count++;
+ spin_unlock_irqrestore(&list->lock, flags);
+ snd_instr_unlock_ops(list);
+ result = 0;
+ __return:
+ instr_result(ev, SNDRV_SEQ_EVENT_INSTR_PUT, result, atomic);
+ return result;
+}
+
+static int instr_get(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_free(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ snd_seq_instr_header_t ifree;
+ snd_seq_kinstr_t *instr, *prev;
+ int result = -EINVAL;
+ unsigned long flags;
+ unsigned int hash;
+
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR)
+ goto __return;
+
+ if (ev->data.ext.len < sizeof(snd_seq_instr_header_t))
+ goto __return;
+ if (copy_from_user(&ifree, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) {
+ result = -EFAULT;
+ goto __return;
+ }
+ if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_ALL ||
+ ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE ||
+ ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER) {
+ result = snd_seq_instr_list_free_cond(list, &ifree, ev->dest.client, atomic);
+ goto __return;
+ }
+ if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_SINGLE) {
+ if (ifree.id.instr.std & 0xff000000) {
+ ifree.id.instr.std &= 0x00ffffff;
+ ifree.id.instr.std |= (unsigned int)ev->source.client << 24;
+ }
+ hash = compute_hash_instr_key(&ifree.id.instr);
+ snd_instr_lock_ops(list);
+ spin_lock_irqsave(&list->lock, flags);
+ instr = list->hash[hash];
+ prev = NULL;
+ while (instr) {
+ if (!compare_instr(&instr->instr, &ifree.id.instr, 1))
+ goto __free_single;
+ prev = instr;
+ instr = instr->next;
+ }
+ result = -ENOENT;
+ spin_unlock_irqrestore(&list->lock, flags);
+ snd_instr_unlock_ops(list);
+ goto __return;
+
+ __free_single:
+ if (prev) {
+ prev->next = instr->next;
+ } else {
+ list->hash[hash] = instr->next;
+ }
+ if (instr->ops && instr->ops->notify)
+ instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE);
+ while (instr->use) {
+ spin_unlock_irqrestore(&list->lock, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ spin_lock_irqsave(&list->lock, flags);
+ }
+ spin_unlock_irqrestore(&list->lock, flags);
+ result = snd_seq_instr_free(instr, atomic);
+ snd_instr_unlock_ops(list);
+ goto __return;
+ }
+
+ __return:
+ instr_result(ev, SNDRV_SEQ_EVENT_INSTR_FREE, result, atomic);
+ return result;
+}
+
+static int instr_list(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+static int instr_cluster(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int atomic, int hop)
+{
+ return -ENXIO;
+}
+
+int snd_seq_instr_event(snd_seq_kinstr_ops_t *ops,
+ snd_seq_kinstr_list_t *list,
+ snd_seq_event_t *ev,
+ int client,
+ int atomic,
+ int hop)
+{
+ int direct = 0;
+
+ snd_assert(ops != NULL && list != NULL && ev != NULL, return -EINVAL);
+ if (snd_seq_ev_is_direct(ev)) {
+ direct = 1;
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_INSTR_BEGIN:
+ return instr_begin(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_END:
+ return instr_end(ops, list, ev, atomic, hop);
+ }
+ }
+ if ((list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT) && !direct)
+ return -EINVAL;
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_INSTR_INFO:
+ return instr_info(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_FINFO:
+ return instr_format_info(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_RESET:
+ return instr_reset(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_STATUS:
+ return instr_status(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_PUT:
+ return instr_put(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_GET:
+ return instr_get(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_FREE:
+ return instr_free(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_LIST:
+ return instr_list(ops, list, ev, atomic, hop);
+ case SNDRV_SEQ_EVENT_INSTR_CLUSTER:
+ return instr_cluster(ops, list, ev, atomic, hop);
+ }
+ return -EINVAL;
+}
+
+/*
+ * Init part
+ */
+
+static int __init alsa_seq_instr_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_seq_instr_exit(void)
+{
+}
+
+module_init(alsa_seq_instr_init)
+module_exit(alsa_seq_instr_exit)
+
+EXPORT_SYMBOL(snd_seq_instr_list_new);
+EXPORT_SYMBOL(snd_seq_instr_list_free);
+EXPORT_SYMBOL(snd_seq_instr_list_free_cond);
+EXPORT_SYMBOL(snd_seq_instr_find);
+EXPORT_SYMBOL(snd_seq_instr_free_use);
+EXPORT_SYMBOL(snd_seq_instr_event);
diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c
new file mode 100644
index 00000000000..b09cee058fa
--- /dev/null
+++ b/sound/core/seq/seq_lock.c
@@ -0,0 +1,48 @@
+/*
+ * Do sleep inside a spin-lock
+ * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <sound/core.h>
+#include "seq_lock.h"
+
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+
+/* wait until all locks are released */
+void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line)
+{
+ int max_count = 5 * HZ;
+
+ if (atomic_read(lockp) < 0) {
+ printk(KERN_WARNING "seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line);
+ return;
+ }
+ while (atomic_read(lockp) > 0) {
+ if (max_count == 0) {
+ snd_printk(KERN_WARNING "seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line);
+ break;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ max_count--;
+ }
+}
+
+#endif
diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h
new file mode 100644
index 00000000000..54044bc2c9e
--- /dev/null
+++ b/sound/core/seq/seq_lock.h
@@ -0,0 +1,33 @@
+#ifndef __SND_SEQ_LOCK_H
+#define __SND_SEQ_LOCK_H
+
+#include <linux/sched.h>
+
+#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG)
+
+typedef atomic_t snd_use_lock_t;
+
+/* initialize lock */
+#define snd_use_lock_init(lockp) atomic_set(lockp, 0)
+
+/* increment lock */
+#define snd_use_lock_use(lockp) atomic_inc(lockp)
+
+/* release lock */
+#define snd_use_lock_free(lockp) atomic_dec(lockp)
+
+/* wait until all locks are released */
+void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line);
+#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__)
+
+#else /* SMP || CONFIG_SND_DEBUG */
+
+typedef spinlock_t snd_use_lock_t; /* dummy */
+#define snd_use_lock_init(lockp) /**/
+#define snd_use_lock_use(lockp) /**/
+#define snd_use_lock_free(lockp) /**/
+#define snd_use_lock_sync(lockp) /**/
+
+#endif /* SMP || CONFIG_SND_DEBUG */
+
+#endif /* __SND_SEQ_LOCK_H */
diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c
new file mode 100644
index 00000000000..00d841e82fb
--- /dev/null
+++ b/sound/core/seq/seq_memory.c
@@ -0,0 +1,510 @@
+/*
+ * ALSA sequencer Memory Manager
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ * Jaroslav Kysela <perex@suse.cz>
+ * 2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <sound/core.h>
+
+#include <sound/seq_kernel.h>
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_info.h"
+#include "seq_lock.h"
+
+/* semaphore in struct file record */
+#define semaphore_of(fp) ((fp)->f_dentry->d_inode->i_sem)
+
+
+inline static int snd_seq_pool_available(pool_t *pool)
+{
+ return pool->total_elements - atomic_read(&pool->counter);
+}
+
+inline static int snd_seq_output_ok(pool_t *pool)
+{
+ return snd_seq_pool_available(pool) >= pool->room;
+}
+
+/*
+ * Variable length event:
+ * The event like sysex uses variable length type.
+ * The external data may be stored in three different formats.
+ * 1) kernel space
+ * This is the normal case.
+ * ext.data.len = length
+ * ext.data.ptr = buffer pointer
+ * 2) user space
+ * When an event is generated via read(), the external data is
+ * kept in user space until expanded.
+ * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR
+ * ext.data.ptr = userspace pointer
+ * 3) chained cells
+ * When the variable length event is enqueued (in prioq or fifo),
+ * the external data is decomposed to several cells.
+ * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED
+ * ext.data.ptr = the additiona cell head
+ * -> cell.next -> cell.next -> ..
+ */
+
+/*
+ * exported:
+ * call dump function to expand external data.
+ */
+
+static int get_var_len(const snd_seq_event_t *event)
+{
+ if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+ return -EINVAL;
+
+ return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+}
+
+int snd_seq_dump_var_event(const snd_seq_event_t *event, snd_seq_dump_func_t func, void *private_data)
+{
+ int len, err;
+ snd_seq_event_cell_t *cell;
+
+ if ((len = get_var_len(event)) <= 0)
+ return len;
+
+ if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
+ char buf[32];
+ char __user *curptr = (char __user *)event->data.ext.ptr;
+ while (len > 0) {
+ int size = sizeof(buf);
+ if (len < size)
+ size = len;
+ if (copy_from_user(buf, curptr, size))
+ return -EFAULT;
+ err = func(private_data, buf, size);
+ if (err < 0)
+ return err;
+ curptr += size;
+ len -= size;
+ }
+ return 0;
+ } if (! (event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) {
+ return func(private_data, event->data.ext.ptr, len);
+ }
+
+ cell = (snd_seq_event_cell_t*)event->data.ext.ptr;
+ for (; len > 0 && cell; cell = cell->next) {
+ int size = sizeof(snd_seq_event_t);
+ if (len < size)
+ size = len;
+ err = func(private_data, &cell->event, size);
+ if (err < 0)
+ return err;
+ len -= size;
+ }
+ return 0;
+}
+
+
+/*
+ * exported:
+ * expand the variable length event to linear buffer space.
+ */
+
+static int seq_copy_in_kernel(char **bufptr, const void *src, int size)
+{
+ memcpy(*bufptr, src, size);
+ *bufptr += size;
+ return 0;
+}
+
+static int seq_copy_in_user(char __user **bufptr, const void *src, int size)
+{
+ if (copy_to_user(*bufptr, src, size))
+ return -EFAULT;
+ *bufptr += size;
+ return 0;
+}
+
+int snd_seq_expand_var_event(const snd_seq_event_t *event, int count, char *buf, int in_kernel, int size_aligned)
+{
+ int len, newlen;
+ int err;
+
+ if ((len = get_var_len(event)) < 0)
+ return len;
+ newlen = len;
+ if (size_aligned > 0)
+ newlen = ((len + size_aligned - 1) / size_aligned) * size_aligned;
+ if (count < newlen)
+ return -EAGAIN;
+
+ if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
+ if (! in_kernel)
+ return -EINVAL;
+ if (copy_from_user(buf, (void __user *)event->data.ext.ptr, len))
+ return -EFAULT;
+ return newlen;
+ }
+ err = snd_seq_dump_var_event(event,
+ in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel :
+ (snd_seq_dump_func_t)seq_copy_in_user,
+ &buf);
+ return err < 0 ? err : newlen;
+}
+
+
+/*
+ * release this cell, free extended data if available
+ */
+
+static inline void free_cell(pool_t *pool, snd_seq_event_cell_t *cell)
+{
+ cell->next = pool->free;
+ pool->free = cell;
+ atomic_dec(&pool->counter);
+}
+
+void snd_seq_cell_free(snd_seq_event_cell_t * cell)
+{
+ unsigned long flags;
+ pool_t *pool;
+
+ snd_assert(cell != NULL, return);
+ pool = cell->pool;
+ snd_assert(pool != NULL, return);
+
+ spin_lock_irqsave(&pool->lock, flags);
+ free_cell(pool, cell);
+ if (snd_seq_ev_is_variable(&cell->event)) {
+ if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) {
+ snd_seq_event_cell_t *curp, *nextptr;
+ curp = cell->event.data.ext.ptr;
+ for (; curp; curp = nextptr) {
+ nextptr = curp->next;
+ curp->next = pool->free;
+ free_cell(pool, curp);
+ }
+ }
+ }
+ if (waitqueue_active(&pool->output_sleep)) {
+ /* has enough space now? */
+ if (snd_seq_output_ok(pool))
+ wake_up(&pool->output_sleep);
+ }
+ spin_unlock_irqrestore(&pool->lock, flags);
+}
+
+
+/*
+ * allocate an event cell.
+ */
+static int snd_seq_cell_alloc(pool_t *pool, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
+{
+ snd_seq_event_cell_t *cell;
+ unsigned long flags;
+ int err = -EAGAIN;
+ wait_queue_t wait;
+
+ if (pool == NULL)
+ return -EINVAL;
+
+ *cellp = NULL;
+
+ init_waitqueue_entry(&wait, current);
+ spin_lock_irqsave(&pool->lock, flags);
+ if (pool->ptr == NULL) { /* not initialized */
+ snd_printd("seq: pool is not initialized\n");
+ err = -EINVAL;
+ goto __error;
+ }
+ while (pool->free == NULL && ! nonblock && ! pool->closing) {
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&pool->output_sleep, &wait);
+ spin_unlock_irq(&pool->lock);
+ schedule();
+ spin_lock_irq(&pool->lock);
+ remove_wait_queue(&pool->output_sleep, &wait);
+ /* interrupted? */
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ goto __error;
+ }
+ }
+ if (pool->closing) { /* closing.. */
+ err = -ENOMEM;
+ goto __error;
+ }
+
+ cell = pool->free;
+ if (cell) {
+ int used;
+ pool->free = cell->next;
+ atomic_inc(&pool->counter);
+ used = atomic_read(&pool->counter);
+ if (pool->max_used < used)
+ pool->max_used = used;
+ pool->event_alloc_success++;
+ /* clear cell pointers */
+ cell->next = NULL;
+ err = 0;
+ } else
+ pool->event_alloc_failures++;
+ *cellp = cell;
+
+__error:
+ spin_unlock_irqrestore(&pool->lock, flags);
+ return err;
+}
+
+
+/*
+ * duplicate the event to a cell.
+ * if the event has external data, the data is decomposed to additional
+ * cells.
+ */
+int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file)
+{
+ int ncells, err;
+ unsigned int extlen;
+ snd_seq_event_cell_t *cell;
+
+ *cellp = NULL;
+
+ ncells = 0;
+ extlen = 0;
+ if (snd_seq_ev_is_variable(event)) {
+ extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+ ncells = (extlen + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+ }
+ if (ncells >= pool->total_elements)
+ return -ENOMEM;
+
+ err = snd_seq_cell_alloc(pool, &cell, nonblock, file);
+ if (err < 0)
+ return err;
+
+ /* copy the event */
+ cell->event = *event;
+
+ /* decompose */
+ if (snd_seq_ev_is_variable(event)) {
+ int len = extlen;
+ int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED;
+ int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR;
+ snd_seq_event_cell_t *src, *tmp, *tail;
+ char *buf;
+
+ cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
+ cell->event.data.ext.ptr = NULL;
+
+ src = (snd_seq_event_cell_t*)event->data.ext.ptr;
+ buf = (char *)event->data.ext.ptr;
+ tail = NULL;
+
+ while (ncells-- > 0) {
+ int size = sizeof(snd_seq_event_t);
+ if (len < size)
+ size = len;
+ err = snd_seq_cell_alloc(pool, &tmp, nonblock, file);
+ if (err < 0)
+ goto __error;
+ if (cell->event.data.ext.ptr == NULL)
+ cell->event.data.ext.ptr = tmp;
+ if (tail)
+ tail->next = tmp;
+ tail = tmp;
+ /* copy chunk */
+ if (is_chained && src) {
+ tmp->event = src->event;
+ src = src->next;
+ } else if (is_usrptr) {
+ if (copy_from_user(&tmp->event, (char __user *)buf, size)) {
+ err = -EFAULT;
+ goto __error;
+ }
+ } else {
+ memcpy(&tmp->event, buf, size);
+ }
+ buf += size;
+ len -= size;
+ }
+ }
+
+ *cellp = cell;
+ return 0;
+
+__error:
+ snd_seq_cell_free(cell);
+ return err;
+}
+
+
+/* poll wait */
+int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait)
+{
+ poll_wait(file, &pool->output_sleep, wait);
+ return snd_seq_output_ok(pool);
+}
+
+
+/* allocate room specified number of events */
+int snd_seq_pool_init(pool_t *pool)
+{
+ int cell;
+ snd_seq_event_cell_t *cellptr;
+ unsigned long flags;
+
+ snd_assert(pool != NULL, return -EINVAL);
+ if (pool->ptr) /* should be atomic? */
+ return 0;
+
+ pool->ptr = vmalloc(sizeof(snd_seq_event_cell_t) * pool->size);
+ if (pool->ptr == NULL) {
+ snd_printd("seq: malloc for sequencer events failed\n");
+ return -ENOMEM;
+ }
+
+ /* add new cells to the free cell list */
+ spin_lock_irqsave(&pool->lock, flags);
+ pool->free = NULL;
+
+ for (cell = 0; cell < pool->size; cell++) {
+ cellptr = pool->ptr + cell;
+ cellptr->pool = pool;
+ cellptr->next = pool->free;
+ pool->free = cellptr;
+ }
+ pool->room = (pool->size + 1) / 2;
+
+ /* init statistics */
+ pool->max_used = 0;
+ pool->total_elements = pool->size;
+ spin_unlock_irqrestore(&pool->lock, flags);
+ return 0;
+}
+
+/* remove events */
+int snd_seq_pool_done(pool_t *pool)
+{
+ unsigned long flags;
+ snd_seq_event_cell_t *ptr;
+ int max_count = 5 * HZ;
+
+ snd_assert(pool != NULL, return -EINVAL);
+
+ /* wait for closing all threads */
+ spin_lock_irqsave(&pool->lock, flags);
+ pool->closing = 1;
+ spin_unlock_irqrestore(&pool->lock, flags);
+
+ if (waitqueue_active(&pool->output_sleep))
+ wake_up(&pool->output_sleep);
+
+ while (atomic_read(&pool->counter) > 0) {
+ if (max_count == 0) {
+ snd_printk(KERN_WARNING "snd_seq_pool_done timeout: %d cells remain\n", atomic_read(&pool->counter));
+ break;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ max_count--;
+ }
+
+ /* release all resources */
+ spin_lock_irqsave(&pool->lock, flags);
+ ptr = pool->ptr;
+ pool->ptr = NULL;
+ pool->free = NULL;
+ pool->total_elements = 0;
+ spin_unlock_irqrestore(&pool->lock, flags);
+
+ vfree(ptr);
+
+ spin_lock_irqsave(&pool->lock, flags);
+ pool->closing = 0;
+ spin_unlock_irqrestore(&pool->lock, flags);
+
+ return 0;
+}
+
+
+/* init new memory pool */
+pool_t *snd_seq_pool_new(int poolsize)
+{
+ pool_t *pool;
+
+ /* create pool block */
+ pool = kcalloc(1, sizeof(*pool), GFP_KERNEL);
+ if (pool == NULL) {
+ snd_printd("seq: malloc failed for pool\n");
+ return NULL;
+ }
+ spin_lock_init(&pool->lock);
+ pool->ptr = NULL;
+ pool->free = NULL;
+ pool->total_elements = 0;
+ atomic_set(&pool->counter, 0);
+ pool->closing = 0;
+ init_waitqueue_head(&pool->output_sleep);
+
+ pool->size = poolsize;
+
+ /* init statistics */
+ pool->max_used = 0;
+ return pool;
+}
+
+/* remove memory pool */
+int snd_seq_pool_delete(pool_t **ppool)
+{
+ pool_t *pool = *ppool;
+
+ *ppool = NULL;
+ if (pool == NULL)
+ return 0;
+ snd_seq_pool_done(pool);
+ kfree(pool);
+ return 0;
+}
+
+/* initialize sequencer memory */
+int __init snd_sequencer_memory_init(void)
+{
+ return 0;
+}
+
+/* release sequencer memory */
+void __exit snd_sequencer_memory_done(void)
+{
+}
+
+
+/* exported to seq_clientmgr.c */
+void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t *pool, char *space)
+{
+ if (pool == NULL)
+ return;
+ snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements);
+ snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter));
+ snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used);
+ snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success);
+ snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures);
+}
diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h
new file mode 100644
index 00000000000..6c4dde5d3d6
--- /dev/null
+++ b/sound/core/seq/seq_memory.h
@@ -0,0 +1,104 @@
+/*
+ * ALSA sequencer Memory Manager
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_MEMORYMGR_H
+#define __SND_SEQ_MEMORYMGR_H
+
+#include <sound/seq_kernel.h>
+#include <linux/poll.h>
+
+typedef struct pool pool_t;
+
+/* container for sequencer event (internal use) */
+typedef struct snd_seq_event_cell_t {
+ snd_seq_event_t event;
+ pool_t *pool; /* used pool */
+ struct snd_seq_event_cell_t *next; /* next cell */
+} snd_seq_event_cell_t;
+
+/* design note: the pool is a contigious block of memory, if we dynamicly
+ want to add additional cells to the pool be better store this in another
+ pool as we need to know the base address of the pool when releasing
+ memory. */
+
+struct pool {
+ snd_seq_event_cell_t *ptr; /* pointer to first event chunk */
+ snd_seq_event_cell_t *free; /* pointer to the head of the free list */
+
+ int total_elements; /* pool size actually allocated */
+ atomic_t counter; /* cells free */
+
+ int size; /* pool size to be allocated */
+ int room; /* watermark for sleep/wakeup */
+
+ int closing;
+
+ /* statistics */
+ int max_used;
+ int event_alloc_nopool;
+ int event_alloc_failures;
+ int event_alloc_success;
+
+ /* Write locking */
+ wait_queue_head_t output_sleep;
+
+ /* Pool lock */
+ spinlock_t lock;
+};
+
+extern void snd_seq_cell_free(snd_seq_event_cell_t* cell);
+
+int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file);
+
+/* return number of unused (free) cells */
+static inline int snd_seq_unused_cells(pool_t *pool)
+{
+ return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
+}
+
+/* return total number of allocated cells */
+static inline int snd_seq_total_cells(pool_t *pool)
+{
+ return pool ? pool->total_elements : 0;
+}
+
+/* init pool - allocate events */
+int snd_seq_pool_init(pool_t *pool);
+
+/* done pool - free events */
+int snd_seq_pool_done(pool_t *pool);
+
+/* create pool */
+pool_t *snd_seq_pool_new(int poolsize);
+
+/* remove pool */
+int snd_seq_pool_delete(pool_t **pool);
+
+/* init memory */
+int snd_sequencer_memory_init(void);
+
+/* release event memory */
+void snd_sequencer_memory_done(void);
+
+/* polling */
+int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait);
+
+
+#endif
diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c
new file mode 100644
index 00000000000..18247db45db
--- /dev/null
+++ b/sound/core/seq/seq_midi.c
@@ -0,0 +1,489 @@
+/*
+ * Generic MIDI synth driver for ALSA sequencer
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ * Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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
+ *
+ */
+
+/*
+Possible options for midisynth module:
+ - automatic opening of midi ports on first received event or subscription
+ (close will be performed when client leaves)
+*/
+
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/moduleparam.h>
+#include <asm/semaphore.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_device.h>
+#include <sound/seq_midi_event.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
+MODULE_LICENSE("GPL");
+static int output_buffer_size = PAGE_SIZE;
+module_param(output_buffer_size, int, 0644);
+MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
+static int input_buffer_size = PAGE_SIZE;
+module_param(input_buffer_size, int, 0644);
+MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
+
+/* data for this midi synth driver */
+typedef struct {
+ snd_card_t *card;
+ int device;
+ int subdevice;
+ snd_rawmidi_file_t input_rfile;
+ snd_rawmidi_file_t output_rfile;
+ int seq_client;
+ int seq_port;
+ snd_midi_event_t *parser;
+} seq_midisynth_t;
+
+typedef struct {
+ int seq_client;
+ int num_ports;
+ int ports_per_device[SNDRV_RAWMIDI_DEVICES];
+ seq_midisynth_t *ports[SNDRV_RAWMIDI_DEVICES];
+} seq_midisynth_client_t;
+
+static seq_midisynth_client_t *synths[SNDRV_CARDS];
+static DECLARE_MUTEX(register_mutex);
+
+/* handle rawmidi input event (MIDI v1.0 stream) */
+static void snd_midi_input_event(snd_rawmidi_substream_t * substream)
+{
+ snd_rawmidi_runtime_t *runtime;
+ seq_midisynth_t *msynth;
+ snd_seq_event_t ev;
+ char buf[16], *pbuf;
+ long res, count;
+
+ if (substream == NULL)
+ return;
+ runtime = substream->runtime;
+ msynth = (seq_midisynth_t *) runtime->private_data;
+ if (msynth == NULL)
+ return;
+ memset(&ev, 0, sizeof(ev));
+ while (runtime->avail > 0) {
+ res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
+ if (res <= 0)
+ continue;
+ if (msynth->parser == NULL)
+ continue;
+ pbuf = buf;
+ while (res > 0) {
+ count = snd_midi_event_encode(msynth->parser, pbuf, res, &ev);
+ if (count < 0)
+ break;
+ pbuf += count;
+ res -= count;
+ if (ev.type != SNDRV_SEQ_EVENT_NONE) {
+ ev.source.port = msynth->seq_port;
+ ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
+ /* clear event and reset header */
+ memset(&ev, 0, sizeof(ev));
+ }
+ }
+ }
+}
+
+static int dump_midi(snd_rawmidi_substream_t *substream, const char *buf, int count)
+{
+ snd_rawmidi_runtime_t *runtime;
+ int tmp;
+
+ snd_assert(substream != NULL || buf != NULL, return -EINVAL);
+ runtime = substream->runtime;
+ if ((tmp = runtime->avail) < count) {
+ snd_printd("warning, output event was lost (count = %i, available = %i)\n", count, tmp);
+ return -ENOMEM;
+ }
+ if (snd_rawmidi_kernel_write(substream, buf, count) < count)
+ return -EINVAL;
+ return 0;
+}
+
+static int event_process_midi(snd_seq_event_t * ev, int direct,
+ void *private_data, int atomic, int hop)
+{
+ seq_midisynth_t *msynth = (seq_midisynth_t *) private_data;
+ unsigned char msg[10]; /* buffer for constructing midi messages */
+ snd_rawmidi_substream_t *substream;
+ int res;
+
+ snd_assert(msynth != NULL, return -EINVAL);
+ substream = msynth->output_rfile.output;
+ if (substream == NULL)
+ return -ENODEV;
+ if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
+ /* invalid event */
+ snd_printd("seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
+ return 0;
+ }
+ res = snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
+ snd_midi_event_reset_decode(msynth->parser);
+ if (res < 0)
+ return res;
+ } else {
+ if (msynth->parser == NULL)
+ return -EIO;
+ res = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
+ if (res < 0)
+ return res;
+ if ((res = dump_midi(substream, msg, res)) < 0) {
+ snd_midi_event_reset_decode(msynth->parser);
+ return res;
+ }
+ }
+ return 0;
+}
+
+
+static int snd_seq_midisynth_new(seq_midisynth_t *msynth,
+ snd_card_t *card,
+ int device,
+ int subdevice)
+{
+ if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
+ return -ENOMEM;
+ msynth->card = card;
+ msynth->device = device;
+ msynth->subdevice = subdevice;
+ return 0;
+}
+
+/* open associated midi device for input */
+static int midisynth_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ int err;
+ seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+ snd_rawmidi_runtime_t *runtime;
+ snd_rawmidi_params_t params;
+
+ /* open midi port */
+ if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile)) < 0) {
+ snd_printd("midi input open failed!!!\n");
+ return err;
+ }
+ runtime = msynth->input_rfile.input->runtime;
+ memset(&params, 0, sizeof(params));
+ params.avail_min = 1;
+ params.buffer_size = input_buffer_size;
+ if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, &params)) < 0) {
+ snd_rawmidi_kernel_release(&msynth->input_rfile);
+ return err;
+ }
+ snd_midi_event_reset_encode(msynth->parser);
+ runtime->event = snd_midi_input_event;
+ runtime->private_data = msynth;
+ snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
+ return 0;
+}
+
+/* close associated midi device for input */
+static int midisynth_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ int err;
+ seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+
+ snd_assert(msynth->input_rfile.input != NULL, return -EINVAL);
+ err = snd_rawmidi_kernel_release(&msynth->input_rfile);
+ return err;
+}
+
+/* open associated midi device for output */
+static int midisynth_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ int err;
+ seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+ snd_rawmidi_params_t params;
+
+ /* open midi port */
+ if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile)) < 0) {
+ snd_printd("midi output open failed!!!\n");
+ return err;
+ }
+ memset(&params, 0, sizeof(params));
+ params.avail_min = 1;
+ params.buffer_size = output_buffer_size;
+ if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, &params)) < 0) {
+ snd_rawmidi_kernel_release(&msynth->output_rfile);
+ return err;
+ }
+ snd_midi_event_reset_decode(msynth->parser);
+ return 0;
+}
+
+/* close associated midi device for output */
+static int midisynth_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ seq_midisynth_t *msynth = (seq_midisynth_t *)private_data;
+ unsigned char buf = 0xff; /* MIDI reset */
+
+ snd_assert(msynth->output_rfile.output != NULL, return -EINVAL);
+ /* sending single MIDI reset message to shut the device up */
+ snd_rawmidi_kernel_write(msynth->output_rfile.output, &buf, 1);
+ snd_rawmidi_drain_output(msynth->output_rfile.output);
+ return snd_rawmidi_kernel_release(&msynth->output_rfile);
+}
+
+/* delete given midi synth port */
+static void snd_seq_midisynth_delete(seq_midisynth_t *msynth)
+{
+ if (msynth == NULL)
+ return;
+
+ if (msynth->seq_client > 0) {
+ /* delete port */
+ snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
+ }
+
+ if (msynth->parser)
+ snd_midi_event_free(msynth->parser);
+}
+
+/* set our client name */
+static int set_client_name(seq_midisynth_client_t *client, snd_card_t *card,
+ snd_rawmidi_info_t *rmidi)
+{
+ snd_seq_client_info_t cinfo;
+ const char *name;
+
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.client = client->seq_client;
+ cinfo.type = KERNEL_CLIENT;
+ name = rmidi->name[0] ? (const char *)rmidi->name : "External MIDI";
+ strlcpy(cinfo.name, name, sizeof(cinfo.name));
+ return snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+}
+
+/* register new midi synth port */
+static int
+snd_seq_midisynth_register_port(snd_seq_device_t *dev)
+{
+ seq_midisynth_client_t *client;
+ seq_midisynth_t *msynth, *ms;
+ snd_seq_port_info_t *port;
+ snd_rawmidi_info_t *info;
+ int newclient = 0;
+ unsigned int p, ports;
+ snd_seq_client_callback_t callbacks;
+ snd_seq_port_callback_t pcallbacks;
+ snd_card_t *card = dev->card;
+ int device = dev->device;
+ unsigned int input_count = 0, output_count = 0;
+
+ snd_assert(card != NULL && device >= 0 && device < SNDRV_RAWMIDI_DEVICES, return -EINVAL);
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (! info)
+ return -ENOMEM;
+ info->device = device;
+ info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+ info->subdevice = 0;
+ if (snd_rawmidi_info_select(card, info) >= 0)
+ output_count = info->subdevices_count;
+ info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+ if (snd_rawmidi_info_select(card, info) >= 0) {
+ input_count = info->subdevices_count;
+ }
+ ports = output_count;
+ if (ports < input_count)
+ ports = input_count;
+ if (ports == 0) {
+ kfree(info);
+ return -ENODEV;
+ }
+ if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
+ ports = 256 / SNDRV_RAWMIDI_DEVICES;
+
+ down(&register_mutex);
+ client = synths[card->number];
+ if (client == NULL) {
+ newclient = 1;
+ client = kcalloc(1, sizeof(*client), GFP_KERNEL);
+ if (client == NULL) {
+ up(&register_mutex);
+ kfree(info);
+ return -ENOMEM;
+ }
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.private_data = client;
+ callbacks.allow_input = callbacks.allow_output = 1;
+ client->seq_client = snd_seq_create_kernel_client(card, 0, &callbacks);
+ if (client->seq_client < 0) {
+ kfree(client);
+ up(&register_mutex);
+ kfree(info);
+ return -ENOMEM;
+ }
+ set_client_name(client, card, info);
+ } else if (device == 0)
+ set_client_name(client, card, info); /* use the first device's name */
+
+ msynth = kcalloc(ports, sizeof(seq_midisynth_t), GFP_KERNEL);
+ port = kmalloc(sizeof(*port), GFP_KERNEL);
+ if (msynth == NULL || port == NULL)
+ goto __nomem;
+
+ for (p = 0; p < ports; p++) {
+ ms = &msynth[p];
+
+ if (snd_seq_midisynth_new(ms, card, device, p) < 0)
+ goto __nomem;
+
+ /* declare port */
+ memset(port, 0, sizeof(*port));
+ port->addr.client = client->seq_client;
+ port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+ memset(info, 0, sizeof(*info));
+ info->device = device;
+ if (p < output_count)
+ info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
+ else
+ info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
+ info->subdevice = p;
+ if (snd_rawmidi_info_select(card, info) >= 0)
+ strcpy(port->name, info->subname);
+ if (! port->name[0]) {
+ if (info->name[0]) {
+ if (ports > 1)
+ snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p);
+ else
+ snprintf(port->name, sizeof(port->name), "%s", info->name);
+ } else {
+ /* last resort */
+ if (ports > 1)
+ sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p);
+ else
+ sprintf(port->name, "MIDI %d-%d", card->number, device);
+ }
+ }
+ if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
+ port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+ if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
+ port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+ if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
+ info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
+ port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+ port->midi_channels = 16;
+ memset(&pcallbacks, 0, sizeof(pcallbacks));
+ pcallbacks.owner = THIS_MODULE;
+ pcallbacks.private_data = ms;
+ pcallbacks.subscribe = midisynth_subscribe;
+ pcallbacks.unsubscribe = midisynth_unsubscribe;
+ pcallbacks.use = midisynth_use;
+ pcallbacks.unuse = midisynth_unuse;
+ pcallbacks.event_input = event_process_midi;
+ port->kernel = &pcallbacks;
+ if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
+ goto __nomem;
+ ms->seq_client = client->seq_client;
+ ms->seq_port = port->addr.port;
+ }
+ client->ports_per_device[device] = ports;
+ client->ports[device] = msynth;
+ client->num_ports++;
+ if (newclient)
+ synths[card->number] = client;
+ up(&register_mutex);
+ return 0; /* success */
+
+ __nomem:
+ if (msynth != NULL) {
+ for (p = 0; p < ports; p++)
+ snd_seq_midisynth_delete(&msynth[p]);
+ kfree(msynth);
+ }
+ if (newclient) {
+ snd_seq_delete_kernel_client(client->seq_client);
+ kfree(client);
+ }
+ kfree(info);
+ kfree(port);
+ up(&register_mutex);
+ return -ENOMEM;
+}
+
+/* release midi synth port */
+static int
+snd_seq_midisynth_unregister_port(snd_seq_device_t *dev)
+{
+ seq_midisynth_client_t *client;
+ seq_midisynth_t *msynth;
+ snd_card_t *card = dev->card;
+ int device = dev->device, p, ports;
+
+ down(&register_mutex);
+ client = synths[card->number];
+ if (client == NULL || client->ports[device] == NULL) {
+ up(&register_mutex);
+ return -ENODEV;
+ }
+ ports = client->ports_per_device[device];
+ client->ports_per_device[device] = 0;
+ msynth = client->ports[device];
+ client->ports[device] = NULL;
+ snd_runtime_check(msynth != NULL || ports <= 0, goto __skip);
+ for (p = 0; p < ports; p++)
+ snd_seq_midisynth_delete(&msynth[p]);
+ kfree(msynth);
+ __skip:
+ client->num_ports--;
+ if (client->num_ports <= 0) {
+ snd_seq_delete_kernel_client(client->seq_client);
+ synths[card->number] = NULL;
+ kfree(client);
+ }
+ up(&register_mutex);
+ return 0;
+}
+
+
+static int __init alsa_seq_midi_init(void)
+{
+ static snd_seq_dev_ops_t ops = {
+ snd_seq_midisynth_register_port,
+ snd_seq_midisynth_unregister_port,
+ };
+ memset(&synths, 0, sizeof(synths));
+ snd_seq_autoload_lock();
+ snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0);
+ snd_seq_autoload_unlock();
+ return 0;
+}
+
+static void __exit alsa_seq_midi_exit(void)
+{
+ snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH);
+}
+
+module_init(alsa_seq_midi_init)
+module_exit(alsa_seq_midi_exit)
diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c
new file mode 100644
index 00000000000..35fe8a7e34b
--- /dev/null
+++ b/sound/core/seq/seq_midi_emul.c
@@ -0,0 +1,735 @@
+/*
+ * GM/GS/XG midi module.
+ *
+ * Copyright (C) 1999 Steve Ratcliffe
+ *
+ * Based on awe_wave.c by Takashi Iwai
+ *
+ * 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
+ *
+ */
+/*
+ * This module is used to keep track of the current midi state.
+ * It can be used for drivers that are required to emulate midi when
+ * the hardware doesn't.
+ *
+ * It was written for a AWE64 driver, but there should be no AWE specific
+ * code in here. If there is it should be reported as a bug.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_emul.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation.");
+MODULE_LICENSE("GPL");
+
+/* Prototypes for static functions */
+static void note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel);
+static void do_control(snd_midi_op_t *ops, void *private,
+ snd_midi_channel_set_t *chset, snd_midi_channel_t *chan,
+ int control, int value);
+static void rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
+static void nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset);
+static void sysex(snd_midi_op_t *ops, void *private, unsigned char *sysex, int len, snd_midi_channel_set_t *chset);
+static void all_sounds_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
+static void all_notes_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan);
+static void snd_midi_reset_controllers(snd_midi_channel_t *chan);
+static void reset_all_channels(snd_midi_channel_set_t *chset);
+
+
+/*
+ * Process an event in a driver independent way. This means dealing
+ * with RPN, NRPN, SysEx etc that are defined for common midi applications
+ * such as GM, GS and XG.
+ * There modes that this module will run in are:
+ * Generic MIDI - no interpretation at all, it will just save current values
+ * of controlers etc.
+ * GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN,
+ * SysEx will be interpreded as defined in General Midi.
+ * GS - You can use all gs_ prefixed elements of chan. Codes for GS will be
+ * interpreted.
+ * XG - You can use all xg_ prefixed elements of chan. Codes for XG will
+ * be interpreted.
+ */
+void
+snd_midi_process_event(snd_midi_op_t *ops,
+ snd_seq_event_t *ev, snd_midi_channel_set_t *chanset)
+{
+ snd_midi_channel_t *chan;
+ void *drv;
+ int dest_channel = 0;
+
+ if (ev == NULL || chanset == NULL) {
+ snd_printd("ev or chanbase NULL (snd_midi_process_event)\n");
+ return;
+ }
+ if (chanset->channels == NULL)
+ return;
+
+ if (snd_seq_ev_is_channel_type(ev)) {
+ dest_channel = ev->data.note.channel;
+ if (dest_channel >= chanset->max_channels) {
+ snd_printd("dest channel is %d, max is %d\n", dest_channel, chanset->max_channels);
+ return;
+ }
+ }
+
+ chan = chanset->channels + dest_channel;
+ drv = chanset->private_data;
+
+ /* EVENT_NOTE should be processed before queued */
+ if (ev->type == SNDRV_SEQ_EVENT_NOTE)
+ return;
+
+ /* Make sure that we don't have a note on that should really be
+ * a note off */
+ if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
+ ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
+
+ /* Make sure the note is within array range */
+ if (ev->type == SNDRV_SEQ_EVENT_NOTEON ||
+ ev->type == SNDRV_SEQ_EVENT_NOTEOFF ||
+ ev->type == SNDRV_SEQ_EVENT_KEYPRESS) {
+ if (ev->data.note.note >= 128)
+ return;
+ }
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEON:
+ if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) {
+ if (ops->note_off)
+ ops->note_off(drv, ev->data.note.note, 0, chan);
+ }
+ chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON;
+ if (ops->note_on)
+ ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan);
+ break;
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON))
+ break;
+ if (ops->note_off)
+ note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity);
+ break;
+ case SNDRV_SEQ_EVENT_KEYPRESS:
+ if (ops->key_press)
+ ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan);
+ break;
+ case SNDRV_SEQ_EVENT_CONTROLLER:
+ do_control(ops, drv, chanset, chan,
+ ev->data.control.param, ev->data.control.value);
+ break;
+ case SNDRV_SEQ_EVENT_PGMCHANGE:
+ chan->midi_program = ev->data.control.value;
+ break;
+ case SNDRV_SEQ_EVENT_PITCHBEND:
+ chan->midi_pitchbend = ev->data.control.value;
+ if (ops->control)
+ ops->control(drv, MIDI_CTL_PITCHBEND, chan);
+ break;
+ case SNDRV_SEQ_EVENT_CHANPRESS:
+ chan->midi_pressure = ev->data.control.value;
+ if (ops->control)
+ ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan);
+ break;
+ case SNDRV_SEQ_EVENT_CONTROL14:
+ /* Best guess is that this is any of the 14 bit controller values */
+ if (ev->data.control.param < 32) {
+ /* set low part first */
+ chan->control[ev->data.control.param + 32] =
+ ev->data.control.value & 0x7f;
+ do_control(ops, drv, chanset, chan,
+ ev->data.control.param,
+ ((ev->data.control.value>>7) & 0x7f));
+ } else
+ do_control(ops, drv, chanset, chan,
+ ev->data.control.param,
+ ev->data.control.value);
+ break;
+ case SNDRV_SEQ_EVENT_NONREGPARAM:
+ /* Break it back into its controler values */
+ chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
+ chan->control[MIDI_CTL_MSB_DATA_ENTRY]
+ = (ev->data.control.value >> 7) & 0x7f;
+ chan->control[MIDI_CTL_LSB_DATA_ENTRY]
+ = ev->data.control.value & 0x7f;
+ chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB]
+ = (ev->data.control.param >> 7) & 0x7f;
+ chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB]
+ = ev->data.control.param & 0x7f;
+ nrpn(ops, drv, chan, chanset);
+ break;
+ case SNDRV_SEQ_EVENT_REGPARAM:
+ /* Break it back into its controler values */
+ chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
+ chan->control[MIDI_CTL_MSB_DATA_ENTRY]
+ = (ev->data.control.value >> 7) & 0x7f;
+ chan->control[MIDI_CTL_LSB_DATA_ENTRY]
+ = ev->data.control.value & 0x7f;
+ chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB]
+ = (ev->data.control.param >> 7) & 0x7f;
+ chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]
+ = ev->data.control.param & 0x7f;
+ rpn(ops, drv, chan, chanset);
+ break;
+ case SNDRV_SEQ_EVENT_SYSEX:
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
+ unsigned char sysexbuf[64];
+ int len;
+ len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0);
+ if (len > 0)
+ sysex(ops, drv, sysexbuf, len, chanset);
+ }
+ break;
+ case SNDRV_SEQ_EVENT_SONGPOS:
+ case SNDRV_SEQ_EVENT_SONGSEL:
+ case SNDRV_SEQ_EVENT_CLOCK:
+ case SNDRV_SEQ_EVENT_START:
+ case SNDRV_SEQ_EVENT_CONTINUE:
+ case SNDRV_SEQ_EVENT_STOP:
+ case SNDRV_SEQ_EVENT_QFRAME:
+ case SNDRV_SEQ_EVENT_TEMPO:
+ case SNDRV_SEQ_EVENT_TIMESIGN:
+ case SNDRV_SEQ_EVENT_KEYSIGN:
+ goto not_yet;
+ case SNDRV_SEQ_EVENT_SENSING:
+ break;
+ case SNDRV_SEQ_EVENT_CLIENT_START:
+ case SNDRV_SEQ_EVENT_CLIENT_EXIT:
+ case SNDRV_SEQ_EVENT_CLIENT_CHANGE:
+ case SNDRV_SEQ_EVENT_PORT_START:
+ case SNDRV_SEQ_EVENT_PORT_EXIT:
+ case SNDRV_SEQ_EVENT_PORT_CHANGE:
+ case SNDRV_SEQ_EVENT_SAMPLE:
+ case SNDRV_SEQ_EVENT_SAMPLE_START:
+ case SNDRV_SEQ_EVENT_SAMPLE_STOP:
+ case SNDRV_SEQ_EVENT_SAMPLE_FREQ:
+ case SNDRV_SEQ_EVENT_SAMPLE_VOLUME:
+ case SNDRV_SEQ_EVENT_SAMPLE_LOOP:
+ case SNDRV_SEQ_EVENT_SAMPLE_POSITION:
+ case SNDRV_SEQ_EVENT_ECHO:
+ not_yet:
+ default:
+ /*snd_printd("Unimplemented event %d\n", ev->type);*/
+ break;
+ }
+}
+
+
+/*
+ * release note
+ */
+static void
+note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel)
+{
+ if (chan->gm_hold) {
+ /* Hold this note until pedal is turned off */
+ chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
+ } else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) {
+ /* Mark this note as release; it will be turned off when sostenuto
+ * is turned off */
+ chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
+ } else {
+ chan->note[note] = 0;
+ if (ops->note_off)
+ ops->note_off(drv, note, vel, chan);
+ }
+}
+
+/*
+ * Do all driver independent operations for this controler and pass
+ * events that need to take place immediately to the driver.
+ */
+static void
+do_control(snd_midi_op_t *ops, void *drv, snd_midi_channel_set_t *chset,
+ snd_midi_channel_t *chan, int control, int value)
+{
+ int i;
+
+ /* Switches */
+ if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) {
+ /* These are all switches; either off or on so set to 0 or 127 */
+ value = (value >= 64)? 127: 0;
+ }
+ chan->control[control] = value;
+
+ switch (control) {
+ case MIDI_CTL_SUSTAIN:
+ if (value == 0) {
+ /* Sustain has been released, turn off held notes */
+ for (i = 0; i < 128; i++) {
+ if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
+ chan->note[i] = SNDRV_MIDI_NOTE_OFF;
+ if (ops->note_off)
+ ops->note_off(drv, i, 0, chan);
+ }
+ }
+ }
+ break;
+ case MIDI_CTL_PORTAMENTO:
+ break;
+ case MIDI_CTL_SOSTENUTO:
+ if (value) {
+ /* Mark each note that is currently held down */
+ for (i = 0; i < 128; i++) {
+ if (chan->note[i] & SNDRV_MIDI_NOTE_ON)
+ chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO;
+ }
+ } else {
+ /* release all notes that were held */
+ for (i = 0; i < 128; i++) {
+ if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) {
+ chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO;
+ if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
+ chan->note[i] = SNDRV_MIDI_NOTE_OFF;
+ if (ops->note_off)
+ ops->note_off(drv, i, 0, chan);
+ }
+ }
+ }
+ }
+ break;
+ case MIDI_CTL_MSB_DATA_ENTRY:
+ chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0;
+ /* go through here */
+ case MIDI_CTL_LSB_DATA_ENTRY:
+ if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED)
+ rpn(ops, drv, chan, chset);
+ else
+ nrpn(ops, drv, chan, chset);
+ break;
+ case MIDI_CTL_REGIST_PARM_NUM_LSB:
+ case MIDI_CTL_REGIST_PARM_NUM_MSB:
+ chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
+ break;
+ case MIDI_CTL_NONREG_PARM_NUM_LSB:
+ case MIDI_CTL_NONREG_PARM_NUM_MSB:
+ chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
+ break;
+
+ case MIDI_CTL_ALL_SOUNDS_OFF:
+ all_sounds_off(ops, drv, chan);
+ break;
+
+ case MIDI_CTL_ALL_NOTES_OFF:
+ all_notes_off(ops, drv, chan);
+ break;
+
+ case MIDI_CTL_MSB_BANK:
+ if (chset->midi_mode == SNDRV_MIDI_MODE_XG) {
+ if (value == 127)
+ chan->drum_channel = 1;
+ else
+ chan->drum_channel = 0;
+ }
+ break;
+ case MIDI_CTL_LSB_BANK:
+ break;
+
+ case MIDI_CTL_RESET_CONTROLLERS:
+ snd_midi_reset_controllers(chan);
+ break;
+
+ case MIDI_CTL_SOFT_PEDAL:
+ case MIDI_CTL_LEGATO_FOOTSWITCH:
+ case MIDI_CTL_HOLD2:
+ case MIDI_CTL_SC1_SOUND_VARIATION:
+ case MIDI_CTL_SC2_TIMBRE:
+ case MIDI_CTL_SC3_RELEASE_TIME:
+ case MIDI_CTL_SC4_ATTACK_TIME:
+ case MIDI_CTL_SC5_BRIGHTNESS:
+ case MIDI_CTL_E1_REVERB_DEPTH:
+ case MIDI_CTL_E2_TREMOLO_DEPTH:
+ case MIDI_CTL_E3_CHORUS_DEPTH:
+ case MIDI_CTL_E4_DETUNE_DEPTH:
+ case MIDI_CTL_E5_PHASER_DEPTH:
+ goto notyet;
+ notyet:
+ default:
+ if (ops->control)
+ ops->control(drv, control, chan);
+ break;
+ }
+}
+
+
+/*
+ * initialize the MIDI status
+ */
+void
+snd_midi_channel_set_clear(snd_midi_channel_set_t *chset)
+{
+ int i;
+
+ chset->midi_mode = SNDRV_MIDI_MODE_GM;
+ chset->gs_master_volume = 127;
+
+ for (i = 0; i < chset->max_channels; i++) {
+ snd_midi_channel_t *chan = chset->channels + i;
+ memset(chan->note, 0, sizeof(chan->note));
+
+ chan->midi_aftertouch = 0;
+ chan->midi_pressure = 0;
+ chan->midi_program = 0;
+ chan->midi_pitchbend = 0;
+ snd_midi_reset_controllers(chan);
+ chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+ chan->gm_rpn_fine_tuning = 0;
+ chan->gm_rpn_coarse_tuning = 0;
+
+ if (i == 9)
+ chan->drum_channel = 1;
+ else
+ chan->drum_channel = 0;
+ }
+}
+
+/*
+ * Process a rpn message.
+ */
+static void
+rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
+ snd_midi_channel_set_t *chset)
+{
+ int type;
+ int val;
+
+ if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) {
+ type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) |
+ chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB];
+ val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
+ chan->control[MIDI_CTL_LSB_DATA_ENTRY];
+
+ switch (type) {
+ case 0x0000: /* Pitch bend sensitivity */
+ /* MSB only / 1 semitone per 128 */
+ chan->gm_rpn_pitch_bend_range = val;
+ break;
+
+ case 0x0001: /* fine tuning: */
+ /* MSB/LSB, 8192=center, 100/8192 cent step */
+ chan->gm_rpn_fine_tuning = val - 8192;
+ break;
+
+ case 0x0002: /* coarse tuning */
+ /* MSB only / 8192=center, 1 semitone per 128 */
+ chan->gm_rpn_coarse_tuning = val - 8192;
+ break;
+
+ case 0x7F7F: /* "lock-in" RPN */
+ /* ignored */
+ break;
+ }
+ }
+ /* should call nrpn or rpn callback here.. */
+}
+
+/*
+ * Process an nrpn message.
+ */
+static void
+nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan,
+ snd_midi_channel_set_t *chset)
+{
+ /* parse XG NRPNs here if possible */
+ if (ops->nrpn)
+ ops->nrpn(drv, chan, chset);
+}
+
+
+/*
+ * convert channel parameter in GS sysex
+ */
+static int
+get_channel(unsigned char cmd)
+{
+ int p = cmd & 0x0f;
+ if (p == 0)
+ p = 9;
+ else if (p < 10)
+ p--;
+ return p;
+}
+
+
+/*
+ * Process a sysex message.
+ */
+static void
+sysex(snd_midi_op_t *ops, void *private, unsigned char *buf, int len, snd_midi_channel_set_t *chset)
+{
+ /* GM on */
+ static unsigned char gm_on_macro[] = {
+ 0x7e,0x7f,0x09,0x01,
+ };
+ /* XG on */
+ static unsigned char xg_on_macro[] = {
+ 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,
+ };
+ /* GS prefix
+ * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off
+ * reverb mode: XX=0x01, YY=0x30, ZZ=0-7
+ * chorus mode: XX=0x01, YY=0x38, ZZ=0-7
+ * master vol: XX=0x00, YY=0x04, ZZ=0-127
+ */
+ static unsigned char gs_pfx_macro[] = {
+ 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/
+ };
+
+ int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED;
+
+ if (len <= 0 || buf[0] != 0xf0)
+ return;
+ /* skip first byte */
+ buf++;
+ len--;
+
+ /* GM on */
+ if (len >= (int)sizeof(gm_on_macro) &&
+ memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) {
+ if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
+ chset->midi_mode != SNDRV_MIDI_MODE_XG) {
+ chset->midi_mode = SNDRV_MIDI_MODE_GM;
+ reset_all_channels(chset);
+ parsed = SNDRV_MIDI_SYSEX_GM_ON;
+ }
+ }
+
+ /* GS macros */
+ else if (len >= 8 &&
+ memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) {
+ if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
+ chset->midi_mode != SNDRV_MIDI_MODE_XG)
+ chset->midi_mode = SNDRV_MIDI_MODE_GS;
+
+ if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) {
+ /* GS reset */
+ parsed = SNDRV_MIDI_SYSEX_GS_RESET;
+ reset_all_channels(chset);
+ }
+
+ else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) {
+ /* drum pattern */
+ int p = get_channel(buf[5]);
+ if (p < chset->max_channels) {
+ parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
+ if (buf[7])
+ chset->channels[p].drum_channel = 1;
+ else
+ chset->channels[p].drum_channel = 0;
+ }
+
+ } else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) {
+ /* program */
+ int p = get_channel(buf[5]);
+ if (p < chset->max_channels &&
+ ! chset->channels[p].drum_channel) {
+ parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
+ chset->channels[p].midi_program = buf[7];
+ }
+
+ } else if (buf[5] == 0x01 && buf[6] == 0x30) {
+ /* reverb mode */
+ parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE;
+ chset->gs_reverb_mode = buf[7];
+
+ } else if (buf[5] == 0x01 && buf[6] == 0x38) {
+ /* chorus mode */
+ parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE;
+ chset->gs_chorus_mode = buf[7];
+
+ } else if (buf[5] == 0x00 && buf[6] == 0x04) {
+ /* master volume */
+ parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME;
+ chset->gs_master_volume = buf[7];
+
+ }
+ }
+
+ /* XG on */
+ else if (len >= (int)sizeof(xg_on_macro) &&
+ memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) {
+ int i;
+ chset->midi_mode = SNDRV_MIDI_MODE_XG;
+ parsed = SNDRV_MIDI_SYSEX_XG_ON;
+ /* reset CC#0 for drums */
+ for (i = 0; i < chset->max_channels; i++) {
+ if (chset->channels[i].drum_channel)
+ chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127;
+ else
+ chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0;
+ }
+ }
+
+ if (ops->sysex)
+ ops->sysex(private, buf - 1, len + 1, parsed, chset);
+}
+
+/*
+ * all sound off
+ */
+static void
+all_sounds_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
+{
+ int n;
+
+ if (! ops->note_terminate)
+ return;
+ for (n = 0; n < 128; n++) {
+ if (chan->note[n]) {
+ ops->note_terminate(drv, n, chan);
+ chan->note[n] = 0;
+ }
+ }
+}
+
+/*
+ * all notes off
+ */
+static void
+all_notes_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan)
+{
+ int n;
+
+ if (! ops->note_off)
+ return;
+ for (n = 0; n < 128; n++) {
+ if (chan->note[n] == SNDRV_MIDI_NOTE_ON)
+ note_off(ops, drv, chan, n, 0);
+ }
+}
+
+/*
+ * Initialise a single midi channel control block.
+ */
+static void snd_midi_channel_init(snd_midi_channel_t *p, int n)
+{
+ if (p == NULL)
+ return;
+
+ memset(p, 0, sizeof(snd_midi_channel_t));
+ p->private = NULL;
+ p->number = n;
+
+ snd_midi_reset_controllers(p);
+ p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+ p->gm_rpn_fine_tuning = 0;
+ p->gm_rpn_coarse_tuning = 0;
+
+ if (n == 9)
+ p->drum_channel = 1; /* Default ch 10 as drums */
+}
+
+/*
+ * Allocate and initialise a set of midi channel control blocks.
+ */
+static snd_midi_channel_t *snd_midi_channel_init_set(int n)
+{
+ snd_midi_channel_t *chan;
+ int i;
+
+ chan = kmalloc(n * sizeof(snd_midi_channel_t), GFP_KERNEL);
+ if (chan) {
+ for (i = 0; i < n; i++)
+ snd_midi_channel_init(chan+i, i);
+ }
+
+ return chan;
+}
+
+/*
+ * reset all midi channels
+ */
+static void
+reset_all_channels(snd_midi_channel_set_t *chset)
+{
+ int ch;
+ for (ch = 0; ch < chset->max_channels; ch++) {
+ snd_midi_channel_t *chan = chset->channels + ch;
+ snd_midi_reset_controllers(chan);
+ chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
+ chan->gm_rpn_fine_tuning = 0;
+ chan->gm_rpn_coarse_tuning = 0;
+
+ if (ch == 9)
+ chan->drum_channel = 1;
+ else
+ chan->drum_channel = 0;
+ }
+}
+
+
+/*
+ * Allocate and initialise a midi channel set.
+ */
+snd_midi_channel_set_t *snd_midi_channel_alloc_set(int n)
+{
+ snd_midi_channel_set_t *chset;
+
+ chset = kmalloc(sizeof(*chset), GFP_KERNEL);
+ if (chset) {
+ chset->channels = snd_midi_channel_init_set(n);
+ chset->private_data = NULL;
+ chset->max_channels = n;
+ }
+ return chset;
+}
+
+/*
+ * Reset the midi controllers on a particular channel to default values.
+ */
+static void snd_midi_reset_controllers(snd_midi_channel_t *chan)
+{
+ memset(chan->control, 0, sizeof(chan->control));
+ chan->gm_volume = 127;
+ chan->gm_expression = 127;
+ chan->gm_pan = 64;
+}
+
+
+/*
+ * Free a midi channel set.
+ */
+void snd_midi_channel_free_set(snd_midi_channel_set_t *chset)
+{
+ if (chset == NULL)
+ return;
+ kfree(chset->channels);
+ kfree(chset);
+}
+
+static int __init alsa_seq_midi_emul_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_seq_midi_emul_exit(void)
+{
+}
+
+module_init(alsa_seq_midi_emul_init)
+module_exit(alsa_seq_midi_emul_exit)
+
+EXPORT_SYMBOL(snd_midi_process_event);
+EXPORT_SYMBOL(snd_midi_channel_set_clear);
+EXPORT_SYMBOL(snd_midi_channel_alloc_set);
+EXPORT_SYMBOL(snd_midi_channel_free_set);
diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c
new file mode 100644
index 00000000000..21e569062bc
--- /dev/null
+++ b/sound/core/seq/seq_midi_event.c
@@ -0,0 +1,539 @@
+/*
+ * MIDI byte <-> sequencer event coder
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>,
+ * Jaroslav Kysela <perex@suse.cz>
+ *
+ * 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 <sound/driver.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_event.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
+MODULE_LICENSE("GPL");
+
+/* queue type */
+/* from 0 to 7 are normal commands (note off, on, etc.) */
+#define ST_NOTEOFF 0
+#define ST_NOTEON 1
+#define ST_SPECIAL 8
+#define ST_SYSEX ST_SPECIAL
+/* from 8 to 15 are events for 0xf0-0xf7 */
+
+
+/* status event types */
+typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev);
+typedef void (*event_decode_t)(snd_seq_event_t *ev, unsigned char *buf);
+
+/*
+ * prototypes
+ */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void note_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf);
+static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf);
+
+/*
+ * event list
+ */
+static struct status_event_list_t {
+ int event;
+ int qlen;
+ event_encode_t encode;
+ event_decode_t decode;
+} status_event[] = {
+ /* 0x80 - 0xf0 */
+ {SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode},
+ {SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode},
+ {SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode},
+ {SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode},
+ {SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode},
+ {SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode},
+ {SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode},
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf0 */
+ /* 0xf0 - 0xff */
+ {SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */
+ {SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */
+ {SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */
+ {SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf4 */
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf5 */
+ {SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf7 */
+ {SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf9 */
+ {SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */
+ {SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */
+ {SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */
+ {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xfd */
+ {SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */
+ {SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */
+};
+
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev);
+
+static struct extra_event_list_t {
+ int event;
+ int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev);
+} extra_event[] = {
+ {SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14},
+ {SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn},
+ {SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn},
+};
+
+/*
+ * new/delete record
+ */
+
+int snd_midi_event_new(int bufsize, snd_midi_event_t **rdev)
+{
+ snd_midi_event_t *dev;
+
+ *rdev = NULL;
+ dev = kcalloc(1, sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+ if (bufsize > 0) {
+ dev->buf = kmalloc(bufsize, GFP_KERNEL);
+ if (dev->buf == NULL) {
+ kfree(dev);
+ return -ENOMEM;
+ }
+ }
+ dev->bufsize = bufsize;
+ dev->lastcmd = 0xff;
+ spin_lock_init(&dev->lock);
+ *rdev = dev;
+ return 0;
+}
+
+void snd_midi_event_free(snd_midi_event_t *dev)
+{
+ if (dev != NULL) {
+ kfree(dev->buf);
+ kfree(dev);
+ }
+}
+
+/*
+ * initialize record
+ */
+inline static void reset_encode(snd_midi_event_t *dev)
+{
+ dev->read = 0;
+ dev->qlen = 0;
+ dev->type = 0;
+}
+
+void snd_midi_event_reset_encode(snd_midi_event_t *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ reset_encode(dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+void snd_midi_event_reset_decode(snd_midi_event_t *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->lastcmd = 0xff;
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+void snd_midi_event_init(snd_midi_event_t *dev)
+{
+ snd_midi_event_reset_encode(dev);
+ snd_midi_event_reset_decode(dev);
+}
+
+void snd_midi_event_no_status(snd_midi_event_t *dev, int on)
+{
+ dev->nostat = on ? 1 : 0;
+}
+
+/*
+ * resize buffer
+ */
+int snd_midi_event_resize_buffer(snd_midi_event_t *dev, int bufsize)
+{
+ unsigned char *new_buf, *old_buf;
+ unsigned long flags;
+
+ if (bufsize == dev->bufsize)
+ return 0;
+ new_buf = kmalloc(bufsize, GFP_KERNEL);
+ if (new_buf == NULL)
+ return -ENOMEM;
+ spin_lock_irqsave(&dev->lock, flags);
+ old_buf = dev->buf;
+ dev->buf = new_buf;
+ dev->bufsize = bufsize;
+ reset_encode(dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ kfree(old_buf);
+ return 0;
+}
+
+/*
+ * read bytes and encode to sequencer event if finished
+ * return the size of encoded bytes
+ */
+long snd_midi_event_encode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
+{
+ long result = 0;
+ int rc;
+
+ ev->type = SNDRV_SEQ_EVENT_NONE;
+
+ while (count-- > 0) {
+ rc = snd_midi_event_encode_byte(dev, *buf++, ev);
+ result++;
+ if (rc < 0)
+ return rc;
+ else if (rc > 0)
+ return result;
+ }
+
+ return result;
+}
+
+/*
+ * read one byte and encode to sequencer event:
+ * return 1 if MIDI bytes are encoded to an event
+ * 0 data is not finished
+ * negative for error
+ */
+int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ c &= 0xff;
+
+ if (c >= MIDI_CMD_COMMON_CLOCK) {
+ /* real-time event */
+ ev->type = status_event[ST_SPECIAL + c - 0xf0].event;
+ ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+ ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ return 1;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->qlen > 0) {
+ /* rest of command */
+ dev->buf[dev->read++] = c;
+ if (dev->type != ST_SYSEX)
+ dev->qlen--;
+ } else {
+ /* new command */
+ dev->read = 1;
+ if (c & 0x80) {
+ dev->buf[0] = c;
+ if ((c & 0xf0) == 0xf0) /* special events */
+ dev->type = (c & 0x0f) + ST_SPECIAL;
+ else
+ dev->type = (c >> 4) & 0x07;
+ dev->qlen = status_event[dev->type].qlen;
+ } else {
+ /* process this byte as argument */
+ dev->buf[dev->read++] = c;
+ dev->qlen = status_event[dev->type].qlen - 1;
+ }
+ }
+ if (dev->qlen == 0) {
+ ev->type = status_event[dev->type].event;
+ ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+ ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ if (status_event[dev->type].encode) /* set data values */
+ status_event[dev->type].encode(dev, ev);
+ rc = 1;
+ } else if (dev->type == ST_SYSEX) {
+ if (c == MIDI_CMD_COMMON_SYSEX_END ||
+ dev->read >= dev->bufsize) {
+ ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+ ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+ ev->type = SNDRV_SEQ_EVENT_SYSEX;
+ ev->data.ext.len = dev->read;
+ ev->data.ext.ptr = dev->buf;
+ if (c != MIDI_CMD_COMMON_SYSEX_END)
+ dev->read = 0; /* continue to parse */
+ else
+ reset_encode(dev); /* all parsed */
+ rc = 1;
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return rc;
+}
+
+/* encode note event */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.note.channel = dev->buf[0] & 0x0f;
+ ev->data.note.note = dev->buf[1];
+ ev->data.note.velocity = dev->buf[2];
+}
+
+/* encode one parameter controls */
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.control.channel = dev->buf[0] & 0x0f;
+ ev->data.control.value = dev->buf[1];
+}
+
+/* encode pitch wheel change */
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.control.channel = dev->buf[0] & 0x0f;
+ ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192;
+}
+
+/* encode midi control change */
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.control.channel = dev->buf[0] & 0x0f;
+ ev->data.control.param = dev->buf[1];
+ ev->data.control.value = dev->buf[2];
+}
+
+/* encode one parameter value*/
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.control.value = dev->buf[1];
+}
+
+/* encode song position */
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+ ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1];
+}
+
+/*
+ * decode from a sequencer event to midi bytes
+ * return the size of decoded midi events
+ */
+long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev)
+{
+ unsigned int cmd, type;
+
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ return -ENOENT;
+
+ for (type = 0; type < ARRAY_SIZE(status_event); type++) {
+ if (ev->type == status_event[type].event)
+ goto __found;
+ }
+ for (type = 0; type < ARRAY_SIZE(extra_event); type++) {
+ if (ev->type == extra_event[type].event)
+ return extra_event[type].decode(dev, buf, count, ev);
+ }
+ return -ENOENT;
+
+ __found:
+ if (type >= ST_SPECIAL)
+ cmd = 0xf0 + (type - ST_SPECIAL);
+ else
+ /* data.note.channel and data.control.channel is identical */
+ cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f);
+
+
+ if (cmd == MIDI_CMD_COMMON_SYSEX) {
+ snd_midi_event_reset_decode(dev);
+ return snd_seq_expand_var_event(ev, count, buf, 1, 0);
+ } else {
+ int qlen;
+ unsigned char xbuf[4];
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) {
+ dev->lastcmd = cmd;
+ spin_unlock_irqrestore(&dev->lock, flags);
+ xbuf[0] = cmd;
+ if (status_event[type].decode)
+ status_event[type].decode(ev, xbuf + 1);
+ qlen = status_event[type].qlen + 1;
+ } else {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ if (status_event[type].decode)
+ status_event[type].decode(ev, xbuf + 0);
+ qlen = status_event[type].qlen;
+ }
+ if (count < qlen)
+ return -ENOMEM;
+ memcpy(buf, xbuf, qlen);
+ return qlen;
+ }
+}
+
+
+/* decode note event */
+static void note_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+ buf[0] = ev->data.note.note & 0x7f;
+ buf[1] = ev->data.note.velocity & 0x7f;
+}
+
+/* decode one parameter controls */
+static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+ buf[0] = ev->data.control.value & 0x7f;
+}
+
+/* decode pitch wheel change */
+static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+ int value = ev->data.control.value + 8192;
+ buf[0] = value & 0x7f;
+ buf[1] = (value >> 7) & 0x7f;
+}
+
+/* decode midi control change */
+static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+ buf[0] = ev->data.control.param & 0x7f;
+ buf[1] = ev->data.control.value & 0x7f;
+}
+
+/* decode song position */
+static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf)
+{
+ buf[0] = ev->data.control.value & 0x7f;
+ buf[1] = (ev->data.control.value >> 7) & 0x7f;
+}
+
+/* decode 14bit control */
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
+{
+ unsigned char cmd;
+ int idx = 0;
+
+ cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+ if (ev->data.control.param < 0x20) {
+ if (count < 4)
+ return -ENOMEM;
+ if (dev->nostat && count < 6)
+ return -ENOMEM;
+ if (cmd != dev->lastcmd || dev->nostat) {
+ if (count < 5)
+ return -ENOMEM;
+ buf[idx++] = dev->lastcmd = cmd;
+ }
+ buf[idx++] = ev->data.control.param;
+ buf[idx++] = (ev->data.control.value >> 7) & 0x7f;
+ if (dev->nostat)
+ buf[idx++] = cmd;
+ buf[idx++] = ev->data.control.param + 0x20;
+ buf[idx++] = ev->data.control.value & 0x7f;
+ } else {
+ if (count < 2)
+ return -ENOMEM;
+ if (cmd != dev->lastcmd || dev->nostat) {
+ if (count < 3)
+ return -ENOMEM;
+ buf[idx++] = dev->lastcmd = cmd;
+ }
+ buf[idx++] = ev->data.control.param & 0x7f;
+ buf[idx++] = ev->data.control.value & 0x7f;
+ }
+ return idx;
+}
+
+/* decode reg/nonreg param */
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev)
+{
+ unsigned char cmd;
+ char *cbytes;
+ static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB,
+ MIDI_CTL_NONREG_PARM_NUM_LSB,
+ MIDI_CTL_MSB_DATA_ENTRY,
+ MIDI_CTL_LSB_DATA_ENTRY };
+ static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB,
+ MIDI_CTL_REGIST_PARM_NUM_LSB,
+ MIDI_CTL_MSB_DATA_ENTRY,
+ MIDI_CTL_LSB_DATA_ENTRY };
+ unsigned char bytes[4];
+ int idx = 0, i;
+
+ if (count < 8)
+ return -ENOMEM;
+ if (dev->nostat && count < 12)
+ return -ENOMEM;
+ cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+ bytes[0] = ev->data.control.param & 0x007f;
+ bytes[1] = (ev->data.control.param & 0x3f80) >> 7;
+ bytes[2] = ev->data.control.value & 0x007f;
+ bytes[3] = (ev->data.control.value & 0x3f80) >> 7;
+ if (cmd != dev->lastcmd && !dev->nostat) {
+ if (count < 9)
+ return -ENOMEM;
+ buf[idx++] = dev->lastcmd = cmd;
+ }
+ cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn;
+ for (i = 0; i < 4; i++) {
+ if (dev->nostat)
+ buf[idx++] = dev->lastcmd = cmd;
+ buf[idx++] = cbytes[i];
+ buf[idx++] = bytes[i];
+ }
+ return idx;
+}
+
+/*
+ * exports
+ */
+
+EXPORT_SYMBOL(snd_midi_event_new);
+EXPORT_SYMBOL(snd_midi_event_free);
+EXPORT_SYMBOL(snd_midi_event_resize_buffer);
+EXPORT_SYMBOL(snd_midi_event_init);
+EXPORT_SYMBOL(snd_midi_event_reset_encode);
+EXPORT_SYMBOL(snd_midi_event_reset_decode);
+EXPORT_SYMBOL(snd_midi_event_no_status);
+EXPORT_SYMBOL(snd_midi_event_encode);
+EXPORT_SYMBOL(snd_midi_event_encode_byte);
+EXPORT_SYMBOL(snd_midi_event_decode);
+
+static int __init alsa_seq_midi_event_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_seq_midi_event_exit(void)
+{
+}
+
+module_init(alsa_seq_midi_event_init)
+module_exit(alsa_seq_midi_event_exit)
diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c
new file mode 100644
index 00000000000..b976951fc10
--- /dev/null
+++ b/sound/core/seq/seq_ports.c
@@ -0,0 +1,674 @@
+/*
+ * ALSA sequencer Ports
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ * Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_system.h"
+#include "seq_ports.h"
+#include "seq_clientmgr.h"
+
+/*
+
+ registration of client ports
+
+ */
+
+
+/*
+
+NOTE: the current implementation of the port structure as a linked list is
+not optimal for clients that have many ports. For sending messages to all
+subscribers of a port we first need to find the address of the port
+structure, which means we have to traverse the list. A direct access table
+(array) would be better, but big preallocated arrays waste memory.
+
+Possible actions:
+
+1) leave it this way, a client does normaly does not have more than a few
+ports
+
+2) replace the linked list of ports by a array of pointers which is
+dynamicly kmalloced. When a port is added or deleted we can simply allocate
+a new array, copy the corresponding pointers, and delete the old one. We
+then only need a pointer to this array, and an integer that tells us how
+much elements are in array.
+
+*/
+
+/* return pointer to port structure - port is locked if found */
+client_port_t *snd_seq_port_use_ptr(client_t *client, int num)
+{
+ struct list_head *p;
+ client_port_t *port;
+
+ if (client == NULL)
+ return NULL;
+ read_lock(&client->ports_lock);
+ list_for_each(p, &client->ports_list_head) {
+ port = list_entry(p, client_port_t, list);
+ if (port->addr.port == num) {
+ if (port->closing)
+ break; /* deleting now */
+ snd_use_lock_use(&port->use_lock);
+ read_unlock(&client->ports_lock);
+ return port;
+ }
+ }
+ read_unlock(&client->ports_lock);
+ return NULL; /* not found */
+}
+
+
+/* search for the next port - port is locked if found */
+client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo)
+{
+ int num;
+ struct list_head *p;
+ client_port_t *port, *found;
+
+ num = pinfo->addr.port;
+ found = NULL;
+ read_lock(&client->ports_lock);
+ list_for_each(p, &client->ports_list_head) {
+ port = list_entry(p, client_port_t, list);
+ if (port->addr.port < num)
+ continue;
+ if (port->addr.port == num) {
+ found = port;
+ break;
+ }
+ if (found == NULL || port->addr.port < found->addr.port)
+ found = port;
+ }
+ if (found) {
+ if (found->closing)
+ found = NULL;
+ else
+ snd_use_lock_use(&found->use_lock);
+ }
+ read_unlock(&client->ports_lock);
+ return found;
+}
+
+
+/* initialize port_subs_info_t */
+static void port_subs_info_init(port_subs_info_t *grp)
+{
+ INIT_LIST_HEAD(&grp->list_head);
+ grp->count = 0;
+ grp->exclusive = 0;
+ rwlock_init(&grp->list_lock);
+ init_rwsem(&grp->list_mutex);
+ grp->open = NULL;
+ grp->close = NULL;
+}
+
+
+/* create a port, port number is returned (-1 on failure) */
+client_port_t *snd_seq_create_port(client_t *client, int port)
+{
+ unsigned long flags;
+ client_port_t *new_port;
+ struct list_head *l;
+ int num = -1;
+
+ /* sanity check */
+ snd_assert(client, return NULL);
+
+ if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) {
+ snd_printk(KERN_WARNING "too many ports for client %d\n", client->number);
+ return NULL;
+ }
+
+ /* create a new port */
+ new_port = kcalloc(1, sizeof(*new_port), GFP_KERNEL);
+ if (! new_port) {
+ snd_printd("malloc failed for registering client port\n");
+ return NULL; /* failure, out of memory */
+ }
+ /* init port data */
+ new_port->addr.client = client->number;
+ new_port->addr.port = -1;
+ new_port->owner = THIS_MODULE;
+ sprintf(new_port->name, "port-%d", num);
+ snd_use_lock_init(&new_port->use_lock);
+ port_subs_info_init(&new_port->c_src);
+ port_subs_info_init(&new_port->c_dest);
+
+ num = port >= 0 ? port : 0;
+ down(&client->ports_mutex);
+ write_lock_irqsave(&client->ports_lock, flags);
+ list_for_each(l, &client->ports_list_head) {
+ client_port_t *p = list_entry(l, client_port_t, list);
+ if (p->addr.port > num)
+ break;
+ if (port < 0) /* auto-probe mode */
+ num = p->addr.port + 1;
+ }
+ /* insert the new port */
+ list_add_tail(&new_port->list, l);
+ client->num_ports++;
+ new_port->addr.port = num; /* store the port number in the port */
+ write_unlock_irqrestore(&client->ports_lock, flags);
+ up(&client->ports_mutex);
+ sprintf(new_port->name, "port-%d", num);
+
+ return new_port;
+}
+
+/* */
+enum group_type_t {
+ SRC_LIST, DEST_LIST
+};
+
+static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
+static int unsubscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack);
+
+
+static client_port_t *get_client_port(snd_seq_addr_t *addr, client_t **cp)
+{
+ client_port_t *p;
+ *cp = snd_seq_client_use_ptr(addr->client);
+ if (*cp) {
+ p = snd_seq_port_use_ptr(*cp, addr->port);
+ if (! p) {
+ snd_seq_client_unlock(*cp);
+ *cp = NULL;
+ }
+ return p;
+ }
+ return NULL;
+}
+
+/*
+ * remove all subscribers on the list
+ * this is called from port_delete, for each src and dest list.
+ */
+static void clear_subscriber_list(client_t *client, client_port_t *port,
+ port_subs_info_t *grp, int grptype)
+{
+ struct list_head *p, *n;
+
+ down_write(&grp->list_mutex);
+ list_for_each_safe(p, n, &grp->list_head) {
+ subscribers_t *subs;
+ client_t *c;
+ client_port_t *aport;
+
+ if (grptype == SRC_LIST) {
+ subs = list_entry(p, subscribers_t, src_list);
+ aport = get_client_port(&subs->info.dest, &c);
+ } else {
+ subs = list_entry(p, subscribers_t, dest_list);
+ aport = get_client_port(&subs->info.sender, &c);
+ }
+ list_del(p);
+ unsubscribe_port(client, port, grp, &subs->info, 0);
+ if (!aport) {
+ /* looks like the connected port is being deleted.
+ * we decrease the counter, and when both ports are deleted
+ * remove the subscriber info
+ */
+ if (atomic_dec_and_test(&subs->ref_count))
+ kfree(subs);
+ } else {
+ /* ok we got the connected port */
+ port_subs_info_t *agrp;
+ agrp = (grptype == SRC_LIST) ? &aport->c_dest : &aport->c_src;
+ down_write(&agrp->list_mutex);
+ if (grptype == SRC_LIST)
+ list_del(&subs->dest_list);
+ else
+ list_del(&subs->src_list);
+ unsubscribe_port(c, aport, agrp, &subs->info, 1);
+ kfree(subs);
+ up_write(&agrp->list_mutex);
+ snd_seq_port_unlock(aport);
+ snd_seq_client_unlock(c);
+ }
+ }
+ up_write(&grp->list_mutex);
+}
+
+/* delete port data */
+static int port_delete(client_t *client, client_port_t *port)
+{
+ /* set closing flag and wait for all port access are gone */
+ port->closing = 1;
+ snd_use_lock_sync(&port->use_lock);
+
+ /* clear subscribers info */
+ clear_subscriber_list(client, port, &port->c_src, SRC_LIST);
+ clear_subscriber_list(client, port, &port->c_dest, DEST_LIST);
+
+ if (port->private_free)
+ port->private_free(port->private_data);
+
+ snd_assert(port->c_src.count == 0,);
+ snd_assert(port->c_dest.count == 0,);
+
+ kfree(port);
+ return 0;
+}
+
+
+/* delete a port with the given port id */
+int snd_seq_delete_port(client_t *client, int port)
+{
+ unsigned long flags;
+ struct list_head *l;
+ client_port_t *found = NULL;
+
+ down(&client->ports_mutex);
+ write_lock_irqsave(&client->ports_lock, flags);
+ list_for_each(l, &client->ports_list_head) {
+ client_port_t *p = list_entry(l, client_port_t, list);
+ if (p->addr.port == port) {
+ /* ok found. delete from the list at first */
+ list_del(l);
+ client->num_ports--;
+ found = p;
+ break;
+ }
+ }
+ write_unlock_irqrestore(&client->ports_lock, flags);
+ up(&client->ports_mutex);
+ if (found)
+ return port_delete(client, found);
+ else
+ return -ENOENT;
+}
+
+/* delete the all ports belonging to the given client */
+int snd_seq_delete_all_ports(client_t *client)
+{
+ unsigned long flags;
+ struct list_head deleted_list, *p, *n;
+
+ /* move the port list to deleted_list, and
+ * clear the port list in the client data.
+ */
+ down(&client->ports_mutex);
+ write_lock_irqsave(&client->ports_lock, flags);
+ if (! list_empty(&client->ports_list_head)) {
+ __list_add(&deleted_list,
+ client->ports_list_head.prev,
+ client->ports_list_head.next);
+ INIT_LIST_HEAD(&client->ports_list_head);
+ } else {
+ INIT_LIST_HEAD(&deleted_list);
+ }
+ client->num_ports = 0;
+ write_unlock_irqrestore(&client->ports_lock, flags);
+
+ /* remove each port in deleted_list */
+ list_for_each_safe(p, n, &deleted_list) {
+ client_port_t *port = list_entry(p, client_port_t, list);
+ list_del(p);
+ snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
+ port_delete(client, port);
+ }
+ up(&client->ports_mutex);
+ return 0;
+}
+
+/* set port info fields */
+int snd_seq_set_port_info(client_port_t * port, snd_seq_port_info_t * info)
+{
+ snd_assert(port && info, return -EINVAL);
+
+ /* set port name */
+ if (info->name[0])
+ strlcpy(port->name, info->name, sizeof(port->name));
+
+ /* set capabilities */
+ port->capability = info->capability;
+
+ /* get port type */
+ port->type = info->type;
+
+ /* information about supported channels/voices */
+ port->midi_channels = info->midi_channels;
+ port->midi_voices = info->midi_voices;
+ port->synth_voices = info->synth_voices;
+
+ /* timestamping */
+ port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
+ port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
+ port->time_queue = info->time_queue;
+
+ return 0;
+}
+
+/* get port info fields */
+int snd_seq_get_port_info(client_port_t * port, snd_seq_port_info_t * info)
+{
+ snd_assert(port && info, return -EINVAL);
+
+ /* get port name */
+ strlcpy(info->name, port->name, sizeof(info->name));
+
+ /* get capabilities */
+ info->capability = port->capability;
+
+ /* get port type */
+ info->type = port->type;
+
+ /* information about supported channels/voices */
+ info->midi_channels = port->midi_channels;
+ info->midi_voices = port->midi_voices;
+ info->synth_voices = port->synth_voices;
+
+ /* get subscriber counts */
+ info->read_use = port->c_src.count;
+ info->write_use = port->c_dest.count;
+
+ /* timestamping */
+ info->flags = 0;
+ if (port->timestamping) {
+ info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
+ if (port->time_real)
+ info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
+ info->time_queue = port->time_queue;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * call callback functions (if any):
+ * the callbacks are invoked only when the first (for connection) or
+ * the last subscription (for disconnection) is done. Second or later
+ * subscription results in increment of counter, but no callback is
+ * invoked.
+ * This feature is useful if these callbacks are associated with
+ * initialization or termination of devices (see seq_midi.c).
+ *
+ * If callback_all option is set, the callback function is invoked
+ * at each connnection/disconnection.
+ */
+
+static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp,
+ snd_seq_port_subscribe_t *info, int send_ack)
+{
+ int err = 0;
+
+ if (!try_module_get(port->owner))
+ return -EFAULT;
+ grp->count++;
+ if (grp->open && (port->callback_all || grp->count == 1)) {
+ err = grp->open(port->private_data, info);
+ if (err < 0) {
+ module_put(port->owner);
+ grp->count--;
+ }
+ }
+ if (err >= 0 && send_ack && client->type == USER_CLIENT)
+ snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
+ info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
+
+ return err;
+}
+
+static int unsubscribe_port(client_t *client, client_port_t *port,
+ port_subs_info_t *grp,
+ snd_seq_port_subscribe_t *info, int send_ack)
+{
+ int err = 0;
+
+ if (! grp->count)
+ return -EINVAL;
+ grp->count--;
+ if (grp->close && (port->callback_all || grp->count == 0))
+ err = grp->close(port->private_data, info);
+ if (send_ack && client->type == USER_CLIENT)
+ snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
+ info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
+ module_put(port->owner);
+ return err;
+}
+
+
+
+/* check if both addresses are identical */
+static inline int addr_match(snd_seq_addr_t *r, snd_seq_addr_t *s)
+{
+ return (r->client == s->client) && (r->port == s->port);
+}
+
+/* check the two subscribe info match */
+/* if flags is zero, checks only sender and destination addresses */
+static int match_subs_info(snd_seq_port_subscribe_t *r,
+ snd_seq_port_subscribe_t *s)
+{
+ if (addr_match(&r->sender, &s->sender) &&
+ addr_match(&r->dest, &s->dest)) {
+ if (r->flags && r->flags == s->flags)
+ return r->queue == s->queue;
+ else if (! r->flags)
+ return 1;
+ }
+ return 0;
+}
+
+
+/* connect two ports */
+int snd_seq_port_connect(client_t *connector,
+ client_t *src_client, client_port_t *src_port,
+ client_t *dest_client, client_port_t *dest_port,
+ snd_seq_port_subscribe_t *info)
+{
+ port_subs_info_t *src = &src_port->c_src;
+ port_subs_info_t *dest = &dest_port->c_dest;
+ subscribers_t *subs;
+ struct list_head *p;
+ int err, src_called = 0;
+ unsigned long flags;
+ int exclusive;
+
+ subs = kcalloc(1, sizeof(*subs), GFP_KERNEL);
+ if (! subs)
+ return -ENOMEM;
+
+ subs->info = *info;
+ atomic_set(&subs->ref_count, 2);
+
+ down_write(&src->list_mutex);
+ down_write(&dest->list_mutex);
+
+ exclusive = info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE ? 1 : 0;
+ err = -EBUSY;
+ if (exclusive) {
+ if (! list_empty(&src->list_head) || ! list_empty(&dest->list_head))
+ goto __error;
+ } else {
+ if (src->exclusive || dest->exclusive)
+ goto __error;
+ /* check whether already exists */
+ list_for_each(p, &src->list_head) {
+ subscribers_t *s = list_entry(p, subscribers_t, src_list);
+ if (match_subs_info(info, &s->info))
+ goto __error;
+ }
+ list_for_each(p, &dest->list_head) {
+ subscribers_t *s = list_entry(p, subscribers_t, dest_list);
+ if (match_subs_info(info, &s->info))
+ goto __error;
+ }
+ }
+
+ if ((err = subscribe_port(src_client, src_port, src, info,
+ connector->number != src_client->number)) < 0)
+ goto __error;
+ src_called = 1;
+
+ if ((err = subscribe_port(dest_client, dest_port, dest, info,
+ connector->number != dest_client->number)) < 0)
+ goto __error;
+
+ /* add to list */
+ write_lock_irqsave(&src->list_lock, flags);
+ // write_lock(&dest->list_lock); // no other lock yet
+ list_add_tail(&subs->src_list, &src->list_head);
+ list_add_tail(&subs->dest_list, &dest->list_head);
+ // write_unlock(&dest->list_lock); // no other lock yet
+ write_unlock_irqrestore(&src->list_lock, flags);
+
+ src->exclusive = dest->exclusive = exclusive;
+
+ up_write(&dest->list_mutex);
+ up_write(&src->list_mutex);
+ return 0;
+
+ __error:
+ if (src_called)
+ unsubscribe_port(src_client, src_port, src, info,
+ connector->number != src_client->number);
+ kfree(subs);
+ up_write(&dest->list_mutex);
+ up_write(&src->list_mutex);
+ return err;
+}
+
+
+/* remove the connection */
+int snd_seq_port_disconnect(client_t *connector,
+ client_t *src_client, client_port_t *src_port,
+ client_t *dest_client, client_port_t *dest_port,
+ snd_seq_port_subscribe_t *info)
+{
+ port_subs_info_t *src = &src_port->c_src;
+ port_subs_info_t *dest = &dest_port->c_dest;
+ subscribers_t *subs;
+ struct list_head *p;
+ int err = -ENOENT;
+ unsigned long flags;
+
+ down_write(&src->list_mutex);
+ down_write(&dest->list_mutex);
+
+ /* look for the connection */
+ list_for_each(p, &src->list_head) {
+ subs = list_entry(p, subscribers_t, src_list);
+ if (match_subs_info(info, &subs->info)) {
+ write_lock_irqsave(&src->list_lock, flags);
+ // write_lock(&dest->list_lock); // no lock yet
+ list_del(&subs->src_list);
+ list_del(&subs->dest_list);
+ // write_unlock(&dest->list_lock);
+ write_unlock_irqrestore(&src->list_lock, flags);
+ src->exclusive = dest->exclusive = 0;
+ unsubscribe_port(src_client, src_port, src, info,
+ connector->number != src_client->number);
+ unsubscribe_port(dest_client, dest_port, dest, info,
+ connector->number != dest_client->number);
+ kfree(subs);
+ err = 0;
+ break;
+ }
+ }
+
+ up_write(&dest->list_mutex);
+ up_write(&src->list_mutex);
+ return err;
+}
+
+
+/* get matched subscriber */
+subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp,
+ snd_seq_addr_t *dest_addr)
+{
+ struct list_head *p;
+ subscribers_t *s, *found = NULL;
+
+ down_read(&src_grp->list_mutex);
+ list_for_each(p, &src_grp->list_head) {
+ s = list_entry(p, subscribers_t, src_list);
+ if (addr_match(dest_addr, &s->info.dest)) {
+ found = s;
+ break;
+ }
+ }
+ up_read(&src_grp->list_mutex);
+ return found;
+}
+
+/*
+ * Attach a device driver that wants to receive events from the
+ * sequencer. Returns the new port number on success.
+ * A driver that wants to receive the events converted to midi, will
+ * use snd_seq_midisynth_register_port().
+ */
+/* exported */
+int snd_seq_event_port_attach(int client,
+ snd_seq_port_callback_t *pcbp,
+ int cap, int type, int midi_channels,
+ int midi_voices, char *portname)
+{
+ snd_seq_port_info_t portinfo;
+ int ret;
+
+ /* Set up the port */
+ memset(&portinfo, 0, sizeof(portinfo));
+ portinfo.addr.client = client;
+ strlcpy(portinfo.name, portname ? portname : "Unamed port",
+ sizeof(portinfo.name));
+
+ portinfo.capability = cap;
+ portinfo.type = type;
+ portinfo.kernel = pcbp;
+ portinfo.midi_channels = midi_channels;
+ portinfo.midi_voices = midi_voices;
+
+ /* Create it */
+ ret = snd_seq_kernel_client_ctl(client,
+ SNDRV_SEQ_IOCTL_CREATE_PORT,
+ &portinfo);
+
+ if (ret >= 0)
+ ret = portinfo.addr.port;
+
+ return ret;
+}
+
+
+/*
+ * Detach the driver from a port.
+ */
+/* exported */
+int snd_seq_event_port_detach(int client, int port)
+{
+ snd_seq_port_info_t portinfo;
+ int err;
+
+ memset(&portinfo, 0, sizeof(portinfo));
+ portinfo.addr.client = client;
+ portinfo.addr.port = port;
+ err = snd_seq_kernel_client_ctl(client,
+ SNDRV_SEQ_IOCTL_DELETE_PORT,
+ &portinfo);
+
+ return err;
+}
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
new file mode 100644
index 00000000000..89fd4416f6f
--- /dev/null
+++ b/sound/core/seq/seq_ports.h
@@ -0,0 +1,128 @@
+/*
+ * ALSA sequencer Ports
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_PORTS_H
+#define __SND_SEQ_PORTS_H
+
+#include <sound/seq_kernel.h>
+#include "seq_lock.h"
+
+/* list of 'exported' ports */
+
+/* Client ports that are not exported are still accessible, but are
+ anonymous ports.
+
+ If a port supports SUBSCRIPTION, that port can send events to all
+ subscribersto a special address, with address
+ (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all
+ recipients that are registered in the subscription list. A typical
+ application for these SUBSCRIPTION events is handling of incoming MIDI
+ data. The port doesn't 'know' what other clients are interested in this
+ message. If for instance a MIDI recording application would like to receive
+ the events from that port, it will first have to subscribe with that port.
+
+*/
+
+typedef struct subscribers_t {
+ snd_seq_port_subscribe_t info; /* additional info */
+ struct list_head src_list; /* link of sources */
+ struct list_head dest_list; /* link of destinations */
+ atomic_t ref_count;
+} subscribers_t;
+
+typedef struct port_subs_info_t {
+ struct list_head list_head; /* list of subscribed ports */
+ unsigned int count; /* count of subscribers */
+ unsigned int exclusive: 1; /* exclusive mode */
+ struct rw_semaphore list_mutex;
+ rwlock_t list_lock;
+ snd_seq_kernel_port_open_t *open;
+ snd_seq_kernel_port_close_t *close;
+} port_subs_info_t;
+
+typedef struct client_port_t {
+
+ snd_seq_addr_t addr; /* client/port number */
+ struct module *owner; /* owner of this port */
+ char name[64]; /* port name */
+ struct list_head list; /* port list */
+ snd_use_lock_t use_lock;
+
+ /* subscribers */
+ port_subs_info_t c_src; /* read (sender) list */
+ port_subs_info_t c_dest; /* write (dest) list */
+
+ snd_seq_kernel_port_input_t *event_input;
+ snd_seq_kernel_port_private_free_t *private_free;
+ void *private_data;
+ unsigned int callback_all : 1;
+ unsigned int closing : 1;
+ unsigned int timestamping: 1;
+ unsigned int time_real: 1;
+ int time_queue;
+
+ /* capability, inport, output, sync */
+ unsigned int capability; /* port capability bits */
+ unsigned int type; /* port type bits */
+
+ /* supported channels */
+ int midi_channels;
+ int midi_voices;
+ int synth_voices;
+
+} client_port_t;
+
+/* return pointer to port structure and lock port */
+client_port_t *snd_seq_port_use_ptr(client_t *client, int num);
+
+/* search for next port - port is locked if found */
+client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo);
+
+/* unlock the port */
+#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock)
+
+/* create a port, port number is returned (-1 on failure) */
+client_port_t *snd_seq_create_port(client_t *client, int port_index);
+
+/* delete a port */
+int snd_seq_delete_port(client_t *client, int port);
+
+/* delete all ports */
+int snd_seq_delete_all_ports(client_t *client);
+
+/* set port info fields */
+int snd_seq_set_port_info(client_port_t *port, snd_seq_port_info_t *info);
+
+/* get port info fields */
+int snd_seq_get_port_info(client_port_t *port, snd_seq_port_info_t *info);
+
+/* add subscriber to subscription list */
+int snd_seq_port_connect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
+
+/* remove subscriber from subscription list */
+int snd_seq_port_disconnect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info);
+
+/* subscribe port */
+int snd_seq_port_subscribe(client_port_t *port, snd_seq_port_subscribe_t *info);
+
+/* get matched subscriber */
+subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp, snd_seq_addr_t *dest_addr);
+
+#endif
diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c
new file mode 100644
index 00000000000..a519732ed83
--- /dev/null
+++ b/sound/core/seq/seq_prioq.c
@@ -0,0 +1,449 @@
+/*
+ * ALSA sequencer Priority Queue
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "seq_timer.h"
+#include "seq_prioq.h"
+
+
+/* Implementation is a simple linked list for now...
+
+ This priority queue orders the events on timestamp. For events with an
+ equeal timestamp the queue behaves as a FIFO.
+
+ *
+ * +-------+
+ * Head --> | first |
+ * +-------+
+ * |next
+ * +-----v-+
+ * | |
+ * +-------+
+ * |
+ * +-----v-+
+ * | |
+ * +-------+
+ * |
+ * +-----v-+
+ * Tail --> | last |
+ * +-------+
+ *
+
+ */
+
+
+
+/* create new prioq (constructor) */
+prioq_t *snd_seq_prioq_new(void)
+{
+ prioq_t *f;
+
+ f = kcalloc(1, sizeof(*f), GFP_KERNEL);
+ if (f == NULL) {
+ snd_printd("oops: malloc failed for snd_seq_prioq_new()\n");
+ return NULL;
+ }
+
+ spin_lock_init(&f->lock);
+ f->head = NULL;
+ f->tail = NULL;
+ f->cells = 0;
+
+ return f;
+}
+
+/* delete prioq (destructor) */
+void snd_seq_prioq_delete(prioq_t **fifo)
+{
+ prioq_t *f = *fifo;
+ *fifo = NULL;
+
+ if (f == NULL) {
+ snd_printd("oops: snd_seq_prioq_delete() called with NULL prioq\n");
+ return;
+ }
+
+ /* release resources...*/
+ /*....................*/
+
+ if (f->cells > 0) {
+ /* drain prioQ */
+ while (f->cells > 0)
+ snd_seq_cell_free(snd_seq_prioq_cell_out(f));
+ }
+
+ kfree(f);
+}
+
+
+
+
+/* compare timestamp between events */
+/* return 1 if a >= b; 0 */
+static inline int compare_timestamp(snd_seq_event_t * a, snd_seq_event_t * b)
+{
+ if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
+ /* compare ticks */
+ return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick));
+ } else {
+ /* compare real time */
+ return (snd_seq_compare_real_time(&a->time.time, &b->time.time));
+ }
+}
+
+/* compare timestamp between events */
+/* return negative if a < b;
+ * zero if a = b;
+ * positive if a > b;
+ */
+static inline int compare_timestamp_rel(snd_seq_event_t *a, snd_seq_event_t *b)
+{
+ if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
+ /* compare ticks */
+ if (a->time.tick > b->time.tick)
+ return 1;
+ else if (a->time.tick == b->time.tick)
+ return 0;
+ else
+ return -1;
+ } else {
+ /* compare real time */
+ if (a->time.time.tv_sec > b->time.time.tv_sec)
+ return 1;
+ else if (a->time.time.tv_sec == b->time.time.tv_sec) {
+ if (a->time.time.tv_nsec > b->time.time.tv_nsec)
+ return 1;
+ else if (a->time.time.tv_nsec == b->time.time.tv_nsec)
+ return 0;
+ else
+ return -1;
+ } else
+ return -1;
+ }
+}
+
+/* enqueue cell to prioq */
+int snd_seq_prioq_cell_in(prioq_t * f, snd_seq_event_cell_t * cell)
+{
+ snd_seq_event_cell_t *cur, *prev;
+ unsigned long flags;
+ int count;
+ int prior;
+
+ snd_assert(f, return -EINVAL);
+ snd_assert(cell, return -EINVAL);
+
+ /* check flags */
+ prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK);
+
+ spin_lock_irqsave(&f->lock, flags);
+
+ /* check if this element needs to inserted at the end (ie. ordered
+ data is inserted) This will be very likeley if a sequencer
+ application or midi file player is feeding us (sequential) data */
+ if (f->tail && !prior) {
+ if (compare_timestamp(&cell->event, &f->tail->event)) {
+ /* add new cell to tail of the fifo */
+ f->tail->next = cell;
+ f->tail = cell;
+ cell->next = NULL;
+ f->cells++;
+ spin_unlock_irqrestore(&f->lock, flags);
+ return 0;
+ }
+ }
+ /* traverse list of elements to find the place where the new cell is
+ to be inserted... Note that this is a order n process ! */
+
+ prev = NULL; /* previous cell */
+ cur = f->head; /* cursor */
+
+ count = 10000; /* FIXME: enough big, isn't it? */
+ while (cur != NULL) {
+ /* compare timestamps */
+ int rel = compare_timestamp_rel(&cell->event, &cur->event);
+ if (rel < 0)
+ /* new cell has earlier schedule time, */
+ break;
+ else if (rel == 0 && prior)
+ /* equal schedule time and prior to others */
+ break;
+ /* new cell has equal or larger schedule time, */
+ /* move cursor to next cell */
+ prev = cur;
+ cur = cur->next;
+ if (! --count) {
+ spin_unlock_irqrestore(&f->lock, flags);
+ snd_printk(KERN_ERR "cannot find a pointer.. infinite loop?\n");
+ return -EINVAL;
+ }
+ }
+
+ /* insert it before cursor */
+ if (prev != NULL)
+ prev->next = cell;
+ cell->next = cur;
+
+ if (f->head == cur) /* this is the first cell, set head to it */
+ f->head = cell;
+ if (cur == NULL) /* reached end of the list */
+ f->tail = cell;
+ f->cells++;
+ spin_unlock_irqrestore(&f->lock, flags);
+ return 0;
+}
+
+/* dequeue cell from prioq */
+snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t * f)
+{
+ snd_seq_event_cell_t *cell;
+ unsigned long flags;
+
+ if (f == NULL) {
+ snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+ return NULL;
+ }
+ spin_lock_irqsave(&f->lock, flags);
+
+ cell = f->head;
+ if (cell) {
+ f->head = cell->next;
+
+ /* reset tail if this was the last element */
+ if (f->tail == cell)
+ f->tail = NULL;
+
+ cell->next = NULL;
+ f->cells--;
+ }
+
+ spin_unlock_irqrestore(&f->lock, flags);
+ return cell;
+}
+
+/* return number of events available in prioq */
+int snd_seq_prioq_avail(prioq_t * f)
+{
+ if (f == NULL) {
+ snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+ return 0;
+ }
+ return f->cells;
+}
+
+
+/* peek at cell at the head of the prioq */
+snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t * f)
+{
+ if (f == NULL) {
+ snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n");
+ return NULL;
+ }
+ return f->head;
+}
+
+
+static inline int prioq_match(snd_seq_event_cell_t *cell, int client, int timestamp)
+{
+ if (cell->event.source.client == client ||
+ cell->event.dest.client == client)
+ return 1;
+ if (!timestamp)
+ return 0;
+ switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+ case SNDRV_SEQ_TIME_STAMP_TICK:
+ if (cell->event.time.tick)
+ return 1;
+ break;
+ case SNDRV_SEQ_TIME_STAMP_REAL:
+ if (cell->event.time.time.tv_sec ||
+ cell->event.time.time.tv_nsec)
+ return 1;
+ break;
+ }
+ return 0;
+}
+
+/* remove cells for left client */
+void snd_seq_prioq_leave(prioq_t * f, int client, int timestamp)
+{
+ register snd_seq_event_cell_t *cell, *next;
+ unsigned long flags;
+ snd_seq_event_cell_t *prev = NULL;
+ snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
+
+ /* collect all removed cells */
+ spin_lock_irqsave(&f->lock, flags);
+ cell = f->head;
+ while (cell) {
+ next = cell->next;
+ if (prioq_match(cell, client, timestamp)) {
+ /* remove cell from prioq */
+ if (cell == f->head) {
+ f->head = cell->next;
+ } else {
+ prev->next = cell->next;
+ }
+ if (cell == f->tail)
+ f->tail = cell->next;
+ f->cells--;
+ /* add cell to free list */
+ cell->next = NULL;
+ if (freefirst == NULL) {
+ freefirst = cell;
+ } else {
+ freeprev->next = cell;
+ }
+ freeprev = cell;
+ } else {
+#if 0
+ printk("type = %i, source = %i, dest = %i, client = %i\n",
+ cell->event.type,
+ cell->event.source.client,
+ cell->event.dest.client,
+ client);
+#endif
+ prev = cell;
+ }
+ cell = next;
+ }
+ spin_unlock_irqrestore(&f->lock, flags);
+
+ /* remove selected cells */
+ while (freefirst) {
+ freenext = freefirst->next;
+ snd_seq_cell_free(freefirst);
+ freefirst = freenext;
+ }
+}
+
+static int prioq_remove_match(snd_seq_remove_events_t *info,
+ snd_seq_event_t *ev)
+{
+ int res;
+
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) {
+ if (ev->dest.client != info->dest.client ||
+ ev->dest.port != info->dest.port)
+ return 0;
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) {
+ if (! snd_seq_ev_is_channel_type(ev))
+ return 0;
+ /* data.note.channel and data.control.channel are identical */
+ if (ev->data.note.channel != info->channel)
+ return 0;
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) {
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+ res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+ else
+ res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+ if (!res)
+ return 0;
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) {
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+ res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+ else
+ res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+ if (res)
+ return 0;
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) {
+ if (ev->type != info->type)
+ return 0;
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) {
+ /* Do not remove off events */
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ /* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */
+ return 0;
+ default:
+ break;
+ }
+ }
+ if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) {
+ if (info->tag != ev->tag)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* remove cells matching remove criteria */
+void snd_seq_prioq_remove_events(prioq_t * f, int client,
+ snd_seq_remove_events_t *info)
+{
+ register snd_seq_event_cell_t *cell, *next;
+ unsigned long flags;
+ snd_seq_event_cell_t *prev = NULL;
+ snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext;
+
+ /* collect all removed cells */
+ spin_lock_irqsave(&f->lock, flags);
+ cell = f->head;
+
+ while (cell) {
+ next = cell->next;
+ if (cell->event.source.client == client &&
+ prioq_remove_match(info, &cell->event)) {
+
+ /* remove cell from prioq */
+ if (cell == f->head) {
+ f->head = cell->next;
+ } else {
+ prev->next = cell->next;
+ }
+
+ if (cell == f->tail)
+ f->tail = cell->next;
+ f->cells--;
+
+ /* add cell to free list */
+ cell->next = NULL;
+ if (freefirst == NULL) {
+ freefirst = cell;
+ } else {
+ freeprev->next = cell;
+ }
+
+ freeprev = cell;
+ } else {
+ prev = cell;
+ }
+ cell = next;
+ }
+ spin_unlock_irqrestore(&f->lock, flags);
+
+ /* remove selected cells */
+ while (freefirst) {
+ freenext = freefirst->next;
+ snd_seq_cell_free(freefirst);
+ freefirst = freenext;
+ }
+}
+
+
diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h
new file mode 100644
index 00000000000..f12af79308b
--- /dev/null
+++ b/sound/core/seq/seq_prioq.h
@@ -0,0 +1,62 @@
+/*
+ * ALSA sequencer Priority Queue
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_PRIOQ_H
+#define __SND_SEQ_PRIOQ_H
+
+#include "seq_memory.h"
+
+
+/* === PRIOQ === */
+
+typedef struct {
+ snd_seq_event_cell_t* head; /* pointer to head of prioq */
+ snd_seq_event_cell_t* tail; /* pointer to tail of prioq */
+ int cells;
+ spinlock_t lock;
+} prioq_t;
+
+
+/* create new prioq (constructor) */
+extern prioq_t *snd_seq_prioq_new(void);
+
+/* delete prioq (destructor) */
+extern void snd_seq_prioq_delete(prioq_t **fifo);
+
+/* enqueue cell to prioq */
+extern int snd_seq_prioq_cell_in(prioq_t *f, snd_seq_event_cell_t *cell);
+
+/* dequeue cell from prioq */
+extern snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t *f);
+
+/* return number of events available in prioq */
+extern int snd_seq_prioq_avail(prioq_t *f);
+
+/* peek at cell at the head of the prioq */
+extern snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t *f);
+
+/* client left queue */
+extern void snd_seq_prioq_leave(prioq_t *f, int client, int timestamp);
+
+/* Remove events */
+void snd_seq_prioq_remove_events(prioq_t * f, int client,
+ snd_seq_remove_events_t *info);
+
+#endif
diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c
new file mode 100644
index 00000000000..3afc7cc0c9a
--- /dev/null
+++ b/sound/core/seq/seq_queue.c
@@ -0,0 +1,783 @@
+/*
+ * ALSA sequencer Timing queue handling
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ * 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
+ *
+ * MAJOR CHANGES
+ * Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de>
+ * - Queues are allocated dynamically via ioctl.
+ * - When owner client is deleted, all owned queues are deleted, too.
+ * - Owner of unlocked queue is kept unmodified even if it is
+ * manipulated by other clients.
+ * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the
+ * caller client. i.e. Changing owner to a third client is not
+ * allowed.
+ *
+ * Aug. 30, 2000 Takashi Iwai
+ * - Queues are managed in static array again, but with better way.
+ * The API itself is identical.
+ * - The queue is locked when queue_t pinter is returned via
+ * queueptr(). This pointer *MUST* be released afterward by
+ * queuefree(ptr).
+ * - Addition of experimental sync support.
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "seq_memory.h"
+#include "seq_queue.h"
+#include "seq_clientmgr.h"
+#include "seq_fifo.h"
+#include "seq_timer.h"
+#include "seq_info.h"
+
+/* list of allocated queues */
+static queue_t *queue_list[SNDRV_SEQ_MAX_QUEUES];
+static DEFINE_SPINLOCK(queue_list_lock);
+/* number of queues allocated */
+static int num_queues;
+
+int snd_seq_queue_get_cur_queues(void)
+{
+ return num_queues;
+}
+
+/*----------------------------------------------------------------*/
+
+/* assign queue id and insert to list */
+static int queue_list_add(queue_t *q)
+{
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue_list_lock, flags);
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if (! queue_list[i]) {
+ queue_list[i] = q;
+ q->queue = i;
+ num_queues++;
+ spin_unlock_irqrestore(&queue_list_lock, flags);
+ return i;
+ }
+ }
+ spin_unlock_irqrestore(&queue_list_lock, flags);
+ return -1;
+}
+
+static queue_t *queue_list_remove(int id, int client)
+{
+ queue_t *q;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue_list_lock, flags);
+ q = queue_list[id];
+ if (q) {
+ spin_lock(&q->owner_lock);
+ if (q->owner == client) {
+ /* found */
+ q->klocked = 1;
+ spin_unlock(&q->owner_lock);
+ queue_list[id] = NULL;
+ num_queues--;
+ spin_unlock_irqrestore(&queue_list_lock, flags);
+ return q;
+ }
+ spin_unlock(&q->owner_lock);
+ }
+ spin_unlock_irqrestore(&queue_list_lock, flags);
+ return NULL;
+}
+
+/*----------------------------------------------------------------*/
+
+/* create new queue (constructor) */
+static queue_t *queue_new(int owner, int locked)
+{
+ queue_t *q;
+
+ q = kcalloc(1, sizeof(*q), GFP_KERNEL);
+ if (q == NULL) {
+ snd_printd("malloc failed for snd_seq_queue_new()\n");
+ return NULL;
+ }
+
+ spin_lock_init(&q->owner_lock);
+ spin_lock_init(&q->check_lock);
+ init_MUTEX(&q->timer_mutex);
+ snd_use_lock_init(&q->use_lock);
+ q->queue = -1;
+
+ q->tickq = snd_seq_prioq_new();
+ q->timeq = snd_seq_prioq_new();
+ q->timer = snd_seq_timer_new();
+ if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {
+ snd_seq_prioq_delete(&q->tickq);
+ snd_seq_prioq_delete(&q->timeq);
+ snd_seq_timer_delete(&q->timer);
+ kfree(q);
+ return NULL;
+ }
+
+ q->owner = owner;
+ q->locked = locked;
+ q->klocked = 0;
+
+ return q;
+}
+
+/* delete queue (destructor) */
+static void queue_delete(queue_t *q)
+{
+ /* stop and release the timer */
+ snd_seq_timer_stop(q->timer);
+ snd_seq_timer_close(q);
+ /* wait until access free */
+ snd_use_lock_sync(&q->use_lock);
+ /* release resources... */
+ snd_seq_prioq_delete(&q->tickq);
+ snd_seq_prioq_delete(&q->timeq);
+ snd_seq_timer_delete(&q->timer);
+
+ kfree(q);
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* setup queues */
+int __init snd_seq_queues_init(void)
+{
+ /*
+ memset(queue_list, 0, sizeof(queue_list));
+ num_queues = 0;
+ */
+ return 0;
+}
+
+/* delete all existing queues */
+void __exit snd_seq_queues_delete(void)
+{
+ int i;
+
+ /* clear list */
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if (queue_list[i])
+ queue_delete(queue_list[i]);
+ }
+}
+
+/* allocate a new queue -
+ * return queue index value or negative value for error
+ */
+int snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
+{
+ queue_t *q;
+
+ q = queue_new(client, locked);
+ if (q == NULL)
+ return -ENOMEM;
+ q->info_flags = info_flags;
+ if (queue_list_add(q) < 0) {
+ queue_delete(q);
+ return -ENOMEM;
+ }
+ snd_seq_queue_use(q->queue, client, 1); /* use this queue */
+ return q->queue;
+}
+
+/* delete a queue - queue must be owned by the client */
+int snd_seq_queue_delete(int client, int queueid)
+{
+ queue_t *q;
+
+ if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
+ return -EINVAL;
+ q = queue_list_remove(queueid, client);
+ if (q == NULL)
+ return -EINVAL;
+ queue_delete(q);
+
+ return 0;
+}
+
+
+/* return pointer to queue structure for specified id */
+queue_t *queueptr(int queueid)
+{
+ queue_t *q;
+ unsigned long flags;
+
+ if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
+ return NULL;
+ spin_lock_irqsave(&queue_list_lock, flags);
+ q = queue_list[queueid];
+ if (q)
+ snd_use_lock_use(&q->use_lock);
+ spin_unlock_irqrestore(&queue_list_lock, flags);
+ return q;
+}
+
+/* return the (first) queue matching with the specified name */
+queue_t *snd_seq_queue_find_name(char *name)
+{
+ int i;
+ queue_t *q;
+
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) != NULL) {
+ if (strncmp(q->name, name, sizeof(q->name)) == 0)
+ return q;
+ queuefree(q);
+ }
+ }
+ return NULL;
+}
+
+
+/* -------------------------------------------------------- */
+
+void snd_seq_check_queue(queue_t *q, int atomic, int hop)
+{
+ unsigned long flags;
+ snd_seq_event_cell_t *cell;
+
+ if (q == NULL)
+ return;
+
+ /* make this function non-reentrant */
+ spin_lock_irqsave(&q->check_lock, flags);
+ if (q->check_blocked) {
+ q->check_again = 1;
+ spin_unlock_irqrestore(&q->check_lock, flags);
+ return; /* other thread is already checking queues */
+ }
+ q->check_blocked = 1;
+ spin_unlock_irqrestore(&q->check_lock, flags);
+
+ __again:
+ /* Process tick queue... */
+ while ((cell = snd_seq_prioq_cell_peek(q->tickq)) != NULL) {
+ if (snd_seq_compare_tick_time(&q->timer->tick.cur_tick, &cell->event.time.tick)) {
+ cell = snd_seq_prioq_cell_out(q->tickq);
+ if (cell)
+ snd_seq_dispatch_event(cell, atomic, hop);
+ } else {
+ /* event remains in the queue */
+ break;
+ }
+ }
+
+
+ /* Process time queue... */
+ while ((cell = snd_seq_prioq_cell_peek(q->timeq)) != NULL) {
+ if (snd_seq_compare_real_time(&q->timer->cur_time, &cell->event.time.time)) {
+ cell = snd_seq_prioq_cell_out(q->timeq);
+ if (cell)
+ snd_seq_dispatch_event(cell, atomic, hop);
+ } else {
+ /* event remains in the queue */
+ break;
+ }
+ }
+
+ /* free lock */
+ spin_lock_irqsave(&q->check_lock, flags);
+ if (q->check_again) {
+ q->check_again = 0;
+ spin_unlock_irqrestore(&q->check_lock, flags);
+ goto __again;
+ }
+ q->check_blocked = 0;
+ spin_unlock_irqrestore(&q->check_lock, flags);
+}
+
+
+/* enqueue a event to singe queue */
+int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop)
+{
+ int dest, err;
+ queue_t *q;
+
+ snd_assert(cell != NULL, return -EINVAL);
+ dest = cell->event.queue; /* destination queue */
+ q = queueptr(dest);
+ if (q == NULL)
+ return -EINVAL;
+ /* handle relative time stamps, convert them into absolute */
+ if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
+ switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+ case SNDRV_SEQ_TIME_STAMP_TICK:
+ cell->event.time.tick += q->timer->tick.cur_tick;
+ break;
+
+ case SNDRV_SEQ_TIME_STAMP_REAL:
+ snd_seq_inc_real_time(&cell->event.time.time, &q->timer->cur_time);
+ break;
+ }
+ cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
+ cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
+ }
+ /* enqueue event in the real-time or midi queue */
+ switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
+ case SNDRV_SEQ_TIME_STAMP_TICK:
+ err = snd_seq_prioq_cell_in(q->tickq, cell);
+ break;
+
+ case SNDRV_SEQ_TIME_STAMP_REAL:
+ default:
+ err = snd_seq_prioq_cell_in(q->timeq, cell);
+ break;
+ }
+
+ if (err < 0) {
+ queuefree(q); /* unlock */
+ return err;
+ }
+
+ /* trigger dispatching */
+ snd_seq_check_queue(q, atomic, hop);
+
+ queuefree(q); /* unlock */
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+static inline int check_access(queue_t *q, int client)
+{
+ return (q->owner == client) || (!q->locked && !q->klocked);
+}
+
+/* check if the client has permission to modify queue parameters.
+ * if it does, lock the queue
+ */
+static int queue_access_lock(queue_t *q, int client)
+{
+ unsigned long flags;
+ int access_ok;
+
+ spin_lock_irqsave(&q->owner_lock, flags);
+ access_ok = check_access(q, client);
+ if (access_ok)
+ q->klocked = 1;
+ spin_unlock_irqrestore(&q->owner_lock, flags);
+ return access_ok;
+}
+
+/* unlock the queue */
+static inline void queue_access_unlock(queue_t *q)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->owner_lock, flags);
+ q->klocked = 0;
+ spin_unlock_irqrestore(&q->owner_lock, flags);
+}
+
+/* exported - only checking permission */
+int snd_seq_queue_check_access(int queueid, int client)
+{
+ queue_t *q = queueptr(queueid);
+ int access_ok;
+ unsigned long flags;
+
+ if (! q)
+ return 0;
+ spin_lock_irqsave(&q->owner_lock, flags);
+ access_ok = check_access(q, client);
+ spin_unlock_irqrestore(&q->owner_lock, flags);
+ queuefree(q);
+ return access_ok;
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * change queue's owner and permission
+ */
+int snd_seq_queue_set_owner(int queueid, int client, int locked)
+{
+ queue_t *q = queueptr(queueid);
+
+ if (q == NULL)
+ return -EINVAL;
+
+ if (! queue_access_lock(q, client)) {
+ queuefree(q);
+ return -EPERM;
+ }
+
+ q->locked = locked ? 1 : 0;
+ q->owner = client;
+ queue_access_unlock(q);
+ queuefree(q);
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* open timer -
+ * q->use mutex should be down before calling this function to avoid
+ * confliction with snd_seq_queue_use()
+ */
+int snd_seq_queue_timer_open(int queueid)
+{
+ int result = 0;
+ queue_t *queue;
+ seq_timer_t *tmr;
+
+ queue = queueptr(queueid);
+ if (queue == NULL)
+ return -EINVAL;
+ tmr = queue->timer;
+ if ((result = snd_seq_timer_open(queue)) < 0) {
+ snd_seq_timer_defaults(tmr);
+ result = snd_seq_timer_open(queue);
+ }
+ queuefree(queue);
+ return result;
+}
+
+/* close timer -
+ * q->use mutex should be down before calling this function
+ */
+int snd_seq_queue_timer_close(int queueid)
+{
+ queue_t *queue;
+ seq_timer_t *tmr;
+ int result = 0;
+
+ queue = queueptr(queueid);
+ if (queue == NULL)
+ return -EINVAL;
+ tmr = queue->timer;
+ snd_seq_timer_close(queue);
+ queuefree(queue);
+ return result;
+}
+
+/* change queue tempo and ppq */
+int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info)
+{
+ queue_t *q = queueptr(queueid);
+ int result;
+
+ if (q == NULL)
+ return -EINVAL;
+ if (! queue_access_lock(q, client)) {
+ queuefree(q);
+ return -EPERM;
+ }
+
+ result = snd_seq_timer_set_tempo(q->timer, info->tempo);
+ if (result >= 0)
+ result = snd_seq_timer_set_ppq(q->timer, info->ppq);
+ if (result >= 0 && info->skew_base > 0)
+ result = snd_seq_timer_set_skew(q->timer, info->skew_value, info->skew_base);
+ queue_access_unlock(q);
+ queuefree(q);
+ return result;
+}
+
+
+/* use or unuse this queue -
+ * if it is the first client, starts the timer.
+ * if it is not longer used by any clients, stop the timer.
+ */
+int snd_seq_queue_use(int queueid, int client, int use)
+{
+ queue_t *queue;
+
+ queue = queueptr(queueid);
+ if (queue == NULL)
+ return -EINVAL;
+ down(&queue->timer_mutex);
+ if (use) {
+ if (!test_and_set_bit(client, queue->clients_bitmap))
+ queue->clients++;
+ } else {
+ if (test_and_clear_bit(client, queue->clients_bitmap))
+ queue->clients--;
+ }
+ if (queue->clients) {
+ if (use && queue->clients == 1)
+ snd_seq_timer_defaults(queue->timer);
+ snd_seq_timer_open(queue);
+ } else {
+ snd_seq_timer_close(queue);
+ }
+ up(&queue->timer_mutex);
+ queuefree(queue);
+ return 0;
+}
+
+/*
+ * check if queue is used by the client
+ * return negative value if the queue is invalid.
+ * return 0 if not used, 1 if used.
+ */
+int snd_seq_queue_is_used(int queueid, int client)
+{
+ queue_t *q;
+ int result;
+
+ q = queueptr(queueid);
+ if (q == NULL)
+ return -EINVAL; /* invalid queue */
+ result = test_bit(client, q->clients_bitmap) ? 1 : 0;
+ queuefree(q);
+ return result;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* notification that client has left the system -
+ * stop the timer on all queues owned by this client
+ */
+void snd_seq_queue_client_termination(int client)
+{
+ unsigned long flags;
+ int i;
+ queue_t *q;
+
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) == NULL)
+ continue;
+ spin_lock_irqsave(&q->owner_lock, flags);
+ if (q->owner == client)
+ q->klocked = 1;
+ spin_unlock_irqrestore(&q->owner_lock, flags);
+ if (q->owner == client) {
+ if (q->timer->running)
+ snd_seq_timer_stop(q->timer);
+ snd_seq_timer_reset(q->timer);
+ }
+ queuefree(q);
+ }
+}
+
+/* final stage notification -
+ * remove cells for no longer exist client (for non-owned queue)
+ * or delete this queue (for owned queue)
+ */
+void snd_seq_queue_client_leave(int client)
+{
+ int i;
+ queue_t *q;
+
+ /* delete own queues from queue list */
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queue_list_remove(i, client)) != NULL)
+ queue_delete(q);
+ }
+
+ /* remove cells from existing queues -
+ * they are not owned by this client
+ */
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) == NULL)
+ continue;
+ if (test_bit(client, q->clients_bitmap)) {
+ snd_seq_prioq_leave(q->tickq, client, 0);
+ snd_seq_prioq_leave(q->timeq, client, 0);
+ snd_seq_queue_use(q->queue, client, 0);
+ }
+ queuefree(q);
+ }
+}
+
+
+
+/*----------------------------------------------------------------*/
+
+/* remove cells from all queues */
+void snd_seq_queue_client_leave_cells(int client)
+{
+ int i;
+ queue_t *q;
+
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) == NULL)
+ continue;
+ snd_seq_prioq_leave(q->tickq, client, 0);
+ snd_seq_prioq_leave(q->timeq, client, 0);
+ queuefree(q);
+ }
+}
+
+/* remove cells based on flush criteria */
+void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info)
+{
+ int i;
+ queue_t *q;
+
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) == NULL)
+ continue;
+ if (test_bit(client, q->clients_bitmap) &&
+ (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||
+ q->queue == info->queue)) {
+ snd_seq_prioq_remove_events(q->tickq, client, info);
+ snd_seq_prioq_remove_events(q->timeq, client, info);
+ }
+ queuefree(q);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * send events to all subscribed ports
+ */
+static void queue_broadcast_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
+{
+ snd_seq_event_t sev;
+
+ sev = *ev;
+
+ sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;
+ sev.time.tick = q->timer->tick.cur_tick;
+ sev.queue = q->queue;
+ sev.data.queue.queue = q->queue;
+
+ /* broadcast events from Timer port */
+ sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+ sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);
+}
+
+/*
+ * process a received queue-control event.
+ * this function is exported for seq_sync.c.
+ */
+void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop)
+{
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_START:
+ snd_seq_prioq_leave(q->tickq, ev->source.client, 1);
+ snd_seq_prioq_leave(q->timeq, ev->source.client, 1);
+ if (! snd_seq_timer_start(q->timer))
+ queue_broadcast_event(q, ev, atomic, hop);
+ break;
+
+ case SNDRV_SEQ_EVENT_CONTINUE:
+ if (! snd_seq_timer_continue(q->timer))
+ queue_broadcast_event(q, ev, atomic, hop);
+ break;
+
+ case SNDRV_SEQ_EVENT_STOP:
+ snd_seq_timer_stop(q->timer);
+ queue_broadcast_event(q, ev, atomic, hop);
+ break;
+
+ case SNDRV_SEQ_EVENT_TEMPO:
+ snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);
+ queue_broadcast_event(q, ev, atomic, hop);
+ break;
+
+ case SNDRV_SEQ_EVENT_SETPOS_TICK:
+ if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {
+ queue_broadcast_event(q, ev, atomic, hop);
+ }
+ break;
+
+ case SNDRV_SEQ_EVENT_SETPOS_TIME:
+ if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {
+ queue_broadcast_event(q, ev, atomic, hop);
+ }
+ break;
+ case SNDRV_SEQ_EVENT_QUEUE_SKEW:
+ if (snd_seq_timer_set_skew(q->timer,
+ ev->data.queue.param.skew.value,
+ ev->data.queue.param.skew.base) == 0) {
+ queue_broadcast_event(q, ev, atomic, hop);
+ }
+ break;
+ }
+}
+
+
+/*
+ * Queue control via timer control port:
+ * this function is exported as a callback of timer port.
+ */
+int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop)
+{
+ queue_t *q;
+
+ snd_assert(ev != NULL, return -EINVAL);
+ q = queueptr(ev->data.queue.queue);
+
+ if (q == NULL)
+ return -EINVAL;
+
+ if (! queue_access_lock(q, ev->source.client)) {
+ queuefree(q);
+ return -EPERM;
+ }
+
+ snd_seq_queue_process_event(q, ev, atomic, hop);
+
+ queue_access_unlock(q);
+ queuefree(q);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/* exported to seq_info.c */
+void snd_seq_info_queues_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ int i, bpm;
+ queue_t *q;
+ seq_timer_t *tmr;
+
+ for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
+ if ((q = queueptr(i)) == NULL)
+ continue;
+
+ tmr = q->timer;
+ if (tmr->tempo)
+ bpm = 60000000 / tmr->tempo;
+ else
+ bpm = 0;
+
+ snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);
+ snd_iprintf(buffer, "owned by client : %d\n", q->owner);
+ snd_iprintf(buffer, "lock status : %s\n", q->locked ? "Locked" : "Free");
+ snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));
+ snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));
+ snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped");
+ snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq);
+ snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo);
+ snd_iprintf(buffer, "current BPM : %d\n", bpm);
+ snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);
+ snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick);
+ snd_iprintf(buffer, "\n");
+ queuefree(q);
+ }
+}
diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h
new file mode 100644
index 00000000000..b1bf5519fb3
--- /dev/null
+++ b/sound/core/seq/seq_queue.h
@@ -0,0 +1,140 @@
+/*
+ * ALSA sequencer Queue handling
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_QUEUE_H
+#define __SND_SEQ_QUEUE_H
+
+#include "seq_memory.h"
+#include "seq_prioq.h"
+#include "seq_timer.h"
+#include "seq_lock.h"
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+
+#define SEQ_QUEUE_NO_OWNER (-1)
+
+struct _snd_seq_queue {
+ int queue; /* queue number */
+
+ char name[64]; /* name of this queue */
+
+ prioq_t *tickq; /* midi tick event queue */
+ prioq_t *timeq; /* real-time event queue */
+
+ seq_timer_t *timer; /* time keeper for this queue */
+ int owner; /* client that 'owns' the timer */
+ unsigned int locked:1, /* timer is only accesibble by owner if set */
+ klocked:1, /* kernel lock (after START) */
+ check_again:1,
+ check_blocked:1;
+
+ unsigned int flags; /* status flags */
+ unsigned int info_flags; /* info for sync */
+
+ spinlock_t owner_lock;
+ spinlock_t check_lock;
+
+ /* clients which uses this queue (bitmap) */
+ DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS);
+ unsigned int clients; /* users of this queue */
+ struct semaphore timer_mutex;
+
+ snd_use_lock_t use_lock;
+};
+
+
+/* get the number of current queues */
+int snd_seq_queue_get_cur_queues(void);
+
+/* init queues structure */
+int snd_seq_queues_init(void);
+
+/* delete queues */
+void snd_seq_queues_delete(void);
+
+
+/* create new queue (constructor) */
+int snd_seq_queue_alloc(int client, int locked, unsigned int flags);
+
+/* delete queue (destructor) */
+int snd_seq_queue_delete(int client, int queueid);
+
+/* notification that client has left the system */
+void snd_seq_queue_client_termination(int client);
+
+/* final stage */
+void snd_seq_queue_client_leave(int client);
+
+/* enqueue a event received from one the clients */
+int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop);
+
+/* Remove events */
+void snd_seq_queue_client_leave_cells(int client);
+void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info);
+
+/* return pointer to queue structure for specified id */
+queue_t *queueptr(int queueid);
+/* unlock */
+#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
+
+/* return the (first) queue matching with the specified name */
+queue_t *snd_seq_queue_find_name(char *name);
+
+/* check single queue and dispatch events */
+void snd_seq_check_queue(queue_t *q, int atomic, int hop);
+
+/* access to queue's parameters */
+int snd_seq_queue_check_access(int queueid, int client);
+int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info);
+int snd_seq_queue_set_owner(int queueid, int client, int locked);
+int snd_seq_queue_set_locked(int queueid, int client, int locked);
+int snd_seq_queue_timer_open(int queueid);
+int snd_seq_queue_timer_close(int queueid);
+int snd_seq_queue_use(int queueid, int client, int use);
+int snd_seq_queue_is_used(int queueid, int client);
+
+int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop);
+void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop);
+
+/*
+ * 64bit division - for sync stuff..
+ */
+#if defined(i386) || defined(i486)
+
+#define udiv_qrnnd(q, r, n1, n0, d) \
+ __asm__ ("divl %4" \
+ : "=a" ((u32)(q)), \
+ "=d" ((u32)(r)) \
+ : "0" ((u32)(n0)), \
+ "1" ((u32)(n1)), \
+ "rm" ((u32)(d)))
+
+#define u64_div(x,y,q) do {u32 __tmp; udiv_qrnnd(q, __tmp, (x)>>32, x, y);} while (0)
+#define u64_mod(x,y,r) do {u32 __tmp; udiv_qrnnd(__tmp, q, (x)>>32, x, y);} while (0)
+#define u64_divmod(x,y,q,r) udiv_qrnnd(q, r, (x)>>32, x, y)
+
+#else
+#define u64_div(x,y,q) ((q) = (u32)((u64)(x) / (u64)(y)))
+#define u64_mod(x,y,r) ((r) = (u32)((u64)(x) % (u64)(y)))
+#define u64_divmod(x,y,q,r) (u64_div(x,y,q), u64_mod(x,y,r))
+#endif
+
+
+#endif
diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c
new file mode 100644
index 00000000000..e8f0a6683d5
--- /dev/null
+++ b/sound/core/seq/seq_system.c
@@ -0,0 +1,190 @@
+/*
+ * ALSA sequencer System services Client
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include "seq_system.h"
+#include "seq_timer.h"
+#include "seq_queue.h"
+
+/* internal client that provide system services, access to timer etc. */
+
+/*
+ * Port "Timer"
+ * - send tempo /start/stop etc. events to this port to manipulate the
+ * queue's timer. The queue address is specified in
+ * data.queue.queue.
+ * - this port supports subscription. The received timer events are
+ * broadcasted to all subscribed clients. The modified tempo
+ * value is stored on data.queue.value.
+ * The modifier client/port is not send.
+ *
+ * Port "Announce"
+ * - does not receive message
+ * - supports supscription. For each client or port attaching to or
+ * detaching from the system an announcement is send to the subscribed
+ * clients.
+ *
+ * Idea: the subscription mechanism might also work handy for distributing
+ * synchronisation and timing information. In this case we would ideally have
+ * a list of subscribers for each type of sync (time, tick), for each timing
+ * queue.
+ *
+ * NOTE: the queue to be started, stopped, etc. must be specified
+ * in data.queue.addr.queue field. queue is used only for
+ * scheduling, and no longer referred as affected queue.
+ * They are used only for timer broadcast (see above).
+ * -- iwai
+ */
+
+
+/* client id of our system client */
+static int sysclient = -1;
+
+/* port id numbers for this client */
+static int announce_port = -1;
+
+
+
+/* fill standard header data, source port & channel are filled in */
+static int setheader(snd_seq_event_t * ev, int client, int port)
+{
+ if (announce_port < 0)
+ return -ENODEV;
+
+ memset(ev, 0, sizeof(snd_seq_event_t));
+
+ ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+ ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
+
+ ev->source.client = sysclient;
+ ev->source.port = announce_port;
+ ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+
+ /* fill data */
+ /*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/
+ ev->data.addr.client = client;
+ ev->data.addr.port = port;
+
+ return 0;
+}
+
+
+/* entry points for broadcasting system events */
+void snd_seq_system_broadcast(int client, int port, int type)
+{
+ snd_seq_event_t ev;
+
+ if (setheader(&ev, client, port) < 0)
+ return;
+ ev.type = type;
+ snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
+}
+
+/* entry points for broadcasting system events */
+int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev)
+{
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ ev->source.client = sysclient;
+ ev->source.port = announce_port;
+ ev->dest.client = client;
+ ev->dest.port = port;
+ return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0);
+}
+
+/* call-back handler for timer events */
+static int event_input_timer(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
+{
+ return snd_seq_control_queue(ev, atomic, hop);
+}
+
+/* register our internal client */
+int __init snd_seq_system_client_init(void)
+{
+
+ snd_seq_client_callback_t callbacks;
+ snd_seq_port_callback_t pcallbacks;
+ snd_seq_client_info_t *inf;
+ snd_seq_port_info_t *port;
+
+ inf = kcalloc(1, sizeof(*inf), GFP_KERNEL);
+ port = kcalloc(1, sizeof(*port), GFP_KERNEL);
+ if (! inf || ! port) {
+ kfree(inf);
+ kfree(port);
+ return -ENOMEM;
+ }
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ memset(&pcallbacks, 0, sizeof(pcallbacks));
+ pcallbacks.owner = THIS_MODULE;
+ pcallbacks.event_input = event_input_timer;
+
+ /* register client */
+ callbacks.allow_input = callbacks.allow_output = 1;
+ sysclient = snd_seq_create_kernel_client(NULL, 0, &callbacks);
+
+ /* set our name */
+ inf->client = 0;
+ inf->type = KERNEL_CLIENT;
+ strcpy(inf->name, "System");
+ snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, inf);
+
+ /* register timer */
+ strcpy(port->name, "Timer");
+ port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */
+ port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */
+ port->kernel = &pcallbacks;
+ port->type = 0;
+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+ port->addr.client = sysclient;
+ port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+ snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+
+ /* register announcement port */
+ strcpy(port->name, "Announce");
+ port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
+ port->kernel = NULL;
+ port->type = 0;
+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+ port->addr.client = sysclient;
+ port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+ snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port);
+ announce_port = port->addr.port;
+
+ kfree(inf);
+ kfree(port);
+ return 0;
+}
+
+
+/* unregister our internal client */
+void __exit snd_seq_system_client_done(void)
+{
+ int oldsysclient = sysclient;
+
+ if (oldsysclient >= 0) {
+ sysclient = -1;
+ announce_port = -1;
+ snd_seq_delete_kernel_client(oldsysclient);
+ }
+}
diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h
new file mode 100644
index 00000000000..900007255bb
--- /dev/null
+++ b/sound/core/seq/seq_system.h
@@ -0,0 +1,46 @@
+/*
+ * ALSA sequencer System Client
+ * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_SYSTEM_H
+#define __SND_SEQ_SYSTEM_H
+
+#include <sound/seq_kernel.h>
+
+
+/* entry points for broadcasting system events */
+void snd_seq_system_broadcast(int client, int port, int type);
+
+#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
+#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
+#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
+#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START)
+#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
+#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
+
+int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev);
+
+/* register our internal client */
+int snd_seq_system_client_init(void);
+
+/* unregister our internal client */
+void snd_seq_system_client_done(void);
+
+
+#endif
diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c
new file mode 100644
index 00000000000..753f1c0863c
--- /dev/null
+++ b/sound/core/seq/seq_timer.c
@@ -0,0 +1,435 @@
+/*
+ * ALSA sequencer Timer
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ * Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_timer.h"
+#include "seq_queue.h"
+#include "seq_info.h"
+
+extern int seq_default_timer_class;
+extern int seq_default_timer_sclass;
+extern int seq_default_timer_card;
+extern int seq_default_timer_device;
+extern int seq_default_timer_subdevice;
+extern int seq_default_timer_resolution;
+
+#define SKEW_BASE 0x10000 /* 16bit shift */
+
+void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks)
+{
+ if (tempo < 1000000)
+ tick->resolution = (tempo * 1000) / ppq;
+ else {
+ /* might overflow.. */
+ unsigned int s;
+ s = tempo % ppq;
+ s = (s * 1000) / ppq;
+ tick->resolution = (tempo / ppq) * 1000;
+ tick->resolution += s;
+ }
+ if (tick->resolution <= 0)
+ tick->resolution = 1;
+ tick->resolution *= nticks;
+ snd_seq_timer_update_tick(tick, 0);
+}
+
+/* create new timer (constructor) */
+seq_timer_t *snd_seq_timer_new(void)
+{
+ seq_timer_t *tmr;
+
+ tmr = kcalloc(1, sizeof(*tmr), GFP_KERNEL);
+ if (tmr == NULL) {
+ snd_printd("malloc failed for snd_seq_timer_new() \n");
+ return NULL;
+ }
+ spin_lock_init(&tmr->lock);
+
+ /* reset setup to defaults */
+ snd_seq_timer_defaults(tmr);
+
+ /* reset time */
+ snd_seq_timer_reset(tmr);
+
+ return tmr;
+}
+
+/* delete timer (destructor) */
+void snd_seq_timer_delete(seq_timer_t **tmr)
+{
+ seq_timer_t *t = *tmr;
+ *tmr = NULL;
+
+ if (t == NULL) {
+ snd_printd("oops: snd_seq_timer_delete() called with NULL timer\n");
+ return;
+ }
+ t->running = 0;
+
+ /* reset time */
+ snd_seq_timer_stop(t);
+ snd_seq_timer_reset(t);
+
+ kfree(t);
+}
+
+void snd_seq_timer_defaults(seq_timer_t * tmr)
+{
+ /* setup defaults */
+ tmr->ppq = 96; /* 96 PPQ */
+ tmr->tempo = 500000; /* 120 BPM */
+ snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+ tmr->running = 0;
+
+ tmr->type = SNDRV_SEQ_TIMER_ALSA;
+ tmr->alsa_id.dev_class = seq_default_timer_class;
+ tmr->alsa_id.dev_sclass = seq_default_timer_sclass;
+ tmr->alsa_id.card = seq_default_timer_card;
+ tmr->alsa_id.device = seq_default_timer_device;
+ tmr->alsa_id.subdevice = seq_default_timer_subdevice;
+ tmr->preferred_resolution = seq_default_timer_resolution;
+
+ tmr->skew = tmr->skew_base = SKEW_BASE;
+}
+
+void snd_seq_timer_reset(seq_timer_t * tmr)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tmr->lock, flags);
+
+ /* reset time & songposition */
+ tmr->cur_time.tv_sec = 0;
+ tmr->cur_time.tv_nsec = 0;
+
+ tmr->tick.cur_tick = 0;
+ tmr->tick.fraction = 0;
+
+ spin_unlock_irqrestore(&tmr->lock, flags);
+}
+
+
+/* called by timer interrupt routine. the period time since previous invocation is passed */
+static void snd_seq_timer_interrupt(snd_timer_instance_t *timeri,
+ unsigned long resolution,
+ unsigned long ticks)
+{
+ unsigned long flags;
+ queue_t *q = (queue_t *)timeri->callback_data;
+ seq_timer_t *tmr;
+
+ if (q == NULL)
+ return;
+ tmr = q->timer;
+ if (tmr == NULL)
+ return;
+ if (!tmr->running)
+ return;
+
+ resolution *= ticks;
+ if (tmr->skew != tmr->skew_base) {
+ /* FIXME: assuming skew_base = 0x10000 */
+ resolution = (resolution >> 16) * tmr->skew +
+ (((resolution & 0xffff) * tmr->skew) >> 16);
+ }
+
+ spin_lock_irqsave(&tmr->lock, flags);
+
+ /* update timer */
+ snd_seq_inc_time_nsec(&tmr->cur_time, resolution);
+
+ /* calculate current tick */
+ snd_seq_timer_update_tick(&tmr->tick, resolution);
+
+ /* register actual time of this timer update */
+ do_gettimeofday(&tmr->last_update);
+
+ spin_unlock_irqrestore(&tmr->lock, flags);
+
+ /* check queues and dispatch events */
+ snd_seq_check_queue(q, 1, 0);
+}
+
+/* set current tempo */
+int snd_seq_timer_set_tempo(seq_timer_t * tmr, int tempo)
+{
+ unsigned long flags;
+
+ snd_assert(tmr, return -EINVAL);
+ if (tempo <= 0)
+ return -EINVAL;
+ spin_lock_irqsave(&tmr->lock, flags);
+ if ((unsigned int)tempo != tmr->tempo) {
+ tmr->tempo = tempo;
+ snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+ }
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ return 0;
+}
+
+/* set current ppq */
+int snd_seq_timer_set_ppq(seq_timer_t * tmr, int ppq)
+{
+ unsigned long flags;
+
+ snd_assert(tmr, return -EINVAL);
+ if (ppq <= 0)
+ return -EINVAL;
+ spin_lock_irqsave(&tmr->lock, flags);
+ if (tmr->running && (ppq != tmr->ppq)) {
+ /* refuse to change ppq on running timers */
+ /* because it will upset the song position (ticks) */
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ snd_printd("seq: cannot change ppq of a running timer\n");
+ return -EBUSY;
+ }
+
+ tmr->ppq = ppq;
+ snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1);
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ return 0;
+}
+
+/* set current tick position */
+int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position)
+{
+ unsigned long flags;
+
+ snd_assert(tmr, return -EINVAL);
+
+ spin_lock_irqsave(&tmr->lock, flags);
+ tmr->tick.cur_tick = position;
+ tmr->tick.fraction = 0;
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ return 0;
+}
+
+/* set current real-time position */
+int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position)
+{
+ unsigned long flags;
+
+ snd_assert(tmr, return -EINVAL);
+
+ snd_seq_sanity_real_time(&position);
+ spin_lock_irqsave(&tmr->lock, flags);
+ tmr->cur_time = position;
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ return 0;
+}
+
+/* set timer skew */
+int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base)
+{
+ unsigned long flags;
+
+ snd_assert(tmr, return -EINVAL);
+
+ /* FIXME */
+ if (base != SKEW_BASE) {
+ snd_printd("invalid skew base 0x%x\n", base);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&tmr->lock, flags);
+ tmr->skew = skew;
+ spin_unlock_irqrestore(&tmr->lock, flags);
+ return 0;
+}
+
+int snd_seq_timer_open(queue_t *q)
+{
+ snd_timer_instance_t *t;
+ seq_timer_t *tmr;
+ char str[32];
+ int err;
+
+ tmr = q->timer;
+ snd_assert(tmr != NULL, return -EINVAL);
+ if (tmr->timeri)
+ return -EBUSY;
+ sprintf(str, "sequencer queue %i", q->queue);
+ if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */
+ return -EINVAL;
+ if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
+ tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
+ err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue);
+ if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
+ if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
+ tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
+ snd_timer_id_t tid;
+ memset(&tid, 0, sizeof(tid));
+ tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
+ tid.card = -1;
+ tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
+ err = snd_timer_open(&t, str, &tid, q->queue);
+ }
+ if (err < 0) {
+ snd_printk(KERN_ERR "seq fatal error: cannot create timer (%i)\n", err);
+ return err;
+ }
+ }
+ t->callback = snd_seq_timer_interrupt;
+ t->callback_data = q;
+ t->flags |= SNDRV_TIMER_IFLG_AUTO;
+ tmr->timeri = t;
+ return 0;
+}
+
+int snd_seq_timer_close(queue_t *q)
+{
+ seq_timer_t *tmr;
+
+ tmr = q->timer;
+ snd_assert(tmr != NULL, return -EINVAL);
+ if (tmr->timeri) {
+ snd_timer_stop(tmr->timeri);
+ snd_timer_close(tmr->timeri);
+ tmr->timeri = NULL;
+ }
+ return 0;
+}
+
+int snd_seq_timer_stop(seq_timer_t * tmr)
+{
+ if (! tmr->timeri)
+ return -EINVAL;
+ if (!tmr->running)
+ return 0;
+ tmr->running = 0;
+ snd_timer_pause(tmr->timeri);
+ return 0;
+}
+
+static int initialize_timer(seq_timer_t *tmr)
+{
+ snd_timer_t *t;
+ t = tmr->timeri->timer;
+ snd_assert(t, return -EINVAL);
+
+ tmr->ticks = 1;
+ if (tmr->preferred_resolution &&
+ ! (t->hw.flags & SNDRV_TIMER_HW_SLAVE)) {
+ unsigned long r = t->hw.resolution;
+ if (! r && t->hw.c_resolution)
+ r = t->hw.c_resolution(t);
+ if (r) {
+ tmr->ticks = (unsigned int)(1000000000uL / (r * tmr->preferred_resolution));
+ if (! tmr->ticks)
+ tmr->ticks = 1;
+ }
+ }
+ tmr->initialized = 1;
+ return 0;
+}
+
+int snd_seq_timer_start(seq_timer_t * tmr)
+{
+ if (! tmr->timeri)
+ return -EINVAL;
+ if (tmr->running)
+ snd_seq_timer_stop(tmr);
+ snd_seq_timer_reset(tmr);
+ if (initialize_timer(tmr) < 0)
+ return -EINVAL;
+ snd_timer_start(tmr->timeri, tmr->ticks);
+ tmr->running = 1;
+ do_gettimeofday(&tmr->last_update);
+ return 0;
+}
+
+int snd_seq_timer_continue(seq_timer_t * tmr)
+{
+ if (! tmr->timeri)
+ return -EINVAL;
+ if (tmr->running)
+ return -EBUSY;
+ if (! tmr->initialized) {
+ snd_seq_timer_reset(tmr);
+ if (initialize_timer(tmr) < 0)
+ return -EINVAL;
+ }
+ snd_timer_start(tmr->timeri, tmr->ticks);
+ tmr->running = 1;
+ do_gettimeofday(&tmr->last_update);
+ return 0;
+}
+
+/* return current 'real' time. use timeofday() to get better granularity. */
+snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr)
+{
+ snd_seq_real_time_t cur_time;
+
+ cur_time = tmr->cur_time;
+ if (tmr->running) {
+ struct timeval tm;
+ int usec;
+ do_gettimeofday(&tm);
+ usec = (int)(tm.tv_usec - tmr->last_update.tv_usec);
+ if (usec < 0) {
+ cur_time.tv_nsec += (1000000 + usec) * 1000;
+ cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec - 1;
+ } else {
+ cur_time.tv_nsec += usec * 1000;
+ cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec;
+ }
+ snd_seq_sanity_real_time(&cur_time);
+ }
+
+ return cur_time;
+}
+
+/* TODO: use interpolation on tick queue (will only be useful for very
+ high PPQ values) */
+snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr)
+{
+ return tmr->tick.cur_tick;
+}
+
+
+/* exported to seq_info.c */
+void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int idx;
+ queue_t *q;
+ seq_timer_t *tmr;
+ snd_timer_instance_t *ti;
+ unsigned long resolution;
+
+ for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) {
+ q = queueptr(idx);
+ if (q == NULL)
+ continue;
+ if ((tmr = q->timer) == NULL ||
+ (ti = tmr->timeri) == NULL) {
+ queuefree(q);
+ continue;
+ }
+ snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name);
+ resolution = snd_timer_resolution(ti) * tmr->ticks;
+ snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000);
+ snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base);
+ queuefree(q);
+ }
+}
diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h
new file mode 100644
index 00000000000..4c0872df893
--- /dev/null
+++ b/sound/core/seq/seq_timer.h
@@ -0,0 +1,141 @@
+/*
+ * ALSA sequencer Timer
+ * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef __SND_SEQ_TIMER_H
+#define __SND_SEQ_TIMER_H
+
+#include <sound/timer.h>
+#include <sound/seq_kernel.h>
+
+typedef struct {
+ snd_seq_tick_time_t cur_tick; /* current tick */
+ unsigned long resolution; /* time per tick in nsec */
+ unsigned long fraction; /* current time per tick in nsec */
+} seq_timer_tick_t;
+
+typedef struct {
+ /* ... tempo / offset / running state */
+
+ unsigned int running:1, /* running state of queue */
+ initialized:1; /* timer is initialized */
+
+ unsigned int tempo; /* current tempo, us/tick */
+ int ppq; /* time resolution, ticks/quarter */
+
+ snd_seq_real_time_t cur_time; /* current time */
+ seq_timer_tick_t tick; /* current tick */
+ int tick_updated;
+
+ int type; /* timer type */
+ snd_timer_id_t alsa_id; /* ALSA's timer ID */
+ snd_timer_instance_t *timeri; /* timer instance */
+ unsigned int ticks;
+ unsigned long preferred_resolution; /* timer resolution, ticks/sec */
+
+ unsigned int skew;
+ unsigned int skew_base;
+
+ struct timeval last_update; /* time of last clock update, used for interpolation */
+
+ spinlock_t lock;
+} seq_timer_t;
+
+
+/* create new timer (constructor) */
+extern seq_timer_t *snd_seq_timer_new(void);
+
+/* delete timer (destructor) */
+extern void snd_seq_timer_delete(seq_timer_t **tmr);
+
+void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks);
+
+/* */
+static inline void snd_seq_timer_update_tick(seq_timer_tick_t *tick, unsigned long resolution)
+{
+ if (tick->resolution > 0) {
+ tick->fraction += resolution;
+ tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution);
+ tick->fraction %= tick->resolution;
+ }
+}
+
+
+/* compare timestamp between events */
+/* return 1 if a >= b; otherwise return 0 */
+static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b)
+{
+ /* compare ticks */
+ return (*a >= *b);
+}
+
+static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b)
+{
+ /* compare real time */
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+ if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec))
+ return 1;
+ return 0;
+}
+
+
+static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm)
+{
+ while (tm->tv_nsec >= 1000000000) {
+ /* roll-over */
+ tm->tv_nsec -= 1000000000;
+ tm->tv_sec++;
+ }
+}
+
+
+/* increment timestamp */
+static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc)
+{
+ tm->tv_sec += inc->tv_sec;
+ tm->tv_nsec += inc->tv_nsec;
+ snd_seq_sanity_real_time(tm);
+}
+
+static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec)
+{
+ tm->tv_nsec += nsec;
+ snd_seq_sanity_real_time(tm);
+}
+
+/* called by timer isr */
+int snd_seq_timer_open(queue_t *q);
+int snd_seq_timer_close(queue_t *q);
+int snd_seq_timer_midi_open(queue_t *q);
+int snd_seq_timer_midi_close(queue_t *q);
+void snd_seq_timer_defaults(seq_timer_t *tmr);
+void snd_seq_timer_reset(seq_timer_t *tmr);
+int snd_seq_timer_stop(seq_timer_t *tmr);
+int snd_seq_timer_start(seq_timer_t *tmr);
+int snd_seq_timer_continue(seq_timer_t *tmr);
+int snd_seq_timer_set_tempo(seq_timer_t *tmr, int tempo);
+int snd_seq_timer_set_ppq(seq_timer_t *tmr, int ppq);
+int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position);
+int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position);
+int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base);
+snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr);
+snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr);
+
+#endif
diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c
new file mode 100644
index 00000000000..6b4e630ace5
--- /dev/null
+++ b/sound/core/seq/seq_virmidi.c
@@ -0,0 +1,551 @@
+/*
+ * Virtual Raw MIDI client on Sequencer
+ *
+ * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>,
+ * Jaroslav Kysela <perex@perex.cz>
+ *
+ * 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
+ *
+ */
+
+/*
+ * Virtual Raw MIDI client
+ *
+ * The virtual rawmidi client is a sequencer client which associate
+ * a rawmidi device file. The created rawmidi device file can be
+ * accessed as a normal raw midi, but its MIDI source and destination
+ * are arbitrary. For example, a user-client software synth connected
+ * to this port can be used as a normal midi device as well.
+ *
+ * The virtual rawmidi device accepts also multiple opens. Each file
+ * has its own input buffer, so that no conflict would occur. The drain
+ * of input/output buffer acts only to the local buffer.
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/minors.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_midi_event.h>
+#include <sound/seq_virmidi.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer");
+MODULE_LICENSE("GPL");
+
+/*
+ * initialize an event record
+ */
+static void snd_virmidi_init_event(snd_virmidi_t *vmidi, snd_seq_event_t *ev)
+{
+ memset(ev, 0, sizeof(*ev));
+ ev->source.port = vmidi->port;
+ switch (vmidi->seq_mode) {
+ case SNDRV_VIRMIDI_SEQ_DISPATCH:
+ ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ break;
+ case SNDRV_VIRMIDI_SEQ_ATTACH:
+ /* FIXME: source and destination are same - not good.. */
+ ev->dest.client = vmidi->client;
+ ev->dest.port = vmidi->port;
+ break;
+ }
+ ev->type = SNDRV_SEQ_EVENT_NONE;
+}
+
+/*
+ * decode input event and put to read buffer of each opened file
+ */
+static int snd_virmidi_dev_receive_event(snd_virmidi_dev_t *rdev, snd_seq_event_t *ev)
+{
+ snd_virmidi_t *vmidi;
+ struct list_head *list;
+ unsigned char msg[4];
+ int len;
+
+ read_lock(&rdev->filelist_lock);
+ list_for_each(list, &rdev->filelist) {
+ vmidi = list_entry(list, snd_virmidi_t, list);
+ if (!vmidi->trigger)
+ continue;
+ if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+ continue;
+ snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream);
+ } else {
+ len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev);
+ if (len > 0)
+ snd_rawmidi_receive(vmidi->substream, msg, len);
+ }
+ }
+ read_unlock(&rdev->filelist_lock);
+
+ return 0;
+}
+
+/*
+ * receive an event from the remote virmidi port
+ *
+ * for rawmidi inputs, you can call this function from the event
+ * handler of a remote port which is attached to the virmidi via
+ * SNDRV_VIRMIDI_SEQ_ATTACH.
+ */
+/* exported */
+int snd_virmidi_receive(snd_rawmidi_t *rmidi, snd_seq_event_t *ev)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = rmidi->private_data;
+ return snd_virmidi_dev_receive_event(rdev, ev);
+}
+
+/*
+ * event handler of virmidi port
+ */
+static int snd_virmidi_event_input(snd_seq_event_t *ev, int direct,
+ void *private_data, int atomic, int hop)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = private_data;
+ if (!(rdev->flags & SNDRV_VIRMIDI_USE))
+ return 0; /* ignored */
+ return snd_virmidi_dev_receive_event(rdev, ev);
+}
+
+/*
+ * trigger rawmidi stream for input
+ */
+static void snd_virmidi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ snd_virmidi_t *vmidi = substream->runtime->private_data;
+
+ if (up) {
+ vmidi->trigger = 1;
+ } else {
+ vmidi->trigger = 0;
+ }
+}
+
+/*
+ * trigger rawmidi stream for output
+ */
+static void snd_virmidi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+ snd_virmidi_t *vmidi = substream->runtime->private_data;
+ int count, res;
+ unsigned char buf[32], *pbuf;
+
+ if (up) {
+ vmidi->trigger = 1;
+ if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH &&
+ !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) {
+ snd_rawmidi_transmit_ack(substream, substream->runtime->buffer_size - substream->runtime->avail);
+ return; /* ignored */
+ }
+ if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
+ if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
+ return;
+ vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
+ }
+ while (1) {
+ count = snd_rawmidi_transmit_peek(substream, buf, sizeof(buf));
+ if (count <= 0)
+ break;
+ pbuf = buf;
+ while (count > 0) {
+ res = snd_midi_event_encode(vmidi->parser, pbuf, count, &vmidi->event);
+ if (res < 0) {
+ snd_midi_event_reset_encode(vmidi->parser);
+ continue;
+ }
+ snd_rawmidi_transmit_ack(substream, res);
+ pbuf += res;
+ count -= res;
+ if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
+ if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0)
+ return;
+ vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
+ }
+ }
+ }
+ } else {
+ vmidi->trigger = 0;
+ }
+}
+
+/*
+ * open rawmidi handle for input
+ */
+static int snd_virmidi_input_open(snd_rawmidi_substream_t * substream)
+{
+ snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+ snd_virmidi_t *vmidi;
+ unsigned long flags;
+
+ vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
+ if (vmidi == NULL)
+ return -ENOMEM;
+ vmidi->substream = substream;
+ if (snd_midi_event_new(0, &vmidi->parser) < 0) {
+ kfree(vmidi);
+ return -ENOMEM;
+ }
+ vmidi->seq_mode = rdev->seq_mode;
+ vmidi->client = rdev->client;
+ vmidi->port = rdev->port;
+ runtime->private_data = vmidi;
+ write_lock_irqsave(&rdev->filelist_lock, flags);
+ list_add_tail(&vmidi->list, &rdev->filelist);
+ write_unlock_irqrestore(&rdev->filelist_lock, flags);
+ vmidi->rdev = rdev;
+ return 0;
+}
+
+/*
+ * open rawmidi handle for output
+ */
+static int snd_virmidi_output_open(snd_rawmidi_substream_t * substream)
+{
+ snd_virmidi_dev_t *rdev = substream->rmidi->private_data;
+ snd_rawmidi_runtime_t *runtime = substream->runtime;
+ snd_virmidi_t *vmidi;
+
+ vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL);
+ if (vmidi == NULL)
+ return -ENOMEM;
+ vmidi->substream = substream;
+ if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) {
+ kfree(vmidi);
+ return -ENOMEM;
+ }
+ vmidi->seq_mode = rdev->seq_mode;
+ vmidi->client = rdev->client;
+ vmidi->port = rdev->port;
+ snd_virmidi_init_event(vmidi, &vmidi->event);
+ vmidi->rdev = rdev;
+ runtime->private_data = vmidi;
+ return 0;
+}
+
+/*
+ * close rawmidi handle for input
+ */
+static int snd_virmidi_input_close(snd_rawmidi_substream_t * substream)
+{
+ snd_virmidi_t *vmidi = substream->runtime->private_data;
+ snd_midi_event_free(vmidi->parser);
+ list_del(&vmidi->list);
+ substream->runtime->private_data = NULL;
+ kfree(vmidi);
+ return 0;
+}
+
+/*
+ * close rawmidi handle for output
+ */
+static int snd_virmidi_output_close(snd_rawmidi_substream_t * substream)
+{
+ snd_virmidi_t *vmidi = substream->runtime->private_data;
+ snd_midi_event_free(vmidi->parser);
+ substream->runtime->private_data = NULL;
+ kfree(vmidi);
+ return 0;
+}
+
+/*
+ * subscribe callback - allow output to rawmidi device
+ */
+static int snd_virmidi_subscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = private_data;
+ if (!try_module_get(rdev->card->module))
+ return -EFAULT;
+ rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE;
+ return 0;
+}
+
+/*
+ * unsubscribe callback - disallow output to rawmidi device
+ */
+static int snd_virmidi_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = private_data;
+ rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE;
+ module_put(rdev->card->module);
+ return 0;
+}
+
+
+/*
+ * use callback - allow input to rawmidi device
+ */
+static int snd_virmidi_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = private_data;
+ if (!try_module_get(rdev->card->module))
+ return -EFAULT;
+ rdev->flags |= SNDRV_VIRMIDI_USE;
+ return 0;
+}
+
+/*
+ * unuse callback - disallow input to rawmidi device
+ */
+static int snd_virmidi_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ snd_virmidi_dev_t *rdev;
+
+ rdev = private_data;
+ rdev->flags &= ~SNDRV_VIRMIDI_USE;
+ module_put(rdev->card->module);
+ return 0;
+}
+
+
+/*
+ * Register functions
+ */
+
+static snd_rawmidi_ops_t snd_virmidi_input_ops = {
+ .open = snd_virmidi_input_open,
+ .close = snd_virmidi_input_close,
+ .trigger = snd_virmidi_input_trigger,
+};
+
+static snd_rawmidi_ops_t snd_virmidi_output_ops = {
+ .open = snd_virmidi_output_open,
+ .close = snd_virmidi_output_close,
+ .trigger = snd_virmidi_output_trigger,
+};
+
+/*
+ * create a sequencer client and a port
+ */
+static int snd_virmidi_dev_attach_seq(snd_virmidi_dev_t *rdev)
+{
+ int client;
+ snd_seq_client_callback_t callbacks;
+ snd_seq_port_callback_t pcallbacks;
+ snd_seq_client_info_t *info;
+ snd_seq_port_info_t *pinfo;
+ int err;
+
+ if (rdev->client >= 0)
+ return 0;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ pinfo = kmalloc(sizeof(*pinfo), GFP_KERNEL);
+ if (! info || ! pinfo) {
+ err = -ENOMEM;
+ goto __error;
+ }
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.private_data = rdev;
+ callbacks.allow_input = 1;
+ callbacks.allow_output = 1;
+ client = snd_seq_create_kernel_client(rdev->card, rdev->device, &callbacks);
+ if (client < 0) {
+ err = client;
+ goto __error;
+ }
+ rdev->client = client;
+
+ /* set client name */
+ memset(info, 0, sizeof(*info));
+ info->client = client;
+ info->type = KERNEL_CLIENT;
+ sprintf(info->name, "%s %d-%d", rdev->rmidi->name, rdev->card->number, rdev->device);
+ snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info);
+
+ /* create a port */
+ memset(pinfo, 0, sizeof(*pinfo));
+ pinfo->addr.client = client;
+ sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device);
+ /* set all capabilities */
+ pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+ pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
+ pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
+ pinfo->midi_channels = 16;
+ memset(&pcallbacks, 0, sizeof(pcallbacks));
+ pcallbacks.owner = THIS_MODULE;
+ pcallbacks.private_data = rdev;
+ pcallbacks.subscribe = snd_virmidi_subscribe;
+ pcallbacks.unsubscribe = snd_virmidi_unsubscribe;
+ pcallbacks.use = snd_virmidi_use;
+ pcallbacks.unuse = snd_virmidi_unuse;
+ pcallbacks.event_input = snd_virmidi_event_input;
+ pinfo->kernel = &pcallbacks;
+ err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo);
+ if (err < 0) {
+ snd_seq_delete_kernel_client(client);
+ rdev->client = -1;
+ goto __error;
+ }
+
+ rdev->port = pinfo->addr.port;
+ err = 0; /* success */
+
+ __error:
+ kfree(info);
+ kfree(pinfo);
+ return err;
+}
+
+
+/*
+ * release the sequencer client
+ */
+static void snd_virmidi_dev_detach_seq(snd_virmidi_dev_t *rdev)
+{
+ if (rdev->client >= 0) {
+ snd_seq_delete_kernel_client(rdev->client);
+ rdev->client = -1;
+ }
+}
+
+/*
+ * register the device
+ */
+static int snd_virmidi_dev_register(snd_rawmidi_t *rmidi)
+{
+ snd_virmidi_dev_t *rdev = rmidi->private_data;
+ int err;
+
+ switch (rdev->seq_mode) {
+ case SNDRV_VIRMIDI_SEQ_DISPATCH:
+ err = snd_virmidi_dev_attach_seq(rdev);
+ if (err < 0)
+ return err;
+ break;
+ case SNDRV_VIRMIDI_SEQ_ATTACH:
+ if (rdev->client == 0)
+ return -EINVAL;
+ /* should check presence of port more strictly.. */
+ break;
+ default:
+ snd_printk(KERN_ERR "seq_mode is not set: %d\n", rdev->seq_mode);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+/*
+ * unregister the device
+ */
+static int snd_virmidi_dev_unregister(snd_rawmidi_t *rmidi)
+{
+ snd_virmidi_dev_t *rdev = rmidi->private_data;
+
+ if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
+ snd_virmidi_dev_detach_seq(rdev);
+ return 0;
+}
+
+/*
+ *
+ */
+static snd_rawmidi_global_ops_t snd_virmidi_global_ops = {
+ .dev_register = snd_virmidi_dev_register,
+ .dev_unregister = snd_virmidi_dev_unregister,
+};
+
+/*
+ * free device
+ */
+static void snd_virmidi_free(snd_rawmidi_t *rmidi)
+{
+ snd_virmidi_dev_t *rdev = rmidi->private_data;
+ kfree(rdev);
+}
+
+/*
+ * create a new device
+ *
+ */
+/* exported */
+int snd_virmidi_new(snd_card_t *card, int device, snd_rawmidi_t **rrmidi)
+{
+ snd_rawmidi_t *rmidi;
+ snd_virmidi_dev_t *rdev;
+ int err;
+
+ *rrmidi = NULL;
+ if ((err = snd_rawmidi_new(card, "VirMidi", device,
+ 16, /* may be configurable */
+ 16, /* may be configurable */
+ &rmidi)) < 0)
+ return err;
+ strcpy(rmidi->name, rmidi->id);
+ rdev = kcalloc(1, sizeof(*rdev), GFP_KERNEL);
+ if (rdev == NULL) {
+ snd_device_free(card, rmidi);
+ return -ENOMEM;
+ }
+ rdev->card = card;
+ rdev->rmidi = rmidi;
+ rdev->device = device;
+ rdev->client = -1;
+ rwlock_init(&rdev->filelist_lock);
+ INIT_LIST_HEAD(&rdev->filelist);
+ rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
+ rmidi->private_data = rdev;
+ rmidi->private_free = snd_virmidi_free;
+ rmidi->ops = &snd_virmidi_global_ops;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops);
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ *rrmidi = rmidi;
+ return 0;
+}
+
+/*
+ * ENTRY functions
+ */
+
+static int __init alsa_virmidi_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_virmidi_exit(void)
+{
+}
+
+module_init(alsa_virmidi_init)
+module_exit(alsa_virmidi_exit)
+
+EXPORT_SYMBOL(snd_virmidi_new);
+EXPORT_SYMBOL(snd_virmidi_receive);
diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c
new file mode 100644
index 00000000000..74745da9deb
--- /dev/null
+++ b/sound/core/sgbuf.c
@@ -0,0 +1,111 @@
+/*
+ * Scatter-Gather buffer
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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/config.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <sound/memalloc.h>
+
+
+/* table entries are align to 32 */
+#define SGBUF_TBL_ALIGN 32
+#define sgbuf_align_table(tbl) ((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * SGBUF_TBL_ALIGN)
+
+int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
+{
+ struct snd_sg_buf *sgbuf = dmab->private_data;
+ struct snd_dma_buffer tmpb;
+ int i;
+
+ if (! sgbuf)
+ return -EINVAL;
+
+ tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
+ tmpb.dev.dev = sgbuf->dev;
+ for (i = 0; i < sgbuf->pages; i++) {
+ tmpb.area = sgbuf->table[i].buf;
+ tmpb.addr = sgbuf->table[i].addr;
+ tmpb.bytes = PAGE_SIZE;
+ snd_dma_free_pages(&tmpb);
+ }
+ if (dmab->area)
+ vunmap(dmab->area);
+ dmab->area = NULL;
+
+ kfree(sgbuf->table);
+ kfree(sgbuf->page_table);
+ kfree(sgbuf);
+ dmab->private_data = NULL;
+
+ return 0;
+}
+
+void *snd_malloc_sgbuf_pages(struct device *device,
+ size_t size, struct snd_dma_buffer *dmab,
+ size_t *res_size)
+{
+ struct snd_sg_buf *sgbuf;
+ unsigned int i, pages;
+ struct snd_dma_buffer tmpb;
+
+ dmab->area = NULL;
+ dmab->addr = 0;
+ dmab->private_data = sgbuf = kmalloc(sizeof(*sgbuf), GFP_KERNEL);
+ if (! sgbuf)
+ return NULL;
+ memset(sgbuf, 0, sizeof(*sgbuf));
+ sgbuf->dev = device;
+ pages = snd_sgbuf_aligned_pages(size);
+ sgbuf->tblsize = sgbuf_align_table(pages);
+ sgbuf->table = kmalloc(sizeof(*sgbuf->table) * sgbuf->tblsize, GFP_KERNEL);
+ if (! sgbuf->table)
+ goto _failed;
+ memset(sgbuf->table, 0, sizeof(*sgbuf->table) * sgbuf->tblsize);
+ sgbuf->page_table = kmalloc(sizeof(*sgbuf->page_table) * sgbuf->tblsize, GFP_KERNEL);
+ if (! sgbuf->page_table)
+ goto _failed;
+ memset(sgbuf->page_table, 0, sizeof(*sgbuf->page_table) * sgbuf->tblsize);
+
+ /* allocate each page */
+ for (i = 0; i < pages; i++) {
+ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
+ if (res_size == NULL)
+ goto _failed;
+ *res_size = size = sgbuf->pages * PAGE_SIZE;
+ break;
+ }
+ sgbuf->table[i].buf = tmpb.area;
+ sgbuf->table[i].addr = tmpb.addr;
+ sgbuf->page_table[i] = virt_to_page(tmpb.area);
+ sgbuf->pages++;
+ }
+
+ sgbuf->size = size;
+ dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
+ if (! dmab->area)
+ goto _failed;
+ return dmab->area;
+
+ _failed:
+ snd_free_sgbuf_pages(dmab); /* free the table */
+ return NULL;
+}
diff --git a/sound/core/sound.c b/sound/core/sound.c
new file mode 100644
index 00000000000..88e052079f8
--- /dev/null
+++ b/sound/core/sound.c
@@ -0,0 +1,491 @@
+/*
+ * Advanced Linux Sound Architecture
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+
+#define SNDRV_OS_MINORS 256
+
+static int major = CONFIG_SND_MAJOR;
+int snd_major;
+static int cards_limit = 1;
+static int device_mode = S_IFCHR | S_IRUGO | S_IWUGO;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards.");
+MODULE_LICENSE("GPL");
+module_param(major, int, 0444);
+MODULE_PARM_DESC(major, "Major # for sound driver.");
+module_param(cards_limit, int, 0444);
+MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards.");
+#ifdef CONFIG_DEVFS_FS
+module_param(device_mode, int, 0444);
+MODULE_PARM_DESC(device_mode, "Device file permission mask for devfs.");
+#endif
+MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR);
+
+/* this one holds the actual max. card number currently available.
+ * as default, it's identical with cards_limit option. when more
+ * modules are loaded manually, this limit number increases, too.
+ */
+int snd_ecards_limit;
+
+static struct list_head snd_minors_hash[SNDRV_CARDS];
+
+static DECLARE_MUTEX(sound_mutex);
+
+extern struct class_simple *sound_class;
+
+
+#ifdef CONFIG_KMOD
+
+/**
+ * snd_request_card - try to load the card module
+ * @card: the card number
+ *
+ * Tries to load the module "snd-card-X" for the given card number
+ * via KMOD. Returns immediately if already loaded.
+ */
+void snd_request_card(int card)
+{
+ int locked;
+
+ if (! current->fs->root)
+ return;
+ read_lock(&snd_card_rwlock);
+ locked = snd_cards_lock & (1 << card);
+ read_unlock(&snd_card_rwlock);
+ if (locked)
+ return;
+ if (card < 0 || card >= cards_limit)
+ return;
+ request_module("snd-card-%i", card);
+}
+
+static void snd_request_other(int minor)
+{
+ char *str;
+
+ if (! current->fs->root)
+ return;
+ switch (minor) {
+ case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break;
+ case SNDRV_MINOR_TIMER: str = "snd-timer"; break;
+ default: return;
+ }
+ request_module(str);
+}
+
+#endif /* request_module support */
+
+static snd_minor_t *snd_minor_search(int minor)
+{
+ struct list_head *list;
+ snd_minor_t *mptr;
+
+ list_for_each(list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]) {
+ mptr = list_entry(list, snd_minor_t, list);
+ if (mptr->number == minor)
+ return mptr;
+ }
+ return NULL;
+}
+
+static int snd_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ int card = SNDRV_MINOR_CARD(minor);
+ int dev = SNDRV_MINOR_DEVICE(minor);
+ snd_minor_t *mptr = NULL;
+ struct file_operations *old_fops;
+ int err = 0;
+
+ if (dev != SNDRV_MINOR_SEQUENCER && dev != SNDRV_MINOR_TIMER) {
+ if (snd_cards[card] == NULL) {
+#ifdef CONFIG_KMOD
+ snd_request_card(card);
+ if (snd_cards[card] == NULL)
+#endif
+ return -ENODEV;
+ }
+ } else {
+#ifdef CONFIG_KMOD
+ if ((mptr = snd_minor_search(minor)) == NULL)
+ snd_request_other(minor);
+#endif
+ }
+ if (mptr == NULL && (mptr = snd_minor_search(minor)) == NULL)
+ return -ENODEV;
+ old_fops = file->f_op;
+ file->f_op = fops_get(mptr->f_ops);
+ if (file->f_op->open)
+ err = file->f_op->open(inode, file);
+ if (err) {
+ fops_put(file->f_op);
+ file->f_op = fops_get(old_fops);
+ }
+ fops_put(old_fops);
+ return err;
+}
+
+static struct file_operations snd_fops =
+{
+ .owner = THIS_MODULE,
+ .open = snd_open
+};
+
+static int snd_kernel_minor(int type, snd_card_t * card, int dev)
+{
+ int minor;
+
+ switch (type) {
+ case SNDRV_DEVICE_TYPE_SEQUENCER:
+ case SNDRV_DEVICE_TYPE_TIMER:
+ minor = type;
+ break;
+ case SNDRV_DEVICE_TYPE_CONTROL:
+ snd_assert(card != NULL, return -EINVAL);
+ minor = SNDRV_MINOR(card->number, type);
+ break;
+ case SNDRV_DEVICE_TYPE_HWDEP:
+ case SNDRV_DEVICE_TYPE_RAWMIDI:
+ case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
+ case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
+ snd_assert(card != NULL, return -EINVAL);
+ minor = SNDRV_MINOR(card->number, type + dev);
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
+ return minor;
+}
+
+/**
+ * snd_register_device - Register the ALSA device file for the card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ * @reg: the snd_minor_t record
+ * @name: the device file name
+ *
+ * Registers an ALSA device file for the given card.
+ * The operators have to be set in reg parameter.
+ *
+ * Retrurns zero if successful, or a negative error code on failure.
+ */
+int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
+{
+ int minor = snd_kernel_minor(type, card, dev);
+ snd_minor_t *preg;
+ struct device *device = NULL;
+
+ if (minor < 0)
+ return minor;
+ snd_assert(name, return -EINVAL);
+ preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t) + strlen(name) + 1, GFP_KERNEL);
+ if (preg == NULL)
+ return -ENOMEM;
+ *preg = *reg;
+ preg->number = minor;
+ preg->device = dev;
+ strcpy(preg->name, name);
+ down(&sound_mutex);
+ if (snd_minor_search(minor)) {
+ up(&sound_mutex);
+ kfree(preg);
+ return -EBUSY;
+ }
+ list_add_tail(&preg->list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]);
+ if (strncmp(name, "controlC", 8) || card->number >= cards_limit)
+ devfs_mk_cdev(MKDEV(major, minor), S_IFCHR | device_mode, "snd/%s", name);
+ if (card)
+ device = card->dev;
+ class_simple_device_add(sound_class, MKDEV(major, minor), device, name);
+
+ up(&sound_mutex);
+ return 0;
+}
+
+/**
+ * snd_unregister_device - unregister the device on the given card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ *
+ * Unregisters the device file already registered via
+ * snd_register_device().
+ *
+ * Returns zero if sucecessful, or a negative error code on failure
+ */
+int snd_unregister_device(int type, snd_card_t * card, int dev)
+{
+ int minor = snd_kernel_minor(type, card, dev);
+ snd_minor_t *mptr;
+
+ if (minor < 0)
+ return minor;
+ down(&sound_mutex);
+ if ((mptr = snd_minor_search(minor)) == NULL) {
+ up(&sound_mutex);
+ return -EINVAL;
+ }
+
+ if (strncmp(mptr->name, "controlC", 8) || card->number >= cards_limit) /* created in sound.c */
+ devfs_remove("snd/%s", mptr->name);
+ class_simple_device_remove(MKDEV(major, minor));
+
+ list_del(&mptr->list);
+ up(&sound_mutex);
+ kfree(mptr);
+ return 0;
+}
+
+/*
+ * INFO PART
+ */
+
+static snd_info_entry_t *snd_minor_info_entry = NULL;
+
+static void snd_minor_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int card, device;
+ struct list_head *list;
+ snd_minor_t *mptr;
+
+ down(&sound_mutex);
+ for (card = 0; card < SNDRV_CARDS; card++) {
+ list_for_each(list, &snd_minors_hash[card]) {
+ mptr = list_entry(list, snd_minor_t, list);
+ if (SNDRV_MINOR_DEVICE(mptr->number) != SNDRV_MINOR_SEQUENCER) {
+ if ((device = mptr->device) >= 0)
+ snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, device, mptr->comment);
+ else
+ snd_iprintf(buffer, "%3i: [%i] : %s\n", mptr->number, card, mptr->comment);
+ } else {
+ snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment);
+ }
+ }
+ }
+ up(&sound_mutex);
+}
+
+int __init snd_minor_info_init(void)
+{
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
+ if (entry) {
+ entry->c.text.read_size = PAGE_SIZE;
+ entry->c.text.read = snd_minor_info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_minor_info_entry = entry;
+ return 0;
+}
+
+int __exit snd_minor_info_done(void)
+{
+ if (snd_minor_info_entry)
+ snd_info_unregister(snd_minor_info_entry);
+ return 0;
+}
+
+/*
+ * INIT PART
+ */
+
+static int __init alsa_sound_init(void)
+{
+ short controlnum;
+ int err;
+ int card;
+
+ snd_major = major;
+ snd_ecards_limit = cards_limit;
+ for (card = 0; card < SNDRV_CARDS; card++)
+ INIT_LIST_HEAD(&snd_minors_hash[card]);
+ if ((err = snd_oss_init_module()) < 0)
+ return err;
+ devfs_mk_dir("snd");
+ if (register_chrdev(major, "alsa", &snd_fops)) {
+ snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
+ devfs_remove("snd");
+ return -EIO;
+ }
+ snd_memory_init();
+ if (snd_info_init() < 0) {
+ snd_memory_done();
+ unregister_chrdev(major, "alsa");
+ devfs_remove("snd");
+ return -ENOMEM;
+ }
+ snd_info_minor_register();
+ for (controlnum = 0; controlnum < cards_limit; controlnum++)
+ devfs_mk_cdev(MKDEV(major, controlnum<<5), S_IFCHR | device_mode, "snd/controlC%d", controlnum);
+#ifndef MODULE
+ printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
+#endif
+ return 0;
+}
+
+static void __exit alsa_sound_exit(void)
+{
+ short controlnum;
+
+ for (controlnum = 0; controlnum < cards_limit; controlnum++)
+ devfs_remove("snd/controlC%d", controlnum);
+
+ snd_info_minor_unregister();
+ snd_info_done();
+ snd_memory_done();
+ if (unregister_chrdev(major, "alsa") != 0)
+ snd_printk(KERN_ERR "unable to unregister major device number %d\n", major);
+ devfs_remove("snd");
+}
+
+module_init(alsa_sound_init)
+module_exit(alsa_sound_exit)
+
+ /* sound.c */
+EXPORT_SYMBOL(snd_major);
+EXPORT_SYMBOL(snd_ecards_limit);
+#if defined(CONFIG_KMOD)
+EXPORT_SYMBOL(snd_request_card);
+#endif
+EXPORT_SYMBOL(snd_register_device);
+EXPORT_SYMBOL(snd_unregister_device);
+#if defined(CONFIG_SND_OSSEMUL)
+EXPORT_SYMBOL(snd_register_oss_device);
+EXPORT_SYMBOL(snd_unregister_oss_device);
+#endif
+ /* memory.c */
+#ifdef CONFIG_SND_DEBUG_MEMORY
+EXPORT_SYMBOL(snd_hidden_kmalloc);
+EXPORT_SYMBOL(snd_hidden_kcalloc);
+EXPORT_SYMBOL(snd_hidden_kfree);
+EXPORT_SYMBOL(snd_hidden_vmalloc);
+EXPORT_SYMBOL(snd_hidden_vfree);
+#endif
+EXPORT_SYMBOL(snd_kmalloc_strdup);
+EXPORT_SYMBOL(copy_to_user_fromio);
+EXPORT_SYMBOL(copy_from_user_toio);
+ /* init.c */
+EXPORT_SYMBOL(snd_cards);
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
+#endif
+EXPORT_SYMBOL(snd_card_new);
+EXPORT_SYMBOL(snd_card_disconnect);
+EXPORT_SYMBOL(snd_card_free);
+EXPORT_SYMBOL(snd_card_free_in_thread);
+EXPORT_SYMBOL(snd_card_register);
+EXPORT_SYMBOL(snd_component_add);
+EXPORT_SYMBOL(snd_card_file_add);
+EXPORT_SYMBOL(snd_card_file_remove);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_power_wait);
+EXPORT_SYMBOL(snd_card_set_pm_callback);
+#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
+EXPORT_SYMBOL(snd_card_set_generic_pm_callback);
+#endif
+#ifdef CONFIG_PCI
+EXPORT_SYMBOL(snd_card_pci_suspend);
+EXPORT_SYMBOL(snd_card_pci_resume);
+#endif
+#endif
+ /* device.c */
+EXPORT_SYMBOL(snd_device_new);
+EXPORT_SYMBOL(snd_device_register);
+EXPORT_SYMBOL(snd_device_free);
+EXPORT_SYMBOL(snd_device_free_all);
+ /* isadma.c */
+#ifdef CONFIG_ISA
+EXPORT_SYMBOL(snd_dma_program);
+EXPORT_SYMBOL(snd_dma_disable);
+EXPORT_SYMBOL(snd_dma_pointer);
+#endif
+ /* info.c */
+#ifdef CONFIG_PROC_FS
+EXPORT_SYMBOL(snd_seq_root);
+EXPORT_SYMBOL(snd_iprintf);
+EXPORT_SYMBOL(snd_info_get_line);
+EXPORT_SYMBOL(snd_info_get_str);
+EXPORT_SYMBOL(snd_info_create_module_entry);
+EXPORT_SYMBOL(snd_info_create_card_entry);
+EXPORT_SYMBOL(snd_info_free_entry);
+EXPORT_SYMBOL(snd_info_register);
+EXPORT_SYMBOL(snd_info_unregister);
+EXPORT_SYMBOL(snd_card_proc_new);
+#endif
+ /* info_oss.c */
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+EXPORT_SYMBOL(snd_oss_info_register);
+#endif
+ /* control.c */
+EXPORT_SYMBOL(snd_ctl_new);
+EXPORT_SYMBOL(snd_ctl_new1);
+EXPORT_SYMBOL(snd_ctl_free_one);
+EXPORT_SYMBOL(snd_ctl_add);
+EXPORT_SYMBOL(snd_ctl_remove);
+EXPORT_SYMBOL(snd_ctl_remove_id);
+EXPORT_SYMBOL(snd_ctl_rename_id);
+EXPORT_SYMBOL(snd_ctl_find_numid);
+EXPORT_SYMBOL(snd_ctl_find_id);
+EXPORT_SYMBOL(snd_ctl_notify);
+EXPORT_SYMBOL(snd_ctl_register_ioctl);
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
+#ifdef CONFIG_COMPAT
+EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
+#endif
+EXPORT_SYMBOL(snd_ctl_elem_read);
+EXPORT_SYMBOL(snd_ctl_elem_write);
+ /* misc.c */
+EXPORT_SYMBOL(snd_task_name);
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+EXPORT_SYMBOL(snd_verbose_printk);
+#endif
+#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
+EXPORT_SYMBOL(snd_verbose_printd);
+#endif
+ /* wrappers */
+#ifdef CONFIG_SND_DEBUG_MEMORY
+EXPORT_SYMBOL(snd_wrapper_kmalloc);
+EXPORT_SYMBOL(snd_wrapper_kfree);
+EXPORT_SYMBOL(snd_wrapper_vmalloc);
+EXPORT_SYMBOL(snd_wrapper_vfree);
+#endif
diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c
new file mode 100644
index 00000000000..de39d212bc1
--- /dev/null
+++ b/sound/core/sound_oss.c
@@ -0,0 +1,250 @@
+/*
+ * Advanced Linux Sound Architecture
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+
+#ifdef CONFIG_SND_OSSEMUL
+
+#if !defined(CONFIG_SOUND) && !(defined(MODULE) && defined(CONFIG_SOUND_MODULE))
+#error "Enable the OSS soundcore multiplexer (CONFIG_SOUND) in the kernel."
+#endif
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <linux/sound.h>
+
+#define SNDRV_OS_MINORS 256
+
+static struct list_head snd_oss_minors_hash[SNDRV_CARDS];
+
+static DECLARE_MUTEX(sound_oss_mutex);
+
+static snd_minor_t *snd_oss_minor_search(int minor)
+{
+ struct list_head *list;
+ snd_minor_t *mptr;
+
+ list_for_each(list, &snd_oss_minors_hash[SNDRV_MINOR_OSS_CARD(minor)]) {
+ mptr = list_entry(list, snd_minor_t, list);
+ if (mptr->number == minor)
+ return mptr;
+ }
+ return NULL;
+}
+
+static int snd_oss_kernel_minor(int type, snd_card_t * card, int dev)
+{
+ int minor;
+
+ switch (type) {
+ case SNDRV_OSS_DEVICE_TYPE_MIXER:
+ snd_assert(card != NULL && dev <= 1, return -EINVAL);
+ minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER));
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
+ minor = SNDRV_MINOR_OSS_SEQUENCER;
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_MUSIC:
+ minor = SNDRV_MINOR_OSS_MUSIC;
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_PCM:
+ snd_assert(card != NULL && dev <= 1, return -EINVAL);
+ minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM));
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_MIDI:
+ snd_assert(card != NULL && dev <= 1, return -EINVAL);
+ minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI));
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_DMFM:
+ minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM);
+ break;
+ case SNDRV_OSS_DEVICE_TYPE_SNDSTAT:
+ minor = SNDRV_MINOR_OSS_SNDSTAT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
+ return minor;
+}
+
+int snd_register_oss_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
+{
+ int minor = snd_oss_kernel_minor(type, card, dev);
+ int minor_unit;
+ snd_minor_t *preg;
+ int cidx = SNDRV_MINOR_OSS_CARD(minor);
+ int track2 = -1;
+ int register1 = -1, register2 = -1;
+
+ if (minor < 0)
+ return minor;
+ preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t), GFP_KERNEL);
+ if (preg == NULL)
+ return -ENOMEM;
+ *preg = *reg;
+ preg->number = minor;
+ preg->device = dev;
+ down(&sound_oss_mutex);
+ list_add_tail(&preg->list, &snd_oss_minors_hash[cidx]);
+ minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);
+ switch (minor_unit) {
+ case SNDRV_MINOR_OSS_PCM:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
+ break;
+ case SNDRV_MINOR_OSS_MIDI:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
+ break;
+ case SNDRV_MINOR_OSS_MIDI1:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
+ break;
+ }
+ register1 = register_sound_special(reg->f_ops, minor);
+ if (register1 != minor)
+ goto __end;
+ if (track2 >= 0) {
+ register2 = register_sound_special(reg->f_ops, track2);
+ if (register2 != track2)
+ goto __end;
+ }
+ up(&sound_oss_mutex);
+ return 0;
+
+ __end:
+ if (register2 >= 0)
+ unregister_sound_special(register2);
+ if (register1 >= 0)
+ unregister_sound_special(register1);
+ list_del(&preg->list);
+ up(&sound_oss_mutex);
+ kfree(preg);
+ return -EBUSY;
+}
+
+int snd_unregister_oss_device(int type, snd_card_t * card, int dev)
+{
+ int minor = snd_oss_kernel_minor(type, card, dev);
+ int cidx = SNDRV_MINOR_OSS_CARD(minor);
+ int track2 = -1;
+ snd_minor_t *mptr;
+
+ if (minor < 0)
+ return minor;
+ down(&sound_oss_mutex);
+ mptr = snd_oss_minor_search(minor);
+ if (mptr == NULL) {
+ up(&sound_oss_mutex);
+ return -ENOENT;
+ }
+ unregister_sound_special(minor);
+ switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
+ case SNDRV_MINOR_OSS_PCM:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
+ break;
+ case SNDRV_MINOR_OSS_MIDI:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
+ break;
+ case SNDRV_MINOR_OSS_MIDI1:
+ track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
+ break;
+ }
+ if (track2 >= 0)
+ unregister_sound_special(track2);
+ list_del(&mptr->list);
+ up(&sound_oss_mutex);
+ kfree(mptr);
+ return 0;
+}
+
+/*
+ * INFO PART
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static snd_info_entry_t *snd_minor_info_oss_entry = NULL;
+
+static void snd_minor_info_oss_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ int card, dev;
+ struct list_head *list;
+ snd_minor_t *mptr;
+
+ down(&sound_oss_mutex);
+ for (card = 0; card < SNDRV_CARDS; card++) {
+ list_for_each(list, &snd_oss_minors_hash[card]) {
+ mptr = list_entry(list, snd_minor_t, list);
+ dev = SNDRV_MINOR_OSS_DEVICE(mptr->number);
+ if (dev != SNDRV_MINOR_OSS_SNDSTAT &&
+ dev != SNDRV_MINOR_OSS_SEQUENCER &&
+ dev != SNDRV_MINOR_OSS_MUSIC)
+ snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, dev, mptr->comment);
+ else
+ snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment);
+ }
+ }
+ up(&sound_oss_mutex);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+int __init snd_minor_info_oss_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ snd_info_entry_t *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root);
+ if (entry) {
+ entry->c.text.read_size = PAGE_SIZE;
+ entry->c.text.read = snd_minor_info_oss_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_minor_info_oss_entry = entry;
+#endif
+ return 0;
+}
+
+int __exit snd_minor_info_oss_done(void)
+{
+#ifdef CONFIG_PROC_FS
+ if (snd_minor_info_oss_entry)
+ snd_info_unregister(snd_minor_info_oss_entry);
+#endif
+ return 0;
+}
+
+int __init snd_oss_init_module(void)
+{
+ int card;
+
+ for (card = 0; card < SNDRV_CARDS; card++)
+ INIT_LIST_HEAD(&snd_oss_minors_hash[card]);
+ return 0;
+}
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/sound/core/timer.c b/sound/core/timer.c
new file mode 100644
index 00000000000..fa762ca439b
--- /dev/null
+++ b/sound/core/timer.c
@@ -0,0 +1,1901 @@
+/*
+ * Timers abstract layer
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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 <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/timer.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+#include <linux/kmod.h>
+#ifdef CONFIG_KERNELD
+#include <linux/kerneld.h>
+#endif
+
+#if defined(CONFIG_SND_HPET) || defined(CONFIG_SND_HPET_MODULE)
+#define DEFAULT_TIMER_LIMIT 3
+#elif defined(CONFIG_SND_RTCTIMER) || defined(CONFIG_SND_RTCTIMER_MODULE)
+#define DEFAULT_TIMER_LIMIT 2
+#else
+#define DEFAULT_TIMER_LIMIT 1
+#endif
+
+static int timer_limit = DEFAULT_TIMER_LIMIT;
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA timer interface");
+MODULE_LICENSE("GPL");
+module_param(timer_limit, int, 0444);
+MODULE_PARM_DESC(timer_limit, "Maximum global timers in system.");
+
+typedef struct {
+ snd_timer_instance_t *timeri;
+ int tread; /* enhanced read with timestamps and events */
+ unsigned long ticks;
+ unsigned long overrun;
+ int qhead;
+ int qtail;
+ int qused;
+ int queue_size;
+ snd_timer_read_t *queue;
+ snd_timer_tread_t *tqueue;
+ spinlock_t qlock;
+ unsigned long last_resolution;
+ unsigned int filter;
+ struct timespec tstamp; /* trigger tstamp */
+ wait_queue_head_t qchange_sleep;
+ struct fasync_struct *fasync;
+} snd_timer_user_t;
+
+/* list of timers */
+static LIST_HEAD(snd_timer_list);
+
+/* list of slave instances */
+static LIST_HEAD(snd_timer_slave_list);
+
+/* lock for slave active lists */
+static DEFINE_SPINLOCK(slave_active_lock);
+
+static DECLARE_MUTEX(register_mutex);
+
+static int snd_timer_free(snd_timer_t *timer);
+static int snd_timer_dev_free(snd_device_t *device);
+static int snd_timer_dev_register(snd_device_t *device);
+static int snd_timer_dev_unregister(snd_device_t *device);
+
+static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left);
+
+/*
+ * create a timer instance with the given owner string.
+ * when timer is not NULL, increments the module counter
+ */
+static snd_timer_instance_t *snd_timer_instance_new(char *owner, snd_timer_t *timer)
+{
+ snd_timer_instance_t *timeri;
+ timeri = kcalloc(1, sizeof(*timeri), GFP_KERNEL);
+ if (timeri == NULL)
+ return NULL;
+ timeri->owner = snd_kmalloc_strdup(owner, GFP_KERNEL);
+ if (! timeri->owner) {
+ kfree(timeri);
+ return NULL;
+ }
+ INIT_LIST_HEAD(&timeri->open_list);
+ INIT_LIST_HEAD(&timeri->active_list);
+ INIT_LIST_HEAD(&timeri->ack_list);
+ INIT_LIST_HEAD(&timeri->slave_list_head);
+ INIT_LIST_HEAD(&timeri->slave_active_head);
+
+ timeri->timer = timer;
+ if (timer && timer->card && !try_module_get(timer->card->module)) {
+ kfree(timeri->owner);
+ kfree(timeri);
+ return NULL;
+ }
+
+ return timeri;
+}
+
+/*
+ * find a timer instance from the given timer id
+ */
+static snd_timer_t *snd_timer_find(snd_timer_id_t *tid)
+{
+ snd_timer_t *timer = NULL;
+ struct list_head *p;
+
+ list_for_each(p, &snd_timer_list) {
+ timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+
+ if (timer->tmr_class != tid->dev_class)
+ continue;
+ if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD ||
+ timer->tmr_class == SNDRV_TIMER_CLASS_PCM) &&
+ (timer->card == NULL ||
+ timer->card->number != tid->card))
+ continue;
+ if (timer->tmr_device != tid->device)
+ continue;
+ if (timer->tmr_subdevice != tid->subdevice)
+ continue;
+ return timer;
+ }
+ return NULL;
+}
+
+#ifdef CONFIG_KMOD
+
+static void snd_timer_request(snd_timer_id_t *tid)
+{
+ if (! current->fs->root)
+ return;
+ switch (tid->dev_class) {
+ case SNDRV_TIMER_CLASS_GLOBAL:
+ if (tid->device < timer_limit)
+ request_module("snd-timer-%i", tid->device);
+ break;
+ case SNDRV_TIMER_CLASS_CARD:
+ case SNDRV_TIMER_CLASS_PCM:
+ if (tid->card < snd_ecards_limit)
+ request_module("snd-card-%i", tid->card);
+ break;
+ default:
+ break;
+ }
+}
+
+#endif
+
+/*
+ * look for a master instance matching with the slave id of the given slave.
+ * when found, relink the open_link of the slave.
+ *
+ * call this with register_mutex down.
+ */
+static void snd_timer_check_slave(snd_timer_instance_t *slave)
+{
+ snd_timer_t *timer;
+ snd_timer_instance_t *master;
+ struct list_head *p, *q;
+
+ /* FIXME: it's really dumb to look up all entries.. */
+ list_for_each(p, &snd_timer_list) {
+ timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ list_for_each(q, &timer->open_list_head) {
+ master = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list);
+ if (slave->slave_class == master->slave_class &&
+ slave->slave_id == master->slave_id) {
+ list_del(&slave->open_list);
+ list_add_tail(&slave->open_list, &master->slave_list_head);
+ spin_lock_irq(&slave_active_lock);
+ slave->master = master;
+ slave->timer = master->timer;
+ spin_unlock_irq(&slave_active_lock);
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * look for slave instances matching with the slave id of the given master.
+ * when found, relink the open_link of slaves.
+ *
+ * call this with register_mutex down.
+ */
+static void snd_timer_check_master(snd_timer_instance_t *master)
+{
+ snd_timer_instance_t *slave;
+ struct list_head *p, *n;
+
+ /* check all pending slaves */
+ list_for_each_safe(p, n, &snd_timer_slave_list) {
+ slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+ if (slave->slave_class == master->slave_class &&
+ slave->slave_id == master->slave_id) {
+ list_del(p);
+ list_add_tail(p, &master->slave_list_head);
+ spin_lock_irq(&slave_active_lock);
+ slave->master = master;
+ slave->timer = master->timer;
+ if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
+ list_add_tail(&slave->active_list, &master->slave_active_head);
+ spin_unlock_irq(&slave_active_lock);
+ }
+ }
+}
+
+/*
+ * open a timer instance
+ * when opening a master, the slave id must be here given.
+ */
+int snd_timer_open(snd_timer_instance_t **ti,
+ char *owner, snd_timer_id_t *tid,
+ unsigned int slave_id)
+{
+ snd_timer_t *timer;
+ snd_timer_instance_t *timeri = NULL;
+
+ if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) {
+ /* open a slave instance */
+ if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE ||
+ tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) {
+ snd_printd("invalid slave class %i\n", tid->dev_sclass);
+ return -EINVAL;
+ }
+ down(&register_mutex);
+ timeri = snd_timer_instance_new(owner, NULL);
+ timeri->slave_class = tid->dev_sclass;
+ timeri->slave_id = tid->device;
+ timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
+ list_add_tail(&timeri->open_list, &snd_timer_slave_list);
+ snd_timer_check_slave(timeri);
+ up(&register_mutex);
+ *ti = timeri;
+ return 0;
+ }
+
+ /* open a master instance */
+ down(&register_mutex);
+ timer = snd_timer_find(tid);
+#ifdef CONFIG_KMOD
+ if (timer == NULL) {
+ up(&register_mutex);
+ snd_timer_request(tid);
+ down(&register_mutex);
+ timer = snd_timer_find(tid);
+ }
+#endif
+ if (timer) {
+ if (!list_empty(&timer->open_list_head)) {
+ timeri = (snd_timer_instance_t *)list_entry(timer->open_list_head.next, snd_timer_instance_t, open_list);
+ if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) {
+ up(&register_mutex);
+ return -EBUSY;
+ }
+ }
+ timeri = snd_timer_instance_new(owner, timer);
+ if (timeri) {
+ timeri->slave_class = tid->dev_sclass;
+ timeri->slave_id = slave_id;
+ if (list_empty(&timer->open_list_head) && timer->hw.open)
+ timer->hw.open(timer);
+ list_add_tail(&timeri->open_list, &timer->open_list_head);
+ snd_timer_check_master(timeri);
+ }
+ } else {
+ up(&register_mutex);
+ return -ENODEV;
+ }
+ up(&register_mutex);
+ *ti = timeri;
+ return 0;
+}
+
+static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event);
+
+/*
+ * close a timer instance
+ */
+int snd_timer_close(snd_timer_instance_t * timeri)
+{
+ snd_timer_t *timer = NULL;
+ struct list_head *p, *n;
+ snd_timer_instance_t *slave;
+
+ snd_assert(timeri != NULL, return -ENXIO);
+
+ /* force to stop the timer */
+ snd_timer_stop(timeri);
+
+ if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+ /* wait, until the active callback is finished */
+ spin_lock_irq(&slave_active_lock);
+ while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {
+ spin_unlock_irq(&slave_active_lock);
+ udelay(10);
+ spin_lock_irq(&slave_active_lock);
+ }
+ spin_unlock_irq(&slave_active_lock);
+ down(&register_mutex);
+ list_del(&timeri->open_list);
+ up(&register_mutex);
+ } else {
+ timer = timeri->timer;
+ /* wait, until the active callback is finished */
+ spin_lock_irq(&timer->lock);
+ while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {
+ spin_unlock_irq(&timer->lock);
+ udelay(10);
+ spin_lock_irq(&timer->lock);
+ }
+ spin_unlock_irq(&timer->lock);
+ down(&register_mutex);
+ list_del(&timeri->open_list);
+ if (timer && list_empty(&timer->open_list_head) && timer->hw.close)
+ timer->hw.close(timer);
+ /* remove slave links */
+ list_for_each_safe(p, n, &timeri->slave_list_head) {
+ slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+ spin_lock_irq(&slave_active_lock);
+ _snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION);
+ list_del(p);
+ list_add_tail(p, &snd_timer_slave_list);
+ slave->master = NULL;
+ slave->timer = NULL;
+ spin_unlock_irq(&slave_active_lock);
+ }
+ up(&register_mutex);
+ }
+ if (timeri->private_free)
+ timeri->private_free(timeri);
+ kfree(timeri->owner);
+ kfree(timeri);
+ if (timer && timer->card)
+ module_put(timer->card->module);
+ return 0;
+}
+
+unsigned long snd_timer_resolution(snd_timer_instance_t * timeri)
+{
+ snd_timer_t * timer;
+
+ if (timeri == NULL)
+ return 0;
+ if ((timer = timeri->timer) != NULL) {
+ if (timer->hw.c_resolution)
+ return timer->hw.c_resolution(timer);
+ return timer->hw.resolution;
+ }
+ return 0;
+}
+
+static void snd_timer_notify1(snd_timer_instance_t *ti, enum sndrv_timer_event event)
+{
+ snd_timer_t *timer;
+ unsigned long flags;
+ unsigned long resolution = 0;
+ snd_timer_instance_t *ts;
+ struct list_head *n;
+ struct timespec tstamp;
+
+ snd_timestamp_now(&tstamp, 1);
+ snd_assert(event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE, return);
+ if (event == SNDRV_TIMER_EVENT_START || event == SNDRV_TIMER_EVENT_CONTINUE)
+ resolution = snd_timer_resolution(ti);
+ if (ti->ccallback)
+ ti->ccallback(ti, SNDRV_TIMER_EVENT_START, &tstamp, resolution);
+ if (ti->flags & SNDRV_TIMER_IFLG_SLAVE)
+ return;
+ timer = ti->timer;
+ if (timer == NULL)
+ return;
+ if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ return;
+ spin_lock_irqsave(&timer->lock, flags);
+ list_for_each(n, &ti->slave_active_head) {
+ ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list);
+ if (ts->ccallback)
+ ts->ccallback(ti, event + 100, &tstamp, resolution);
+ }
+ spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+static int snd_timer_start1(snd_timer_t *timer, snd_timer_instance_t *timeri, unsigned long sticks)
+{
+ list_del(&timeri->active_list);
+ list_add_tail(&timeri->active_list, &timer->active_list_head);
+ if (timer->running) {
+ if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ goto __start_now;
+ timer->flags |= SNDRV_TIMER_FLG_RESCHED;
+ timeri->flags |= SNDRV_TIMER_IFLG_START;
+ return 1; /* delayed start */
+ } else {
+ timer->sticks = sticks;
+ timer->hw.start(timer);
+ __start_now:
+ timer->running++;
+ timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;
+ return 0;
+ }
+}
+
+static int snd_timer_start_slave(snd_timer_instance_t *timeri)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&slave_active_lock, flags);
+ timeri->flags |= SNDRV_TIMER_IFLG_RUNNING;
+ if (timeri->master)
+ list_add_tail(&timeri->active_list, &timeri->master->slave_active_head);
+ spin_unlock_irqrestore(&slave_active_lock, flags);
+ return 1; /* delayed start */
+}
+
+/*
+ * start the timer instance
+ */
+int snd_timer_start(snd_timer_instance_t * timeri, unsigned int ticks)
+{
+ snd_timer_t *timer;
+ int result = -EINVAL;
+ unsigned long flags;
+
+ if (timeri == NULL || ticks < 1)
+ return -EINVAL;
+ if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+ result = snd_timer_start_slave(timeri);
+ snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+ return result;
+ }
+ timer = timeri->timer;
+ if (timer == NULL)
+ return -EINVAL;
+ spin_lock_irqsave(&timer->lock, flags);
+ timeri->ticks = timeri->cticks = ticks;
+ timeri->pticks = 0;
+ result = snd_timer_start1(timer, timeri, ticks);
+ spin_unlock_irqrestore(&timer->lock, flags);
+ snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START);
+ return result;
+}
+
+static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event)
+{
+ snd_timer_t *timer;
+ unsigned long flags;
+
+ snd_assert(timeri != NULL, return -ENXIO);
+
+ if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) {
+ if (!keep_flag) {
+ spin_lock_irqsave(&slave_active_lock, flags);
+ timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
+ spin_unlock_irqrestore(&slave_active_lock, flags);
+ }
+ goto __end;
+ }
+ timer = timeri->timer;
+ if (!timer)
+ return -EINVAL;
+ spin_lock_irqsave(&timer->lock, flags);
+ list_del_init(&timeri->ack_list);
+ list_del_init(&timeri->active_list);
+ if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) &&
+ !(--timer->running)) {
+ timer->hw.stop(timer);
+ if (timer->flags & SNDRV_TIMER_FLG_RESCHED) {
+ timer->flags &= ~SNDRV_TIMER_FLG_RESCHED;
+ snd_timer_reschedule(timer, 0);
+ if (timer->flags & SNDRV_TIMER_FLG_CHANGE) {
+ timer->flags &= ~SNDRV_TIMER_FLG_CHANGE;
+ timer->hw.start(timer);
+ }
+ }
+ }
+ if (!keep_flag)
+ timeri->flags &= ~(SNDRV_TIMER_IFLG_RUNNING|SNDRV_TIMER_IFLG_START);
+ spin_unlock_irqrestore(&timer->lock, flags);
+ __end:
+ if (event != SNDRV_TIMER_EVENT_RESOLUTION)
+ snd_timer_notify1(timeri, event);
+ return 0;
+}
+
+/*
+ * stop the timer instance.
+ *
+ * do not call this from the timer callback!
+ */
+int snd_timer_stop(snd_timer_instance_t * timeri)
+{
+ snd_timer_t *timer;
+ unsigned long flags;
+ int err;
+
+ err = _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_STOP);
+ if (err < 0)
+ return err;
+ timer = timeri->timer;
+ spin_lock_irqsave(&timer->lock, flags);
+ timeri->cticks = timeri->ticks;
+ timeri->pticks = 0;
+ spin_unlock_irqrestore(&timer->lock, flags);
+ return 0;
+}
+
+/*
+ * start again.. the tick is kept.
+ */
+int snd_timer_continue(snd_timer_instance_t * timeri)
+{
+ snd_timer_t *timer;
+ int result = -EINVAL;
+ unsigned long flags;
+
+ if (timeri == NULL)
+ return result;
+ if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)
+ return snd_timer_start_slave(timeri);
+ timer = timeri->timer;
+ if (! timer)
+ return -EINVAL;
+ spin_lock_irqsave(&timer->lock, flags);
+ if (!timeri->cticks)
+ timeri->cticks = 1;
+ timeri->pticks = 0;
+ result = snd_timer_start1(timer, timeri, timer->sticks);
+ spin_unlock_irqrestore(&timer->lock, flags);
+ snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_CONTINUE);
+ return result;
+}
+
+/*
+ * pause.. remember the ticks left
+ */
+int snd_timer_pause(snd_timer_instance_t * timeri)
+{
+ return _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_PAUSE);
+}
+
+/*
+ * reschedule the timer
+ *
+ * start pending instances and check the scheduling ticks.
+ * when the scheduling ticks is changed set CHANGE flag to reprogram the timer.
+ */
+static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left)
+{
+ snd_timer_instance_t *ti;
+ unsigned long ticks = ~0UL;
+ struct list_head *p;
+
+ list_for_each(p, &timer->active_list_head) {
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+ if (ti->flags & SNDRV_TIMER_IFLG_START) {
+ ti->flags &= ~SNDRV_TIMER_IFLG_START;
+ ti->flags |= SNDRV_TIMER_IFLG_RUNNING;
+ timer->running++;
+ }
+ if (ti->flags & SNDRV_TIMER_IFLG_RUNNING) {
+ if (ticks > ti->cticks)
+ ticks = ti->cticks;
+ }
+ }
+ if (ticks == ~0UL) {
+ timer->flags &= ~SNDRV_TIMER_FLG_RESCHED;
+ return;
+ }
+ if (ticks > timer->hw.ticks)
+ ticks = timer->hw.ticks;
+ if (ticks_left != ticks)
+ timer->flags |= SNDRV_TIMER_FLG_CHANGE;
+ timer->sticks = ticks;
+}
+
+/*
+ * timer tasklet
+ *
+ */
+static void snd_timer_tasklet(unsigned long arg)
+{
+ snd_timer_t *timer = (snd_timer_t *) arg;
+ snd_timer_instance_t *ti;
+ struct list_head *p;
+ unsigned long resolution, ticks;
+
+ spin_lock(&timer->lock);
+ /* now process all callbacks */
+ while (!list_empty(&timer->sack_list_head)) {
+ p = timer->sack_list_head.next; /* get first item */
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list);
+
+ /* remove from ack_list and make empty */
+ list_del_init(p);
+
+ ticks = ti->pticks;
+ ti->pticks = 0;
+ resolution = ti->resolution;
+
+ ti->flags |= SNDRV_TIMER_IFLG_CALLBACK;
+ spin_unlock(&timer->lock);
+ if (ti->callback)
+ ti->callback(ti, resolution, ticks);
+ spin_lock(&timer->lock);
+ ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK;
+ }
+ spin_unlock(&timer->lock);
+}
+
+/*
+ * timer interrupt
+ *
+ * ticks_left is usually equal to timer->sticks.
+ *
+ */
+void snd_timer_interrupt(snd_timer_t * timer, unsigned long ticks_left)
+{
+ snd_timer_instance_t *ti, *ts;
+ unsigned long resolution, ticks;
+ struct list_head *p, *q, *n;
+ int use_tasklet = 0;
+
+ if (timer == NULL)
+ return;
+
+ spin_lock(&timer->lock);
+
+ /* remember the current resolution */
+ if (timer->hw.c_resolution)
+ resolution = timer->hw.c_resolution(timer);
+ else
+ resolution = timer->hw.resolution;
+
+ /* loop for all active instances
+ * here we cannot use list_for_each because the active_list of a processed
+ * instance is relinked to done_list_head before callback is called.
+ */
+ list_for_each_safe(p, n, &timer->active_list_head) {
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+ if (!(ti->flags & SNDRV_TIMER_IFLG_RUNNING))
+ continue;
+ ti->pticks += ticks_left;
+ ti->resolution = resolution;
+ if (ti->cticks < ticks_left)
+ ti->cticks = 0;
+ else
+ ti->cticks -= ticks_left;
+ if (ti->cticks) /* not expired */
+ continue;
+ if (ti->flags & SNDRV_TIMER_IFLG_AUTO) {
+ ti->cticks = ti->ticks;
+ } else {
+ ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING;
+ if (--timer->running)
+ list_del(p);
+ }
+ if (list_empty(&ti->ack_list)) {
+ if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
+ (ti->flags & SNDRV_TIMER_IFLG_FAST)) {
+ list_add_tail(&ti->ack_list, &timer->ack_list_head);
+ } else {
+ list_add_tail(&ti->ack_list, &timer->sack_list_head);
+ }
+ }
+ list_for_each(q, &ti->slave_active_head) {
+ ts = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, active_list);
+ ts->pticks = ti->pticks;
+ ts->resolution = resolution;
+ if (list_empty(&ts->ack_list)) {
+ if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
+ (ti->flags & SNDRV_TIMER_IFLG_FAST)) {
+ list_add_tail(&ts->ack_list, &timer->ack_list_head);
+ } else {
+ list_add_tail(&ts->ack_list, &timer->sack_list_head);
+ }
+ }
+ }
+ }
+ if (timer->flags & SNDRV_TIMER_FLG_RESCHED)
+ snd_timer_reschedule(timer, ticks_left);
+ if (timer->running) {
+ if (timer->hw.flags & SNDRV_TIMER_HW_STOP) {
+ timer->hw.stop(timer);
+ timer->flags |= SNDRV_TIMER_FLG_CHANGE;
+ }
+ if (!(timer->hw.flags & SNDRV_TIMER_HW_AUTO) ||
+ (timer->flags & SNDRV_TIMER_FLG_CHANGE)) {
+ /* restart timer */
+ timer->flags &= ~SNDRV_TIMER_FLG_CHANGE;
+ timer->hw.start(timer);
+ }
+ } else {
+ timer->hw.stop(timer);
+ }
+
+ /* now process all fast callbacks */
+ while (!list_empty(&timer->ack_list_head)) {
+ p = timer->ack_list_head.next; /* get first item */
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list);
+
+ /* remove from ack_list and make empty */
+ list_del_init(p);
+
+ ticks = ti->pticks;
+ ti->pticks = 0;
+
+ ti->flags |= SNDRV_TIMER_IFLG_CALLBACK;
+ spin_unlock(&timer->lock);
+ if (ti->callback)
+ ti->callback(ti, resolution, ticks);
+ spin_lock(&timer->lock);
+ ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK;
+ }
+
+ /* do we have any slow callbacks? */
+ use_tasklet = !list_empty(&timer->sack_list_head);
+ spin_unlock(&timer->lock);
+
+ if (use_tasklet)
+ tasklet_hi_schedule(&timer->task_queue);
+}
+
+/*
+
+ */
+
+int snd_timer_new(snd_card_t *card, char *id, snd_timer_id_t *tid, snd_timer_t ** rtimer)
+{
+ snd_timer_t *timer;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_timer_dev_free,
+ .dev_register = snd_timer_dev_register,
+ .dev_unregister = snd_timer_dev_unregister
+ };
+
+ snd_assert(tid != NULL, return -EINVAL);
+ snd_assert(rtimer != NULL, return -EINVAL);
+ *rtimer = NULL;
+ timer = kcalloc(1, sizeof(*timer), GFP_KERNEL);
+ if (timer == NULL)
+ return -ENOMEM;
+ timer->tmr_class = tid->dev_class;
+ timer->card = card;
+ timer->tmr_device = tid->device;
+ timer->tmr_subdevice = tid->subdevice;
+ if (id)
+ strlcpy(timer->id, id, sizeof(timer->id));
+ INIT_LIST_HEAD(&timer->device_list);
+ INIT_LIST_HEAD(&timer->open_list_head);
+ INIT_LIST_HEAD(&timer->active_list_head);
+ INIT_LIST_HEAD(&timer->ack_list_head);
+ INIT_LIST_HEAD(&timer->sack_list_head);
+ spin_lock_init(&timer->lock);
+ tasklet_init(&timer->task_queue, snd_timer_tasklet, (unsigned long)timer);
+ if (card != NULL) {
+ if ((err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops)) < 0) {
+ snd_timer_free(timer);
+ return err;
+ }
+ }
+ *rtimer = timer;
+ return 0;
+}
+
+static int snd_timer_free(snd_timer_t *timer)
+{
+ snd_assert(timer != NULL, return -ENXIO);
+ if (timer->private_free)
+ timer->private_free(timer);
+ kfree(timer);
+ return 0;
+}
+
+int snd_timer_dev_free(snd_device_t *device)
+{
+ snd_timer_t *timer = device->device_data;
+ return snd_timer_free(timer);
+}
+
+int snd_timer_dev_register(snd_device_t *dev)
+{
+ snd_timer_t *timer = dev->device_data;
+ snd_timer_t *timer1;
+ struct list_head *p;
+
+ snd_assert(timer != NULL && timer->hw.start != NULL && timer->hw.stop != NULL, return -ENXIO);
+ if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) &&
+ !timer->hw.resolution && timer->hw.c_resolution == NULL)
+ return -EINVAL;
+
+ down(&register_mutex);
+ list_for_each(p, &snd_timer_list) {
+ timer1 = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ if (timer1->tmr_class > timer->tmr_class)
+ break;
+ if (timer1->tmr_class < timer->tmr_class)
+ continue;
+ if (timer1->card && timer->card) {
+ if (timer1->card->number > timer->card->number)
+ break;
+ if (timer1->card->number < timer->card->number)
+ continue;
+ }
+ if (timer1->tmr_device > timer->tmr_device)
+ break;
+ if (timer1->tmr_device < timer->tmr_device)
+ continue;
+ if (timer1->tmr_subdevice > timer->tmr_subdevice)
+ break;
+ if (timer1->tmr_subdevice < timer->tmr_subdevice)
+ continue;
+ /* conflicts.. */
+ up(&register_mutex);
+ return -EBUSY;
+ }
+ list_add_tail(&timer->device_list, p);
+ up(&register_mutex);
+ return 0;
+}
+
+int snd_timer_unregister(snd_timer_t *timer)
+{
+ struct list_head *p, *n;
+ snd_timer_instance_t *ti;
+
+ snd_assert(timer != NULL, return -ENXIO);
+ down(&register_mutex);
+ if (! list_empty(&timer->open_list_head)) {
+ snd_printk(KERN_WARNING "timer 0x%lx is busy?\n", (long)timer);
+ list_for_each_safe(p, n, &timer->open_list_head) {
+ list_del_init(p);
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list);
+ ti->timer = NULL;
+ }
+ }
+ list_del(&timer->device_list);
+ up(&register_mutex);
+ return snd_timer_free(timer);
+}
+
+static int snd_timer_dev_unregister(snd_device_t *device)
+{
+ snd_timer_t *timer = device->device_data;
+ return snd_timer_unregister(timer);
+}
+
+void snd_timer_notify(snd_timer_t *timer, enum sndrv_timer_event event, struct timespec *tstamp)
+{
+ unsigned long flags;
+ unsigned long resolution = 0;
+ snd_timer_instance_t *ti, *ts;
+ struct list_head *p, *n;
+
+ snd_runtime_check(timer->hw.flags & SNDRV_TIMER_HW_SLAVE, return);
+ snd_assert(event >= SNDRV_TIMER_EVENT_MSTART && event <= SNDRV_TIMER_EVENT_MPAUSE, return);
+ spin_lock_irqsave(&timer->lock, flags);
+ if (event == SNDRV_TIMER_EVENT_MSTART || event == SNDRV_TIMER_EVENT_MCONTINUE) {
+ if (timer->hw.c_resolution)
+ resolution = timer->hw.c_resolution(timer);
+ else
+ resolution = timer->hw.resolution;
+ }
+ list_for_each(p, &timer->active_list_head) {
+ ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list);
+ if (ti->ccallback)
+ ti->ccallback(ti, event, tstamp, resolution);
+ list_for_each(n, &ti->slave_active_head) {
+ ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list);
+ if (ts->ccallback)
+ ts->ccallback(ts, event, tstamp, resolution);
+ }
+ }
+ spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+/*
+ * exported functions for global timers
+ */
+int snd_timer_global_new(char *id, int device, snd_timer_t **rtimer)
+{
+ snd_timer_id_t tid;
+
+ tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
+ tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ tid.card = -1;
+ tid.device = device;
+ tid.subdevice = 0;
+ return snd_timer_new(NULL, id, &tid, rtimer);
+}
+
+int snd_timer_global_free(snd_timer_t *timer)
+{
+ return snd_timer_free(timer);
+}
+
+int snd_timer_global_register(snd_timer_t *timer)
+{
+ snd_device_t dev;
+
+ memset(&dev, 0, sizeof(dev));
+ dev.device_data = timer;
+ return snd_timer_dev_register(&dev);
+}
+
+int snd_timer_global_unregister(snd_timer_t *timer)
+{
+ return snd_timer_unregister(timer);
+}
+
+/*
+ * System timer
+ */
+
+struct snd_timer_system_private {
+ struct timer_list tlist;
+ struct timer * timer;
+ unsigned long last_expires;
+ unsigned long last_jiffies;
+ unsigned long correction;
+};
+
+unsigned int snd_timer_system_resolution(void)
+{
+ return 1000000000L / HZ;
+}
+
+static void snd_timer_s_function(unsigned long data)
+{
+ snd_timer_t *timer = (snd_timer_t *)data;
+ struct snd_timer_system_private *priv = timer->private_data;
+ unsigned long jiff = jiffies;
+ if (time_after(jiff, priv->last_expires))
+ priv->correction = (long)jiff - (long)priv->last_expires;
+ snd_timer_interrupt(timer, (long)jiff - (long)priv->last_jiffies);
+}
+
+static int snd_timer_s_start(snd_timer_t * timer)
+{
+ struct snd_timer_system_private *priv;
+ unsigned long njiff;
+
+ priv = (struct snd_timer_system_private *) timer->private_data;
+ njiff = (priv->last_jiffies = jiffies);
+ if (priv->correction > timer->sticks - 1) {
+ priv->correction -= timer->sticks - 1;
+ njiff++;
+ } else {
+ njiff += timer->sticks - priv->correction;
+ priv->correction -= timer->sticks;
+ }
+ priv->last_expires = priv->tlist.expires = njiff;
+ add_timer(&priv->tlist);
+ return 0;
+}
+
+static int snd_timer_s_stop(snd_timer_t * timer)
+{
+ struct snd_timer_system_private *priv;
+ unsigned long jiff;
+
+ priv = (struct snd_timer_system_private *) timer->private_data;
+ del_timer(&priv->tlist);
+ jiff = jiffies;
+ if (time_before(jiff, priv->last_expires))
+ timer->sticks = priv->last_expires - jiff;
+ else
+ timer->sticks = 1;
+ return 0;
+}
+
+static struct _snd_timer_hardware snd_timer_system =
+{
+ .flags = SNDRV_TIMER_HW_FIRST | SNDRV_TIMER_HW_TASKLET,
+ .resolution = 1000000000L / HZ,
+ .ticks = 10000000L,
+ .start = snd_timer_s_start,
+ .stop = snd_timer_s_stop
+};
+
+static void snd_timer_free_system(snd_timer_t *timer)
+{
+ kfree(timer->private_data);
+}
+
+static int snd_timer_register_system(void)
+{
+ snd_timer_t *timer;
+ struct snd_timer_system_private *priv;
+ int err;
+
+ if ((err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer)) < 0)
+ return err;
+ strcpy(timer->name, "system timer");
+ timer->hw = snd_timer_system;
+ priv = kcalloc(1, sizeof(*priv), GFP_KERNEL);
+ if (priv == NULL) {
+ snd_timer_free(timer);
+ return -ENOMEM;
+ }
+ init_timer(&priv->tlist);
+ priv->tlist.function = snd_timer_s_function;
+ priv->tlist.data = (unsigned long) timer;
+ timer->private_data = priv;
+ timer->private_free = snd_timer_free_system;
+ return snd_timer_global_register(timer);
+}
+
+/*
+ * Info interface
+ */
+
+static void snd_timer_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t * buffer)
+{
+ unsigned long flags;
+ snd_timer_t *timer;
+ snd_timer_instance_t *ti;
+ struct list_head *p, *q;
+
+ down(&register_mutex);
+ list_for_each(p, &snd_timer_list) {
+ timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ switch (timer->tmr_class) {
+ case SNDRV_TIMER_CLASS_GLOBAL:
+ snd_iprintf(buffer, "G%i: ", timer->tmr_device);
+ break;
+ case SNDRV_TIMER_CLASS_CARD:
+ snd_iprintf(buffer, "C%i-%i: ", timer->card->number, timer->tmr_device);
+ break;
+ case SNDRV_TIMER_CLASS_PCM:
+ snd_iprintf(buffer, "P%i-%i-%i: ", timer->card->number, timer->tmr_device, timer->tmr_subdevice);
+ break;
+ default:
+ snd_iprintf(buffer, "?%i-%i-%i-%i: ", timer->tmr_class, timer->card ? timer->card->number : -1, timer->tmr_device, timer->tmr_subdevice);
+ }
+ snd_iprintf(buffer, "%s :", timer->name);
+ if (timer->hw.resolution)
+ snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", timer->hw.resolution / 1000, timer->hw.resolution % 1000, timer->hw.ticks);
+ if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ snd_iprintf(buffer, " SLAVE");
+ snd_iprintf(buffer, "\n");
+ spin_lock_irqsave(&timer->lock, flags);
+ list_for_each(q, &timer->open_list_head) {
+ ti = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list);
+ snd_iprintf(buffer, " Client %s : %s : lost interrupts %li\n",
+ ti->owner ? ti->owner : "unknown",
+ ti->flags & (SNDRV_TIMER_IFLG_START|SNDRV_TIMER_IFLG_RUNNING) ? "running" : "stopped",
+ ti->lost);
+ }
+ spin_unlock_irqrestore(&timer->lock, flags);
+ }
+ up(&register_mutex);
+}
+
+/*
+ * USER SPACE interface
+ */
+
+static void snd_timer_user_interrupt(snd_timer_instance_t *timeri,
+ unsigned long resolution,
+ unsigned long ticks)
+{
+ snd_timer_user_t *tu = timeri->callback_data;
+ snd_timer_read_t *r;
+ int prev;
+
+ spin_lock(&tu->qlock);
+ if (tu->qused > 0) {
+ prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1;
+ r = &tu->queue[prev];
+ if (r->resolution == resolution) {
+ r->ticks += ticks;
+ goto __wake;
+ }
+ }
+ if (tu->qused >= tu->queue_size) {
+ tu->overrun++;
+ } else {
+ r = &tu->queue[tu->qtail++];
+ tu->qtail %= tu->queue_size;
+ r->resolution = resolution;
+ r->ticks = ticks;
+ tu->qused++;
+ }
+ __wake:
+ spin_unlock(&tu->qlock);
+ kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+ wake_up(&tu->qchange_sleep);
+}
+
+static void snd_timer_user_append_to_tqueue(snd_timer_user_t *tu, snd_timer_tread_t *tread)
+{
+ if (tu->qused >= tu->queue_size) {
+ tu->overrun++;
+ } else {
+ memcpy(&tu->tqueue[tu->qtail++], tread, sizeof(*tread));
+ tu->qtail %= tu->queue_size;
+ tu->qused++;
+ }
+}
+
+static void snd_timer_user_ccallback(snd_timer_instance_t *timeri,
+ enum sndrv_timer_event event,
+ struct timespec *tstamp,
+ unsigned long resolution)
+{
+ snd_timer_user_t *tu = timeri->callback_data;
+ snd_timer_tread_t r1;
+
+ if (event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE)
+ tu->tstamp = *tstamp;
+ if ((tu->filter & (1 << event)) == 0 || !tu->tread)
+ return;
+ r1.event = event;
+ r1.tstamp = *tstamp;
+ r1.val = resolution;
+ spin_lock(&tu->qlock);
+ snd_timer_user_append_to_tqueue(tu, &r1);
+ spin_unlock(&tu->qlock);
+ kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+ wake_up(&tu->qchange_sleep);
+}
+
+static void snd_timer_user_tinterrupt(snd_timer_instance_t *timeri,
+ unsigned long resolution,
+ unsigned long ticks)
+{
+ snd_timer_user_t *tu = timeri->callback_data;
+ snd_timer_tread_t *r, r1;
+ struct timespec tstamp;
+ int prev, append = 0;
+
+ snd_timestamp_zero(&tstamp);
+ spin_lock(&tu->qlock);
+ if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION)|(1 << SNDRV_TIMER_EVENT_TICK))) == 0) {
+ spin_unlock(&tu->qlock);
+ return;
+ }
+ if (tu->last_resolution != resolution || ticks > 0)
+ snd_timestamp_now(&tstamp, 1);
+ if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) {
+ r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
+ r1.tstamp = tstamp;
+ r1.val = resolution;
+ snd_timer_user_append_to_tqueue(tu, &r1);
+ tu->last_resolution = resolution;
+ append++;
+ }
+ if ((tu->filter & (1 << SNDRV_TIMER_EVENT_TICK)) == 0)
+ goto __wake;
+ if (ticks == 0)
+ goto __wake;
+ if (tu->qused > 0) {
+ prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1;
+ r = &tu->tqueue[prev];
+ if (r->event == SNDRV_TIMER_EVENT_TICK) {
+ r->tstamp = tstamp;
+ r->val += ticks;
+ append++;
+ goto __wake;
+ }
+ }
+ r1.event = SNDRV_TIMER_EVENT_TICK;
+ r1.tstamp = tstamp;
+ r1.val = ticks;
+ snd_timer_user_append_to_tqueue(tu, &r1);
+ append++;
+ __wake:
+ spin_unlock(&tu->qlock);
+ if (append == 0)
+ return;
+ kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+ wake_up(&tu->qchange_sleep);
+}
+
+static int snd_timer_user_open(struct inode *inode, struct file *file)
+{
+ snd_timer_user_t *tu;
+
+ tu = kcalloc(1, sizeof(*tu), GFP_KERNEL);
+ if (tu == NULL)
+ return -ENOMEM;
+ spin_lock_init(&tu->qlock);
+ init_waitqueue_head(&tu->qchange_sleep);
+ tu->ticks = 1;
+ tu->queue_size = 128;
+ tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+ if (tu->queue == NULL) {
+ kfree(tu);
+ return -ENOMEM;
+ }
+ file->private_data = tu;
+ return 0;
+}
+
+static int snd_timer_user_release(struct inode *inode, struct file *file)
+{
+ snd_timer_user_t *tu;
+
+ if (file->private_data) {
+ tu = file->private_data;
+ file->private_data = NULL;
+ fasync_helper(-1, file, 0, &tu->fasync);
+ if (tu->timeri)
+ snd_timer_close(tu->timeri);
+ kfree(tu->queue);
+ kfree(tu->tqueue);
+ kfree(tu);
+ }
+ return 0;
+}
+
+static void snd_timer_user_zero_id(snd_timer_id_t *id)
+{
+ id->dev_class = SNDRV_TIMER_CLASS_NONE;
+ id->dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ id->card = -1;
+ id->device = -1;
+ id->subdevice = -1;
+}
+
+static void snd_timer_user_copy_id(snd_timer_id_t *id, snd_timer_t *timer)
+{
+ id->dev_class = timer->tmr_class;
+ id->dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+ id->card = timer->card ? timer->card->number : -1;
+ id->device = timer->tmr_device;
+ id->subdevice = timer->tmr_subdevice;
+}
+
+static int snd_timer_user_next_device(snd_timer_id_t __user *_tid)
+{
+ snd_timer_id_t id;
+ snd_timer_t *timer;
+ struct list_head *p;
+
+ if (copy_from_user(&id, _tid, sizeof(id)))
+ return -EFAULT;
+ down(&register_mutex);
+ if (id.dev_class < 0) { /* first item */
+ if (list_empty(&snd_timer_list))
+ snd_timer_user_zero_id(&id);
+ else {
+ timer = (snd_timer_t *)list_entry(snd_timer_list.next, snd_timer_t, device_list);
+ snd_timer_user_copy_id(&id, timer);
+ }
+ } else {
+ switch (id.dev_class) {
+ case SNDRV_TIMER_CLASS_GLOBAL:
+ id.device = id.device < 0 ? 0 : id.device + 1;
+ list_for_each(p, &snd_timer_list) {
+ timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ if (timer->tmr_class > SNDRV_TIMER_CLASS_GLOBAL) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (timer->tmr_device >= id.device) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ }
+ if (p == &snd_timer_list)
+ snd_timer_user_zero_id(&id);
+ break;
+ case SNDRV_TIMER_CLASS_CARD:
+ case SNDRV_TIMER_CLASS_PCM:
+ if (id.card < 0) {
+ id.card = 0;
+ } else {
+ if (id.card < 0) {
+ id.card = 0;
+ } else {
+ if (id.device < 0) {
+ id.device = 0;
+ } else {
+ id.subdevice = id.subdevice < 0 ? 0 : id.subdevice + 1;
+ }
+ }
+ }
+ list_for_each(p, &snd_timer_list) {
+ timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ if (timer->tmr_class > id.dev_class) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (timer->tmr_class < id.dev_class)
+ continue;
+ if (timer->card->number > id.card) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (timer->card->number < id.card)
+ continue;
+ if (timer->tmr_device > id.device) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (timer->tmr_device < id.device)
+ continue;
+ if (timer->tmr_subdevice > id.subdevice) {
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (timer->tmr_subdevice < id.subdevice)
+ continue;
+ snd_timer_user_copy_id(&id, timer);
+ break;
+ }
+ if (p == &snd_timer_list)
+ snd_timer_user_zero_id(&id);
+ break;
+ default:
+ snd_timer_user_zero_id(&id);
+ }
+ }
+ up(&register_mutex);
+ if (copy_to_user(_tid, &id, sizeof(*_tid)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_timer_user_ginfo(struct file *file, snd_timer_ginfo_t __user *_ginfo)
+{
+ snd_timer_ginfo_t *ginfo;
+ snd_timer_id_t tid;
+ snd_timer_t *t;
+ struct list_head *p;
+ int err = 0;
+
+ ginfo = kmalloc(sizeof(*ginfo), GFP_KERNEL);
+ if (! ginfo)
+ return -ENOMEM;
+ if (copy_from_user(ginfo, _ginfo, sizeof(*ginfo))) {
+ kfree(ginfo);
+ return -EFAULT;
+ }
+ tid = ginfo->tid;
+ memset(ginfo, 0, sizeof(*ginfo));
+ ginfo->tid = tid;
+ down(&register_mutex);
+ t = snd_timer_find(&tid);
+ if (t != NULL) {
+ ginfo->card = t->card ? t->card->number : -1;
+ if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ ginfo->flags |= SNDRV_TIMER_FLG_SLAVE;
+ strlcpy(ginfo->id, t->id, sizeof(ginfo->id));
+ strlcpy(ginfo->name, t->name, sizeof(ginfo->name));
+ ginfo->resolution = t->hw.resolution;
+ if (t->hw.resolution_min > 0) {
+ ginfo->resolution_min = t->hw.resolution_min;
+ ginfo->resolution_max = t->hw.resolution_max;
+ }
+ list_for_each(p, &t->open_list_head) {
+ ginfo->clients++;
+ }
+ } else {
+ err = -ENODEV;
+ }
+ up(&register_mutex);
+ if (err >= 0 && copy_to_user(_ginfo, ginfo, sizeof(*ginfo)))
+ err = -EFAULT;
+ kfree(ginfo);
+ return err;
+}
+
+static int snd_timer_user_gparams(struct file *file, snd_timer_gparams_t __user *_gparams)
+{
+ snd_timer_gparams_t gparams;
+ snd_timer_t *t;
+ int err;
+
+ if (copy_from_user(&gparams, _gparams, sizeof(gparams)))
+ return -EFAULT;
+ down(&register_mutex);
+ t = snd_timer_find(&gparams.tid);
+ if (t != NULL) {
+ if (list_empty(&t->open_list_head)) {
+ if (t->hw.set_period)
+ err = t->hw.set_period(t, gparams.period_num, gparams.period_den);
+ else
+ err = -ENOSYS;
+ } else {
+ err = -EBUSY;
+ }
+ } else {
+ err = -ENODEV;
+ }
+ up(&register_mutex);
+ return err;
+}
+
+static int snd_timer_user_gstatus(struct file *file, snd_timer_gstatus_t __user *_gstatus)
+{
+ snd_timer_gstatus_t gstatus;
+ snd_timer_id_t tid;
+ snd_timer_t *t;
+ int err = 0;
+
+ if (copy_from_user(&gstatus, _gstatus, sizeof(gstatus)))
+ return -EFAULT;
+ tid = gstatus.tid;
+ memset(&gstatus, 0, sizeof(gstatus));
+ gstatus.tid = tid;
+ down(&register_mutex);
+ t = snd_timer_find(&tid);
+ if (t != NULL) {
+ if (t->hw.c_resolution)
+ gstatus.resolution = t->hw.c_resolution(t);
+ else
+ gstatus.resolution = t->hw.resolution;
+ if (t->hw.precise_resolution) {
+ t->hw.precise_resolution(t, &gstatus.resolution_num, &gstatus.resolution_den);
+ } else {
+ gstatus.resolution_num = gstatus.resolution;
+ gstatus.resolution_den = 1000000000uL;
+ }
+ } else {
+ err = -ENODEV;
+ }
+ up(&register_mutex);
+ if (err >= 0 && copy_to_user(_gstatus, &gstatus, sizeof(gstatus)))
+ err = -EFAULT;
+ return err;
+}
+
+static int snd_timer_user_tselect(struct file *file, snd_timer_select_t __user *_tselect)
+{
+ snd_timer_user_t *tu;
+ snd_timer_select_t tselect;
+ char str[32];
+ int err;
+
+ tu = file->private_data;
+ if (tu->timeri)
+ snd_timer_close(tu->timeri);
+ if (copy_from_user(&tselect, _tselect, sizeof(tselect)))
+ return -EFAULT;
+ sprintf(str, "application %i", current->pid);
+ if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
+ tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
+ if ((err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid)) < 0)
+ return err;
+
+ if (tu->queue) {
+ kfree(tu->queue);
+ tu->queue = NULL;
+ }
+ if (tu->tqueue) {
+ kfree(tu->tqueue);
+ tu->tqueue = NULL;
+ }
+ if (tu->tread) {
+ tu->tqueue = (snd_timer_tread_t *)kmalloc(tu->queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL);
+ if (tu->tqueue == NULL) {
+ snd_timer_close(tu->timeri);
+ return -ENOMEM;
+ }
+ } else {
+ tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+ if (tu->queue == NULL) {
+ snd_timer_close(tu->timeri);
+ return -ENOMEM;
+ }
+ }
+
+ tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST;
+ tu->timeri->callback = tu->tread ? snd_timer_user_tinterrupt : snd_timer_user_interrupt;
+ tu->timeri->ccallback = snd_timer_user_ccallback;
+ tu->timeri->callback_data = (void *)tu;
+ return 0;
+}
+
+static int snd_timer_user_info(struct file *file, snd_timer_info_t __user *_info)
+{
+ snd_timer_user_t *tu;
+ snd_timer_info_t *info;
+ snd_timer_t *t;
+ int err = 0;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ t = tu->timeri->timer;
+ snd_assert(t != NULL, return -ENXIO);
+
+ info = kcalloc(1, sizeof(*info), GFP_KERNEL);
+ if (! info)
+ return -ENOMEM;
+ info->card = t->card ? t->card->number : -1;
+ if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ info->flags |= SNDRV_TIMER_FLG_SLAVE;
+ strlcpy(info->id, t->id, sizeof(info->id));
+ strlcpy(info->name, t->name, sizeof(info->name));
+ info->resolution = t->hw.resolution;
+ if (copy_to_user(_info, info, sizeof(*_info)))
+ err = -EFAULT;
+ kfree(info);
+ return err;
+}
+
+static int snd_timer_user_params(struct file *file, snd_timer_params_t __user *_params)
+{
+ snd_timer_user_t *tu;
+ snd_timer_params_t params;
+ snd_timer_t *t;
+ snd_timer_read_t *tr;
+ snd_timer_tread_t *ttr;
+ int err;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ t = tu->timeri->timer;
+ snd_assert(t != NULL, return -ENXIO);
+ if (copy_from_user(&params, _params, sizeof(params)))
+ return -EFAULT;
+ if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE) && params.ticks < 1) {
+ err = -EINVAL;
+ goto _end;
+ }
+ if (params.queue_size > 0 && (params.queue_size < 32 || params.queue_size > 1024)) {
+ err = -EINVAL;
+ goto _end;
+ }
+ if (params.filter & ~((1<<SNDRV_TIMER_EVENT_RESOLUTION)|
+ (1<<SNDRV_TIMER_EVENT_TICK)|
+ (1<<SNDRV_TIMER_EVENT_START)|
+ (1<<SNDRV_TIMER_EVENT_STOP)|
+ (1<<SNDRV_TIMER_EVENT_CONTINUE)|
+ (1<<SNDRV_TIMER_EVENT_PAUSE)|
+ (1<<SNDRV_TIMER_EVENT_MSTART)|
+ (1<<SNDRV_TIMER_EVENT_MSTOP)|
+ (1<<SNDRV_TIMER_EVENT_MCONTINUE)|
+ (1<<SNDRV_TIMER_EVENT_MPAUSE))) {
+ err = -EINVAL;
+ goto _end;
+ }
+ snd_timer_stop(tu->timeri);
+ spin_lock_irq(&t->lock);
+ tu->timeri->flags &= ~(SNDRV_TIMER_IFLG_AUTO|
+ SNDRV_TIMER_IFLG_EXCLUSIVE|
+ SNDRV_TIMER_IFLG_EARLY_EVENT);
+ if (params.flags & SNDRV_TIMER_PSFLG_AUTO)
+ tu->timeri->flags |= SNDRV_TIMER_IFLG_AUTO;
+ if (params.flags & SNDRV_TIMER_PSFLG_EXCLUSIVE)
+ tu->timeri->flags |= SNDRV_TIMER_IFLG_EXCLUSIVE;
+ if (params.flags & SNDRV_TIMER_PSFLG_EARLY_EVENT)
+ tu->timeri->flags |= SNDRV_TIMER_IFLG_EARLY_EVENT;
+ spin_unlock_irq(&t->lock);
+ if (params.queue_size > 0 && (unsigned int)tu->queue_size != params.queue_size) {
+ if (tu->tread) {
+ ttr = (snd_timer_tread_t *)kmalloc(params.queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL);
+ if (ttr) {
+ kfree(tu->tqueue);
+ tu->queue_size = params.queue_size;
+ tu->tqueue = ttr;
+ }
+ } else {
+ tr = (snd_timer_read_t *)kmalloc(params.queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
+ if (tr) {
+ kfree(tu->queue);
+ tu->queue_size = params.queue_size;
+ tu->queue = tr;
+ }
+ }
+ }
+ tu->qhead = tu->qtail = tu->qused = 0;
+ if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) {
+ if (tu->tread) {
+ snd_timer_tread_t tread;
+ tread.event = SNDRV_TIMER_EVENT_EARLY;
+ tread.tstamp.tv_sec = 0;
+ tread.tstamp.tv_nsec = 0;
+ tread.val = 0;
+ snd_timer_user_append_to_tqueue(tu, &tread);
+ } else {
+ snd_timer_read_t *r = &tu->queue[0];
+ r->resolution = 0;
+ r->ticks = 0;
+ tu->qused++;
+ tu->qtail++;
+ }
+
+ }
+ tu->filter = params.filter;
+ tu->ticks = params.ticks;
+ err = 0;
+ _end:
+ if (copy_to_user(_params, &params, sizeof(params)))
+ return -EFAULT;
+ return err;
+}
+
+static int snd_timer_user_status(struct file *file, snd_timer_status_t __user *_status)
+{
+ snd_timer_user_t *tu;
+ snd_timer_status_t status;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ memset(&status, 0, sizeof(status));
+ status.tstamp = tu->tstamp;
+ status.resolution = snd_timer_resolution(tu->timeri);
+ status.lost = tu->timeri->lost;
+ status.overrun = tu->overrun;
+ spin_lock_irq(&tu->qlock);
+ status.queue = tu->qused;
+ spin_unlock_irq(&tu->qlock);
+ if (copy_to_user(_status, &status, sizeof(status)))
+ return -EFAULT;
+ return 0;
+}
+
+static int snd_timer_user_start(struct file *file)
+{
+ int err;
+ snd_timer_user_t *tu;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ snd_timer_stop(tu->timeri);
+ tu->timeri->lost = 0;
+ tu->last_resolution = 0;
+ return (err = snd_timer_start(tu->timeri, tu->ticks)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_stop(struct file *file)
+{
+ int err;
+ snd_timer_user_t *tu;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_continue(struct file *file)
+{
+ int err;
+ snd_timer_user_t *tu;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ tu->timeri->lost = 0;
+ return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0;
+}
+
+static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ snd_timer_user_t *tu;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ tu = file->private_data;
+ switch (cmd) {
+ case SNDRV_TIMER_IOCTL_PVERSION:
+ return put_user(SNDRV_TIMER_VERSION, p) ? -EFAULT : 0;
+ case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
+ return snd_timer_user_next_device(argp);
+ case SNDRV_TIMER_IOCTL_TREAD:
+ {
+ int xarg;
+
+ if (tu->timeri) /* too late */
+ return -EBUSY;
+ if (get_user(xarg, p))
+ return -EFAULT;
+ tu->tread = xarg ? 1 : 0;
+ return 0;
+ }
+ case SNDRV_TIMER_IOCTL_GINFO:
+ return snd_timer_user_ginfo(file, argp);
+ case SNDRV_TIMER_IOCTL_GPARAMS:
+ return snd_timer_user_gparams(file, argp);
+ case SNDRV_TIMER_IOCTL_GSTATUS:
+ return snd_timer_user_gstatus(file, argp);
+ case SNDRV_TIMER_IOCTL_SELECT:
+ return snd_timer_user_tselect(file, argp);
+ case SNDRV_TIMER_IOCTL_INFO:
+ return snd_timer_user_info(file, argp);
+ case SNDRV_TIMER_IOCTL_PARAMS:
+ return snd_timer_user_params(file, argp);
+ case SNDRV_TIMER_IOCTL_STATUS:
+ return snd_timer_user_status(file, argp);
+ case SNDRV_TIMER_IOCTL_START:
+ return snd_timer_user_start(file);
+ case SNDRV_TIMER_IOCTL_STOP:
+ return snd_timer_user_stop(file);
+ case SNDRV_TIMER_IOCTL_CONTINUE:
+ return snd_timer_user_continue(file);
+ }
+ return -ENOTTY;
+}
+
+static int snd_timer_user_fasync(int fd, struct file * file, int on)
+{
+ snd_timer_user_t *tu;
+ int err;
+
+ tu = file->private_data;
+ err = fasync_helper(fd, file, on, &tu->fasync);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, size_t count, loff_t *offset)
+{
+ snd_timer_user_t *tu;
+ long result = 0, unit;
+ int err = 0;
+
+ tu = file->private_data;
+ unit = tu->tread ? sizeof(snd_timer_tread_t) : sizeof(snd_timer_read_t);
+ spin_lock_irq(&tu->qlock);
+ while ((long)count - result >= unit) {
+ while (!tu->qused) {
+ wait_queue_t wait;
+
+ if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
+ err = -EAGAIN;
+ break;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&tu->qchange_sleep, &wait);
+
+ spin_unlock_irq(&tu->qlock);
+ schedule();
+ spin_lock_irq(&tu->qlock);
+
+ remove_wait_queue(&tu->qchange_sleep, &wait);
+
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ }
+
+ spin_unlock_irq(&tu->qlock);
+ if (err < 0)
+ goto _error;
+
+ if (tu->tread) {
+ if (copy_to_user(buffer, &tu->tqueue[tu->qhead++], sizeof(snd_timer_tread_t))) {
+ err = -EFAULT;
+ goto _error;
+ }
+ } else {
+ if (copy_to_user(buffer, &tu->queue[tu->qhead++], sizeof(snd_timer_read_t))) {
+ err = -EFAULT;
+ goto _error;
+ }
+ }
+
+ tu->qhead %= tu->queue_size;
+
+ result += unit;
+ buffer += unit;
+
+ spin_lock_irq(&tu->qlock);
+ tu->qused--;
+ }
+ spin_unlock_irq(&tu->qlock);
+ _error:
+ return result > 0 ? result : err;
+}
+
+static unsigned int snd_timer_user_poll(struct file *file, poll_table * wait)
+{
+ unsigned int mask;
+ snd_timer_user_t *tu;
+
+ tu = file->private_data;
+
+ poll_wait(file, &tu->qchange_sleep, wait);
+
+ mask = 0;
+ if (tu->qused)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+#ifdef CONFIG_COMPAT
+#include "timer_compat.c"
+#else
+#define snd_timer_user_ioctl_compat NULL
+#endif
+
+static struct file_operations snd_timer_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = snd_timer_user_read,
+ .open = snd_timer_user_open,
+ .release = snd_timer_user_release,
+ .poll = snd_timer_user_poll,
+ .unlocked_ioctl = snd_timer_user_ioctl,
+ .compat_ioctl = snd_timer_user_ioctl_compat,
+ .fasync = snd_timer_user_fasync,
+};
+
+static snd_minor_t snd_timer_reg =
+{
+ .comment = "timer",
+ .f_ops = &snd_timer_f_ops,
+};
+
+/*
+ * ENTRY functions
+ */
+
+static snd_info_entry_t *snd_timer_proc_entry = NULL;
+
+static int __init alsa_timer_init(void)
+{
+ int err;
+ snd_info_entry_t *entry;
+
+#ifdef SNDRV_OSS_INFO_DEV_TIMERS
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1, "system timer");
+#endif
+ if ((entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL)) != NULL) {
+ entry->c.text.read_size = SNDRV_TIMER_DEVICES * 128;
+ entry->c.text.read = snd_timer_proc_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ snd_timer_proc_entry = entry;
+ if ((err = snd_timer_register_system()) < 0)
+ snd_printk(KERN_ERR "unable to register system timer (%i)\n", err);
+ if ((err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER,
+ NULL, 0, &snd_timer_reg, "timer"))<0)
+ snd_printk(KERN_ERR "unable to register timer device (%i)\n", err);
+ return 0;
+}
+
+static void __exit alsa_timer_exit(void)
+{
+ struct list_head *p, *n;
+
+ snd_unregister_device(SNDRV_DEVICE_TYPE_TIMER, NULL, 0);
+ /* unregister the system timer */
+ list_for_each_safe(p, n, &snd_timer_list) {
+ snd_timer_t *timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list);
+ snd_timer_unregister(timer);
+ }
+ if (snd_timer_proc_entry) {
+ snd_info_unregister(snd_timer_proc_entry);
+ snd_timer_proc_entry = NULL;
+ }
+#ifdef SNDRV_OSS_INFO_DEV_TIMERS
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1);
+#endif
+}
+
+module_init(alsa_timer_init)
+module_exit(alsa_timer_exit)
+
+EXPORT_SYMBOL(snd_timer_open);
+EXPORT_SYMBOL(snd_timer_close);
+EXPORT_SYMBOL(snd_timer_resolution);
+EXPORT_SYMBOL(snd_timer_start);
+EXPORT_SYMBOL(snd_timer_stop);
+EXPORT_SYMBOL(snd_timer_continue);
+EXPORT_SYMBOL(snd_timer_pause);
+EXPORT_SYMBOL(snd_timer_new);
+EXPORT_SYMBOL(snd_timer_notify);
+EXPORT_SYMBOL(snd_timer_global_new);
+EXPORT_SYMBOL(snd_timer_global_free);
+EXPORT_SYMBOL(snd_timer_global_register);
+EXPORT_SYMBOL(snd_timer_global_unregister);
+EXPORT_SYMBOL(snd_timer_interrupt);
+EXPORT_SYMBOL(snd_timer_system_resolution);
diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c
new file mode 100644
index 00000000000..9fbc3957a22
--- /dev/null
+++ b/sound/core/timer_compat.c
@@ -0,0 +1,119 @@
+/*
+ * 32bit -> 64bit ioctl wrapper for timer API
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ * 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
+ *
+ */
+
+/* This file included from timer.c */
+
+#include <linux/compat.h>
+
+struct sndrv_timer_info32 {
+ u32 flags;
+ s32 card;
+ unsigned char id[64];
+ unsigned char name[80];
+ u32 reserved0;
+ u32 resolution;
+ unsigned char reserved[64];
+};
+
+static int snd_timer_user_info_compat(struct file *file,
+ struct sndrv_timer_info32 __user *_info)
+{
+ snd_timer_user_t *tu;
+ struct sndrv_timer_info32 info;
+ snd_timer_t *t;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ t = tu->timeri->timer;
+ snd_assert(t != NULL, return -ENXIO);
+ memset(&info, 0, sizeof(info));
+ info.card = t->card ? t->card->number : -1;
+ if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
+ info.flags |= SNDRV_TIMER_FLG_SLAVE;
+ strlcpy(info.id, t->id, sizeof(info.id));
+ strlcpy(info.name, t->name, sizeof(info.name));
+ info.resolution = t->hw.resolution;
+ if (copy_to_user(_info, &info, sizeof(*_info)))
+ return -EFAULT;
+ return 0;
+}
+
+struct sndrv_timer_status32 {
+ struct compat_timespec tstamp;
+ u32 resolution;
+ u32 lost;
+ u32 overrun;
+ u32 queue;
+ unsigned char reserved[64];
+};
+
+static int snd_timer_user_status_compat(struct file *file,
+ struct sndrv_timer_status32 __user *_status)
+{
+ snd_timer_user_t *tu;
+ snd_timer_status_t status;
+
+ tu = file->private_data;
+ snd_assert(tu->timeri != NULL, return -ENXIO);
+ memset(&status, 0, sizeof(status));
+ status.tstamp = tu->tstamp;
+ status.resolution = snd_timer_resolution(tu->timeri);
+ status.lost = tu->timeri->lost;
+ status.overrun = tu->overrun;
+ spin_lock_irq(&tu->qlock);
+ status.queue = tu->qused;
+ spin_unlock_irq(&tu->qlock);
+ if (copy_to_user(_status, &status, sizeof(status)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ */
+
+enum {
+ SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct sndrv_timer_info32),
+ SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct sndrv_timer_status32),
+};
+
+static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = compat_ptr(arg);
+
+ switch (cmd) {
+ case SNDRV_TIMER_IOCTL_PVERSION:
+ case SNDRV_TIMER_IOCTL_TREAD:
+ case SNDRV_TIMER_IOCTL_GINFO:
+ case SNDRV_TIMER_IOCTL_GPARAMS:
+ case SNDRV_TIMER_IOCTL_GSTATUS:
+ case SNDRV_TIMER_IOCTL_SELECT:
+ case SNDRV_TIMER_IOCTL_PARAMS:
+ case SNDRV_TIMER_IOCTL_START:
+ case SNDRV_TIMER_IOCTL_STOP:
+ case SNDRV_TIMER_IOCTL_CONTINUE:
+ case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
+ return snd_timer_user_ioctl(file, cmd, (unsigned long)argp);
+ case SNDRV_TIMER_IOCTL_INFO32:
+ return snd_timer_user_info_compat(file, argp);
+ case SNDRV_TIMER_IOCTL_STATUS32:
+ return snd_timer_user_status_compat(file, argp);
+ }
+ return -ENOIOCTLCMD;
+}
diff --git a/sound/core/wrappers.c b/sound/core/wrappers.c
new file mode 100644
index 00000000000..9f393023c32
--- /dev/null
+++ b/sound/core/wrappers.c
@@ -0,0 +1,50 @@
+/*
+ * Various wrappers
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * 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/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+
+#ifdef CONFIG_SND_DEBUG_MEMORY
+void *snd_wrapper_kmalloc(size_t size, int flags)
+{
+ return kmalloc(size, flags);
+}
+
+void snd_wrapper_kfree(const void *obj)
+{
+ kfree(obj);
+}
+
+void *snd_wrapper_vmalloc(unsigned long size)
+{
+ return vmalloc(size);
+}
+
+void snd_wrapper_vfree(void *obj)
+{
+ vfree(obj);
+}
+#endif
+