aboutsummaryrefslogtreecommitdiff
path: root/sound/core/seq
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core/seq')
-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
53 files changed, 16708 insertions, 0 deletions
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);