mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 14:58:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
16
sound/core/seq/Kconfig
Normal file
16
sound/core/seq/Kconfig
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# define SND_XXX_SEQ to min(SND_SEQUENCER,SND_XXX)
|
||||
|
||||
config SND_RAWMIDI_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_RAWMIDI
|
||||
|
||||
config SND_OPL3_LIB_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_OPL3_LIB
|
||||
|
||||
config SND_OPL4_LIB_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_OPL4_LIB
|
||||
|
||||
config SND_SBAWE_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_SBAWE
|
||||
|
||||
config SND_EMU10K1_SEQ
|
||||
def_tristate SND_SEQUENCER && SND_EMU10K1
|
||||
29
sound/core/seq/Makefile
Normal file
29
sound/core/seq/Makefile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
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-dummy-objs := seq_dummy.o
|
||||
snd-seq-virmidi-objs := seq_virmidi.o
|
||||
|
||||
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
|
||||
obj-$(CONFIG_SND_SEQUENCER) += oss/
|
||||
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-$(CONFIG_SND_RAWMIDI_SEQ) += snd-seq-midi.o snd-seq-midi-event.o
|
||||
obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
|
||||
obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
|
||||
obj-$(CONFIG_SND_SBAWE_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
|
||||
obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
|
||||
10
sound/core/seq/oss/Makefile
Normal file
10
sound/core/seq/oss/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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
|
||||
302
sound/core/seq/oss/seq_oss.c
Normal file
302
sound/core/seq/oss/seq_oss.c
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.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);
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int register_device(void);
|
||||
static void unregister_device(void);
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static int register_proc(void);
|
||||
static void unregister_proc(void);
|
||||
#else
|
||||
static inline int register_proc(void) { return 0; }
|
||||
static inline void unregister_proc(void) {}
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
|
||||
/*
|
||||
* module interface
|
||||
*/
|
||||
|
||||
static int __init alsa_seq_oss_init(void)
|
||||
{
|
||||
int rc;
|
||||
static struct snd_seq_dev_ops 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(struct snd_seq_oss_reg))) < 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 DEFINE_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;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
rc = snd_seq_oss_open(file, level);
|
||||
mutex_unlock(®ister_mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
odev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
if ((dp = file->private_data) == NULL)
|
||||
return 0;
|
||||
|
||||
snd_seq_oss_drain_write(dp);
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
snd_seq_oss_release(dp);
|
||||
mutex_unlock(®ister_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
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)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_write(dp, buf, count, file);
|
||||
}
|
||||
|
||||
static long
|
||||
odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
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)
|
||||
{
|
||||
struct seq_oss_devinfo *dp;
|
||||
dp = file->private_data;
|
||||
if (snd_BUG_ON(!dp))
|
||||
return -ENXIO;
|
||||
return snd_seq_oss_poll(dp, file, wait);
|
||||
}
|
||||
|
||||
/*
|
||||
* registration of sequencer minor device
|
||||
*/
|
||||
|
||||
static const 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,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static int __init
|
||||
register_device(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
|
||||
NULL, 0,
|
||||
&seq_oss_f_ops, NULL)) < 0) {
|
||||
pr_err("ALSA: seq_oss: can't register device seq\n");
|
||||
mutex_unlock(®ister_mutex);
|
||||
return rc;
|
||||
}
|
||||
if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
|
||||
NULL, 0,
|
||||
&seq_oss_f_ops, NULL)) < 0) {
|
||||
pr_err("ALSA: seq_oss: can't register device music\n");
|
||||
snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return rc;
|
||||
}
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_device(void)
|
||||
{
|
||||
mutex_lock(®ister_mutex);
|
||||
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
|
||||
pr_err("ALSA: seq_oss: error unregister device music\n");
|
||||
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
|
||||
pr_err("ALSA: seq_oss: error unregister device seq\n");
|
||||
mutex_unlock(®ister_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* /proc interface
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
static struct snd_info_entry *info_entry;
|
||||
|
||||
static void
|
||||
info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf)
|
||||
{
|
||||
mutex_lock(®ister_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);
|
||||
mutex_unlock(®ister_mutex);
|
||||
}
|
||||
|
||||
|
||||
static int __init
|
||||
register_proc(void)
|
||||
{
|
||||
struct snd_info_entry *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 = info_read;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
info_entry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_proc(void)
|
||||
{
|
||||
snd_info_free_entry(info_entry);
|
||||
info_entry = NULL;
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
176
sound/core/seq/oss/seq_oss_device.h
Normal file
176
sound/core/seq/oss/seq_oss_device.h
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 <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>
|
||||
|
||||
/* 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_PROCNAME "oss"
|
||||
|
||||
|
||||
/*
|
||||
* type definitions
|
||||
*/
|
||||
|
||||
typedef unsigned int reltime_t;
|
||||
typedef unsigned int abstime_t;
|
||||
|
||||
|
||||
/*
|
||||
* synthesizer channel information
|
||||
*/
|
||||
struct seq_oss_chinfo {
|
||||
int note, vel;
|
||||
};
|
||||
|
||||
/*
|
||||
* synthesizer information
|
||||
*/
|
||||
struct seq_oss_synthinfo {
|
||||
struct snd_seq_oss_arg arg;
|
||||
struct seq_oss_chinfo *ch;
|
||||
struct seq_oss_synth_sysex *sysex;
|
||||
int nr_voices;
|
||||
int opened;
|
||||
int is_midi;
|
||||
int midi_mapped;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* sequencer client information
|
||||
*/
|
||||
|
||||
struct seq_oss_devinfo {
|
||||
|
||||
int index; /* application index */
|
||||
int cseq; /* sequencer client number */
|
||||
int port; /* sequencer port number */
|
||||
int queue; /* sequencer queue number */
|
||||
|
||||
struct snd_seq_addr 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;
|
||||
struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
|
||||
int synth_opened;
|
||||
|
||||
/* output queue */
|
||||
struct seq_oss_writeq *writeq;
|
||||
|
||||
/* midi input queue */
|
||||
struct seq_oss_readq *readq;
|
||||
|
||||
/* timer */
|
||||
struct seq_oss_timer *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(struct seq_oss_devinfo *dp);
|
||||
int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg);
|
||||
int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count);
|
||||
int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt);
|
||||
unsigned int snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait);
|
||||
|
||||
void snd_seq_oss_reset(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_drain_write(struct seq_oss_devinfo *dp);
|
||||
|
||||
/* */
|
||||
void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time);
|
||||
|
||||
|
||||
/* proc interface */
|
||||
void snd_seq_oss_system_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf);
|
||||
void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *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 */
|
||||
static inline int
|
||||
snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop)
|
||||
{
|
||||
return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
|
||||
}
|
||||
|
||||
/* ioctl */
|
||||
static inline int
|
||||
snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg)
|
||||
{
|
||||
return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
|
||||
}
|
||||
|
||||
/* fill the addresses in header */
|
||||
static inline void
|
||||
snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *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);
|
||||
|
||||
#endif /* __SEQ_OSS_DEVICE_H */
|
||||
457
sound/core/seq/oss/seq_oss_event.c
Normal file
457
sound/core/seq/oss/seq_oss_event.c
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* 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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
||||
static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev);
|
||||
static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev);
|
||||
static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev);
|
||||
|
||||
|
||||
/*
|
||||
* convert an OSS event to ALSA event
|
||||
* return 0 : enqueued
|
||||
* non-zero : invalid - ignored
|
||||
*/
|
||||
|
||||
int
|
||||
snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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 {
|
||||
union evrec 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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (!snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
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(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (!snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
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(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *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(struct snd_seq_event *ev, int direct, void *private_data,
|
||||
int atomic, int hop)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
|
||||
union evrec *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 = (union evrec*)&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;
|
||||
}
|
||||
|
||||
112
sound/core/seq/oss/seq_oss_event.h
Normal file
112
sound/core/seq/oss/seq_oss_event.h
Normal file
|
|
@ -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) */
|
||||
struct evrec_short {
|
||||
unsigned char code;
|
||||
unsigned char parm1;
|
||||
unsigned char dev;
|
||||
unsigned char parm2;
|
||||
};
|
||||
|
||||
/* short note events (4bytes) */
|
||||
struct evrec_note {
|
||||
unsigned char code;
|
||||
unsigned char chn;
|
||||
unsigned char note;
|
||||
unsigned char vel;
|
||||
};
|
||||
|
||||
/* long timer events (8bytes) */
|
||||
struct evrec_timer {
|
||||
unsigned char code;
|
||||
unsigned char cmd;
|
||||
unsigned char dummy1, dummy2;
|
||||
unsigned int time;
|
||||
};
|
||||
|
||||
/* long extended events (8bytes) */
|
||||
struct evrec_extended {
|
||||
unsigned char code;
|
||||
unsigned char cmd;
|
||||
unsigned char dev;
|
||||
unsigned char chn;
|
||||
unsigned char p1, p2, p3, p4;
|
||||
};
|
||||
|
||||
/* long channel events (8bytes) */
|
||||
struct evrec_long {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char cmd;
|
||||
unsigned char chn;
|
||||
unsigned char p1, p2;
|
||||
unsigned short val;
|
||||
};
|
||||
|
||||
/* channel voice events (8bytes) */
|
||||
struct evrec_voice {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char cmd;
|
||||
unsigned char chn;
|
||||
unsigned char note, parm;
|
||||
unsigned short dummy;
|
||||
};
|
||||
|
||||
/* sysex events (8bytes) */
|
||||
struct evrec_sysex {
|
||||
unsigned char code;
|
||||
unsigned char dev;
|
||||
unsigned char buf[6];
|
||||
};
|
||||
|
||||
/* event record */
|
||||
union evrec {
|
||||
struct evrec_short s;
|
||||
struct evrec_note n;
|
||||
struct evrec_long l;
|
||||
struct evrec_voice v;
|
||||
struct evrec_timer t;
|
||||
struct evrec_extended e;
|
||||
struct evrec_sysex 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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
||||
int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q);
|
||||
int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
|
||||
|
||||
|
||||
#endif /* __SEQ_OSS_EVENT_H */
|
||||
539
sound/core/seq/oss/seq_oss_init.c
Normal file
539
sound/core/seq/oss/seq_oss_init.c
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
/*
|
||||
* 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/export.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.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 struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop);
|
||||
static int translate_mode(struct file *file);
|
||||
static int create_port(struct seq_oss_devinfo *dp);
|
||||
static int delete_port(struct seq_oss_devinfo *dp);
|
||||
static int alloc_seq_queue(struct seq_oss_devinfo *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)
|
||||
|
||||
|
||||
/* call snd_seq_oss_midi_lookup_ports() asynchronously */
|
||||
static void async_call_lookup_ports(struct work_struct *work)
|
||||
{
|
||||
snd_seq_oss_midi_lookup_ports(system_client);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(async_lookup_work, async_call_lookup_ports);
|
||||
|
||||
/*
|
||||
* create sequencer client for OSS sequencer
|
||||
*/
|
||||
int __init
|
||||
snd_seq_oss_create_client(void)
|
||||
{
|
||||
int rc;
|
||||
struct snd_seq_port_info *port;
|
||||
struct snd_seq_port_callback port_callback;
|
||||
|
||||
port = kmalloc(sizeof(*port), GFP_KERNEL);
|
||||
if (!port) {
|
||||
rc = -ENOMEM;
|
||||
goto __error;
|
||||
}
|
||||
|
||||
/* create ALSA client */
|
||||
rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,
|
||||
"OSS sequencer");
|
||||
if (rc < 0)
|
||||
goto __error;
|
||||
|
||||
system_client = rc;
|
||||
|
||||
/* 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) {
|
||||
struct snd_seq_port_subscribe 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;
|
||||
|
||||
/* look up midi devices */
|
||||
schedule_work(&async_lookup_work);
|
||||
|
||||
__error:
|
||||
kfree(port);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* receive annoucement from system port, and check the midi device
|
||||
*/
|
||||
static int
|
||||
receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)
|
||||
{
|
||||
struct snd_seq_port_info 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)
|
||||
{
|
||||
cancel_work_sync(&async_lookup_work);
|
||||
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;
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
|
||||
if (!dp) {
|
||||
pr_err("ALSA: seq_oss: can't malloc device info\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dp->cseq = system_client;
|
||||
dp->port = -1;
|
||||
dp->queue = -1;
|
||||
|
||||
for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
|
||||
if (client_table[i] == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
dp->index = i;
|
||||
if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
|
||||
pr_err("ALSA: seq_oss: too many applications\n");
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
/* pr_err("ALSA: seq_oss: no device found\n"); */
|
||||
rc = -ENODEV;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* create port */
|
||||
rc = create_port(dp);
|
||||
if (rc < 0) {
|
||||
pr_err("ALSA: seq_oss: can't create port\n");
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* allocate queue */
|
||||
rc = alloc_seq_queue(dp);
|
||||
if (rc < 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 */
|
||||
if (is_read_mode(dp->file_mode)) {
|
||||
dp->readq = snd_seq_oss_readq_new(dp, maxqlen);
|
||||
if (!dp->readq) {
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize write queue */
|
||||
if (is_write_mode(dp->file_mode)) {
|
||||
dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
|
||||
if (!dp->writeq) {
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize timer */
|
||||
dp->timer = snd_seq_oss_timer_new(dp);
|
||||
if (!dp->timer) {
|
||||
pr_err("ALSA: seq_oss: can't alloc timer\n");
|
||||
rc = -ENOMEM;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
/* 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++;
|
||||
|
||||
return 0;
|
||||
|
||||
_error:
|
||||
snd_seq_oss_synth_cleanup(dp);
|
||||
snd_seq_oss_midi_cleanup(dp);
|
||||
delete_seq_queue(dp->queue);
|
||||
delete_port(dp);
|
||||
|
||||
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(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int rc;
|
||||
struct snd_seq_port_info port;
|
||||
struct snd_seq_port_callback 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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* delete ALSA port
|
||||
*/
|
||||
static int
|
||||
delete_port(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
if (dp->port < 0) {
|
||||
kfree(dp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return snd_seq_event_port_detach(dp->cseq, dp->port);
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate a queue
|
||||
*/
|
||||
static int
|
||||
alloc_seq_queue(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
struct snd_seq_queue_info 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)
|
||||
{
|
||||
struct snd_seq_queue_info 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)
|
||||
pr_err("ALSA: 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)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)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(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int queue;
|
||||
|
||||
client_table[dp->index] = NULL;
|
||||
num_clients--;
|
||||
|
||||
snd_seq_oss_reset(dp);
|
||||
|
||||
snd_seq_oss_synth_cleanup(dp);
|
||||
snd_seq_oss_midi_cleanup(dp);
|
||||
|
||||
/* clear slot */
|
||||
queue = dp->queue;
|
||||
if (dp->port >= 0)
|
||||
delete_port(dp);
|
||||
delete_seq_queue(queue);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Wait until the queue is empty (if we don't have nonblock)
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_drain_write(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
if (! dp->timer->running)
|
||||
return;
|
||||
if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) &&
|
||||
dp->writeq) {
|
||||
while (snd_seq_oss_writeq_sync(dp->writeq))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* reset sequencer devices
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_reset(struct seq_oss_devinfo *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);
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* 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(struct snd_info_buffer *buf)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_devinfo *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);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
191
sound/core/seq/oss/seq_oss_ioctl.c
Normal file
191
sound/core/seq/oss/seq_oss_ioctl.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *dp, void __user *arg)
|
||||
{
|
||||
unsigned char ev[8];
|
||||
struct snd_seq_event 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, (union evrec *)ev, &tmpev)) {
|
||||
snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
snd_seq_oss_ioctl(struct seq_oss_devinfo *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:
|
||||
snd_seq_oss_reset(dp);
|
||||
return -EINVAL;
|
||||
|
||||
case SNDCTL_SEQ_SYNC:
|
||||
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:
|
||||
snd_seq_oss_reset(dp);
|
||||
return 0;
|
||||
|
||||
case SNDCTL_SEQ_TESTMIDI:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
return snd_seq_oss_midi_open(dp, dev, dp->file_mode);
|
||||
|
||||
case SNDCTL_SEQ_GETINCOUNT:
|
||||
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:
|
||||
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:
|
||||
return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_RESETSAMPLES:
|
||||
if (get_user(dev, p))
|
||||
return -EFAULT;
|
||||
return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
|
||||
|
||||
case SNDCTL_SEQ_NRSYNTHS:
|
||||
return put_user(dp->max_synthdev, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SEQ_NRMIDIS:
|
||||
return put_user(dp->max_mididev, p) ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_SYNTH_MEMAVL:
|
||||
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:
|
||||
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:
|
||||
return snd_seq_oss_synth_info_user(dp, arg);
|
||||
|
||||
case SNDCTL_SEQ_OUTOFBAND:
|
||||
return snd_seq_oss_oob_user(dp, arg);
|
||||
|
||||
case SNDCTL_MIDI_INFO:
|
||||
return snd_seq_oss_midi_info_user(dp, arg);
|
||||
|
||||
case SNDCTL_SEQ_THRESHOLD:
|
||||
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:
|
||||
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:
|
||||
if (! is_write_mode(dp->file_mode))
|
||||
return -EIO;
|
||||
return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
711
sound/core/seq/oss/seq_oss_midi.c
Normal file
711
sound/core/seq/oss/seq_oss_midi.c
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
/*
|
||||
* 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 <sound/asoundef.h>
|
||||
#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>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
||||
/*
|
||||
* constants
|
||||
*/
|
||||
#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30
|
||||
|
||||
/*
|
||||
* definition of midi device record
|
||||
*/
|
||||
struct seq_oss_midi {
|
||||
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];
|
||||
struct snd_midi_event *coder; /* MIDI event coder */
|
||||
struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */
|
||||
snd_use_lock_t use_lock;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* midi device table
|
||||
*/
|
||||
static int max_midi_devs;
|
||||
static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
|
||||
|
||||
static DEFINE_SPINLOCK(register_lock);
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static struct seq_oss_midi *get_mdev(int dev);
|
||||
static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev);
|
||||
static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev);
|
||||
static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev);
|
||||
|
||||
/*
|
||||
* look up the existing ports
|
||||
* this looks a very exhausting job.
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_lookup_ports(int client)
|
||||
{
|
||||
struct snd_seq_client_info *clinfo;
|
||||
struct snd_seq_port_info *pinfo;
|
||||
|
||||
clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL);
|
||||
pinfo = kzalloc(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 struct seq_oss_midi *
|
||||
get_mdev(int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
mdev = midi_devs[dev];
|
||||
if (mdev)
|
||||
snd_use_lock_use(&mdev->use_lock);
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return mdev;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for the identical slot
|
||||
*/
|
||||
static struct seq_oss_midi *
|
||||
find_slot(int client, int port)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_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(®ister_lock, flags);
|
||||
return mdev;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(®ister_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(struct snd_seq_port_info *pinfo)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
/* 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 = kzalloc(sizeof(*mdev), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: 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) {
|
||||
pr_err("ALSA: seq_oss: 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(®ister_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(®ister_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(®ister_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the midi device if it was registered
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_midi_check_exit_port(int client, int port)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
int index;
|
||||
|
||||
if ((mdev = find_slot(client, port)) != NULL) {
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
midi_devs[mdev->seq_device] = NULL;
|
||||
spin_unlock_irqrestore(®ister_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(®ister_lock, flags);
|
||||
for (index = max_midi_devs - 1; index >= 0; index--) {
|
||||
if (midi_devs[index])
|
||||
break;
|
||||
}
|
||||
max_midi_devs = index + 1;
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* release the midi device if it was registered
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_clear_all(void)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *mdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_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(®ister_lock, flags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set up midi tables
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
dp->max_mididev = max_midi_devs;
|
||||
}
|
||||
|
||||
/*
|
||||
* clean up midi tables
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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 struct seq_oss_midi *
|
||||
get_mididev(struct seq_oss_devinfo *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(struct seq_oss_devinfo *dp, int dev, int fmode)
|
||||
{
|
||||
int perm;
|
||||
struct seq_oss_midi *mdev;
|
||||
struct snd_seq_port_subscribe 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(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *mdev;
|
||||
struct snd_seq_port_subscribe 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;
|
||||
}
|
||||
|
||||
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(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_midi *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) {
|
||||
struct snd_seq_event ev;
|
||||
int c;
|
||||
|
||||
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);
|
||||
}
|
||||
for (c = 0; c < 16; c++) {
|
||||
ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
|
||||
ev.data.control.channel = c;
|
||||
ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
|
||||
ev.data.control.param =
|
||||
MIDI_CTL_RESET_CONTROLLERS;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
|
||||
ev.data.control.value = 0;
|
||||
snd_seq_oss_dispatch(dp, &ev, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr)
|
||||
{
|
||||
struct seq_oss_midi *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(struct snd_seq_event *ev, int direct, void *private_data)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
|
||||
struct seq_oss_midi *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(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev)
|
||||
{
|
||||
union evrec 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(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev)
|
||||
{
|
||||
struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf)
|
||||
{
|
||||
struct seq_oss_midi *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;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* 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(struct snd_info_buffer *buf)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_midi *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);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
48
sound/core/seq/oss/seq_oss_midi.h
Normal file
48
sound/core/seq/oss/seq_oss_midi.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
int snd_seq_oss_midi_lookup_ports(int client);
|
||||
int snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *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(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp);
|
||||
|
||||
int snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int file_mode);
|
||||
void snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode);
|
||||
int snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev);
|
||||
void snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev);
|
||||
int snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c,
|
||||
struct snd_seq_event *ev);
|
||||
int snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private);
|
||||
int snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev);
|
||||
int snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf);
|
||||
void snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr);
|
||||
|
||||
#endif
|
||||
237
sound/core/seq/oss/seq_oss_readq.c
Normal file
237
sound/core/seq/oss/seq_oss_readq.c
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/*
|
||||
* constants
|
||||
*/
|
||||
//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1)
|
||||
#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600)
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* create a read queue
|
||||
*/
|
||||
struct seq_oss_readq *
|
||||
snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen)
|
||||
{
|
||||
struct seq_oss_readq *q;
|
||||
|
||||
if ((q = kzalloc(sizeof(*q), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc read queue\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: 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(struct seq_oss_readq *q)
|
||||
{
|
||||
if (q) {
|
||||
kfree(q->q);
|
||||
kfree(q);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* reset the read queue
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_readq_clear(struct seq_oss_readq *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(struct seq_oss_readq *q, int dev, unsigned char *data, int len)
|
||||
{
|
||||
union evrec 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(struct seq_oss_readq *q, union evrec *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(struct seq_oss_readq *q, union evrec *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(struct seq_oss_readq *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(struct seq_oss_readq *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(struct seq_oss_readq *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(struct seq_oss_readq *q, unsigned long curt, int seq_mode)
|
||||
{
|
||||
if (curt != q->input_time) {
|
||||
union evrec 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;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* proc interface
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf)
|
||||
{
|
||||
snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n",
|
||||
(waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
|
||||
q->qlen, q->input_time);
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
56
sound/core/seq/oss/seq_oss_readq.h
Normal file
56
sound/core/seq/oss/seq_oss_readq.h
Normal file
|
|
@ -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 {
|
||||
union evrec *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;
|
||||
};
|
||||
|
||||
struct seq_oss_readq *snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen);
|
||||
void snd_seq_oss_readq_delete(struct seq_oss_readq *q);
|
||||
void snd_seq_oss_readq_clear(struct seq_oss_readq *readq);
|
||||
unsigned int snd_seq_oss_readq_poll(struct seq_oss_readq *readq, struct file *file, poll_table *wait);
|
||||
int snd_seq_oss_readq_puts(struct seq_oss_readq *readq, int dev, unsigned char *data, int len);
|
||||
int snd_seq_oss_readq_put_event(struct seq_oss_readq *readq, union evrec *ev);
|
||||
int snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *readq, unsigned long curt, int seq_mode);
|
||||
int snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec);
|
||||
void snd_seq_oss_readq_wait(struct seq_oss_readq *q);
|
||||
void snd_seq_oss_readq_free(struct seq_oss_readq *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
|
||||
216
sound/core/seq/oss/seq_oss_rw.c
Normal file
216
sound/core/seq/oss/seq_oss_rw.c
Normal file
|
|
@ -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(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt);
|
||||
|
||||
|
||||
/*
|
||||
* read interface
|
||||
*/
|
||||
|
||||
int
|
||||
snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count)
|
||||
{
|
||||
struct seq_oss_readq *readq = dp->readq;
|
||||
int result = 0, err = 0;
|
||||
int ev_len;
|
||||
union evrec 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(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt)
|
||||
{
|
||||
int result = 0, err = 0;
|
||||
int ev_size, fmt;
|
||||
union evrec 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(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt)
|
||||
{
|
||||
int rc = 0;
|
||||
struct snd_seq_event 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(struct seq_oss_devinfo *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;
|
||||
}
|
||||
661
sound/core/seq/oss/seq_oss_synth.c
Normal file
661
sound/core/seq/oss/seq_oss_synth.c
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.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 {
|
||||
int len;
|
||||
int skip;
|
||||
unsigned char buf[MAX_SYSEX_BUFLEN];
|
||||
};
|
||||
|
||||
/* synth info */
|
||||
struct seq_oss_synth {
|
||||
int seq_device;
|
||||
|
||||
/* for synth_info */
|
||||
int synth_type;
|
||||
int synth_subtype;
|
||||
int nr_voices;
|
||||
|
||||
char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
|
||||
struct snd_seq_oss_callback oper;
|
||||
|
||||
int opened;
|
||||
|
||||
void *private_data;
|
||||
snd_use_lock_t use_lock;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* device table
|
||||
*/
|
||||
static int max_synth_devs;
|
||||
static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
|
||||
static struct seq_oss_synth 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 struct seq_oss_synth *get_synthdev(struct seq_oss_devinfo *dp, int dev);
|
||||
static void reset_channels(struct seq_oss_synthinfo *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(struct snd_seq_device *dev)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_synth *rec;
|
||||
struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
unsigned long flags;
|
||||
|
||||
if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: 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(®ister_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(®ister_lock, flags);
|
||||
pr_err("ALSA: seq_oss: no more synth slot\n");
|
||||
kfree(rec);
|
||||
return -ENOMEM;
|
||||
}
|
||||
max_synth_devs++;
|
||||
}
|
||||
rec->seq_device = i;
|
||||
synth_devs[i] = rec;
|
||||
spin_unlock_irqrestore(®ister_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(struct snd_seq_device *dev)
|
||||
{
|
||||
int index;
|
||||
struct seq_oss_synth *rec = dev->driver_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
for (index = 0; index < max_synth_devs; index++) {
|
||||
if (synth_devs[index] == rec)
|
||||
break;
|
||||
}
|
||||
if (index >= max_synth_devs) {
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
pr_err("ALSA: seq_oss: 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(®ister_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 struct seq_oss_synth *
|
||||
get_sdev(int dev)
|
||||
{
|
||||
struct seq_oss_synth *rec;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(®ister_lock, flags);
|
||||
rec = synth_devs[dev];
|
||||
if (rec)
|
||||
snd_use_lock_use(&rec->use_lock);
|
||||
spin_unlock_irqrestore(®ister_lock, flags);
|
||||
return rec;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set up synth tables
|
||||
*/
|
||||
|
||||
void
|
||||
snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_synth *rec;
|
||||
struct seq_oss_synthinfo *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(struct seq_oss_chinfo), GFP_KERNEL);
|
||||
if (!info->ch) {
|
||||
pr_err("ALSA: seq_oss: Cannot malloc voices\n");
|
||||
rec->oper.close(&info->arg);
|
||||
module_put(rec->oper.owner);
|
||||
snd_use_lock_free(&rec->use_lock);
|
||||
continue;
|
||||
}
|
||||
reset_channels(info);
|
||||
}
|
||||
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(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
|
||||
return;
|
||||
|
||||
for (i = 0; i < dp->max_mididev; i++) {
|
||||
struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_synth *rec;
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (snd_BUG_ON(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) {
|
||||
rec->oper.close(&info->arg);
|
||||
module_put(rec->oper.owner);
|
||||
rec->opened = 0;
|
||||
}
|
||||
snd_use_lock_free(&rec->use_lock);
|
||||
}
|
||||
kfree(info->sysex);
|
||||
info->sysex = NULL;
|
||||
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(struct seq_oss_devinfo *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 struct seq_oss_synth *
|
||||
get_synthdev(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_synth *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(struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_synth *rec;
|
||||
struct seq_oss_synthinfo *info;
|
||||
|
||||
if (snd_BUG_ON(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;
|
||||
kfree(info->sysex);
|
||||
info->sysex = NULL;
|
||||
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 {
|
||||
struct snd_seq_event 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(struct seq_oss_devinfo *dp, int dev, int fmt,
|
||||
const char __user *buf, int p, int c)
|
||||
{
|
||||
struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev)
|
||||
{
|
||||
struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev)
|
||||
{
|
||||
int i, send;
|
||||
unsigned char *dest;
|
||||
struct seq_oss_synth_sysex *sysex;
|
||||
|
||||
if (! snd_seq_oss_synth_is_valid(dp, dev))
|
||||
return -ENXIO;
|
||||
|
||||
sysex = dp->synths[dev].sysex;
|
||||
if (sysex == NULL) {
|
||||
sysex = kzalloc(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(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, unsigned long addr)
|
||||
{
|
||||
struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev, unsigned char *data, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf)
|
||||
{
|
||||
struct seq_oss_synth *rec;
|
||||
|
||||
if (dev < 0 || dev >= dp->max_synthdev)
|
||||
return -ENXIO;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* proc interface
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_synth_info_read(struct snd_info_buffer *buf)
|
||||
{
|
||||
int i;
|
||||
struct seq_oss_synth *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);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
51
sound/core/seq/oss/seq_oss_synth.h
Normal file
51
sound/core/seq/oss/seq_oss_synth.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
void snd_seq_oss_synth_init(void);
|
||||
int snd_seq_oss_synth_register(struct snd_seq_device *dev);
|
||||
int snd_seq_oss_synth_unregister(struct snd_seq_device *dev);
|
||||
void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp);
|
||||
|
||||
void snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev);
|
||||
int snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt,
|
||||
const char __user *buf, int p, int c);
|
||||
int snd_seq_oss_synth_is_valid(struct seq_oss_devinfo *dp, int dev);
|
||||
int snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf,
|
||||
struct snd_seq_event *ev);
|
||||
int snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev);
|
||||
int snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd,
|
||||
unsigned long addr);
|
||||
int snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev,
|
||||
unsigned char *data, struct snd_seq_event *ev);
|
||||
|
||||
int snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf);
|
||||
|
||||
#endif
|
||||
277
sound/core/seq/oss/seq_oss_timer.c
Normal file
277
sound/core/seq/oss/seq_oss_timer.c
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/slab.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(struct seq_oss_timer *timer);
|
||||
static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value);
|
||||
|
||||
|
||||
/*
|
||||
* create and register a new timer.
|
||||
* if queue is not started yet, start it.
|
||||
*/
|
||||
struct seq_oss_timer *
|
||||
snd_seq_oss_timer_new(struct seq_oss_devinfo *dp)
|
||||
{
|
||||
struct seq_oss_timer *rec;
|
||||
|
||||
rec = kzalloc(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(struct seq_oss_timer *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(struct seq_oss_timer *rec, union evrec *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(struct seq_oss_timer *timer)
|
||||
{
|
||||
timer->tempo = (60 * 1000000) / timer->oss_tempo;
|
||||
timer->ppq = timer->oss_timebase;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* dispatch a timer event
|
||||
*/
|
||||
static int
|
||||
send_timer_event(struct seq_oss_devinfo *dp, int type, int value)
|
||||
{
|
||||
struct snd_seq_event 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(struct seq_oss_timer *timer)
|
||||
{
|
||||
struct seq_oss_devinfo *dp = timer->dp;
|
||||
struct snd_seq_queue_tempo 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(struct seq_oss_timer *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(struct seq_oss_timer *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(struct seq_oss_timer *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(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (cmd == SNDCTL_SEQ_CTRLRATE) {
|
||||
/* 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:
|
||||
return snd_seq_oss_timer_start(timer);
|
||||
case SNDCTL_TMR_STOP:
|
||||
return snd_seq_oss_timer_stop(timer);
|
||||
case SNDCTL_TMR_CONTINUE:
|
||||
return snd_seq_oss_timer_continue(timer);
|
||||
case SNDCTL_TMR_TEMPO:
|
||||
if (get_user(value, arg))
|
||||
return -EFAULT;
|
||||
return snd_seq_oss_timer_tempo(timer, value);
|
||||
case SNDCTL_TMR_TIMEBASE:
|
||||
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:
|
||||
/* not supported */
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
70
sound/core/seq/oss/seq_oss_timer.h
Normal file
70
sound/core/seq/oss/seq_oss_timer.h
Normal file
|
|
@ -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 {
|
||||
struct seq_oss_devinfo *dp;
|
||||
reltime_t cur_tick;
|
||||
int realtime;
|
||||
int running;
|
||||
int tempo, ppq; /* ALSA queue */
|
||||
int oss_tempo, oss_timebase;
|
||||
};
|
||||
|
||||
|
||||
struct seq_oss_timer *snd_seq_oss_timer_new(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_timer_delete(struct seq_oss_timer *dp);
|
||||
|
||||
int snd_seq_oss_timer_start(struct seq_oss_timer *timer);
|
||||
int snd_seq_oss_timer_stop(struct seq_oss_timer *timer);
|
||||
int snd_seq_oss_timer_continue(struct seq_oss_timer *timer);
|
||||
int snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value);
|
||||
#define snd_seq_oss_timer_reset snd_seq_oss_timer_start
|
||||
|
||||
int snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg);
|
||||
|
||||
/*
|
||||
* get current processed time
|
||||
*/
|
||||
static inline abstime_t
|
||||
snd_seq_oss_timer_cur_tick(struct seq_oss_timer *timer)
|
||||
{
|
||||
return timer->cur_tick;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* is realtime event?
|
||||
*/
|
||||
static inline int
|
||||
snd_seq_oss_timer_is_realtime(struct seq_oss_timer *timer)
|
||||
{
|
||||
return timer->realtime;
|
||||
}
|
||||
|
||||
#endif
|
||||
173
sound/core/seq/oss/seq_oss_writeq.c
Normal file
173
sound/core/seq/oss/seq_oss_writeq.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
||||
/*
|
||||
* create a write queue record
|
||||
*/
|
||||
struct seq_oss_writeq *
|
||||
snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen)
|
||||
{
|
||||
struct seq_oss_writeq *q;
|
||||
struct snd_seq_client_pool pool;
|
||||
|
||||
if ((q = kzalloc(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(struct seq_oss_writeq *q)
|
||||
{
|
||||
if (q) {
|
||||
snd_seq_oss_writeq_clear(q); /* to be sure */
|
||||
kfree(q);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* reset the write queue
|
||||
*/
|
||||
void
|
||||
snd_seq_oss_writeq_clear(struct seq_oss_writeq *q)
|
||||
{
|
||||
struct snd_seq_remove_events 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(struct seq_oss_writeq *q)
|
||||
{
|
||||
struct seq_oss_devinfo *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) {
|
||||
struct snd_seq_event ev;
|
||||
union evrec *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 = (union evrec *)&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(struct seq_oss_writeq *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(struct seq_oss_writeq *q)
|
||||
{
|
||||
struct snd_seq_client_pool 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(struct seq_oss_writeq *q, int val)
|
||||
{
|
||||
struct snd_seq_client_pool 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);
|
||||
}
|
||||
|
||||
50
sound/core/seq/oss/seq_oss_writeq.h
Normal file
50
sound/core/seq/oss/seq_oss_writeq.h
Normal file
|
|
@ -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 {
|
||||
struct seq_oss_devinfo *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
|
||||
*/
|
||||
struct seq_oss_writeq *snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen);
|
||||
void snd_seq_oss_writeq_delete(struct seq_oss_writeq *q);
|
||||
void snd_seq_oss_writeq_clear(struct seq_oss_writeq *q);
|
||||
int snd_seq_oss_writeq_sync(struct seq_oss_writeq *q);
|
||||
void snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time);
|
||||
int snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q);
|
||||
void snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int size);
|
||||
|
||||
|
||||
#endif
|
||||
137
sound/core/seq/seq.c
Normal file
137
sound/core/seq/seq.c
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.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/minors.h>
|
||||
#include <sound/seq_device.h>
|
||||
|
||||
#if defined(CONFIG_SND_SEQ_DUMMY_MODULE)
|
||||
int seq_client_load[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1};
|
||||
#else
|
||||
int seq_client_load[15] = {[0 ... 14] = -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 =
|
||||
#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT
|
||||
SNDRV_TIMER_GLOBAL_HRTIMER
|
||||
#elif defined(CONFIG_SND_SEQ_RTCTIMER_DEFAULT)
|
||||
SNDRV_TIMER_GLOBAL_RTC
|
||||
#else
|
||||
SNDRV_TIMER_GLOBAL_SYSTEM
|
||||
#endif
|
||||
;
|
||||
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@perex.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.");
|
||||
|
||||
MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_SEQUENCER);
|
||||
MODULE_ALIAS("devname:snd/seq");
|
||||
|
||||
/*
|
||||
* 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)
|
||||
2603
sound/core/seq/seq_clientmgr.c
Normal file
2603
sound/core/seq/seq_clientmgr.c
Normal file
File diff suppressed because it is too large
Load diff
103
sound/core/seq/seq_clientmgr.h
Normal file
103
sound/core/seq/seq_clientmgr.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 */
|
||||
struct snd_seq_fifo *fifo; /* queue for incoming events */
|
||||
int fifo_pool_size;
|
||||
};
|
||||
|
||||
struct snd_seq_kernel_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 mutex ports_mutex;
|
||||
int convert32; /* convert 32->64bit */
|
||||
|
||||
/* output pool */
|
||||
struct snd_seq_pool *pool; /* memory pool for this client */
|
||||
|
||||
union {
|
||||
struct snd_seq_user_client user;
|
||||
struct snd_seq_kernel_client kernel;
|
||||
} data;
|
||||
};
|
||||
|
||||
/* usage statistics */
|
||||
struct snd_seq_usage {
|
||||
int cur;
|
||||
int peak;
|
||||
};
|
||||
|
||||
|
||||
int client_init_data(void);
|
||||
int snd_sequencer_device_init(void);
|
||||
void snd_sequencer_device_done(void);
|
||||
|
||||
/* get locked pointer to client */
|
||||
struct snd_seq_client *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) */
|
||||
int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop);
|
||||
|
||||
/* exported to other modules */
|
||||
int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, int atomic, int hop);
|
||||
int snd_seq_kernel_client_enqueue_blocking(int client, struct snd_seq_event * 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,
|
||||
struct snd_seq_port_subscribe *info, int evtype);
|
||||
|
||||
extern int seq_client_load[15];
|
||||
|
||||
#endif
|
||||
138
sound/core/seq/seq_compat.c
Normal file
138
sound/core/seq/seq_compat.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct snd_seq_port_info32 {
|
||||
struct snd_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(struct snd_seq_client *client, unsigned int cmd,
|
||||
struct snd_seq_port_info32 __user *data32)
|
||||
{
|
||||
int err = -EFAULT;
|
||||
struct snd_seq_port_info *data;
|
||||
mm_segment_t fs;
|
||||
|
||||
data = memdup_user(data32, sizeof(*data32));
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (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 snd_seq_port_info32),
|
||||
SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32),
|
||||
SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32),
|
||||
SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32),
|
||||
SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32),
|
||||
};
|
||||
|
||||
static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_seq_client *client = file->private_data;
|
||||
void __user *argp = compat_ptr(arg);
|
||||
|
||||
if (snd_BUG_ON(!client))
|
||||
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;
|
||||
}
|
||||
575
sound/core/seq/seq_device.c
Normal file
575
sound/core/seq/seq_device.c
Normal file
|
|
@ -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 <linux/init.h>
|
||||
#include <linux/module.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>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
|
||||
MODULE_DESCRIPTION("ALSA sequencer device management");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* 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 */
|
||||
struct snd_seq_dev_ops ops;
|
||||
|
||||
/* registered 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 mutex reg_mutex;
|
||||
|
||||
struct list_head list; /* next driver */
|
||||
};
|
||||
|
||||
|
||||
static LIST_HEAD(opslist);
|
||||
static int num_ops;
|
||||
static DEFINE_MUTEX(ops_mutex);
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static struct snd_info_entry *info_entry;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int snd_seq_device_free(struct snd_seq_device *dev);
|
||||
static int snd_seq_device_dev_free(struct snd_device *device);
|
||||
static int snd_seq_device_dev_register(struct snd_device *device);
|
||||
static int snd_seq_device_dev_disconnect(struct snd_device *device);
|
||||
|
||||
static int init_device(struct snd_seq_device *dev, struct ops_list *ops);
|
||||
static int free_device(struct snd_seq_device *dev, struct ops_list *ops);
|
||||
static struct ops_list *find_driver(char *id, int create_if_empty);
|
||||
static struct ops_list *create_driver(char *id);
|
||||
static void unlock_driver(struct ops_list *ops);
|
||||
static void remove_drivers(void);
|
||||
|
||||
/*
|
||||
* show all drivers and their status
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static void snd_seq_device_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, 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);
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* load all registered drivers (called from seq_clientmgr.c)
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
/* 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_MODULES
|
||||
struct ops_list *ops;
|
||||
|
||||
/* Calling request_module during module_init()
|
||||
* may cause blocking.
|
||||
*/
|
||||
if (snd_seq_in_init)
|
||||
return;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if (! (ops->driver & DRIVER_LOADED) &&
|
||||
! (ops->driver & DRIVER_REQUESTED)) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
ops->driver |= DRIVER_REQUESTED;
|
||||
request_module("snd-%s", ops->id);
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* register a sequencer device
|
||||
* card = card info
|
||||
* device = device number (if any)
|
||||
* id = id of driver
|
||||
* result = return pointer (NULL allowed if unnecessary)
|
||||
*/
|
||||
int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
||||
struct snd_seq_device **result)
|
||||
{
|
||||
struct snd_seq_device *dev;
|
||||
struct ops_list *ops;
|
||||
int err;
|
||||
static struct snd_device_ops dops = {
|
||||
.dev_free = snd_seq_device_dev_free,
|
||||
.dev_register = snd_seq_device_dev_register,
|
||||
.dev_disconnect = snd_seq_device_dev_disconnect,
|
||||
};
|
||||
|
||||
if (result)
|
||||
*result = NULL;
|
||||
|
||||
if (snd_BUG_ON(!id))
|
||||
return -EINVAL;
|
||||
|
||||
ops = find_driver(id, 1);
|
||||
if (ops == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = kzalloc(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 */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
list_add_tail(&dev->list, &ops->dev_list);
|
||||
ops->num_devices++;
|
||||
mutex_unlock(&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(struct snd_seq_device *dev)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
if (snd_BUG_ON(!dev))
|
||||
return -EINVAL;
|
||||
|
||||
ops = find_driver(dev->id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
/* remove the device from the list */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
list_del(&dev->list);
|
||||
ops->num_devices--;
|
||||
mutex_unlock(&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(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
return snd_seq_device_free(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* register the device
|
||||
*/
|
||||
static int snd_seq_device_dev_register(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
struct ops_list *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(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
struct ops_list *ops;
|
||||
|
||||
ops = find_driver(dev->id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
free_device(dev, ops);
|
||||
|
||||
unlock_driver(ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* register device driver
|
||||
* id = driver id
|
||||
* entry = driver operators - duplicated to each instance
|
||||
*/
|
||||
int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
||||
int argsize)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
struct snd_seq_device *dev;
|
||||
|
||||
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) {
|
||||
pr_warn("ALSA: seq: driver_register: driver '%s' already exists\n", id);
|
||||
unlock_driver(ops);
|
||||
snd_seq_autoload_unlock();
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
/* copy driver operators */
|
||||
ops->ops = *entry;
|
||||
ops->driver |= DRIVER_LOADED;
|
||||
ops->argsize = argsize;
|
||||
|
||||
/* initialize existing devices if necessary */
|
||||
list_for_each_entry(dev, &ops->dev_list, list) {
|
||||
init_device(dev, ops);
|
||||
}
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
unlock_driver(ops);
|
||||
snd_seq_autoload_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* create driver record
|
||||
*/
|
||||
static struct ops_list * create_driver(char *id)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
|
||||
if (ops == NULL)
|
||||
return ops;
|
||||
|
||||
/* set up driver entry */
|
||||
strlcpy(ops->id, id, sizeof(ops->id));
|
||||
mutex_init(&ops->reg_mutex);
|
||||
/*
|
||||
* The ->reg_mutex locking rules are per-driver, so we create
|
||||
* separate per-driver lock classes:
|
||||
*/
|
||||
lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id);
|
||||
|
||||
ops->driver = DRIVER_EMPTY;
|
||||
INIT_LIST_HEAD(&ops->dev_list);
|
||||
/* lock this instance */
|
||||
ops->used = 1;
|
||||
|
||||
/* register driver entry */
|
||||
mutex_lock(&ops_mutex);
|
||||
list_add_tail(&ops->list, &opslist);
|
||||
num_ops++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* unregister the specified driver
|
||||
*/
|
||||
int snd_seq_device_unregister_driver(char *id)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
struct snd_seq_device *dev;
|
||||
|
||||
ops = find_driver(id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENXIO;
|
||||
if (! (ops->driver & DRIVER_LOADED) ||
|
||||
(ops->driver & DRIVER_LOCKED)) {
|
||||
pr_err("ALSA: seq: 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 */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
|
||||
list_for_each_entry(dev, &ops->dev_list, list) {
|
||||
free_device(dev, ops);
|
||||
}
|
||||
|
||||
ops->driver = 0;
|
||||
if (ops->num_init_devices > 0)
|
||||
pr_err("ALSA: seq: free_driver: init_devices > 0!! (%d)\n",
|
||||
ops->num_init_devices);
|
||||
mutex_unlock(&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;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
head = opslist.next;
|
||||
while (head != &opslist) {
|
||||
struct ops_list *ops = list_entry(head, struct ops_list, 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;
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize the device - call init_device operator
|
||||
*/
|
||||
static int init_device(struct snd_seq_device *dev, struct ops_list *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) {
|
||||
pr_err("ALSA: seq: 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 {
|
||||
pr_err("ALSA: seq: init_device failed: %s: %s\n",
|
||||
dev->name, dev->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the device - call free_device operator
|
||||
*/
|
||||
static int free_device(struct snd_seq_device *dev, struct ops_list *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) {
|
||||
pr_err("ALSA: seq: 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 {
|
||||
pr_err("ALSA: seq: free_device failed: %s: %s\n",
|
||||
dev->name, dev->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* find the matching driver with given id
|
||||
*/
|
||||
static struct ops_list * find_driver(char *id, int create_if_empty)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if (strcmp(ops->id, id) == 0) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
if (create_if_empty)
|
||||
return create_driver(id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void unlock_driver(struct ops_list *ops)
|
||||
{
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* module part
|
||||
*/
|
||||
|
||||
static int __init alsa_seq_device_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PROC_FS
|
||||
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 = snd_seq_device_info;
|
||||
if (snd_info_register(info_entry) < 0) {
|
||||
snd_info_free_entry(info_entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_device_exit(void)
|
||||
{
|
||||
remove_drivers();
|
||||
#ifdef CONFIG_PROC_FS
|
||||
snd_info_free_entry(info_entry);
|
||||
#endif
|
||||
if (num_ops)
|
||||
pr_err("ALSA: seq: 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_MODULES
|
||||
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
||||
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
||||
#endif
|
||||
230
sound/core/seq/seq_dummy.c
Normal file
230
sound/core/seq/seq_dummy.c
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.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 a configuration file under /etc/modprobe.d/:
|
||||
|
||||
option snd-seq-dummy ports=4
|
||||
|
||||
The model 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 bool duplex;
|
||||
|
||||
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");
|
||||
|
||||
struct snd_seq_dummy_port {
|
||||
int client;
|
||||
int port;
|
||||
int duplex;
|
||||
int connect;
|
||||
};
|
||||
|
||||
static int my_client = -1;
|
||||
|
||||
/*
|
||||
* event input callback - just redirect events to subscribers
|
||||
*/
|
||||
static int
|
||||
dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
|
||||
int atomic, int hop)
|
||||
{
|
||||
struct snd_seq_dummy_port *p;
|
||||
struct snd_seq_event 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)
|
||||
{
|
||||
kfree(private_data);
|
||||
}
|
||||
|
||||
/*
|
||||
* create a port
|
||||
*/
|
||||
static struct snd_seq_dummy_port __init *
|
||||
create_port(int idx, int type)
|
||||
{
|
||||
struct snd_seq_port_info pinfo;
|
||||
struct snd_seq_port_callback pcb;
|
||||
struct snd_seq_dummy_port *rec;
|
||||
|
||||
if ((rec = kzalloc(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
|
||||
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
|
||||
| SNDRV_SEQ_PORT_TYPE_PORT;
|
||||
memset(&pcb, 0, sizeof(pcb));
|
||||
pcb.owner = THIS_MODULE;
|
||||
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)
|
||||
{
|
||||
struct snd_seq_dummy_port *rec1, *rec2;
|
||||
int i;
|
||||
|
||||
if (ports < 1) {
|
||||
pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* create client */
|
||||
my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
|
||||
"Midi Through");
|
||||
if (my_client < 0)
|
||||
return my_client;
|
||||
|
||||
/* 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)
|
||||
272
sound/core/seq/seq_fifo.c
Normal file
272
sound/core/seq/seq_fifo.c
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* 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/core.h>
|
||||
#include <linux/slab.h>
|
||||
#include "seq_fifo.h"
|
||||
#include "seq_lock.h"
|
||||
|
||||
|
||||
/* FIFO */
|
||||
|
||||
/* create new fifo */
|
||||
struct snd_seq_fifo *snd_seq_fifo_new(int poolsize)
|
||||
{
|
||||
struct snd_seq_fifo *f;
|
||||
|
||||
f = kzalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_fifo **fifo)
|
||||
{
|
||||
struct snd_seq_fifo *f;
|
||||
|
||||
if (snd_BUG_ON(!fifo))
|
||||
return;
|
||||
f = *fifo;
|
||||
if (snd_BUG_ON(!f))
|
||||
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 struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f);
|
||||
|
||||
/* clear queue */
|
||||
void snd_seq_fifo_clear(struct snd_seq_fifo *f)
|
||||
{
|
||||
struct snd_seq_event_cell *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(struct snd_seq_fifo *f,
|
||||
struct snd_seq_event *event)
|
||||
{
|
||||
struct snd_seq_event_cell *cell;
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!f))
|
||||
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) || (err == -EAGAIN))
|
||||
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 struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f)
|
||||
{
|
||||
struct snd_seq_event_cell *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(struct snd_seq_fifo *f,
|
||||
struct snd_seq_event_cell **cellp, int nonblock)
|
||||
{
|
||||
struct snd_seq_event_cell *cell;
|
||||
unsigned long flags;
|
||||
wait_queue_t wait;
|
||||
|
||||
if (snd_BUG_ON(!f))
|
||||
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(struct snd_seq_fifo *f,
|
||||
struct snd_seq_event_cell *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(struct snd_seq_fifo *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(struct snd_seq_fifo *f, int poolsize)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_pool *newpool, *oldpool;
|
||||
struct snd_seq_event_cell *cell, *next, *oldhead;
|
||||
|
||||
if (snd_BUG_ON(!f || !f->pool))
|
||||
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;
|
||||
}
|
||||
72
sound/core/seq/seq_fifo.h
Normal file
72
sound/core/seq/seq_fifo.h
Normal file
|
|
@ -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 === */
|
||||
|
||||
struct snd_seq_fifo {
|
||||
struct snd_seq_pool *pool; /* FIFO pool */
|
||||
struct snd_seq_event_cell *head; /* pointer to head of fifo */
|
||||
struct snd_seq_event_cell *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;
|
||||
|
||||
};
|
||||
|
||||
/* create new fifo (constructor) */
|
||||
struct snd_seq_fifo *snd_seq_fifo_new(int poolsize);
|
||||
|
||||
/* delete fifo (destructor) */
|
||||
void snd_seq_fifo_delete(struct snd_seq_fifo **f);
|
||||
|
||||
|
||||
/* enqueue event to fifo */
|
||||
int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *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(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock);
|
||||
|
||||
/* free dequeued cell - fifo should be locked */
|
||||
void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell);
|
||||
|
||||
/* clean up queue */
|
||||
void snd_seq_fifo_clear(struct snd_seq_fifo *f);
|
||||
|
||||
/* polling */
|
||||
int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait);
|
||||
|
||||
/* resize pool in fifo */
|
||||
int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize);
|
||||
|
||||
|
||||
#endif
|
||||
72
sound/core/seq/seq_info.c
Normal file
72
sound/core/seq/seq_info.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/core.h>
|
||||
|
||||
#include "seq_info.h"
|
||||
#include "seq_clientmgr.h"
|
||||
#include "seq_timer.h"
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static struct snd_info_entry *queues_entry;
|
||||
static struct snd_info_entry *clients_entry;
|
||||
static struct snd_info_entry *timer_entry;
|
||||
|
||||
|
||||
static struct snd_info_entry * __init
|
||||
create_info_entry(char *name, void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *))
|
||||
{
|
||||
struct snd_info_entry *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 = 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",
|
||||
snd_seq_info_queues_read);
|
||||
clients_entry = create_info_entry("clients",
|
||||
snd_seq_info_clients_read);
|
||||
timer_entry = create_info_entry("timer", snd_seq_info_timer_read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __exit snd_seq_info_done(void)
|
||||
{
|
||||
snd_info_free_entry(queues_entry);
|
||||
snd_info_free_entry(clients_entry);
|
||||
snd_info_free_entry(timer_entry);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
40
sound/core/seq/seq_info.h
Normal file
40
sound/core/seq/seq_info.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
|
||||
void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
|
||||
void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
int snd_seq_info_init( void );
|
||||
int snd_seq_info_done( void );
|
||||
#else
|
||||
static inline int snd_seq_info_init(void) { return 0; }
|
||||
static inline int snd_seq_info_done(void) { return 0; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
49
sound/core/seq/seq_lock.c
Normal file
49
sound/core/seq/seq_lock.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 <linux/export.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) {
|
||||
pr_warn("ALSA: 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) {
|
||||
pr_warn("ALSA: seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line);
|
||||
break;
|
||||
}
|
||||
schedule_timeout_uninterruptible(1);
|
||||
max_count--;
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_use_lock_sync_helper);
|
||||
|
||||
#endif
|
||||
33
sound/core/seq/seq_lock.h
Normal file
33
sound/core/seq/seq_lock.h
Normal file
|
|
@ -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 */
|
||||
521
sound/core/seq/seq_memory.c
Normal file
521
sound/core/seq/seq_memory.c
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
/*
|
||||
* ALSA sequencer Memory Manager
|
||||
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
|
||||
* Jaroslav Kysela <perex@perex.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 <linux/init.h>
|
||||
#include <linux/export.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"
|
||||
|
||||
static inline int snd_seq_pool_available(struct snd_seq_pool *pool)
|
||||
{
|
||||
return pool->total_elements - atomic_read(&pool->counter);
|
||||
}
|
||||
|
||||
static inline int snd_seq_output_ok(struct snd_seq_pool *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 struct snd_seq_event *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 struct snd_seq_event *event,
|
||||
snd_seq_dump_func_t func, void *private_data)
|
||||
{
|
||||
int len, err;
|
||||
struct snd_seq_event_cell *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 __force __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 = (struct snd_seq_event_cell *)event->data.ext.ptr;
|
||||
for (; len > 0 && cell; cell = cell->next) {
|
||||
int size = sizeof(struct snd_seq_event);
|
||||
if (len < size)
|
||||
size = len;
|
||||
err = func(private_data, &cell->event, size);
|
||||
if (err < 0)
|
||||
return err;
|
||||
len -= size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_seq_dump_var_event);
|
||||
|
||||
|
||||
/*
|
||||
* 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 struct snd_seq_event *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 = roundup(len, 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 __force __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;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_seq_expand_var_event);
|
||||
|
||||
/*
|
||||
* release this cell, free extended data if available
|
||||
*/
|
||||
|
||||
static inline void free_cell(struct snd_seq_pool *pool,
|
||||
struct snd_seq_event_cell *cell)
|
||||
{
|
||||
cell->next = pool->free;
|
||||
pool->free = cell;
|
||||
atomic_dec(&pool->counter);
|
||||
}
|
||||
|
||||
void snd_seq_cell_free(struct snd_seq_event_cell * cell)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_pool *pool;
|
||||
|
||||
if (snd_BUG_ON(!cell))
|
||||
return;
|
||||
pool = cell->pool;
|
||||
if (snd_BUG_ON(!pool))
|
||||
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) {
|
||||
struct snd_seq_event_cell *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(struct snd_seq_pool *pool,
|
||||
struct snd_seq_event_cell **cellp,
|
||||
int nonblock, struct file *file)
|
||||
{
|
||||
struct snd_seq_event_cell *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 */
|
||||
pr_debug("ALSA: 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(struct snd_seq_pool *pool, struct snd_seq_event *event,
|
||||
struct snd_seq_event_cell **cellp, int nonblock,
|
||||
struct file *file)
|
||||
{
|
||||
int ncells, err;
|
||||
unsigned int extlen;
|
||||
struct snd_seq_event_cell *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(struct snd_seq_event) - 1) / sizeof(struct snd_seq_event);
|
||||
}
|
||||
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;
|
||||
struct snd_seq_event_cell *src, *tmp, *tail;
|
||||
char *buf;
|
||||
|
||||
cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
|
||||
cell->event.data.ext.ptr = NULL;
|
||||
|
||||
src = (struct snd_seq_event_cell *)event->data.ext.ptr;
|
||||
buf = (char *)event->data.ext.ptr;
|
||||
tail = NULL;
|
||||
|
||||
while (ncells-- > 0) {
|
||||
int size = sizeof(struct snd_seq_event);
|
||||
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 __force __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(struct snd_seq_pool *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(struct snd_seq_pool *pool)
|
||||
{
|
||||
int cell;
|
||||
struct snd_seq_event_cell *cellptr;
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!pool))
|
||||
return -EINVAL;
|
||||
if (pool->ptr) /* should be atomic? */
|
||||
return 0;
|
||||
|
||||
pool->ptr = vmalloc(sizeof(struct snd_seq_event_cell) * pool->size);
|
||||
if (pool->ptr == NULL) {
|
||||
pr_debug("ALSA: 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(struct snd_seq_pool *pool)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_event_cell *ptr;
|
||||
int max_count = 5 * HZ;
|
||||
|
||||
if (snd_BUG_ON(!pool))
|
||||
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) {
|
||||
pr_warn("ALSA: snd_seq_pool_done timeout: %d cells remain\n", atomic_read(&pool->counter));
|
||||
break;
|
||||
}
|
||||
schedule_timeout_uninterruptible(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 */
|
||||
struct snd_seq_pool *snd_seq_pool_new(int poolsize)
|
||||
{
|
||||
struct snd_seq_pool *pool;
|
||||
|
||||
/* create pool block */
|
||||
pool = kzalloc(sizeof(*pool), GFP_KERNEL);
|
||||
if (pool == NULL) {
|
||||
pr_debug("ALSA: 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(struct snd_seq_pool **ppool)
|
||||
{
|
||||
struct snd_seq_pool *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(struct snd_info_buffer *buffer,
|
||||
struct snd_seq_pool *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);
|
||||
}
|
||||
107
sound/core/seq/seq_memory.h
Normal file
107
sound/core/seq/seq_memory.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
struct snd_info_buffer;
|
||||
|
||||
/* container for sequencer event (internal use) */
|
||||
struct snd_seq_event_cell {
|
||||
struct snd_seq_event event;
|
||||
struct snd_seq_pool *pool; /* used pool */
|
||||
struct snd_seq_event_cell *next; /* next cell */
|
||||
};
|
||||
|
||||
/* design note: the pool is a contiguous 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 snd_seq_pool {
|
||||
struct snd_seq_event_cell *ptr; /* pointer to first event chunk */
|
||||
struct snd_seq_event_cell *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;
|
||||
};
|
||||
|
||||
void snd_seq_cell_free(struct snd_seq_event_cell *cell);
|
||||
|
||||
int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
|
||||
struct snd_seq_event_cell **cellp, int nonblock, struct file *file);
|
||||
|
||||
/* return number of unused (free) cells */
|
||||
static inline int snd_seq_unused_cells(struct snd_seq_pool *pool)
|
||||
{
|
||||
return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
|
||||
}
|
||||
|
||||
/* return total number of allocated cells */
|
||||
static inline int snd_seq_total_cells(struct snd_seq_pool *pool)
|
||||
{
|
||||
return pool ? pool->total_elements : 0;
|
||||
}
|
||||
|
||||
/* init pool - allocate events */
|
||||
int snd_seq_pool_init(struct snd_seq_pool *pool);
|
||||
|
||||
/* done pool - free events */
|
||||
int snd_seq_pool_done(struct snd_seq_pool *pool);
|
||||
|
||||
/* create pool */
|
||||
struct snd_seq_pool *snd_seq_pool_new(int poolsize);
|
||||
|
||||
/* remove pool */
|
||||
int snd_seq_pool_delete(struct snd_seq_pool **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(struct snd_seq_pool *pool, struct file *file, poll_table *wait);
|
||||
|
||||
void snd_seq_info_pool(struct snd_info_buffer *buffer,
|
||||
struct snd_seq_pool *pool, char *space);
|
||||
|
||||
#endif
|
||||
481
sound/core/seq/seq_midi.c
Normal file
481
sound/core/seq/seq_midi.c
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
/*
|
||||
* Generic MIDI synth driver for ALSA sequencer
|
||||
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
Possible options for midisynth module:
|
||||
- automatic opening of midi ports on first received event or subscription
|
||||
(close will be performed when client leaves)
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.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@perex.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 */
|
||||
struct seq_midisynth {
|
||||
struct snd_card *card;
|
||||
int device;
|
||||
int subdevice;
|
||||
struct snd_rawmidi_file input_rfile;
|
||||
struct snd_rawmidi_file output_rfile;
|
||||
int seq_client;
|
||||
int seq_port;
|
||||
struct snd_midi_event *parser;
|
||||
};
|
||||
|
||||
struct seq_midisynth_client {
|
||||
int seq_client;
|
||||
int num_ports;
|
||||
int ports_per_device[SNDRV_RAWMIDI_DEVICES];
|
||||
struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES];
|
||||
};
|
||||
|
||||
static struct seq_midisynth_client *synths[SNDRV_CARDS];
|
||||
static DEFINE_MUTEX(register_mutex);
|
||||
|
||||
/* handle rawmidi input event (MIDI v1.0 stream) */
|
||||
static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_rawmidi_runtime *runtime;
|
||||
struct seq_midisynth *msynth;
|
||||
struct snd_seq_event ev;
|
||||
char buf[16], *pbuf;
|
||||
long res, count;
|
||||
|
||||
if (substream == NULL)
|
||||
return;
|
||||
runtime = substream->runtime;
|
||||
msynth = 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(struct snd_rawmidi_substream *substream, const char *buf, int count)
|
||||
{
|
||||
struct snd_rawmidi_runtime *runtime;
|
||||
int tmp;
|
||||
|
||||
if (snd_BUG_ON(!substream || !buf))
|
||||
return -EINVAL;
|
||||
runtime = substream->runtime;
|
||||
if ((tmp = runtime->avail) < count) {
|
||||
if (printk_ratelimit())
|
||||
pr_err("ALSA: seq_midi: MIDI output buffer overrun\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (snd_rawmidi_kernel_write(substream, buf, count) < count)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int event_process_midi(struct snd_seq_event *ev, int direct,
|
||||
void *private_data, int atomic, int hop)
|
||||
{
|
||||
struct seq_midisynth *msynth = private_data;
|
||||
unsigned char msg[10]; /* buffer for constructing midi messages */
|
||||
struct snd_rawmidi_substream *substream;
|
||||
int len;
|
||||
|
||||
if (snd_BUG_ON(!msynth))
|
||||
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 */
|
||||
pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
|
||||
return 0;
|
||||
}
|
||||
snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
|
||||
snd_midi_event_reset_decode(msynth->parser);
|
||||
} else {
|
||||
if (msynth->parser == NULL)
|
||||
return -EIO;
|
||||
len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
|
||||
if (len < 0)
|
||||
return 0;
|
||||
if (dump_midi(substream, msg, len) < 0)
|
||||
snd_midi_event_reset_decode(msynth->parser);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int snd_seq_midisynth_new(struct seq_midisynth *msynth,
|
||||
struct snd_card *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, struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
int err;
|
||||
struct seq_midisynth *msynth = private_data;
|
||||
struct snd_rawmidi_runtime *runtime;
|
||||
struct snd_rawmidi_params params;
|
||||
|
||||
/* open midi port */
|
||||
if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
|
||||
msynth->subdevice,
|
||||
SNDRV_RAWMIDI_LFLG_INPUT,
|
||||
&msynth->input_rfile)) < 0) {
|
||||
pr_debug("ALSA: seq_midi: midi input open failed!!!\n");
|
||||
return err;
|
||||
}
|
||||
runtime = msynth->input_rfile.input->runtime;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.avail_min = 1;
|
||||
params.buffer_size = input_buffer_size;
|
||||
if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms)) < 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, struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
int err;
|
||||
struct seq_midisynth *msynth = private_data;
|
||||
|
||||
if (snd_BUG_ON(!msynth->input_rfile.input))
|
||||
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, struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
int err;
|
||||
struct seq_midisynth *msynth = private_data;
|
||||
struct snd_rawmidi_params params;
|
||||
|
||||
/* open midi port */
|
||||
if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
|
||||
msynth->subdevice,
|
||||
SNDRV_RAWMIDI_LFLG_OUTPUT,
|
||||
&msynth->output_rfile)) < 0) {
|
||||
pr_debug("ALSA: seq_midi: midi output open failed!!!\n");
|
||||
return err;
|
||||
}
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.avail_min = 1;
|
||||
params.buffer_size = output_buffer_size;
|
||||
params.no_active_sensing = 1;
|
||||
if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms)) < 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, struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct seq_midisynth *msynth = private_data;
|
||||
|
||||
if (snd_BUG_ON(!msynth->output_rfile.output))
|
||||
return -EINVAL;
|
||||
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(struct seq_midisynth *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);
|
||||
}
|
||||
|
||||
/* register new midi synth port */
|
||||
static int
|
||||
snd_seq_midisynth_register_port(struct snd_seq_device *dev)
|
||||
{
|
||||
struct seq_midisynth_client *client;
|
||||
struct seq_midisynth *msynth, *ms;
|
||||
struct snd_seq_port_info *port;
|
||||
struct snd_rawmidi_info *info;
|
||||
struct snd_rawmidi *rmidi = dev->private_data;
|
||||
int newclient = 0;
|
||||
unsigned int p, ports;
|
||||
struct snd_seq_port_callback pcallbacks;
|
||||
struct snd_card *card = dev->card;
|
||||
int device = dev->device;
|
||||
unsigned int input_count = 0, output_count = 0;
|
||||
|
||||
if (snd_BUG_ON(!card || 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;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
client = synths[card->number];
|
||||
if (client == NULL) {
|
||||
newclient = 1;
|
||||
client = kzalloc(sizeof(*client), GFP_KERNEL);
|
||||
if (client == NULL) {
|
||||
mutex_unlock(®ister_mutex);
|
||||
kfree(info);
|
||||
return -ENOMEM;
|
||||
}
|
||||
client->seq_client =
|
||||
snd_seq_create_kernel_client(
|
||||
card, 0, "%s", card->shortname[0] ?
|
||||
(const char *)card->shortname : "External MIDI");
|
||||
if (client->seq_client < 0) {
|
||||
kfree(client);
|
||||
mutex_unlock(®ister_mutex);
|
||||
kfree(info);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
msynth = kcalloc(ports, sizeof(struct seq_midisynth), 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-%u", 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-%u", 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
|
||||
| SNDRV_SEQ_PORT_TYPE_HARDWARE
|
||||
| SNDRV_SEQ_PORT_TYPE_PORT;
|
||||
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 (rmidi->ops && rmidi->ops->get_port_info)
|
||||
rmidi->ops->get_port_info(rmidi, p, port);
|
||||
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;
|
||||
mutex_unlock(®ister_mutex);
|
||||
kfree(info);
|
||||
kfree(port);
|
||||
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);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* release midi synth port */
|
||||
static int
|
||||
snd_seq_midisynth_unregister_port(struct snd_seq_device *dev)
|
||||
{
|
||||
struct seq_midisynth_client *client;
|
||||
struct seq_midisynth *msynth;
|
||||
struct snd_card *card = dev->card;
|
||||
int device = dev->device, p, ports;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
client = synths[card->number];
|
||||
if (client == NULL || client->ports[device] == NULL) {
|
||||
mutex_unlock(®ister_mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
ports = client->ports_per_device[device];
|
||||
client->ports_per_device[device] = 0;
|
||||
msynth = client->ports[device];
|
||||
client->ports[device] = NULL;
|
||||
for (p = 0; p < ports; p++)
|
||||
snd_seq_midisynth_delete(&msynth[p]);
|
||||
kfree(msynth);
|
||||
client->num_ports--;
|
||||
if (client->num_ports <= 0) {
|
||||
snd_seq_delete_kernel_client(client->seq_client);
|
||||
synths[card->number] = NULL;
|
||||
kfree(client);
|
||||
}
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int __init alsa_seq_midi_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops 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)
|
||||
740
sound/core/seq/seq_midi_emul.c
Normal file
740
sound/core/seq/seq_midi_emul.c
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/module.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(struct snd_midi_op *ops, void *drv,
|
||||
struct snd_midi_channel *chan,
|
||||
int note, int vel);
|
||||
static void do_control(struct snd_midi_op *ops, void *private,
|
||||
struct snd_midi_channel_set *chset,
|
||||
struct snd_midi_channel *chan,
|
||||
int control, int value);
|
||||
static void rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
||||
struct snd_midi_channel_set *chset);
|
||||
static void nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
||||
struct snd_midi_channel_set *chset);
|
||||
static void sysex(struct snd_midi_op *ops, void *private, unsigned char *sysex,
|
||||
int len, struct snd_midi_channel_set *chset);
|
||||
static void all_sounds_off(struct snd_midi_op *ops, void *private,
|
||||
struct snd_midi_channel *chan);
|
||||
static void all_notes_off(struct snd_midi_op *ops, void *private,
|
||||
struct snd_midi_channel *chan);
|
||||
static void snd_midi_reset_controllers(struct snd_midi_channel *chan);
|
||||
static void reset_all_channels(struct snd_midi_channel_set *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 controllers 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(struct snd_midi_op *ops,
|
||||
struct snd_seq_event *ev,
|
||||
struct snd_midi_channel_set *chanset)
|
||||
{
|
||||
struct snd_midi_channel *chan;
|
||||
void *drv;
|
||||
int dest_channel = 0;
|
||||
|
||||
if (ev == NULL || chanset == NULL) {
|
||||
pr_debug("ALSA: seq_midi_emul: 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) {
|
||||
pr_debug("ALSA: seq_midi_emul: 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 controller 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 controller 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_ECHO:
|
||||
not_yet:
|
||||
default:
|
||||
/*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* release note
|
||||
*/
|
||||
static void
|
||||
note_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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 controller and pass
|
||||
* events that need to take place immediately to the driver.
|
||||
*/
|
||||
static void
|
||||
do_control(struct snd_midi_op *ops, void *drv, struct snd_midi_channel_set *chset,
|
||||
struct snd_midi_channel *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(struct snd_midi_channel_set *chset)
|
||||
{
|
||||
int i;
|
||||
|
||||
chset->midi_mode = SNDRV_MIDI_MODE_GM;
|
||||
chset->gs_master_volume = 127;
|
||||
|
||||
for (i = 0; i < chset->max_channels; i++) {
|
||||
struct snd_midi_channel *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
||||
struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
||||
struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *private, unsigned char *buf, int len,
|
||||
struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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(struct snd_midi_channel *p, int n)
|
||||
{
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
memset(p, 0, sizeof(struct snd_midi_channel));
|
||||
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 struct snd_midi_channel *snd_midi_channel_init_set(int n)
|
||||
{
|
||||
struct snd_midi_channel *chan;
|
||||
int i;
|
||||
|
||||
chan = kmalloc(n * sizeof(struct snd_midi_channel), 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(struct snd_midi_channel_set *chset)
|
||||
{
|
||||
int ch;
|
||||
for (ch = 0; ch < chset->max_channels; ch++) {
|
||||
struct snd_midi_channel *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.
|
||||
*/
|
||||
struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n)
|
||||
{
|
||||
struct snd_midi_channel_set *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(struct snd_midi_channel *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(struct snd_midi_channel_set *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);
|
||||
550
sound/core/seq/seq_midi_event.c
Normal file
550
sound/core/seq/seq_midi_event.c
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
/*
|
||||
* MIDI byte <-> sequencer event coder
|
||||
*
|
||||
* Copyright (C) 1998,99 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
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/module.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@perex.cz>");
|
||||
MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* event type, index into status_event[] */
|
||||
/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */
|
||||
#define ST_INVALID 7
|
||||
#define ST_SPECIAL 8
|
||||
#define ST_SYSEX ST_SPECIAL
|
||||
/* from 8 to 15 are events for 0xf0-0xf7 */
|
||||
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
static void note_decode(struct snd_seq_event *ev, unsigned char *buf);
|
||||
static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf);
|
||||
static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf);
|
||||
static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf);
|
||||
static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf);
|
||||
|
||||
/*
|
||||
* event list
|
||||
*/
|
||||
static struct status_event_list {
|
||||
int event;
|
||||
int qlen;
|
||||
void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev);
|
||||
void (*decode)(struct snd_seq_event *ev, unsigned char *buf);
|
||||
} status_event[] = {
|
||||
/* 0x80 - 0xef */
|
||||
{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},
|
||||
/* invalid */
|
||||
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL},
|
||||
/* 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, -1, NULL, NULL}, /* 0xf4 */
|
||||
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf5 */
|
||||
{SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */
|
||||
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf7 */
|
||||
{SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */
|
||||
{SNDRV_SEQ_EVENT_NONE, -1, 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, -1, NULL, NULL}, /* 0xfd */
|
||||
{SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */
|
||||
{SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */
|
||||
};
|
||||
|
||||
static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len,
|
||||
struct snd_seq_event *ev);
|
||||
static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count,
|
||||
struct snd_seq_event *ev);
|
||||
|
||||
static struct extra_event_list {
|
||||
int event;
|
||||
int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len,
|
||||
struct snd_seq_event *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, struct snd_midi_event **rdev)
|
||||
{
|
||||
struct snd_midi_event *dev;
|
||||
|
||||
*rdev = NULL;
|
||||
dev = kzalloc(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;
|
||||
dev->type = ST_INVALID;
|
||||
spin_lock_init(&dev->lock);
|
||||
*rdev = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void snd_midi_event_free(struct snd_midi_event *dev)
|
||||
{
|
||||
if (dev != NULL) {
|
||||
kfree(dev->buf);
|
||||
kfree(dev);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize record
|
||||
*/
|
||||
static inline void reset_encode(struct snd_midi_event *dev)
|
||||
{
|
||||
dev->read = 0;
|
||||
dev->qlen = 0;
|
||||
dev->type = ST_INVALID;
|
||||
}
|
||||
|
||||
void snd_midi_event_reset_encode(struct snd_midi_event *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(struct snd_midi_event *dev)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
dev->lastcmd = 0xff;
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void snd_midi_event_init(struct snd_midi_event *dev)
|
||||
{
|
||||
snd_midi_event_reset_encode(dev);
|
||||
snd_midi_event_reset_decode(dev);
|
||||
}
|
||||
#endif /* 0 */
|
||||
|
||||
void snd_midi_event_no_status(struct snd_midi_event *dev, int on)
|
||||
{
|
||||
dev->nostat = on ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* resize buffer
|
||||
*/
|
||||
#if 0
|
||||
int snd_midi_event_resize_buffer(struct snd_midi_event *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;
|
||||
}
|
||||
#endif /* 0 */
|
||||
|
||||
/*
|
||||
* read bytes and encode to sequencer event if finished
|
||||
* return the size of encoded bytes
|
||||
*/
|
||||
long snd_midi_event_encode(struct snd_midi_event *dev, unsigned char *buf, long count,
|
||||
struct snd_seq_event *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(struct snd_midi_event *dev, int c,
|
||||
struct snd_seq_event *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 ev->type != SNDRV_SEQ_EVENT_NONE;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if ((c & 0x80) &&
|
||||
(c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) {
|
||||
/* new command */
|
||||
dev->buf[0] = c;
|
||||
if ((c & 0xf0) == 0xf0) /* system messages */
|
||||
dev->type = (c & 0x0f) + ST_SPECIAL;
|
||||
else
|
||||
dev->type = (c >> 4) & 0x07;
|
||||
dev->read = 1;
|
||||
dev->qlen = status_event[dev->type].qlen;
|
||||
} else {
|
||||
if (dev->qlen > 0) {
|
||||
/* rest of command */
|
||||
dev->buf[dev->read++] = c;
|
||||
if (dev->type != ST_SYSEX)
|
||||
dev->qlen--;
|
||||
} else {
|
||||
/* running status */
|
||||
dev->buf[1] = c;
|
||||
dev->qlen = status_event[dev->type].qlen - 1;
|
||||
dev->read = 2;
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (dev->type >= ST_SPECIAL)
|
||||
dev->type = ST_INVALID;
|
||||
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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *ev)
|
||||
{
|
||||
ev->data.control.value = dev->buf[1];
|
||||
}
|
||||
|
||||
/* encode song position */
|
||||
static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf, long count,
|
||||
struct snd_seq_event *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(struct snd_seq_event *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(struct snd_seq_event *ev, unsigned char *buf)
|
||||
{
|
||||
buf[0] = ev->data.control.value & 0x7f;
|
||||
}
|
||||
|
||||
/* decode pitch wheel change */
|
||||
static void pitchbend_decode(struct snd_seq_event *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(struct snd_seq_event *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(struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf,
|
||||
int count, struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf,
|
||||
int count, struct snd_seq_event *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 & 0x3f80) >> 7;
|
||||
bytes[1] = ev->data.control.param & 0x007f;
|
||||
bytes[2] = (ev->data.control.value & 0x3f80) >> 7;
|
||||
bytes[3] = ev->data.control.value & 0x007f;
|
||||
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_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)
|
||||
685
sound/core/seq/seq_ports.c
Normal file
685
sound/core/seq/seq_ports.c
Normal file
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
* ALSA sequencer Ports
|
||||
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.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 */
|
||||
struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client,
|
||||
int num)
|
||||
{
|
||||
struct snd_seq_client_port *port;
|
||||
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
read_lock(&client->ports_lock);
|
||||
list_for_each_entry(port, &client->ports_list_head, 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 */
|
||||
struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
|
||||
struct snd_seq_port_info *pinfo)
|
||||
{
|
||||
int num;
|
||||
struct snd_seq_client_port *port, *found;
|
||||
|
||||
num = pinfo->addr.port;
|
||||
found = NULL;
|
||||
read_lock(&client->ports_lock);
|
||||
list_for_each_entry(port, &client->ports_list_head, 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 snd_seq_port_subs_info */
|
||||
static void port_subs_info_init(struct snd_seq_port_subs_info *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) */
|
||||
struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
|
||||
int port)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_client_port *new_port, *p;
|
||||
int num = -1;
|
||||
|
||||
/* sanity check */
|
||||
if (snd_BUG_ON(!client))
|
||||
return NULL;
|
||||
|
||||
if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) {
|
||||
pr_warn("ALSA: seq: too many ports for client %d\n", client->number);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* create a new port */
|
||||
new_port = kzalloc(sizeof(*new_port), GFP_KERNEL);
|
||||
if (! new_port) {
|
||||
pr_debug("ALSA: seq: 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;
|
||||
mutex_lock(&client->ports_mutex);
|
||||
write_lock_irqsave(&client->ports_lock, flags);
|
||||
list_for_each_entry(p, &client->ports_list_head, 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, &p->list);
|
||||
client->num_ports++;
|
||||
new_port->addr.port = num; /* store the port number in the port */
|
||||
write_unlock_irqrestore(&client->ports_lock, flags);
|
||||
mutex_unlock(&client->ports_mutex);
|
||||
sprintf(new_port->name, "port-%d", num);
|
||||
|
||||
return new_port;
|
||||
}
|
||||
|
||||
/* */
|
||||
enum group_type {
|
||||
SRC_LIST, DEST_LIST
|
||||
};
|
||||
|
||||
static int subscribe_port(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subs_info *grp,
|
||||
struct snd_seq_port_subscribe *info, int send_ack);
|
||||
static int unsubscribe_port(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subs_info *grp,
|
||||
struct snd_seq_port_subscribe *info, int send_ack);
|
||||
|
||||
|
||||
static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr,
|
||||
struct snd_seq_client **cp)
|
||||
{
|
||||
struct snd_seq_client_port *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(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subs_info *grp,
|
||||
int grptype)
|
||||
{
|
||||
struct list_head *p, *n;
|
||||
|
||||
list_for_each_safe(p, n, &grp->list_head) {
|
||||
struct snd_seq_subscribers *subs;
|
||||
struct snd_seq_client *c;
|
||||
struct snd_seq_client_port *aport;
|
||||
|
||||
if (grptype == SRC_LIST) {
|
||||
subs = list_entry(p, struct snd_seq_subscribers, src_list);
|
||||
aport = get_client_port(&subs->info.dest, &c);
|
||||
} else {
|
||||
subs = list_entry(p, struct snd_seq_subscribers, 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 */
|
||||
struct snd_seq_port_subs_info *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);
|
||||
up_write(&agrp->list_mutex);
|
||||
unsubscribe_port(c, aport, agrp, &subs->info, 1);
|
||||
kfree(subs);
|
||||
snd_seq_port_unlock(aport);
|
||||
snd_seq_client_unlock(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* delete port data */
|
||||
static int port_delete(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *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_BUG_ON(port->c_src.count != 0);
|
||||
snd_BUG_ON(port->c_dest.count != 0);
|
||||
|
||||
kfree(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* delete a port with the given port id */
|
||||
int snd_seq_delete_port(struct snd_seq_client *client, int port)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_client_port *found = NULL, *p;
|
||||
|
||||
mutex_lock(&client->ports_mutex);
|
||||
write_lock_irqsave(&client->ports_lock, flags);
|
||||
list_for_each_entry(p, &client->ports_list_head, list) {
|
||||
if (p->addr.port == port) {
|
||||
/* ok found. delete from the list at first */
|
||||
list_del(&p->list);
|
||||
client->num_ports--;
|
||||
found = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write_unlock_irqrestore(&client->ports_lock, flags);
|
||||
mutex_unlock(&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(struct snd_seq_client *client)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct list_head deleted_list;
|
||||
struct snd_seq_client_port *port, *tmp;
|
||||
|
||||
/* move the port list to deleted_list, and
|
||||
* clear the port list in the client data.
|
||||
*/
|
||||
mutex_lock(&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);
|
||||
list_del_init(&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_entry_safe(port, tmp, &deleted_list, list) {
|
||||
list_del(&port->list);
|
||||
snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
|
||||
port_delete(client, port);
|
||||
}
|
||||
mutex_unlock(&client->ports_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set port info fields */
|
||||
int snd_seq_set_port_info(struct snd_seq_client_port * port,
|
||||
struct snd_seq_port_info * info)
|
||||
{
|
||||
if (snd_BUG_ON(!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(struct snd_seq_client_port * port,
|
||||
struct snd_seq_port_info * info)
|
||||
{
|
||||
if (snd_BUG_ON(!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 connection/disconnection.
|
||||
*/
|
||||
|
||||
static int subscribe_port(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subs_info *grp,
|
||||
struct snd_seq_port_subscribe *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(struct snd_seq_client *client,
|
||||
struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subs_info *grp,
|
||||
struct snd_seq_port_subscribe *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(struct snd_seq_addr *r, struct snd_seq_addr *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(struct snd_seq_port_subscribe *r,
|
||||
struct snd_seq_port_subscribe *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(struct snd_seq_client *connector,
|
||||
struct snd_seq_client *src_client,
|
||||
struct snd_seq_client_port *src_port,
|
||||
struct snd_seq_client *dest_client,
|
||||
struct snd_seq_client_port *dest_port,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_seq_port_subs_info *src = &src_port->c_src;
|
||||
struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
|
||||
struct snd_seq_subscribers *subs, *s;
|
||||
int err, src_called = 0;
|
||||
unsigned long flags;
|
||||
int exclusive;
|
||||
|
||||
subs = kzalloc(sizeof(*subs), GFP_KERNEL);
|
||||
if (! subs)
|
||||
return -ENOMEM;
|
||||
|
||||
subs->info = *info;
|
||||
atomic_set(&subs->ref_count, 2);
|
||||
|
||||
down_write(&src->list_mutex);
|
||||
down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING);
|
||||
|
||||
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_entry(s, &src->list_head, src_list) {
|
||||
if (match_subs_info(info, &s->info))
|
||||
goto __error;
|
||||
}
|
||||
list_for_each_entry(s, &dest->list_head, 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(struct snd_seq_client *connector,
|
||||
struct snd_seq_client *src_client,
|
||||
struct snd_seq_client_port *src_port,
|
||||
struct snd_seq_client *dest_client,
|
||||
struct snd_seq_client_port *dest_port,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_seq_port_subs_info *src = &src_port->c_src;
|
||||
struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
|
||||
struct snd_seq_subscribers *subs;
|
||||
int err = -ENOENT;
|
||||
unsigned long flags;
|
||||
|
||||
down_write(&src->list_mutex);
|
||||
down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING);
|
||||
|
||||
/* look for the connection */
|
||||
list_for_each_entry(subs, &src->list_head, 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 */
|
||||
struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
|
||||
struct snd_seq_addr *dest_addr)
|
||||
{
|
||||
struct snd_seq_subscribers *s, *found = NULL;
|
||||
|
||||
down_read(&src_grp->list_mutex);
|
||||
list_for_each_entry(s, &src_grp->list_head, 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,
|
||||
struct snd_seq_port_callback *pcbp,
|
||||
int cap, int type, int midi_channels,
|
||||
int midi_voices, char *portname)
|
||||
{
|
||||
struct snd_seq_port_info 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;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_seq_event_port_attach);
|
||||
|
||||
/*
|
||||
* Detach the driver from a port.
|
||||
*/
|
||||
/* exported */
|
||||
int snd_seq_event_port_detach(int client, int port)
|
||||
{
|
||||
struct snd_seq_port_info 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;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_seq_event_port_detach);
|
||||
142
sound/core/seq/seq_ports.h
Normal file
142
sound/core/seq/seq_ports.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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.
|
||||
|
||||
*/
|
||||
|
||||
struct snd_seq_subscribers {
|
||||
struct snd_seq_port_subscribe info; /* additional info */
|
||||
struct list_head src_list; /* link of sources */
|
||||
struct list_head dest_list; /* link of destinations */
|
||||
atomic_t ref_count;
|
||||
};
|
||||
|
||||
struct snd_seq_port_subs_info {
|
||||
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;
|
||||
int (*open)(void *private_data, struct snd_seq_port_subscribe *info);
|
||||
int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
|
||||
};
|
||||
|
||||
struct snd_seq_client_port {
|
||||
|
||||
struct snd_seq_addr 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 */
|
||||
struct snd_seq_port_subs_info c_src; /* read (sender) list */
|
||||
struct snd_seq_port_subs_info c_dest; /* write (dest) list */
|
||||
|
||||
int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data,
|
||||
int atomic, int hop);
|
||||
void (*private_free)(void *private_data);
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
struct snd_seq_client;
|
||||
|
||||
/* return pointer to port structure and lock port */
|
||||
struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num);
|
||||
|
||||
/* search for next port - port is locked if found */
|
||||
struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
|
||||
struct snd_seq_port_info *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) */
|
||||
struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index);
|
||||
|
||||
/* delete a port */
|
||||
int snd_seq_delete_port(struct snd_seq_client *client, int port);
|
||||
|
||||
/* delete all ports */
|
||||
int snd_seq_delete_all_ports(struct snd_seq_client *client);
|
||||
|
||||
/* set port info fields */
|
||||
int snd_seq_set_port_info(struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_info *info);
|
||||
|
||||
/* get port info fields */
|
||||
int snd_seq_get_port_info(struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_info *info);
|
||||
|
||||
/* add subscriber to subscription list */
|
||||
int snd_seq_port_connect(struct snd_seq_client *caller,
|
||||
struct snd_seq_client *s, struct snd_seq_client_port *sp,
|
||||
struct snd_seq_client *d, struct snd_seq_client_port *dp,
|
||||
struct snd_seq_port_subscribe *info);
|
||||
|
||||
/* remove subscriber from subscription list */
|
||||
int snd_seq_port_disconnect(struct snd_seq_client *caller,
|
||||
struct snd_seq_client *s, struct snd_seq_client_port *sp,
|
||||
struct snd_seq_client *d, struct snd_seq_client_port *dp,
|
||||
struct snd_seq_port_subscribe *info);
|
||||
|
||||
/* subscribe port */
|
||||
int snd_seq_port_subscribe(struct snd_seq_client_port *port,
|
||||
struct snd_seq_port_subscribe *info);
|
||||
|
||||
/* get matched subscriber */
|
||||
struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
|
||||
struct snd_seq_addr *dest_addr);
|
||||
|
||||
#endif
|
||||
453
sound/core/seq/seq_prioq.c
Normal file
453
sound/core/seq/seq_prioq.c
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* 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 <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) */
|
||||
struct snd_seq_prioq *snd_seq_prioq_new(void)
|
||||
{
|
||||
struct snd_seq_prioq *f;
|
||||
|
||||
f = kzalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_prioq **fifo)
|
||||
{
|
||||
struct snd_seq_prioq *f = *fifo;
|
||||
*fifo = NULL;
|
||||
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_event *a,
|
||||
struct snd_seq_event *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(struct snd_seq_event *a,
|
||||
struct snd_seq_event *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(struct snd_seq_prioq * f,
|
||||
struct snd_seq_event_cell * cell)
|
||||
{
|
||||
struct snd_seq_event_cell *cur, *prev;
|
||||
unsigned long flags;
|
||||
int count;
|
||||
int prior;
|
||||
|
||||
if (snd_BUG_ON(!f || !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);
|
||||
pr_err("ALSA: seq: 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 */
|
||||
struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f)
|
||||
{
|
||||
struct snd_seq_event_cell *cell;
|
||||
unsigned long flags;
|
||||
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_prioq * f)
|
||||
{
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n");
|
||||
return 0;
|
||||
}
|
||||
return f->cells;
|
||||
}
|
||||
|
||||
|
||||
/* peek at cell at the head of the prioq */
|
||||
struct snd_seq_event_cell *snd_seq_prioq_cell_peek(struct snd_seq_prioq * f)
|
||||
{
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n");
|
||||
return NULL;
|
||||
}
|
||||
return f->head;
|
||||
}
|
||||
|
||||
|
||||
static inline int prioq_match(struct snd_seq_event_cell *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(struct snd_seq_prioq * f, int client, int timestamp)
|
||||
{
|
||||
register struct snd_seq_event_cell *cell, *next;
|
||||
unsigned long flags;
|
||||
struct snd_seq_event_cell *prev = NULL;
|
||||
struct snd_seq_event_cell *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
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_remove_events *info,
|
||||
struct snd_seq_event *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(struct snd_seq_prioq * f, int client,
|
||||
struct snd_seq_remove_events *info)
|
||||
{
|
||||
struct snd_seq_event_cell *cell, *next;
|
||||
unsigned long flags;
|
||||
struct snd_seq_event_cell *prev = NULL;
|
||||
struct snd_seq_event_cell *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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
62
sound/core/seq/seq_prioq.h
Normal file
62
sound/core/seq/seq_prioq.h
Normal file
|
|
@ -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 === */
|
||||
|
||||
struct snd_seq_prioq {
|
||||
struct snd_seq_event_cell *head; /* pointer to head of prioq */
|
||||
struct snd_seq_event_cell *tail; /* pointer to tail of prioq */
|
||||
int cells;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
|
||||
/* create new prioq (constructor) */
|
||||
struct snd_seq_prioq *snd_seq_prioq_new(void);
|
||||
|
||||
/* delete prioq (destructor) */
|
||||
void snd_seq_prioq_delete(struct snd_seq_prioq **fifo);
|
||||
|
||||
/* enqueue cell to prioq */
|
||||
int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell);
|
||||
|
||||
/* dequeue cell from prioq */
|
||||
struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f);
|
||||
|
||||
/* return number of events available in prioq */
|
||||
int snd_seq_prioq_avail(struct snd_seq_prioq *f);
|
||||
|
||||
/* peek at cell at the head of the prioq */
|
||||
struct snd_seq_event_cell *snd_seq_prioq_cell_peek(struct snd_seq_prioq *f);
|
||||
|
||||
/* client left queue */
|
||||
void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp);
|
||||
|
||||
/* Remove events */
|
||||
void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client,
|
||||
struct snd_seq_remove_events *info);
|
||||
|
||||
#endif
|
||||
795
sound/core/seq/seq_queue.c
Normal file
795
sound/core/seq/seq_queue.c
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
/*
|
||||
* 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 struct snd_seq_queue pointer is returned via
|
||||
* queueptr(). This pointer *MUST* be released afterward by
|
||||
* queuefree(ptr).
|
||||
* - Addition of experimental sync support.
|
||||
*/
|
||||
|
||||
#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 struct snd_seq_queue *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(struct snd_seq_queue *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 struct snd_seq_queue *queue_list_remove(int id, int client)
|
||||
{
|
||||
struct snd_seq_queue *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 struct snd_seq_queue *queue_new(int owner, int locked)
|
||||
{
|
||||
struct snd_seq_queue *q;
|
||||
|
||||
q = kzalloc(sizeof(*q), GFP_KERNEL);
|
||||
if (q == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for snd_seq_queue_new()\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_init(&q->owner_lock);
|
||||
spin_lock_init(&q->check_lock);
|
||||
mutex_init(&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(struct snd_seq_queue *q)
|
||||
{
|
||||
/* stop and release the timer */
|
||||
mutex_lock(&q->timer_mutex);
|
||||
snd_seq_timer_stop(q->timer);
|
||||
snd_seq_timer_close(q);
|
||||
mutex_unlock(&q->timer_mutex);
|
||||
/* 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)
|
||||
{
|
||||
struct snd_seq_queue *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)
|
||||
{
|
||||
struct snd_seq_queue *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 */
|
||||
struct snd_seq_queue *queueptr(int queueid)
|
||||
{
|
||||
struct snd_seq_queue *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 */
|
||||
struct snd_seq_queue *snd_seq_queue_find_name(char *name)
|
||||
{
|
||||
int i;
|
||||
struct snd_seq_queue *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(struct snd_seq_queue *q, int atomic, int hop)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_event_cell *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(struct snd_seq_event_cell *cell, int atomic, int hop)
|
||||
{
|
||||
int dest, err;
|
||||
struct snd_seq_queue *q;
|
||||
|
||||
if (snd_BUG_ON(!cell))
|
||||
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(struct snd_seq_queue *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(struct snd_seq_queue *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(struct snd_seq_queue *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)
|
||||
{
|
||||
struct snd_seq_queue *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)
|
||||
{
|
||||
struct snd_seq_queue *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;
|
||||
struct snd_seq_queue *queue;
|
||||
struct snd_seq_timer *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)
|
||||
{
|
||||
struct snd_seq_queue *queue;
|
||||
int result = 0;
|
||||
|
||||
queue = queueptr(queueid);
|
||||
if (queue == NULL)
|
||||
return -EINVAL;
|
||||
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,
|
||||
struct snd_seq_queue_tempo *info)
|
||||
{
|
||||
struct snd_seq_queue *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)
|
||||
{
|
||||
struct snd_seq_queue *queue;
|
||||
|
||||
queue = queueptr(queueid);
|
||||
if (queue == NULL)
|
||||
return -EINVAL;
|
||||
mutex_lock(&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);
|
||||
}
|
||||
mutex_unlock(&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)
|
||||
{
|
||||
struct snd_seq_queue *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;
|
||||
struct snd_seq_queue *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;
|
||||
struct snd_seq_queue *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;
|
||||
struct snd_seq_queue *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, struct snd_seq_remove_events *info)
|
||||
{
|
||||
int i;
|
||||
struct snd_seq_queue *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(struct snd_seq_queue *q, struct snd_seq_event *ev,
|
||||
int atomic, int hop)
|
||||
{
|
||||
struct snd_seq_event 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.
|
||||
*/
|
||||
static void snd_seq_queue_process_event(struct snd_seq_queue *q,
|
||||
struct snd_seq_event *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(struct snd_seq_event *ev, int atomic, int hop)
|
||||
{
|
||||
struct snd_seq_queue *q;
|
||||
|
||||
if (snd_BUG_ON(!ev))
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/* exported to seq_info.c */
|
||||
void snd_seq_info_queues_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
int i, bpm;
|
||||
struct snd_seq_queue *q;
|
||||
struct snd_seq_timer *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);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
139
sound/core/seq/seq_queue.h
Normal file
139
sound/core/seq/seq_queue.h
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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 */
|
||||
|
||||
struct snd_seq_prioq *tickq; /* midi tick event queue */
|
||||
struct snd_seq_prioq *timeq; /* real-time event queue */
|
||||
|
||||
struct snd_seq_timer *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 mutex 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(struct snd_seq_event_cell *cell, int atomic, int hop);
|
||||
|
||||
/* Remove events */
|
||||
void snd_seq_queue_client_leave_cells(int client);
|
||||
void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info);
|
||||
|
||||
/* return pointer to queue structure for specified id */
|
||||
struct snd_seq_queue *queueptr(int queueid);
|
||||
/* unlock */
|
||||
#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
|
||||
|
||||
/* return the (first) queue matching with the specified name */
|
||||
struct snd_seq_queue *snd_seq_queue_find_name(char *name);
|
||||
|
||||
/* check single queue and dispatch events */
|
||||
void snd_seq_check_queue(struct snd_seq_queue *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, struct snd_seq_queue_tempo *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(struct snd_seq_event *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
|
||||
175
sound/core/seq/seq_system.c
Normal file
175
sound/core/seq/seq_system.c
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.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(struct snd_seq_event * ev, int client, int port)
|
||||
{
|
||||
if (announce_port < 0)
|
||||
return -ENODEV;
|
||||
|
||||
memset(ev, 0, sizeof(struct snd_seq_event));
|
||||
|
||||
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)
|
||||
{
|
||||
struct snd_seq_event 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, struct snd_seq_event *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(struct snd_seq_event * 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)
|
||||
{
|
||||
struct snd_seq_port_callback pcallbacks;
|
||||
struct snd_seq_port_info *port;
|
||||
|
||||
port = kzalloc(sizeof(*port), GFP_KERNEL);
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(&pcallbacks, 0, sizeof(pcallbacks));
|
||||
pcallbacks.owner = THIS_MODULE;
|
||||
pcallbacks.event_input = event_input_timer;
|
||||
|
||||
/* register client */
|
||||
sysclient = snd_seq_create_kernel_client(NULL, 0, "System");
|
||||
|
||||
/* 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(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);
|
||||
}
|
||||
}
|
||||
46
sound/core/seq/seq_system.h
Normal file
46
sound/core/seq/seq_system.h
Normal file
|
|
@ -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, struct snd_seq_event *ev);
|
||||
|
||||
/* register our internal client */
|
||||
int snd_seq_system_client_init(void);
|
||||
|
||||
/* unregister our internal client */
|
||||
void snd_seq_system_client_done(void);
|
||||
|
||||
|
||||
#endif
|
||||
455
sound/core/seq/seq_timer.c
Normal file
455
sound/core/seq/seq_timer.c
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
* ALSA sequencer Timer
|
||||
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <linux/slab.h>
|
||||
#include "seq_timer.h"
|
||||
#include "seq_queue.h"
|
||||
#include "seq_info.h"
|
||||
|
||||
/* allowed sequencer timer frequencies, in Hz */
|
||||
#define MIN_FREQUENCY 10
|
||||
#define MAX_FREQUENCY 6250
|
||||
#define DEFAULT_FREQUENCY 1000
|
||||
|
||||
#define SKEW_BASE 0x10000 /* 16bit shift */
|
||||
|
||||
static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr)
|
||||
{
|
||||
if (tmr->tempo < 1000000)
|
||||
tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq;
|
||||
else {
|
||||
/* might overflow.. */
|
||||
unsigned int s;
|
||||
s = tmr->tempo % tmr->ppq;
|
||||
s = (s * 1000) / tmr->ppq;
|
||||
tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000;
|
||||
tmr->tick.resolution += s;
|
||||
}
|
||||
if (tmr->tick.resolution <= 0)
|
||||
tmr->tick.resolution = 1;
|
||||
snd_seq_timer_update_tick(&tmr->tick, 0);
|
||||
}
|
||||
|
||||
/* create new timer (constructor) */
|
||||
struct snd_seq_timer *snd_seq_timer_new(void)
|
||||
{
|
||||
struct snd_seq_timer *tmr;
|
||||
|
||||
tmr = kzalloc(sizeof(*tmr), GFP_KERNEL);
|
||||
if (tmr == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_timer **tmr)
|
||||
{
|
||||
struct snd_seq_timer *t = *tmr;
|
||||
*tmr = NULL;
|
||||
|
||||
if (t == NULL) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_timer * tmr)
|
||||
{
|
||||
/* setup defaults */
|
||||
tmr->ppq = 96; /* 96 PPQ */
|
||||
tmr->tempo = 500000; /* 120 BPM */
|
||||
snd_seq_timer_set_tick_resolution(tmr);
|
||||
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(struct snd_seq_timer * 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(struct snd_timer_instance *timeri,
|
||||
unsigned long resolution,
|
||||
unsigned long ticks)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_seq_queue *q = timeri->callback_data;
|
||||
struct snd_seq_timer *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(struct snd_seq_timer * tmr, int tempo)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!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);
|
||||
}
|
||||
spin_unlock_irqrestore(&tmr->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set current ppq */
|
||||
int snd_seq_timer_set_ppq(struct snd_seq_timer * tmr, int ppq)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!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);
|
||||
pr_debug("ALSA: seq: cannot change ppq of a running timer\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
tmr->ppq = ppq;
|
||||
snd_seq_timer_set_tick_resolution(tmr);
|
||||
spin_unlock_irqrestore(&tmr->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set current tick position */
|
||||
int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr,
|
||||
snd_seq_tick_time_t position)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!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(struct snd_seq_timer *tmr,
|
||||
snd_seq_real_time_t position)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!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(struct snd_seq_timer *tmr, unsigned int skew,
|
||||
unsigned int base)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (snd_BUG_ON(!tmr))
|
||||
return -EINVAL;
|
||||
|
||||
/* FIXME */
|
||||
if (base != SKEW_BASE) {
|
||||
pr_debug("ALSA: seq: 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(struct snd_seq_queue *q)
|
||||
{
|
||||
struct snd_timer_instance *t;
|
||||
struct snd_seq_timer *tmr;
|
||||
char str[32];
|
||||
int err;
|
||||
|
||||
tmr = q->timer;
|
||||
if (snd_BUG_ON(!tmr))
|
||||
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) {
|
||||
struct snd_timer_id 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) {
|
||||
pr_err("ALSA: 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(struct snd_seq_queue *q)
|
||||
{
|
||||
struct snd_seq_timer *tmr;
|
||||
|
||||
tmr = q->timer;
|
||||
if (snd_BUG_ON(!tmr))
|
||||
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(struct snd_seq_timer * 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(struct snd_seq_timer *tmr)
|
||||
{
|
||||
struct snd_timer *t;
|
||||
unsigned long freq;
|
||||
|
||||
t = tmr->timeri->timer;
|
||||
if (snd_BUG_ON(!t))
|
||||
return -EINVAL;
|
||||
|
||||
freq = tmr->preferred_resolution;
|
||||
if (!freq)
|
||||
freq = DEFAULT_FREQUENCY;
|
||||
else if (freq < MIN_FREQUENCY)
|
||||
freq = MIN_FREQUENCY;
|
||||
else if (freq > MAX_FREQUENCY)
|
||||
freq = MAX_FREQUENCY;
|
||||
|
||||
tmr->ticks = 1;
|
||||
if (!(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 * freq));
|
||||
if (! tmr->ticks)
|
||||
tmr->ticks = 1;
|
||||
}
|
||||
}
|
||||
tmr->initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_seq_timer_start(struct snd_seq_timer * 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(struct snd_seq_timer * 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(struct snd_seq_timer *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(struct snd_seq_timer *tmr)
|
||||
{
|
||||
return tmr->tick.cur_tick;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/* exported to seq_info.c */
|
||||
void snd_seq_info_timer_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
int idx;
|
||||
struct snd_seq_queue *q;
|
||||
struct snd_seq_timer *tmr;
|
||||
struct snd_timer_instance *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);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
148
sound/core/seq/seq_timer.h
Normal file
148
sound/core/seq/seq_timer.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
struct snd_seq_timer_tick {
|
||||
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 */
|
||||
};
|
||||
|
||||
struct snd_seq_timer {
|
||||
/* ... 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 */
|
||||
struct snd_seq_timer_tick tick; /* current tick */
|
||||
int tick_updated;
|
||||
|
||||
int type; /* timer type */
|
||||
struct snd_timer_id alsa_id; /* ALSA's timer ID */
|
||||
struct snd_timer_instance *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;
|
||||
};
|
||||
|
||||
|
||||
/* create new timer (constructor) */
|
||||
struct snd_seq_timer *snd_seq_timer_new(void);
|
||||
|
||||
/* delete timer (destructor) */
|
||||
void snd_seq_timer_delete(struct snd_seq_timer **tmr);
|
||||
|
||||
/* */
|
||||
static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *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 */
|
||||
struct snd_seq_queue;
|
||||
int snd_seq_timer_open(struct snd_seq_queue *q);
|
||||
int snd_seq_timer_close(struct snd_seq_queue *q);
|
||||
int snd_seq_timer_midi_open(struct snd_seq_queue *q);
|
||||
int snd_seq_timer_midi_close(struct snd_seq_queue *q);
|
||||
void snd_seq_timer_defaults(struct snd_seq_timer *tmr);
|
||||
void snd_seq_timer_reset(struct snd_seq_timer *tmr);
|
||||
int snd_seq_timer_stop(struct snd_seq_timer *tmr);
|
||||
int snd_seq_timer_start(struct snd_seq_timer *tmr);
|
||||
int snd_seq_timer_continue(struct snd_seq_timer *tmr);
|
||||
int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo);
|
||||
int snd_seq_timer_set_ppq(struct snd_seq_timer *tmr, int ppq);
|
||||
int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position);
|
||||
int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position);
|
||||
int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base);
|
||||
snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr);
|
||||
snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr);
|
||||
|
||||
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;
|
||||
|
||||
#endif
|
||||
543
sound/core/seq/seq_virmidi.c
Normal file
543
sound/core/seq/seq_virmidi.c
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/module.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(struct snd_virmidi *vmidi,
|
||||
struct snd_seq_event *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(struct snd_virmidi_dev *rdev,
|
||||
struct snd_seq_event *ev)
|
||||
{
|
||||
struct snd_virmidi *vmidi;
|
||||
unsigned char msg[4];
|
||||
int len;
|
||||
|
||||
read_lock(&rdev->filelist_lock);
|
||||
list_for_each_entry(vmidi, &rdev->filelist, 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.
|
||||
*/
|
||||
#if 0
|
||||
int snd_virmidi_receive(struct snd_rawmidi *rmidi, struct snd_seq_event *ev)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev;
|
||||
|
||||
rdev = rmidi->private_data;
|
||||
return snd_virmidi_dev_receive_event(rdev, ev);
|
||||
}
|
||||
#endif /* 0 */
|
||||
|
||||
/*
|
||||
* event handler of virmidi port
|
||||
*/
|
||||
static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct,
|
||||
void *private_data, int atomic, int hop)
|
||||
{
|
||||
struct snd_virmidi_dev *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(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
struct snd_virmidi *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(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
struct snd_virmidi *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, in_atomic(), 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, in_atomic(), 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(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
|
||||
struct snd_rawmidi_runtime *runtime = substream->runtime;
|
||||
struct snd_virmidi *vmidi;
|
||||
unsigned long flags;
|
||||
|
||||
vmidi = kzalloc(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(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
|
||||
struct snd_rawmidi_runtime *runtime = substream->runtime;
|
||||
struct snd_virmidi *vmidi;
|
||||
|
||||
vmidi = kzalloc(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(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_virmidi *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(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_virmidi *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,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_virmidi_dev *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,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_virmidi_dev *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,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_virmidi_dev *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,
|
||||
struct snd_seq_port_subscribe *info)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev;
|
||||
|
||||
rdev = private_data;
|
||||
rdev->flags &= ~SNDRV_VIRMIDI_USE;
|
||||
module_put(rdev->card->module);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Register functions
|
||||
*/
|
||||
|
||||
static struct snd_rawmidi_ops snd_virmidi_input_ops = {
|
||||
.open = snd_virmidi_input_open,
|
||||
.close = snd_virmidi_input_close,
|
||||
.trigger = snd_virmidi_input_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops 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(struct snd_virmidi_dev *rdev)
|
||||
{
|
||||
int client;
|
||||
struct snd_seq_port_callback pcallbacks;
|
||||
struct snd_seq_port_info *pinfo;
|
||||
int err;
|
||||
|
||||
if (rdev->client >= 0)
|
||||
return 0;
|
||||
|
||||
pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
|
||||
if (!pinfo) {
|
||||
err = -ENOMEM;
|
||||
goto __error;
|
||||
}
|
||||
|
||||
client = snd_seq_create_kernel_client(rdev->card, rdev->device,
|
||||
"%s %d-%d", rdev->rmidi->name,
|
||||
rdev->card->number,
|
||||
rdev->device);
|
||||
if (client < 0) {
|
||||
err = client;
|
||||
goto __error;
|
||||
}
|
||||
rdev->client = client;
|
||||
|
||||
/* create a port */
|
||||
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
|
||||
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
|
||||
| SNDRV_SEQ_PORT_TYPE_PORT;
|
||||
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(pinfo);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* release the sequencer client
|
||||
*/
|
||||
static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev)
|
||||
{
|
||||
if (rdev->client >= 0) {
|
||||
snd_seq_delete_kernel_client(rdev->client);
|
||||
rdev->client = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* register the device
|
||||
*/
|
||||
static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi)
|
||||
{
|
||||
struct snd_virmidi_dev *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:
|
||||
pr_err("ALSA: seq_virmidi: seq_mode is not set: %d\n", rdev->seq_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* unregister the device
|
||||
*/
|
||||
static int snd_virmidi_dev_unregister(struct snd_rawmidi *rmidi)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev = rmidi->private_data;
|
||||
|
||||
if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
|
||||
snd_virmidi_dev_detach_seq(rdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static struct snd_rawmidi_global_ops snd_virmidi_global_ops = {
|
||||
.dev_register = snd_virmidi_dev_register,
|
||||
.dev_unregister = snd_virmidi_dev_unregister,
|
||||
};
|
||||
|
||||
/*
|
||||
* free device
|
||||
*/
|
||||
static void snd_virmidi_free(struct snd_rawmidi *rmidi)
|
||||
{
|
||||
struct snd_virmidi_dev *rdev = rmidi->private_data;
|
||||
kfree(rdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* create a new device
|
||||
*
|
||||
*/
|
||||
/* exported */
|
||||
int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi)
|
||||
{
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_virmidi_dev *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 = kzalloc(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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue