Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

231
sound/core/Kconfig Normal file
View file

@ -0,0 +1,231 @@
# ALSA soundcard-configuration
config SND_TIMER
tristate
config SND_PCM
tristate
select SND_TIMER
config SND_DMAENGINE_PCM
tristate
config SND_HWDEP
tristate
config SND_RAWMIDI
tristate
config SND_COMPRESS_OFFLOAD
tristate
# To be effective this also requires INPUT - users should say:
# select SND_JACK if INPUT=y || INPUT=SND
# to avoid having to force INPUT on.
config SND_JACK
bool
config SND_SEQUENCER
tristate "Sequencer support"
select SND_TIMER
help
Say Y or M to enable MIDI sequencer and router support. This
feature allows routing and enqueueing of MIDI events. Events
can be processed at a given time.
Many programs require this feature, so you should enable it
unless you know what you're doing.
config SND_SEQ_DUMMY
tristate "Sequencer dummy client"
depends on SND_SEQUENCER
help
Say Y here to enable the dummy sequencer client. This client
is a simple MIDI-through client: all normal input events are
redirected to the output port immediately.
You don't need this unless you want to connect many MIDI
devices or applications together.
To compile this driver as a module, choose M here: the module
will be called snd-seq-dummy.
config SND_OSSEMUL
select SOUND_OSS_CORE
bool
config SND_MIXER_OSS
tristate "OSS Mixer API"
select SND_OSSEMUL
help
To enable OSS mixer API emulation (/dev/mixer*), say Y here
and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-mixer-oss.
config SND_PCM_OSS
tristate "OSS PCM (digital audio) API"
select SND_OSSEMUL
select SND_PCM
help
To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y
here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>.
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-pcm-oss.
config SND_PCM_OSS_PLUGINS
bool "OSS PCM (digital audio) API - Include plugin system"
depends on SND_PCM_OSS
default y
help
If you disable this option, the ALSA's OSS PCM API will not
support conversion of channels, formats and rates. It will
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
config SND_SEQUENCER_OSS
bool "OSS Sequencer API"
depends on SND_SEQUENCER
select SND_OSSEMUL
help
Say Y here to enable OSS sequencer emulation (both
/dev/sequencer and /dev/music interfaces).
Many programs still use the OSS API, so say Y.
If you choose M in "Sequencer support" (SND_SEQUENCER),
this will be compiled as a module. The module will be called
snd-seq-oss.
config SND_HRTIMER
tristate "HR-timer backend support"
depends on HIGH_RES_TIMERS
select SND_TIMER
help
Say Y here to enable HR-timer backend for ALSA timer. ALSA uses
the hrtimer as a precise timing source. The ALSA sequencer code
also can use this timing source.
To compile this driver as a module, choose M here: the module
will be called snd-hrtimer.
config SND_SEQ_HRTIMER_DEFAULT
bool "Use HR-timer as default sequencer timer"
depends on SND_HRTIMER && SND_SEQUENCER
default y
help
Say Y here to use the HR-timer backend as the default sequencer
timer.
config SND_RTCTIMER
tristate "RTC Timer support"
depends on RTC
select SND_TIMER
help
Say Y here to enable RTC timer support for ALSA. ALSA uses
the RTC timer as a precise timing source and maps the RTC
timer to ALSA's timer interface. The ALSA sequencer code also
can use this timing source.
To compile this driver as a module, choose M here: the module
will be called snd-rtctimer.
Note that this option is exclusive with the new RTC drivers
(CONFIG_RTC_CLASS) since this requires the old API.
config SND_SEQ_RTCTIMER_DEFAULT
bool "Use RTC as default sequencer timer"
depends on SND_RTCTIMER && SND_SEQUENCER
depends on !SND_SEQ_HRTIMER_DEFAULT
default y
help
Say Y here to use the RTC timer as the default sequencer
timer. This is strongly recommended because it ensures
precise MIDI timing even when the system timer runs at less
than 1000 Hz.
If in doubt, say Y.
config SND_DYNAMIC_MINORS
bool "Dynamic device file minor numbers"
help
If you say Y here, the minor numbers of ALSA device files in
/dev/snd/ are allocated dynamically. This allows you to have
more than 8 sound cards, but requires a dynamic device file
system like udev.
If you are unsure about this, say N here.
config SND_MAX_CARDS
int "Max number of sound cards"
range 4 256
default 32
depends on SND_DYNAMIC_MINORS
help
Specify the max number of sound cards that can be assigned
on a single machine.
config SND_SUPPORT_OLD_API
bool "Support old ALSA API"
default y
help
Say Y here to support the obsolete ALSA PCM API (ver.0.9.0 rc3
or older).
config SND_VERBOSE_PROCFS
bool "Verbose procfs contents"
depends on PROC_FS
default y
help
Say Y here to include code for verbose procfs contents (provides
useful information to developers when a problem occurs). On the
other side, it makes the ALSA subsystem larger.
config SND_VERBOSE_PRINTK
bool "Verbose printk"
help
Say Y here to enable verbose log messages. These messages
will help to identify source file and position containing
printed messages.
You don't need this unless you're debugging ALSA.
config SND_DEBUG
bool "Debug"
help
Say Y here to enable ALSA debug code.
config SND_DEBUG_VERBOSE
bool "More verbose debug"
depends on SND_DEBUG
help
Say Y here to enable extra-verbose debugging messages.
Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
So, say Y only if you are ready to be annoyed.
config SND_PCM_XRUN_DEBUG
bool "Enable PCM ring buffer overrun/underrun debugging"
default n
depends on SND_DEBUG && SND_VERBOSE_PROCFS
help
Say Y to enable the PCM ring buffer overrun/underrun debugging.
It is usually not required, but if you have trouble with
sound clicking when system is loaded, it may help to determine
the process or driver which causes the scheduling gaps.
config SND_VMASTER
bool
config SND_KCTL_JACK
bool
config SND_DMA_SGBUF
def_bool y
depends on X86
source "sound/core/seq/Kconfig"

39
sound/core/Makefile Normal file
View file

@ -0,0 +1,39 @@
#
# Makefile for ALSA
# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-y := sound.o init.o memory.o info.o control.o misc.o device.o
snd-$(CONFIG_ISA_DMA_API) += isadma.o
snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o
snd-$(CONFIG_SND_VMASTER) += vmaster.o
snd-$(CONFIG_SND_KCTL_JACK) += ctljack.o
snd-$(CONFIG_SND_JACK) += jack.o
snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
pcm_memory.o memalloc.o
snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
snd-pcm-dmaengine-objs := pcm_dmaengine.o
snd-rawmidi-objs := rawmidi.o
snd-timer-objs := timer.o
snd-hrtimer-objs := hrtimer.o
snd-rtctimer-objs := rtctimer.o
snd-hwdep-objs := hwdep.o
snd-compress-objs := compress_offload.o
obj-$(CONFIG_SND) += snd.o
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
obj-$(CONFIG_SND_TIMER) += snd-timer.o
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o
obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o
obj-$(CONFIG_SND_PCM) += snd-pcm.o
obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
obj-$(CONFIG_SND_OSSEMUL) += oss/
obj-$(CONFIG_SND_SEQUENCER) += seq/
obj-$(CONFIG_SND_COMPRESS_OFFLOAD) += snd-compress.o

File diff suppressed because it is too large Load diff

1759
sound/core/control.c Normal file

File diff suppressed because it is too large Load diff

448
sound/core/control_compat.c Normal file
View file

@ -0,0 +1,448 @@
/*
* compat ioctls for control API
*
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* this file included from control.c */
#include <linux/compat.h>
#include <linux/slab.h>
struct snd_ctl_elem_list32 {
u32 offset;
u32 space;
u32 used;
u32 count;
u32 pids;
unsigned char reserved[50];
} /* don't set packed attribute here */;
static int snd_ctl_elem_list_compat(struct snd_card *card,
struct snd_ctl_elem_list32 __user *data32)
{
struct snd_ctl_elem_list __user *data;
compat_caddr_t ptr;
int err;
data = compat_alloc_user_space(sizeof(*data));
/* offset, space, used, count */
if (copy_in_user(data, data32, 4 * sizeof(u32)))
return -EFAULT;
/* pids */
if (get_user(ptr, &data32->pids) ||
put_user(compat_ptr(ptr), &data->pids))
return -EFAULT;
err = snd_ctl_elem_list(card, data);
if (err < 0)
return err;
/* copy the result */
if (copy_in_user(data32, data, 4 * sizeof(u32)))
return -EFAULT;
return 0;
}
/*
* control element info
* it uses union, so the things are not easy..
*/
struct snd_ctl_elem_info32 {
struct snd_ctl_elem_id id; // the size of struct is same
s32 type;
u32 access;
u32 count;
s32 owner;
union {
struct {
s32 min;
s32 max;
s32 step;
} integer;
struct {
u64 min;
u64 max;
u64 step;
} integer64;
struct {
u32 items;
u32 item;
char name[64];
u64 names_ptr;
u32 names_length;
} enumerated;
unsigned char reserved[128];
} value;
unsigned char reserved[64];
} __attribute__((packed));
static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl,
struct snd_ctl_elem_info32 __user *data32)
{
struct snd_ctl_elem_info *data;
int err;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (! data)
return -ENOMEM;
err = -EFAULT;
/* copy id */
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
goto error;
/* we need to copy the item index.
* hope this doesn't break anything..
*/
if (get_user(data->value.enumerated.item, &data32->value.enumerated.item))
goto error;
snd_power_lock(ctl->card);
err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
if (err >= 0)
err = snd_ctl_elem_info(ctl, data);
snd_power_unlock(ctl->card);
if (err < 0)
goto error;
/* restore info to 32bit */
err = -EFAULT;
/* id, type, access, count */
if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) ||
copy_to_user(&data32->type, &data->type, 3 * sizeof(u32)))
goto error;
if (put_user(data->owner, &data32->owner))
goto error;
switch (data->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
if (put_user(data->value.integer.min, &data32->value.integer.min) ||
put_user(data->value.integer.max, &data32->value.integer.max) ||
put_user(data->value.integer.step, &data32->value.integer.step))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
if (copy_to_user(&data32->value.integer64,
&data->value.integer64,
sizeof(data->value.integer64)))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
if (copy_to_user(&data32->value.enumerated,
&data->value.enumerated,
sizeof(data->value.enumerated)))
goto error;
break;
default:
break;
}
err = 0;
error:
kfree(data);
return err;
}
/* read / write */
struct snd_ctl_elem_value32 {
struct snd_ctl_elem_id id;
unsigned int indirect; /* bit-field causes misalignment */
union {
s32 integer[128];
unsigned char data[512];
#ifndef CONFIG_X86_64
s64 integer64[64];
#endif
} value;
unsigned char reserved[128];
};
/* get the value type and count of the control */
static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id,
int *countp)
{
struct snd_kcontrol *kctl;
struct snd_ctl_elem_info *info;
int err;
down_read(&card->controls_rwsem);
kctl = snd_ctl_find_id(card, id);
if (! kctl) {
up_read(&card->controls_rwsem);
return -ENXIO;
}
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (info == NULL) {
up_read(&card->controls_rwsem);
return -ENOMEM;
}
info->id = *id;
err = kctl->info(kctl, info);
up_read(&card->controls_rwsem);
if (err >= 0) {
err = info->type;
*countp = info->count;
}
kfree(info);
return err;
}
static int get_elem_size(int type, int count)
{
switch (type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
return sizeof(s64) * count;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
return sizeof(int) * count;
case SNDRV_CTL_ELEM_TYPE_BYTES:
return 512;
case SNDRV_CTL_ELEM_TYPE_IEC958:
return sizeof(struct snd_aes_iec958);
default:
return -1;
}
}
static int copy_ctl_value_from_user(struct snd_card *card,
struct snd_ctl_elem_value *data,
struct snd_ctl_elem_value32 __user *data32,
int *typep, int *countp)
{
int i, type, size;
int uninitialized_var(count);
unsigned int indirect;
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)))
return -EFAULT;
if (get_user(indirect, &data32->indirect))
return -EFAULT;
if (indirect)
return -EINVAL;
type = get_ctl_type(card, &data->id, &count);
if (type < 0)
return type;
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
for (i = 0; i < count; i++) {
int val;
if (get_user(val, &data32->value.integer[i]))
return -EFAULT;
data->value.integer.value[i] = val;
}
} else {
size = get_elem_size(type, count);
if (size < 0) {
dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
return -EINVAL;
}
if (copy_from_user(data->value.bytes.data,
data32->value.data, size))
return -EFAULT;
}
*typep = type;
*countp = count;
return 0;
}
/* restore the value to 32bit */
static int copy_ctl_value_to_user(struct snd_ctl_elem_value32 __user *data32,
struct snd_ctl_elem_value *data,
int type, int count)
{
int i, size;
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
for (i = 0; i < count; i++) {
int val;
val = data->value.integer.value[i];
if (put_user(val, &data32->value.integer[i]))
return -EFAULT;
}
} else {
size = get_elem_size(type, count);
if (copy_to_user(data32->value.data,
data->value.bytes.data, size))
return -EFAULT;
}
return 0;
}
static int snd_ctl_elem_read_user_compat(struct snd_card *card,
struct snd_ctl_elem_value32 __user *data32)
{
struct snd_ctl_elem_value *data;
int err, type, count;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
goto error;
snd_power_lock(card);
err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
if (err >= 0)
err = snd_ctl_elem_read(card, data);
snd_power_unlock(card);
if (err >= 0)
err = copy_ctl_value_to_user(data32, data, type, count);
error:
kfree(data);
return err;
}
static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file,
struct snd_ctl_elem_value32 __user *data32)
{
struct snd_ctl_elem_value *data;
struct snd_card *card = file->card;
int err, type, count;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
goto error;
snd_power_lock(card);
err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
if (err >= 0)
err = snd_ctl_elem_write(card, file, data);
snd_power_unlock(card);
if (err >= 0)
err = copy_ctl_value_to_user(data32, data, type, count);
error:
kfree(data);
return err;
}
/* add or replace a user control */
static int snd_ctl_elem_add_compat(struct snd_ctl_file *file,
struct snd_ctl_elem_info32 __user *data32,
int replace)
{
struct snd_ctl_elem_info *data;
int err;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (! data)
return -ENOMEM;
err = -EFAULT;
/* id, type, access, count */ \
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) ||
copy_from_user(&data->type, &data32->type, 3 * sizeof(u32)))
goto error;
if (get_user(data->owner, &data32->owner) ||
get_user(data->type, &data32->type))
goto error;
switch (data->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
if (get_user(data->value.integer.min, &data32->value.integer.min) ||
get_user(data->value.integer.max, &data32->value.integer.max) ||
get_user(data->value.integer.step, &data32->value.integer.step))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
if (copy_from_user(&data->value.integer64,
&data32->value.integer64,
sizeof(data->value.integer64)))
goto error;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
if (copy_from_user(&data->value.enumerated,
&data32->value.enumerated,
sizeof(data->value.enumerated)))
goto error;
data->value.enumerated.names_ptr =
(uintptr_t)compat_ptr(data->value.enumerated.names_ptr);
break;
default:
break;
}
err = snd_ctl_elem_add(file, data, replace);
error:
kfree(data);
return err;
}
enum {
SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32),
SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32),
};
static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_ctl_file *ctl;
struct snd_kctl_ioctl *p;
void __user *argp = compat_ptr(arg);
int err;
ctl = file->private_data;
if (snd_BUG_ON(!ctl || !ctl->card))
return -ENXIO;
switch (cmd) {
case SNDRV_CTL_IOCTL_PVERSION:
case SNDRV_CTL_IOCTL_CARD_INFO:
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
case SNDRV_CTL_IOCTL_POWER:
case SNDRV_CTL_IOCTL_POWER_STATE:
case SNDRV_CTL_IOCTL_ELEM_LOCK:
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
case SNDRV_CTL_IOCTL_TLV_READ:
case SNDRV_CTL_IOCTL_TLV_WRITE:
case SNDRV_CTL_IOCTL_TLV_COMMAND:
return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_CTL_IOCTL_ELEM_LIST32:
return snd_ctl_elem_list_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO32:
return snd_ctl_elem_info_compat(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ32:
return snd_ctl_elem_read_user_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE32:
return snd_ctl_elem_write_user_compat(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_ADD32:
return snd_ctl_elem_add_compat(ctl, argp, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE32:
return snd_ctl_elem_add_compat(ctl, argp, 1);
}
down_read(&snd_ioctl_rwsem);
list_for_each_entry(p, &snd_control_compat_ioctls, list) {
if (p->fioctl) {
err = p->fioctl(ctl->card, ctl, cmd, arg);
if (err != -ENOIOCTLCMD) {
up_read(&snd_ioctl_rwsem);
return err;
}
}
}
up_read(&snd_ioctl_rwsem);
return -ENOIOCTLCMD;
}

56
sound/core/ctljack.c Normal file
View file

@ -0,0 +1,56 @@
/*
* Helper functions for jack-detection kcontrols
*
* Copyright (c) 2011 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.
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/control.h>
#define jack_detect_kctl_info snd_ctl_boolean_mono_info
static int jack_detect_kctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = kcontrol->private_value;
return 0;
}
static struct snd_kcontrol_new jack_detect_kctl = {
/* name is filled later */
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.info = jack_detect_kctl_info,
.get = jack_detect_kctl_get,
};
struct snd_kcontrol *
snd_kctl_jack_new(const char *name, int idx, void *private_data)
{
struct snd_kcontrol *kctl;
kctl = snd_ctl_new1(&jack_detect_kctl, private_data);
if (!kctl)
return NULL;
snprintf(kctl->id.name, sizeof(kctl->id.name), "%s Jack", name);
kctl->id.index = idx;
kctl->private_value = 0;
return kctl;
}
EXPORT_SYMBOL_GPL(snd_kctl_jack_new);
void snd_kctl_jack_report(struct snd_card *card,
struct snd_kcontrol *kctl, bool status)
{
if (kctl->private_value == status)
return;
kctl->private_value = status;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
}
EXPORT_SYMBOL_GPL(snd_kctl_jack_report);

224
sound/core/device.c Normal file
View file

@ -0,0 +1,224 @@
/*
* Device management routines
* Copyright (c) by 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/time.h>
#include <linux/export.h>
#include <linux/errno.h>
#include <sound/core.h>
/**
* snd_device_new - create an ALSA device component
* @card: the card instance
* @type: the device type, SNDRV_DEV_XXX
* @device_data: the data pointer of this device
* @ops: the operator table
*
* Creates a new device component for the given data pointer.
* The device will be assigned to the card and managed together
* by the card.
*
* The data pointer plays a role as the identifier, too, so the
* pointer address must be unique and unchanged.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_device_new(struct snd_card *card, enum snd_device_type type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
struct list_head *p;
if (snd_BUG_ON(!card || !device_data || !ops))
return -ENXIO;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
dev_err(card->dev, "Cannot allocate device, type=%d\n", type);
return -ENOMEM;
}
INIT_LIST_HEAD(&dev->list);
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
/* insert the entry in an incrementally sorted list */
list_for_each_prev(p, &card->devices) {
struct snd_device *pdev = list_entry(p, struct snd_device, list);
if ((unsigned int)pdev->type <= (unsigned int)type)
break;
}
list_add(&dev->list, p);
return 0;
}
EXPORT_SYMBOL(snd_device_new);
static int __snd_device_disconnect(struct snd_device *dev)
{
if (dev->state == SNDRV_DEV_REGISTERED) {
if (dev->ops->dev_disconnect &&
dev->ops->dev_disconnect(dev))
dev_err(dev->card->dev, "device disconnect failure\n");
dev->state = SNDRV_DEV_DISCONNECTED;
}
return 0;
}
static void __snd_device_free(struct snd_device *dev)
{
/* unlink */
list_del(&dev->list);
__snd_device_disconnect(dev);
if (dev->ops->dev_free) {
if (dev->ops->dev_free(dev))
dev_err(dev->card->dev, "device free failure\n");
}
kfree(dev);
}
static struct snd_device *look_for_dev(struct snd_card *card, void *device_data)
{
struct snd_device *dev;
list_for_each_entry(dev, &card->devices, list)
if (dev->device_data == device_data)
return dev;
return NULL;
}
/**
* snd_device_free - release the device from the card
* @card: the card instance
* @device_data: the data pointer to release
*
* Removes the device from the list on the card and invokes the
* callbacks, dev_disconnect and dev_free, corresponding to the state.
* Then release the device.
*/
void snd_device_free(struct snd_card *card, void *device_data)
{
struct snd_device *dev;
if (snd_BUG_ON(!card || !device_data))
return;
dev = look_for_dev(card, device_data);
if (dev)
__snd_device_free(dev);
else
dev_dbg(card->dev, "device free %p (from %pF), not found\n",
device_data, __builtin_return_address(0));
}
EXPORT_SYMBOL(snd_device_free);
static int __snd_device_register(struct snd_device *dev)
{
if (dev->state == SNDRV_DEV_BUILD) {
if (dev->ops->dev_register) {
int err = dev->ops->dev_register(dev);
if (err < 0)
return err;
}
dev->state = SNDRV_DEV_REGISTERED;
}
return 0;
}
/**
* snd_device_register - register the device
* @card: the card instance
* @device_data: the data pointer to register
*
* Registers the device which was already created via
* snd_device_new(). Usually this is called from snd_card_register(),
* but it can be called later if any new devices are created after
* invocation of snd_card_register().
*
* Return: Zero if successful, or a negative error code on failure or if the
* device not found.
*/
int snd_device_register(struct snd_card *card, void *device_data)
{
struct snd_device *dev;
if (snd_BUG_ON(!card || !device_data))
return -ENXIO;
dev = look_for_dev(card, device_data);
if (dev)
return __snd_device_register(dev);
snd_BUG();
return -ENXIO;
}
EXPORT_SYMBOL(snd_device_register);
/*
* register all the devices on the card.
* called from init.c
*/
int snd_device_register_all(struct snd_card *card)
{
struct snd_device *dev;
int err;
if (snd_BUG_ON(!card))
return -ENXIO;
list_for_each_entry(dev, &card->devices, list) {
err = __snd_device_register(dev);
if (err < 0)
return err;
}
return 0;
}
/*
* disconnect all the devices on the card.
* called from init.c
*/
int snd_device_disconnect_all(struct snd_card *card)
{
struct snd_device *dev;
int err = 0;
if (snd_BUG_ON(!card))
return -ENXIO;
list_for_each_entry_reverse(dev, &card->devices, list) {
if (__snd_device_disconnect(dev) < 0)
err = -ENXIO;
}
return err;
}
/*
* release all the devices on the card.
* called from init.c
*/
void snd_device_free_all(struct snd_card *card)
{
struct snd_device *dev, *next;
if (snd_BUG_ON(!card))
return;
list_for_each_entry_safe_reverse(dev, next, &card->devices, list)
__snd_device_free(dev);
}

167
sound/core/hrtimer.c Normal file
View file

@ -0,0 +1,167 @@
/*
* ALSA timer back-end using hrtimer
* Copyright (C) 2008 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
*
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/hrtimer.h>
#include <sound/core.h>
#include <sound/timer.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("ALSA hrtimer backend");
MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));
#define NANO_SEC 1000000000UL /* 10^9 in sec */
static unsigned int resolution;
struct snd_hrtimer {
struct snd_timer *timer;
struct hrtimer hrt;
atomic_t running;
};
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
{
struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
struct snd_timer *t = stime->timer;
unsigned long oruns;
if (!atomic_read(&stime->running))
return HRTIMER_NORESTART;
oruns = hrtimer_forward_now(hrt, ns_to_ktime(t->sticks * resolution));
snd_timer_interrupt(stime->timer, t->sticks * oruns);
if (!atomic_read(&stime->running))
return HRTIMER_NORESTART;
return HRTIMER_RESTART;
}
static int snd_hrtimer_open(struct snd_timer *t)
{
struct snd_hrtimer *stime;
stime = kmalloc(sizeof(*stime), GFP_KERNEL);
if (!stime)
return -ENOMEM;
hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
stime->timer = t;
stime->hrt.function = snd_hrtimer_callback;
atomic_set(&stime->running, 0);
t->private_data = stime;
return 0;
}
static int snd_hrtimer_close(struct snd_timer *t)
{
struct snd_hrtimer *stime = t->private_data;
if (stime) {
hrtimer_cancel(&stime->hrt);
kfree(stime);
t->private_data = NULL;
}
return 0;
}
static int snd_hrtimer_start(struct snd_timer *t)
{
struct snd_hrtimer *stime = t->private_data;
atomic_set(&stime->running, 0);
hrtimer_try_to_cancel(&stime->hrt);
hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
HRTIMER_MODE_REL);
atomic_set(&stime->running, 1);
return 0;
}
static int snd_hrtimer_stop(struct snd_timer *t)
{
struct snd_hrtimer *stime = t->private_data;
atomic_set(&stime->running, 0);
hrtimer_try_to_cancel(&stime->hrt);
return 0;
}
static struct snd_timer_hardware hrtimer_hw = {
.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_TASKLET,
.open = snd_hrtimer_open,
.close = snd_hrtimer_close,
.start = snd_hrtimer_start,
.stop = snd_hrtimer_stop,
};
/*
* entry functions
*/
static struct snd_timer *mytimer;
static int __init snd_hrtimer_init(void)
{
struct snd_timer *timer;
struct timespec tp;
int err;
hrtimer_get_res(CLOCK_MONOTONIC, &tp);
if (tp.tv_sec > 0 || !tp.tv_nsec) {
pr_err("snd-hrtimer: Invalid resolution %u.%09u",
(unsigned)tp.tv_sec, (unsigned)tp.tv_nsec);
return -EINVAL;
}
resolution = tp.tv_nsec;
/* Create a new timer and set up the fields */
err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,
&timer);
if (err < 0)
return err;
timer->module = THIS_MODULE;
strcpy(timer->name, "HR timer");
timer->hw = hrtimer_hw;
timer->hw.resolution = resolution;
timer->hw.ticks = NANO_SEC / resolution;
err = snd_timer_global_register(timer);
if (err < 0) {
snd_timer_global_free(timer);
return err;
}
mytimer = timer; /* remember this */
return 0;
}
static void __exit snd_hrtimer_exit(void)
{
if (mytimer) {
snd_timer_global_free(mytimer);
mytimer = NULL;
}
}
module_init(snd_hrtimer_init);
module_exit(snd_hrtimer_exit);

570
sound/core/hwdep.c Normal file
View file

@ -0,0 +1,570 @@
/*
* Hardware dependent layer
* Copyright (c) by 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/major.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/hwdep.h>
#include <sound/info.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Hardware dependent layer");
MODULE_LICENSE("GPL");
static LIST_HEAD(snd_hwdep_devices);
static DEFINE_MUTEX(register_mutex);
static int snd_hwdep_free(struct snd_hwdep *hwdep);
static int snd_hwdep_dev_free(struct snd_device *device);
static int snd_hwdep_dev_register(struct snd_device *device);
static int snd_hwdep_dev_disconnect(struct snd_device *device);
static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device)
{
struct snd_hwdep *hwdep;
list_for_each_entry(hwdep, &snd_hwdep_devices, list)
if (hwdep->card == card && hwdep->device == device)
return hwdep;
return NULL;
}
static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
{
struct snd_hwdep *hw = file->private_data;
if (hw->ops.llseek)
return hw->ops.llseek(hw, file, offset, orig);
return -ENXIO;
}
static ssize_t snd_hwdep_read(struct file * file, char __user *buf,
size_t count, loff_t *offset)
{
struct snd_hwdep *hw = file->private_data;
if (hw->ops.read)
return hw->ops.read(hw, buf, count, offset);
return -ENXIO;
}
static ssize_t snd_hwdep_write(struct file * file, const char __user *buf,
size_t count, loff_t *offset)
{
struct snd_hwdep *hw = file->private_data;
if (hw->ops.write)
return hw->ops.write(hw, buf, count, offset);
return -ENXIO;
}
static int snd_hwdep_open(struct inode *inode, struct file * file)
{
int major = imajor(inode);
struct snd_hwdep *hw;
int err;
wait_queue_t wait;
if (major == snd_major) {
hw = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_HWDEP);
#ifdef CONFIG_SND_OSSEMUL
} else if (major == SOUND_MAJOR) {
hw = snd_lookup_oss_minor_data(iminor(inode),
SNDRV_OSS_DEVICE_TYPE_DMFM);
#endif
} else
return -ENXIO;
if (hw == NULL)
return -ENODEV;
if (!try_module_get(hw->card->module)) {
snd_card_unref(hw->card);
return -EFAULT;
}
init_waitqueue_entry(&wait, current);
add_wait_queue(&hw->open_wait, &wait);
mutex_lock(&hw->open_mutex);
while (1) {
if (hw->exclusive && hw->used > 0) {
err = -EBUSY;
break;
}
if (!hw->ops.open) {
err = 0;
break;
}
err = hw->ops.open(hw, file);
if (err >= 0)
break;
if (err == -EAGAIN) {
if (file->f_flags & O_NONBLOCK) {
err = -EBUSY;
break;
}
} else
break;
set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&hw->open_mutex);
schedule();
mutex_lock(&hw->open_mutex);
if (hw->card->shutdown) {
err = -ENODEV;
break;
}
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
}
remove_wait_queue(&hw->open_wait, &wait);
if (err >= 0) {
err = snd_card_file_add(hw->card, file);
if (err >= 0) {
file->private_data = hw;
hw->used++;
} else {
if (hw->ops.release)
hw->ops.release(hw, file);
}
}
mutex_unlock(&hw->open_mutex);
if (err < 0)
module_put(hw->card->module);
snd_card_unref(hw->card);
return err;
}
static int snd_hwdep_release(struct inode *inode, struct file * file)
{
int err = 0;
struct snd_hwdep *hw = file->private_data;
struct module *mod = hw->card->module;
mutex_lock(&hw->open_mutex);
if (hw->ops.release)
err = hw->ops.release(hw, file);
if (hw->used > 0)
hw->used--;
mutex_unlock(&hw->open_mutex);
wake_up(&hw->open_wait);
snd_card_file_remove(hw->card, file);
module_put(mod);
return err;
}
static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
{
struct snd_hwdep *hw = file->private_data;
if (hw->ops.poll)
return hw->ops.poll(hw, file, wait);
return 0;
}
static int snd_hwdep_info(struct snd_hwdep *hw,
struct snd_hwdep_info __user *_info)
{
struct snd_hwdep_info info;
memset(&info, 0, sizeof(info));
info.card = hw->card->number;
strlcpy(info.id, hw->id, sizeof(info.id));
strlcpy(info.name, hw->name, sizeof(info.name));
info.iface = hw->iface;
if (copy_to_user(_info, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_hwdep_dsp_status(struct snd_hwdep *hw,
struct snd_hwdep_dsp_status __user *_info)
{
struct snd_hwdep_dsp_status info;
int err;
if (! hw->ops.dsp_status)
return -ENXIO;
memset(&info, 0, sizeof(info));
info.dsp_loaded = hw->dsp_loaded;
if ((err = hw->ops.dsp_status(hw, &info)) < 0)
return err;
if (copy_to_user(_info, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int snd_hwdep_dsp_load(struct snd_hwdep *hw,
struct snd_hwdep_dsp_image __user *_info)
{
struct snd_hwdep_dsp_image info;
int err;
if (! hw->ops.dsp_load)
return -ENXIO;
memset(&info, 0, sizeof(info));
if (copy_from_user(&info, _info, sizeof(info)))
return -EFAULT;
/* check whether the dsp was already loaded */
if (hw->dsp_loaded & (1 << info.index))
return -EBUSY;
if (!access_ok(VERIFY_READ, info.image, info.length))
return -EFAULT;
err = hw->ops.dsp_load(hw, &info);
if (err < 0)
return err;
hw->dsp_loaded |= (1 << info.index);
return 0;
}
static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,
unsigned long arg)
{
struct snd_hwdep *hw = file->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case SNDRV_HWDEP_IOCTL_PVERSION:
return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
case SNDRV_HWDEP_IOCTL_INFO:
return snd_hwdep_info(hw, argp);
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
return snd_hwdep_dsp_status(hw, argp);
case SNDRV_HWDEP_IOCTL_DSP_LOAD:
return snd_hwdep_dsp_load(hw, argp);
}
if (hw->ops.ioctl)
return hw->ops.ioctl(hw, file, cmd, arg);
return -ENOTTY;
}
static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
{
struct snd_hwdep *hw = file->private_data;
if (hw->ops.mmap)
return hw->ops.mmap(hw, file, vma);
return -ENXIO;
}
static int snd_hwdep_control_ioctl(struct snd_card *card,
struct snd_ctl_file * control,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
{
int device;
if (get_user(device, (int __user *)arg))
return -EFAULT;
mutex_lock(&register_mutex);
if (device < 0)
device = 0;
else if (device < SNDRV_MINOR_HWDEPS)
device++;
else
device = SNDRV_MINOR_HWDEPS;
while (device < SNDRV_MINOR_HWDEPS) {
if (snd_hwdep_search(card, device))
break;
device++;
}
if (device >= SNDRV_MINOR_HWDEPS)
device = -1;
mutex_unlock(&register_mutex);
if (put_user(device, (int __user *)arg))
return -EFAULT;
return 0;
}
case SNDRV_CTL_IOCTL_HWDEP_INFO:
{
struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;
int device, err;
struct snd_hwdep *hwdep;
if (get_user(device, &info->device))
return -EFAULT;
mutex_lock(&register_mutex);
hwdep = snd_hwdep_search(card, device);
if (hwdep)
err = snd_hwdep_info(hwdep, info);
else
err = -ENXIO;
mutex_unlock(&register_mutex);
return err;
}
}
return -ENOIOCTLCMD;
}
#ifdef CONFIG_COMPAT
#include "hwdep_compat.c"
#else
#define snd_hwdep_ioctl_compat NULL
#endif
/*
*/
static const struct file_operations snd_hwdep_f_ops =
{
.owner = THIS_MODULE,
.llseek = snd_hwdep_llseek,
.read = snd_hwdep_read,
.write = snd_hwdep_write,
.open = snd_hwdep_open,
.release = snd_hwdep_release,
.poll = snd_hwdep_poll,
.unlocked_ioctl = snd_hwdep_ioctl,
.compat_ioctl = snd_hwdep_ioctl_compat,
.mmap = snd_hwdep_mmap,
};
/**
* snd_hwdep_new - create a new hwdep instance
* @card: the card instance
* @id: the id string
* @device: the device index (zero-based)
* @rhwdep: the pointer to store the new hwdep instance
*
* Creates a new hwdep instance with the given index on the card.
* The callbacks (hwdep->ops) must be set on the returned instance
* after this call manually by the caller.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_hwdep_new(struct snd_card *card, char *id, int device,
struct snd_hwdep **rhwdep)
{
struct snd_hwdep *hwdep;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_hwdep_dev_free,
.dev_register = snd_hwdep_dev_register,
.dev_disconnect = snd_hwdep_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rhwdep)
*rhwdep = NULL;
hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
if (hwdep == NULL) {
dev_err(card->dev, "hwdep: cannot allocate\n");
return -ENOMEM;
}
hwdep->card = card;
hwdep->device = device;
if (id)
strlcpy(hwdep->id, id, sizeof(hwdep->id));
#ifdef CONFIG_SND_OSSEMUL
hwdep->oss_type = -1;
#endif
if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
snd_hwdep_free(hwdep);
return err;
}
init_waitqueue_head(&hwdep->open_wait);
mutex_init(&hwdep->open_mutex);
if (rhwdep)
*rhwdep = hwdep;
return 0;
}
EXPORT_SYMBOL(snd_hwdep_new);
static int snd_hwdep_free(struct snd_hwdep *hwdep)
{
if (!hwdep)
return 0;
if (hwdep->private_free)
hwdep->private_free(hwdep);
kfree(hwdep);
return 0;
}
static int snd_hwdep_dev_free(struct snd_device *device)
{
struct snd_hwdep *hwdep = device->device_data;
return snd_hwdep_free(hwdep);
}
static int snd_hwdep_dev_register(struct snd_device *device)
{
struct snd_hwdep *hwdep = device->device_data;
struct snd_card *card = hwdep->card;
struct device *dev;
int err;
char name[32];
mutex_lock(&register_mutex);
if (snd_hwdep_search(card, hwdep->device)) {
mutex_unlock(&register_mutex);
return -EBUSY;
}
list_add_tail(&hwdep->list, &snd_hwdep_devices);
sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
dev = hwdep->dev;
if (!dev)
dev = snd_card_get_device_link(hwdep->card);
err = snd_register_device_for_dev(SNDRV_DEVICE_TYPE_HWDEP,
hwdep->card, hwdep->device,
&snd_hwdep_f_ops, hwdep, name, dev);
if (err < 0) {
dev_err(dev,
"unable to register hardware dependent device %i:%i\n",
card->number, hwdep->device);
list_del(&hwdep->list);
mutex_unlock(&register_mutex);
return err;
}
if (hwdep->groups) {
struct device *d = snd_get_device(SNDRV_DEVICE_TYPE_HWDEP,
hwdep->card, hwdep->device);
if (d) {
if (hwdep->private_data)
dev_set_drvdata(d, hwdep->private_data);
err = sysfs_create_groups(&d->kobj, hwdep->groups);
if (err < 0)
dev_warn(dev,
"hwdep %d:%d: cannot create sysfs groups\n",
card->number, hwdep->device);
put_device(d);
}
}
#ifdef CONFIG_SND_OSSEMUL
hwdep->ossreg = 0;
if (hwdep->oss_type >= 0) {
if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
dev_warn(dev,
"only hwdep device 0 can be registered as OSS direct FM device!\n");
} else {
if (snd_register_oss_device(hwdep->oss_type,
card, hwdep->device,
&snd_hwdep_f_ops, hwdep) < 0) {
dev_err(dev,
"unable to register OSS compatibility device %i:%i\n",
card->number, hwdep->device);
} else
hwdep->ossreg = 1;
}
}
#endif
mutex_unlock(&register_mutex);
return 0;
}
static int snd_hwdep_dev_disconnect(struct snd_device *device)
{
struct snd_hwdep *hwdep = device->device_data;
if (snd_BUG_ON(!hwdep))
return -ENXIO;
mutex_lock(&register_mutex);
if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
mutex_unlock(&register_mutex);
return -EINVAL;
}
mutex_lock(&hwdep->open_mutex);
wake_up(&hwdep->open_wait);
#ifdef CONFIG_SND_OSSEMUL
if (hwdep->ossreg)
snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
#endif
snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
list_del_init(&hwdep->list);
mutex_unlock(&hwdep->open_mutex);
mutex_unlock(&register_mutex);
return 0;
}
#ifdef CONFIG_PROC_FS
/*
* Info interface
*/
static void snd_hwdep_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_hwdep *hwdep;
mutex_lock(&register_mutex);
list_for_each_entry(hwdep, &snd_hwdep_devices, list)
snd_iprintf(buffer, "%02i-%02i: %s\n",
hwdep->card->number, hwdep->device, hwdep->name);
mutex_unlock(&register_mutex);
}
static struct snd_info_entry *snd_hwdep_proc_entry;
static void __init snd_hwdep_proc_init(void)
{
struct snd_info_entry *entry;
if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
entry->c.text.read = snd_hwdep_proc_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_hwdep_proc_entry = entry;
}
static void __exit snd_hwdep_proc_done(void)
{
snd_info_free_entry(snd_hwdep_proc_entry);
}
#else /* !CONFIG_PROC_FS */
#define snd_hwdep_proc_init()
#define snd_hwdep_proc_done()
#endif /* CONFIG_PROC_FS */
/*
* ENTRY functions
*/
static int __init alsa_hwdep_init(void)
{
snd_hwdep_proc_init();
snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
return 0;
}
static void __exit alsa_hwdep_exit(void)
{
snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
snd_hwdep_proc_done();
}
module_init(alsa_hwdep_init)
module_exit(alsa_hwdep_exit)

78
sound/core/hwdep_compat.c Normal file
View file

@ -0,0 +1,78 @@
/*
* 32bit -> 64bit ioctl wrapper for hwdep API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file is included from hwdep.c */
#include <linux/compat.h>
struct snd_hwdep_dsp_image32 {
u32 index;
unsigned char name[64];
u32 image; /* pointer */
u32 length;
u32 driver_data;
} /* don't set packed attribute here */;
static int snd_hwdep_dsp_load_compat(struct snd_hwdep *hw,
struct snd_hwdep_dsp_image32 __user *src)
{
struct snd_hwdep_dsp_image __user *dst;
compat_caddr_t ptr;
u32 val;
dst = compat_alloc_user_space(sizeof(*dst));
/* index and name */
if (copy_in_user(dst, src, 4 + 64))
return -EFAULT;
if (get_user(ptr, &src->image) ||
put_user(compat_ptr(ptr), &dst->image))
return -EFAULT;
if (get_user(val, &src->length) ||
put_user(val, &dst->length))
return -EFAULT;
if (get_user(val, &src->driver_data) ||
put_user(val, &dst->driver_data))
return -EFAULT;
return snd_hwdep_dsp_load(hw, dst);
}
enum {
SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct snd_hwdep_dsp_image32)
};
static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd,
unsigned long arg)
{
struct snd_hwdep *hw = file->private_data;
void __user *argp = compat_ptr(arg);
switch (cmd) {
case SNDRV_HWDEP_IOCTL_PVERSION:
case SNDRV_HWDEP_IOCTL_INFO:
case SNDRV_HWDEP_IOCTL_DSP_STATUS:
return snd_hwdep_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_HWDEP_IOCTL_DSP_LOAD32:
return snd_hwdep_dsp_load_compat(hw, argp);
}
if (hw->ops.ioctl_compat)
return hw->ops.ioctl_compat(hw, file, cmd, arg);
return -ENOIOCTLCMD;
}

1010
sound/core/info.c Normal file

File diff suppressed because it is too large Load diff

138
sound/core/info_oss.c Normal file
View file

@ -0,0 +1,138 @@
/*
* Information interface for ALSA driver
* Copyright (c) by 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/time.h>
#include <linux/string.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <linux/utsname.h>
#include <linux/mutex.h>
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
/*
* OSS compatible part
*/
static DEFINE_MUTEX(strings);
static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
static struct snd_info_entry *snd_sndstat_proc_entry;
int snd_oss_info_register(int dev, int num, char *string)
{
char *x;
if (snd_BUG_ON(dev < 0 || dev >= SNDRV_OSS_INFO_DEV_COUNT))
return -ENXIO;
if (snd_BUG_ON(num < 0 || num >= SNDRV_CARDS))
return -ENXIO;
mutex_lock(&strings);
if (string == NULL) {
if ((x = snd_sndstat_strings[num][dev]) != NULL) {
kfree(x);
x = NULL;
}
} else {
x = kstrdup(string, GFP_KERNEL);
if (x == NULL) {
mutex_unlock(&strings);
return -ENOMEM;
}
}
snd_sndstat_strings[num][dev] = x;
mutex_unlock(&strings);
return 0;
}
EXPORT_SYMBOL(snd_oss_info_register);
static int snd_sndstat_show_strings(struct snd_info_buffer *buf, char *id, int dev)
{
int idx, ok = -1;
char *str;
snd_iprintf(buf, "\n%s:", id);
mutex_lock(&strings);
for (idx = 0; idx < SNDRV_CARDS; idx++) {
str = snd_sndstat_strings[idx][dev];
if (str) {
if (ok < 0) {
snd_iprintf(buf, "\n");
ok++;
}
snd_iprintf(buf, "%i: %s\n", idx, str);
}
}
mutex_unlock(&strings);
if (ok < 0)
snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
return ok;
}
static void snd_sndstat_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA emulation code)\n");
snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n",
init_utsname()->sysname,
init_utsname()->nodename,
init_utsname()->release,
init_utsname()->version,
init_utsname()->machine);
snd_iprintf(buffer, "Config options: 0\n");
snd_iprintf(buffer, "\nInstalled drivers: \n");
snd_iprintf(buffer, "Type 10: ALSA emulation\n");
snd_iprintf(buffer, "\nCard config: \n");
snd_card_info_read_oss(buffer);
snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO);
snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH);
snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI);
snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS);
snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS);
}
int snd_info_minor_register(void)
{
struct snd_info_entry *entry;
memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings));
if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) {
entry->c.text.read = snd_sndstat_proc_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_sndstat_proc_entry = entry;
return 0;
}
int snd_info_minor_unregister(void)
{
snd_info_free_entry(snd_sndstat_proc_entry);
snd_sndstat_proc_entry = NULL;
return 0;
}
#endif /* CONFIG_SND_OSSEMUL */

990
sound/core/init.c Normal file
View file

@ -0,0 +1,990 @@
/*
* Initialization routines
* Copyright (c) by 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/init.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/ctype.h>
#include <linux/pm.h>
#include <linux/completion.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
/* monitor files for graceful shutdown (hotplug) */
struct snd_monitor_file {
struct file *file;
const struct file_operations *disconnected_f_op;
struct list_head shutdown_list; /* still need to shutdown */
struct list_head list; /* link of monitor files */
};
static DEFINE_SPINLOCK(shutdown_lock);
static LIST_HEAD(shutdown_files);
static const struct file_operations snd_shutdown_f_ops;
/* locked for registering/using */
static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS);
struct snd_card *snd_cards[SNDRV_CARDS];
EXPORT_SYMBOL(snd_cards);
static DEFINE_MUTEX(snd_card_mutex);
static char *slots[SNDRV_CARDS];
module_param_array(slots, charp, NULL, 0444);
MODULE_PARM_DESC(slots, "Module names assigned to the slots.");
/* return non-zero if the given index is reserved for the given
* module via slots option
*/
static int module_slot_match(struct module *module, int idx)
{
int match = 1;
#ifdef MODULE
const char *s1, *s2;
if (!module || !*module->name || !slots[idx])
return 0;
s1 = module->name;
s2 = slots[idx];
if (*s2 == '!') {
match = 0; /* negative match */
s2++;
}
/* compare module name strings
* hyphens are handled as equivalent with underscore
*/
for (;;) {
char c1 = *s1++;
char c2 = *s2++;
if (c1 == '-')
c1 = '_';
if (c2 == '-')
c2 = '_';
if (c1 != c2)
return !match;
if (!c1)
break;
}
#endif /* MODULE */
return match;
}
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag);
EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
#endif
#ifdef CONFIG_PROC_FS
static void snd_card_id_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_iprintf(buffer, "%s\n", entry->card->id);
}
static inline int init_info_for_card(struct snd_card *card)
{
int err;
struct snd_info_entry *entry;
if ((err = snd_info_card_register(card)) < 0) {
dev_dbg(card->dev, "unable to create card info\n");
return err;
}
if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
dev_dbg(card->dev, "unable to create card entry\n");
return err;
}
entry->c.text.read = snd_card_id_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
card->proc_id = entry;
return 0;
}
#else /* !CONFIG_PROC_FS */
#define init_info_for_card(card)
#endif
static int check_empty_slot(struct module *module, int slot)
{
return !slots[slot] || !*slots[slot];
}
/* return an empty slot number (>= 0) found in the given bitmask @mask.
* @mask == -1 == 0xffffffff means: take any free slot up to 32
* when no slot is available, return the original @mask as is.
*/
static int get_slot_from_bitmask(int mask, int (*check)(struct module *, int),
struct module *module)
{
int slot;
for (slot = 0; slot < SNDRV_CARDS; slot++) {
if (slot < 32 && !(mask & (1U << slot)))
continue;
if (!test_bit(slot, snd_cards_lock)) {
if (check(module, slot))
return slot; /* found */
}
}
return mask; /* unchanged */
}
static int snd_card_do_free(struct snd_card *card);
static const struct attribute_group *card_dev_attr_groups[];
static void release_card_device(struct device *dev)
{
snd_card_do_free(dev_to_snd_card(dev));
}
/**
* snd_card_new - create and initialize a soundcard structure
* @parent: the parent device object
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
* @xid: card identification (ASCII string)
* @module: top level module for locking
* @extra_size: allocate this extra size after the main soundcard structure
* @card_ret: the pointer to store the created card instance
*
* Creates and initializes a soundcard structure.
*
* The function allocates snd_card instance via kzalloc with the given
* space for the driver to use freely. The allocated struct is stored
* in the given card_ret pointer.
*
* Return: Zero if successful or a negative error code.
*/
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
int err;
if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;
if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
if (!card)
return -ENOMEM;
if (extra_size > 0)
card->private_data = (char *)card + sizeof(struct snd_card);
if (xid)
strlcpy(card->id, xid, sizeof(card->id));
err = 0;
mutex_lock(&snd_card_mutex);
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
if (idx < 0)
err = -ENODEV;
else if (idx < snd_ecards_limit) {
if (test_bit(idx, snd_cards_lock))
err = -EBUSY; /* invalid */
} else if (idx >= SNDRV_CARDS)
err = -ENODEV;
if (err < 0) {
mutex_unlock(&snd_card_mutex);
dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",
idx, snd_ecards_limit - 1, err);
kfree(card);
return err;
}
set_bit(idx, snd_cards_lock); /* lock it */
if (idx >= snd_ecards_limit)
snd_ecards_limit = idx + 1; /* increase the limit */
mutex_unlock(&snd_card_mutex);
card->dev = parent;
card->number = idx;
card->module = module;
INIT_LIST_HEAD(&card->devices);
init_rwsem(&card->controls_rwsem);
rwlock_init(&card->ctl_files_rwlock);
mutex_init(&card->user_ctl_lock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
INIT_LIST_HEAD(&card->files_list);
#ifdef CONFIG_PM
mutex_init(&card->power_lock);
init_waitqueue_head(&card->power_sleep);
#endif
device_initialize(&card->card_dev);
card->card_dev.parent = parent;
card->card_dev.class = sound_class;
card->card_dev.release = release_card_device;
card->card_dev.groups = card_dev_attr_groups;
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
if (err < 0)
goto __error;
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
err = snd_ctl_create(card);
if (err < 0) {
dev_err(parent, "unable to register control minors\n");
goto __error;
}
err = snd_info_card_create(card);
if (err < 0) {
dev_err(parent, "unable to create card info\n");
goto __error_ctl;
}
*card_ret = card;
return 0;
__error_ctl:
snd_device_free_all(card);
__error:
put_device(&card->card_dev);
return err;
}
EXPORT_SYMBOL(snd_card_new);
/* return non-zero if a card is already locked */
int snd_card_locked(int card)
{
int locked;
mutex_lock(&snd_card_mutex);
locked = test_bit(card, snd_cards_lock);
mutex_unlock(&snd_card_mutex);
return locked;
}
static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig)
{
return -ENODEV;
}
static ssize_t snd_disconnect_read(struct file *file, char __user *buf,
size_t count, loff_t *offset)
{
return -ENODEV;
}
static ssize_t snd_disconnect_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
return -ENODEV;
}
static int snd_disconnect_release(struct inode *inode, struct file *file)
{
struct snd_monitor_file *df = NULL, *_df;
spin_lock(&shutdown_lock);
list_for_each_entry(_df, &shutdown_files, shutdown_list) {
if (_df->file == file) {
df = _df;
list_del_init(&df->shutdown_list);
break;
}
}
spin_unlock(&shutdown_lock);
if (likely(df)) {
if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync)
df->disconnected_f_op->fasync(-1, file, 0);
return df->disconnected_f_op->release(inode, file);
}
panic("%s(%p, %p) failed!", __func__, inode, file);
}
static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
{
return POLLERR | POLLNVAL;
}
static long snd_disconnect_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
return -ENODEV;
}
static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma)
{
return -ENODEV;
}
static int snd_disconnect_fasync(int fd, struct file *file, int on)
{
return -ENODEV;
}
static const struct file_operations snd_shutdown_f_ops =
{
.owner = THIS_MODULE,
.llseek = snd_disconnect_llseek,
.read = snd_disconnect_read,
.write = snd_disconnect_write,
.release = snd_disconnect_release,
.poll = snd_disconnect_poll,
.unlocked_ioctl = snd_disconnect_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = snd_disconnect_ioctl,
#endif
.mmap = snd_disconnect_mmap,
.fasync = snd_disconnect_fasync
};
/**
* snd_card_disconnect - disconnect all APIs from the file-operations (user space)
* @card: soundcard structure
*
* Disconnects all APIs from the file-operations (user space).
*
* Return: Zero, otherwise a negative error code.
*
* Note: The current implementation replaces all active file->f_op with special
* dummy file operations (they do nothing except release).
*/
int snd_card_disconnect(struct snd_card *card)
{
struct snd_monitor_file *mfile;
int err;
if (!card)
return -EINVAL;
spin_lock(&card->files_lock);
if (card->shutdown) {
spin_unlock(&card->files_lock);
return 0;
}
card->shutdown = 1;
spin_unlock(&card->files_lock);
/* phase 1: disable fops (user space) operations for ALSA API */
mutex_lock(&snd_card_mutex);
snd_cards[card->number] = NULL;
clear_bit(card->number, snd_cards_lock);
mutex_unlock(&snd_card_mutex);
/* phase 2: replace file->f_op with special dummy operations */
spin_lock(&card->files_lock);
list_for_each_entry(mfile, &card->files_list, list) {
/* it's critical part, use endless loop */
/* we have no room to fail */
mfile->disconnected_f_op = mfile->file->f_op;
spin_lock(&shutdown_lock);
list_add(&mfile->shutdown_list, &shutdown_files);
spin_unlock(&shutdown_lock);
mfile->file->f_op = &snd_shutdown_f_ops;
fops_get(mfile->file->f_op);
}
spin_unlock(&card->files_lock);
/* phase 3: notify all connected devices about disconnection */
/* at this point, they cannot respond to any calls except release() */
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
#endif
/* notify all devices that we are disconnected */
err = snd_device_disconnect_all(card);
if (err < 0)
dev_err(card->dev, "not all devices for card %i can be disconnected\n", card->number);
snd_info_card_disconnect(card);
if (card->registered) {
device_del(&card->card_dev);
card->registered = false;
}
#ifdef CONFIG_PM
wake_up(&card->power_sleep);
#endif
return 0;
}
EXPORT_SYMBOL(snd_card_disconnect);
/**
* snd_card_free - frees given soundcard structure
* @card: soundcard structure
*
* This function releases the soundcard structure and the all assigned
* devices automatically. That is, you don't have to release the devices
* by yourself.
*
* Return: Zero. Frees all associated devices and frees the control
* interface associated to given soundcard.
*/
static int snd_card_do_free(struct snd_card *card)
{
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
#endif
snd_device_free_all(card);
if (card->private_free)
card->private_free(card);
snd_info_free_entry(card->proc_id);
if (snd_info_card_free(card) < 0) {
dev_warn(card->dev, "unable to free card info\n");
/* Not fatal error */
}
if (card->release_completion)
complete(card->release_completion);
kfree(card);
return 0;
}
int snd_card_free_when_closed(struct snd_card *card)
{
int ret = snd_card_disconnect(card);
if (ret)
return ret;
put_device(&card->card_dev);
return 0;
}
EXPORT_SYMBOL(snd_card_free_when_closed);
int snd_card_free(struct snd_card *card)
{
struct completion released;
int ret;
init_completion(&released);
card->release_completion = &released;
ret = snd_card_free_when_closed(card);
if (ret)
return ret;
/* wait, until all devices are ready for the free operation */
wait_for_completion(&released);
return 0;
}
EXPORT_SYMBOL(snd_card_free);
/* retrieve the last word of shortname or longname */
static const char *retrieve_id_from_card_name(const char *name)
{
const char *spos = name;
while (*name) {
if (isspace(*name) && isalnum(name[1]))
spos = name + 1;
name++;
}
return spos;
}
/* return true if the given id string doesn't conflict any other card ids */
static bool card_id_ok(struct snd_card *card, const char *id)
{
int i;
if (!snd_info_check_reserved_words(id))
return false;
for (i = 0; i < snd_ecards_limit; i++) {
if (snd_cards[i] && snd_cards[i] != card &&
!strcmp(snd_cards[i]->id, id))
return false;
}
return true;
}
/* copy to card->id only with valid letters from nid */
static void copy_valid_id_string(struct snd_card *card, const char *src,
const char *nid)
{
char *id = card->id;
while (*nid && !isalnum(*nid))
nid++;
if (isdigit(*nid))
*id++ = isalpha(*src) ? *src : 'D';
while (*nid && (size_t)(id - card->id) < sizeof(card->id) - 1) {
if (isalnum(*nid))
*id++ = *nid;
nid++;
}
*id = 0;
}
/* Set card->id from the given string
* If the string conflicts with other ids, add a suffix to make it unique.
*/
static void snd_card_set_id_no_lock(struct snd_card *card, const char *src,
const char *nid)
{
int len, loops;
bool is_default = false;
char *id;
copy_valid_id_string(card, src, nid);
id = card->id;
again:
/* use "Default" for obviously invalid strings
* ("card" conflicts with proc directories)
*/
if (!*id || !strncmp(id, "card", 4)) {
strcpy(id, "Default");
is_default = true;
}
len = strlen(id);
for (loops = 0; loops < SNDRV_CARDS; loops++) {
char *spos;
char sfxstr[5]; /* "_012" */
int sfxlen;
if (card_id_ok(card, id))
return; /* OK */
/* Add _XYZ suffix */
sprintf(sfxstr, "_%X", loops + 1);
sfxlen = strlen(sfxstr);
if (len + sfxlen >= sizeof(card->id))
spos = id + sizeof(card->id) - sfxlen - 1;
else
spos = id + len;
strcpy(spos, sfxstr);
}
/* fallback to the default id */
if (!is_default) {
*id = 0;
goto again;
}
/* last resort... */
dev_err(card->dev, "unable to set card id (%s)\n", id);
if (card->proc_root->name)
strlcpy(card->id, card->proc_root->name, sizeof(card->id));
}
/**
* snd_card_set_id - set card identification name
* @card: soundcard structure
* @nid: new identification string
*
* This function sets the card identification and checks for name
* collisions.
*/
void snd_card_set_id(struct snd_card *card, const char *nid)
{
/* check if user specified own card->id */
if (card->id[0] != '\0')
return;
mutex_lock(&snd_card_mutex);
snd_card_set_id_no_lock(card, nid, nid);
mutex_unlock(&snd_card_mutex);
}
EXPORT_SYMBOL(snd_card_set_id);
static ssize_t
card_id_show_attr(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
return snprintf(buf, PAGE_SIZE, "%s\n", card->id);
}
static ssize_t
card_id_store_attr(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
char buf1[sizeof(card->id)];
size_t copy = count > sizeof(card->id) - 1 ?
sizeof(card->id) - 1 : count;
size_t idx;
int c;
for (idx = 0; idx < copy; idx++) {
c = buf[idx];
if (!isalnum(c) && c != '_' && c != '-')
return -EINVAL;
}
memcpy(buf1, buf, copy);
buf1[copy] = '\0';
mutex_lock(&snd_card_mutex);
if (!card_id_ok(NULL, buf1)) {
mutex_unlock(&snd_card_mutex);
return -EEXIST;
}
strcpy(card->id, buf1);
snd_info_card_id_change(card);
mutex_unlock(&snd_card_mutex);
return count;
}
static DEVICE_ATTR(id, S_IRUGO | S_IWUSR, card_id_show_attr, card_id_store_attr);
static ssize_t
card_number_show_attr(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct snd_card *card = container_of(dev, struct snd_card, card_dev);
return snprintf(buf, PAGE_SIZE, "%i\n", card->number);
}
static DEVICE_ATTR(number, S_IRUGO, card_number_show_attr, NULL);
static struct attribute *card_dev_attrs[] = {
&dev_attr_id.attr,
&dev_attr_number.attr,
NULL
};
static struct attribute_group card_dev_attr_group = {
.attrs = card_dev_attrs,
};
static const struct attribute_group *card_dev_attr_groups[] = {
&card_dev_attr_group,
NULL
};
/**
* snd_card_register - register the soundcard
* @card: soundcard structure
*
* This function registers all the devices assigned to the soundcard.
* Until calling this, the ALSA control interface is blocked from the
* external accesses. Thus, you should call this function at the end
* of the initialization of the card.
*
* Return: Zero otherwise a negative error code if the registration failed.
*/
int snd_card_register(struct snd_card *card)
{
int err;
if (snd_BUG_ON(!card))
return -EINVAL;
if (!card->registered) {
err = device_add(&card->card_dev);
if (err < 0)
return err;
card->registered = true;
}
if ((err = snd_device_register_all(card)) < 0)
return err;
mutex_lock(&snd_card_mutex);
if (snd_cards[card->number]) {
/* already registered */
mutex_unlock(&snd_card_mutex);
return 0;
}
if (*card->id) {
/* make a unique id name from the given string */
char tmpid[sizeof(card->id)];
memcpy(tmpid, card->id, sizeof(card->id));
snd_card_set_id_no_lock(card, tmpid, tmpid);
} else {
/* create an id from either shortname or longname */
const char *src;
src = *card->shortname ? card->shortname : card->longname;
snd_card_set_id_no_lock(card, src,
retrieve_id_from_card_name(src));
}
snd_cards[card->number] = card;
mutex_unlock(&snd_card_mutex);
init_info_for_card(card);
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
return 0;
}
EXPORT_SYMBOL(snd_card_register);
#ifdef CONFIG_PROC_FS
static struct snd_info_entry *snd_card_info_entry;
static void snd_card_info_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int idx, count;
struct snd_card *card;
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
mutex_lock(&snd_card_mutex);
if ((card = snd_cards[idx]) != NULL) {
count++;
snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
idx,
card->id,
card->driver,
card->shortname);
snd_iprintf(buffer, " %s\n",
card->longname);
}
mutex_unlock(&snd_card_mutex);
}
if (!count)
snd_iprintf(buffer, "--- no soundcards ---\n");
}
#ifdef CONFIG_SND_OSSEMUL
void snd_card_info_read_oss(struct snd_info_buffer *buffer)
{
int idx, count;
struct snd_card *card;
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
mutex_lock(&snd_card_mutex);
if ((card = snd_cards[idx]) != NULL) {
count++;
snd_iprintf(buffer, "%s\n", card->longname);
}
mutex_unlock(&snd_card_mutex);
}
if (!count) {
snd_iprintf(buffer, "--- no soundcards ---\n");
}
}
#endif
#ifdef MODULE
static struct snd_info_entry *snd_card_module_info_entry;
static void snd_card_module_info_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int idx;
struct snd_card *card;
for (idx = 0; idx < SNDRV_CARDS; idx++) {
mutex_lock(&snd_card_mutex);
if ((card = snd_cards[idx]) != NULL)
snd_iprintf(buffer, "%2i %s\n",
idx, card->module->name);
mutex_unlock(&snd_card_mutex);
}
}
#endif
int __init snd_card_info_init(void)
{
struct snd_info_entry *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
if (! entry)
return -ENOMEM;
entry->c.text.read = snd_card_info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
snd_card_info_entry = entry;
#ifdef MODULE
entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL);
if (entry) {
entry->c.text.read = snd_card_module_info_read;
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
else
snd_card_module_info_entry = entry;
}
#endif
return 0;
}
int __exit snd_card_info_done(void)
{
snd_info_free_entry(snd_card_info_entry);
#ifdef MODULE
snd_info_free_entry(snd_card_module_info_entry);
#endif
return 0;
}
#endif /* CONFIG_PROC_FS */
/**
* snd_component_add - add a component string
* @card: soundcard structure
* @component: the component id string
*
* This function adds the component id string to the supported list.
* The component can be referred from the alsa-lib.
*
* Return: Zero otherwise a negative error code.
*/
int snd_component_add(struct snd_card *card, const char *component)
{
char *ptr;
int len = strlen(component);
ptr = strstr(card->components, component);
if (ptr != NULL) {
if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
return 1;
}
if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
snd_BUG();
return -ENOMEM;
}
if (card->components[0] != '\0')
strcat(card->components, " ");
strcat(card->components, component);
return 0;
}
EXPORT_SYMBOL(snd_component_add);
/**
* snd_card_file_add - add the file to the file list of the card
* @card: soundcard structure
* @file: file pointer
*
* This function adds the file to the file linked-list of the card.
* This linked-list is used to keep tracking the connection state,
* and to avoid the release of busy resources by hotplug.
*
* Return: zero or a negative error code.
*/
int snd_card_file_add(struct snd_card *card, struct file *file)
{
struct snd_monitor_file *mfile;
mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
if (mfile == NULL)
return -ENOMEM;
mfile->file = file;
mfile->disconnected_f_op = NULL;
INIT_LIST_HEAD(&mfile->shutdown_list);
spin_lock(&card->files_lock);
if (card->shutdown) {
spin_unlock(&card->files_lock);
kfree(mfile);
return -ENODEV;
}
list_add(&mfile->list, &card->files_list);
get_device(&card->card_dev);
spin_unlock(&card->files_lock);
return 0;
}
EXPORT_SYMBOL(snd_card_file_add);
/**
* snd_card_file_remove - remove the file from the file list
* @card: soundcard structure
* @file: file pointer
*
* This function removes the file formerly added to the card via
* snd_card_file_add() function.
* If all files are removed and snd_card_free_when_closed() was
* called beforehand, it processes the pending release of
* resources.
*
* Return: Zero or a negative error code.
*/
int snd_card_file_remove(struct snd_card *card, struct file *file)
{
struct snd_monitor_file *mfile, *found = NULL;
spin_lock(&card->files_lock);
list_for_each_entry(mfile, &card->files_list, list) {
if (mfile->file == file) {
list_del(&mfile->list);
spin_lock(&shutdown_lock);
list_del(&mfile->shutdown_list);
spin_unlock(&shutdown_lock);
if (mfile->disconnected_f_op)
fops_put(mfile->disconnected_f_op);
found = mfile;
break;
}
}
spin_unlock(&card->files_lock);
if (!found) {
dev_err(card->dev, "card file remove problem (%p)\n", file);
return -ENOENT;
}
kfree(found);
put_device(&card->card_dev);
return 0;
}
EXPORT_SYMBOL(snd_card_file_remove);
#ifdef CONFIG_PM
/**
* snd_power_wait - wait until the power-state is changed.
* @card: soundcard structure
* @power_state: expected power state
*
* Waits until the power-state is changed.
*
* Return: Zero if successful, or a negative error code.
*
* Note: the power lock must be active before call.
*/
int snd_power_wait(struct snd_card *card, unsigned int power_state)
{
wait_queue_t wait;
int result = 0;
/* fastpath */
if (snd_power_get_state(card) == power_state)
return 0;
init_waitqueue_entry(&wait, current);
add_wait_queue(&card->power_sleep, &wait);
while (1) {
if (card->shutdown) {
result = -ENODEV;
break;
}
if (snd_power_get_state(card) == power_state)
break;
set_current_state(TASK_UNINTERRUPTIBLE);
snd_power_unlock(card);
schedule_timeout(30 * HZ);
snd_power_lock(card);
}
remove_wait_queue(&card->power_sleep, &wait);
return result;
}
EXPORT_SYMBOL(snd_power_wait);
#endif /* CONFIG_PM */

117
sound/core/isadma.c Normal file
View file

@ -0,0 +1,117 @@
/*
* ISA DMA support functions
* Copyright (c) by 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
*
*/
/*
* Defining following add some delay. Maybe this helps for some broken
* ISA DMA controllers.
*/
#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
#include <linux/export.h>
#include <sound/core.h>
#include <asm/dma.h>
/**
* snd_dma_program - program an ISA DMA transfer
* @dma: the dma number
* @addr: the physical address of the buffer
* @size: the DMA transfer size
* @mode: the DMA transfer mode, DMA_MODE_XXX
*
* Programs an ISA DMA transfer for the given buffer.
*/
void snd_dma_program(unsigned long dma,
unsigned long addr, unsigned int size,
unsigned short mode)
{
unsigned long flags;
flags = claim_dma_lock();
disable_dma(dma);
clear_dma_ff(dma);
set_dma_mode(dma, mode);
set_dma_addr(dma, addr);
set_dma_count(dma, size);
if (!(mode & DMA_MODE_NO_ENABLE))
enable_dma(dma);
release_dma_lock(flags);
}
EXPORT_SYMBOL(snd_dma_program);
/**
* snd_dma_disable - stop the ISA DMA transfer
* @dma: the dma number
*
* Stops the ISA DMA transfer.
*/
void snd_dma_disable(unsigned long dma)
{
unsigned long flags;
flags = claim_dma_lock();
clear_dma_ff(dma);
disable_dma(dma);
release_dma_lock(flags);
}
EXPORT_SYMBOL(snd_dma_disable);
/**
* snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
* @dma: the dma number
* @size: the dma transfer size
*
* Return: The current pointer in DMA transfer buffer in bytes.
*/
unsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
{
unsigned long flags;
unsigned int result, result1;
flags = claim_dma_lock();
clear_dma_ff(dma);
if (!isa_dma_bridge_buggy)
disable_dma(dma);
result = get_dma_residue(dma);
/*
* HACK - read the counter again and choose higher value in order to
* avoid reading during counter lower byte roll over if the
* isa_dma_bridge_buggy is set.
*/
result1 = get_dma_residue(dma);
if (!isa_dma_bridge_buggy)
enable_dma(dma);
release_dma_lock(flags);
if (unlikely(result < result1))
result = result1;
#ifdef CONFIG_SND_DEBUG
if (result > size)
pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
#endif
if (result >= size || result == 0)
return 0;
else
return size - result;
}
EXPORT_SYMBOL(snd_dma_pointer);

260
sound/core/jack.c Normal file
View file

@ -0,0 +1,260 @@
/*
* Jack abstraction layer
*
* Copyright 2008 Wolfson Microelectronics
*
* 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/input.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/jack.h>
#include <sound/core.h>
static int jack_switch_types[SND_JACK_SWITCH_TYPES] = {
SW_HEADPHONE_INSERT,
SW_MICROPHONE_INSERT,
SW_LINEOUT_INSERT,
SW_JACK_PHYSICAL_INSERT,
SW_VIDEOOUT_INSERT,
SW_LINEIN_INSERT,
};
static int snd_jack_dev_disconnect(struct snd_device *device)
{
struct snd_jack *jack = device->device_data;
if (!jack->input_dev)
return 0;
/* If the input device is registered with the input subsystem
* then we need to use a different deallocator. */
if (jack->registered)
input_unregister_device(jack->input_dev);
else
input_free_device(jack->input_dev);
jack->input_dev = NULL;
return 0;
}
static int snd_jack_dev_free(struct snd_device *device)
{
struct snd_jack *jack = device->device_data;
if (jack->private_free)
jack->private_free(jack);
snd_jack_dev_disconnect(device);
kfree(jack->id);
kfree(jack);
return 0;
}
static int snd_jack_dev_register(struct snd_device *device)
{
struct snd_jack *jack = device->device_data;
struct snd_card *card = device->card;
int err, i;
snprintf(jack->name, sizeof(jack->name), "%s %s",
card->shortname, jack->id);
jack->input_dev->name = jack->name;
/* Default to the sound card device. */
if (!jack->input_dev->dev.parent)
jack->input_dev->dev.parent = snd_card_get_device_link(card);
/* Add capabilities for any keys that are enabled */
for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
int testbit = SND_JACK_BTN_0 >> i;
if (!(jack->type & testbit))
continue;
if (!jack->key[i])
jack->key[i] = BTN_0 + i;
input_set_capability(jack->input_dev, EV_KEY, jack->key[i]);
}
err = input_register_device(jack->input_dev);
if (err == 0)
jack->registered = 1;
return err;
}
/**
* snd_jack_new - Create a new jack
* @card: the card instance
* @id: an identifying string for this jack
* @type: a bitmask of enum snd_jack_type values that can be detected by
* this jack
* @jjack: Used to provide the allocated jack object to the caller.
*
* Creates a new jack object.
*
* Return: Zero if successful, or a negative error code on failure.
* On success @jjack will be initialised.
*/
int snd_jack_new(struct snd_card *card, const char *id, int type,
struct snd_jack **jjack)
{
struct snd_jack *jack;
int err;
int i;
static struct snd_device_ops ops = {
.dev_free = snd_jack_dev_free,
.dev_register = snd_jack_dev_register,
.dev_disconnect = snd_jack_dev_disconnect,
};
jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL);
if (jack == NULL)
return -ENOMEM;
jack->id = kstrdup(id, GFP_KERNEL);
jack->input_dev = input_allocate_device();
if (jack->input_dev == NULL) {
err = -ENOMEM;
goto fail_input;
}
jack->input_dev->phys = "ALSA";
jack->type = type;
for (i = 0; i < SND_JACK_SWITCH_TYPES; i++)
if (type & (1 << i))
input_set_capability(jack->input_dev, EV_SW,
jack_switch_types[i]);
err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops);
if (err < 0)
goto fail_input;
*jjack = jack;
return 0;
fail_input:
input_free_device(jack->input_dev);
kfree(jack->id);
kfree(jack);
return err;
}
EXPORT_SYMBOL(snd_jack_new);
/**
* snd_jack_set_parent - Set the parent device for a jack
*
* @jack: The jack to configure
* @parent: The device to set as parent for the jack.
*
* Set the parent for the jack devices in the device tree. This
* function is only valid prior to registration of the jack. If no
* parent is configured then the parent device will be the sound card.
*/
void snd_jack_set_parent(struct snd_jack *jack, struct device *parent)
{
WARN_ON(jack->registered);
jack->input_dev->dev.parent = parent;
}
EXPORT_SYMBOL(snd_jack_set_parent);
/**
* snd_jack_set_key - Set a key mapping on a jack
*
* @jack: The jack to configure
* @type: Jack report type for this key
* @keytype: Input layer key type to be reported
*
* Map a SND_JACK_BTN_ button type to an input layer key, allowing
* reporting of keys on accessories via the jack abstraction. If no
* mapping is provided but keys are enabled in the jack type then
* BTN_n numeric buttons will be reported.
*
* If jacks are not reporting via the input API this call will have no
* effect.
*
* Note that this is intended to be use by simple devices with small
* numbers of keys that can be reported. It is also possible to
* access the input device directly - devices with complex input
* capabilities on accessories should consider doing this rather than
* using this abstraction.
*
* This function may only be called prior to registration of the jack.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type,
int keytype)
{
int key = fls(SND_JACK_BTN_0) - fls(type);
WARN_ON(jack->registered);
if (!keytype || key >= ARRAY_SIZE(jack->key))
return -EINVAL;
jack->type |= type;
jack->key[key] = keytype;
return 0;
}
EXPORT_SYMBOL(snd_jack_set_key);
/**
* snd_jack_report - Report the current status of a jack
*
* @jack: The jack to report status for
* @status: The current status of the jack
*/
void snd_jack_report(struct snd_jack *jack, int status)
{
int i;
if (!jack)
return;
for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
int testbit = SND_JACK_BTN_0 >> i;
if (jack->type & testbit)
input_report_key(jack->input_dev, jack->key[i],
status & testbit);
}
for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) {
int testbit = 1 << i;
if (jack->type & testbit)
input_report_switch(jack->input_dev,
jack_switch_types[i],
status & testbit);
}
input_sync(jack->input_dev);
}
EXPORT_SYMBOL(snd_jack_report);
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("Jack detection support for ALSA");
MODULE_LICENSE("GPL");

299
sound/core/memalloc.c Normal file
View file

@ -0,0 +1,299 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Takashi Iwai <tiwai@suse.de>
*
* Generic memory allocators
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/genalloc.h>
#include <sound/memalloc.h>
/*
*
* Generic memory allocators
*
*/
/**
* snd_malloc_pages - allocate pages with the given size
* @size: the size to allocate in bytes
* @gfp_flags: the allocation conditions, GFP_XXX
*
* Allocates the physically contiguous pages with the given size.
*
* Return: The pointer of the buffer, or %NULL if no enough memory.
*/
void *snd_malloc_pages(size_t size, gfp_t gfp_flags)
{
int pg;
if (WARN_ON(!size))
return NULL;
if (WARN_ON(!gfp_flags))
return NULL;
gfp_flags |= __GFP_COMP; /* compound page lets parts be mapped */
pg = get_order(size);
return (void *) __get_free_pages(gfp_flags, pg);
}
/**
* snd_free_pages - release the pages
* @ptr: the buffer pointer to release
* @size: the allocated buffer size
*
* Releases the buffer allocated via snd_malloc_pages().
*/
void snd_free_pages(void *ptr, size_t size)
{
int pg;
if (ptr == NULL)
return;
pg = get_order(size);
free_pages((unsigned long) ptr, pg);
}
/*
*
* Bus-specific memory allocators
*
*/
#ifdef CONFIG_HAS_DMA
/* allocate the coherent DMA pages */
static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma)
{
int pg;
gfp_t gfp_flags;
if (WARN_ON(!dma))
return NULL;
pg = get_order(size);
gfp_flags = GFP_KERNEL
| __GFP_COMP /* compound page lets parts be mapped */
| __GFP_NORETRY /* don't trigger OOM-killer */
| __GFP_NOWARN; /* no stack trace print - this call is non-critical */
return dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
}
/* free the coherent DMA pages */
static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr,
dma_addr_t dma)
{
int pg;
if (ptr == NULL)
return;
pg = get_order(size);
dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
}
#ifdef CONFIG_GENERIC_ALLOCATOR
/**
* snd_malloc_dev_iram - allocate memory from on-chip internal ram
* @dmab: buffer allocation record to store the allocated data
* @size: number of bytes to allocate from the iram
*
* This function requires iram phandle provided via of_node
*/
static void snd_malloc_dev_iram(struct snd_dma_buffer *dmab, size_t size)
{
struct device *dev = dmab->dev.dev;
struct gen_pool *pool = NULL;
dmab->area = NULL;
dmab->addr = 0;
if (dev->of_node)
pool = of_get_named_gen_pool(dev->of_node, "iram", 0);
if (!pool)
return;
/* Assign the pool into private_data field */
dmab->private_data = pool;
dmab->area = gen_pool_dma_alloc(pool, size, &dmab->addr);
}
/**
* snd_free_dev_iram - free allocated specific memory from on-chip internal ram
* @dmab: buffer allocation record to store the allocated data
*/
static void snd_free_dev_iram(struct snd_dma_buffer *dmab)
{
struct gen_pool *pool = dmab->private_data;
if (pool && dmab->area)
gen_pool_free(pool, (unsigned long)dmab->area, dmab->bytes);
}
#endif /* CONFIG_GENERIC_ALLOCATOR */
#endif /* CONFIG_HAS_DMA */
/*
*
* ALSA generic memory management
*
*/
/**
* snd_dma_alloc_pages - allocate the buffer area according to the given type
* @type: the DMA buffer type
* @device: the device pointer
* @size: the buffer size to allocate
* @dmab: buffer allocation record to store the allocated data
*
* Calls the memory-allocator function for the corresponding
* buffer type.
*
* Return: Zero if the buffer with the given size is allocated successfully,
* otherwise a negative value on error.
*/
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
struct snd_dma_buffer *dmab)
{
if (WARN_ON(!size))
return -ENXIO;
if (WARN_ON(!dmab))
return -ENXIO;
dmab->dev.type = type;
dmab->dev.dev = device;
dmab->bytes = 0;
switch (type) {
case SNDRV_DMA_TYPE_CONTINUOUS:
dmab->area = snd_malloc_pages(size,
(__force gfp_t)(unsigned long)device);
dmab->addr = 0;
break;
#ifdef CONFIG_HAS_DMA
#ifdef CONFIG_GENERIC_ALLOCATOR
case SNDRV_DMA_TYPE_DEV_IRAM:
snd_malloc_dev_iram(dmab, size);
if (dmab->area)
break;
/* Internal memory might have limited size and no enough space,
* so if we fail to malloc, try to fetch memory traditionally.
*/
dmab->dev.type = SNDRV_DMA_TYPE_DEV;
#endif /* CONFIG_GENERIC_ALLOCATOR */
case SNDRV_DMA_TYPE_DEV:
dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
break;
#endif
#ifdef CONFIG_SND_DMA_SGBUF
case SNDRV_DMA_TYPE_DEV_SG:
snd_malloc_sgbuf_pages(device, size, dmab, NULL);
break;
#endif
default:
pr_err("snd-malloc: invalid device type %d\n", type);
dmab->area = NULL;
dmab->addr = 0;
return -ENXIO;
}
if (! dmab->area)
return -ENOMEM;
dmab->bytes = size;
return 0;
}
/**
* snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback
* @type: the DMA buffer type
* @device: the device pointer
* @size: the buffer size to allocate
* @dmab: buffer allocation record to store the allocated data
*
* Calls the memory-allocator function for the corresponding
* buffer type. When no space is left, this function reduces the size and
* tries to allocate again. The size actually allocated is stored in
* res_size argument.
*
* Return: Zero if the buffer with the given size is allocated successfully,
* otherwise a negative value on error.
*/
int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size,
struct snd_dma_buffer *dmab)
{
int err;
while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
size_t aligned_size;
if (err != -ENOMEM)
return err;
if (size <= PAGE_SIZE)
return -ENOMEM;
aligned_size = PAGE_SIZE << get_order(size);
if (size != aligned_size)
size = aligned_size;
else
size >>= 1;
}
if (! dmab->area)
return -ENOMEM;
return 0;
}
/**
* snd_dma_free_pages - release the allocated buffer
* @dmab: the buffer allocation record to release
*
* Releases the allocated buffer via snd_dma_alloc_pages().
*/
void snd_dma_free_pages(struct snd_dma_buffer *dmab)
{
switch (dmab->dev.type) {
case SNDRV_DMA_TYPE_CONTINUOUS:
snd_free_pages(dmab->area, dmab->bytes);
break;
#ifdef CONFIG_HAS_DMA
#ifdef CONFIG_GENERIC_ALLOCATOR
case SNDRV_DMA_TYPE_DEV_IRAM:
snd_free_dev_iram(dmab);
break;
#endif /* CONFIG_GENERIC_ALLOCATOR */
case SNDRV_DMA_TYPE_DEV:
snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
break;
#endif
#ifdef CONFIG_SND_DMA_SGBUF
case SNDRV_DMA_TYPE_DEV_SG:
snd_free_sgbuf_pages(dmab);
break;
#endif
default:
pr_err("snd-malloc: invalid device type %d\n", dmab->dev.type);
}
}
/*
* exports
*/
EXPORT_SYMBOL(snd_dma_alloc_pages);
EXPORT_SYMBOL(snd_dma_alloc_pages_fallback);
EXPORT_SYMBOL(snd_dma_free_pages);
EXPORT_SYMBOL(snd_malloc_pages);
EXPORT_SYMBOL(snd_free_pages);

92
sound/core/memory.c Normal file
View file

@ -0,0 +1,92 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
* Misc memory accessors
*
*
* 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 <asm/io.h>
#include <asm/uaccess.h>
#include <sound/core.h>
/**
* copy_to_user_fromio - copy data from mmio-space to user-space
* @dst: the destination pointer on user-space
* @src: the source pointer on mmio
* @count: the data size to copy in bytes
*
* Copies the data from mmio-space to user-space.
*
* Return: Zero if successful, or non-zero on failure.
*/
int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
return copy_to_user(dst, (const void __force*)src, count) ? -EFAULT : 0;
#else
char buf[256];
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
memcpy_fromio(buf, (void __iomem *)src, c);
if (copy_to_user(dst, buf, c))
return -EFAULT;
count -= c;
dst += c;
src += c;
}
return 0;
#endif
}
EXPORT_SYMBOL(copy_to_user_fromio);
/**
* copy_from_user_toio - copy data from user-space to mmio-space
* @dst: the destination pointer on mmio-space
* @src: the source pointer on user-space
* @count: the data size to copy in bytes
*
* Copies the data from user-space to mmio-space.
*
* Return: Zero if successful, or non-zero on failure.
*/
int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
return copy_from_user((void __force *)dst, src, count) ? -EFAULT : 0;
#else
char buf[256];
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
if (copy_from_user(buf, src, c))
return -EFAULT;
memcpy_toio(dst, buf, c);
count -= c;
dst += c;
src += c;
}
return 0;
#endif
}
EXPORT_SYMBOL(copy_from_user_toio);

155
sound/core/misc.c Normal file
View file

@ -0,0 +1,155 @@
/*
* Misc and compatibility things
* Copyright (c) by 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/init.h>
#include <linux/export.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <sound/core.h>
#ifdef CONFIG_SND_DEBUG
#ifdef CONFIG_SND_DEBUG_VERBOSE
#define DEFAULT_DEBUG_LEVEL 2
#else
#define DEFAULT_DEBUG_LEVEL 1
#endif
static int debug = DEFAULT_DEBUG_LEVEL;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Debug level (0 = disable)");
#endif /* CONFIG_SND_DEBUG */
void release_and_free_resource(struct resource *res)
{
if (res) {
release_resource(res);
kfree(res);
}
}
EXPORT_SYMBOL(release_and_free_resource);
#ifdef CONFIG_SND_VERBOSE_PRINTK
/* strip the leading path if the given path is absolute */
static const char *sanity_file_name(const char *path)
{
if (*path == '/')
return strrchr(path, '/') + 1;
else
return path;
}
#endif
#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK)
void __snd_printk(unsigned int level, const char *path, int line,
const char *format, ...)
{
va_list args;
#ifdef CONFIG_SND_VERBOSE_PRINTK
int kern_level;
struct va_format vaf;
char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV";
#endif
#ifdef CONFIG_SND_DEBUG
if (debug < level)
return;
#endif
va_start(args, format);
#ifdef CONFIG_SND_VERBOSE_PRINTK
vaf.fmt = format;
vaf.va = &args;
kern_level = printk_get_level(format);
if (kern_level) {
const char *end_of_header = printk_skip_level(format);
memcpy(verbose_fmt, format, end_of_header - format);
vaf.fmt = end_of_header;
} else if (level)
memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1);
printk(verbose_fmt, sanity_file_name(path), line, &vaf);
#else
vprintk(format, args);
#endif
va_end(args);
}
EXPORT_SYMBOL_GPL(__snd_printk);
#endif
#ifdef CONFIG_PCI
#include <linux/pci.h>
/**
* snd_pci_quirk_lookup_id - look up a PCI SSID quirk list
* @vendor: PCI SSV id
* @device: PCI SSD id
* @list: quirk list, terminated by a null entry
*
* Look through the given quirk list and finds a matching entry
* with the same PCI SSID. When subdevice is 0, all subdevice
* values may match.
*
* Returns the matched entry pointer, or NULL if nothing matched.
*/
const struct snd_pci_quirk *
snd_pci_quirk_lookup_id(u16 vendor, u16 device,
const struct snd_pci_quirk *list)
{
const struct snd_pci_quirk *q;
for (q = list; q->subvendor; q++) {
if (q->subvendor != vendor)
continue;
if (!q->subdevice ||
(device & q->subdevice_mask) == q->subdevice)
return q;
}
return NULL;
}
EXPORT_SYMBOL(snd_pci_quirk_lookup_id);
/**
* snd_pci_quirk_lookup - look up a PCI SSID quirk list
* @pci: pci_dev handle
* @list: quirk list, terminated by a null entry
*
* Look through the given quirk list and finds a matching entry
* with the same PCI SSID. When subdevice is 0, all subdevice
* values may match.
*
* Returns the matched entry pointer, or NULL if nothing matched.
*/
const struct snd_pci_quirk *
snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list)
{
if (!pci)
return NULL;
return snd_pci_quirk_lookup_id(pci->subsystem_vendor,
pci->subsystem_device,
list);
}
EXPORT_SYMBOL(snd_pci_quirk_lookup);
#endif

13
sound/core/oss/Makefile Normal file
View file

@ -0,0 +1,13 @@
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
#
snd-mixer-oss-objs := mixer_oss.o
snd-pcm-oss-y := pcm_oss.o
snd-pcm-oss-$(CONFIG_SND_PCM_OSS_PLUGINS) += pcm_plugin.o \
io.o copy.o linear.o mulaw.o route.o rate.o
obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o
obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o

92
sound/core/oss/copy.c Normal file
View file

@ -0,0 +1,92 @@
/*
* Linear conversion Plug-In
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
static snd_pcm_sframes_t copy_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
unsigned int channel;
unsigned int nchannels;
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
return -ENXIO;
if (frames == 0)
return 0;
nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; channel++) {
if (snd_BUG_ON(src_channels->area.first % 8 ||
src_channels->area.step % 8))
return -ENXIO;
if (snd_BUG_ON(dst_channels->area.first % 8 ||
dst_channels->area.step % 8))
return -ENXIO;
if (!src_channels->enabled) {
if (dst_channels->wanted)
snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format);
dst_channels->enabled = 0;
continue;
}
dst_channels->enabled = 1;
snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format);
src_channels++;
dst_channels++;
}
return frames;
}
int snd_pcm_plugin_build_copy(struct snd_pcm_substream *plug,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin)
{
int err;
struct snd_pcm_plugin *plugin;
int width;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(src_format->format != dst_format->format))
return -ENXIO;
if (snd_BUG_ON(src_format->rate != dst_format->rate))
return -ENXIO;
if (snd_BUG_ON(src_format->channels != dst_format->channels))
return -ENXIO;
width = snd_pcm_format_physical_width(src_format->format);
if (snd_BUG_ON(width <= 0))
return -ENXIO;
err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format,
0, &plugin);
if (err < 0)
return err;
plugin->transfer = copy_transfer;
*r_plugin = plugin;
return 0;
}

141
sound/core/oss/io.c Normal file
View file

@ -0,0 +1,141 @@
/*
* PCM I/O Plug-In Interface
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "pcm_plugin.h"
#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1)
#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1)
#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1)
#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1)
/*
* Basic io plugin
*/
static snd_pcm_sframes_t io_playback_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
if (snd_BUG_ON(!plugin))
return -ENXIO;
if (snd_BUG_ON(!src_channels))
return -ENXIO;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
return pcm_write(plugin->plug, src_channels->area.addr, frames);
} else {
int channel, channels = plugin->dst_format.channels;
void **bufs = (void**)plugin->extra_data;
if (snd_BUG_ON(!bufs))
return -ENXIO;
for (channel = 0; channel < channels; channel++) {
if (src_channels[channel].enabled)
bufs[channel] = src_channels[channel].area.addr;
else
bufs[channel] = NULL;
}
return pcm_writev(plugin->plug, bufs, frames);
}
}
static snd_pcm_sframes_t io_capture_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
if (snd_BUG_ON(!plugin))
return -ENXIO;
if (snd_BUG_ON(!dst_channels))
return -ENXIO;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
return pcm_read(plugin->plug, dst_channels->area.addr, frames);
} else {
int channel, channels = plugin->dst_format.channels;
void **bufs = (void**)plugin->extra_data;
if (snd_BUG_ON(!bufs))
return -ENXIO;
for (channel = 0; channel < channels; channel++) {
if (dst_channels[channel].enabled)
bufs[channel] = dst_channels[channel].area.addr;
else
bufs[channel] = NULL;
}
return pcm_readv(plugin->plug, bufs, frames);
}
return 0;
}
static snd_pcm_sframes_t io_src_channels(struct snd_pcm_plugin *plugin,
snd_pcm_uframes_t frames,
struct snd_pcm_plugin_channel **channels)
{
int err;
unsigned int channel;
struct snd_pcm_plugin_channel *v;
err = snd_pcm_plugin_client_channels(plugin, frames, &v);
if (err < 0)
return err;
*channels = v;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v)
v->wanted = 1;
}
return frames;
}
int snd_pcm_plugin_build_io(struct snd_pcm_substream *plug,
struct snd_pcm_hw_params *params,
struct snd_pcm_plugin **r_plugin)
{
int err;
struct snd_pcm_plugin_format format;
struct snd_pcm_plugin *plugin;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(!plug || !params))
return -ENXIO;
format.format = params_format(params);
format.rate = params_rate(params);
format.channels = params_channels(params);
err = snd_pcm_plugin_build(plug, "I/O io",
&format, &format,
sizeof(void *) * format.channels,
&plugin);
if (err < 0)
return err;
plugin->access = params_access(params);
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
plugin->transfer = io_playback_transfer;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED)
plugin->client_channels = io_src_channels;
} else {
plugin->transfer = io_capture_transfer;
}
*r_plugin = plugin;
return 0;
}

178
sound/core/oss/linear.c Normal file
View file

@ -0,0 +1,178 @@
/*
* Linear conversion Plug-In
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>,
* Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
/*
* Basic linear conversion plugin
*/
struct linear_priv {
int cvt_endian; /* need endian conversion? */
unsigned int src_ofs; /* byte offset in source format */
unsigned int dst_ofs; /* byte soffset in destination format */
unsigned int copy_ofs; /* byte offset in temporary u32 data */
unsigned int dst_bytes; /* byte size of destination format */
unsigned int copy_bytes; /* bytes to copy per conversion */
unsigned int flip; /* MSB flip for signeness, done after endian conv */
};
static inline void do_convert(struct linear_priv *data,
unsigned char *dst, unsigned char *src)
{
unsigned int tmp = 0;
unsigned char *p = (unsigned char *)&tmp;
memcpy(p + data->copy_ofs, src + data->src_ofs, data->copy_bytes);
if (data->cvt_endian)
tmp = swab32(tmp);
tmp ^= data->flip;
memcpy(dst, p + data->dst_ofs, data->dst_bytes);
}
static void convert(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
struct linear_priv *data = (struct linear_priv *)plugin->extra_data;
int channel;
int nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
do_convert(data, dst, src);
src += src_step;
dst += dst_step;
}
}
}
static snd_pcm_sframes_t linear_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
return -ENXIO;
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
src_channels[channel].area.step % 8))
return -ENXIO;
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
dst_channels[channel].area.step % 8))
return -ENXIO;
}
}
#endif
convert(plugin, src_channels, dst_channels, frames);
return frames;
}
static void init_data(struct linear_priv *data,
snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
{
int src_le, dst_le, src_bytes, dst_bytes;
src_bytes = snd_pcm_format_width(src_format) / 8;
dst_bytes = snd_pcm_format_width(dst_format) / 8;
src_le = snd_pcm_format_little_endian(src_format) > 0;
dst_le = snd_pcm_format_little_endian(dst_format) > 0;
data->dst_bytes = dst_bytes;
data->cvt_endian = src_le != dst_le;
data->copy_bytes = src_bytes < dst_bytes ? src_bytes : dst_bytes;
if (src_le) {
data->copy_ofs = 4 - data->copy_bytes;
data->src_ofs = src_bytes - data->copy_bytes;
} else
data->src_ofs = snd_pcm_format_physical_width(src_format) / 8 -
src_bytes;
if (dst_le)
data->dst_ofs = 4 - data->dst_bytes;
else
data->dst_ofs = snd_pcm_format_physical_width(dst_format) / 8 -
dst_bytes;
if (snd_pcm_format_signed(src_format) !=
snd_pcm_format_signed(dst_format)) {
if (dst_le)
data->flip = (__force u32)cpu_to_le32(0x80000000);
else
data->flip = (__force u32)cpu_to_be32(0x80000000);
}
}
int snd_pcm_plugin_build_linear(struct snd_pcm_substream *plug,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin)
{
int err;
struct linear_priv *data;
struct snd_pcm_plugin *plugin;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(src_format->rate != dst_format->rate))
return -ENXIO;
if (snd_BUG_ON(src_format->channels != dst_format->channels))
return -ENXIO;
if (snd_BUG_ON(!snd_pcm_format_linear(src_format->format) ||
!snd_pcm_format_linear(dst_format->format)))
return -ENXIO;
err = snd_pcm_plugin_build(plug, "linear format conversion",
src_format, dst_format,
sizeof(struct linear_priv), &plugin);
if (err < 0)
return err;
data = (struct linear_priv *)plugin->extra_data;
init_data(data, src_format->format, dst_format->format);
plugin->transfer = linear_transfer;
*r_plugin = plugin;
return 0;
}

1424
sound/core/oss/mixer_oss.c Normal file

File diff suppressed because it is too large Load diff

344
sound/core/oss/mulaw.c Normal file
View file

@ -0,0 +1,344 @@
/*
* Mu-Law conversion Plug-In Interface
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
* Uros Bizjak <uros@kss-loka.si>
*
* Based on reference implementation by Sun Microsystems, Inc.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */
#define QUANT_MASK (0xf) /* Quantization field mask. */
#define NSEGS (8) /* Number of u-law segments. */
#define SEG_SHIFT (4) /* Left shift for segment number. */
#define SEG_MASK (0x70) /* Segment field mask. */
static inline int val_seg(int val)
{
int r = 0;
val >>= 7;
if (val & 0xf0) {
val >>= 4;
r += 4;
}
if (val & 0x0c) {
val >>= 2;
r += 2;
}
if (val & 0x02)
r += 1;
return r;
}
#define BIAS (0x84) /* Bias for linear code. */
/*
* linear2ulaw() - Convert a linear PCM value to u-law
*
* In order to simplify the encoding process, the original linear magnitude
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
* (33 - 8191). The result can be seen in the following encoding table:
*
* Biased Linear Input Code Compressed Code
* ------------------------ ---------------
* 00000001wxyza 000wxyz
* 0000001wxyzab 001wxyz
* 000001wxyzabc 010wxyz
* 00001wxyzabcd 011wxyz
* 0001wxyzabcde 100wxyz
* 001wxyzabcdef 101wxyz
* 01wxyzabcdefg 110wxyz
* 1wxyzabcdefgh 111wxyz
*
* Each biased linear code has a leading 1 which identifies the segment
* number. The value of the segment number is equal to 7 minus the number
* of leading 0's. The quantization interval is directly available as the
* four bits wxyz. * The trailing bits (a - h) are ignored.
*
* Ordinarily the complement of the resulting code word is used for
* transmission, and so the code word is complemented before it is returned.
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */
{
int mask;
int seg;
unsigned char uval;
/* Get the sign and the magnitude of the value. */
if (pcm_val < 0) {
pcm_val = BIAS - pcm_val;
mask = 0x7F;
} else {
pcm_val += BIAS;
mask = 0xFF;
}
if (pcm_val > 0x7FFF)
pcm_val = 0x7FFF;
/* Convert the scaled magnitude to segment number. */
seg = val_seg(pcm_val);
/*
* Combine the sign, segment, quantization bits;
* and complement the code word.
*/
uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
return uval ^ mask;
}
/*
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
static int ulaw2linear(unsigned char u_val)
{
int t;
/* Complement to obtain normal u-law value. */
u_val = ~u_val;
/*
* Extract and bias the quantization bits. Then
* shift up by the segment number and subtract out the bias.
*/
t = ((u_val & QUANT_MASK) << 3) + BIAS;
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}
/*
* Basic Mu-Law plugin
*/
typedef void (*mulaw_f)(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames);
struct mulaw_priv {
mulaw_f func;
int cvt_endian; /* need endian conversion? */
unsigned int native_ofs; /* byte offset in native format */
unsigned int copy_ofs; /* byte offset in s16 format */
unsigned int native_bytes; /* byte size of the native format */
unsigned int copy_bytes; /* bytes to copy per conversion */
u16 flip; /* MSB flip for signedness, done after endian conversion */
};
static inline void cvt_s16_to_native(struct mulaw_priv *data,
unsigned char *dst, u16 sample)
{
sample ^= data->flip;
if (data->cvt_endian)
sample = swab16(sample);
if (data->native_bytes > data->copy_bytes)
memset(dst, 0, data->native_bytes);
memcpy(dst + data->native_ofs, (char *)&sample + data->copy_ofs,
data->copy_bytes);
}
static void mulaw_decode(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
int channel;
int nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
signed short sample = ulaw2linear(*src);
cvt_s16_to_native(data, dst, sample);
src += src_step;
dst += dst_step;
}
}
}
static inline signed short cvt_native_to_s16(struct mulaw_priv *data,
unsigned char *src)
{
u16 sample = 0;
memcpy((char *)&sample + data->copy_ofs, src + data->native_ofs,
data->copy_bytes);
if (data->cvt_endian)
sample = swab16(sample);
sample ^= data->flip;
return (signed short)sample;
}
static void mulaw_encode(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
int channel;
int nchannels = plugin->src_format.channels;
for (channel = 0; channel < nchannels; ++channel) {
char *src;
char *dst;
int src_step, dst_step;
snd_pcm_uframes_t frames1;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = src_channels[channel].area.addr + src_channels[channel].area.first / 8;
dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8;
src_step = src_channels[channel].area.step / 8;
dst_step = dst_channels[channel].area.step / 8;
frames1 = frames;
while (frames1-- > 0) {
signed short sample = cvt_native_to_s16(data, src);
*dst = linear2ulaw(sample);
src += src_step;
dst += dst_step;
}
}
}
static snd_pcm_sframes_t mulaw_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
struct mulaw_priv *data;
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
return -ENXIO;
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
src_channels[channel].area.step % 8))
return -ENXIO;
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
dst_channels[channel].area.step % 8))
return -ENXIO;
}
}
#endif
data = (struct mulaw_priv *)plugin->extra_data;
data->func(plugin, src_channels, dst_channels, frames);
return frames;
}
static void init_data(struct mulaw_priv *data, snd_pcm_format_t format)
{
#ifdef SNDRV_LITTLE_ENDIAN
data->cvt_endian = snd_pcm_format_big_endian(format) > 0;
#else
data->cvt_endian = snd_pcm_format_little_endian(format) > 0;
#endif
if (!snd_pcm_format_signed(format))
data->flip = 0x8000;
data->native_bytes = snd_pcm_format_physical_width(format) / 8;
data->copy_bytes = data->native_bytes < 2 ? 1 : 2;
if (snd_pcm_format_little_endian(format)) {
data->native_ofs = data->native_bytes - data->copy_bytes;
data->copy_ofs = 2 - data->copy_bytes;
} else {
/* S24 in 4bytes need an 1 byte offset */
data->native_ofs = data->native_bytes -
snd_pcm_format_width(format) / 8;
}
}
int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *plug,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin)
{
int err;
struct mulaw_priv *data;
struct snd_pcm_plugin *plugin;
struct snd_pcm_plugin_format *format;
mulaw_f func;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(src_format->rate != dst_format->rate))
return -ENXIO;
if (snd_BUG_ON(src_format->channels != dst_format->channels))
return -ENXIO;
if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
format = src_format;
func = mulaw_encode;
}
else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) {
format = dst_format;
func = mulaw_decode;
}
else {
snd_BUG();
return -EINVAL;
}
if (snd_BUG_ON(!snd_pcm_format_linear(format->format)))
return -ENXIO;
err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
src_format, dst_format,
sizeof(struct mulaw_priv), &plugin);
if (err < 0)
return err;
data = (struct mulaw_priv *)plugin->extra_data;
data->func = func;
init_data(data, format->format);
plugin->transfer = mulaw_transfer;
*r_plugin = plugin;
return 0;
}

3112
sound/core/oss/pcm_oss.c Normal file

File diff suppressed because it is too large Load diff

758
sound/core/oss/pcm_plugin.c Normal file
View file

@ -0,0 +1,758 @@
/*
* PCM Plug-In shared (kernel/library) code
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#if 0
#define PLUGIN_DEBUG
#endif
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "pcm_plugin.h"
#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
/*
* because some cards might have rates "very close", we ignore
* all "resampling" requests within +-5%
*/
static int rate_match(unsigned int src_rate, unsigned int dst_rate)
{
unsigned int low = (src_rate * 95) / 100;
unsigned int high = (src_rate * 105) / 100;
return dst_rate >= low && dst_rate <= high;
}
static int snd_pcm_plugin_alloc(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
{
struct snd_pcm_plugin_format *format;
ssize_t width;
size_t size;
unsigned int channel;
struct snd_pcm_plugin_channel *c;
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
format = &plugin->src_format;
} else {
format = &plugin->dst_format;
}
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
return width;
size = frames * format->channels * width;
if (snd_BUG_ON(size % 8))
return -ENXIO;
size /= 8;
if (plugin->buf_frames < frames) {
vfree(plugin->buf);
plugin->buf = vmalloc(size);
plugin->buf_frames = frames;
}
if (!plugin->buf) {
plugin->buf_frames = 0;
return -ENOMEM;
}
c = plugin->buf_channels;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf;
c->area.first = channel * width;
c->area.step = format->channels * width;
}
} else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
if (snd_BUG_ON(size % format->channels))
return -EINVAL;
size /= format->channels;
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf + (channel * size);
c->area.first = 0;
c->area.step = width;
}
} else
return -EINVAL;
return 0;
}
int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames)
{
int err;
if (snd_BUG_ON(!snd_pcm_plug_first(plug)))
return -ENXIO;
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug);
while (plugin->next) {
if (plugin->dst_frames)
frames = plugin->dst_frames(plugin, frames);
if (snd_BUG_ON(frames <= 0))
return -ENXIO;
plugin = plugin->next;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
} else {
struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug);
while (plugin->prev) {
if (plugin->src_frames)
frames = plugin->src_frames(plugin, frames);
if (snd_BUG_ON(frames <= 0))
return -ENXIO;
plugin = plugin->prev;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
snd_pcm_uframes_t frames,
struct snd_pcm_plugin_channel **channels)
{
*channels = plugin->buf_channels;
return frames;
}
int snd_pcm_plugin_build(struct snd_pcm_substream *plug,
const char *name,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
size_t extra,
struct snd_pcm_plugin **ret)
{
struct snd_pcm_plugin *plugin;
unsigned int channels;
if (snd_BUG_ON(!plug))
return -ENXIO;
if (snd_BUG_ON(!src_format || !dst_format))
return -ENXIO;
plugin = kzalloc(sizeof(*plugin) + extra, GFP_KERNEL);
if (plugin == NULL)
return -ENOMEM;
plugin->name = name;
plugin->plug = plug;
plugin->stream = snd_pcm_plug_stream(plug);
plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
plugin->src_format = *src_format;
plugin->src_width = snd_pcm_format_physical_width(src_format->format);
snd_BUG_ON(plugin->src_width <= 0);
plugin->dst_format = *dst_format;
plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
snd_BUG_ON(plugin->dst_width <= 0);
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
channels = src_format->channels;
else
channels = dst_format->channels;
plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
if (plugin->buf_channels == NULL) {
snd_pcm_plugin_free(plugin);
return -ENOMEM;
}
plugin->client_channels = snd_pcm_plugin_client_channels;
*ret = plugin;
return 0;
}
int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin)
{
if (! plugin)
return 0;
if (plugin->private_free)
plugin->private_free(plugin);
kfree(plugin->buf_channels);
vfree(plugin->buf);
kfree(plugin);
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t drv_frames)
{
struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
int stream;
if (snd_BUG_ON(!plug))
return -ENXIO;
if (drv_frames == 0)
return 0;
stream = snd_pcm_plug_stream(plug);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_last(plug);
while (plugin && drv_frames > 0) {
plugin_prev = plugin->prev;
if (plugin->src_frames)
drv_frames = plugin->src_frames(plugin, drv_frames);
plugin = plugin_prev;
}
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
plugin = snd_pcm_plug_first(plug);
while (plugin && drv_frames > 0) {
plugin_next = plugin->next;
if (plugin->dst_frames)
drv_frames = plugin->dst_frames(plugin, drv_frames);
plugin = plugin_next;
}
} else
snd_BUG();
return drv_frames;
}
snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t clt_frames)
{
struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
snd_pcm_sframes_t frames;
int stream;
if (snd_BUG_ON(!plug))
return -ENXIO;
if (clt_frames == 0)
return 0;
frames = clt_frames;
stream = snd_pcm_plug_stream(plug);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
plugin_next = plugin->next;
if (plugin->dst_frames) {
frames = plugin->dst_frames(plugin, frames);
if (frames < 0)
return frames;
}
plugin = plugin_next;
}
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
plugin = snd_pcm_plug_last(plug);
while (plugin) {
plugin_prev = plugin->prev;
if (plugin->src_frames) {
frames = plugin->src_frames(plugin, frames);
if (frames < 0)
return frames;
}
plugin = plugin_prev;
}
} else
snd_BUG();
return frames;
}
static int snd_pcm_plug_formats(struct snd_mask *mask, snd_pcm_format_t format)
{
struct snd_mask formats = *mask;
u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW);
if (formats.bits[0] & (u32)linfmts)
formats.bits[0] |= (u32)linfmts;
if (formats.bits[1] & (u32)(linfmts >> 32))
formats.bits[1] |= (u32)(linfmts >> 32);
return snd_mask_test(&formats, (__force int)format);
}
static snd_pcm_format_t preferred_formats[] = {
SNDRV_PCM_FORMAT_S16_LE,
SNDRV_PCM_FORMAT_S16_BE,
SNDRV_PCM_FORMAT_U16_LE,
SNDRV_PCM_FORMAT_U16_BE,
SNDRV_PCM_FORMAT_S24_3LE,
SNDRV_PCM_FORMAT_S24_3BE,
SNDRV_PCM_FORMAT_U24_3LE,
SNDRV_PCM_FORMAT_U24_3BE,
SNDRV_PCM_FORMAT_S24_LE,
SNDRV_PCM_FORMAT_S24_BE,
SNDRV_PCM_FORMAT_U24_LE,
SNDRV_PCM_FORMAT_U24_BE,
SNDRV_PCM_FORMAT_S32_LE,
SNDRV_PCM_FORMAT_S32_BE,
SNDRV_PCM_FORMAT_U32_LE,
SNDRV_PCM_FORMAT_U32_BE,
SNDRV_PCM_FORMAT_S8,
SNDRV_PCM_FORMAT_U8
};
snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format,
struct snd_mask *format_mask)
{
int i;
if (snd_mask_test(format_mask, (__force int)format))
return format;
if (!snd_pcm_plug_formats(format_mask, format))
return (__force snd_pcm_format_t)-EINVAL;
if (snd_pcm_format_linear(format)) {
unsigned int width = snd_pcm_format_width(format);
int unsignd = snd_pcm_format_unsigned(format) > 0;
int big = snd_pcm_format_big_endian(format) > 0;
unsigned int badness, best = -1;
snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1;
for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) {
snd_pcm_format_t f = preferred_formats[i];
unsigned int w;
if (!snd_mask_test(format_mask, (__force int)f))
continue;
w = snd_pcm_format_width(f);
if (w >= width)
badness = w - width;
else
badness = width - w + 32;
badness += snd_pcm_format_unsigned(f) != unsignd;
badness += snd_pcm_format_big_endian(f) != big;
if (badness < best) {
best_format = f;
best = badness;
}
}
if ((__force int)best_format >= 0)
return best_format;
else
return (__force snd_pcm_format_t)-EINVAL;
} else {
switch (format) {
case SNDRV_PCM_FORMAT_MU_LAW:
for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
snd_pcm_format_t format1 = preferred_formats[i];
if (snd_mask_test(format_mask, (__force int)format1))
return format1;
}
default:
return (__force snd_pcm_format_t)-EINVAL;
}
}
}
int snd_pcm_plug_format_plugins(struct snd_pcm_substream *plug,
struct snd_pcm_hw_params *params,
struct snd_pcm_hw_params *slave_params)
{
struct snd_pcm_plugin_format tmpformat;
struct snd_pcm_plugin_format dstformat;
struct snd_pcm_plugin_format srcformat;
snd_pcm_access_t src_access, dst_access;
struct snd_pcm_plugin *plugin = NULL;
int err;
int stream = snd_pcm_plug_stream(plug);
int slave_interleaved = (params_channels(slave_params) == 1 ||
params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
switch (stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
dstformat.format = params_format(slave_params);
dstformat.rate = params_rate(slave_params);
dstformat.channels = params_channels(slave_params);
srcformat.format = params_format(params);
srcformat.rate = params_rate(params);
srcformat.channels = params_channels(params);
src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
break;
case SNDRV_PCM_STREAM_CAPTURE:
dstformat.format = params_format(params);
dstformat.rate = params_rate(params);
dstformat.channels = params_channels(params);
srcformat.format = params_format(slave_params);
srcformat.rate = params_rate(slave_params);
srcformat.channels = params_channels(slave_params);
src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
break;
default:
snd_BUG();
return -EINVAL;
}
tmpformat = srcformat;
pdprintf("srcformat: format=%i, rate=%i, channels=%i\n",
srcformat.format,
srcformat.rate,
srcformat.channels);
pdprintf("dstformat: format=%i, rate=%i, channels=%i\n",
dstformat.format,
dstformat.rate,
dstformat.channels);
/* Format change (linearization) */
if (! rate_match(srcformat.rate, dstformat.rate) &&
! snd_pcm_format_linear(srcformat.format)) {
if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW)
return -EINVAL;
tmpformat.format = SNDRV_PCM_FORMAT_S16;
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels reduction */
if (srcformat.channels > dstformat.channels) {
tmpformat.channels = dstformat.channels;
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* rate resampling */
if (!rate_match(srcformat.rate, dstformat.rate)) {
if (srcformat.format != SNDRV_PCM_FORMAT_S16) {
/* convert to S16 for resampling */
tmpformat.format = SNDRV_PCM_FORMAT_S16;
err = snd_pcm_plugin_build_linear(plug,
&srcformat, &tmpformat,
&plugin);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
tmpformat.rate = dstformat.rate;
err = snd_pcm_plugin_build_rate(plug,
&srcformat, &tmpformat,
&plugin);
pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* format change */
if (srcformat.format != dstformat.format) {
tmpformat.format = dstformat.format;
if (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW ||
tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
}
else if (snd_pcm_format_linear(srcformat.format) &&
snd_pcm_format_linear(tmpformat.format)) {
err = snd_pcm_plugin_build_linear(plug,
&srcformat, &tmpformat,
&plugin);
}
else
return -EINVAL;
pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels extension */
if (srcformat.channels < dstformat.channels) {
tmpformat.channels = dstformat.channels;
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* de-interleave */
if (src_access != dst_access) {
err = snd_pcm_plugin_build_copy(plug,
&srcformat,
&tmpformat,
&plugin);
pdprintf("interleave change (copy: returns %i)\n", err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *plug,
char *buf,
snd_pcm_uframes_t count,
struct snd_pcm_plugin_channel **channels)
{
struct snd_pcm_plugin *plugin;
struct snd_pcm_plugin_channel *v;
struct snd_pcm_plugin_format *format;
int width, nchannels, channel;
int stream = snd_pcm_plug_stream(plug);
if (snd_BUG_ON(!buf))
return -ENXIO;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_first(plug);
format = &plugin->src_format;
} else {
plugin = snd_pcm_plug_last(plug);
format = &plugin->dst_format;
}
v = plugin->buf_channels;
*channels = v;
if ((width = snd_pcm_format_physical_width(format->format)) < 0)
return width;
nchannels = format->channels;
if (snd_BUG_ON(plugin->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
format->channels > 1))
return -ENXIO;
for (channel = 0; channel < nchannels; channel++, v++) {
v->frames = count;
v->enabled = 1;
v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
v->area.addr = buf;
v->area.first = channel * width;
v->area.step = nchannels * width;
}
return count;
}
snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size)
{
struct snd_pcm_plugin *plugin, *next;
struct snd_pcm_plugin_channel *dst_channels;
int err;
snd_pcm_sframes_t frames = size;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
if ((next = plugin->next) != NULL) {
snd_pcm_sframes_t frames1 = frames;
if (plugin->dst_frames)
frames1 = plugin->dst_frames(plugin, frames);
if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) {
return err;
}
if (err != frames1) {
frames = err;
if (plugin->src_frames)
frames = plugin->src_frames(plugin, frames1);
}
} else
dst_channels = NULL;
pdprintf("write plugin: %s, %li\n", plugin->name, frames);
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
return frames;
src_channels = dst_channels;
plugin = next;
}
return snd_pcm_plug_client_size(plug, frames);
}
snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size)
{
struct snd_pcm_plugin *plugin, *next;
struct snd_pcm_plugin_channel *src_channels, *dst_channels;
snd_pcm_sframes_t frames = size;
int err;
frames = snd_pcm_plug_slave_size(plug, frames);
if (frames < 0)
return frames;
src_channels = NULL;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
if ((next = plugin->next) != NULL) {
if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) {
return err;
}
frames = err;
} else {
dst_channels = dst_channels_final;
}
pdprintf("read plugin: %s, %li\n", plugin->name, frames);
if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0)
return frames;
plugin = next;
src_channels = dst_channels;
}
return frames;
}
int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
size_t samples, snd_pcm_format_t format)
{
/* FIXME: sub byte resolution and odd dst_offset */
unsigned char *dst;
unsigned int dst_step;
int width;
const unsigned char *silence;
if (!dst_area->addr)
return 0;
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (dst_area->step == (unsigned int) width && width >= 8)
return snd_pcm_format_set_silence(format, dst, samples);
silence = snd_pcm_format_silence_64(format);
if (! silence)
return -EINVAL;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
if (dstbit)
*dst &= 0xf0;
else
*dst &= 0x0f;
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, silence, width);
dst += dst_step;
}
}
return 0;
}
int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_area, size_t src_offset,
const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
size_t samples, snd_pcm_format_t format)
{
/* FIXME: sub byte resolution and odd dst_offset */
char *src, *dst;
int width;
int src_step, dst_step;
src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
if (!src_area->addr)
return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
if (!dst_area->addr)
return 0;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (src_area->step == (unsigned int) width &&
dst_area->step == (unsigned int) width && width >= 8) {
size_t bytes = samples * width / 8;
memcpy(dst, src, bytes);
return 0;
}
src_step = src_area->step / 8;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int srcbit = src_area->first % 8;
int srcbit_step = src_area->step % 8;
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
unsigned char srcval;
if (srcbit)
srcval = *src & 0x0f;
else
srcval = (*src & 0xf0) >> 4;
if (dstbit)
*dst = (*dst & 0xf0) | srcval;
else
*dst = (*dst & 0x0f) | (srcval << 4);
src += src_step;
srcbit += srcbit_step;
if (srcbit == 8) {
src++;
srcbit = 0;
}
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, src, width);
src += src_step;
dst += dst_step;
}
}
return 0;
}

185
sound/core/oss/pcm_plugin.h Normal file
View file

@ -0,0 +1,185 @@
#ifndef __PCM_PLUGIN_H
#define __PCM_PLUGIN_H
/*
* Digital Audio (Plugin interface) abstract layer
* Copyright (c) by 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
*
*/
#ifdef CONFIG_SND_PCM_OSS_PLUGINS
#define snd_pcm_plug_stream(plug) ((plug)->stream)
enum snd_pcm_plugin_action {
INIT = 0,
PREPARE = 1,
};
struct snd_pcm_channel_area {
void *addr; /* base address of channel samples */
unsigned int first; /* offset to first sample in bits */
unsigned int step; /* samples distance in bits */
};
struct snd_pcm_plugin_channel {
void *aptr; /* pointer to the allocated area */
struct snd_pcm_channel_area area;
snd_pcm_uframes_t frames; /* allocated frames */
unsigned int enabled:1; /* channel need to be processed */
unsigned int wanted:1; /* channel is wanted */
};
struct snd_pcm_plugin_format {
snd_pcm_format_t format;
unsigned int rate;
unsigned int channels;
};
struct snd_pcm_plugin {
const char *name; /* plug-in name */
int stream;
struct snd_pcm_plugin_format src_format; /* source format */
struct snd_pcm_plugin_format dst_format; /* destination format */
int src_width; /* sample width in bits */
int dst_width; /* sample width in bits */
snd_pcm_access_t access;
snd_pcm_sframes_t (*src_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t dst_frames);
snd_pcm_sframes_t (*dst_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t src_frames);
snd_pcm_sframes_t (*client_channels)(struct snd_pcm_plugin *plugin,
snd_pcm_uframes_t frames,
struct snd_pcm_plugin_channel **channels);
snd_pcm_sframes_t (*transfer)(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames);
int (*action)(struct snd_pcm_plugin *plugin,
enum snd_pcm_plugin_action action,
unsigned long data);
struct snd_pcm_plugin *prev;
struct snd_pcm_plugin *next;
struct snd_pcm_substream *plug;
void *private_data;
void (*private_free)(struct snd_pcm_plugin *plugin);
char *buf;
snd_pcm_uframes_t buf_frames;
struct snd_pcm_plugin_channel *buf_channels;
char extra_data[0];
};
int snd_pcm_plugin_build(struct snd_pcm_substream *handle,
const char *name,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
size_t extra,
struct snd_pcm_plugin **ret);
int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin);
int snd_pcm_plugin_clear(struct snd_pcm_plugin **first);
int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames);
snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size);
snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size);
#define FULL ROUTE_PLUGIN_RESOLUTION
#define HALF ROUTE_PLUGIN_RESOLUTION / 2
int snd_pcm_plugin_build_io(struct snd_pcm_substream *handle,
struct snd_pcm_hw_params *params,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plugin_build_linear(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plugin_build_rate(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plugin_build_route(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plugin_build_copy(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin);
int snd_pcm_plug_format_plugins(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_pcm_hw_params *slave_params);
snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format,
struct snd_mask *format_mask);
int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin);
snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_channel *src_channels,
snd_pcm_uframes_t size);
snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *handle,
struct snd_pcm_plugin_channel *dst_channels_final,
snd_pcm_uframes_t size);
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *handle,
char *buf, snd_pcm_uframes_t count,
struct snd_pcm_plugin_channel **channels);
snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
snd_pcm_uframes_t frames,
struct snd_pcm_plugin_channel **channels);
int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_channel,
size_t dst_offset,
size_t samples, snd_pcm_format_t format);
int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_channel,
size_t src_offset,
const struct snd_pcm_channel_area *dst_channel,
size_t dst_offset,
size_t samples, snd_pcm_format_t format);
void *snd_pcm_plug_buf_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t size);
void snd_pcm_plug_buf_unlock(struct snd_pcm_substream *plug, void *ptr);
snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream,
const char *ptr, snd_pcm_uframes_t size,
int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream,
char *ptr, snd_pcm_uframes_t size, int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream,
void **bufs, snd_pcm_uframes_t frames,
int in_kernel);
snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream,
void **bufs, snd_pcm_uframes_t frames,
int in_kernel);
#else
static inline snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size) { return drv_size; }
static inline snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size) { return clt_size; }
static inline int snd_pcm_plug_slave_format(int format, struct snd_mask *format_mask) { return format; }
#endif
#ifdef PLUGIN_DEBUG
#define pdprintf(fmt, args...) printk(KERN_DEBUG "plugin: " fmt, ##args)
#else
#define pdprintf(fmt, args...)
#endif
#endif /* __PCM_PLUGIN_H */

348
sound/core/oss/rate.c Normal file
View file

@ -0,0 +1,348 @@
/*
* Rate conversion Plug-In
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
#define SHIFT 11
#define BITS (1<<SHIFT)
#define R_MASK (BITS-1)
/*
* Basic rate conversion plugin
*/
struct rate_channel {
signed short last_S1;
signed short last_S2;
};
typedef void (*rate_f)(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
int src_frames, int dst_frames);
struct rate_priv {
unsigned int pitch;
unsigned int pos;
rate_f func;
snd_pcm_sframes_t old_src_frames, old_dst_frames;
struct rate_channel channels[0];
};
static void rate_init(struct snd_pcm_plugin *plugin)
{
unsigned int channel;
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
data->pos = 0;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
data->channels[channel].last_S1 = 0;
data->channels[channel].last_S2 = 0;
}
}
static void resample_expand(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
int src_frames, int dst_frames)
{
unsigned int pos = 0;
signed int val;
signed short S1, S2;
signed short *src, *dst;
unsigned int channel;
int src_step, dst_step;
int src_frames1, dst_frames1;
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
struct rate_channel *rchannels = data->channels;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
pos = data->pos;
S1 = rchannels->last_S1;
S2 = rchannels->last_S2;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = (signed short *)src_channels[channel].area.addr +
src_channels[channel].area.first / 8 / 2;
dst = (signed short *)dst_channels[channel].area.addr +
dst_channels[channel].area.first / 8 / 2;
src_step = src_channels[channel].area.step / 8 / 2;
dst_step = dst_channels[channel].area.step / 8 / 2;
src_frames1 = src_frames;
dst_frames1 = dst_frames;
while (dst_frames1-- > 0) {
if (pos & ~R_MASK) {
pos &= R_MASK;
S1 = S2;
if (src_frames1-- > 0) {
S2 = *src;
src += src_step;
}
}
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
if (val < -32768)
val = -32768;
else if (val > 32767)
val = 32767;
*dst = val;
dst += dst_step;
pos += data->pitch;
}
rchannels->last_S1 = S1;
rchannels->last_S2 = S2;
rchannels++;
}
data->pos = pos;
}
static void resample_shrink(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
int src_frames, int dst_frames)
{
unsigned int pos = 0;
signed int val;
signed short S1, S2;
signed short *src, *dst;
unsigned int channel;
int src_step, dst_step;
int src_frames1, dst_frames1;
struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
struct rate_channel *rchannels = data->channels;
for (channel = 0; channel < plugin->src_format.channels; ++channel) {
pos = data->pos;
S1 = rchannels->last_S1;
S2 = rchannels->last_S2;
if (!src_channels[channel].enabled) {
if (dst_channels[channel].wanted)
snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format);
dst_channels[channel].enabled = 0;
continue;
}
dst_channels[channel].enabled = 1;
src = (signed short *)src_channels[channel].area.addr +
src_channels[channel].area.first / 8 / 2;
dst = (signed short *)dst_channels[channel].area.addr +
dst_channels[channel].area.first / 8 / 2;
src_step = src_channels[channel].area.step / 8 / 2;
dst_step = dst_channels[channel].area.step / 8 / 2;
src_frames1 = src_frames;
dst_frames1 = dst_frames;
while (dst_frames1 > 0) {
S1 = S2;
if (src_frames1-- > 0) {
S2 = *src;
src += src_step;
}
if (pos & ~R_MASK) {
pos &= R_MASK;
val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
if (val < -32768)
val = -32768;
else if (val > 32767)
val = 32767;
*dst = val;
dst += dst_step;
dst_frames1--;
}
pos += data->pitch;
}
rchannels->last_S1 = S1;
rchannels->last_S2 = S2;
rchannels++;
}
data->pos = pos;
}
static snd_pcm_sframes_t rate_src_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
{
struct rate_priv *data;
snd_pcm_sframes_t res;
if (snd_BUG_ON(!plugin))
return -ENXIO;
if (frames == 0)
return 0;
data = (struct rate_priv *)plugin->extra_data;
if (plugin->src_format.rate < plugin->dst_format.rate) {
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
} else {
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
}
if (data->old_src_frames > 0) {
snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames;
while (data->old_src_frames < frames1) {
frames1 >>= 1;
res1 <<= 1;
}
while (data->old_src_frames > frames1) {
frames1 <<= 1;
res1 >>= 1;
}
if (data->old_src_frames == frames1)
return res1;
}
data->old_src_frames = frames;
data->old_dst_frames = res;
return res;
}
static snd_pcm_sframes_t rate_dst_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
{
struct rate_priv *data;
snd_pcm_sframes_t res;
if (snd_BUG_ON(!plugin))
return -ENXIO;
if (frames == 0)
return 0;
data = (struct rate_priv *)plugin->extra_data;
if (plugin->src_format.rate < plugin->dst_format.rate) {
res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch);
} else {
res = (((frames * data->pitch) + (BITS/2)) >> SHIFT);
}
if (data->old_dst_frames > 0) {
snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames;
while (data->old_dst_frames < frames1) {
frames1 >>= 1;
res1 <<= 1;
}
while (data->old_dst_frames > frames1) {
frames1 <<= 1;
res1 >>= 1;
}
if (data->old_dst_frames == frames1)
return res1;
}
data->old_dst_frames = frames;
data->old_src_frames = res;
return res;
}
static snd_pcm_sframes_t rate_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
snd_pcm_uframes_t dst_frames;
struct rate_priv *data;
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
return -ENXIO;
if (frames == 0)
return 0;
#ifdef CONFIG_SND_DEBUG
{
unsigned int channel;
for (channel = 0; channel < plugin->src_format.channels; channel++) {
if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
src_channels[channel].area.step % 8))
return -ENXIO;
if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
dst_channels[channel].area.step % 8))
return -ENXIO;
}
}
#endif
dst_frames = rate_dst_frames(plugin, frames);
if (dst_frames > dst_channels[0].frames)
dst_frames = dst_channels[0].frames;
data = (struct rate_priv *)plugin->extra_data;
data->func(plugin, src_channels, dst_channels, frames, dst_frames);
return dst_frames;
}
static int rate_action(struct snd_pcm_plugin *plugin,
enum snd_pcm_plugin_action action,
unsigned long udata)
{
if (snd_BUG_ON(!plugin))
return -ENXIO;
switch (action) {
case INIT:
case PREPARE:
rate_init(plugin);
break;
default:
break;
}
return 0; /* silenty ignore other actions */
}
int snd_pcm_plugin_build_rate(struct snd_pcm_substream *plug,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin)
{
int err;
struct rate_priv *data;
struct snd_pcm_plugin *plugin;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(src_format->channels != dst_format->channels))
return -ENXIO;
if (snd_BUG_ON(src_format->channels <= 0))
return -ENXIO;
if (snd_BUG_ON(src_format->format != SNDRV_PCM_FORMAT_S16))
return -ENXIO;
if (snd_BUG_ON(dst_format->format != SNDRV_PCM_FORMAT_S16))
return -ENXIO;
if (snd_BUG_ON(src_format->rate == dst_format->rate))
return -ENXIO;
err = snd_pcm_plugin_build(plug, "rate conversion",
src_format, dst_format,
sizeof(struct rate_priv) +
src_format->channels * sizeof(struct rate_channel),
&plugin);
if (err < 0)
return err;
data = (struct rate_priv *)plugin->extra_data;
if (src_format->rate < dst_format->rate) {
data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate;
data->func = resample_expand;
} else {
data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate;
data->func = resample_shrink;
}
data->pos = 0;
rate_init(plugin);
data->old_src_frames = data->old_dst_frames = 0;
plugin->transfer = rate_transfer;
plugin->src_frames = rate_src_frames;
plugin->dst_frames = rate_dst_frames;
plugin->action = rate_action;
*r_plugin = plugin;
return 0;
}

109
sound/core/oss/route.c Normal file
View file

@ -0,0 +1,109 @@
/*
* Route Plug-In
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcm_plugin.h"
static void zero_areas(struct snd_pcm_plugin_channel *dvp, int ndsts,
snd_pcm_uframes_t frames, snd_pcm_format_t format)
{
int dst = 0;
for (; dst < ndsts; ++dst) {
if (dvp->wanted)
snd_pcm_area_silence(&dvp->area, 0, frames, format);
dvp->enabled = 0;
dvp++;
}
}
static inline void copy_area(const struct snd_pcm_plugin_channel *src_channel,
struct snd_pcm_plugin_channel *dst_channel,
snd_pcm_uframes_t frames, snd_pcm_format_t format)
{
dst_channel->enabled = 1;
snd_pcm_area_copy(&src_channel->area, 0, &dst_channel->area, 0, frames, format);
}
static snd_pcm_sframes_t route_transfer(struct snd_pcm_plugin *plugin,
const struct snd_pcm_plugin_channel *src_channels,
struct snd_pcm_plugin_channel *dst_channels,
snd_pcm_uframes_t frames)
{
int nsrcs, ndsts, dst;
struct snd_pcm_plugin_channel *dvp;
snd_pcm_format_t format;
if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
return -ENXIO;
if (frames == 0)
return 0;
nsrcs = plugin->src_format.channels;
ndsts = plugin->dst_format.channels;
format = plugin->dst_format.format;
dvp = dst_channels;
if (nsrcs <= 1) {
/* expand to all channels */
for (dst = 0; dst < ndsts; ++dst) {
copy_area(src_channels, dvp, frames, format);
dvp++;
}
return frames;
}
for (dst = 0; dst < ndsts && dst < nsrcs; ++dst) {
copy_area(src_channels, dvp, frames, format);
dvp++;
src_channels++;
}
if (dst < ndsts)
zero_areas(dvp, ndsts - dst, frames, format);
return frames;
}
int snd_pcm_plugin_build_route(struct snd_pcm_substream *plug,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
struct snd_pcm_plugin **r_plugin)
{
struct snd_pcm_plugin *plugin;
int err;
if (snd_BUG_ON(!r_plugin))
return -ENXIO;
*r_plugin = NULL;
if (snd_BUG_ON(src_format->rate != dst_format->rate))
return -ENXIO;
if (snd_BUG_ON(src_format->format != dst_format->format))
return -ENXIO;
err = snd_pcm_plugin_build(plug, "route conversion",
src_format, dst_format, 0, &plugin);
if (err < 0)
return err;
plugin->transfer = route_transfer;
*r_plugin = plugin;
return 0;
}

1257
sound/core/pcm.c Normal file

File diff suppressed because it is too large Load diff

542
sound/core/pcm_compat.c Normal file
View file

@ -0,0 +1,542 @@
/*
* 32bit -> 64bit ioctl wrapper for PCM API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from pcm_native.c */
#include <linux/compat.h>
#include <linux/slab.h>
static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *substream,
s32 __user *src)
{
snd_pcm_sframes_t delay;
mm_segment_t fs;
int err;
fs = snd_enter_user();
err = snd_pcm_delay(substream, &delay);
snd_leave_user(fs);
if (err < 0)
return err;
if (put_user(delay, src))
return -EFAULT;
return err;
}
static int snd_pcm_ioctl_rewind_compat(struct snd_pcm_substream *substream,
u32 __user *src)
{
snd_pcm_uframes_t frames;
int err;
if (get_user(frames, src))
return -EFAULT;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_playback_rewind(substream, frames);
else
err = snd_pcm_capture_rewind(substream, frames);
if (put_user(err, src))
return -EFAULT;
return err < 0 ? err : 0;
}
static int snd_pcm_ioctl_forward_compat(struct snd_pcm_substream *substream,
u32 __user *src)
{
snd_pcm_uframes_t frames;
int err;
if (get_user(frames, src))
return -EFAULT;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_playback_forward(substream, frames);
else
err = snd_pcm_capture_forward(substream, frames);
if (put_user(err, src))
return -EFAULT;
return err < 0 ? err : 0;
}
struct snd_pcm_hw_params32 {
u32 flags;
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
struct snd_mask mres[5]; /* reserved masks */
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
struct snd_interval ires[9]; /* reserved intervals */
u32 rmask;
u32 cmask;
u32 info;
u32 msbits;
u32 rate_num;
u32 rate_den;
u32 fifo_size;
unsigned char reserved[64];
};
struct snd_pcm_sw_params32 {
s32 tstamp_mode;
u32 period_step;
u32 sleep_min;
u32 avail_min;
u32 xfer_align;
u32 start_threshold;
u32 stop_threshold;
u32 silence_threshold;
u32 silence_size;
u32 boundary;
u32 proto;
u32 tstamp_type;
unsigned char reserved[56];
};
/* recalcuate the boundary within 32bit */
static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime)
{
snd_pcm_uframes_t boundary;
if (! runtime->buffer_size)
return 0;
boundary = runtime->buffer_size;
while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
boundary *= 2;
return boundary;
}
static int snd_pcm_ioctl_sw_params_compat(struct snd_pcm_substream *substream,
struct snd_pcm_sw_params32 __user *src)
{
struct snd_pcm_sw_params params;
snd_pcm_uframes_t boundary;
int err;
memset(&params, 0, sizeof(params));
if (get_user(params.tstamp_mode, &src->tstamp_mode) ||
get_user(params.period_step, &src->period_step) ||
get_user(params.sleep_min, &src->sleep_min) ||
get_user(params.avail_min, &src->avail_min) ||
get_user(params.xfer_align, &src->xfer_align) ||
get_user(params.start_threshold, &src->start_threshold) ||
get_user(params.stop_threshold, &src->stop_threshold) ||
get_user(params.silence_threshold, &src->silence_threshold) ||
get_user(params.silence_size, &src->silence_size) ||
get_user(params.tstamp_type, &src->tstamp_type) ||
get_user(params.proto, &src->proto))
return -EFAULT;
/*
* Check silent_size parameter. Since we have 64bit boundary,
* silence_size must be compared with the 32bit boundary.
*/
boundary = recalculate_boundary(substream->runtime);
if (boundary && params.silence_size >= boundary)
params.silence_size = substream->runtime->boundary;
err = snd_pcm_sw_params(substream, &params);
if (err < 0)
return err;
if (boundary && put_user(boundary, &src->boundary))
return -EFAULT;
return err;
}
struct snd_pcm_channel_info32 {
u32 channel;
u32 offset;
u32 first;
u32 step;
};
static int snd_pcm_ioctl_channel_info_compat(struct snd_pcm_substream *substream,
struct snd_pcm_channel_info32 __user *src)
{
struct snd_pcm_channel_info info;
int err;
if (get_user(info.channel, &src->channel) ||
get_user(info.offset, &src->offset) ||
get_user(info.first, &src->first) ||
get_user(info.step, &src->step))
return -EFAULT;
err = snd_pcm_channel_info(substream, &info);
if (err < 0)
return err;
if (put_user(info.channel, &src->channel) ||
put_user(info.offset, &src->offset) ||
put_user(info.first, &src->first) ||
put_user(info.step, &src->step))
return -EFAULT;
return err;
}
struct snd_pcm_status32 {
s32 state;
struct compat_timespec trigger_tstamp;
struct compat_timespec tstamp;
u32 appl_ptr;
u32 hw_ptr;
s32 delay;
u32 avail;
u32 avail_max;
u32 overrange;
s32 suspended_state;
u32 reserved_alignment;
struct compat_timespec audio_tstamp;
unsigned char reserved[56-sizeof(struct compat_timespec)];
} __attribute__((packed));
static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
struct snd_pcm_status32 __user *src)
{
struct snd_pcm_status status;
int err;
err = snd_pcm_status(substream, &status);
if (err < 0)
return err;
if (clear_user(src, sizeof(*src)))
return -EFAULT;
if (put_user(status.state, &src->state) ||
compat_put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) ||
compat_put_timespec(&status.tstamp, &src->tstamp) ||
put_user(status.appl_ptr, &src->appl_ptr) ||
put_user(status.hw_ptr, &src->hw_ptr) ||
put_user(status.delay, &src->delay) ||
put_user(status.avail, &src->avail) ||
put_user(status.avail_max, &src->avail_max) ||
put_user(status.overrange, &src->overrange) ||
put_user(status.suspended_state, &src->suspended_state) ||
compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp))
return -EFAULT;
return err;
}
/* both for HW_PARAMS and HW_REFINE */
static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream,
int refine,
struct snd_pcm_hw_params32 __user *data32)
{
struct snd_pcm_hw_params *data;
struct snd_pcm_runtime *runtime;
int err;
if (! (runtime = substream->runtime))
return -ENOTTY;
/* only fifo_size is different, so just copy all */
data = memdup_user(data32, sizeof(*data32));
if (IS_ERR(data))
return PTR_ERR(data);
if (refine)
err = snd_pcm_hw_refine(substream, data);
else
err = snd_pcm_hw_params(substream, data);
if (err < 0)
goto error;
if (copy_to_user(data32, data, sizeof(*data32)) ||
put_user(data->fifo_size, &data32->fifo_size)) {
err = -EFAULT;
goto error;
}
if (! refine) {
unsigned int new_boundary = recalculate_boundary(runtime);
if (new_boundary)
runtime->boundary = new_boundary;
}
error:
kfree(data);
return err;
}
/*
*/
struct snd_xferi32 {
s32 result;
u32 buf;
u32 frames;
};
static int snd_pcm_ioctl_xferi_compat(struct snd_pcm_substream *substream,
int dir, struct snd_xferi32 __user *data32)
{
compat_caddr_t buf;
u32 frames;
int err;
if (! substream->runtime)
return -ENOTTY;
if (substream->stream != dir)
return -EINVAL;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
if (get_user(buf, &data32->buf) ||
get_user(frames, &data32->frames))
return -EFAULT;
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_lib_write(substream, compat_ptr(buf), frames);
else
err = snd_pcm_lib_read(substream, compat_ptr(buf), frames);
if (err < 0)
return err;
/* copy the result */
if (put_user(err, &data32->result))
return -EFAULT;
return 0;
}
/* snd_xfern needs remapping of bufs */
struct snd_xfern32 {
s32 result;
u32 bufs; /* this is void **; */
u32 frames;
};
/*
* xfern ioctl nees to copy (up to) 128 pointers on stack.
* although we may pass the copied pointers through f_op->ioctl, but the ioctl
* handler there expands again the same 128 pointers on stack, so it is better
* to handle the function (calling pcm_readv/writev) directly in this handler.
*/
static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream,
int dir, struct snd_xfern32 __user *data32)
{
compat_caddr_t buf;
compat_caddr_t __user *bufptr;
u32 frames;
void __user **bufs;
int err, ch, i;
if (! substream->runtime)
return -ENOTTY;
if (substream->stream != dir)
return -EINVAL;
if ((ch = substream->runtime->channels) > 128)
return -EINVAL;
if (get_user(buf, &data32->bufs) ||
get_user(frames, &data32->frames))
return -EFAULT;
bufptr = compat_ptr(buf);
bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL);
if (bufs == NULL)
return -ENOMEM;
for (i = 0; i < ch; i++) {
u32 ptr;
if (get_user(ptr, bufptr)) {
kfree(bufs);
return -EFAULT;
}
bufs[i] = compat_ptr(ptr);
bufptr++;
}
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
err = snd_pcm_lib_writev(substream, bufs, frames);
else
err = snd_pcm_lib_readv(substream, bufs, frames);
if (err >= 0) {
if (put_user(err, &data32->result))
err = -EFAULT;
}
kfree(bufs);
return err;
}
struct snd_pcm_mmap_status32 {
s32 state;
s32 pad1;
u32 hw_ptr;
struct compat_timespec tstamp;
s32 suspended_state;
struct compat_timespec audio_tstamp;
} __attribute__((packed));
struct snd_pcm_mmap_control32 {
u32 appl_ptr;
u32 avail_min;
};
struct snd_pcm_sync_ptr32 {
u32 flags;
union {
struct snd_pcm_mmap_status32 status;
unsigned char reserved[64];
} s;
union {
struct snd_pcm_mmap_control32 control;
unsigned char reserved[64];
} c;
} __attribute__((packed));
static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
struct snd_pcm_sync_ptr32 __user *src)
{
struct snd_pcm_runtime *runtime = substream->runtime;
volatile struct snd_pcm_mmap_status *status;
volatile struct snd_pcm_mmap_control *control;
u32 sflags;
struct snd_pcm_mmap_control scontrol;
struct snd_pcm_mmap_status sstatus;
snd_pcm_uframes_t boundary;
int err;
if (snd_BUG_ON(!runtime))
return -EINVAL;
if (get_user(sflags, &src->flags) ||
get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
get_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT;
if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
err = snd_pcm_hwsync(substream);
if (err < 0)
return err;
}
status = runtime->status;
control = runtime->control;
boundary = recalculate_boundary(runtime);
if (! boundary)
boundary = 0x7fffffff;
snd_pcm_stream_lock_irq(substream);
/* FIXME: we should consider the boundary for the sync from app */
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
control->appl_ptr = scontrol.appl_ptr;
else
scontrol.appl_ptr = control->appl_ptr % boundary;
if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
control->avail_min = scontrol.avail_min;
else
scontrol.avail_min = control->avail_min;
sstatus.state = status->state;
sstatus.hw_ptr = status->hw_ptr % boundary;
sstatus.tstamp = status->tstamp;
sstatus.suspended_state = status->suspended_state;
sstatus.audio_tstamp = status->audio_tstamp;
snd_pcm_stream_unlock_irq(substream);
if (put_user(sstatus.state, &src->s.status.state) ||
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) ||
put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
compat_put_timespec(&sstatus.audio_tstamp,
&src->s.status.audio_tstamp) ||
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
put_user(scontrol.avail_min, &src->c.control.avail_min))
return -EFAULT;
return 0;
}
/*
*/
enum {
SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct snd_pcm_hw_params32),
SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32),
SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32),
SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct snd_pcm_status32),
SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_pcm_channel_info32),
SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32),
SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct snd_xferi32),
SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct snd_xferi32),
SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct snd_xfern32),
SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct snd_xfern32),
SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr32),
};
static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_pcm_file *pcm_file;
struct snd_pcm_substream *substream;
void __user *argp = compat_ptr(arg);
pcm_file = file->private_data;
if (! pcm_file)
return -ENOTTY;
substream = pcm_file->substream;
if (! substream)
return -ENOTTY;
/*
* When PCM is used on 32bit mode, we need to disable
* mmap of PCM status/control records because of the size
* incompatibility.
*/
pcm_file->no_compat_mmap = 1;
switch (cmd) {
case SNDRV_PCM_IOCTL_PVERSION:
case SNDRV_PCM_IOCTL_INFO:
case SNDRV_PCM_IOCTL_TSTAMP:
case SNDRV_PCM_IOCTL_TTSTAMP:
case SNDRV_PCM_IOCTL_HWSYNC:
case SNDRV_PCM_IOCTL_PREPARE:
case SNDRV_PCM_IOCTL_RESET:
case SNDRV_PCM_IOCTL_START:
case SNDRV_PCM_IOCTL_DROP:
case SNDRV_PCM_IOCTL_DRAIN:
case SNDRV_PCM_IOCTL_PAUSE:
case SNDRV_PCM_IOCTL_HW_FREE:
case SNDRV_PCM_IOCTL_RESUME:
case SNDRV_PCM_IOCTL_XRUN:
case SNDRV_PCM_IOCTL_LINK:
case SNDRV_PCM_IOCTL_UNLINK:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return snd_pcm_playback_ioctl1(file, substream, cmd, argp);
else
return snd_pcm_capture_ioctl1(file, substream, cmd, argp);
case SNDRV_PCM_IOCTL_HW_REFINE32:
return snd_pcm_ioctl_hw_params_compat(substream, 1, argp);
case SNDRV_PCM_IOCTL_HW_PARAMS32:
return snd_pcm_ioctl_hw_params_compat(substream, 0, argp);
case SNDRV_PCM_IOCTL_SW_PARAMS32:
return snd_pcm_ioctl_sw_params_compat(substream, argp);
case SNDRV_PCM_IOCTL_STATUS32:
return snd_pcm_status_user_compat(substream, argp);
case SNDRV_PCM_IOCTL_SYNC_PTR32:
return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
return snd_pcm_ioctl_channel_info_compat(substream, argp);
case SNDRV_PCM_IOCTL_WRITEI_FRAMES32:
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
case SNDRV_PCM_IOCTL_READI_FRAMES32:
return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
case SNDRV_PCM_IOCTL_WRITEN_FRAMES32:
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp);
case SNDRV_PCM_IOCTL_READN_FRAMES32:
return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp);
case SNDRV_PCM_IOCTL_DELAY32:
return snd_pcm_ioctl_delay_compat(substream, argp);
case SNDRV_PCM_IOCTL_REWIND32:
return snd_pcm_ioctl_rewind_compat(substream, argp);
case SNDRV_PCM_IOCTL_FORWARD32:
return snd_pcm_ioctl_forward_compat(substream, argp);
}
return -ENOIOCTLCMD;
}

371
sound/core/pcm_dmaengine.c Normal file
View file

@ -0,0 +1,371 @@
/*
* Copyright (C) 2012, Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Based on:
* imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
* mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
* ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
* Copyright (C) 2006 Applied Data Systems
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/dmaengine.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
struct dmaengine_pcm_runtime_data {
struct dma_chan *dma_chan;
dma_cookie_t cookie;
unsigned int pos;
};
static inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
const struct snd_pcm_substream *substream)
{
return substream->runtime->private_data;
}
struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
return prtd->dma_chan;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan);
/**
* snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
* @substream: PCM substream
* @params: hw_params
* @slave_config: DMA slave config
*
* This function can be used to initialize a dma_slave_config from a substream
* and hw_params in a dmaengine based PCM driver implementation.
*/
int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
const struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
enum dma_slave_buswidth buswidth;
int bits;
bits = params_physical_width(params);
if (bits < 8 || bits > 64)
return -EINVAL;
else if (bits == 8)
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
else if (bits == 16)
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
else if (bits == 24)
buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
else if (bits <= 32)
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
else
buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
slave_config->direction = DMA_MEM_TO_DEV;
slave_config->dst_addr_width = buswidth;
} else {
slave_config->direction = DMA_DEV_TO_MEM;
slave_config->src_addr_width = buswidth;
}
slave_config->device_fc = false;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
/**
* snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config
* using DAI DMA data.
* @substream: PCM substream
* @dma_data: DAI DMA data
* @slave_config: DMA slave configuration
*
* Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width and
* slave_id fields of the DMA slave config from the same fields of the DAI DMA
* data struct. The src and dst fields will be initialized depending on the
* direction of the substream. If the substream is a playback stream the dst
* fields will be initialized, if it is a capture stream the src fields will be
* initialized. The {dst,src}_addr_width field will only be initialized if the
* addr_width field of the DAI DMA data struct is not equal to
* DMA_SLAVE_BUSWIDTH_UNDEFINED.
*/
void snd_dmaengine_pcm_set_config_from_dai_data(
const struct snd_pcm_substream *substream,
const struct snd_dmaengine_dai_dma_data *dma_data,
struct dma_slave_config *slave_config)
{
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
slave_config->dst_addr = dma_data->addr;
slave_config->dst_maxburst = dma_data->maxburst;
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
slave_config->dst_addr_width = dma_data->addr_width;
} else {
slave_config->src_addr = dma_data->addr;
slave_config->src_maxburst = dma_data->maxburst;
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
slave_config->src_addr_width = dma_data->addr_width;
}
slave_config->slave_id = dma_data->slave_id;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data);
static void dmaengine_pcm_dma_complete(void *arg)
{
struct snd_pcm_substream *substream = arg;
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
prtd->pos += snd_pcm_lib_period_bytes(substream);
if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
prtd->pos = 0;
snd_pcm_period_elapsed(substream);
}
static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
struct dma_chan *chan = prtd->dma_chan;
struct dma_async_tx_descriptor *desc;
enum dma_transfer_direction direction;
unsigned long flags = DMA_CTRL_ACK;
direction = snd_pcm_substream_to_dma_direction(substream);
if (!substream->runtime->no_period_wakeup)
flags |= DMA_PREP_INTERRUPT;
prtd->pos = 0;
desc = dmaengine_prep_dma_cyclic(chan,
substream->runtime->dma_addr,
snd_pcm_lib_buffer_bytes(substream),
snd_pcm_lib_period_bytes(substream), direction, flags);
if (!desc)
return -ENOMEM;
desc->callback = dmaengine_pcm_dma_complete;
desc->callback_param = substream;
prtd->cookie = dmaengine_submit(desc);
return 0;
}
/**
* snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
* @substream: PCM substream
* @cmd: Trigger command
*
* Returns 0 on success, a negative error code otherwise.
*
* This function can be used as the PCM trigger callback for dmaengine based PCM
* driver implementations.
*/
int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
ret = dmaengine_pcm_prepare_and_submit(substream);
if (ret)
return ret;
dma_async_issue_pending(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
dmaengine_resume(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
dmaengine_pause(prtd->dma_chan);
else
dmaengine_terminate_all(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
dmaengine_pause(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_STOP:
dmaengine_terminate_all(prtd->dma_chan);
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
/**
* snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
* @substream: PCM substream
*
* This function is deprecated and should not be used by new drivers, as its
* results may be unreliable.
*/
snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
return bytes_to_frames(substream->runtime, prtd->pos);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue);
/**
* snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
* @substream: PCM substream
*
* This function can be used as the PCM pointer callback for dmaengine based PCM
* driver implementations.
*/
snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
struct dma_tx_state state;
enum dma_status status;
unsigned int buf_size;
unsigned int pos = 0;
status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
buf_size = snd_pcm_lib_buffer_bytes(substream);
if (state.residue > 0 && state.residue <= buf_size)
pos = buf_size - state.residue;
}
return bytes_to_frames(substream->runtime, pos);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer);
/**
* snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
* @filter_fn: Filter function used to request the DMA channel
* @filter_data: Data passed to the DMA filter function
*
* Returns NULL or the requested DMA channel.
*
* This function request a DMA channel for usage with dmaengine PCM.
*/
struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn,
void *filter_data)
{
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_CYCLIC, mask);
return dma_request_channel(mask, filter_fn, filter_data);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
/**
* snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
* @substream: PCM substream
* @chan: DMA channel to use for data transfers
*
* Returns 0 on success, a negative error code otherwise.
*
* The function should usually be called from the pcm open callback. Note that
* this function will use private_data field of the substream's runtime. So it
* is not availabe to your pcm driver implementation.
*/
int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
struct dma_chan *chan)
{
struct dmaengine_pcm_runtime_data *prtd;
int ret;
if (!chan)
return -ENXIO;
ret = snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
return ret;
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
if (!prtd)
return -ENOMEM;
prtd->dma_chan = chan;
substream->runtime->private_data = prtd;
return 0;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
/**
* snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
* @substream: PCM substream
* @filter_fn: Filter function used to request the DMA channel
* @filter_data: Data passed to the DMA filter function
*
* Returns 0 on success, a negative error code otherwise.
*
* This function will request a DMA channel using the passed filter function and
* data. The function should usually be called from the pcm open callback. Note
* that this function will use private_data field of the substream's runtime. So
* it is not availabe to your pcm driver implementation.
*/
int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream,
dma_filter_fn filter_fn, void *filter_data)
{
return snd_dmaengine_pcm_open(substream,
snd_dmaengine_pcm_request_channel(filter_fn, filter_data));
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan);
/**
* snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
* @substream: PCM substream
*/
int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
kfree(prtd);
return 0;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close);
/**
* snd_dmaengine_pcm_release_chan_close - Close a dmaengine based PCM substream and release channel
* @substream: PCM substream
*
* Releases the DMA channel associated with the PCM substream.
*/
int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
{
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
dma_release_channel(prtd->dma_chan);
return snd_dmaengine_pcm_close(substream);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
MODULE_LICENSE("GPL");

2600
sound/core/pcm_lib.c Normal file

File diff suppressed because it is too large Load diff

460
sound/core/pcm_memory.c Normal file
View file

@ -0,0 +1,460 @@
/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by 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 <asm/io.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <linux/vmalloc.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/initval.h>
static int preallocate_dma = 1;
module_param(preallocate_dma, int, 0444);
MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
static int maximum_substreams = 4;
module_param(maximum_substreams, int, 0444);
MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
static const size_t snd_minimum_buffer = 16384;
/*
* try to allocate as the large pages as possible.
* stores the resultant memory size in *res_size.
*
* the minimum size is snd_minimum_buffer. it should be power of 2.
*/
static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size)
{
struct snd_dma_buffer *dmab = &substream->dma_buffer;
size_t orig_size = size;
int err;
do {
if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,
size, dmab)) < 0) {
if (err != -ENOMEM)
return err; /* fatal error */
} else
return 0;
size >>= 1;
} while (size >= snd_minimum_buffer);
dmab->bytes = 0; /* tell error */
pr_warn("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
substream->pcm->card->number, substream->pcm->device,
substream->stream ? 'c' : 'p', substream->number,
substream->pcm->name, orig_size);
return 0;
}
/*
* release the preallocated buffer if not yet done.
*/
static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream)
{
if (substream->dma_buffer.area == NULL)
return;
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer.area = NULL;
}
/**
* snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
* @substream: the pcm substream instance
*
* Releases the pre-allocated buffer of the given substream.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
{
snd_pcm_lib_preallocate_dma_free(substream);
#ifdef CONFIG_SND_VERBOSE_PROCFS
snd_info_free_entry(substream->proc_prealloc_max_entry);
substream->proc_prealloc_max_entry = NULL;
snd_info_free_entry(substream->proc_prealloc_entry);
substream->proc_prealloc_entry = NULL;
#endif
return 0;
}
/**
* snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
* @pcm: the pcm instance
*
* Releases all the pre-allocated buffers on the given pcm.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
int stream;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
snd_pcm_lib_preallocate_free(substream);
return 0;
}
EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
#ifdef CONFIG_SND_VERBOSE_PROCFS
/*
* read callback for prealloc proc file
*
* prints the current allocated size in kB.
*/
static void snd_pcm_lib_preallocate_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
}
/*
* read callback for prealloc_max proc file
*
* prints the maximum allowed size in kB.
*/
static void snd_pcm_lib_preallocate_max_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_max / 1024);
}
/*
* write callback for prealloc proc file
*
* accepts the preallocation size in kB.
*/
static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
char line[64], str[64];
size_t size;
struct snd_dma_buffer new_dmab;
if (substream->runtime) {
buffer->error = -EBUSY;
return;
}
if (!snd_info_get_line(buffer, line, sizeof(line))) {
snd_info_get_str(str, line, sizeof(str));
size = simple_strtoul(str, NULL, 10) * 1024;
if ((size != 0 && size < 8192) || size > substream->dma_max) {
buffer->error = -EINVAL;
return;
}
if (substream->dma_buffer.bytes == size)
return;
memset(&new_dmab, 0, sizeof(new_dmab));
new_dmab.dev = substream->dma_buffer.dev;
if (size > 0) {
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
substream->dma_buffer.dev.dev,
size, &new_dmab) < 0) {
buffer->error = -ENOMEM;
return;
}
substream->buffer_bytes_max = size;
} else {
substream->buffer_bytes_max = UINT_MAX;
}
if (substream->dma_buffer.area)
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer = new_dmab;
} else {
buffer->error = -EINVAL;
}
}
static inline void preallocate_info_init(struct snd_pcm_substream *substream)
{
struct snd_info_entry *entry;
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
entry->mode |= S_IWUSR;
entry->private_data = substream;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_prealloc_entry = entry;
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) {
entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read;
entry->private_data = substream;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_prealloc_max_entry = entry;
}
#else /* !CONFIG_SND_VERBOSE_PROCFS */
#define preallocate_info_init(s)
#endif /* CONFIG_SND_VERBOSE_PROCFS */
/*
* pre-allocate the buffer and create a proc file for the substream
*/
static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
size_t size, size_t max)
{
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
preallocate_pcm_pages(substream, size);
if (substream->dma_buffer.bytes > 0)
substream->buffer_bytes_max = substream->dma_buffer.bytes;
substream->dma_max = max;
preallocate_info_init(substream);
return 0;
}
/**
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
* @substream: the pcm substream instance
* @type: DMA type (SNDRV_DMA_TYPE_*)
* @data: DMA type dependent data
* @size: the requested pre-allocation size in bytes
* @max: the max. allowed pre-allocation size
*
* Do pre-allocation for the given DMA buffer type.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
int type, struct device *data,
size_t size, size_t max)
{
substream->dma_buffer.dev.type = type;
substream->dma_buffer.dev.dev = data;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
/**
* snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continuous memory type (all substreams)
* @pcm: the pcm instance
* @type: DMA type (SNDRV_DMA_TYPE_*)
* @data: DMA type dependent data
* @size: the requested pre-allocation size in bytes
* @max: the max. allowed pre-allocation size
*
* Do pre-allocation to all substreams of the given pcm for the
* specified DMA type.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
int type, void *data,
size_t size, size_t max)
{
struct snd_pcm_substream *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
return err;
return 0;
}
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
#ifdef CONFIG_SND_DMA_SGBUF
/**
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
* @substream: the pcm substream instance
* @offset: the buffer offset
*
* Used as the page callback of PCM ops.
*
* Return: The page struct at the given buffer offset. %NULL on failure.
*/
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigned long offset)
{
struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
unsigned int idx = offset >> PAGE_SHIFT;
if (idx >= (unsigned int)sgbuf->pages)
return NULL;
return sgbuf->page_table[idx];
}
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
#endif /* CONFIG_SND_DMA_SGBUF */
/**
* snd_pcm_lib_malloc_pages - allocate the DMA buffer
* @substream: the substream to allocate the DMA buffer to
* @size: the requested buffer size in bytes
*
* Allocates the DMA buffer on the BUS type given earlier to
* snd_pcm_lib_preallocate_xxx_pages().
*
* Return: 1 if the buffer is changed, 0 if not changed, or a negative
* code on failure.
*/
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
{
struct snd_pcm_runtime *runtime;
struct snd_dma_buffer *dmab = NULL;
if (PCM_RUNTIME_CHECK(substream))
return -EINVAL;
if (snd_BUG_ON(substream->dma_buffer.dev.type ==
SNDRV_DMA_TYPE_UNKNOWN))
return -EINVAL;
runtime = substream->runtime;
if (runtime->dma_buffer_p) {
/* perphaps, we might free the large DMA memory region
to save some space here, but the actual solution
costs us less time */
if (runtime->dma_buffer_p->bytes >= size) {
runtime->dma_bytes = size;
return 0; /* ok, do not change */
}
snd_pcm_lib_free_pages(substream);
}
if (substream->dma_buffer.area != NULL &&
substream->dma_buffer.bytes >= size) {
dmab = &substream->dma_buffer; /* use the pre-allocated buffer */
} else {
dmab = kzalloc(sizeof(*dmab), GFP_KERNEL);
if (! dmab)
return -ENOMEM;
dmab->dev = substream->dma_buffer.dev;
if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,
substream->dma_buffer.dev.dev,
size, dmab) < 0) {
kfree(dmab);
return -ENOMEM;
}
}
snd_pcm_set_runtime_buffer(substream, dmab);
runtime->dma_bytes = size;
return 1; /* area was changed */
}
EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
/**
* snd_pcm_lib_free_pages - release the allocated DMA buffer.
* @substream: the substream to release the DMA buffer
*
* Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
if (PCM_RUNTIME_CHECK(substream))
return -EINVAL;
runtime = substream->runtime;
if (runtime->dma_area == NULL)
return 0;
if (runtime->dma_buffer_p != &substream->dma_buffer) {
/* it's a newly allocated buffer. release it now. */
snd_dma_free_pages(runtime->dma_buffer_p);
kfree(runtime->dma_buffer_p);
}
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
EXPORT_SYMBOL(snd_pcm_lib_free_pages);
int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
size_t size, gfp_t gfp_flags)
{
struct snd_pcm_runtime *runtime;
if (PCM_RUNTIME_CHECK(substream))
return -EINVAL;
runtime = substream->runtime;
if (runtime->dma_area) {
if (runtime->dma_bytes >= size)
return 0; /* already large enough */
vfree(runtime->dma_area);
}
runtime->dma_area = __vmalloc(size, gfp_flags, PAGE_KERNEL);
if (!runtime->dma_area)
return -ENOMEM;
runtime->dma_bytes = size;
return 1;
}
EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer);
/**
* snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer
* @substream: the substream with a buffer allocated by
* snd_pcm_lib_alloc_vmalloc_buffer()
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
if (PCM_RUNTIME_CHECK(substream))
return -EINVAL;
runtime = substream->runtime;
vfree(runtime->dma_area);
runtime->dma_area = NULL;
return 0;
}
EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer);
/**
* snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct
* @substream: the substream with a buffer allocated by
* snd_pcm_lib_alloc_vmalloc_buffer()
* @offset: offset in the buffer
*
* This function is to be used as the page callback in the PCM ops.
*
* Return: The page struct, or %NULL on failure.
*/
struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
unsigned long offset)
{
return vmalloc_to_page(substream->runtime->dma_area + offset);
}
EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page);

567
sound/core/pcm_misc.c Normal file
View file

@ -0,0 +1,567 @@
/*
* PCM Interface - misc routines
* Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/pcm.h>
#define SND_PCM_FORMAT_UNKNOWN (-1)
/* NOTE: "signed" prefix must be given below since the default char is
* unsigned on some architectures!
*/
struct pcm_format_data {
unsigned char width; /* bit width */
unsigned char phys; /* physical bit width */
signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */
signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */
unsigned char silence[8]; /* silence data to fill */
};
/* we do lots of calculations on snd_pcm_format_t; shut up sparse */
#define INT __force int
static struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = {
[SNDRV_PCM_FORMAT_S8] = {
.width = 8, .phys = 8, .le = -1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U8] = {
.width = 8, .phys = 8, .le = -1, .signd = 0,
.silence = { 0x80 },
},
[SNDRV_PCM_FORMAT_S16_LE] = {
.width = 16, .phys = 16, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S16_BE] = {
.width = 16, .phys = 16, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U16_LE] = {
.width = 16, .phys = 16, .le = 1, .signd = 0,
.silence = { 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U16_BE] = {
.width = 16, .phys = 16, .le = 0, .signd = 0,
.silence = { 0x80, 0x00 },
},
[SNDRV_PCM_FORMAT_S24_LE] = {
.width = 24, .phys = 32, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S24_BE] = {
.width = 24, .phys = 32, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U24_LE] = {
.width = 24, .phys = 32, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U24_BE] = {
.width = 24, .phys = 32, .le = 0, .signd = 0,
.silence = { 0x00, 0x80, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S32_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S32_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U32_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U32_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = 0,
.silence = { 0x80, 0x00, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_FLOAT_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT64_LE] = {
.width = 64, .phys = 64, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_FLOAT64_BE] = {
.width = 64, .phys = 64, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_MU_LAW] = {
.width = 8, .phys = 8, .le = -1, .signd = -1,
.silence = { 0x7f },
},
[SNDRV_PCM_FORMAT_A_LAW] = {
.width = 8, .phys = 8, .le = -1, .signd = -1,
.silence = { 0x55 },
},
[SNDRV_PCM_FORMAT_IMA_ADPCM] = {
.width = 4, .phys = 4, .le = -1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_G723_24] = {
.width = 3, .phys = 3, .le = -1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_G723_40] = {
.width = 5, .phys = 5, .le = -1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_DSD_U8] = {
.width = 8, .phys = 8, .le = 1, .signd = 0,
.silence = { 0x69 },
},
[SNDRV_PCM_FORMAT_DSD_U16_LE] = {
.width = 16, .phys = 16, .le = 1, .signd = 0,
.silence = { 0x69, 0x69 },
},
[SNDRV_PCM_FORMAT_DSD_U32_LE] = {
.width = 32, .phys = 32, .le = 1, .signd = 0,
.silence = { 0x69, 0x69, 0x69, 0x69 },
},
[SNDRV_PCM_FORMAT_DSD_U16_BE] = {
.width = 16, .phys = 16, .le = 0, .signd = 0,
.silence = { 0x69, 0x69 },
},
[SNDRV_PCM_FORMAT_DSD_U32_BE] = {
.width = 32, .phys = 32, .le = 0, .signd = 0,
.silence = { 0x69, 0x69, 0x69, 0x69 },
},
/* FIXME: the following three formats are not defined properly yet */
[SNDRV_PCM_FORMAT_MPEG] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_GSM] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_SPECIAL] = {
.le = -1, .signd = -1,
},
[SNDRV_PCM_FORMAT_S24_3LE] = {
.width = 24, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S24_3BE] = {
.width = 24, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U24_3LE] = {
.width = 24, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x80 },
},
[SNDRV_PCM_FORMAT_U24_3BE] = {
.width = 24, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x80, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S20_3LE] = {
.width = 20, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S20_3BE] = {
.width = 20, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U20_3LE] = {
.width = 20, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x08 },
},
[SNDRV_PCM_FORMAT_U20_3BE] = {
.width = 20, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x08, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_S18_3LE] = {
.width = 18, .phys = 24, .le = 1, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_S18_3BE] = {
.width = 18, .phys = 24, .le = 0, .signd = 1,
.silence = {},
},
[SNDRV_PCM_FORMAT_U18_3LE] = {
.width = 18, .phys = 24, .le = 1, .signd = 0,
.silence = { 0x00, 0x00, 0x02 },
},
[SNDRV_PCM_FORMAT_U18_3BE] = {
.width = 18, .phys = 24, .le = 0, .signd = 0,
.silence = { 0x02, 0x00, 0x00 },
},
[SNDRV_PCM_FORMAT_G723_24_1B] = {
.width = 3, .phys = 8, .le = -1, .signd = -1,
.silence = {},
},
[SNDRV_PCM_FORMAT_G723_40_1B] = {
.width = 5, .phys = 8, .le = -1, .signd = -1,
.silence = {},
},
};
/**
* snd_pcm_format_signed - Check the PCM format is signed linear
* @format: the format to check
*
* Return: 1 if the given PCM format is signed linear, 0 if unsigned
* linear, and a negative error code for non-linear formats.
*/
int snd_pcm_format_signed(snd_pcm_format_t format)
{
int val;
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[(INT)format].signd) < 0)
return -EINVAL;
return val;
}
EXPORT_SYMBOL(snd_pcm_format_signed);
/**
* snd_pcm_format_unsigned - Check the PCM format is unsigned linear
* @format: the format to check
*
* Return: 1 if the given PCM format is unsigned linear, 0 if signed
* linear, and a negative error code for non-linear formats.
*/
int snd_pcm_format_unsigned(snd_pcm_format_t format)
{
int val;
val = snd_pcm_format_signed(format);
if (val < 0)
return val;
return !val;
}
EXPORT_SYMBOL(snd_pcm_format_unsigned);
/**
* snd_pcm_format_linear - Check the PCM format is linear
* @format: the format to check
*
* Return: 1 if the given PCM format is linear, 0 if not.
*/
int snd_pcm_format_linear(snd_pcm_format_t format)
{
return snd_pcm_format_signed(format) >= 0;
}
EXPORT_SYMBOL(snd_pcm_format_linear);
/**
* snd_pcm_format_little_endian - Check the PCM format is little-endian
* @format: the format to check
*
* Return: 1 if the given PCM format is little-endian, 0 if
* big-endian, or a negative error code if endian not specified.
*/
int snd_pcm_format_little_endian(snd_pcm_format_t format)
{
int val;
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[(INT)format].le) < 0)
return -EINVAL;
return val;
}
EXPORT_SYMBOL(snd_pcm_format_little_endian);
/**
* snd_pcm_format_big_endian - Check the PCM format is big-endian
* @format: the format to check
*
* Return: 1 if the given PCM format is big-endian, 0 if
* little-endian, or a negative error code if endian not specified.
*/
int snd_pcm_format_big_endian(snd_pcm_format_t format)
{
int val;
val = snd_pcm_format_little_endian(format);
if (val < 0)
return val;
return !val;
}
EXPORT_SYMBOL(snd_pcm_format_big_endian);
/**
* snd_pcm_format_width - return the bit-width of the format
* @format: the format to check
*
* Return: The bit-width of the format, or a negative error code
* if unknown format.
*/
int snd_pcm_format_width(snd_pcm_format_t format)
{
int val;
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[(INT)format].width) == 0)
return -EINVAL;
return val;
}
EXPORT_SYMBOL(snd_pcm_format_width);
/**
* snd_pcm_format_physical_width - return the physical bit-width of the format
* @format: the format to check
*
* Return: The physical bit-width of the format, or a negative error code
* if unknown format.
*/
int snd_pcm_format_physical_width(snd_pcm_format_t format)
{
int val;
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if ((val = pcm_formats[(INT)format].phys) == 0)
return -EINVAL;
return val;
}
EXPORT_SYMBOL(snd_pcm_format_physical_width);
/**
* snd_pcm_format_size - return the byte size of samples on the given format
* @format: the format to check
* @samples: sampling rate
*
* Return: The byte size of the given samples for the format, or a
* negative error code if unknown format.
*/
ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
{
int phys_width = snd_pcm_format_physical_width(format);
if (phys_width < 0)
return -EINVAL;
return samples * phys_width / 8;
}
EXPORT_SYMBOL(snd_pcm_format_size);
/**
* snd_pcm_format_silence_64 - return the silent data in 8 bytes array
* @format: the format to check
*
* Return: The format pattern to fill or %NULL if error.
*/
const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format)
{
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return NULL;
if (! pcm_formats[(INT)format].phys)
return NULL;
return pcm_formats[(INT)format].silence;
}
EXPORT_SYMBOL(snd_pcm_format_silence_64);
/**
* snd_pcm_format_set_silence - set the silence data on the buffer
* @format: the PCM format
* @data: the buffer pointer
* @samples: the number of samples to set silence
*
* Sets the silence data on the buffer for the given samples.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
{
int width;
unsigned char *dst, *pat;
if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST)
return -EINVAL;
if (samples == 0)
return 0;
width = pcm_formats[(INT)format].phys; /* physical width */
pat = pcm_formats[(INT)format].silence;
if (! width)
return -EINVAL;
/* signed or 1 byte data */
if (pcm_formats[(INT)format].signd == 1 || width <= 8) {
unsigned int bytes = samples * width / 8;
memset(data, *pat, bytes);
return 0;
}
/* non-zero samples, fill using a loop */
width /= 8;
dst = data;
#if 0
while (samples--) {
memcpy(dst, pat, width);
dst += width;
}
#else
/* a bit optimization for constant width */
switch (width) {
case 2:
while (samples--) {
memcpy(dst, pat, 2);
dst += 2;
}
break;
case 3:
while (samples--) {
memcpy(dst, pat, 3);
dst += 3;
}
break;
case 4:
while (samples--) {
memcpy(dst, pat, 4);
dst += 4;
}
break;
case 8:
while (samples--) {
memcpy(dst, pat, 8);
dst += 8;
}
break;
}
#endif
return 0;
}
EXPORT_SYMBOL(snd_pcm_format_set_silence);
/**
* snd_pcm_limit_hw_rates - determine rate_min/rate_max fields
* @runtime: the runtime instance
*
* Determines the rate_min and rate_max fields from the rates bits of
* the given runtime->hw.
*
* Return: Zero if successful.
*/
int snd_pcm_limit_hw_rates(struct snd_pcm_runtime *runtime)
{
int i;
for (i = 0; i < (int)snd_pcm_known_rates.count; i++) {
if (runtime->hw.rates & (1 << i)) {
runtime->hw.rate_min = snd_pcm_known_rates.list[i];
break;
}
}
for (i = (int)snd_pcm_known_rates.count - 1; i >= 0; i--) {
if (runtime->hw.rates & (1 << i)) {
runtime->hw.rate_max = snd_pcm_known_rates.list[i];
break;
}
}
return 0;
}
EXPORT_SYMBOL(snd_pcm_limit_hw_rates);
/**
* snd_pcm_rate_to_rate_bit - converts sample rate to SNDRV_PCM_RATE_xxx bit
* @rate: the sample rate to convert
*
* Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate, or
* SNDRV_PCM_RATE_KNOT for an unknown rate.
*/
unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate)
{
unsigned int i;
for (i = 0; i < snd_pcm_known_rates.count; i++)
if (snd_pcm_known_rates.list[i] == rate)
return 1u << i;
return SNDRV_PCM_RATE_KNOT;
}
EXPORT_SYMBOL(snd_pcm_rate_to_rate_bit);
/**
* snd_pcm_rate_bit_to_rate - converts SNDRV_PCM_RATE_xxx bit to sample rate
* @rate_bit: the rate bit to convert
*
* Return: The sample rate that corresponds to the given SNDRV_PCM_RATE_xxx flag
* or 0 for an unknown rate bit.
*/
unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit)
{
unsigned int i;
for (i = 0; i < snd_pcm_known_rates.count; i++)
if ((1u << i) == rate_bit)
return snd_pcm_known_rates.list[i];
return 0;
}
EXPORT_SYMBOL(snd_pcm_rate_bit_to_rate);
static unsigned int snd_pcm_rate_mask_sanitize(unsigned int rates)
{
if (rates & SNDRV_PCM_RATE_CONTINUOUS)
return SNDRV_PCM_RATE_CONTINUOUS;
else if (rates & SNDRV_PCM_RATE_KNOT)
return SNDRV_PCM_RATE_KNOT;
return rates;
}
/**
* snd_pcm_rate_mask_intersect - computes the intersection between two rate masks
* @rates_a: The first rate mask
* @rates_b: The second rate mask
*
* This function computes the rates that are supported by both rate masks passed
* to the function. It will take care of the special handling of
* SNDRV_PCM_RATE_CONTINUOUS and SNDRV_PCM_RATE_KNOT.
*
* Return: A rate mask containing the rates that are supported by both rates_a
* and rates_b.
*/
unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a,
unsigned int rates_b)
{
rates_a = snd_pcm_rate_mask_sanitize(rates_a);
rates_b = snd_pcm_rate_mask_sanitize(rates_b);
if (rates_a & SNDRV_PCM_RATE_CONTINUOUS)
return rates_b;
else if (rates_b & SNDRV_PCM_RATE_CONTINUOUS)
return rates_a;
else if (rates_a & SNDRV_PCM_RATE_KNOT)
return rates_b;
else if (rates_b & SNDRV_PCM_RATE_KNOT)
return rates_a;
return rates_a & rates_b;
}
EXPORT_SYMBOL_GPL(snd_pcm_rate_mask_intersect);

3634
sound/core/pcm_native.c Normal file

File diff suppressed because it is too large Load diff

143
sound/core/pcm_timer.c Normal file
View file

@ -0,0 +1,143 @@
/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by 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/time.h>
#include <linux/gcd.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/timer.h>
/*
* Timer functions
*/
void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream)
{
unsigned long rate, mult, fsize, l, post;
struct snd_pcm_runtime *runtime = substream->runtime;
mult = 1000000000;
rate = runtime->rate;
if (snd_BUG_ON(!rate))
return;
l = gcd(mult, rate);
mult /= l;
rate /= l;
fsize = runtime->period_size;
if (snd_BUG_ON(!fsize))
return;
l = gcd(rate, fsize);
rate /= l;
fsize /= l;
post = 1;
while ((mult * fsize) / fsize != mult) {
mult /= 2;
post *= 2;
}
if (rate == 0) {
pcm_err(substream->pcm,
"pcm timer resolution out of range (rate = %u, period_size = %lu)\n",
runtime->rate, runtime->period_size);
runtime->timer_resolution = -1;
return;
}
runtime->timer_resolution = (mult * fsize / rate) * post;
}
static unsigned long snd_pcm_timer_resolution(struct snd_timer * timer)
{
struct snd_pcm_substream *substream;
substream = timer->private_data;
return substream->runtime ? substream->runtime->timer_resolution : 0;
}
static int snd_pcm_timer_start(struct snd_timer * timer)
{
struct snd_pcm_substream *substream;
substream = snd_timer_chip(timer);
substream->timer_running = 1;
return 0;
}
static int snd_pcm_timer_stop(struct snd_timer * timer)
{
struct snd_pcm_substream *substream;
substream = snd_timer_chip(timer);
substream->timer_running = 0;
return 0;
}
static struct snd_timer_hardware snd_pcm_timer =
{
.flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE,
.resolution = 0,
.ticks = 1,
.c_resolution = snd_pcm_timer_resolution,
.start = snd_pcm_timer_start,
.stop = snd_pcm_timer_stop,
};
/*
* Init functions
*/
static void snd_pcm_timer_free(struct snd_timer *timer)
{
struct snd_pcm_substream *substream = timer->private_data;
substream->timer = NULL;
}
void snd_pcm_timer_init(struct snd_pcm_substream *substream)
{
struct snd_timer_id tid;
struct snd_timer *timer;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.dev_class = SNDRV_TIMER_CLASS_PCM;
tid.card = substream->pcm->card->number;
tid.device = substream->pcm->device;
tid.subdevice = (substream->number << 1) | (substream->stream & 1);
if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0)
return;
sprintf(timer->name, "PCM %s %i-%i-%i",
substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
"capture" : "playback",
tid.card, tid.device, tid.subdevice);
timer->hw = snd_pcm_timer;
if (snd_device_register(timer->card, timer) < 0) {
snd_device_free(timer->card, timer);
return;
}
timer->private_data = substream;
timer->private_free = snd_pcm_timer_free;
substream->timer = timer;
}
void snd_pcm_timer_done(struct snd_pcm_substream *substream)
{
if (substream->timer) {
snd_device_free(substream->pcm->card, substream->timer);
substream->timer = NULL;
}
}

1744
sound/core/rawmidi.c Normal file

File diff suppressed because it is too large Load diff

120
sound/core/rawmidi_compat.c Normal file
View file

@ -0,0 +1,120 @@
/*
* 32bit -> 64bit ioctl wrapper for raw MIDI API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from rawmidi.c */
#include <linux/compat.h>
struct snd_rawmidi_params32 {
s32 stream;
u32 buffer_size;
u32 avail_min;
unsigned int no_active_sensing; /* avoid bit-field */
unsigned char reserved[16];
} __attribute__((packed));
static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile,
struct snd_rawmidi_params32 __user *src)
{
struct snd_rawmidi_params params;
unsigned int val;
if (rfile->output == NULL)
return -EINVAL;
if (get_user(params.stream, &src->stream) ||
get_user(params.buffer_size, &src->buffer_size) ||
get_user(params.avail_min, &src->avail_min) ||
get_user(val, &src->no_active_sensing))
return -EFAULT;
params.no_active_sensing = val;
switch (params.stream) {
case SNDRV_RAWMIDI_STREAM_OUTPUT:
return snd_rawmidi_output_params(rfile->output, &params);
case SNDRV_RAWMIDI_STREAM_INPUT:
return snd_rawmidi_input_params(rfile->input, &params);
}
return -EINVAL;
}
struct snd_rawmidi_status32 {
s32 stream;
struct compat_timespec tstamp;
u32 avail;
u32 xruns;
unsigned char reserved[16];
} __attribute__((packed));
static int snd_rawmidi_ioctl_status_compat(struct snd_rawmidi_file *rfile,
struct snd_rawmidi_status32 __user *src)
{
int err;
struct snd_rawmidi_status status;
if (rfile->output == NULL)
return -EINVAL;
if (get_user(status.stream, &src->stream))
return -EFAULT;
switch (status.stream) {
case SNDRV_RAWMIDI_STREAM_OUTPUT:
err = snd_rawmidi_output_status(rfile->output, &status);
break;
case SNDRV_RAWMIDI_STREAM_INPUT:
err = snd_rawmidi_input_status(rfile->input, &status);
break;
default:
return -EINVAL;
}
if (err < 0)
return err;
if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
put_user(status.avail, &src->avail) ||
put_user(status.xruns, &src->xruns))
return -EFAULT;
return 0;
}
enum {
SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct snd_rawmidi_params32),
SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct snd_rawmidi_status32),
};
static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_rawmidi_file *rfile;
void __user *argp = compat_ptr(arg);
rfile = file->private_data;
switch (cmd) {
case SNDRV_RAWMIDI_IOCTL_PVERSION:
case SNDRV_RAWMIDI_IOCTL_INFO:
case SNDRV_RAWMIDI_IOCTL_DROP:
case SNDRV_RAWMIDI_IOCTL_DRAIN:
return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_RAWMIDI_IOCTL_PARAMS32:
return snd_rawmidi_ioctl_params_compat(rfile, argp);
case SNDRV_RAWMIDI_IOCTL_STATUS32:
return snd_rawmidi_ioctl_status_compat(rfile, argp);
}
return -ENOIOCTLCMD;
}

187
sound/core/rtctimer.c Normal file
View file

@ -0,0 +1,187 @@
/*
* RTC based high-frequency timer
*
* Copyright (C) 2000 Takashi Iwai
* based on rtctimer.c by Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/log2.h>
#include <sound/core.h>
#include <sound/timer.h>
#if IS_ENABLED(CONFIG_RTC)
#include <linux/mc146818rtc.h>
#define RTC_FREQ 1024 /* default frequency */
#define NANO_SEC 1000000000L /* 10^9 in sec */
/*
* prototypes
*/
static int rtctimer_open(struct snd_timer *t);
static int rtctimer_close(struct snd_timer *t);
static int rtctimer_start(struct snd_timer *t);
static int rtctimer_stop(struct snd_timer *t);
/*
* The hardware dependent description for this timer.
*/
static struct snd_timer_hardware rtc_hw = {
.flags = SNDRV_TIMER_HW_AUTO |
SNDRV_TIMER_HW_FIRST |
SNDRV_TIMER_HW_TASKLET,
.ticks = 100000000L, /* FIXME: XXX */
.open = rtctimer_open,
.close = rtctimer_close,
.start = rtctimer_start,
.stop = rtctimer_stop,
};
static int rtctimer_freq = RTC_FREQ; /* frequency */
static struct snd_timer *rtctimer;
static struct tasklet_struct rtc_tasklet;
static rtc_task_t rtc_task;
static int
rtctimer_open(struct snd_timer *t)
{
int err;
err = rtc_register(&rtc_task);
if (err < 0)
return err;
t->private_data = &rtc_task;
return 0;
}
static int
rtctimer_close(struct snd_timer *t)
{
rtc_task_t *rtc = t->private_data;
if (rtc) {
rtc_unregister(rtc);
tasklet_kill(&rtc_tasklet);
t->private_data = NULL;
}
return 0;
}
static int
rtctimer_start(struct snd_timer *timer)
{
rtc_task_t *rtc = timer->private_data;
if (snd_BUG_ON(!rtc))
return -EINVAL;
rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
rtc_control(rtc, RTC_PIE_ON, 0);
return 0;
}
static int
rtctimer_stop(struct snd_timer *timer)
{
rtc_task_t *rtc = timer->private_data;
if (snd_BUG_ON(!rtc))
return -EINVAL;
rtc_control(rtc, RTC_PIE_OFF, 0);
return 0;
}
static void rtctimer_tasklet(unsigned long data)
{
snd_timer_interrupt((struct snd_timer *)data, 1);
}
/*
* interrupt
*/
static void rtctimer_interrupt(void *private_data)
{
tasklet_schedule(private_data);
}
/*
* ENTRY functions
*/
static int __init rtctimer_init(void)
{
int err;
struct snd_timer *timer;
if (rtctimer_freq < 2 || rtctimer_freq > 8192 ||
!is_power_of_2(rtctimer_freq)) {
pr_err("ALSA: rtctimer: invalid frequency %d\n", rtctimer_freq);
return -EINVAL;
}
/* Create a new timer and set up the fields */
err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
if (err < 0)
return err;
timer->module = THIS_MODULE;
strcpy(timer->name, "RTC timer");
timer->hw = rtc_hw;
timer->hw.resolution = NANO_SEC / rtctimer_freq;
tasklet_init(&rtc_tasklet, rtctimer_tasklet, (unsigned long)timer);
/* set up RTC callback */
rtc_task.func = rtctimer_interrupt;
rtc_task.private_data = &rtc_tasklet;
err = snd_timer_global_register(timer);
if (err < 0) {
snd_timer_global_free(timer);
return err;
}
rtctimer = timer; /* remember this */
return 0;
}
static void __exit rtctimer_exit(void)
{
if (rtctimer) {
snd_timer_global_free(rtctimer);
rtctimer = NULL;
}
}
/*
* exported stuff
*/
module_init(rtctimer_init)
module_exit(rtctimer_exit)
module_param(rtctimer_freq, int, 0444);
MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
#endif /* IS_ENABLED(CONFIG_RTC) */

16
sound/core/seq/Kconfig Normal file
View 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
View 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

View 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

View 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(&register_mutex);
rc = snd_seq_oss_open(file, level);
mutex_unlock(&register_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(&register_mutex);
snd_seq_oss_release(dp);
mutex_unlock(&register_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(&register_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(&register_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(&register_mutex);
return rc;
}
mutex_unlock(&register_mutex);
return 0;
}
static void
unregister_device(void)
{
mutex_lock(&register_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(&register_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(&register_mutex);
snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
snd_seq_oss_system_info_read(buf);
snd_seq_oss_synth_info_read(buf);
snd_seq_oss_midi_info_read(buf);
mutex_unlock(&register_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 */

View 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 */

View 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;
}

View 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 */

View 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 */

View 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;
}

View 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(&register_lock, flags);
mdev = midi_devs[dev];
if (mdev)
snd_use_lock_use(&mdev->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return mdev;
}
/*
* look for the identical slot
*/
static struct seq_oss_midi *
find_slot(int client, int port)
{
int i;
struct seq_oss_midi *mdev;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
mdev = midi_devs[i];
if (mdev && mdev->client == client && mdev->port == port) {
/* found! */
snd_use_lock_use(&mdev->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return mdev;
}
}
spin_unlock_irqrestore(&register_lock, flags);
return NULL;
}
#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
/*
* register a new port if it doesn't exist yet
*/
int
snd_seq_oss_midi_check_new_port(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(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
if (midi_devs[i] == NULL)
break;
}
if (i >= max_midi_devs) {
if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
spin_unlock_irqrestore(&register_lock, flags);
snd_midi_event_free(mdev->coder);
kfree(mdev);
return -ENOMEM;
}
max_midi_devs++;
}
mdev->seq_device = i;
midi_devs[mdev->seq_device] = mdev;
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
/*
* release the midi device if it was registered
*/
int
snd_seq_oss_midi_check_exit_port(int client, int port)
{
struct seq_oss_midi *mdev;
unsigned long flags;
int index;
if ((mdev = find_slot(client, port)) != NULL) {
spin_lock_irqsave(&register_lock, flags);
midi_devs[mdev->seq_device] = NULL;
spin_unlock_irqrestore(&register_lock, flags);
snd_use_lock_free(&mdev->use_lock);
snd_use_lock_sync(&mdev->use_lock);
if (mdev->coder)
snd_midi_event_free(mdev->coder);
kfree(mdev);
}
spin_lock_irqsave(&register_lock, flags);
for (index = max_midi_devs - 1; index >= 0; index--) {
if (midi_devs[index])
break;
}
max_midi_devs = index + 1;
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
/*
* release the midi device if it was registered
*/
void
snd_seq_oss_midi_clear_all(void)
{
int i;
struct seq_oss_midi *mdev;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (i = 0; i < max_midi_devs; i++) {
if ((mdev = midi_devs[i]) != NULL) {
if (mdev->coder)
snd_midi_event_free(mdev->coder);
kfree(mdev);
midi_devs[i] = NULL;
}
}
max_midi_devs = 0;
spin_unlock_irqrestore(&register_lock, flags);
}
/*
* set up midi tables
*/
void
snd_seq_oss_midi_setup(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 */

View 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

View 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 */

View 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

View 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;
}

View 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(&register_lock, flags);
for (i = 0; i < max_synth_devs; i++) {
if (synth_devs[i] == NULL)
break;
}
if (i >= max_synth_devs) {
if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) {
spin_unlock_irqrestore(&register_lock, flags);
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(&register_lock, flags);
dev->driver_data = rec;
#ifdef SNDRV_OSS_INFO_DEV_SYNTH
if (i < SNDRV_CARDS)
snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name);
#endif
return 0;
}
int
snd_seq_oss_synth_unregister(struct snd_seq_device *dev)
{
int index;
struct seq_oss_synth *rec = dev->driver_data;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
for (index = 0; index < max_synth_devs; index++) {
if (synth_devs[index] == rec)
break;
}
if (index >= max_synth_devs) {
spin_unlock_irqrestore(&register_lock, flags);
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(&register_lock, flags);
#ifdef SNDRV_OSS_INFO_DEV_SYNTH
if (rec->seq_device < SNDRV_CARDS)
snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device);
#endif
snd_use_lock_sync(&rec->use_lock);
kfree(rec);
return 0;
}
/*
*/
static struct seq_oss_synth *
get_sdev(int dev)
{
struct seq_oss_synth *rec;
unsigned long flags;
spin_lock_irqsave(&register_lock, flags);
rec = synth_devs[dev];
if (rec)
snd_use_lock_use(&rec->use_lock);
spin_unlock_irqrestore(&register_lock, flags);
return rec;
}
/*
* set up synth tables
*/
void
snd_seq_oss_synth_setup(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 */

View 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

View 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;
}

View 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

View 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);
}

View 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
View 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)

File diff suppressed because it is too large Load diff

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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(&params, 0, sizeof(params));
params.avail_min = 1;
params.buffer_size = input_buffer_size;
if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, &params)) < 0) {
snd_rawmidi_kernel_release(&msynth->input_rfile);
return err;
}
snd_midi_event_reset_encode(msynth->parser);
runtime->event = snd_midi_input_event;
runtime->private_data = msynth;
snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
return 0;
}
/* close associated midi device for input */
static int midisynth_unsubscribe(void *private_data, 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(&params, 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, &params)) < 0) {
snd_rawmidi_kernel_release(&msynth->output_rfile);
return err;
}
snd_midi_event_reset_decode(msynth->parser);
return 0;
}
/* close associated midi device for output */
static int midisynth_unuse(void *private_data, 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(&register_mutex);
client = synths[card->number];
if (client == NULL) {
newclient = 1;
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (client == NULL) {
mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_mutex);
client = synths[card->number];
if (client == NULL || client->ports[device] == NULL) {
mutex_unlock(&register_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(&register_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)

View 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);

View 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
View 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
View 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
View 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;
}
}

View 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
View 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
View 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
View 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);
}
}

View 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
View 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
View 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

View 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);

165
sound/core/sgbuf.c Normal file
View file

@ -0,0 +1,165 @@
/*
* Scatter-Gather buffer
*
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/export.h>
#include <sound/memalloc.h>
/* table entries are align to 32 */
#define SGBUF_TBL_ALIGN 32
#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN)
int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
{
struct snd_sg_buf *sgbuf = dmab->private_data;
struct snd_dma_buffer tmpb;
int i;
if (! sgbuf)
return -EINVAL;
if (dmab->area)
vunmap(dmab->area);
dmab->area = NULL;
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
tmpb.dev.dev = sgbuf->dev;
for (i = 0; i < sgbuf->pages; i++) {
if (!(sgbuf->table[i].addr & ~PAGE_MASK))
continue; /* continuous pages */
tmpb.area = sgbuf->table[i].buf;
tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
snd_dma_free_pages(&tmpb);
}
kfree(sgbuf->table);
kfree(sgbuf->page_table);
kfree(sgbuf);
dmab->private_data = NULL;
return 0;
}
#define MAX_ALLOC_PAGES 32
void *snd_malloc_sgbuf_pages(struct device *device,
size_t size, struct snd_dma_buffer *dmab,
size_t *res_size)
{
struct snd_sg_buf *sgbuf;
unsigned int i, pages, chunk, maxpages;
struct snd_dma_buffer tmpb;
struct snd_sg_page *table;
struct page **pgtable;
dmab->area = NULL;
dmab->addr = 0;
dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
if (! sgbuf)
return NULL;
sgbuf->dev = device;
pages = snd_sgbuf_aligned_pages(size);
sgbuf->tblsize = sgbuf_align_table(pages);
table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
if (!table)
goto _failed;
sgbuf->table = table;
pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
if (!pgtable)
goto _failed;
sgbuf->page_table = pgtable;
/* allocate pages */
maxpages = MAX_ALLOC_PAGES;
while (pages > 0) {
chunk = pages;
/* don't be too eager to take a huge chunk */
if (chunk > maxpages)
chunk = maxpages;
chunk <<= PAGE_SHIFT;
if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device,
chunk, &tmpb) < 0) {
if (!sgbuf->pages)
goto _failed;
if (!res_size)
goto _failed;
size = sgbuf->pages * PAGE_SIZE;
break;
}
chunk = tmpb.bytes >> PAGE_SHIFT;
for (i = 0; i < chunk; i++) {
table->buf = tmpb.area;
table->addr = tmpb.addr;
if (!i)
table->addr |= chunk; /* mark head */
table++;
*pgtable++ = virt_to_page(tmpb.area);
tmpb.area += PAGE_SIZE;
tmpb.addr += PAGE_SIZE;
}
sgbuf->pages += chunk;
pages -= chunk;
if (chunk < maxpages)
maxpages = chunk;
}
sgbuf->size = size;
dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
if (! dmab->area)
goto _failed;
if (res_size)
*res_size = sgbuf->size;
return dmab->area;
_failed:
snd_free_sgbuf_pages(dmab); /* free the table */
return NULL;
}
/*
* compute the max chunk size with continuous pages on sg-buffer
*/
unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
unsigned int ofs, unsigned int size)
{
struct snd_sg_buf *sg = dmab->private_data;
unsigned int start, end, pg;
start = ofs >> PAGE_SHIFT;
end = (ofs + size - 1) >> PAGE_SHIFT;
/* check page continuity */
pg = sg->table[start].addr >> PAGE_SHIFT;
for (;;) {
start++;
if (start > end)
break;
pg++;
if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
return (start << PAGE_SHIFT) - ofs;
}
/* ok, all on continuous pages */
return size;
}
EXPORT_SYMBOL(snd_sgbuf_get_chunk_size);

486
sound/core/sound.c Normal file
View file

@ -0,0 +1,486 @@
/*
* Advanced Linux Sound Architecture
* Copyright (c) by 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/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <linux/kmod.h>
#include <linux/mutex.h>
static int major = CONFIG_SND_MAJOR;
int snd_major;
EXPORT_SYMBOL(snd_major);
static int cards_limit = 1;
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards.");
MODULE_LICENSE("GPL");
module_param(major, int, 0444);
MODULE_PARM_DESC(major, "Major # for sound driver.");
module_param(cards_limit, int, 0444);
MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards.");
MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR);
/* this one holds the actual max. card number currently available.
* as default, it's identical with cards_limit option. when more
* modules are loaded manually, this limit number increases, too.
*/
int snd_ecards_limit;
EXPORT_SYMBOL(snd_ecards_limit);
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
static DEFINE_MUTEX(sound_mutex);
#ifdef CONFIG_MODULES
/**
* snd_request_card - try to load the card module
* @card: the card number
*
* Tries to load the module "snd-card-X" for the given card number
* via request_module. Returns immediately if already loaded.
*/
void snd_request_card(int card)
{
if (snd_card_locked(card))
return;
if (card < 0 || card >= cards_limit)
return;
request_module("snd-card-%i", card);
}
EXPORT_SYMBOL(snd_request_card);
static void snd_request_other(int minor)
{
char *str;
switch (minor) {
case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break;
case SNDRV_MINOR_TIMER: str = "snd-timer"; break;
default: return;
}
request_module(str);
}
#endif /* modular kernel */
/**
* snd_lookup_minor_data - get user data of a registered device
* @minor: the minor number
* @type: device type (SNDRV_DEVICE_TYPE_XXX)
*
* Checks that a minor device with the specified type is registered, and returns
* its user data pointer.
*
* This function increments the reference counter of the card instance
* if an associated instance with the given minor number and type is found.
* The caller must call snd_card_unref() appropriately later.
*
* Return: The user data pointer if the specified device is found. %NULL
* otherwise.
*/
void *snd_lookup_minor_data(unsigned int minor, int type)
{
struct snd_minor *mreg;
void *private_data;
if (minor >= ARRAY_SIZE(snd_minors))
return NULL;
mutex_lock(&sound_mutex);
mreg = snd_minors[minor];
if (mreg && mreg->type == type) {
private_data = mreg->private_data;
if (private_data && mreg->card_ptr)
get_device(&mreg->card_ptr->card_dev);
} else
private_data = NULL;
mutex_unlock(&sound_mutex);
return private_data;
}
EXPORT_SYMBOL(snd_lookup_minor_data);
#ifdef CONFIG_MODULES
static struct snd_minor *autoload_device(unsigned int minor)
{
int dev;
mutex_unlock(&sound_mutex); /* release lock temporarily */
dev = SNDRV_MINOR_DEVICE(minor);
if (dev == SNDRV_MINOR_CONTROL) {
/* /dev/aloadC? */
int card = SNDRV_MINOR_CARD(minor);
if (snd_cards[card] == NULL)
snd_request_card(card);
} else if (dev == SNDRV_MINOR_GLOBAL) {
/* /dev/aloadSEQ */
snd_request_other(minor);
}
mutex_lock(&sound_mutex); /* reacuire lock */
return snd_minors[minor];
}
#else /* !CONFIG_MODULES */
#define autoload_device(minor) NULL
#endif /* CONFIG_MODULES */
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor];
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
new_fops = fops_get(mptr->f_ops);
mutex_unlock(&sound_mutex);
if (!new_fops)
return -ENODEV;
replace_fops(file, new_fops);
if (file->f_op->open)
err = file->f_op->open(inode, file);
return err;
}
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
#ifdef CONFIG_SND_DYNAMIC_MINORS
static int snd_find_free_minor(int type)
{
int minor;
/* static minors for module auto loading */
if (type == SNDRV_DEVICE_TYPE_SEQUENCER)
return SNDRV_MINOR_SEQUENCER;
if (type == SNDRV_DEVICE_TYPE_TIMER)
return SNDRV_MINOR_TIMER;
for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) {
/* skip static minors still used for module auto loading */
if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL)
continue;
if (minor == SNDRV_MINOR_SEQUENCER ||
minor == SNDRV_MINOR_TIMER)
continue;
if (!snd_minors[minor])
return minor;
}
return -EBUSY;
}
#else
static int snd_kernel_minor(int type, struct snd_card *card, int dev)
{
int minor;
switch (type) {
case SNDRV_DEVICE_TYPE_SEQUENCER:
case SNDRV_DEVICE_TYPE_TIMER:
minor = type;
break;
case SNDRV_DEVICE_TYPE_CONTROL:
if (snd_BUG_ON(!card))
return -EINVAL;
minor = SNDRV_MINOR(card->number, type);
break;
case SNDRV_DEVICE_TYPE_HWDEP:
case SNDRV_DEVICE_TYPE_RAWMIDI:
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
case SNDRV_DEVICE_TYPE_COMPRESS:
if (snd_BUG_ON(!card))
return -EINVAL;
minor = SNDRV_MINOR(card->number, type + dev);
break;
default:
return -EINVAL;
}
if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS))
return -EINVAL;
return minor;
}
#endif
/**
* snd_register_device_for_dev - Register the ALSA device file for the card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
* @f_ops: the file operations
* @private_data: user pointer for f_ops->open()
* @name: the device file name
* @device: the &struct device to link this new device to
*
* Registers an ALSA device file for the given card.
* The operators have to be set in reg parameter.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
if (snd_BUG_ON(!name))
return -EINVAL;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
minor = snd_find_free_minor(type);
#else
minor = snd_kernel_minor(type, card, dev);
if (minor >= 0 && snd_minors[minor])
minor = -EBUSY;
#endif
if (minor < 0) {
mutex_unlock(&sound_mutex);
kfree(preg);
return minor;
}
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
if (IS_ERR(preg->dev)) {
snd_minors[minor] = NULL;
mutex_unlock(&sound_mutex);
minor = PTR_ERR(preg->dev);
kfree(preg);
return minor;
}
mutex_unlock(&sound_mutex);
return 0;
}
EXPORT_SYMBOL(snd_register_device_for_dev);
/* find the matching minor record
* return the index of snd_minor, or -1 if not found
*/
static int find_snd_minor(int type, struct snd_card *card, int dev)
{
int cardnum, minor;
struct snd_minor *mptr;
cardnum = card ? card->number : -1;
for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor)
if ((mptr = snd_minors[minor]) != NULL &&
mptr->type == type &&
mptr->card == cardnum &&
mptr->device == dev)
return minor;
return -1;
}
/**
* snd_unregister_device - unregister the device on the given card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
*
* Unregisters the device file already registered via
* snd_register_device().
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_unregister_device(int type, struct snd_card *card, int dev)
{
int minor;
mutex_lock(&sound_mutex);
minor = find_snd_minor(type, card, dev);
if (minor < 0) {
mutex_unlock(&sound_mutex);
return -EINVAL;
}
device_destroy(sound_class, MKDEV(major, minor));
kfree(snd_minors[minor]);
snd_minors[minor] = NULL;
mutex_unlock(&sound_mutex);
return 0;
}
EXPORT_SYMBOL(snd_unregister_device);
/* get the assigned device to the given type and device number;
* the caller needs to release it via put_device() after using it
*/
struct device *snd_get_device(int type, struct snd_card *card, int dev)
{
int minor;
struct device *d = NULL;
mutex_lock(&sound_mutex);
minor = find_snd_minor(type, card, dev);
if (minor >= 0) {
d = snd_minors[minor]->dev;
if (d)
get_device(d);
}
mutex_unlock(&sound_mutex);
return d;
}
EXPORT_SYMBOL(snd_get_device);
#ifdef CONFIG_PROC_FS
/*
* INFO PART
*/
static struct snd_info_entry *snd_minor_info_entry;
static const char *snd_device_type_name(int type)
{
switch (type) {
case SNDRV_DEVICE_TYPE_CONTROL:
return "control";
case SNDRV_DEVICE_TYPE_HWDEP:
return "hardware dependent";
case SNDRV_DEVICE_TYPE_RAWMIDI:
return "raw midi";
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
return "digital audio playback";
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
return "digital audio capture";
case SNDRV_DEVICE_TYPE_SEQUENCER:
return "sequencer";
case SNDRV_DEVICE_TYPE_TIMER:
return "timer";
default:
return "?";
}
}
static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
int minor;
struct snd_minor *mptr;
mutex_lock(&sound_mutex);
for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) {
if (!(mptr = snd_minors[minor]))
continue;
if (mptr->card >= 0) {
if (mptr->device >= 0)
snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n",
minor, mptr->card, mptr->device,
snd_device_type_name(mptr->type));
else
snd_iprintf(buffer, "%3i: [%2i] : %s\n",
minor, mptr->card,
snd_device_type_name(mptr->type));
} else
snd_iprintf(buffer, "%3i: : %s\n", minor,
snd_device_type_name(mptr->type));
}
mutex_unlock(&sound_mutex);
}
int __init snd_minor_info_init(void)
{
struct snd_info_entry *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
if (entry) {
entry->c.text.read = snd_minor_info_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_minor_info_entry = entry;
return 0;
}
int __exit snd_minor_info_done(void)
{
snd_info_free_entry(snd_minor_info_entry);
return 0;
}
#endif /* CONFIG_PROC_FS */
/*
* INIT PART
*/
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
#ifndef MODULE
pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
return 0;
}
static void __exit alsa_sound_exit(void)
{
snd_info_minor_unregister();
snd_info_done();
unregister_chrdev(major, "alsa");
}
subsys_initcall(alsa_sound_init);
module_exit(alsa_sound_exit);

284
sound/core/sound_oss.c Normal file
View file

@ -0,0 +1,284 @@
/*
* Advanced Linux Sound Architecture
* Copyright (c) by 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
*
*/
#ifdef CONFIG_SND_OSSEMUL
#if !IS_ENABLED(CONFIG_SOUND)
#error "Enable the OSS soundcore multiplexer (CONFIG_SOUND) in the kernel."
#endif
#include <linux/init.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/info.h>
#include <linux/sound.h>
#include <linux/mutex.h>
#define SNDRV_OSS_MINORS 256
static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS];
static DEFINE_MUTEX(sound_oss_mutex);
/* NOTE: This function increments the refcount of the associated card like
* snd_lookup_minor_data(); the caller must call snd_card_unref() appropriately
*/
void *snd_lookup_oss_minor_data(unsigned int minor, int type)
{
struct snd_minor *mreg;
void *private_data;
if (minor >= ARRAY_SIZE(snd_oss_minors))
return NULL;
mutex_lock(&sound_oss_mutex);
mreg = snd_oss_minors[minor];
if (mreg && mreg->type == type) {
private_data = mreg->private_data;
if (private_data && mreg->card_ptr)
get_device(&mreg->card_ptr->card_dev);
} else
private_data = NULL;
mutex_unlock(&sound_oss_mutex);
return private_data;
}
EXPORT_SYMBOL(snd_lookup_oss_minor_data);
static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev)
{
int minor;
switch (type) {
case SNDRV_OSS_DEVICE_TYPE_MIXER:
if (snd_BUG_ON(!card || dev < 0 || dev > 1))
return -EINVAL;
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER));
break;
case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
minor = SNDRV_MINOR_OSS_SEQUENCER;
break;
case SNDRV_OSS_DEVICE_TYPE_MUSIC:
minor = SNDRV_MINOR_OSS_MUSIC;
break;
case SNDRV_OSS_DEVICE_TYPE_PCM:
if (snd_BUG_ON(!card || dev < 0 || dev > 1))
return -EINVAL;
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM));
break;
case SNDRV_OSS_DEVICE_TYPE_MIDI:
if (snd_BUG_ON(!card || dev < 0 || dev > 1))
return -EINVAL;
minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI));
break;
case SNDRV_OSS_DEVICE_TYPE_DMFM:
minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM);
break;
case SNDRV_OSS_DEVICE_TYPE_SNDSTAT:
minor = SNDRV_MINOR_OSS_SNDSTAT;
break;
default:
return -EINVAL;
}
if (minor < 0 || minor >= SNDRV_OSS_MINORS)
return -EINVAL;
return minor;
}
int snd_register_oss_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops, void *private_data)
{
int minor = snd_oss_kernel_minor(type, card, dev);
int minor_unit;
struct snd_minor *preg;
int cidx = SNDRV_MINOR_OSS_CARD(minor);
int track2 = -1;
int register1 = -1, register2 = -1;
struct device *carddev = snd_card_get_device_link(card);
if (card && card->number >= SNDRV_MINOR_OSS_DEVICES)
return 0; /* ignore silently */
if (minor < 0)
return minor;
preg = kmalloc(sizeof(struct snd_minor), GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_oss_mutex);
snd_oss_minors[minor] = preg;
minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);
switch (minor_unit) {
case SNDRV_MINOR_OSS_PCM:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
break;
case SNDRV_MINOR_OSS_MIDI:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
break;
case SNDRV_MINOR_OSS_MIDI1:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
break;
}
register1 = register_sound_special_device(f_ops, minor, carddev);
if (register1 != minor)
goto __end;
if (track2 >= 0) {
register2 = register_sound_special_device(f_ops, track2,
carddev);
if (register2 != track2)
goto __end;
snd_oss_minors[track2] = preg;
}
mutex_unlock(&sound_oss_mutex);
return 0;
__end:
if (register2 >= 0)
unregister_sound_special(register2);
if (register1 >= 0)
unregister_sound_special(register1);
snd_oss_minors[minor] = NULL;
mutex_unlock(&sound_oss_mutex);
kfree(preg);
return -EBUSY;
}
EXPORT_SYMBOL(snd_register_oss_device);
int snd_unregister_oss_device(int type, struct snd_card *card, int dev)
{
int minor = snd_oss_kernel_minor(type, card, dev);
int cidx = SNDRV_MINOR_OSS_CARD(minor);
int track2 = -1;
struct snd_minor *mptr;
if (card && card->number >= SNDRV_MINOR_OSS_DEVICES)
return 0;
if (minor < 0)
return minor;
mutex_lock(&sound_oss_mutex);
mptr = snd_oss_minors[minor];
if (mptr == NULL) {
mutex_unlock(&sound_oss_mutex);
return -ENOENT;
}
unregister_sound_special(minor);
switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
case SNDRV_MINOR_OSS_PCM:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO);
break;
case SNDRV_MINOR_OSS_MIDI:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI);
break;
case SNDRV_MINOR_OSS_MIDI1:
track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1);
break;
}
if (track2 >= 0) {
unregister_sound_special(track2);
snd_oss_minors[track2] = NULL;
}
snd_oss_minors[minor] = NULL;
mutex_unlock(&sound_oss_mutex);
kfree(mptr);
return 0;
}
EXPORT_SYMBOL(snd_unregister_oss_device);
/*
* INFO PART
*/
#ifdef CONFIG_PROC_FS
static struct snd_info_entry *snd_minor_info_oss_entry;
static const char *snd_oss_device_type_name(int type)
{
switch (type) {
case SNDRV_OSS_DEVICE_TYPE_MIXER:
return "mixer";
case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
case SNDRV_OSS_DEVICE_TYPE_MUSIC:
return "sequencer";
case SNDRV_OSS_DEVICE_TYPE_PCM:
return "digital audio";
case SNDRV_OSS_DEVICE_TYPE_MIDI:
return "raw midi";
case SNDRV_OSS_DEVICE_TYPE_DMFM:
return "hardware dependent";
default:
return "?";
}
}
static void snd_minor_info_oss_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int minor;
struct snd_minor *mptr;
mutex_lock(&sound_oss_mutex);
for (minor = 0; minor < SNDRV_OSS_MINORS; ++minor) {
if (!(mptr = snd_oss_minors[minor]))
continue;
if (mptr->card >= 0)
snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", minor,
mptr->card, mptr->device,
snd_oss_device_type_name(mptr->type));
else
snd_iprintf(buffer, "%3i: : %s\n", minor,
snd_oss_device_type_name(mptr->type));
}
mutex_unlock(&sound_oss_mutex);
}
int __init snd_minor_info_oss_init(void)
{
struct snd_info_entry *entry;
entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root);
if (entry) {
entry->c.text.read = snd_minor_info_oss_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_minor_info_oss_entry = entry;
return 0;
}
int __exit snd_minor_info_oss_done(void)
{
snd_info_free_entry(snd_minor_info_oss_entry);
return 0;
}
#endif /* CONFIG_PROC_FS */
#endif /* CONFIG_SND_OSSEMUL */

2016
sound/core/timer.c Normal file

File diff suppressed because it is too large Load diff

127
sound/core/timer_compat.c Normal file
View file

@ -0,0 +1,127 @@
/*
* 32bit -> 64bit ioctl wrapper for timer API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* This file included from timer.c */
#include <linux/compat.h>
struct snd_timer_info32 {
u32 flags;
s32 card;
unsigned char id[64];
unsigned char name[80];
u32 reserved0;
u32 resolution;
unsigned char reserved[64];
};
static int snd_timer_user_info_compat(struct file *file,
struct snd_timer_info32 __user *_info)
{
struct snd_timer_user *tu;
struct snd_timer_info32 info;
struct snd_timer *t;
tu = file->private_data;
if (snd_BUG_ON(!tu->timeri))
return -ENXIO;
t = tu->timeri->timer;
if (snd_BUG_ON(!t))
return -ENXIO;
memset(&info, 0, sizeof(info));
info.card = t->card ? t->card->number : -1;
if (t->hw.flags & SNDRV_TIMER_HW_SLAVE)
info.flags |= SNDRV_TIMER_FLG_SLAVE;
strlcpy(info.id, t->id, sizeof(info.id));
strlcpy(info.name, t->name, sizeof(info.name));
info.resolution = t->hw.resolution;
if (copy_to_user(_info, &info, sizeof(*_info)))
return -EFAULT;
return 0;
}
struct snd_timer_status32 {
struct compat_timespec tstamp;
u32 resolution;
u32 lost;
u32 overrun;
u32 queue;
unsigned char reserved[64];
};
static int snd_timer_user_status_compat(struct file *file,
struct snd_timer_status32 __user *_status)
{
struct snd_timer_user *tu;
struct snd_timer_status status;
tu = file->private_data;
if (snd_BUG_ON(!tu->timeri))
return -ENXIO;
memset(&status, 0, sizeof(status));
status.tstamp = tu->tstamp;
status.resolution = snd_timer_resolution(tu->timeri);
status.lost = tu->timeri->lost;
status.overrun = tu->overrun;
spin_lock_irq(&tu->qlock);
status.queue = tu->qused;
spin_unlock_irq(&tu->qlock);
if (copy_to_user(_status, &status, sizeof(status)))
return -EFAULT;
return 0;
}
/*
*/
enum {
SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct snd_timer_info32),
SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct snd_timer_status32),
};
static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = compat_ptr(arg);
switch (cmd) {
case SNDRV_TIMER_IOCTL_PVERSION:
case SNDRV_TIMER_IOCTL_TREAD:
case SNDRV_TIMER_IOCTL_GINFO:
case SNDRV_TIMER_IOCTL_GPARAMS:
case SNDRV_TIMER_IOCTL_GSTATUS:
case SNDRV_TIMER_IOCTL_SELECT:
case SNDRV_TIMER_IOCTL_PARAMS:
case SNDRV_TIMER_IOCTL_START:
case SNDRV_TIMER_IOCTL_START_OLD:
case SNDRV_TIMER_IOCTL_STOP:
case SNDRV_TIMER_IOCTL_STOP_OLD:
case SNDRV_TIMER_IOCTL_CONTINUE:
case SNDRV_TIMER_IOCTL_CONTINUE_OLD:
case SNDRV_TIMER_IOCTL_PAUSE:
case SNDRV_TIMER_IOCTL_PAUSE_OLD:
case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
return snd_timer_user_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_TIMER_IOCTL_INFO32:
return snd_timer_user_info_compat(file, argp);
case SNDRV_TIMER_IOCTL_STATUS32:
return snd_timer_user_status_compat(file, argp);
}
return -ENOIOCTLCMD;
}

486
sound/core/vmaster.c Normal file
View file

@ -0,0 +1,486 @@
/*
* Virtual master and slave controls
*
* Copyright (c) 2008 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, version 2.
*
*/
#include <linux/slab.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
/*
* a subset of information returned via ctl info callback
*/
struct link_ctl_info {
snd_ctl_elem_type_t type; /* value type */
int count; /* item count */
int min_val, max_val; /* min, max values */
};
/*
* link master - this contains a list of slave controls that are
* identical types, i.e. info returns the same value type and value
* ranges, but may have different number of counts.
*
* The master control is so far only mono volume/switch for simplicity.
* The same value will be applied to all slaves.
*/
struct link_master {
struct list_head slaves;
struct link_ctl_info info;
int val; /* the master value */
unsigned int tlv[4];
void (*hook)(void *private_data, int);
void *hook_private_data;
};
/*
* link slave - this contains a slave control element
*
* It fakes the control callbacsk with additional attenuation by the
* master control. A slave may have either one or two channels.
*/
struct link_slave {
struct list_head list;
struct link_master *master;
struct link_ctl_info info;
int vals[2]; /* current values */
unsigned int flags;
struct snd_kcontrol *kctl; /* original kcontrol pointer */
struct snd_kcontrol slave; /* the copy of original control entry */
};
static int slave_update(struct link_slave *slave)
{
struct snd_ctl_elem_value *uctl;
int err, ch;
uctl = kmalloc(sizeof(*uctl), GFP_KERNEL);
if (!uctl)
return -ENOMEM;
uctl->id = slave->slave.id;
err = slave->slave.get(&slave->slave, uctl);
for (ch = 0; ch < slave->info.count; ch++)
slave->vals[ch] = uctl->value.integer.value[ch];
kfree(uctl);
return 0;
}
/* get the slave ctl info and save the initial values */
static int slave_init(struct link_slave *slave)
{
struct snd_ctl_elem_info *uinfo;
int err;
if (slave->info.count) {
/* already initialized */
if (slave->flags & SND_CTL_SLAVE_NEED_UPDATE)
return slave_update(slave);
return 0;
}
uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
if (!uinfo)
return -ENOMEM;
uinfo->id = slave->slave.id;
err = slave->slave.info(&slave->slave, uinfo);
if (err < 0) {
kfree(uinfo);
return err;
}
slave->info.type = uinfo->type;
slave->info.count = uinfo->count;
if (slave->info.count > 2 ||
(slave->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER &&
slave->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) {
pr_err("ALSA: vmaster: invalid slave element\n");
kfree(uinfo);
return -EINVAL;
}
slave->info.min_val = uinfo->value.integer.min;
slave->info.max_val = uinfo->value.integer.max;
kfree(uinfo);
return slave_update(slave);
}
/* initialize master volume */
static int master_init(struct link_master *master)
{
struct link_slave *slave;
if (master->info.count)
return 0; /* already initialized */
list_for_each_entry(slave, &master->slaves, list) {
int err = slave_init(slave);
if (err < 0)
return err;
master->info = slave->info;
master->info.count = 1; /* always mono */
/* set full volume as default (= no attenuation) */
master->val = master->info.max_val;
if (master->hook)
master->hook(master->hook_private_data, master->val);
return 1;
}
return -ENOENT;
}
static int slave_get_val(struct link_slave *slave,
struct snd_ctl_elem_value *ucontrol)
{
int err, ch;
err = slave_init(slave);
if (err < 0)
return err;
for (ch = 0; ch < slave->info.count; ch++)
ucontrol->value.integer.value[ch] = slave->vals[ch];
return 0;
}
static int slave_put_val(struct link_slave *slave,
struct snd_ctl_elem_value *ucontrol)
{
int err, ch, vol;
err = master_init(slave->master);
if (err < 0)
return err;
switch (slave->info.type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
for (ch = 0; ch < slave->info.count; ch++)
ucontrol->value.integer.value[ch] &=
!!slave->master->val;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER:
for (ch = 0; ch < slave->info.count; ch++) {
/* max master volume is supposed to be 0 dB */
vol = ucontrol->value.integer.value[ch];
vol += slave->master->val - slave->master->info.max_val;
if (vol < slave->info.min_val)
vol = slave->info.min_val;
else if (vol > slave->info.max_val)
vol = slave->info.max_val;
ucontrol->value.integer.value[ch] = vol;
}
break;
}
return slave->slave.put(&slave->slave, ucontrol);
}
/*
* ctl callbacks for slaves
*/
static int slave_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct link_slave *slave = snd_kcontrol_chip(kcontrol);
return slave->slave.info(&slave->slave, uinfo);
}
static int slave_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct link_slave *slave = snd_kcontrol_chip(kcontrol);
return slave_get_val(slave, ucontrol);
}
static int slave_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct link_slave *slave = snd_kcontrol_chip(kcontrol);
int err, ch, changed = 0;
err = slave_init(slave);
if (err < 0)
return err;
for (ch = 0; ch < slave->info.count; ch++) {
if (slave->vals[ch] != ucontrol->value.integer.value[ch]) {
changed = 1;
slave->vals[ch] = ucontrol->value.integer.value[ch];
}
}
if (!changed)
return 0;
err = slave_put_val(slave, ucontrol);
if (err < 0)
return err;
return 1;
}
static int slave_tlv_cmd(struct snd_kcontrol *kcontrol,
int op_flag, unsigned int size,
unsigned int __user *tlv)
{
struct link_slave *slave = snd_kcontrol_chip(kcontrol);
/* FIXME: this assumes that the max volume is 0 dB */
return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv);
}
static void slave_free(struct snd_kcontrol *kcontrol)
{
struct link_slave *slave = snd_kcontrol_chip(kcontrol);
if (slave->slave.private_free)
slave->slave.private_free(&slave->slave);
if (slave->master)
list_del(&slave->list);
kfree(slave);
}
/*
* Add a slave control to the group with the given master control
*
* All slaves must be the same type (returning the same information
* via info callback). The function doesn't check it, so it's your
* responsibility.
*
* Also, some additional limitations:
* - at most two channels
* - logarithmic volume control (dB level), no linear volume
* - master can only attenuate the volume, no gain
*/
int _snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave,
unsigned int flags)
{
struct link_master *master_link = snd_kcontrol_chip(master);
struct link_slave *srec;
srec = kzalloc(sizeof(*srec) +
slave->count * sizeof(*slave->vd), GFP_KERNEL);
if (!srec)
return -ENOMEM;
srec->kctl = slave;
srec->slave = *slave;
memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd));
srec->master = master_link;
srec->flags = flags;
/* override callbacks */
slave->info = slave_info;
slave->get = slave_get;
slave->put = slave_put;
if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)
slave->tlv.c = slave_tlv_cmd;
slave->private_data = srec;
slave->private_free = slave_free;
list_add_tail(&srec->list, &master_link->slaves);
return 0;
}
EXPORT_SYMBOL(_snd_ctl_add_slave);
/*
* ctl callbacks for master controls
*/
static int master_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
int ret;
ret = master_init(master);
if (ret < 0)
return ret;
uinfo->type = master->info.type;
uinfo->count = master->info.count;
uinfo->value.integer.min = master->info.min_val;
uinfo->value.integer.max = master->info.max_val;
return 0;
}
static int master_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
int err = master_init(master);
if (err < 0)
return err;
ucontrol->value.integer.value[0] = master->val;
return 0;
}
static int sync_slaves(struct link_master *master, int old_val, int new_val)
{
struct link_slave *slave;
struct snd_ctl_elem_value *uval;
uval = kmalloc(sizeof(*uval), GFP_KERNEL);
if (!uval)
return -ENOMEM;
list_for_each_entry(slave, &master->slaves, list) {
master->val = old_val;
uval->id = slave->slave.id;
slave_get_val(slave, uval);
master->val = new_val;
slave_put_val(slave, uval);
}
kfree(uval);
return 0;
}
static int master_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
int err, new_val, old_val;
bool first_init;
err = master_init(master);
if (err < 0)
return err;
first_init = err;
old_val = master->val;
new_val = ucontrol->value.integer.value[0];
if (new_val == old_val)
return 0;
err = sync_slaves(master, old_val, new_val);
if (err < 0)
return err;
if (master->hook && !first_init)
master->hook(master->hook_private_data, master->val);
return 1;
}
static void master_free(struct snd_kcontrol *kcontrol)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
struct link_slave *slave, *n;
/* free all slave links and retore the original slave kctls */
list_for_each_entry_safe(slave, n, &master->slaves, list) {
struct snd_kcontrol *sctl = slave->kctl;
struct list_head olist = sctl->list;
memcpy(sctl, &slave->slave, sizeof(*sctl));
memcpy(sctl->vd, slave->slave.vd,
sctl->count * sizeof(*sctl->vd));
sctl->list = olist; /* keep the current linked-list */
kfree(slave);
}
kfree(master);
}
/**
* snd_ctl_make_virtual_master - Create a virtual master control
* @name: name string of the control element to create
* @tlv: optional TLV int array for dB information
*
* Creates a virtual master control with the given name string.
*
* After creating a vmaster element, you can add the slave controls
* via snd_ctl_add_slave() or snd_ctl_add_slave_uncached().
*
* The optional argument @tlv can be used to specify the TLV information
* for dB scale of the master control. It should be a single element
* with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or
* #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB.
*
* Return: The created control element, or %NULL for errors (ENOMEM).
*/
struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
const unsigned int *tlv)
{
struct link_master *master;
struct snd_kcontrol *kctl;
struct snd_kcontrol_new knew;
memset(&knew, 0, sizeof(knew));
knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
knew.name = name;
knew.info = master_info;
master = kzalloc(sizeof(*master), GFP_KERNEL);
if (!master)
return NULL;
INIT_LIST_HEAD(&master->slaves);
kctl = snd_ctl_new1(&knew, master);
if (!kctl) {
kfree(master);
return NULL;
}
/* override some callbacks */
kctl->info = master_info;
kctl->get = master_get;
kctl->put = master_put;
kctl->private_free = master_free;
/* additional (constant) TLV read */
if (tlv &&
(tlv[0] == SNDRV_CTL_TLVT_DB_SCALE ||
tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX ||
tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX_MUTE)) {
kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
memcpy(master->tlv, tlv, sizeof(master->tlv));
kctl->tlv.p = master->tlv;
}
return kctl;
}
EXPORT_SYMBOL(snd_ctl_make_virtual_master);
/**
* snd_ctl_add_vmaster_hook - Add a hook to a vmaster control
* @kcontrol: vmaster kctl element
* @hook: the hook function
* @private_data: the private_data pointer to be saved
*
* Adds the given hook to the vmaster control element so that it's called
* at each time when the value is changed.
*
* Return: Zero.
*/
int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol,
void (*hook)(void *private_data, int),
void *private_data)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
master->hook = hook;
master->hook_private_data = private_data;
return 0;
}
EXPORT_SYMBOL_GPL(snd_ctl_add_vmaster_hook);
/**
* snd_ctl_sync_vmaster - Sync the vmaster slaves and hook
* @kcontrol: vmaster kctl element
* @hook_only: sync only the hook
*
* Forcibly call the put callback of each slave and call the hook function
* to synchronize with the current value of the given vmaster element.
* NOP when NULL is passed to @kcontrol.
*/
void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only)
{
struct link_master *master;
bool first_init = false;
if (!kcontrol)
return;
master = snd_kcontrol_chip(kcontrol);
if (!hook_only) {
int err = master_init(master);
if (err < 0)
return;
first_init = err;
err = sync_slaves(master, master->val, master->val);
if (err < 0)
return;
}
if (master->hook && !first_init)
master->hook(master->hook_private_data, master->val);
}
EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster);